WOW64のファイルシステムのリダイレクトをOFFにする

会社で、WiXを使って、とあるプロジェクトのインストーラを作っているのですが、ほとんどは.NETなものの、一部のコードだけ、ドライバに依存するため、64bit対応するに当たって、system32(system64ではないんだなこれが。)にファイルをコピーしないといけません(プログラム自体は、32bitなので、32bit/64bit兼用のインストーラ)。

本来なら、%windir%\Sysnativeにファイルをコピーすれば、リダイレクトの影響を受けずにファイルをコピーできるはずなんですが、なぜか、sysnativeがちゃんと動きません。普通にsystem32にファイルをコピーすればsyswow64にリダイレクトされるので、八方塞がりです。
仕方がないので、カスタムアクションで、ファイルをコピーするという暴挙に出ることにしました。仕方がありません。下のコードは、WOW64のリダイレクトを一時的にOFFにするコード。

class Wow64RedirDisabler
{
public:
  Wow64RedirDisabler()
  {
    HINSTANCE hKrnl32 = GetModuleHandle(_T("kernel32"));
    m_disable = (LPFN_WOW64Disable)
      ::GetProcAddress(hKrnl32, "Wow64DisableWow64FsRedirection");
    m_revert = (LPFN_WOW64Revert)
      ::GetProcAddress(hKrnl32, "Wow64RevertWow64FsRedirection");
    
    if(m_disable)
      m_disablingWow64Redirection = m_disable(&m_val);
    else
      m_disablingWow64Redirection = FALSE;
  }

  ~Wow64RedirDisabler()
  {
    if(m_revert && m_disablingWow64Redirection)
      m_revert(m_val);
  }

private:
  typedef BOOL (WINAPI* LPFN_WOW64Disable)(PVOID*);
  typedef BOOL (WINAPI* LPFN_WOW64Revert)(PVOID);
  LPFN_WOW64Disable m_disable;
  LPFN_WOW64Revert m_revert;
  PVOID m_val;
  BOOL m_disablingWow64Redirection;
};

system32のパス自体は、

TCHAR sys32[MAX_PATH];
SHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, 0, sys32);

で取得できます。APIが微妙に古いのはWindows 2000も対応しないといけないため。おもしろいことに(?)、このAPIは、WOW64下でも、%windir%\system32のパスを返します。完全にリダイレクトに頼りきりなんですね。まぁ、逆に言えば、このAPIがsyswow64を返したぐらいで、32bitのプログラムが全部動くような理想的な世界だったら、WOW64のリダイレクションなんていらなかったのかもしれませんけど、微妙に矛盾を感じます。

Visual C++ 9.0のランタイム問題

Visual C++ 9.0を使って共有DLL(/MD)でビルドしたコードを配布しようとすると(C++/CLIを利用すると必然的にこの状況になる)、CRTのDLLを配布する羽目になるんですが、これには、3つの方法があることになっています。

vcredist_x86.exeを利用する

この方法は、前もってランタイムをユーザーにインストールしてもらうという、ある意味では、もっともユーザーに負担をかける方法です。

Microsoft Visual C++ 2008 Redistributable Package (x86)

上記のURLからダウンロードしたセットアッププログラムを実行するだけなので、まぁ、最悪ともいえない方法ではあるのですが、このセットアップには困ったバグがあって、インストール時に、C:\の直下にファイルをぶちまけたあげくに、それらのファイルを消さずにインストールを完了してしまいます。何かのタイミングにC:\を除いて、その惨状に気づくという・・・。普通の人には、もはや、どのファイルが消しても良いファイルなのかわからなくなる状況です。

MSM(マージモジュール)を利用する

インストーラをMSIで作成するのはもはや時代の流れとしては当然なので、MSMを利用してランタイムをインストールするのは正攻法の中の正攻法といえるでしょう。通常、Visual Studioをインストールしていれば、

C:\Program Files\Common Files\Merge Modules

あるいは(x64版のOS)、

C:\Program Files (x86)\Common Files\Merge Modules

にたくさんインストールされているマージモジュールのうち、

Microsoft_VC90_CRT_x86.msm
policy_9_0_Microsoft_VC90_CRT_x86.msm

を利用するだけです。非常に簡単です。

・・・という感じに進めば話が簡単でいいんですが、実際には、WOW64下で動作するWindows Installer(つまり、32-bitの普通のアプリケーションのインストーラ)でこれらのマージモジュールを利用しても、ランタイムがインストールされないみたいなんです。少なくとも、WinSxSディレクトリの下には該当フォルダが作成されない!
うーん、何か簡単なことでも見落としているんでしょうか。*1

プライベートDLLとしてインストール

最後の手段は、自分のアプリケーションのディレクトリにランタイムをインストールしてしまうという方法です。なるべくなら避けたい方法なんですが、上記の2つの方法がなんともかんともという現状では、この方法を選ばざるを得ません。
しかしながら、XP、2003、Vista、2008といったOSでは、自分のアプリのディレクトリにファイルをぶちまけるということはやってはいけません。というか、VS2005以降のコンパイラを使っているのであれば、やっても無駄です。EXEファイルに暗黙のうちに埋め込まれたアプリケーションマニフェストがあなたの行く手を阻みます。いや、阻むというか、アプリケーションマニフェストによって、彼らは特定のバージョンのDLLとしかリンクしないようになっているんです。

このアプリケーションマニフェストっていうのは、平たくいうと、自分が動作を確認した特定のバージョンのDLL以外は使いたくないということを表明(manifest)するための手段です。
この仕組みが使われている例を挙げると、たとえば、Windows XPでは、互換性の低下を代償に見た目をそれ以前のOSと変えて今風にするバージョンのcomctl32.dllと、Windows 2000と代わり映えのしないバージョンのcomctl32.dllの2つが提供されており、アプリケーションはマニフェストを使って、同じ名前のDLLのどちらを使うかを選択できるようになりました。
XP以降のOSでは、このアプリケーションマニフェストを使って、様々なDLLのバージョン違いを共存させることが、OSレベルで可能になりました。もはや、古典的なPATHが通っているDLLをロードするなんてレベルではなくなっています。この仕組みは、正しくは、Side by Side (略してSxS)と呼ばれています。

前置きが長くなりましたが、具体的にどうやるかというと、exeが存在するフォルダにMicrosoft.VC90.CRTのようなフォルダをおいて、その中にランタイムを置くような形になります。

AppDir
  hogehoge.exe
  Microsoft.VC90.CRT
    Microsoft.VC90.CRT.manifest
    msvcm90.dll
    msvcp90.dll
    msvcr90.dll

ちなみにここで使っているランタイムのファイル群は、Visual Studioがインストールされていれば、

C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\redist\x86

の下にあるはずです。x64用は、amd64の下。

あとは、今更、そんな人はいないと思いますが、どう血迷っても、ランタイムをそのままsystem32の下にコピーするような暴挙には出ないでください。物事が悪い方向に行くことはあっても、改善する方向に動くことはないでしょう。

*1:その後の調査により、どうも、WiXの問題であることが露見。VS2008で作った簡易インストーラだったら動作する。WiX3.0ならうまくいくんだろうか・・・・。