次の方法で共有


組み合わせる

C++ 相互運用機能による Windows フォームの MFC アプリケーションへの統合

Marcus Heege


この記事で取り上げる話題:

  • C++ 相互運用機能の動作
  • プロジェクトでの C++ 相互運用機能の使用
  • MFC アプリケーションでの Windows フォーム コントロールの使用
  • Windows フォームから WPF ビューへの移行

この記事で使用する技術:

  • C++、MFC、.NET Framework、Windows Presentation Foundation

翻訳元: Integrate Windows Forms Into Your MFC Applications Through C++ Interop (英語)


目次

  1. C++ 相互運用機能の使いやすさ
  2. C++ 相互運用機能の使用
  3. MFC アプリケーションでの Windows フォーム コントロール
  4. コントロールのイベントの処理
  5. ダイアログ ボックスとしてのコントロール
  6. Windows フォーム ビュー
  7. Avalon のための準備
  8. まとめ

Microsoft .NET Framework が最初に公開されてから約 5 年が経過し、最近では、バージョン 2.0 もリリースされましたが、多くの C++ アプリケーションが、まだ完全なアンマネージ コードのままです。しかし、.NET Framework への関心は、C++ 開発者の間でも急速に高まっています。それは、今後、多くの Windows API が .NET Framework に基づくようになると予想されるためです。この予想は、WinFX コンポーネント Windows Presentation Foundation、Windows Communication Foundation、および Windows Workflow Foundation において、確かに真実となりました。

しかし、多くの C++ 開発者は、既存 API に対するラッパーではなく、ネイティブ API を使用したいと考えています。ラッパーは、通常、バグが発生しやすく、遅く、さらに柔軟性も低いと考えられています。それが真実であるかどうかは別としても、実際、WinFX のような膨大な API を、ネイティブ コードから使用できるようにマップすることは困難な作業となります。

多くの場合、C++ アプリケーションを .NET Framework の機能で拡張することは、ほとんどの開発者が考えているより簡単です。Visual C++ には、C++ 相互運用機能と呼ばれる機能が含まれており、これは、IJW ("It Just Works"、つまり "そのままで動く") と呼ばれることもあります。この機能を使用して、.NET ベースのコードを既存の C++ ソース コードにシームレスに統合することができます。

C++ 相互運用機能は、主に、2 つの機能に基づいています。まず、C++ コンパイラの /clr スイッチを使用することにより、既存の C++ コードを Microsoft 中間言語 (MSIL) にコンパイルできます。これで、コードは、ガベージ コレクション、サンドボックス化によるセキュリティ、膨大な .NET Framework 基本クラス ライブラリからの型など、.NET 機能を使用できるようになります。

もう 1 つの機能は、混在モードと呼ばれます。これも同じように重要です。これは、マネージ コード、およびアンマネージ コードの両方を組み合わせることを意味します。C++ コードを MSIL にコンパイルする場合、コンパイラは、ネイティブ アセンブリ コードを含むアンマネージ オブジェクト ファイルではなく、MSIL コードを含むマネージ オブジェクト ファイルを作成します。リンカは、マネージ、およびアンマネージの両方のオブジェクト ファイルを入力として使用することができます。リンカは、マネージ入力を 1 つでも検出すると、マネージ コード、およびアンマネージ コードの両方を含むことが可能な 1 つの .NET アセンブリを作成します。これが、混在モードと呼ばれる理由です (図 1 を参照)。この機能は、マネージ空間とアンマネージ空間を移動する回数の大幅な削減を可能にするため、パフォーマンスの最適化においてとても重要になります。

図 1 MTS
図 1 MTS


1. C++ 相互運用機能の使いやすさ

筆者の経験から述べると、C++ 相互運用機能の信頼性はとても高く、いくつかの制限はありますが、開発者が、使用できる技能として習得しておく価値は十分にあります。この機能がどのように使用可能になるかを理解するためには、C++ 相互運用機能が .NET Framework の設計の特徴であったことを知っておく必要があります。.NET Framework の基本仕様である共通言語インフラストラクチャ (CLI) は、この機能をサポートするために設計されています。実際、CLI には、単に C++ 相互運用機能が必要とするから、という理由で定義された部分が多くあります。

たとえば、マネージ メタデータは、グローバル関数をサポートします。ただし、C#、および他のほとんどの .NET 対象言語では、すべてのメソッドが 1 つのクラスのスコープ内に存在することが必要となります。.NET ベースのコードにマップされる C++ コードは、グローバル関数を保持することが可能であり、この C++ コードをマネージ コードにマップするために、メタデータで同様の概念が必要となったため、このような機能が生まれました。

MSIL 命令セットも、C++ 相互運用機能を考慮して設計されています。C++ コードを MSIL コードにマップするには、MSIL が、一般的な Boolean、算術、浮動小数点演算など、すべての共通データ操作演算をサポートする必要があります。MSIL は、仮想メモリへのポインタ ベースでのアクセスもサポートします。この機能は、マネージ コードでネイティブ型を使用するために重要です。C++ コードでネイティブ オブジェクトのフィールドにアクセスする場合、C++/CLI コンパイラは、スタックにネイティブ オブジェクトのアドレスをプッシュし、フィールドのオフセットをそのアドレスに追加し、その結果のアドレスを使用して目的のフィールドにアクセスする MSIL コードを出力できます。これはすべて、MSIL 命令セットに、アドレスによって仮想メモリにアクセスできる演算があるために可能です。

もう 1 つ、IL 言語の C++ 固有の機能として、CALLI 命令があります。これを使用することにより、関数ポインタによってネイティブ関数を呼び出すことができます。この機能は、ネイティブ型の仮想関数を呼び出すために使用されます。COM インターフェイスは、仮想関数を持つネイティブ型であるためにこれが重要です。この方法による COM 相互運用は、他の言語で使用される別の COM 相互運用の技法に比べて多くの利点があります。

最後に、.NET を使用する開発者であれば、誰にとっても明らかである形態上の影響、つまりコンポーネントのファイル形式について説明します。Java は、独自のファイル形式 (.class ファイル) を定義します。一方、.NET Framework は標準 PE ファイル形式を使用します。このため、.NET ベースのコンポーネントのファイル拡張子は、扱いやすい DLL、および EXE となります。混在モードのアセンブリをサポートするには、PE ファイルが使用されているということが重要です。

ページのトップへ


2. C++ 相互運用機能の使用

C++ 相互運用機能がプロジェクトにおいて正常に機能するかどうかを評価するには、まず、プロジェクトをビルドする際のこの機能の影響、つまりコンパイラの設定を理解しておく必要があります。また、こうしたビルド プロセスの出力に及ぼされる影響も理解しておく必要があります。

/clr オプションを付けてコンパイルした場合、コードは、暗黙的に、C ランタイム ライブラリの (CRT) マルチスレッド DLL バージョンに依存します。つまり、プロジェクトのすべてのファイルは、/clr なしでコンパイルされたファイルも含めて、CRT のマルチスレッド DLL バージョンを使用することになります。MFC プロジェクトにおいては、この点も重要です。静的な MFC ライブラリは、/clr 付きでのコンパイルと互換性のない、静的な CRT ライブラリに依存します。このため、MFC プロジェクトが、必ず、MFC の DLL バージョンにリンクするようにする必要があります。

実行時間が問題となる可能性もあります。CLR の初期化には、ある程度の時間がかかり、JIT コンパイルもオーバーヘッドとなります。このため、起動時間が長くなる可能性があります。しかし、多くのプロジェクトでは、これが深刻な問題となることはありません。

マネージとアンマネージ間の移動を伴うメソッド呼び出しは、通常のメソッド呼び出しよりも時間が長くなります。前に説明したように、アプリケーションの特定の部分をマネージ コードにコンパイルすることにより、マネージ コードとアンマネージ コードの間の移動の回数を大幅に減らすことができます。呼び出し規則 __clrcall も、移動の回数を大幅に減らすことができる重要な最適化機能です。ここでは、この機能の詳細については省略します。

多くの場合、ジャストインタイム (JIT) コンパイルされたコード自体は、パフォーマンス低下の大きな原因となりません。実際には、JIT コンパイラでは可能であり、C++ コンパイラやリンカでは可能でない最適化がいくつかあります。たとえば、JIT コンパイラは、ターゲットとするマシンのプロセッサ アーキテクチャに合わせてコードを最適化することができます。C++ コンパイラとは異なり、JIT コンパイラは、コンポーネント間でのインライン化も提供できます。コンポーネント間でのインライン化を有効にしてコンパイルされたコードは、その後さらに最適化することが可能になるため、とても効果的な最適化です。

ここまでに説明した問題以外にも、いくつか影響のある部分がありますが、ここでは詳細な説明は省略します。問題の多くは、コンパイラ、およびリンカの設定を変更することですぐに簡単に解決できます。最適なコンパイラ設定が、常にわかりやすいとは言えません。設定の中には、C++ 相互運用機能コンパイルに必須の機能を有効にするための設定と、一方、/clr 付きでのコンパイルでは互換性のない機能、または Microsoft 中間言語では別の意味で解釈される機能を無効にするための設定とがあります。

ページのトップへ


3. MFC アプリケーションでの Windows フォーム コントロール

/clr オプション付きでコンパイルされるソース ファイルはすべて、.NET Framework 基本クラス ライブラリからの型を使用できます。この基本クラス ライブラリのほとんどの部分は、通常に使用する場合と同様に使用できます。ただし、特に注意が必要な場合もあります。このような例の 1 つが、Windows フォーム コントロールの MFC アプリケーションへの統合です。

CWnd* が想定される場合に、System::Windows::Forms::Control^ を渡すことはできません。しかし、Windows Forms API、および MFC は、共通の遺産である古き良き User32.dll を共有します。どちらの API も、HWND へのバックドアを提供します。MFC CWnd クラスは、HWND への変換演算子を提供します。Windows フォーム コントロール クラスは、名前空間 System::Windows::Forms からのインターフェイス IWin32Window を実装してウィンドウ ハンドルを提供します。

public interface IWin32Window {
  property IntPtr Handle {
    IntPtr get ();
  }
};

しかし、通常は、ウィンドウ オブジェクトからウィンドウ ハンドルを取得するだけでは不十分です。MFC には、さまざまなシナリオで Windows フォーム コントロールの処理をサポートするクラスがいくつかあります。これらのクラスは、afxwinforms.h で宣言されています。

これらのクラスの中でも、最も重要なのが CWinFormsControl です。これは、名前空間 Microsoft::VisualC::MFC で定義されています。このクラスによって、Windows フォーム コントロールを MFC ダイアログ、または CDialog 派生クラスでホストすることが可能になります。これは、1 つのテンプレート引数を持つテンプレート クラスです。引数は、ホストする Windows フォーム コントロールの型を渡すために使用されます。型は、後でインスタンス化する具象型、または具象型の基本クラスのどちらにすることも可能です。CWinFormsControl テンプレートは、とても単純です。注目すべき特徴は、CWnd を継承すること、および名前のとおりの機能を持つ CreateManagedControl メソッドの複数のオーバーロードを備えていることの 2 つです。

Windows フォーム コントロールを MFC ダイアログ、または CDialog 派生クラスでホストする場合は、クラスで CWinFormsControl<TWinFormsCtrl> 型のメンバ変数を定義し、CreateManagedControl のオーバーロードの 1 つを選択します。CDialog 派生クラスを実装する場合、マネージ コントロールの作成には、OnInitDialog が適切です。他のほとんどのクラスでは、OnCreate (WM_CREATE のメッセージ ハンドラ) が最適な選択肢となります。ダイアログ クラスの場合は、特に注意するオーバーロードが 1 つあります。このオーバーロードは、静的なプレースホルダ コントロールがダイアログ リソースに追加されることを想定しています。図 4 に示すように、Windows フォーム コントロールと同じ位置、および大きさの静的コントロールを配置し、それに固有 ID を指定します。ホストされるコントロールをインスタンス化するには、図 2 に示すように、CreateManagedControl を呼び出します。この代わりに、図 3 のように、Dialog Data Exchange (DDX) の特殊形を使用することもできます。

図 2 ホストされるコントロールの作成

using Microsoft::VisualC::MFC;
using System::Windows::Forms;
class CMyDialog : public CDialog
{
public:
    CMyDialog() : CDialog(CMyDialog::IDD, NULL) {}
    enum { IDD = IDD_MYDIALOG };
private:
    CWinFormsControl<Button> m_wfBtn;
public:
    BOOL OnInitDialog() {
        CDialog::OnInitDialog();
        return this->m_wfBtn.CreateManagedControl(
            WS_VISIBLE | WS_CHILD, IDC_WFBTN, this));
    }
}

図 3 Dialog Data Exchange によるコントロールのホスト

using Microsoft::VisualC::MFC;
using System::Windows::Forms;
class CMyDialog : public CDialog
{
public:
    CMyDialog(CWnd* pParent = NULL): CDialog(CMyDialog::IDD, pParent) {}
    enum { IDD = IDD_MYDIALOG };
private:
    CWinFormsControl<Button> m_wfBtn;
protected:
    virtual void DoDataExchange(CDataExchange* pDX) {
        DDX_ManagedControl(pDX, IDC_WFBTN, this->m_wfBtn);
    }
};

図 4 ActiveX コントロールの使用
図 4 ActiveX コントロールの使用

この方法により Windows フォーム コントロールを MFC アプリケーションでホストする場合は、Windows フォームと MFC のもう 1 つの共通の遺産である ActiveX コントロールを使用していることに注意してください。図 5 のリストからわかるように、System::Windows::Forms::Control は、OLE、および ActiveX コントロールの経験者によく知られているインターフェイスを多く実装しています。CreateManagedControl の中で、Windows フォーム コントロールは、gcnew 演算子によってインスタンス化され、通常の ActiveX コントロールと同じようにインプレースで起動されます。

図 5 インターフェイス

System::Windows::Forms::Control
IOleObject
IOleControl
IOleInPlaceObject
IOleInPlaceActiveObject
IOleWindow
IOleViewObject
IOleViewObject2
IPersist
IPersistStreamInit
IPersistPropertyBag
IPersistStorage
IQuickActivate

CWinFormsControl が、-> 演算子をオーバーロードし、ホストされるコントロールが戻されるようにしていることにも注意してください。これにより、次のように、単純な代入によってボタンの Text プロパティが初期化できます。

m_wfBtn.Text = "Click me!";

これは、コントロールのプロパティをプロパティ ウィンドウで変更する場合より簡単とまでは言えませんが、現在のところ存在する制限されたデザイナ サポートを使用する場合でも、Windows フォーム コントロールの世界が、信頼性の高い従来の MFC ダイアログ ボックスに開かれることになります。

ページのトップへ


4. コントロールのイベントの処理

ここで説明する必要のある点が、もう 1 つあります。今のままでは、まだボタンのクリック イベントが処理できません。Windows フォーム コントロールは、ActiveX コントロールのようにホストされるため、イベント処理には 2 つの選択肢があります。1 つは、ActiveX コントロールによって提供される接続ポイント、もう 1 つは、Windows フォーム コントロールのイベント メンバです。接続ポイントの方法は、Windows フォーム コントロールが、COM ベースのイベントをサポートするように準備されている場合にのみ正常に機能します。多くの Windows フォーム コントロールではこのような準備がされていないため、ここでは、.NET でのイベント処理が唯一の選択肢となります。マネージ クラスのイベントは、型メンバの特別な種類であり、イベント ハンドラ デリゲートの登録、および登録解除を可能にします。以下に、ボタンのクリック イベントを登録する方法を示します。

m_wfBtn.Click += <an event handler delegate for the click event>

このようなデリゲートを提供するには、次の構文を持つ関数が必要です。

void EventHandler(Object^ sender, EventArgs^ e);

しかし、このようなメソッドを CDialog 派生クラスで実装するには、これだけでは不十分です。デリゲートの対象となる関数は、マネージ クラスのメンバ関数であり、CDialog 派生クラスは、ネイティブです。Visual C++ には、この問題の汎用的な解決手段となる、ヘッダー ファイル \Msclr\Event.h があります。このヘッダー ファイルの定義は、.NET イベントを、ネイティブ C++ クラスのメソッドで処理するときに使用できます。後で説明しますが、このヘッダーは、Windows Presentation Foundation の Visual 機能を統合する場合にも使用できます。このヘッダーは、さまざまなシナリオで役に立つため、これが提供する手段について詳しく説明します。

Windows フォーム コントロールのイベントを処理するには、メッセージ ハンドラを含むマネージ クラスを実装する必要があります。C++ 相互運用機能を使用することにより、このマネージ クラスを CDialog 派生クラスにネストすることが可能なため、次のように、エレガントな方法でこれが実現できます。

class CMyDialog : public CDialog {
    ref class nested_managed_class {
        void OnWFBtnClick(Object^ sender, EventArgs^ e) { 
            // ボタンのクリック イベントをここで処理します。
        }
    };
// 他のメンバ

};

ボタンのクリック イベントを処理する最も便利な方法は、このメソッド呼び出しを、CDialog 派生クラスのメンバ関数に転送することです。クリック イベントを最終的に処理するメソッドは、想定されるイベント ハンドラの動作で、派生クラスの他のメンバに簡単にアクセスできます。デリゲートの対象を含むマネージ クラスは、最終的に、単純なプロキシ型となります。このようなプロキシ型、およびそのプロキシ イベント ハンドラは、Event.h で定義されるマクロで作成できますが、その前に、手動での定義について説明します (図 6 を参照)。

図 6 CMyDialog

class CMyDialog : public CDialog
{
public:
    ref class delegate_proxy_type;
    gcroot<delegate_proxy_type^> m_gc_managed_native_delegate_proxy;
    delegate_proxy_type^ get_proxy(CMyDialog* pNativeTarget) {
        if((delegate_proxy_type^)m_gc_managed_native_delegate_proxy == 
            nullptr)
        {
            m_gc_managed_native_delegate_proxy = 
                gcnew delegate_proxy_type(pNativeTarget);
        }
        return (delegate_proxy_type^)m_gc_managed_native_delegate_proxy; 
    }
ref class delegate_proxy_type {
    CMyDialog* m_p_native_target;
public:
    delegate_proxy_type(CMyDialog* pNativeTarget) :
        m_p_native_target(pNativeTarget) {}
    void OnWFBtnClick(Object^ sender, EventArgs^ e) {
        this-&gt;m_p_native_target-&gt;OnWFBtnClick(sender, e);
    }
};

void OnWFBtnClick(Object^ sender, EventArgs^ e) {
    this-&gt;m_wfBtn-&gt;Text = &quot;Thanks for clicking&quot;;
}

// 他のメンバ

};

このコードからわかるように、デリゲート プロキシには、CMyDialog* を引数として受け取るコンストラクタがあります。このようなコンストラクタは、メソッド呼び出しを CDialog 派生クラスに転送するために必要です。このデリゲート プロキシ型は、ダイアログ クラスのすべての Windows フォーム コントロールのすべてのイベント ハンドラに対して使用できます。delegate_proxy_type のメンバ変数は、派生クラスに追加できません。ネイティブ クラスは、ガベージ コレクションされるオブジェクトへのハンドルをフィールドとして持つことができないためです。ここでは、gcroot テンプレートを使用して、この制限を回避できます。このためには、gcroot<delegate_proxy_type> 型のメンバ変数が必要です。しかし、まだ疑問が残ります。デリゲート プロキシ型への gcroot の参照は、どのように適切に初期化できるでしょうか。これは、delegate_proxy_type へのハンドルが必要となるときに必ず呼び出される別のメソッドで処理されます。

図 6 のコードにより、デリゲートが、次のサンプルのように簡単に登録できます。

virtual BOOL OnInitDialog() {
    CDialog::OnInitDialog();
this-&gt;m_wfBtn-&gt;Click += 
    gcnew System::EventHandler(get_proxy(this), 
    &amp;delegate_proxy_type::OnWFBtnClick);

return TRUE;

}

このコードをすべて作成すると、Windows フォーム コントロールのイベントを CDialog 派生クラスで単純に処理するだけでも相当な作業が必要です。Event.h には、同様の処理を、図 7 に示すように、より少ない行で実現するマクロ、およびテンプレートが含まれています。

図 7 簡略化された CMyDialog

class CMyDialog : public CDialog
{
public:
    CMyDialog(CWnd* pParent = NULL): CDialog(CMyDialog::IDD, pParent) {}
enum { IDD = IDD_MYDIALOG };

private: CWinFormsControl<Button> m_wfBtn;

protected: virtual void DoDataExchange(CDataExchange* pDX) { DDX_ManagedControl(pDX, IDC_WFBTN, this->m_wfBtn); }

public: BEGIN_DELEGATE_MAP(CMyDialog) EVENT_DELEGATE_ENTRY(OnWFBtnClick, Object^, EventArgs^) END_DELEGATE_MAP()

void OnWFBtnClick(Object^ sender, EventArgs^ e) {
    this-&gt;m_wfBtn-&gt;Text = &quot;Thanks for clicking&quot;;
}

public: virtual BOOL OnInitDialog(){ CDialog::OnInitDialog();

    this-&gt;m_wfBtn-&gt;Click += MAKE_DELEGATE(System::EventHandler, 
        OnWFBtnClick);
    return TRUE;
}

};

MFC のバージョンが新しくなるごとに、少なくとも 1 つ新しいマップがもたらされています。現在のバージョンには、デリゲート マップが導入されています。マクロ BEGIN_DELEGATE_MAP は、デリゲートの対象を含む delegate_proxy_type を定義します。EVENT_DELEGATE_ENTRY の 1 行で、このような対象となるメソッドを 1 つずつマネージ クラスに追加します。END_DELEGATE_MAP は、単純にマネージ クラスを終了しています。最後に、MAKE_DELEGATE で、対象となる delegate_proxy_type のメソッドを参照するデリゲートをインスタンス化します。実際には、マクロは、ここで説明した以外の処理も行います。マクロは、既に存在していないネイティブ オブジェクトに送信されるイベントをマネージ クラスで発生させるようなシナリオにも対応しています。

ページのトップへ


5. ダイアログ ボックスとしてのコントロール

テンプレート CWinFormsDialog も、簡単で役に立つヘルパです。これは、Windows フォーム コントロールをモーダル、または モードレスのダイアログ ボックスとして表示するために使用します。このクラスは、とても単純です。これは、CDialog を拡張しているため、MFC ダイアログ ボックスの標準的な動作を提供します。さらに、これは、CWinFormsControl メンバ変数を使用して、Windows フォーム コントロールをホストし、ダイアログ ボックスのキャプションをホストされるコントロールの Text プロパティで初期化します。また、ダイアログの初期サイズは、コントロールに完全に合うように設定され、ホストされるコントロールのサイズは、親ウィンドウのサイズの変更に応じて調整されるようになります。

次の 1 行は、YourWinFormsDlgControl をホストする一時ダイアログ ボックス オブジェクトをインスタンス化し、それをモーダル ダイアログ ボックスとして表示しています。

CWinFormsDialog<YourWinFormsDlgControl>().DoModal();

しかし、この方法は、DoModal を使用するダイアログの戻り値を取得する手段がないのでお勧めできません。実用的なシナリオでは、CWinFormsDialog<YourWinFormsDlgControl> から独自のクラスを派生させることを検討します。派生させることにより、OnInitDialog をオーバーライドし、ネストされた Windows フォーム コントロールのプロパティを設定したり、ホストされるコントロールのイベントを処理したりすることが可能になります。この場合も、前に説明した Event.h のマクロが役立ちます。詳細については、https://msdn.microsoft.com/ja-jp/library/ahdd1h97.aspx を参照してください。

ページのトップへ


6. Windows フォーム ビュー

最後に、Windows フォーム コントロールをビューとしてホストできることを説明しましょう。CWinFormsView は、このシナリオで必要となる簡単なヘルパです。CWinFormsDialog と異なり、これは、テンプレート クラスではありません。しかし、重要な類似点があります。どちらの場合も、コントロールの実際のホストは、CWinFormsControl テンプレートが行い、ホストされるコントロールのサイズ変更を処理できます。

Windows フォーム コントロールを MFC ダイアログ ボックスとしてホストするには、CWinFormsView からビュー クラスを継承する必要があります。次のコードは、CWinFormsView に基づく単純なビュー クラスの宣言です。

class CMyWinFormsBasedView : public CWinFormsView
{
    CMyWinFormsBasedView ();
    DECLARE_DYNCREATE(CMyWinFormsBasedView)
};

ウィザードでビュー クラスを生成した場合には、ビューのクラス宣言の基本クラス リストだけでなく、その実装でも、CWinFormsView への切り替えを確実にする必要があります。このためには、IMPLEMENT_DYNAMIC、および BEGIN_MESSAGE_MAP などのマクロのパラメータを変更します。ホストされる Windows フォーム コントロールをインスタンス化するには、CWinFormsView (作成するビューの基本クラス) のコンストラクタで、Windows フォーム コントロールの System::Type オブジェクトへのハンドルを取得します。ビュー クラスの実装も簡単です。次のようにします。

IMPLEMENT_DYNCREATE(CMyWinFormsBasedView, CWinFormsView)

CMyWinFormsBasedView:: CMyWinFormsBasedView () : CWinFormsView(WinFormsViewControl::typeid) {}

このコードは、一見、実際のシナリオには単純過ぎるように思えますが、多くの場合、実際に必要となるのはこれだけです。ビュー クラスは、ビューのコントロールに存在する実際の実装の単純なプロキシとして機能します。

多くのビューの実装では、MFC CView 基本クラスの仮想関数をオーバーライドします。ネイティブ ビュー クラスを単純なプロキシとして機能させるために、ネイティブ ビュー クラス内のこれらのメソッドをオーバーライドし、呼び出しを、Windows フォーム コントロール内の同等のメソッドに転送することができます。CView の最も重要な 3 つのオーバーライドでは、このような転送は、既に、CWinFormsView で実装されています。3 つのメソッドとは、OnInitialUpdate、OnUpdate、および OnActivateView です。転送は、アセンブリ Mfcmifc80.dll で定義されるマネージ インターフェイス Microsoft::VisualC::MFC::IView に基づきます。これらのメソッドをオーバーライドするには、図 8 に示すように、このインターフェイスをビューの Windows フォーム コントロール クラスで実装します。

図 8 IView のオーバーライド

#using <mfcmifc80.dll>
using Microsoft::VisualC::MFC::IView;

public ref class MyWinFormsViewControl : public System::Windows::Forms::UserControl, public IView { ... protected: // IView の実装 virtual void OnInitialUpdate() = IView::OnInitialUpdate { // 実装 }

virtual void OnUpdate() = IView::OnUpdate {
    // 実装
}

virtual void OnActivateView(bool activate) = IView::OnActivateView {
    // 実装
}

};

ビューでは、仮想関数のオーバーライドだけでなく、コマンド ハンドラの実装も可能です。仮想関数の場合と同じように、コマンド ハンドラをネイティブ ビュー クラスで実装し、Windows フォーム コントロールに転送する方法も可能です。しかし、コマンド ハンドラの実装は、よく必要とされるシナリオであるため、MFC には、より便利なソリューションが用意されています。Mfcmifc80.dll アセンブリには、このシナリオのために、マネージ ヘルパ型がいくつか含まれています。MFC のコマンド ルーティング インフラストラクチャからコマンド メッセージを受け取るには、次のように、Windows フォーム コントロールで Microsoft::VisualC::MFC::ICommandTarget インターフェイスを実装する必要があります。

interface class ICommandTarget
{
    void Initialize(ICommandSource^ commandSource);
};

ICommandTarget を実装するビューを作成する場合は、そのビューの ICommandTarget::Initialize を呼び出し、新しいコマンド ソース オブジェクトへのハンドルを引数として渡します。この引数の型は、ICommandSource^ です。引数が参照するオブジェクトは、複数のハンドラを含む 1 つのコンテナです。これらのハンドラは、MFC 開発者がよく使用するものです。これらは、コマンドが発行されたときに呼び出されるコマンド ハンドラ、およびコマンド UI ハンドラです。コマンド UI ハンドラは、コントロールの UI を有効にするか、チェックするか、またはラジオ ボタンでのチェックにするか、およびコマンドの UI にどのような文字を表示するかを制御できます。

ハンドラを登録するには、コマンド ソースの AddCommandHandler、および AddCommandUIHandler などのメソッドを呼び出します。これらのメソッドは、2 つの引数を受け取ります。コマンド ID を示す符号なし整数と、コントロールのハンドラ関数を渡すために使用されるデリゲートです。コマンド ハンドラとコマンド UI ハンドラのシグネチャは異なるため、名前空間 Microsoft::VisualC::MFC には、次のように、2 つの異なる型が定義されています。

delegate void CommandHandler(unsigned int);
delegate void CommandUIHandler(unsigned int,
    Microsoft::VisualC::MFC::ICommandUI^);

図 9 に、ID_EDIT_PASTE コマンドのコマンド ハンドラを登録する方法を示します。

図 9 コマンド ハンドラの登録

public ref class MyWinFormsViewControl : 
    public System::Windows::Forms::UserControl, 
    public ICommandTarget
{
...
protected: // ICommandTarget の実装
    virtual void RegisterCmdHandlers(ICommandSource^ cmdSrc) = 
        ICommandTarget::Initialize
    {
        cmdSrc->AddCommandHandler(ID_EDIT_PASTE, 
            gcnew CommandHandler(this, &WinFormsView::OnEditPaste));
        cmdSrc->AddCommandUIHandler(ID_EDIT_PASTE, 
            gcnew CommandUIHandler(this, 
            &WinFormsView::OnUpdateEditPaste));
    // 他のコマンド ハンドラを登録します...
}

void OnEditPaste(unsigned int) 
{
    // コマンド ハンドラを実装します。
}

void OnUpdateEditPaste(unsigned int, ICommandUI^ cmdUI)
{
    cmdUI-&gt;Enabled = ...; // 独自のロジックをここに置きます。
}

};

ビューでコマンド処理をサポートする場合は、ビューのメンバ関数を呼び出すためにデリゲートが使用されます。コマンドの ID は、引数として渡されます。すべてのコマンドにそれぞれのプライベート ハンドラ関数がある場合、この引数は重要ではありません。しかし、1 つのメソッドを、複数のコマンドのハンドラとして使用する場合があります。AddCommand[UI]RangeHandler で、複数のコマンドに 1 つのデリゲートを登録することができます。

コマンド UI ハンドラのデリゲートには、この他に、ICommandUI^ 型の引数があります。このパラメータを使用して渡されるハンドルは、MFC CCmdUI クラスのラッパーです。このクラスは、メニュー項目、およびツール バー ボタンなどのインターフェイス要素の外観、および有用性を制御します。

ICommandSource::Initialize でコマンド ハンドラを追加する以外に、ICommandTarget ハンドルを Windows フォーム コントロールのメンバ変数に保管することも有用な場合があります。この方法では、後で別のコマンド ハンドラを追加、または削除するオプションが提供されます。また、ICommandTarget::SendMessage、または ICommandTarget::PostMessage によって同期的、または非同期的にコマンド イベントを発生させることもできます。

ページのトップへ


7. Avalon のための準備

Windows Presentation Foundation がまだ最終的な形ではリリースされていないため、現在のバージョンの MFC には、Visual と、CWnds や CDialogs と統合するためのヘルパ クラス、およびテンプレートがありません。これは、Visual の MFC アプリケーションへの統合が不可能であることを意味しているわけではありません。実際に、これらのヘルパ クラスがなくても、Visual を CWnd または CDialog でホストするために必要なコードはほんの数行です。

このような統合で重要となる機能は、Windows Presentation Foundation 自体から提供されます。アセンブリ Windows.PresentationCore.dll は、名前空間 System::Windows::Interop で、強力なクラス HwndSource を提供します。このクラスは、HWND と Visual をつなぐ機能を提供します。ホストする USER32 ウィンドウ オブジェクトからは、Visual が、通常の HWND を持つ子ウィンドウのように見えます。RootVisual プロパティを使用することで、Windows Presentation Foundation の新しい世界に切り替えることができます。(ここでの情報は、Windows Presentation Foundation のプレリリース版に基づいており、最終的なリリースでは変更になる可能性があります。)

HwndSource コンストラクタは、引数として HwndSourceParameters 値型を受け取ります。この値型に含まれる情報は、Win32 関数 CreateWindowEx を呼び出す際に渡す引数と似ています。図 10 を参照してください。図 11 に、OnInitDialog の実装で HwndSource を使用する方法を示します。

図 10 CreateWindowEx と HwndSourceParameters

CreateWindowEx 引数 HwndSourceParameters プロパティ
LPCTSTR lpClassName なし
なし WindowClassStyle
LPCTSTR lpWindowName WindowName
DWORD dwStyle WindowStyle
DWORD dwExStyle ExtendedWindowStyle
int x PositionX
int y PositionY
int nWidth Width
int nHeight Height
HWND hWndParent ParentWindow
HMENU hMenu なし
HINSTANCE hInstance なし

図 11 HwndSource の使用

BOOL CWPFDemoDialog::OnInitDialog()
{
    __super::OnInitDialog();
    
    CRect rectClient;
    this->GetClientRect(&rectClient);
System::Windows::Interop::HwndSourceParameters hwsPars;
hwsPars.ParentWindow = System::IntPtr(this-&gt;m_hWnd);
hwsPars.WindowStyle = WS_CHILD | WS_VISIBLE;
hwsPars.PositionX = 0;
hwsPars.PositionY = 0;
hwsPars.Width = rectClient.Width();
hwsPars.Height = rectClient.Height();
System::Windows::Interop::HwndSource^ hws;
hws = gcnew System::Windows::Interop::HwndSource(hwsPars);

using System::Windows::Controls::MonthCalendar;
MonthCalendar^ mc = gcnew MonthCalendar;    
hws-&gt;RootVisual = mc;

return TRUE;

}

ページのトップへ


8. まとめ

C++ 相互運用機能を使用することにより、マネージ コードを、既存の C++ ソースにシームレスに統合することができます。MFC での Windows フォームに対するサポートから、この統合が、Visual C++ で意図的にサポートされている機能であることがはっきりとわかります。この統合を簡単に行うために、いくつかのヘルパ クラス、およびテンプレートが存在します。この統合のための層の使用は、複雑でも、困難でもありません。

不足している点を挙げるとすれば、それは、これらの機能が、Visual Studio のウィザードにはシームレスに統合されていない点です。多くのシナリオでは、実装のほとんどが、ホストされる Windows フォーム コントロールに残るため、これが大きな問題となることはありません。Windows フォーム コントロールには、Visual Studio でのすばらしいデザイナ サポートがあります。Windows Presentation Foundation が出荷されるときには、Visual と MFC アプリケーションの統合にも、同等のサポートが提供される可能性があります。しかし、そのようなサポートがなくても、Visual を MFC アプリケーションでホストすることは難しくありません。


Marcus Heege は、DevelopMentor のコース作成者、およびトレーナーとして活躍しています。また、さまざまな IT 企業へのコンサルティングも行っています。彼は、C++/CLI、および .NET に関する考えを、彼のブログ www.heege.net に掲載しています。


この記事は、MSDN マガジン - 2006 年 5 月からの翻訳です。

QJ: 060503

ページのトップへ