3. 32 ビット関数の呼び出し
いくら 16 ビットでプログラミングをしようとしても、新しい Windows (Win95/WinNT) 下で実行させる場合、どうしても 32 ビット関数を呼び出さなければいけないことがあります。例えば、Windows のシャットダウン処理は、16 ビット関数の ExitWindows 関数で指定できるフラグと、32 ビット関数の ExitWindowsEx 関数で指定できるフラグは異なります。
32 ビット関数を呼び出す関数
そこで、以下のような関数が「新しい Windows」の 16 ビットモジュールに提供されています。
extern "C" { DWORD FAR PASCAL LoadLibraryEx32W(LPCSTR lpszLibFile, DWORD hFile, DWORD dwFlags); DWORD FAR PASCAL GetProcAddress32W(DWORD hLibModule, LPCSTR lpszProc); DWORD FAR PASCAL FreeLibrary32W(DWORD hLibModule); //DWORD FAR PASCAL CallProc32W(DWORD dwParam1, ..., // DWORD lpProcAddress, DWORD fAddressConvert, DWORD nParams); DWORD FAR WINAPIV CallProcEx32W(DWORD nParams, DWORD fAddressConvert, DWORD lpProcAddress, DWORD dwParam1, ...); DWORD FAR PASCAL GetVDMPointer32W(LPVOID lpPointer, UINT fMode); }
これらの関数を使用する際は、専用のライブラリをインポートするか、GetProcAddress 関数を使用して KERNEL モジュールから関数のアドレスを取得します。
typedef DWORD (FAR PASCAL* PFN_LoadLibraryEx32W)(LPCSTR, DWORD, DWORD);
PFN_LoadLibraryEx32W pfn = (PFN_LoadLibraryEx32W)
GetProcAddress(GetModuleHandle("kernel"), "LoadLibraryEx32W");
※ Watcom コンパイラで使用できる専用のライブラリは、こちらからダウンロードしてください。→ thunk16.zip (425 バイト)
各関数の説明
- LoadLibraryEx32W
- 32 ビットモジュールをロードします。
- lpszLibFile
- 32 ビットモジュール名 (kernel32.dll, gdi32.dll など) を指定します。
- hFile
- NULL を指定してください。
- dwFlags
- DONT_RESOLVE_DLL_REFERENCES, LOAD_LIBRARY_AS_DATAFILE, LOAD_WITH_ALTERED_SEARCH_PATH のいずれかの値、または 0 を指定します。これらの値は、LoadLibraryEx 関数の dwFlags 引数で指定する値と同じです。
- GetProcAddress32W
- 32 ビットモジュール内の関数アドレスを取得します。
- hLibModule
- 32 ビットモジュールのハンドルを指定します。これは、LoadLibraryEx32W 関数が返したハンドルです。
- lpszProc
- アドレスを取得する関数名を指定します。
- FreeLibrary32W
- 32 ビットモジュールをアンロードします。
- hLibModule
- 32 ビットモジュールのハンドルを指定します。これは、LoadLibraryEx32W 関数が返したハンドルです。
- CallProc32W
- 32 ビット関数を呼び出します。この関数の代わりに、CallProcEx32W 関数を使用してください (引数の内容は同じです)。
- CallProcEx32W
- 32 ビット関数を呼び出します。
- nParams
- dwParam1 以降の、32 ビット関数の呼び出しに必要な引数の数を指定します (引数は最大で 32 個です)。また、その 32 ビット関数の呼び出し規約フラグを OR 演算子で加えます。
CPEX_DEST_STDCALL = 0x00000000L 標準呼び出し規約 (__stdcall)。 CPEX_DEST_CDECL = 0x80000000L C 呼び出し規約 (__cdecl)。 - fAddressConvert
- dwParam1 以降の引数でのポインタ変換に関するフラグを指定します。このフラグは、いわゆる 16:16 ポインタを 32:0 ポインタに変換するかどうかのフラグで、16:16 ポインタの引数を変換する場合は、対応するビットを 1 に設定します。ビットは、dwParam1 を変換する場合は 0x00000001、dwParam2 は 0x00000002、dwParam3 は 0x00000004、dwParamN は 2 ^ (N - 1) となります。
- lpProcAddress
- 32 ビット関数のアドレスを指定します。GetProcAddress32W 関数で取得できます。
- dwParam1, ...
- 32 ビット関数が必要とする引数を、すべて DWORD 型で指定します。引数は 32 個まで指定できます。
元の定義では「DWORD dwParam1」という記述は無く (「...」のみ)、引数無しの 32 ビット関数も呼び出せます。
- GetVDMPointer32W
- 16:16 ポインタを 32:0 ポインタに変換します。
- lpPointer
- far ポインタ (16:16 ポインタ) を指定します。
- fMode
- ポインタの変換モードを指定します。0 の場合はリアルモード、1 の場合はプロテクトモードとしてポインタを解釈します。Win16 では 1 を指定するといいと思います。
少し補足をすると、CallProcEx32W 関数で 32 ビット関数に渡す引数は、すべて DWORD 型で渡す必要があります。また、ここでポインタを渡す時も、fAddressConvert でフラグを指定して far ポインタで渡すか、あらかじめ GetVDMPointer32W 関数で変換されたものを渡します。
32 ビット関数を使ったシャットダウン処理
上で説明した関数を使ったサンプルとして、32 ビット関数のシャットダウン処理を示します。
32 ビット関数である ExitWindowsEx 関数を呼び出す手順は以下の通りです。
- LoadLibraryEx32W 関数で user32.dll をロードする
- GetProcAddress32W 関数で ExitWindowsEx 関数のアドレスを取得する
- CallProcEx32W 関数で ExitWindowsEx 関数を呼び出す
- シャットダウンに失敗したら FreeLibrary32W 関数で user32.dll をアンロードする
※ シャットダウンに成功した時、user32.dll はアンロード出来ません。
この手順をコードに示すと、以下の通りになります。
#define EWX_LOGOFF 0 #define EWX_SHUTDOWN 0x00000001 #define EWX_REBOOT 0x00000002 #define EWX_FORCE 0x00000004 #define EWX_POWEROFF 0x00000008 #define EWX_FORCEIFHUNG 0x00000010 DWORD dwUser32; DWORD dwExitWindowsEx; // user32.dll をロード dwUser32 = LoadLibraryEx32W("user32.dll", NULL, 0); if (dwUser32) { // ExitWindowsEx のアドレスを取得 dwExitWindowsEx = GetProcAddress32W(dwUser32, "ExitWindowsEx"); if (dwExitWindowsEx) { // ExitWindowsEx を呼び出し CallProcEx32W(2 | CPEX_DEST_STDCALL, // 引数は 2 個, WINAPI 0, // ポインタは無し dwExitWindowsEx, // ExitWindowsEx (DWORD) EWX_SHUTDOWN, // UINT uFlags (DWORD) 0); // DWORD dwReserved } // シャットダウンできなかった時にここに来る // user32.dll をアンロード FreeLibrary32W(dwUser32); } MyError("シャットダウン失敗");
ただし、このコードだけでは、Windows NT 上でシャットダウン処理を行うことができません。Windows NT では、シャットダウンの処理に
- OpenProcessToken 関数でアクセストークンを取得
- LookupPrivilegeValue 関数でシャットダウン権限の ID を取得
- AdjustTokenPrivileges 関数で、さっき取得した ID を使ってシャットダウン権限を有効にする
- GetLastError 関数で成功したかどうかを確認したら、ExitWindowsEx 関数でシャットダウン
という処理を行わなければなりません。この内容は、32 ビット アプリケーションでは以下の通りになります。
HANDLE hProcessMe; HANDLE hToken; TOKEN_PRIVILEGES tkp; // 現在のプロセスハンドルを取得 hProcessMe = GetCurrentProcess(); // 現在のプロセスのアクセストークンを取得 if (!OpenProcessToken(hProcessMe, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { // Windows 95/98/Me では関数の実体が無いため、 // エラーとせずシャットダウン処理を行えるようにする if (GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) goto OnExit; } else { // シャットダウン権限の ID を取得 LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); // 1 つの権限 (= シャットダウン権限) を有効にする tkp.PrivilegeCount = 1; tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // シャットダウン権限を有効にする AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0); // 有効にできなかったら失敗 (現在のユーザーで認められていない場合、など) if (GetLastError() != ERROR_SUCCESS) { // ハンドルを閉じる CloseHandle(hToken); goto OnExit; } // ハンドルを閉じる CloseHandle(hToken); } // シャットダウンを実行 ExitWindowsEx(EWX_SHUTDOWN, 0); OnExit: MyError("シャットダウン失敗");
これを 16 ビットのコードで表すと、以下の通りになります。(必要な定義などを含めたサンプル ソースは下記のリンクから参照してください。)
DWORD dwKernel32; DWORD dwUser32; DWORD dwAdvapi32; DWORD dwGetCurrentProcess; DWORD dwOpenProcessToken; DWORD dwGetLastError; DWORD dwLookupPrivilegeValue; DWORD dwAdjustTokenPrivileges; DWORD dwCloseHandle; DWORD dwExitWindowsEx; bool bRet; bRet = true; // 各種ライブラリをロード // kernel32.dll: GetCurrentProcess, GetLastError, CloseHandle // user32.dll: ExitWindowsEx // advapi32.dll: OpenProcessToken, // LookupPrivilegeValue(A), AdjustTokenPrivileges dwKernel32 = LoadLibraryEx32W("kernel32.dll", NULL, 0); dwUser32 = LoadLibraryEx32W("user32.dll", NULL, 0); dwAdvapi32 = LoadLibraryEx32W("advapi32.dll", NULL, 0); if (dwAdvapi32 && dwKernel32) { // ハンドルも DWORD で定義 DWORD hProcessMe; DWORD hToken; TOKEN_PRIVILEGES tkp; DWORD dwRet; DWORD dwPParam1, dwPParam2; // 関数アドレスの取得 dwGetCurrentProcess = GetProcAddress32W(dwKernel32, "GetCurrentProcess"); dwGetLastError = GetProcAddress32W(dwKernel32, "GetLastError"); dwCloseHandle = GetProcAddress32W(dwKernel32, "CloseHandle"); dwOpenProcessToken = GetProcAddress32W(dwAdvapi32, "OpenProcessToken"); dwLookupPrivilegeValue = GetProcAddress32W(dwAdvapi32, "LookupPrivilegeValueA"); dwAdjustTokenPrivileges = GetProcAddress32W(dwAdvapi32, "AdjustTokenPrivileges"); // 現在のプロセスハンドルを取得 hProcessMe = CallProcEx32W(0 | CPEX_DEST_STDCALL, 0, dwGetCurrentProcess); // 現在のプロセスのアクセストークンを取得 if (dwOpenProcessToken) { dwPParam1 = GetVDMPointer32W(&hToken, 1); dwRet = CallProcEx32W(3 | CPEX_DEST_STDCALL, 0, dwOpenProcessToken, hProcessMe, (DWORD) (TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY), dwPParam1); } else { // 関数が取得できていない: 失敗を示す戻り値 (NULL) を設定 dwRet = 0; } if (!dwRet) { // Windows 95/98/Me では関数の実体が無いため、 // エラーとせずシャットダウン処理を行えるようにする if (dwGetLastError) { dwRet = CallProcEx32W(0 | CPEX_DEST_STDCALL, 0, dwGetLastError); if (dwRet != ERROR_CALL_NOT_IMPLEMENTED) bRet = false; } } else { // シャットダウン権限の ID を取得 // 引数となるポインタ (文字列含む) は GetVDMPointer32W で変換 dwPParam1 = GetVDMPointer32W(SE_SHUTDOWN_NAME, 1); dwPParam2 = GetVDMPointer32W(&tkp.Privileges[0].Luid, 1); CallProcEx32W(3 | CPEX_DEST_STDCALL, 0, dwLookupPrivilegeValue, (DWORD) NULL, dwPParam1, dwPParam2); // 1 つの権限 (= シャットダウン権限) を有効にする tkp.PrivilegeCount = 1; tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // シャットダウン権限を有効にする // ※ 全ての引数が DWORD で渡されることに注意 dwPParam1 = GetVDMPointer32W((LPBYTE) &tkp, 1); dwRet = CallProcEx32W(6 | CPEX_DEST_STDCALL, 0, dwAdjustTokenPrivileges, hToken, (DWORD) FALSE, dwPParam1, (DWORD) 0, (DWORD) NULL, (DWORD) NULL); // 有効にできなかったら失敗 (現在のユーザーで認められていない場合、など) // GetLastError 関数のアドレスが取得できていなくても無視 // (CallProcEx32W は渡された関数アドレスが 0 なら 0 を返す) dwRet = CallProcEx32W(0 | CPEX_DEST_STDCALL, 0, dwGetLastError); // ERROR_SUCCESS: 0 if (dwRet != ERROR_SUCCESS) bRet = false; // ハンドルを閉じる CallProcEx32W(1 | CPEX_DEST_STDCALL, 0, dwCloseHandle, hToken); } } // 先に使わないライブラリをアンロードする if (dwAdvapi32) FreeLibrary32W(dwAdvapi32); if (dwKernel32) FreeLibrary32W(dwKernel32); // 成功したら ExitWindowsEx を呼び出し if (bRet) { if (dwUser32) CallProcEx32W(2 | CPEX_DEST_STDCALL, 0, dwExitWindowsEx, dwFlags, 0); } // シャットダウンできなかった時にここに来る / user32.dll をアンロード if (dwUser32) FreeLibrary32W(dwUser32); MyError("シャットダウン失敗");
このように、コードは非常に長くなります。しかしながら、これを使うと 16 ビットと 32 ビットの両方でシャットダウンを柔軟に行えるアプリケーションを作ることができます。
さらに、コンパイル環境と実行環境で Win16 か Win32 かどうかを条件分岐させたコードもあります。(以下のリンクからご覧下さい。これに似たコードを利用したアプリケーションはこちらからダウンロードできます。)
→ サンプル
<< 前へ | 次へ >>
最終更新日: 2004/12/25