monolithic kernel

64bit対応なフックを使ったアプリの作り方

September 13, 2009

    拙作SmartWheelのように、32bitと64bitのどちらの環境でも動作するフックを使ったアプリケーションの作り方を説明したいと思います。

    まず、大前提としてWindowsでは32bitアプリは64bitアプリにフックをインストールできませんし、64bitアプリは32bitアプリにフックをインストールできません。そのため、32bitアプリと64bitアプリの両方にフックをインストールしたい場合、同じ動作をする32bitのバイナリと64bitのバイナリを用意して両方を実行する必要があります。幸い、普段からポインタや型の大きさを意識してコードを記述していれば32bit、64bitの両方でまったく同じコードを利用できるので、64bit対応は意外と簡単に実現可能なのです。

    しかし、利用者に2つアプリを起動してもらうというのはなんだか格好悪いですよね。そこで、SmartWheelでは32bit版のプログラムに64bit版を自動的に起動する処理やユーザインターフェイスを内蔵し、64bit版のプログラムはフックを行うだけという構造をとっています。このようにすることで、32bit版Windows上では32bit版のプログラム単体で完結し、64bit版Windows上では2つのプロセスで動作するようにできます。タスクマネージャを見るとプロセスが2つあってやっぱり微妙なのですが、利用者が不便を被るようなことはなくせます。

    ここからは実現方法を追ってみます。まず、64bit版Windowsかどうかを判定するコードを記述します。先述の通り、32bit/64bitのどちらのOSを利用していても、始めに利用者が起動するのは32bit版のプログラムです。そのため、32bit版のプログラムは起動直後にWOW64環境かどうかのチェックを行い、WOW64環境であれば64bit版Windowsを利用していると判断します。実際のコードは以下の通りです。

    bool IsWow64() {
      typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
      LPFN_ISWOW64PROCESS fnIsWow64Process;
      // IsWow64Process関数が存在するかどうか調べる
      fnIsWow64Process = reinterpret_cast<LPFN_ISWOW64PROCESS>(
        GetProcAddress(GetModuleHandle(_T("kernel32")), "IsWow64Process"));
      if(fnIsWow64Process) {
        // 存在する場合、呼び出して確認
        BOOL isWow64 = FALSE;
        return !!(fnIsWow64Process(GetCurrentProcess(), &isWow64) && isWow64);
      }
      // 存在しない場合は確実に非WOW64環境
      return false;
    }

    そして、この関数を呼び出した結果がtrueだった場合には64bit版のプログラムを起動します。

    #ifndef WIN64
      if(IsWow64()) {
        STARTUPINFO lpStartupInfo;
        GetStartupInfo(&lpStartupInfo);
        PROCESS_INFORMATION lpProcessInformation;
        CreateProcess(_T("App64"), 0, 0, 0, TRUE, NORMAL_PRIORITY_CLASS, 0, 0, &lpStartupInfo, &lpProcessInformation);
      }
    #endif

    最後に、利用者がプログラムを終了しようとした場合には何らかの手段で64bit版のプロセスにもそのことを通知しましょう。これにはいろいろな方法があると思いますが、SmartWheelでは非表示のウィンドウを用意しておいてそこにWM_CLOSEを送信しています。

    LPCTSTR CLASS_NAME32 = _T("WindowClass32");
    LPCTSTR CLASS_NAME64 = _T("WindowClass64");
    #ifndef WIN64
      HWND hwnd = FindWindow(CLASS_NAME64, 0);
      if(hwnd) {
        SendMessage(hwnd, WM_CLOSE, 0, 0);
      }
    #endif

    長々と書いてみましたが、要するに64bitのアプリが少なくて不便なのでみんなもいっぱい作ってね、ということです。