C++/CLIなしでMFC/WPF相互運用への道
WPFだけ「classic desktop」に昇格したらしい
『ちなみに余談ですが、私は Windows デスクトップ アプリが「クラシック」と表現されていることに納得していません。』
ごもっとも。というか、まだまだ、MFC/Windows Forms現役ですよね・・・
でも、こっちにはMFC/Windows Forms/WPFは「traditional desktop」って書いてあるけど、WPFだけ「classic desktop」に昇格したんですかね?
UWPの展開にはまだ問題が山積み
UWPを広めたいのはわかるけど・・・
「世界的な超巨大企業には、機密保持のためにインターネットに接続していないオフラインラボのデバイスがワンサカあるのを知らんのか」
愚痴はさておき、UWPのサイドローディングはまだ早い。取り敢えず同じXAML系のWPFに移行するのが、当面の目標。
そもそも、UWPにしろというなら、顧客企業が弱小メーカのソフトを展開するときに、
「サイドローディング?いいよ~」
って言ってくれる状態になってから言ってもらいたい。
200~1000のオフライン端末にサイドローディングする必要があるなら、インフラ(サーバ)あるかもしれないけど、取り敢えず1~10から始めるときに、インフラなかったら、立ててくれると思っているのか・・・
「LOB向けWindowsAppストアも、取り敢えず立てましたってレベルでしょう?ハードル高いし・・・」
LOB アプリの企業への配布 - Windows app development
「それに、中国ないし・・・」
「UWP以外の部分とかどうすんの?Webインストーラ?高速インターネット網が整備されていない国がまだ多いの知ってます?」
.NET 3.5/4.xのSide by Sideと相互運用
まあ、でもXAML+MVVMは将来的には必須。なのでWPF 4.5/4.6。
だが、しかし、MFC以外にも、.NET 3.5.1のWindows Formsコンポーネントやデータベース周りのC#コンポーネントもまだまだ健在。
.NET 3.5/4.xのSide by Sideやるのには、COMでラップしてやる必要がある。
には、マネージCOMとあるが、MFC/ATL COMでラップしても同じこと。
普通にやると、以下のようなコンポーネント分けになる。
- Windows Forms
- MFC ActiveX + C++/CLI + CWinFormsControl
- Windows Forms UserControl
- MFC での Windows フォーム ユーザー コントロールの使用
- WPF
天邪鬼だから、
「マクロまみれなMFC + めんどくさいActiveX + 分けわかんないC++/CLIに、いろいろ問題ありそうな相互運用で無理やりWindows Forms/WPF使えと・・・」
と突っ込みたくなるが、MFC Appの規模がデカいと、WPF UserControlレベルでノウハウ作って、置き換えていったほうが、リスクがなさそうなので、ここはジッと我慢。
C++/CLIは避けたい
しかし、MFC ActiveXがあるってことは、MFC App側にActiveXとの相互運用の実装が必要ってこと。
「COM Interopで出来たら、そもそもMFC ActiveX + C++/CLIの層は要らないんじゃないか・・・」
って、誰でも思うわけです。
そこで調べてみた結果が、
取り敢えず、MFC + Windows Forms + ElementHost + WPFの2段構えの相互運用でWPFも行けます。
もっとも、以下にあるように、Windows Forms ControlをActiveXに偽装しなくても、MFCはWindows Forms Controlのホストが出来るので、タイプライブラリがあればMFCクラスウィザードでActiveXラッパークラスを作成して、MFCとWindows Forms Controlは相互運用可能です。
Windows フォームおよびアンマネージ アプリケーションの概要
以下にサンプルを上げています。
BlogSamples/MfcClassWizardIssue at Article_20160531 · tmori3y2/BlogSamples · GitHub
敢えて、2つのCLRバージョンの相互運用を行うために、一部は.NET 3.5をターゲットにしていますが、3.5を入れたくない場合は4.xにターゲットを変更してくださいね。
2段構えの相互運用には問題も・・・
サンプルに含まれる.idlファイルはタイプライブラリから手動で生成したもので、参考に添付してあります。
このサンプルでは、非推奨とされているデフォルトのクラスインターフェースを使用せずに、インターフェースを独自実装しているコントロールにおいて、MFCクラスウィザードによるActiveXラッパークラスの生成に失敗してしまいました。
これは、MFCクラスウィザードが、ActiveXのcoclassとdefaultinterfaceとのマッチングをcustom属性 (0F21F359-AB84-41E8-9A78-36D110E6D2F9)、通称、GUID_ManagedName Attributeで行っているためと思われます。
アセンブリに他のComVisibleなコントロールがあると失敗しますが、一度作成したラッパークラスはインターフェースを変更しない限り使用可能なので、一時的に他のコントロールのソースを外して、MFCクラスウィザードを使用するなどの回避策があります。
さて、これからが本題です。
サンプルでは手を抜いていますが、OnPaintやOnSizeの時の追加処理や、WndProcのカスタマイズなど、追加でやることはありますが、ElementHost経由でWPF UserControlを貼り付けても、それなりに動作します。
コールドスタート時は、アセンブリのロードや初期化のコストが大きく、時には描画しきれないなどの問題があるので、アセンブリの事前ロードなどで、改善する必要が実際にはあります。
しかし、サンプルのように、MFC AppにWindows Forms UserControlの枠を貼り付けて、WPF UserControlを貼ると、タブキーでWPF UserConrrolから抜けられないなどの問題があることが最近判明しました。
これは、Windows FormsとWPF UserConrrolの相互運用では発生しない現象です。
こうなってくると、COM InteropでMFCとWPF UserConrrolの相互運用は出来ないものかと思ってしまいます。
COM InteropによるMFCとWPF UserConrrolの相互運用
いろいろと調べてみると、syghさんのブログが引っかかりましたが、詳細は不明です。
試しにWPF UserConrrolをComVisibleにマークすると、親クラスがCOMに非公開なので、うまくいきません。
まあ、でも探してみるものです。COM経由でWindowsハンドルを渡し、マネージコードでHwndSourceを使用する方法がありました。
Hosting WinForm and WPF user controls in Win32 form via COM Interop
またまた、再描画もちゃんと出来ていませんが、取り敢えず貼り付けて表示するだけのサンプルは以下にあります。
BlogSamples/WpfInterop at Article_20160531 · tmori3y2/BlogSamples · GitHub
ちょっとはまったのが、最初WindowsハンドルをIntPtrとしていたこと。
IntPtrはサイズがプラットフォームに依存するので、実行時にインターフェースがないと怒られてしまいました。
いろいろ悩みましたが、Int32にすることで、無事起動しました。
タブキーの移動はどうかな?と思いましたが、そのままでは移動しないようです。残念。