プログラミング

WSHでWin32APIを呼び出す-その8

2005年7月5日

(←戻る)
 前回までの記事で、VBscriptとDynaWrapに簡単で汎用的なDLLを加えることで、WinAPIによりウインドウを表示させることが出来た。最後にいよいよ、『Hello, World!』の表示である。これには、コールバック関数の処理をしなければならない。

 wscript.exeの実行により開始されたプロセスは、スクリプトの実行用にスレッドを一つ走らせている。前回の記事のtest.vbsでは、新規ウインドウの作成によりスレッドの制御が例のコールバック関数に移るわけであるが、このコールバック関数が終了しない限りスレッドの制御がスクリプトの実行に戻ることはないようである。例えば、『コールバック関数の中でSleep関数の呼び出しを繰り返すようにループさせておき、一度スクリプトに制御を移す。続けて、このスクリプトの実行結果をなにかしらの形で捕らえた時にSleep関数のループから抜け出す。』といったことを試してみたが、全く駄目であった。

 結論としては、コールバック関数の中で新たなスレッドを作成し、そこで処理をさせるということになった。この処理もVBscriptで行えないと意味がないので、wscript.exeの新たなプロセスを開始させるようにした。今回作成した自作DLLのコールバック関数は次の通りである。

char HexStr[9]="00000000\0";
char* Hex(unsigned long L)
{
    int i;
    for (i=7;0<=i;i--)
    {
        HexStr[i]="0123456789ABCDEF"[L-(L/16)*16];
        L=L/16;
    }
    return HexStr;
}

long __stdcall DwWndProc( HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam )
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    char cmdLine[256];
    int i,j;

    switch( uiMsg )
    {
    case WM_PAINT:
    case WM_DESTROY:
        //make command line
        for (i=0;(cmdLine[i]="wscript.exe wndproc.vbs "[i])!=0;i++);
        for (j=0;(cmdLine[i]=Hex(hWnd)[j++])!=0;i++);
        cmdLine[i++]=' ';
        for (j=0;(cmdLine[i]=Hex(uiMsg)[j++])!=0;i++);
        cmdLine[i++]=' ';
        for (j=0;(cmdLine[i]=Hex(wParam)[j++])!=0;i++);
        cmdLine[i++]=' ';
        for (j=0;(cmdLine[i]=Hex(lParam)[j++])!=0;i++);
        //prepare for CreateProcess
        ZeroMemory(&si, sizeof(si));
        ZeroMemory(&pi, sizeof(pi));
        si.cb = sizeof(si);
        CreateProcess(NULL, cmdLine, NULL, NULL, 
            FALSE, 0, NULL, NULL, &si, &pi);
        //wait until end of process
        WaitForSingleObject(pi.hProcess,INFINITE);
        GetExitCodeProcess(pi.hProcess,&i);
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
        return i;
    }
    return DefWindowProc( hWnd, uiMsg, wParam, lParam );
}

"wscript.exe wndproc.vbs xxxx xxxx xxxx xxxx"(xxxx の部分は、4つのパラメータ)という文字列を作成し、これをCreateProcessで呼び出した。この新たなプロセスの終了まで待った後、コールバック関数は終了する。これに対応したwndproc.vbsは次の通り。

option explicit
Dim UserWrap
Set UserWrap = CreateObject("DynamicWrapper")
UserWrap.Register "debug\dwtools.dll", "DwGetAddressOfString", "I=su", "f=s", "R=l"
UserWrap.Register "user32.dll", "BeginPaint", "I=ll", "f=s", "R=l"
UserWrap.Register "user32.dll", "EndPaint", "I=ll", "f=s", "R=l"
UserWrap.Register "gdi32.dll", "TextOut", "I=lllsl", "f=s", "R=l"
UserWrap.Register "user32.dll", "PostMessageA", "I=llll", "f=s", "R=l"

Const WM_PAINT = &HF
Const WM_DESTROY = &H2
Const WM_QUIT = &H12

Dim arg,hWnd,uiMsg,wParam,lParam 
Set arg = WScript.Arguments
hWnd=int("&h"+arg(0))
uiMsg=int("&h"+arg(1))
wParam=int("&h"+arg(2))
lParam=int("&h"+arg(3))

select case uiMsg
case WM_PAINT
  'Hello World! の書き込み
  Dim hPaintDC
  Dim stPaint
  Dim chHello
  stPaint=UserWrap.DwGetAddressOfString("",33) 'PAINTSTRUCT: length=33
  chHello = "Hello, world!"
  hPaintDC = UserWrap.BeginPaint(hWnd, stPaint)
  Call UserWrap.TextOut(hPaintDC, 0, 0, chHello+"", Len(chHello))
  Call UserWrap.EndPaint(hWnd, stPaint)
  Wscript.Quit(0)
case WM_DESTROY
  'WM_QUITをウィンドウに送る
  Call UserWrap.PostMessageA(hWnd,WM_QUIT,0,0)
  Wscript.Quit(0)
end select

ごらんの通り、このスクリプトのコードは、先のVBで作った『Hello World!』プログラムのWndProc関数のコードと殆ど同じである。大きく違うのは、DefWindowProcの呼び出しをここで行っていない点である。これもwndproc.vbsにやらせることが出来るのだが、いかんせん、それをするとプログラムがあまりにも重くなりすぎるため、やむなく自作DLLの中に残しておいた。このようにして出来たhello.vbsとwndproc.vbsを組み合わせたVBscriptの実行結果は次の通り!

やった!Hello, World!

速度も、スクリプトにしては申し分なし。ようやくHello Worldプログラムが完成した。これなら、簡単なダイアログを作成することぐらいはVBscriptで出来そうである。次回以降は、作成したDLL、『dwtools.dll』の汎用化について述べていきたい。

(ここで使用したhello.vbs, wndproc.vbs及びdwtools.dllはここからダウンロードできます。)

(続く)

コメント

コメントはありません

コメント送信