VC++ におけるマルチプラットフォーム対応、まとめ
ここ数日、XP/Vista/7 の x86/x64 で共通で動作するアプリの作成を行った。思っていたより大変だったので、どの辺が大変だったのかをメモしておこう。
リソース埋め込み
アプリにリソースを埋めこんで、それを実行時に読み出し、ファイルとして保存するという対応を行う必要があった。リソースの数がそれなりにあり、それぞれのターゲットプラットフォームで使用するファイルが異なるので、ZIP に固めたファイルをリソースとして埋め込み、それを展開する形にしようとした。
エクスプローラやシェルは展開機能を持っているのに、API としては提供されていない?ようなので、zlib をスタティックリンクすることにした。
Unicode 対応
XP/Vista/7 のあらゆる言語上で動作しなければならないので、文字は Unicode で扱う必要がある。ただし、zlib が Unicode を扱えず?、また Unicode から Multi byte code に変更しようとしても、例えばひとつのパスに日本語と欧州言語が混ざっていた場合、Unicode では問題なく扱えるけど、Multi byte code では表すことができないので、実質変換では対応ができない。
埋め込んだ zlib を読み込んで、テンポラリフォルダに展開するテストをしているときに、パスにユーザ名が混ざっていることで気がついた。
結局、ファイルはひとつずつ埋め込んで、ひとつずつ読み出すことにした。
テンポラリフォルダ
テンポラリフォルダは、環境変数 tmp を読んで、なければ temp を読んで、それでもなければ 95 系 はカレントを、NT 系は Windows フォルダを使用する(GetTempPath() の仕様)。
環境変数 tmp も temp もないケースというのは、あまり考えられないけど起こりうるので、Windows フォルダへの書き込みができるように管理者権限で起動するようにしなければならない。
管理者権限
管理者権限で起動するには、アプリにマニフェストを埋め込むか、アプリと同じ名前で同じ場所にマニフェストを用意するかすれば可能だが、VC++ 2005 IDE 以降で簡単に設定可能な前者がお勧め。管理者権限と権限昇格はまた違うので注意しなければならない。
権限昇格
システムフォルダへの読み書きなどは管理者権限なんだけど、システムを変更する場合、権限昇格をしなければならない。具体的にはインストールなどがこれにあたる。僕がハマったのは、権限昇格を行わないまま権限昇格が必要な実行ファイルを起動すると、機能を持つ DLL だけがロードに失敗するというものだった。他の DLL は問題なく、特定の DLL だけロードに失敗するので、壊れているのかと、そちらの方向ばかり調査して時間を浪費してしまった。
実行ファイル起動
権限昇格が必要な実行ファイルを起動するには CreateProcess() ではダメで ShellExecuteEx() を使わなければならない、というのもハマり所。ShellExecuteEx で "runas" を指定して実行ファイルを起動するんだけど、XP と Vista/7 で微妙に動作が異なる。XP の方がプロセスの起動が不自然になってしまうが、同一のバイナリをマルチプラットフォームで動かすんだから、ある程度どこかが犠牲になるのはしょうがない。
Wow 関連
マルチアーキテクチャということは、x64 で x86 の実行ファイルを動かさなければならないんだけど、Wow 関連の API は kernel32.dll に入っているものの、x64 でしか入っていないので、関数のエントリを DLL から取り出して、実行しなければならない。
FindFirstFile
FindFirstFile は、日本語と英語で情報量が異なる。
具体的には、
If you are writing a 32-bit application to list all the files in a directory and the application may be run on a 64-bit computer, you should call the Wow64DisableWow64FsRedirectionfunction before calling FindFirstFile and call Wow64RevertWow64FsRedirection after the last call to FindNextFile. For more information, see File System Redirector.
FindFirstFileA function | Microsoft Docs
ということで、64bit OS 上で 32bit プロセスから FindFirstFile を呼び出す前に、リダイレクトされないように Wow 関連の関数を呼び出しておかなければならない。
これもハマった。英語の原典をあたる重要さを痛感。