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