tmori3y2のブログ

主にWindowsのプログラムなど

C++/CLIなしでMFC/WPF相互運用への道

WPFだけ「classic desktop」に昇格したらしい

grabacr.net

『ちなみに余談ですが、私は Windows デスクトップ アプリが「クラシック」と表現されていることに納得していません。』

ごもっとも。というか、まだまだ、MFC/Windows Forms現役ですよね・・・

でも、こっちにはMFC/Windows Forms/WPFは「traditional desktop」って書いてあるけど、WPFだけ「classic desktop」に昇格したんですかね?

blogs.msdn.microsoft.com

UWPの展開にはまだ問題が山積み

UWPを広めたいのはわかるけど・・・

「世界的な超巨大企業には、機密保持のためにインターネットに接続していないオフラインラボのデバイスがワンサカあるのを知らんのか」

愚痴はさておき、UWPのサイドローディングはまだ早い。取り敢えず同じXAML系のWPFに移行するのが、当面の目標。

そもそも、UWPにしろというなら、顧客企業が弱小メーカのソフトを展開するときに、

「サイドローディング?いいよ~」

って言ってくれる状態になってから言ってもらいたい。

200~1000のオフライン端末にサイドローディングする必要があるなら、インフラ(サーバ)あるかもしれないけど、取り敢えず1~10から始めるときに、インフラなかったら、立ててくれると思っているのか・・・

「LOB向けWindowsAppストアも、取り敢えず立てましたってレベルでしょう?ハードル高いし・・・」

ビジネス向け Microsoft Windows ストア

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でラップしてやる必要がある。

インプロセスの side-by-side 実行

には、マネージCOMとあるが、MFC/ATL COMでラップしても同じこと。

普通にやると、以下のようなコンポーネント分けになる。

天邪鬼だから、

「マクロまみれなMFC + めんどくさいActiveX + 分けわかんないC++/CLIに、いろいろ問題ありそうな相互運用で無理やりWindows Forms/WPF使えと・・・」

と突っ込みたくなるが、MFC Appの規模がデカいと、WPF UserControlレベルでノウハウ作って、置き換えていったほうが、リスクがなさそうなので、ここはジッと我慢。

C++/CLIは避けたい

しかし、MFC ActiveXがあるってことは、MFC App側にActiveXとの相互運用の実装が必要ってこと。

「COM Interopで出来たら、そもそもMFC ActiveX + C++/CLIの層は要らないんじゃないか・・・」

って、誰でも思うわけです。

そこで調べてみた結果が、

tmori3y2.hatenablog.com

取り敢えず、MFC + Windows Forms + ElementHost + WPFの2段構えの相互運用でWPFも行けます。

もっとも、以下にあるように、Windows Forms ControlをActiveXに偽装しなくても、MFCWindows Forms Controlのホストが出来るので、タイプライブラリがあればMFCクラスウィザードでActiveXラッパークラスを作成して、MFCWindows 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 InteropMFCWPF UserConrrolの相互運用は出来ないものかと思ってしまいます。

COM InteropによるMFCWPF UserConrrolの相互運用

いろいろと調べてみると、syghさんのブログが引っかかりましたが、詳細は不明です。

sygh.hatenadiary.jp

試しにWPF UserConrrolをComVisibleにマークすると、親クラスがCOMに非公開なので、うまくいきません。

まあ、でも探してみるものです。COM経由でWindowsハンドルを渡し、マネージコードでHwndSourceを使用する方法がありました。

Hosting WinForm and WPF user controls in Win32 form via COM Interop

Hosting WinForm and WPF user controls in Win32 form via COM Interop - Windows Presentation Foundation (WPF) - www.gittprogram.com

またまた、再描画もちゃんと出来ていませんが、取り敢えず貼り付けて表示するだけのサンプルは以下にあります。

BlogSamples/WpfInterop at Article_20160531 · tmori3y2/BlogSamples · GitHub

ちょっとはまったのが、最初WindowsハンドルをIntPtrとしていたこと。

IntPtrはサイズがプラットフォームに依存するので、実行時にインターフェースがないと怒られてしまいました。

いろいろ悩みましたが、Int32にすることで、無事起動しました。

タブキーの移動はどうかな?と思いましたが、そのままでは移動しないようです。残念。