UWSCでコールバック関数を処理する
方法を見つけてしまった。
いや、見つけた、というほどのことではないのだけど。
もちろん、Asmモジュールを使ってCプログラムを作れば呼べる。
でもそれは、個々のCallbackそれぞれに対して、Cプログラムが必要となる。
汎用性がない。
今回の発見はそこを打破する。
スクリプト
Callback.uws
OPTION EXPLICIT CALL Asm IFB GET_UWSC_NAME = "Callback.uws" THEN DEF_DLL EnumWindows(DWORD,DWORD): BOOL: user32 DIM para = Callback.Create(8, "SampleEnumWindowsProc(para, op)", 10, TRUE) MSGBOX("EnumWindows: " + Callback.Do(para, "EnumWindows(addr, op)", 135)) Callback.Dispose(para) Callback.Dispose() ENDIF PROCEDURE SampleEnumWindowsProc(para, wait=10) DIM hwnd, lParam, addr = Callback.Addr(para) WHILE Callback.IsAlive(para) IFB Callback.WaitEvent(para) = 0 THEN hwnd = Asm.GetDword(addr) lParam = Asm.GetDword(addr + 4) PRINT FORMAT(hwnd, 8, -1) + " " + STATUS(HNDTOID(hwnd), ST_TITLE) + " " + lParam Callback.SetDone(para) ENDIF WEND FEND MODULE Callback DEF_DLL CreateEventW(DWORD,BOOL,BOOL,wstring): DWORD: kernel32 DEF_DLL SetEvent(DWORD): BOOL: kernel32 CONST PARA_BASE = 40 PUBLIC minWait = 15 HASHTBL _allocs, _results FUNCTION Create(size, evalstr, op=EMPTY, ret=0, wait=1000, free=0, stdcall=TRUE) DIM addr = Asm.Set("U7tEMyIRuP////+6AAAAAIP7AHQ8i0sk4xX8i8aL14v0g8YIi/uDxyjzpIvwi/r/" + _ "cwj/E/9zFP9zDP9TBIlDIItDGLoAAAAAg3scAHQDi1MkW1kD4lHD") DIM paraSize = size + PARA_BASE, para = addr + 96, i = 0 DIM hEvWait = CreateEventW(NULL, FALSE, FALSE, NULL) DIM hEvNext = CreateEventW(NULL, FALSE, FALSE, NULL) IFB addr > 0 AND para > 0 AND hEvWait > 0 AND hEvNext > 0 THEN i = i + Asm.SetDword(para + i, Asm.GetProcAddress(Asm.hK32, "SetEvent")) i = i + Asm.SetDword(para + i, Asm.GetProcAddress(Asm.hK32, "WaitForSingleObject")) i = i + Asm.SetDword(para + i, hEvWait) i = i + Asm.SetDword(para + i, hEvNext) i = i + Asm.SetDword(para + i, free) // free i = i + Asm.SetDword(para + i, wait) // Wait time i = i + Asm.SetDword(para + i, ret) // callback ret i = i + Asm.SetDword(para + i, stdcall) // stdcall i = i + Asm.SetDword(para + i, 0) // wait result i = i + Asm.SetDword(para + i, size) // param size Asm.SetDword(addr + 2, para) _allocs[para] = addr THREAD PrivateThreadEval(para, evalstr, op) RESULT = para ELSE IF addr > 0 THEN Asm.Free(addr) IF para > 0 THEN Asm.Free(para) IF hEvWait > 0 THEN Asm.CloseHandle(hEvWait) IF hEvNext > 0 THEN Asm.CloseHandle(hEvNext) addr = 0 para = 0 hEvWait = 0 hEvNext = 0 ENDIF FEND PROCEDURE PrivateThreadEval(para, evalstr, op, addr=0, ev=0) DIM ret = 0 TRY ret = Eval(evalstr) EXCEPT ret = TRY_ERRMSG ENDTRY IFB ev > 0 THEN SetEvent(ev) _results[para] = ret ENDIF FEND FUNCTION Do(para, evalstr, op=EMPTY) DIM addr = _allocs[para], ev = CreateEventW(NULL, FALSE, FALSE, NULL) THREAD PrivateThreadEval(para, evalstr, op, addr, ev) DIM ret = 258 WHILE ret = 258 ret = Asm.WaitForSingleObject(ev, minWait) WEND RESULT = _results[para] Asm.CloseHandle(ev) SLEEP(0.01) FEND PROCEDURE Dispose(para=-1) IFB para = -1 THEN WHILE LENGTH(_allocs) Dispose(_allocs[0, HASH_KEY]) WEND ELSE DIM addr = _allocs[para] DIM ret = _allocs[para, HASH_REMOVE] ret = _results[para, HASH_REMOVE] Asm.CloseHandle(Asm.GetDword(para + 8)) Asm.CloseHandle(Asm.GetDword(para + 12)) Asm.Free(para) Asm.Free(addr) ENDIF FEND FUNCTION WaitEvent(para) DIM ev = Asm.GetDword(para + 8) RESULT = 258 WHILE RESULT = 258 RESULT = Asm.WaitForSingleObject(ev, minWait) WEND FEND FUNCTION SetDone(para) RESULT = SetEvent(Asm.GetDword(para + 12)) FEND FUNCTION IsAlive(para) RESULT = _allocs[para, HASH_EXISTS] AND !_results[para, HASH_EXISTS] FEND FUNCTION Data(para, pos, data=EMPTY) RESULT = Asm.GetDword(para + pos) IF data <> EMPTY THEN Asm.SetDword(para + pos, data) FEND FUNCTION Free(para, data=EMPTY) RESULT = Data(para, 16, data) FEND FUNCTION Wait(para, data=EMPTY) RESULT = Data(para, 20, data) FEND FUNCTION Ret(para, data=EMPTY) RESULT = Data(para, 24, data) FEND FUNCTION Call(para, data=EMPTY) RESULT = Data(para, 28, data) FEND FUNCTION WaitRet(para, data=EMPTY) RESULT = Data(para, 32, data) FEND FUNCTION Addr(para) RESULT = para + PARA_BASE FEND ENDMODULE
サンプルスクリプトは、EnumWindows。
UWSCには、GETALLWIN関数があるので、全く不要なスクリプトですけど。
使い方
以下の流れでスクリプトを組みます。
コールバック関数用UWSCスクリプトの作成
サンプルスクリプトでは、SampleEnumWindowsProc関数のようなものです。
paraは必ず受け取るようにしてください。
他はCallback.Create関数経由でopが受け取れます。(任意)
Callback.IsAlive(para)の間ループしている想定です。
Callback.WaitEvent(para)のリターンが0の場合、コールバックイベントが発生しています。
Callback.Addr(para)を先頭に、コールバック引数のパラメーターが入っています。
Asm.GetDword等で取得してください。
コールバック関数の戻り値を変更したい場合、Callback.Ret(para, 戻り値)で変更してください。
Callback.SetDone(para)で、コールバック関数処理を終わり、次のイベント待ちします。
Callback.Create関数の実行
第一引数:コールバック関数の引数のサイズ(Byte)
第二引数:上で作成したスクリプト関数の呼び出し文字列。必ずparaを渡す。他渡したいものがあればopに詰める
第三引数:op。コールバック関数用スクリプト関数に渡したいもの(任意)
第四引数:コールバック関数の初期戻り値。Callback.Retで変更可能
第五引数:コールバック関数用スクリプト関数の終了待ち時間
第六引数:自由使用エリア。4Byte。Callback.Freeで変更可能。第三引数は初回の値だがこれは外部からも変更可
第七引数:呼び出し規約。通常TRUE:stdcall(省略値)。FALSE:cdecl
戻りは、paraでCallbackの関数は基本的にこの値を必要とする
Callback.Do関数の実行
第一引数:para
第二引数:実行関数呼び出し文字列。コールバック関数のポインターは、addrであることに注意
第三引数:実行関数に渡したい場合、opとして使用(任意)