今回やるのは、前回と同じく EnumWindows 関数を使用したウィンドウの列挙なのであります。
前回は、アドレスや最大数などの情報を直接マシン語のバイナリコードの中に書き込んでいくということを行なっていましたね。これは、今のように小さな関数ならばそれほど問題ではないのですが、関数が大きくなるにつれて、これらの情報をどこに格納するのか(オフセット)などの情報が複雑になってくるでしょうし、ちょっとしたミスでプログラムが大暴走、ということも考えられます。
今回は、より安全にプログラミングを行なうということを考えてみましょう。で、どうすればいいのかというと、マシン語の中に直接埋め込むデータが少なければ少ないほどいいわけです。そこで今回は、HSPスクリプトから関数側に渡されるすべての情報をあらかじめ構造体(HSP側から見れば配列変数)に格納しておき、この構造体のアドレス1つだけを関数に渡す(埋め込む)ということにします。
しかも今回の場合は、 EnumWindows 関数の lParam パラメータに指定された値がそのまま EnumWindowsProc の lParam パラメータに渡されるので、これを構造体アドレスとすれば、マシン語コード内には何も埋め込まずに済みますよね。
ということで、今回は以下のよう ENUMWND_DATA 構造体を定義してみることにしましょう。
typedef struct { HWND *phwnd; // ハンドルを格納する配列変数アドレス int nmax; // 最大数(配列の要素数) int ncntr; // カウンタ } ENUMWND_DATA;
この構造体の実体は、HSP側で準備された配列変数になります。注意するべきことは、前回はカウンタ変数のアドレスを関数に埋め込んでいましたが、この構造体はメンバそのものがカウンタになっているという点です。
コールバック関数のソースコードは以下のようになります。
#include <windows.h> // HSPからの情報を渡すための構造体 typedef struct { HWND *phwnd; // ハンドルを格納する配列変数アドレス int maxnum; // 最大数(配列の要素数) int cnt; // カウンタ } ENUMWND_DATA; BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { // lParam は ENUMWND_DATA 構造体アドレス ENUMWND_DATA *pData = (ENUMWND_DATA *)lParam; if (pData->cnt >= pData->maxnum) return FALSE; pData->phwnd[pData->cnt] = hwnd; pData->cnt++; return TRUE; }
コンパイルについては前回とおんなじ。DOSプロンプト(コマンドプロンプト)を起動して、カレントディレクトリの移動、ソースのコンパイル、 TDUMP でマシン語情報の取り出しという手順をとります。
ただ、コンパイルのたびに、前回にやったようなコマンドラインをいちいち入力していくのは面倒ですよね。そこで、コンパイルとダンプ作業を行なうバッチファイルを作成して、作業を単純化させてみましょう。まず、以下のファイルを作成して、作業フォルダに mcnbcc.bat というファイル名で保存しておきます。
bcc32 -c -O1 %1.c @if errorlevel == 1 goto END tdump -oxCOMENT %1.obj %1.txt start %1.txt :END
上のバッチファイルの「%1」の部分が、パラメータとして指定された文字列に置き換えられて実行されるようになります。例えば、今回の場合、
mcnbcc.bat enumwnd2
または、コマンド入力時に拡張子『.bat』は省略できるので
mcnbcc enumwnd2
とすることで、「%1」の部分はすべて「enumwnd2」に置き換えられて実行されます。このとき、まず、ソースファイルのコンパイルを実行した後、コンパイラがエラーを返さなければ、TDUMPを実行し、最後にテキストファイル形式で出力されたダンプを開くようになっています。ファイルが自動的に開かれないようにしたい場合には、「start %1.txt」の行を削除してください。
実際の入出力は以下のようになります。
C:\WINDOWS>d: D:\>cd hspmcn D:\hspmcn>mcnbcc enumwnd2 D:\hspmcn>bcc32 -c -O1 enumwnd2.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland enumwnd2.c: D:\hspmcn>tdump -oxCOMENT enumwnd2.obj enumwnd2.txt Turbo Dump Version 5.0.16.12 Copyright (c) 1988, 2000 Inprise Corporation D:\hspmcn>start enumwnd2.txt D:\hspmcn>
これによって作成されたファイル enumwnd2.txt の内容は以下のようになっています。
(…………………… 省 略 ……………………) 000DF1 PUBDEF 'EnumWindowsProc' Segment: _TEXT:0000 000E26 LEDATA Segment: _TEXT Offset: 0000 Length: 0028 0000: 55 8B EC 53 8B 45 0C 8B 50 08 3B 50 04 7C 04 33 U駆S畿.輝.;P.|.3 0010: C0 EB 10 8B 08 8B 5D 08 89 1C 91 FF 40 08 B8 01 タ....犠.....@.ク. 0020: 00 00 00 5B 5D C2 08 00 ...[]ツ.. 000E55 MODE32
生成されたマシン語を変数に格納するのですが、前回のようにして直接手で打つのは、ミスによるバグを招く確率が非常に高いですね。今回はちょっと別の方法です。とりあえず、下のスクリプトを実行してみてください。
; マシン語コード変換スクリプト #include "llmod.as" varname = "fncode" ; 変数名 numline = 5 ; 1行の要素数 sdim cod, 10000 ; 出力されたマシン語コード cod = {" 55 8B EC 53 8B 45 0C 8B 50 08 3B 50 04 7C 04 33 C0 EB 10 8B 08 8B 5D 08 89 1C 91 FF 40 08 B8 01 00 00 00 5B 5D C2 08 00 "} ; 文字列形式からバイナリコードに変換 dim bin, 800 ll_bin bin, cod if stat : dialog "マシン語に変換できませんでした。", 1 : end ; dllret には変換サイズが格納されます n = dllret + 3 / 4 ; バイナリコードの配列変数の要素数 dim buf, 10000 buf = "xdim "+varname+", "+n+"\n" repeat if cnt == n : break if cnt \ numline == 0 : buf += varname+"."+cnt+" = " t = bin.cnt : str t, 24 : buf += "$"+t if (cnt+1\numline!=0)&(cnt+1!=n) : buf += ", " : else : buf += "\n" loop mesbox buf, winx, winy, 4 stop
上のスクリプトは、 loadlib.dll の ll_bin 命令を使用して、文字列形式で記述されているマシン語コードをバイナリ形式に変換し、それを HSP の変数に格納するためのスクリプトを作成します。このスクリプトを実行させると、以下のコードが取得できます。これをコピーしてそのままスクリプトに貼り付けてしまいましょう。
xdim fncode, 10 fncode.0 = $53ec8b55, $8b0c458b, $503b0850, $33047c04, $8b10ebc0 fncode.5 = $085d8b08, $ff911c89, $01b80840, $5b000000, $0008c25d
HSPスクリプトは以下のようになります。今回はマシン語コード中に埋め込む情報はありません。代わりに、上で定義した ENUMWND_DATA 構造体となる配列変数に必要な情報を格納し、その変数のアドレスを EnumWindows 関数の lParam パラメータに渡さなくてはなりません。また、呼び出し前にはカウンタとなるメンバ(ここでは変数 ewdata.2)を 0 に初期化するのも忘れてはいけません。
#include "llmod.as" #include "xdim.as" ; コールバック関数のマシン語コードを変数に格納 xdim fncode, 10 fncode.0 = $53ec8b55, $8b0c458b, $503b0850, $33047c04, $8b10ebc0 fncode.5 = $085d8b08, $ff911c89, $01b80840, $5b000000, $0008c25d ; ウィンドウハンドルを格納する配列変数 dim hwnd, 512 ; ENUMWND_DATA 構造体 getptr ewdata.0, hwnd ; 配列変数のアドレス ewdata.1 = 512 ; 最大数(配列の要素数) ewdata.2 = 0 ; カウンタ(0 に初期化する) ; EnumWindows の呼び出し getptr pm.0, fncode ; コールバック関数(マシン語コード)アドレス getptr pm.1, ewdata ; ENUMWND_DATA 構造体アドレス dllproc "EnumWindows", pm, 2, D_USER dialog "" + ewdata.2 + "個のウィンドウハンドルが取得されました。" stop
前回よりもすっきりした感じがしますね。安全性も、前回よりずっと高まっているのであります。