自作WindowsAPIクラスにウインドウのクラス名を返すFunctionを追加した
ウインドウのクラス名を返すFunction
WinAPIの勉強中。
コチラの本に、アプリケーション別のクラス名が掲載されていたのだが、Internet Explorerのクラス名が載っていなかったので、アプリケーションのクラス名を返すFunctionを作ってみた。
コードを書いてから実行する中で、何箇所か致命的なタイプミスがあって、何度かExcelが強制終了したのだが、大丈夫なのだろうか……。
もちろん、Win32API関数を使います。
家のPCは64bitなんだけれど、職場のPCが32bitなので……。
使用するWinAPI関数
次の関数を使った。
FindWindow
関数GetNextWindow
関数IsWindowVisible
関数GetWindowText
関数GetClassName
関数
以上五つ。
例によって、自作のWindowsAPI
クラスに組み込んだ。
WindowsAPIクラスへの追加
追加するものが多いので、順に挙げていく。
リスト1 クラスモジュール宣言セクション
Private Declare Function FindWindow Lib "user32" _ Alias "FindWindowA" (ByVal lpClassName As String, _ ByVal lpWindowName As String) As Long Private Declare Function GetNextWindow Lib "user32" _ Alias "GetWindow" (ByVal hwnd As Long, _ ByVal wFlag As Long) As Long Private Declare Function IsWindowVisible Lib "user32" ( _ ByVal hwnd As Long) As Long Private Declare Function GetWindowText Lib "user32" _ Alias "GetWindowTextA" (ByVal hwnd As Long, _ ByVal lpString As String, _ ByVal cch As Long) As Long Private Declare Function GetClassName Lib "user32" _ Alias "GetClassNameA" (ByVal hwnd As Long, _ ByVal lpClassName As String, _ ByVal nMaxCount As Long) As Long
まずは、Win32APIの関数群。
最初、あろうことかGetClassName
関数の返り値の型をなぜか「String
」と打ち間違えていたために、コード実行後にExcelが強制終了したのだが、大丈夫だったのだろうか……。コワーーー。
リスト2 クラスモジュール宣言セクション
Private Const GW_HWNDLAST As Long = 1 Private Const GW_HWNDNEXT As Long = 2
これらは、GetNextWindow
関数の引数(第2引数wFlag
)にするための定数。
どうも、
hwnd = GetNextWindow(hwnd, GW_HWNDLAST)
とすれば、最後に取得するウインドウである場合にGetNextWindow
関数がウインドウハンドル(hwnd
の値)と同じ値を返すっぽい。
それによって終了判定に使うことができるらしい。ふーん。
リスト3 クラスモジュール
Public Function getWindowClassName(ByVal targetAppNameKeyWord As String) As String Dim gotClassName As String * 100 '……(1)' Dim gotCaption As String * 200 Dim hwnd As Long hwnd = FindWindow(vbNullString, vbNullString) '……(2)' Do If IsWindowVisible(hwnd) Then '……(3)' Call GetWindowText(hwnd, gotCaption, Len(gotCaption)) If InStr(1, gotCaption, targetAppNameKeyWord) > 0 Then '……(4)' Call GetClassName(hwnd, gotClassName, Len(gotClassName)) Dim ret As String ret = Left(gotClassName, InStr(gotClassName, vbNullChar) - 1) '……(5)' Exit Do End If End If hwnd = GetNextWindow(hwnd, GW_HWNDNEXT) '……(6)' Loop Until hwnd = GetNextWindow(hwnd, GW_HWNDLAST) '……(7)' getWindowClassName = ret End Function
これがメインのコード。
たぶん、気をつけないといけないのは、(1)の
Dim gotClassName As String * 100 Dim gotCaption As String * 200
でString
型変数を宣言する際に固定長にしておくこと。
固定長にして、使用するメモリのサイズを厳密に確保しておかないと、えらいことになるような気がする。このあたり、C言語に詳しい人がいたら、教えろ教えてください。
(2)の
hwnd = FindWindow(vbNullString, vbNullString)
は、FindWindow
関数を用いて、ウインドウハンドルを取得。引数に二つともvbNullString
を渡しているので、とりあえずテキトーにどれか一つを取得しているのだと思う。違っていたら教えろ教えてください。
ここからDo
ループに突入。
(3)からの
If IsWindowVisible(hwnd) Then '……(3)' Call GetWindowText(hwnd, gotCaption, Len(gotCaption)) If InStr(1, gotCaption, targetAppNameKeyWord) > 0 Then '……(4)' Call GetClassName(hwnd, gotClassName, Len(gotClassName)) Dim ret As String ret = Left(gotClassName, InStr(gotClassName, vbNullChar) - 1) '……(5)' Exit Do End If End If hwnd = GetNextWindow(hwnd, GW_HWNDNEXT) '……(6)'
If
文がネストしているので、読みづらいかも知れない。
まず、(3)でIsWindowVisible
関数を用いて、可視状態のウインドウかどうかを調べる。
この関門をクリアすると、今度はGetWindowText
でウインドウのキャプションを取得する。
Functionのくせに返り値を受け取る形にしないのがキモチワルイけれど、ステップ実行してみると、
Call GetWindowText(hwnd, gotCaption, Len(gotCaption))
の実行直後に「gotCaption
」にウィンドウのキャプションと残りの文字数をNull文字で埋めた固定長の文字列が格納されていることがわかる。ここの返り値が結構長い文字列になることがあるので、とりあえず固定長を200
と大きめに取っておいたが、これが適切なのかどうかもよくわからない。これまた詳しい人がいたら教えろ教えてください。
次に、(4)の
If InStr(1, gotCaption, targetAppNameKeyWord) > 0 Then
の条件判定。
変数gotCaption
には、ウインドウのキャプションとNull文字が入っているので、引数の「targetAppNameKeyWord
」を含んでいるなら、お目当てのウインドウだということで、If
ブロック内に進むようにした。
たいていアプリケーション名が入っていると思うので。
ただ、アプリケーション名が全角文字のときにどうなるのかはわからない。一太郎とか。
このあたりも、詳しい人がいたら、教えろ教えてください。
If
ブロック内に突入したら、まず
Call GetClassName(hwnd, gotClassName, Len(gotClassName))
でGetClassName
を呼び出す。
これで、gotClassName
に残りの文字数をNull文字で埋めた固定長の文字列が格納される。
あとは、(5)の
ret = Left(gotClassName, InStr(gotClassName, vbNullChar) - 1)
で正味の文字の部分だけを切り出してret
に格納し、ループを抜ける。
ループを抜けたら、
getWindowClassName = ret
でret
の内容をreturnして終わり。
ループから抜けられなかったら、(6)の
hwnd = GetNextWindow(hwnd, GW_HWNDNEXT)
で次のウインドウハンドルを取得してループの先頭へ。
お目当てのウインドウにぶち当たらずに最後まで行ってしまったら、(7)の
Loop Until hwnd = GetNextWindow(hwnd, GW_HWNDLAST)
によってループを抜ける。この場合は""
が返ることになる。
使ってみる
目的は、Internet Explorerのクラス名を知ることなので、次のコードで実行する。
リスト4 標準モジュール
Public Sub test() Debug.Print WindowsAPI.getWindowClassName("Internet") End Sub
アホみたいに簡単。
テキトーにIEを起動してから実行すると、イミディエイト・ウインドウに
「IEFrame
」と表示された。
これがInternet Explorerのクラス名ということだ。
おわりに
何かに使えるかも知れないので、「WinAPIEnums
」という標準モジュールを挿入し、次のような列挙体を作った。
リスト5 標準モジュール宣言セクション
Public Enum AppClassName acNotepad acPaint acWordPad acExcel acWord acOutlook acPowerpoint acInternetExplorer End Enum
その上で、我がWindowsAPI
クラスのモジュールに次のPrivate
メソッドを追加。
リスト6 クラスモジュール
Private Function getApplicationClassName(ByVal targetApp As AppClassName) As String Dim ret As String Select Case targetApp Case acNotepad: ret = "Notepad" Case acPaint: ret = "MSPaintApp" Case acWordPad: ret = "WordPadClass" Case acExcel: ret = "XLMAIN" Case acWord: ret = "OpusApp" Case acOutlook: ret = "rctrl_renwnd32" Case acPowerpoint: ret = "PPTFrameClass" Case anInternetExplorer: ret = "IEFrame" Case Else: ret = "" End Select getApplicationClassName = ret End Function
標準モジュール「WinAPIEnums
」と必ずセットでインポートしなければならなくなるけれど、このようにすることで、今後WindowsAPI
オブジェクトを利用する際にクラス名を指定しやすくなった。
……とはいえ、列挙されているアプリ以外については、今後追加していかねばなりませんが……。