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

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

VS 2008のWPF-MFC相互運用

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

以下を参考に、MFC アプリから WPF を使おうとして、Visual Studio 2008 SP1 のバグに遭遇しました。
WPF ユーザー コントロールを HwndSource 経由でホストする Win32 アプリ……といいつつ、結局 C++/CLI による中継が必要になりますのでご注意。
なお、VS 2010 では C++/CLI のインテリセンスが効かないという致命的なダウングレードがあるため、MFC-WPF の相互運用をするならば、VS 2008/2012/2013 を使うことをお勧めします。

バグ現象

スクリーンショット (a) は、WPF ユーザー コントロール プロジェクトと、それを使う予定の MFC クライアント アプリ プロジェクトを格納したソリューション。

(a)
https://sygh-jp.github.io/content_hosting/my_program_ss/Wpf_Designer_Error_ss_2010_05_31a.png

一見何の変哲も無いが、ここで C# コードのタブ (.xaml.cs) へ移動し、WPF のプロジェクトをリビルドする。そして、XAML デザイナのタブ (.xaml) へ再度移動する。
すると、スクリーンショット (b) のように再読み込みを促す情報バーが出るので、クリックすると、スクリーンショット (c) のように IDE がクラッシュする。

(b)
https://sygh-jp.github.io/content_hosting/my_program_ss/Wpf_Designer_Error_ss_2010_05_31b.png

(c)
https://sygh-jp.github.io/content_hosting/my_program_ss/Wpf_Designer_Error_ss_2010_05_31c.png

再度ソリューションを開くと、スクリーンショット (d) のエラーが待っている。

「オブジェクト参照がオブジェクト インスタンスに設定されていません。」

なん……だと……

いわゆる NullReferenceException のハンドルされない例外が発生している。よくあるアプリケーションが落ちる原因のひとつだが、こいつが WPF デザイナー(Visual Studio IDE プロセス)内部で発生している。

(d)
https://sygh-jp.github.io/content_hosting/my_program_ss/Wpf_Designer_Error_ss_2010_05_31d.png

なんぞこれ……ということで調べてみたところ、MS のサポートページにひっそりと修正プログラムが公開されていた。

上記によると、Win32 プロジェクトがスタートアップ プロジェクトとして設定されているとバグるらしい(ソリューション エクスプローラーにおいてプロジェクト名がボールド体になっているものがスタートアップ)。
確かにスクリーンショット (e) のように、WPF のほうをスタートアップ プロジェクトに設定して、リビルドすると先ほどのエラーは発生しない。

(e)
https://sygh-jp.github.io/content_hosting/my_program_ss/Wpf_Designer_Error_ss_2010_05_31e.png

ともかく、修正プログラム 963035 を適用せよ、とのこと。下記へ飛ぶ。

修正プログラムは下記のサイトにアップロードされている、とのこと。なんというたらいまわし

このサイトの Downloads タブに目的の修正プログラム「VS90SP1-KB963035-x86.exe」のダウンロード リンクがある。
プログラム適用によって問題が一応修正されていることを確認。
開発者にとっては非常に重要な修正プログラムですが、Windows Update 経由では適用されません。また、2014年現在、修正プログラムへのリンクが切れているようです。代替の修正プログラムが公開されているのかどうかは不明。

しかし Win32 アプリをスタートアップにするとバグるっていう理由が意味不明です。WPF との相互運用を図るとき、普通にやる組み合わせだと思うんですが……最低限そのくらいはテストしてからリリースして欲しいです。

余談:Win32 アプリから WPF を使う3つの方法

  1. /clr を使って C++/CLI でホストコードを書く。
  2. /clr を使って C++/CLI でホストコードを書く。ただし C++/CLI のコードは DLL に押し込む。
  3. WPF アセンブリを COM 公開する。

1番目は自明なので説明は不要ですね。

2番目の方法を具体的に説明すると、/clr を有効にした Win32 DLL、MFC 拡張 DLL もしくは MFC 標準 DLL 内で WPF を使用して、ネイティブ MFC インターフェイスもしくは C 言語形式関数を公開する形にしておけば OK。例えば、ネイティブの HWND ウィンドウ ハンドルを受け取って WPF コントロールのホストとして使う C 言語形式関数を C++/CLI で実装して、WPF コントロールインスタンスを DLL 内で gcnew するようにラップしてしまえば、ネイティブ C++ で書かれた EXE(厳密に言うと、/clr を無効にした EXE)から普通に呼び出せます。ただし /clr を使う場合は CRT/MFC/ATL を静的リンクではなく動的リンクする必要があるので注意しましょう。

1番目および2番目の方法において、もし混合クラスのメンバーとしてマネージ オブジェクトを保持したい場合は、ラッパークラス テンプレートの gcroot を使います。

ちなみに C++/CLI だと、ネイティブ/マネージのうちどちらか片方のインターフェイスだけでなく、両方のインターフェイスを合わせ持つ混合アセンブリ(COM でいうデュアル I/F に近い)まで作れてしまいます。つまり、ネイティブ Win32/MFC 向けの関数/クラスと、C# or VB.NET 向けのクラス両方をひとつのアセンブリに含めるという変態的なことができてしまう。

C++/CLI を一切使いたくない or 使えない場合、3番目の方法として、WPF アセンブリを COM コンポーネントとして公開する方法があります。COM 公開するには、regasm でレジストリ登録する方法のほか、マニフェストで Side-by-Side アセンブリにする方法があるようです。

ATL で開発した従来の COM コンポーネント DLL に関しては、マニフェストで Side-by-Side アセンブリ化(プライベート アセンブリ化)し、実運用にも十分耐えうることを確認したことがあるんですが、WPF アセンブリに関しては未検証です。

もし純粋なネイティブ Win32 アプリ(従来のネイティブ MFCVB 6.0 などで開発したアプリ)から直接 WPF アセンブリを使いたい場合は、2番目もしくは3番目の方法を使う必要があります。ただし、境界面のやりとりに手間や労力がかかることは覚悟しておく必要があります。