UWSC DEF_DLL書き方

過去にこんな記事を書いた。
UWSCのDEF_DLLについて - じゅんじゅんのきまぐれ


これは、自分用のメモとなっているため、思考の多くが省略されています。
自分としては自明なため、書いてないことを含めて、DEF_DLLの書き方を記載しようと思います。
今さらではありますが。



重要な話

まあ、落ち着いて。本当にDLLを呼ぶ必要はあるの?
PowerShellでいいんじゃない?PowerShellで。PowerShellで!!!
というのは、おいといて。


ポインターは何か?
すみません、それは別で勉強してください。
それは(なんとなく)分かってるけど、UWSCのDEF_DLLがよくわからん、という人が本記事の対象です。


その上で、
varをつけるとポインターになりますが、DEF_DLLの{}や引数に配列を渡す場合も、必ずポインターになります。
そして、{}や配列の引数にvarをつけても、ポインターポインターにはなりません。(意味なし)
文字列は、文字の配列です。
この辺が前提知識。


そして、もう一つ。
引数を伴った関数コール、というものは、アセンブリにはない、ということ。
ないのでどうするか。
呼び出し側・呼び出される側、双方でルールを決めて、メモリーを用意してジャンプしよう、となりました。
なので、引数を伴った関数コール、とは、メモリーを確保してジャンプ、ということになります。
モリーがあれば良い、ということです。
変数の数は重要ではないのです!


さて、実践。

引数が実値

SendMessageWというWin32APIがある。

LRESULT SendMessageW(
  HWND   hWnd,
  UINT   Msg,
  WPARAM wParam,
  LPARAM lParam
);

これは簡単。
例えば、

DEF_DLL SendMessageW(HWND, DWORD, DWORD, DWORD): DWORD: user32

SendMessageは、送るMsgによりパラメーターや戻り値の意味が変わります。
このため、それにあわせて適切に定義した方が使いやすいです。


ここで、一歩進めます。
SendMessageはパラメーターが4つ。
しかし、先ほど書いたように、メモリーを確保してジャンプ、です。
HWNDもDWORDもUWSCでは4byteなので、16byteのメモリーを確保し、4byteが返ってきます。
なので、これでも動作します。

DEF_DLL SendMessageW(longlong, longlong): bool: user32

longlongは8byteなので、これも16byteのメモリーを確保し、4byteが返ってくる定義。


ポイント:メモリーを意識すること

引数が構造体

意外と難しいのがこれ。

HMONITOR MonitorFromPoint(POINT pt, DWORD dwFlags);

typedef struct tagPOINT { 
  LONG x; 
  LONG y; 
} POINT, *PPOINT;

構造体だし、こうでしょ、と書きがち。

DEF_DLL MonitorFromPoint({long,long}, DWORD): DWORD: user32

前の記事でも書いたけど、これははずれ。
ここまでで書いているように、「{}」はポインターになります。
このため「{long,long}」はポインターの先に8byteのメモリーを確保したポインター(4byte)が渡されます。
ポインターとDWORDで8byteを渡しています。
しかし、MonitorFromPointが欲しいのは、POINT構造体(8byte)とDWORD(4byte)の12byte。
なので、これが正解。

DEF_DLL MonitorFromPoint(long,long,dword): dword: user32


ポイント:構造体はメンバーをそのまま書く

引数が実値のポインター

DWORD GetWindowThreadProcessId(
  HWND    hWnd,
  LPDWORD lpdwProcessId
);

「LPDWORD」はDWORDへのポインター

DEF_DLL GetWindowThreadProcessId(HWND,var DWORD): DWORD: user32


ポイント:実値のポインターは、varをつければOK

引数が構造体のポインター

BOOL GetLastInputInfo(
  PLASTINPUTINFO plii
);

typedef struct tagLASTINPUTINFO {
  UINT  cbSize;
  DWORD dwTime;
} LASTINPUTINFO, *PLASTINPUTINFO;
DEF_DLL GetLastInputInfo({DWORD, DWORD}): BOOL: user32

構造体の場合と、構造体のポインターの場合の違いに注意。
構造体の場合は、その構造体のサイズのメモリーを確保してやる必要があります。
構造体のポインターの場合は、ポインターの先にメモリーを確保したポインターを渡す。


ポイント:構造体のポインターは、{}でメンバーをかけばOK


ここでもう一つ。
ポインターの先にメモリーを確保したポインターを渡す方法は、
「{}」を使う方法と配列を使う方法があります。
配列も同じようにできます。

DEF_DLL GetLastInputInfo(DWORD[]): BOOL: user32

これに、数値二つの配列を渡す。(数値一つの配列だとメモリーが壊れるので注意)
これが横着定義。


ポイント:ポインターを渡す場合は、配列でもOK(ただし数に注意)

応用

GetMonitorInfoWをDEF_DLLにせよ!
第二引数には、MONITORINFOEXWを指定するものとする。
まあ、答えは前回書いているわけですが。

BOOL GetMonitorInfoW(
  HMONITOR         hMonitor,
  LPMONITORINFOEXW lpmi
);

typedef struct tagMONITORINFO {
  DWORD cbSize;
  RECT  rcMonitor;
  RECT  rcWork;
  DWORD dwFlags;
  WCHAR szDevice[CCHDEVICENAME];
} MONITORINFOEXW, *LPMONITORINFOEXW;

typedef struct tagRECT {
  LONG left;
  LONG top;
  LONG right;
  LONG bottom;
} RECT, *PRECT, *NPRECT, *LPRECT;

CCHDEVICENAMEは32です。


第一引数は、HMONITOR。これは4byteの数値。
DWORDで良いでしょう。
第二引数は構造体のポインターです。
構造体のポインターは、「{}」で書く、ですね。

DEF_DLL GetMonitorInfoW(DWORD,{long[],wstring}): BOOL: user32

お、横着定義ですね。
RECTはlongが4つ。4byteの数値が4つ。
そのRECTが2つと、DWORD・これも4byteの数値が2つ。
すなわち、4byteの数値が10個。
あと、WCHARが32個。
全部で、4*10+2*32=104byte。
なので、long10個の配列とstringに32文字確保して渡せばOK。
そう考えますよね。


はい、残念。不正解。
これは、配列と文字列がポインターになっていることを失念しています。
(DEF_DLLでは、wstringはwcharと同じです)
この場合、第二引数のポインターの先には、ポインターが二つあるだけ(8byte)で、
そのそれぞれの先に、40byteと64byteが別々に存在します。
104byteへのポインターになっていないのでダメです。


正解は、「long」のところにはlongを10個。
「wchar[]」のところにはwcharを32個書きましょう。
これでいける、、、んじゃないかなー、と思います。
それなら、104byteを確保したポインターになります。
(引数の数が多すぎで上手くいかない気もしますが、、、)
書きたくないので、検証もしてません。


さてさて。
個人的に、こんな面倒なことはできません。
横着定義に限ります。
「ポインターを渡す場合は、配列でもOK(ただし数に注意)」
ですね。
104byteを確保したポインターを渡せば良い。
104/4=26。
そうこれでOKです。

DEF_DLL GetMonitorInfoW(DWORD,long[]): BOOL: user32

で、26個渡す。
MONITORINFO構造体部分の数値は取り出しやすいけど、szDeviceはどうやるのよ、というのは
前回の記事を読んでください。
工夫して取り出せます。

まとめ

DEF_DLLするときは、メモリーとして何byte必要か、ポインターはあるか、ポインターの先に何byte必要か、を意識しましょう。