2012年8月5日日曜日

[ExcelVBA] WindowsAPIについて


WindowsAPIを触るにあたって最初に知っておくと少しは楽になるのではないかということをまとめてみました。

■APIとは
あるプラットフォーム(OSやミドルウェア)向けのソフトウェアを開発する際に使用できる命令や関数の集合のこと。【IT用語辞典 e-Wordsより
Excelで言うのであれば、Excelを操作するために、RangeやCellsと言った関数を提供している。
それらを使うことでやりたいことを実現できるわけ。



■WindowsAPIとは
今のを踏まえるとWindowを操作するために、BringWindowToTopやGetClassNameと言った関数を提供しているということになる。では、Windowsって一言で言っているけど何が操作できるのか?ということになるが、日々何気なく操作していることができるようになります。
例えば、マウスであるアプリをクリックすることで最前面に表示したり、そのアプリを移動したりサイズを変更したりといったこと。また、ウィンドウを閉じたりなんてこともWindowsAPIを利用して行うことができます。



■そもそも…
今でこそ、VisualBasicのような言語があるので、何の疑問もなくウィンドウの生成やボタン配置を行なっていますがそれ以前の言語ではどうしてたのでしょうか?
例えばC言語。
マウス操作ぽちぽちでは、ウィンドウを生成できないので、WindowsAPIを利用して簡単なウィンドウを表示するだけでも長いコードを書いていたわけです。それが不便なのでもっと簡単にとVisualBasicのような言語が誕生したわけなので、ExcelVBAからWindowsAPIを触るのはある意味逆行しているようにも見えます。
とはいえ、VisualBasicの誕生で直接WindowsAPIを触る手間が減ったとはいえ、全てのAPIが網羅されているわけではないので、凝ったことを行おうとするとWindowsAPIも触ることになるという感じでしょうか・・・。



■ハンドルとは
WindowsAPIを扱うようになってよく見かけるものの一つに“ハンドル”があります。
ハンドルとは、ウィンドウハンドルとも呼ばれ、ウィンドウやそのウィンドウが管理しているボタン等のオブジェクト全てに付けられた番号のことを指しています。この番号は、立ち上げるたびに重複のない番号がつけられることになっています。
例えば、電卓を立ち上げます。
そして、以下のコードを実行します。
Option Explicit

Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" _
  (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
  
Sub SampleCode()
    Debug.Print FindWindow("SciCalc", "電卓")
End Sub

そうすると、今立ち上げた電卓には、2622408という番号がつけられていたということがわかりました。
この番号は、立ち上げるたびに変わる番号なので、皆さんが実行した際には重複することのない番号がつけられています。
そして、この電卓を閉じたい場合は、この番号に対し閉じなさい!
というメッセージを送ることになります。
Option Explicit

Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" _
  (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" _
        (ByVal hwnd As Long, ByVal msg As Long, _
         ByVal wParam As Long, ByVal lParam As Long) As Long
Const WM_CLOSE = &H10

Sub SampleCode()
    Dim hwnd As Long, Ret As Long
    '操作したいウィンドウのハンドルを調べる
    hwnd = FindWindow("SciCalc", "電卓") 
    
    '調べたハンドルに対し閉じるメッセージを送る
    Ret = SendMessage(hwnd, WM_CLOSE, 0, 0) 
End Sub
このように、ハンドルというものを通して、操作を行ったりしていきます。


また、全てのハンドルは同一階層に存在しているわけでなく、階層上になっています。
各ウィンドウの一番上のハンドルは、トップレベルウィンドウに所属していることになっています。
ひとまず、階層上に管理されているんだ。と認識していればよいと思います。



■関数について
APIを使う前にモジュールの先頭部分で宣言をする必要があります。
例えば…FindWindowという関数を使いたい場合は以下のようにです。
Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" _
    (ByVal lpClassName As String, ByVal lpWindowName As String) As Integer

宣言について補足しておきます。
・Declare - お決まりの単語です。変数を宣言する際にDimをつけるのと同じくAPIを利用する際にDeclareから始まります。

・Sub or Function - 値を返さなければSub。値を返せばFunction。

・関数名 - ここではFindWindow

・Lib "***.dll" - 利用したい関数が収められているDLL名を指定。関数名とDLL名はセットになりますね。

・Alias "****" - エイリアスつまり別名です。別名だからって自由に名前つけていいわけじゃありません。
別名なのは、関数名の方が別名なんです…

例えば、電卓のハンドルを取得使用とした場合
ゆく見かける定義をそのまま使うと
Option Explicit

Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" _
    (ByVal lpClassName As String, ByVal lpWindowName As String) As Integer
    
Sub SampleCode()
    Dim hwnd As Long
    hwnd = FindWindow("SciCalc", "電卓")
End Sub

ただし、関数名は自由につけることができるので
Option Explicit

Declare Function ハンドルゲッター Lib "user32.dll" Alias "FindWindowA" _
    (ByVal lpClassName As String, ByVal lpWindowName As String) As Integer
    
Sub SampleCode()
    Dim hwnd As Long
    hwnd = ハンドルゲッター("SciCalc", "電卓")
End Sub

これでも問題なく動きます。
ちなみに、関数はANSIとUnicode用の2種類があり
9X系はANSIのみでNX系はANSIでもUnicodeでもどちらでも使えるとのこと。
省略した場合は、9X系ではANSI、NX系では、Unicode用の関数が呼び出される。
Aliasに記載されている関数の末尾がAだとANSI用の関数。WだとUnicode用の関数。
なければOSによって使い分けとなる。
ちなみに、Aliasに存在する名前を関数名につけた時Aliasを省略することが可能です。


最後に型についてです。
ExcelVBAで、注意すべきポイントは2つ。
1つ目は、独自の型が指定されている時は、Typeで宣言して用意する必要がある。
例)
Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long

この関数は、第一引数に渡したハンドルのウィンドウの座標を返す関数です。
左上のx,y座標、右下のx,y座標を返します。
では、どのように返すかというと第二引数をみると
"RECT"という見慣れない型になっています。
この定義を調べるとRECTという型はLEFT/TOP/RIGHT/BOTTOM
という変数を持った構造体であることがわかります。
よって、事前に以下のように変数を宣言しておく必要があります
Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type


では、もう1つのポイントは
String型ですね。
文字列を渡す時は、特に問題ないのですが文字列を受取る時
例えば、ハンドル名からウインドウのタイトルを取得したい時など
こういった時は、変数を渡してそこに格納してもらうことをします。
しかしながら、単純にString型の変数を作って引数に渡せばいいかというとそういうわけではありません。
受取るであろうバイト数より大きめの文字数を代入しておくことで受取る文字+vbNullCharで上書きした値がかえってきます。

具体的に言うと、abcという文字列を受け取ろうとした時
str = String(4, vbNullChar)
としておく必要があります。
そうすることでstrには
a + b + c + vbNullChar が代入されます。
後は、Left,Instr等でvbNullCharの手前までの情報を取得すればよいわけです。


ちなみにvbNullCharである必要はありません。
str = String(5, "*")
こうした場合
* + * + * + * + *
の状態が
a + b + c + vbNullChar + *
となるだけです。
同様にLeft/Instr等でvbNullCharより前を取得すればOKです。

ところで、これから文字をうけとるのに、文字数をどうやって知るんだ?
という話になりますが、不思議に感じるかもしれませんが、ハンドルから
文字数を調べることができるようになっているので事前に取得すればいいだけです。
SendMessage関数でWB_GETTEXTLENGTHメッセージを利用すれば調べられたりします。
わからない時は、あきらかに多い文字数を指定してvbNullCharより手前を取り出すしかないと思います。

0 件のコメント: