syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

MFC-CLI 相互運用時の注意点‏

(これは2011-05-09に書いた故OCNブログの記事を移植したものです)

それなりに規模の大きい MFC プロジェクトなどで、共通言語ランタイムのサポート /clr を追加して .NET ハイブリッド アプリケーションを作るときに、起動時に EETypeLoadException 例外が発生して実行できない場合、コンパイル オプションに /GF(同一文字列の削除)を追加します。

VS 2008 IDE から設定する場合は、「構成プロパティ」→「C/C++」→「コード生成」→「文字列プール」を「はい」にします。
特にデバッグ ビルドで最適化を行なわない場合はこのオプションが OFF になっていることが多いので注意。ただし特定の最適化オプションを有効にすると自動で ON になることもあるらしいです。

今、仕事で関わっている案件では、これまでまともなビルドマスターやモジュール構成管理者がいなかったらしく、ひとつのEXEのプロジェクト ファイルですさまじい数のソースファイルがコンパイル&リンクされます。どうやらMFC拡張DLLの存在を知らなかったらしい(MFC拡張DLLを使えば簡単かつ効率的にMFCアプリを分割ビルドできるのに……)。

この状態で、すべてのソースを/clrコンパイルすると、ただでさえネイティブのみの場合でも長かったリンク時間がさらに長くなります。

多分、「x86 + MSIL」や「x64 + MSIL」の混在コードをリンクする処理に時間がかかっているみたいです。なので、すべて /clr を使ってコンパイルするのではなく、ネイティブのみでコンパイルできる部分は明示的に /clr なしで通常どおりコンパイルするようにしないと厳しいかもしれません。

なお、/clr ありと /clr なしとでは、プリコンパイル済みヘッダー(pch)を共有できないので、pchを作成するヘッダーとソース(通例stdafx.hとstdafx.cpp)を明示的に分ける必要があります。

例えば、

  • ネイティブ用に stdafx.h, stdafx.cpp, $(TargetName).pch
  • /clr 用に stdclr.h, stdclr.cpp, $(TargetName)_clr.pch

とか。

はっきり言ってめんどいことこの上なくて死にそうなので、もういっそネイティブのみを使うコードと/clrを使うコードを別々のプロジェクト(ネイティブEXEと混合DLL、もしくはネイティブDLLと混合EXE)に分離してしまったほうが良いです。

方法 : /clr に移行する

あと、C#はともかくC++/CLIをまともに使える開発者というのはかなり少ないので、MFCしか使えない人達には、

・/clrを使うホストEXE側のコードはいじらないで、ネイティブのMFC拡張DLLのコードだけいじるようにしてね(要するに筐体はいじらないで部品だけいじるようにしてね)

もしくは

・/clrを使うMFC拡張DLLにWPFラッパーのネイティブMFCインターフェイスのみを公開してあげるから、ホストとなるネイティブEXE側のコードだけいじるようにしてね(要するに部品はブラックボックスとして使ってね)

というルール作りも必要かと。そうでないと/clrオプションのもとにとんでもないコードを書き始める危険性があります。C#の素人にはunsafeを許可させないで制約を課したほうがいいのと似たようなものです(状況は逆だけど)。

ネイティブ・マネージ混在デバッグの注意点

VS 2008でx64のネイティブ・マネージ混在コードをデバッグするとき、EXE側の「構成プロパティ→デバッグ→デバッガのタイプ」を「混合」にしておくと、デバッグ実行時にブレークポイントが機能しなかったりステップインできなかったり、さらにASSERT(ATLASSERT)などによるアサートに引っかかったときプロセスが落ちる(アサート ダイアログで「再試行」とか「無視」を選択すると、本来続行可能な場面でも落ちる)現象が発生することがあるので、もしx64ネイティブ コードのほうをデバッグしたかったら「ネイティブ」に設定しておく必要があります。ただし「ネイティブ」に設定すると、今度はマネージ コードに対してブレークポイントが効かなくなったり、.NET側でIDE出力ウィンドウへのUnicode文字の出力ができなくなったりするので注意。これは結構面倒。x86のネイティブ・マネージ混在コードをデバッグ実行するときは「混合」でもアサートで落ちることはないのに……なので、64bit対応のMFC + .NETハイブリッド アプリケーションを作る場合は、まず32bit版で十分デバッグしてバグを取り除いておいたほうがいいです。64bit版でしかテスト・デバッグできないコードに関しては、面倒だがあきらめてデバッグ設定で回避するしかなさそう。なお、「デバッガのタイプ」のデフォルト設定「自動」は、EXEのプロジェクトがネイティブかそれとも/clrありの混合か、に左右されます。

ちなみにC#のコードからWin32のネイティブDLL関数をP/Invoke呼び出ししている場合に、デバッガでネイティブ関数内にステップインする場合、C#プロジェクト設定の「デバッグ」タブにある「アンマネージ コード デバッグを有効にする」にチェックを入れておきます。VC++もVC#も、こういったデバッグ時の設定に関しては、.vcprojとか.csprojといったプロジェクト ファイルではなく、.vcproj.<Windows Log-in User Name>.userとか.csproj.userとかのユーザー設定ファイルに書き込まれるので、開発者ごと(端末ごと)に逐一設定してやる必要があることに注意しましょう。

C++/CLI のインテリセンス

VC 2010ではC++/CLI言語のインテリセンスが動作しません(バグではなく仕様)。VC 2012ではちゃんと復活しています。なんで2010で一度死んだのかは不明ですが、C++0x対応やコンパイル前文法チェック機構の強化などを優先した関係上、C++のインテリセンスを再設計する必要があって、その余波でC++/CLI対応が後回しになったのかもしれません。ちなみにVC 2012では Windows ストア アプリ(WinRT)用に新たに C++/CX 拡張モードが追加されています。なお、C++/CX と C++/CLI は混在できません。

従来のWin32ネイティブ業務アプリをWPFに移行したいと考えたとき、全部をWPFに置き換えるのは現実的にいって不可能なので(WPF+C#自体は生産性は高いけどC#C++ほどきめ細かい制御ができないし、逆にネイティブC/C++GUIコード資産をWPFから使うにはあまりに制約が多い上に労多くして実りが少ない)、普通はHwndSourceとか使ってMFCアプリ内にWPFコントロールを埋め込む方法をとって徐々にUI部品などから.NETへ移行していく、またパフォーマンスが要求される部分だけC++実装を残しておいてC++/CLIでマネージインターフェイスを書く、という方法をとると思うんですが、肝心のC++/CLI言語に対するIDEサポートがおざなりだと致命的になります。C++/CLIは安易に使うと(ネイティブC++以上に)地獄を見るという、完全にマニア向けの言語ですが、MSにはもっとC++/CLIを使った相互運用のメリット・デメリットや具体的シナリオを説明するページを作成して欲しいです。