執筆者: エディフィストラーニング株式会社 矢嶋 聡

動作確認環境: Visual Studio 2010 (MFC を利用できるエディション、Express よりも上位のエディション)、検証環境は Windows 7


ここでは、以下の例 1 ~例 4 に示す MFC ベースのダイアログ ボックス クラス (ネイティブ コード) を題材にして、このクラスを .NET 対応プログラムから利用できるようするためのサンプル コードを示します (プログラム実行の様子は後述の図 8.2 を参照)。このサンプルでは、C++/CLI を用いて、MFC の初期化、MFC で使用するリソースを持つモジュールの指定、および .NET 版向けマネージ コードのラッパー クラス作成を行います。ここでは、ダイアログ ボックス用の MFC クラスを用いていますが、このサンプルで使用する手法は、他の MFC クラスでも利用できるでしょう。

例 1. ファイル: MyMFCDlg.h

C++
スクリプトの編集|Remove
#pragma once 
#include "MyMFCDlgRes.h" 
 
// CMyMFCDlg ダイアログボックス 
class CMyMFCDlg : public CDialog 
{ 
// コンストラクション 
public: 
    CMyMFCDlg(CWnd* pParent = NULL);     // 標準コンストラクター 
 
// ダイアログ データ 
    enum { IDD = IDD_MYMFCDLG_DIALOG };  // ダイアログのリソースID 
 
protected: 
    virtual void DoDataExchange(CDataExchange* pDX);  // DDX/DDV サポート 
 
// 実装 
public: 
    CString m_strEdit1; 
};
 

例 2. ファイル: MyMFCDlg.cpp

C++
スクリプトの編集|Remove
#include "Stdafx.h" 
#include "MyMFCDlg.h" 
 
CMyMFCDlg::CMyMFCDlg(CWnd* pParent /*=NULL*/) 
    : CDialog(CMyMFCDlg::IDD, pParent) 
    , m_strEdit1(_T("")) 
{ 
} 
 
void CMyMFCDlg::DoDataExchange(CDataExchange* pDX) 
{ 
    CDialog::DoDataExchange(pDX); 
    DDX_Text(pDX, IDC_EDIT1, m_strEdit1); 
}
 

例 3. ファイル: MyMFCDlgRes.h

 

C++
スクリプトの編集|Remove
#pragma once 
 
#define IDD_MYMFCDLG_DIALOG 5001 
#define IDC_EDIT1 7001
 

例 4. ファイル: MyMFCDlgRes.rc

C++
スクリプトの編集|Remove
#include <afxres.h> 
#include "MyMFCDlgRes.h" 
 
IDD_MYMFCDLG_DIALOG DIALOGEX 0019267 
STYLE DS_SETFONT | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME 
CAPTION "My MFC Dialog" 
FONT 9"MS UI Gothic"00, 0x1 
BEGIN 
    EDITTEXT        IDC_EDIT1,8,8,164,14,ES_AUTOHSCROLL 
    DEFPUSHBUTTON   "OK",IDOK,73,45,50,14 
    PUSHBUTTON      "キャンセル",IDCANCEL,134,45,50,14 
END
 

これから取り上げるマネージ コード版のラッパー クラスでは、その内部でネイティブ コード向けの C++ を用いて、MFC クラスにアクセスします。そのために、C++/CLI のソース コードと従来の C++ のソース コードが混在するハイブリッド型のプロジェクトが必要となります。最初に、次の手順でプロジェクトを用意します。

  1. .NET 向け (CLR 向け) の「クラス ライブラリ」プロジェクト テンプレートを用いて、プロジェクトを新規に作成します。ここでは、プロジェクト名を「MyMFCInterop」とします。
  2. このプロジェクトを MFC 対応にするため、プロジェクトのプロパティ ページを表示させ (ソリューション エクスプローラー上で、MyMFCInterop プロジェクト ノードを右クリックして [プロパティ] をクリック)、左のツリーでは、[構成プロパティ] ノード配下の [全般] を選択します。このときの右ペインでは、「MFC の使用」欄を「共有 DLL で MFC を使う」を選択します (図 8.1)。なお、「共通言語ランタイム サポート」欄が、「共通言語ランタイム サポート (/clr)」になっていることを確認します。この設定によってハイブリッド型になります。

    図 8.1 MFC 対応およびハイブリッド型の設定

    Note:

    ここでは簡単にするため、文字セットについては、既定の Unicode 文字セットを使用することを前提にしています。また、ターゲット プラットフォームも既定の x86 を使用しています。

  3. MFC ライブラリを利用可能にするため、ヘッダー Stdafx.h を次のように修正します (_WIN32_WINNT マクロに定義されて数値は、ターゲット OS のバージョンに合わせて変更してください)。

    例 5. ファイル: Stdafx.h

    C++
    スクリプトの編集|Remove
    #pragma once 
     
    #define _WIN32_WINNT  0x0601 // Windows 7 を設定しています 
    #include <afxwin.h>          // MFC のコアおよび標準コンポーネント 
    #include <afxext.h>          // MFC の拡張部分
     
  4. 前述の 4 つのファイル (MyMFCDlg.h、MyMFCDlg.cpp、MyMFCDlgRes.h、ならびに MyMFCDlgRes.rc) を MyMFCInterop プロジェクトに追加します(まだ不完全ですが、ここまででビルドすることができます)。

    これで、ハイブリッド型のプロジェクトに、再利用したい MFC ベースのクラスが追加されました。あとは、このクラスを使用するマネージ ラッパーを作成するほか、MFC を使用する上で必要な初期化のコードを追加します。

    プロジェクトの既存のファイル「MyMFCInterop.h」に、次に示すように、2 種類のマネージ版のクラスを追加してください。

    例 6. ファイル: MyMFCInterop.h

    C++
    スクリプトの編集|Remove
    #pragma once 
    #include "MyMFCDlg.h" 
     
    using namespace System; 
    using namespace System::Runtime::InteropServices;  
     
    namespace MyMFCInterop { 
     
        // MFC環境で必要なグローバルオブジェクト 
        CWinApp app;  //←[1] 
     
        public ref class CMFCUtil abstract sealed  //←[2] 
        { 
        private: 
            static bool isInitialized = false//初期化済みの判定フラグ ←[3] 
        internal: 
            // MFCライブラリの初期化 
            static bool Initialize()  //←[4] 
            { 
                if(isInitialized) return true//既に初期化済みの場合は何もしない ←[5] 
     
                HMODULE hAppModule = ::GetModuleHandle(NULL);                    //←[6] 
                HMODULE hDllModule = ::GetModuleHandle(_T("MyMFCInterop.dll"));  //←[7] 
                if (hAppModule == NULL || hDllModule == NULL) 
                    return false; 
     
                // MFCライブラリの初期化 
                if( ! ::AfxWinInit(hAppModule, NULL, ::GetCommandLine(), 0) )    //←[8] 
                    return false; 
                // リソースを含むモジュールのハンドルを指定 
                ::AfxSetResourceHandle(hDllModule);    //←[9] 
     
                isInitialized = true//←[10] 
     
                return true; 
            } 
        }; 
     
        // MFC版ダイアログのマネージラッパー 
        public ref class CMyWrapperDlg    //←[11] 
        { 
        private: 
            // MFC版のネイティブダイアログ 
            CMyMFCDlg* m_pNativeDlg;      //←[12] 
        public: 
            // コンストラクター 
            CMyWrapperDlg() 
            { 
                if( ! CMFCUtil::Initialize() ) //念のため、MFCライブラリの初期化 //←[13] 
                { 
                    System::GC::SuppressFinalize(this);  //ファィナライザー呼び出しの抑止 
                    throw gcnew Exception("MFCライブラリの初期化に失敗ました。"); 
                } 
     
                m_pNativeDlg = new CMyMFCDlg(); 
            } 
        public: 
            // データのやり取り 
            property String^ EDIT1       //←[14] 
            { 
                String^ get()            //←[15] 
                { 
                    LPCWSTR pStrEdit1 = (LPCWSTR) m_pNativeDlg->m_strEdit1;  //←[16] 
                    String ^str1 = 
                        Marshal::PtrToStringUni(IntPtr((Int32)pStrEdit1)); //32ビット環境が前提 
                    return str1; 
                } 
                void set(String^ value)  //←[17] 
                { 
                    IntPtr p1; 
                    p1 = Marshal::StringToHGlobalUni( value ); 
                    m_pNativeDlg->m_strEdit1 = (LPCWSTR) p1.ToPointer(); 
                    Marshal::FreeHGlobal(p1);  
                } 
            } 
        public: 
            // モーダル表示 
            bool ShowDialog()    //←[18] 
            { 
                INT_PTR result; 
                result = m_pNativeDlg->DoModal();   //←[19] 
                switch(result) 
                { 
                case IDOK:     return true; 
                case IDCANCEL: return false; 
                default:       ASSERT(FALSE); 
                } 
                return false; 
            } 
        public: 
            //デストラクター 
            ~CMyWrapperDlg()    //←[20] 
            { 
                this->!CMyWrapperDlg(); 
            } 
            //ファイナライザー 
            !CMyWrapperDlg()    //←[21] 
            { 
                delete m_pNativeDlg; 
            } 
        }; 
    }
     

    この MyMFCInterop プロジェクトのソース コードはこれで全部です。これをビルドすると、.NET 対応の DLL ファイル (アセンブリ ファイル) MyMFCInterop.dll が出力されます。

    この例では、MFC を使用する上で必要な初期化などを行う [2] の CMFCUtil クラスと、MFC ベースのダイアログ ボックスをラップした [11] の CMyWrapperDlg クラスが定義されています。なお、[2] のCMFCUtil クラスは、[4] の静的メンバー関数 (メソッド) を持つユーティリティ クラス (インスタンスを作成しないクラス) なので、[4] のクラス定義には、abstract 修飾子と sealed 修飾子が付いています。

    MFC を使用する際に必要となる初期化は [4] の Initialize メンバー関数の中に記述されています。特に重要な点は、[8] の AfxWinInit 関数呼び出しと、[9] の AfxSetResourceHandle 関数呼び出しです。ここで、この 2 つの関数の役割について確認しましょう。

    なお、ここでは Initialize メソッドが 2 度呼びだれても問題ないように、[3] のフラグで初期化済みか否かの判定をして、初期化済みの場合は、[5] のように初期化をスキップするようにしました。この実装を外して簡単にするのであれば、このフラグに係る部分、つまり、[3]、[5]、および、[10] は削除してもかまいません。

    Note:

    このサンプルのフラグの実装は、シングル スレッドを前提にしており、スレッド セーフではありません。

    まず、AfxWinInit 関数です。通常の MFC アプリケーションでは、MFC ライブラリが提供する WinMain 関数 (メイン関数) の中から、AfxWinInit 関数を呼び出して初期化を行っています。そのため、純粋な MFC アプリケーションでは、プログラマーが明示的に AfxWinInit 関数を呼び出す必要はありません。しかし、今回のクラス ライブラリ プロジェクト (MyMFCInterop.dll) は .NET から利用されることを想定しており、.NET 側の Main 関数 (メイン メソッド) が使用されるので、[8] のようにプログラマーが明示的に AfxWinInit 関数を呼び出す必要があります。

    なお、第 1 引数 (hAppModule) のモジュール ハンドルは、[6] の GetModuleHandle 関数を呼び出して取得しています。[6] の GetModuleHandle 関数の引数は NULL になっているので、ここで返されるハンドルは、プロセスを作成した EXE ファイルのモジュール ハンドル (アプリケーションのインスタンス ハンドル) です。

    また、AfxWinInit 関数を呼び出すためには、[1] のように CWinApp (または、その派生クラス) のインスタンスを、グローバル オブジェクトとして宣言しておく必要があります。

    一方、[9] の AfxSetResourceHandle 関数では、MFC ライブラリなどが使用するリソースを含むモジュールのハンドルを指定します。このハンドルは、[7] の GetModuleHandle 関数呼び出しによって取得しており、[7] の引数が示すように、このクラス ライブラリ自身の MyMFCInterop.dll のハンドルを取得しています。この DLL のハンドルを [9] の AfxSetResourceHandle 関数呼び出しに指定することで、MFC ライブラリは必要なリソースをこの DLL からロードできるようになります。たとえば、この MyMFCInterop.dll に含まれる、例 1 の CMyMFCDlg ダイアログ ボックス クラスも、この DLL に含まれる例 4 のダイアログ リソースを読み込むことができます。逆に言えば、[9] で適切なハンドルを指定しないと、正しくリソースを読み込むことができません。

    次に、[11] のラッパー クラス CMyWrapperDlg を確認します。このクラスの内部では、[12] の m_pNativeDlg メンバー変数 (ポインター) を使用して、ネイティブ コード版のCMyMFGDlg ダイアログ ボックス クラスのオブジェクトを管理しています。

    また、この例では、この CMyWrapperDlg クラスを使用する利用者が、MFC ライブラリの初期化を意識せずに済むように、コンストラクターの中では [13] のように、MFC ライブラリの初期化を行っています。万が一、MFC ライブラリの初期化に失敗し、[13] の Initialize メンバー関数呼び出しの戻り値が false の場合は、if ブロックの中に示したように、ファィナライザーの呼び出しが不要なので抑止したのち、例外を発生することにしています。(本来であれば、MFC 初期化失敗を意味する独自の Exception 派生クラスをスローすべきですが、ここでは簡単にするため、Exception クラスをスローしています。)

    なお、このコンストラクターの実装を簡単にする方法としては、[13] の if ブロックを削除して、代わりに、CMFCUtil::Initialize メンバー関数をパブリックとして公開し、利用者が明示的に呼び出す方法もあります。

    [14] の EDIT1 プロパティは、CMyMFGDlg ダイアログ ボックスの m_strEdit1 メンバー変数 (EDIT コントロールのデータに相当) にアクセスするためのラッパーです。[15] の get アクセサーの中では、[16] のように m_strEdit1 メンバー変数から、ネイティブ文字列のポインターを取得したのち、String 型マネージ オブジェクトに変換して返しています。[17] の set アクセサーでは、この逆の変換を行っています。

    Note:

    ネイティブ文字列と String 型マネージ オブジェクトとの変換については、以下の記事にも記載があります。

    モーダル ダイアログを表示しているのは、[18] の ShowDialog メンバー関数であり、[19] にあるようにネイティブ版のダイアログ ボックスの DoModal メンバー関数を呼び出しています。

    また、後処理としては、プログラマーが .NET 側から明示的に呼び出す [20] のデストラクター (IDisposable インターフェイスの Dispose メソッド) と、ガベージ コレクションによって呼び出される [21] のファイナライザー (Finalize メソッド) という 2 種類があります。ファイナライザーが呼び出される際には、マネージ関連の後処理はガベージ コレクションによって全て自動的に行われるので、プログラマーが行うべき後処理は、ネイティブ オブジェクトの後処理だけです。よって、[21] のファイナライザーの内部でも、「delete m_pNativeDlg;」のように、ネイティブ版のダイアログ ボックスのオブジェクトのみ削除しています。また、いずれにしても、この例ではマネージ オブジェクトに関して後処理は必要ないので、[20] のデストラクターの内部でも、「this->!CMyWrapperDlg();」というように、ファイナライザーを呼び出して、同様にネイティブ オブジェクトを削除しています。

    Note:

    デストラクターとファイナライザーで行うべき実装については、以下の記事にも記載があります。

    この MyMFCInterop.dll を C# のコンソール アプリケーションから使用する場合、次のようなコードが考えられます。

    例 7. (参考) C# からの利用

    C++
    スクリプトの編集|Remove
    using System; 
    using System.Windows.Formsusing MyMFCInterop; 
     
    namespace CSConApp 
    { 
        class Program 
        { 
            static void Main(string[] args) 
            { 
                 
     
                CMyWrapperDlg dlg = null; 
                string msg; 
                try 
                { 
                    dlg = new CMyWrapperDlg();  //CMyWrapperDlgインスタンス作成 
                } 
                catch(Exception ex) 
                { 
                    MessageBox.Show(ex.Message);  //C#側でのメッセージボックス 
                    return; 
                } 
                dlg.EDIT1 = "テストデータ";  //CMyWrapperDlg::EDIT1プロパティに設定 
                if (dlg.ShowDialog())       //CMyWrapperDlg::ShowModalメンバー関数呼び出し 
                    msg = dlg.EDIT1;        //CMyWrapperDlg::EDIT1プロパティを参照 
                else 
                    msg = "キャンセルしました。"; 
                MessageBox.Show(msg, "Managed MessageBox");  //C#側でのメッセージボックス 
     
                dlg.Dispose();              //CMyWrapperDlg::~CMyWrapperDlg呼び出し 
            } 
        } 
    }
     

    これを実行すると、Main メソッド内の最後の ShowDialog メンバー関数を呼び出した際に、次図のように MFC 版のダイアログ ボックスが表示されます。

    図 8.2 C# のプログラム コードから呼び出された MFC 版のダイアログ ボックス


Code Recipe Visual C++ デベロッパー センター

 

ページのトップへ