UWSCでGetLastError情報を取得する
MSDNを見てると、Falseが返ったらErrorだからGetLastErrorしろ、とか良く書いてある。
CreateFileもINVALID_HANDLE_VALUE(0xFFFFFFFF)ならエラーだから、GetLastErrorしろ、と。
でも、、、UWSCでは、GetLastErrorが呼べない。
いや、呼べるけど、呼ぶ過程で?SetLastError(0)されているみたいで常に0。
何がいけないのか、わかりません、、、。
ということで、StructモジュールとAsmモジュールを使って、APIとGetLastErrorを同時に呼ぶモジュールを作ってみた。
スクリプト
Dll.uws
OPTION EXPLICIT CALL Struct CALL Asm // なんでGetLastErrorできないの!って時用 // ま、デバッグ用途な気がする IFB GET_UWSC_NAME = "Dll.uws" THEN DIM name = "SetLastError" MSGBOX(name + "試験") DIM ret, e // DWORD値を渡す場合、第五引数は0(省略値) IF Dll.Run(ret, e, Asm.GetProcAddress(Asm.hK32, name), 12345) THEN MSGBOX(name + "(12345)の結果: " + e) ELSE MSGBOX(name + " Dll.Run失敗 " + e) ENDIF name = "GetOpenFileNameA" MSGBOX(name + "試験<#CR><#CR>構造体定義はStructモジュール参照") DIM openFileName = Struct.Create(name, _open_file_name) openFileName.lpstrFilter = "uwsファイル(*.uws)" + chr(0) + "*.uws" openFileName.nMaxFile = 256 openFileName.lpstrFile = FORMAT(CHR(0), openFileName.nMaxFile) openFileName.lpstrInitDir = GET_CUR_DIR openFileName.lpstrTitle = "ファイルを選んでね" openFileName.Flags = OFN_HIDEREADONLY OR OFN_FILEMUSTEXIST OR OFN_EXPLORER //openFileName.lpstrCustomF = "" DIM ptr = Struct.Alloc(openFileName) // ポインターを渡す場合、第五引数は0 IF Dll.Run(ret, e, name, ptr, 0, "comdlg32") THEN IF ret THEN Struct.Update(openFileName, ptr, FALSE) MSGBOX(name + " 選んだファイル: " + openFileName.lpstrFile) ELSEIF e = 0 THEN MSGBOX(name + " キャンセルされました") ELSE MSGBOX(name + "失敗 " + e) ENDIF ELSE MSGBOX(name + " Dll.Run失敗 " + e) ENDIF Struct.Free(ptr) name = "CreateFileA" MSGBOX(name + "試験") TEXTBLOCK _create_file { lpFileName: ['string', null], dwDesiredAccess: ['DWORD'], dwShareMode: ['DWORD'], lpSecurityAttributes: ['LPVOID'], dwCreationDisposition: ['DWORD'], dwFlagsAndAttributes: ['DWORD'], hTemplateFile: ['DWORD'] } ENDTEXTBLOCK DIM createFile = Struct.Create(name, _create_file) createFile.lpFileName = openFileName.lpstrFile createFile.dwDesiredAccess = $80000000 //GENERIC_READ createFile.dwShareMode = 1 //FILE_SHARE_READ createFile.dwCreationDisposition = 3 //OPEN_EXISTING ptr = Struct.Alloc(createFile) // 引数が複数の場合、第五引数はサイズ(バイト単位) IF Dll.Run(ret, e, name, ptr, Struct.GetSize(name), "kernel32") THEN IF ret <> $FFFFFFFF THEN MSGBOX(name + "成功 " + ret) Asm.CloseHandle(ret) ELSE MSGBOX(name + "失敗 " + e + " " + Dll.FormatMessage(e)) ENDIF ELSE MSGBOX(name + " Dll.Run失敗 " + e) ENDIF Struct.Free(ptr) Struct.Dispose() Dll.Dispose() ENDIF MODULE Dll DEF_DLL LoadLibraryW(wstring): DWORD: kernel32 DEF_DLL FreeLibrary(DWORD): BOOL: kernel32 DEF_DLL FormatMessageW(DWORD,DWORD,DWORD,DWORD,var wstring,DWORD,DWORD): DWORD: kernel32 DIM _procAddr, _paraSize, _paraAddr PROCEDURE Dll Asm.Asm _procAddr = Asm.Set("VYvsg+wMi0UIiUX8x0X4AAAAAItN/IN5CAB0ZIll+ItV/IN6DAB0QYtF/ItN+CtI_ DIlN+MdF9AAAAADrCYtV9IPCAYlV9ItF/ItN9DtIDHMWi1X8i0IIi034A030i1X0_ igQQiAHr1usUi034g+kEiU34i1X4i0X8i0gIiQqLZfiLVfyLQgT/0IlF+ItN/ItV_ +IlREItF/IsI/9GLVfyJQhSLRfiL5V3CBADM") _paraSize = 24 _paraAddr = Asm.Alloc(_paraSize) IF _paraAddr > 0 THEN Asm.SetDword(_paraAddr, Asm.GetProcAddress(Asm.hK32, "GetLastError")) FEND PROCEDURE Dispose() Asm.Free(_paraAddr) Asm.Free(_procAddr) FEND // paraSize:即値の場合は0.中身の場合はそのサイズを指定 FUNCTION Run(var ret, var e, func, para=0, paraSize=0, dll=EMPTY) RESULT = FALSE DIM hMod = NULL, i = 4, type IFB dll <> EMPTY THEN hMod = LoadLibraryW(dll) IFB !hMod THEN e = "LoadLibrary fail. " + dll EXIT ENDIF ENDIF IFB hMod = NULL AND VARTYPE(func) <> VAR_BSTR THEN i = i + Asm.SetDword(_paraAddr + i, func) ELSEIF hMod <> NULL AND VARTYPE(func) = VAR_BSTR THEN type = Asm.GetProcAddress(hMod, func) IFB !type THEN e = "GetProcAddress fail. " + func EXIT ENDIF i = i + Asm.SetDword(_paraAddr + i, type) ELSE e = "Invalid func. " + func EXIT ENDIF i = i + Asm.SetDword(_paraAddr + i, para) i = i + Asm.SetDword(_paraAddr + i, paraSize) + 4 Asm.SetDword(_paraAddr + i, e) ret = Asm.Run(_procAddr, _paraAddr, type) e = Asm.GetDword(_paraAddr + i) RESULT = (type = 0) IF hMod <> NULL THEN FreeLibrary(hMod) FEND FUNCTION FormatMessage(msgId, langId=0) RESULT = EMPTY DIM p = 256, i = p WHILE i >= p - 1 p = p * 2 RESULT = FORMAT(CHR(0), p) i = FormatMessageW($1200, 0, msgId, langId, RESULT, p, 0) WEND FEND ENDMODULE
必須は、Asmモジュール。
UWSCにできないことはない? - じゅんじゅんのきまぐれ
Structモジュールはあると便利(排除可能)
UWSCで構造体を使う - じゅんじゅんのきまぐれ
使い方
テストコードを参照してもらえれば、わかると思いますが、、、
DWORDが一つの関数を呼びたい場合(引数が4Byteまでの関数を呼ぶ場合)
Dll.Run関数の引数は
- 関数の戻り値用バッファ
- 直後のGetLastError値用バッファ
- 呼び出し関数名もしくは関数ポインター
- 関数に渡す値(DWORD)
- 0(即値の場合は0)
- 関数を持つDLL名(関数ポインター指定時は省略)
Dll.Run関数の戻りがTRUEの場合、Dll.Run成功。
テストコードには、SetLastErrorを使った例で書いてあります。
(SetLastErrorは戻り値voidなので、第一引数は気にしないこと)
もちろん、関数ポインターでなく、名前でも呼べます。
Dll.Run(ret, e, "SetLastError", 12345, 0, "kernel32")
あ、引数がない関数を呼びたい場合は、第四引数は適当で良いです。
引数が4Byteまでの関数の場合、エンディアンとstdcall呼び出し規約を考慮して、数値変換してください。
具体的には、WORD二つの場合、引数で渡すDWORDの上位が第二引数・下位が第一引数です。
BYTE四つなら、下位から順に第一・第二・第三・第四引数を設定してください。
(式的には、(((第四*256)+第三)*256+第二)*256+第一、となります)
4Byte未満なら下位から順に埋めて、上位が空く形です。
(式的には、上と同じ式で、三つなら第四=0なだけです)
応用
みんな大好きEVAL悪魔ですが、DEF_DLLはできません。
が、これを使えばEVALでDLL関数が呼べてしまいます。
もしかして、、、EVALでできないことが、、、OPTION指定くらいになったかな?
定数定義は、ま、できなくても困らない。(変数としてしまえばOK)
関数定義は、自前でがんばれば模すことができないことはない。(やりたくないけど)
OPTION指定も、、、がんばれば、、、いやだけど、、、。
ということで、2年ほど前に作ったUWSCのインタープリター。
UWSCインタープリター - じゅんじゅんのきまぐれ
DLL呼べないな、と思ってましたが、こっちのスクリプトをCALLしておけば、呼べますね。
呼ぶ準備、面倒だけど。
簡易構文でも用意しようかしらん。
いや、それこそDEF_DLLを文字列で受け取る関数を作れば良いだけな気がする、、、。
うむ、可能じゃな。
要望があれば、、、そのうちやる、、、かもしれない。
解説
やっていることと言えば、関数を呼び出すことと、GetLastErrorを呼び出すこと。
それをネイティブスレッド内で順次行うことで、LastErrorが上書きされることを防いでいます。
呼び出したい関数の引数が不明なので、stdcall規約に従ってスタック操作しています。
多分、、、cdeclでも問題ない、、、はず?はず。
成否だけ返すAPIをUWSCでコールすると、自分のスクリプトの不備が何かわからなくて、腹立たしい。
CreateFileにしても、失敗時は理由がわからない。
ということで、我慢できなくなり作りました。
Structモジュール作っている時も、GetOpenFileNameAのヤローが、キャンセルなのかエラーなのかわからない。
あのときは、ウインドウが出るなら100msecくらい過ぎるだろう、とせこい判定にしたけど、これで安心。
ま、キャンセルとエラーなんて同じでいいじゃん、とか聞こえてきそうですが。
パフォーマンス?
関数一つ呼ぶだけのために、スレッド起動してますからね。
良いわけないじゃない!(調べてないけど)
名前で呼ぶと、LoadLibraryして呼び終わった後、FreeLibraryしているから、かなり効率悪そうね。
(UWSCが呼び出しているDLLを除く)
Cのコードとしては、以下のような感じのものが動いてます。
引数をスタックに積む処理がダサい。
DWORD Proc(PPara p) { DWORD res = 0; if(p->pPara) { _asm mov dword ptr [res], esp; if(p->nPara) { res -= p->nPara; for(DWORD i = 0; i < p->nPara; i++) ((BYTE*)res)[i] = ((BYTE*)p->pPara)[i]; } else { res -= sizeof(DWORD); *(DWORD*)res = (DWORD)p->pPara; } _asm mov esp, dword ptr [res]; } res = p->Func(); p->nRet = res; p->nLastError = p->GetLastError(); return res; }
エラートラップ等さぼりまくりなので、何か間違えるとすぐ落ちる。
なお、Dll.FormatMessageでシステムエラーコードを変換できます。
第二引数LangIDは0だと適当なのになるので、日本語環境なら日本語に変換されるかと思います。
英語にしたいなら、en_US($0409)でもどうぞ。
最近、アセンブリと親しみすぎな気がする、、、。
時代逆行も甚だしい。
でも、アセンブリならCPU依存なので、Intelが無事な限り使える。
MacもIntelだし、意外と良いのかも。