UWSCのDEF_DLLについて
DEF_DLLについて書きたくなった。
ちょっともやもやするんで、整理を兼ねて。
最重要「UWSCの配列はポインターであり、ポインターが必要なところ全てで渡せる」
全て同じ型の構造体は配列である
stuncloudさんの偉大な発見である。
ファイル時間を変更する #uwsc | たっぷす庵
全部WORDやDWORDの構造体は、配列のポインター定義にしてしまって良い。
{DWORD,DWORD} -> var DWORD[](2個の配列を渡す)
全て同じ型でなくても構造体は配列である
上を一歩進める。
例えば、WSADATA構造体
#define WSADESCRIPTION_LEN 256 #define WSASYS_STATUS_LEN 128 struct WSAData { WORD wVersion; WORD wHighVersion; char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYSSTATUS_LEN+1]; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char FAR * lpVendorInfo; };
最後の*lpVendorInfoは使わないので適当にサイズをあわせると、DWORDで良い。
すなわち、
{WORD,WORD,char[],char[],WORD,WORD,DWORD}
といったイメージ。(あくまでイメージ。多分配列はポインターにされてしまうので実際はダメっぽい)
ここで、それぞれのサイズを考える。
2Byte WORD wVersion; 2Byte WORD wHighVersion; 257Byte char szDescription[WSADESCRIPTION_LEN+1]; 129Byte char szSystemStatus[WSASYSSTATUS_LEN+1]; 2Byte unsigned short iMaxSockets; 2Byte unsigned short iMaxUdpDg; 4Byte char FAR * lpVendorInfo;
szDescriptionとかszSystemStatusとかどうでも良いとすると、そこに奇数があるだけで他は偶数。
しかも奇数は二つ。
これって、2Byteの塊じゃない?
2+2+257+129+2+2+4=398Byte = 199個のWORD
ということで、WSADATA構造体 -> var WORD(199個の配列を渡す)
ただ、ポインターの指す先が大きくても怒られはしないので、var DWORD(100個の配列を渡す)でもOK。
中身に応じて、単位を決めてください。
構造体は全てポインターで、そのサイズより大きくすることのみが重要ということ。
string/wstringもポインターである
確保した領域のポインターを渡している。
配列と同じ効果が見込めるが、「var」の動作が異なる。
数値の配列では0も扱えるが、string/wstringは0を終端とみなし、それ以上はコピーされない。
pchar/pbyte/string
pbyteというヘルプにない宣言がある模様。
でも多分、pcharと同じ。(だから書いてない模様)
pcharとstringはどちらもcharへのポインターですが、0を終端とみなすか否かの違いがある!
ということで、GetPrivateProfileStringAでキーをNULLにしたりセクションをNULLにすると、NULL区切りで文字列が取得できますが、これをbyte配列で受けてごにょごにょしなくて良いということです。
test.iniのセクション一覧ならこんな感じ
DEF_DLL GetPrivateProfileStringA(string,string,string,var pchar,DWORD,string): DWORD: kernel32 buf = FORMAT(CHR(0), 1024) // バッファサイズは適当に IFB GetPrivateProfileStringA(NULL,NULL,"ないっす",buf,LENGTH(buf),".\test.ini") THEN sections = SPLIT(buf, CHR(0), TRUE) FOR i = 0 TO LENGTH(sections) - 1 MSGBOX(sections[i]) NEXT ENDIF
"ないっす"は、いらなきゃNULLで。
もうちょっと早く気づきたかったかなー。
多分UWSCは[]や{}をポインターにしてしまう
意外と呼ぶのが難しいuser32のAPI
HMONITOR MonitorFromPoint(POINT pt, DWORD dwFlags); typedef struct tagPOINT { LONG x; LONG y; } POINT, *PPOINT;
一発でDEF_DLLにできますか?
最初、どうしてもこう書く
DEF_DLL MonitorFromPoint({long,long},dword): dword: user32
ところがこれが期待動作しない。
{}はポインターにしてしまうのか?と推理。では配列は?
DEF_DLL MonitorFromPoint(long[],dword): dword: user32
これもダメ。
正解は
DEF_DLL MonitorFromPoint(long,long,dword): dword: user32
元の定義から考えると、MonitorFromPointの引数は、POINT=8ByteとDWORD=4Byteの12Byte。
{}やの場合、ポインター=4ByteとDWORD=4Byteの8Byteしか渡ってない模様。
正解定義は、12Byteが渡る。
{}やは無条件でポインターにするのはやめて欲しかったかな。
横着定義ができなくなるから。
全てはメモリー
符号がなくなってしまうのでマイナスが面倒になりますが、
DEF_DLL MonitorFromPoint(word,word,word,word,word,word): dword: user32
もOK。
これも12Byte渡りますから。
もちろん
DEF_DLL MonitorFromPoint(longlong,dword): dword: user32
もOK。(これまた合成が面倒)
varをつければポインターになる、は厳密には正しくない。
{}や[]もポインターにする効果があるが、それにvarをつけてもポインターのポインターにはならない。
varはポインターでないものをポインターにして、関数リターン時に値コピーする、という機能。
引数として渡すサイズはいくつか。
どれがポインターで、ポインターの指す先に必要なサイズはいくつか。
それを意識していれば、DEF_DLLで苦戦はしない、、、といいな。
DLLモジュールについて
UWSCでDLLを扱う - じゅんじゅんのきまぐれ
メモリーからDLLをロードするモジュールですが、Win32APIの呼び出し用としても便利ですよ!
上のMonitorFromPointなら以下のように呼び出せます。
Dll.CallEx(r, e, "MonitorFromPoint", "user32", 3, x, y, 0)
(rは呼び出し結果:成功(True)・失敗(False)。eはGetLastErrorのリターン)
ジャンプ前にスタックに積むメモリーがわかっているなら、自前で用意してDll.Callするのもあり。
Dll.CallExを使うなら、数値は4Byte、文字列はポインターになることに留意してください。
メモリーを想定できるなら便利に使えるもの。
InterpreterにCALLしておくと、DLLが自在に呼べて便利です。
メモリーの確保もDll.Allocでできますしね!
(小さいサイズのメモリーの場合、プロセスヒープから確保するように修正しました。
メモリーを想像してたら、ページ単位でジャブジャブ使うのが嫌になったので。)
おまけ
もし要望するなら、DLL利用のもう1手段。
DLL_IMPステートメントをお願いしたい。
定義はほぼ同じだけど、{}や[]をポインターにしない。
varの能力は同じ。
string/wstringもポインターにしない、かな?
まあでもなんとかなるので、いらんか。
それより、EVALでDEF_DLLを通してくれる方が嬉しいかな。
(定義文字列を手動で書かなくてよくなるし、RtlMoveMemoryは変身させたい)
この辺の話は、アセンブリを書いてて気づいた。
API呼び出しって、引数渡す、とかいうイメージあるじゃないですか。
でも、アセンブリ的には正しくなくて、API呼び出しに引数はない。
ジャンプがあるだけ。
ただ、ジャンプ先に値を渡したいから、呼び出し規約に従って、スタックに引数相当を積んでジャンプとなっている。
ようは、APIの期待通りにスタックを積めば良いの。
DEF_DLLはジャンプする際にスタックにどう積むかの指示、ということ。
あと気づいたこと。
EVAL、CALLが通る。
ただし、既にCALL済みのもので、ファイルは読まない。
スクリプトを動的に実行するのは無理、ということ。
DOSCMDやEXECでUWSCを起動すれば、動的実行も可能だけど。