Programming Field - Win16 プログラミング

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 引数で指定する値と同じです。
戻り値はロードしたモジュールの 32 ビットハンドルです。失敗した場合は NULL が返ります。この関数を使ってロードしたモジュールは、FreeLibrary32W 関数でアンロードします。
GetProcAddress32W
32 ビットモジュール内の関数アドレスを取得します。
hLibModule
32 ビットモジュールのハンドルを指定します。これは、LoadLibraryEx32W 関数が返したハンドルです。
lpszProc
アドレスを取得する関数名を指定します。
戻り値は 32 ビット関数のアドレスです。失敗した場合は NULL が返ります。このアドレスは CallProcEx32W で使用します。また、このアドレスを解放する必要はありません。
FreeLibrary32W
32 ビットモジュールをアンロードします。
hLibModule
32 ビットモジュールのハンドルを指定します。これは、LoadLibraryEx32W 関数が返したハンドルです。
この関数が成功した場合、戻り値は TRUE です。失敗した場合は FALSE が返ります。
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 ビット関数も呼び出せます。
戻り値は 32 ビット関数の戻り値です。失敗した場合は 0 が返りますが、これは nParams32 より大きい場合、lpProcAddress0 の場合、lpProcAddress 関数が 0 (NULL) を返した場合があるので注意してください。
GetVDMPointer32W
16:16 ポインタを 32:0 ポインタに変換します。
lpPointer
far ポインタ (16:16 ポインタ) を指定します。
fMode
ポインタの変換モードを指定します。0 の場合はリアルモード、1 の場合はプロテクトモードとしてポインタを解釈します。Win16 では 1 を指定するといいと思います。
戻り値は変換された 32:0 アドレスです。失敗した場合は NULL が返ります。このアドレスは CallProcEx32W で使用できます。また、このアドレスを解放する必要はありません。

少し補足をすると、CallProcEx32W 関数で 32 ビット関数に渡す引数は、すべて DWORD 型で渡す必要があります。また、ここでポインタを渡す時も、fAddressConvert でフラグを指定して far ポインタで渡すか、あらかじめ GetVDMPointer32W 関数で変換されたものを渡します。

32 ビット関数を使ったシャットダウン処理

上で説明した関数を使ったサンプルとして、32 ビット関数のシャットダウン処理を示します。

32 ビット関数である ExitWindowsEx 関数を呼び出す手順は以下の通りです。

  1. LoadLibraryEx32W 関数で user32.dll をロードする
  2. GetProcAddress32W 関数で ExitWindowsEx 関数のアドレスを取得する
  3. CallProcEx32W 関数で ExitWindowsEx 関数を呼び出す
  4. シャットダウンに失敗したら 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 では、シャットダウンの処理に

  1. OpenProcessToken 関数でアクセストークンを取得
  2. LookupPrivilegeValue 関数でシャットダウン権限の ID を取得
  3. AdjustTokenPrivileges 関数で、さっき取得した ID を使ってシャットダウン権限を有効にする
  4. 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