テクニカル ノート 11: DLL の構成要素としての MFC
ここでは、MFC ライブラリを Windows ダイナミック リンク ライブラリ (DLL) の一部として使えるようにする標準 DLL について説明します。 このテクニカル ノートは、Windows DLL とそのビルド方法に精通したプログラマを対象としています。 MFC ライブラリの拡張機能を作成するための MFC 拡張 DLL の詳細については、「テクニカル ノート 33: MFC の DLL バージョン」を参照してください。
DLL インターフェイス
標準 DLL では、アプリケーションと DLL の間のインターフェイスは、C スタイルの関数または明示的にエクスポートしたクラスで指定されることが前提となります。 MFC のクラス インターフェイスはエクスポートできません。
DLL とアプリケーションの両方で MFC を使用する場合は、共有バージョンの MFC ライブラリを使用するか、ライブラリのコピーに静的にリンクするかをそれぞれが選択できます。 アプリケーションと DLL の両方が、標準バージョンの MFC ライブラリの 1 つを使用することもあります。
標準 DLL には次のような利点があります。
DLL を使用するアプリケーションは、MFC を使用する必要がありません。また、Visual C++ アプリケーションでなくてもかまいません。
MFC と静的にリンクする標準 DLL のサイズは、使用されてリンクされる MFC と C のランタイム ルーチンにのみ依存します。
MFC と動的にリンクする標準 DLL では、MFC の共有バージョンを使用することで大幅なメモリの節約を期待できます。 ただし、共有 DLL の Mfc<version>.dll と Msvvcrt<version>.dll を DLL と一緒に配布する必要があります。
DLL のデザインはクラスの実装方法に依存しません。 独自のデザインの DLL では、必要な API だけをエクスポートします。 そのため、実装が変わっても、標準 DLL は同じように使用できます。
MFC と静的にリンクする標準 DLL では、DLL とアプリケーションの両方で MFC を使用する場合に、MFC のバージョンがアプリケーションと DLL で異なっていても問題はありません。 MFC ライブラリは各 DLL または EXE と静的にリンクされるので、どのバージョンを使っても問題はありません。
API の制約
MFC の機能には、DLL バージョンでは使用できないものがあります。これは、技術上の制約による場合と、そのサービスが通常はアプリケーションから提供されるためという理由による場合があります。 現在のバージョンの MFC では、使用できない関数は CWinApp::SetDialogBkColor だけです。
独自の DLL のビルド
MFC と静的にリンクする標準 DLL をコンパイルするときは、シンボル _USRDLL と _WINDLL を定義する必要があります。 また、独自の DLL のコードは次のコンパイラ スイッチを使ってコンパイルする必要があります。
/D_WINDLL は DLL のコンパイルを意味します。
/D_USRDLL は標準 DLL のビルドを意味します。
MFC と動的にリンクする標準 DLL をコンパイルするときも、これらのシンボルを定義し、コンパイラ スイッチを使用する必要があります。 さらに、シンボル _AFXDLL を定義し、次のコンパイラ スイッチを使用して DLL のコードをコンパイルする必要があります。
- /D_AFXDLL は、MFC と動的にリンクする標準 DLL のビルドを意味します。
アプリケーションと DLL 間のインターフェイス (API) は明示的にエクスポートしてください。 独自のインターフェイスは低帯域に定義して、できるだけ C インターフェイスを使うことをお勧めします。 直接的な C インターフェイスの方が、複雑な C++ クラスより保守が簡単です。
独自の API は別のヘッダー ファイルに記述し、C と C++ の両方のファイルにインクルードできるようにします。 例については、『MFC サンプル』の DLLScreenCap のヘッダー ファイル ScreenCap.h を参照してください。 独自の関数をエクスポートするには、関数をモジュール定義 (.DEF) ファイルの EXPORTS セクションに入れるか、関数定義に __declspec(dllexport) を含めます。 これらの関数をクライアント実行可能ファイルにインポートするには、__declspec(dllimport) を使用します。
MFC と動的にリンクする標準 DLL では、エクスポートされるすべての関数の先頭に AFX_MANAGE_STATE マクロを追加する必要があります。 このマクロは、現在のモジュール ステートを DLL 用に設定します。 このマクロを使用するには、DLL からエクスポートされる関数の先頭に次のコード行を追加します。
AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
WinMain -> DllMain
MFC ライブラリでは、Win32 の標準の DllMain エントリ ポイントが定義され、CWinApp からの派生オブジェクトが通常の MFC アプリケーションと同じように初期化されます。 DLL 固有の初期化処理はすべて、通常の MFC アプリケーションと同様に InitInstance メソッドに記述します。
CWinApp::Run の機構は DLL では使用できません。これは、アプリケーションがメイン メッセージ ポンプを所有しているためです。 DLL でモードレス ダイアログを表示したり、独自のメイン フレーム ウィンドウを作成したりする場合は、まず DLL によってエクスポートされたルーチンをアプリケーションのメイン メッセージ ポンプから呼び出し、次にそのルーチンから CWinApp::PreTranslateMessage を呼び出す必要があります。
この関数の使い方については、DLLScreenCap サンプルを参照してください。
MFC の提供する DllMain 関数は、DLL がアンロードされる前に、CWinApp から派生したクラスの CWinApp::ExitInstance メソッドを呼び出します。
独自の DLL のリンク
MFC と静的にリンクする標準 DLL では、独自の DLL を Nafxcwd.lib または Nafxcw.lib とリンクし、C ランタイム ライブラリの Libcmt.lib ともリンクする必要があります。 これらのライブラリは既にビルドされており、Visual C++ のセットアップの実行時に指定してインストールできます。
サンプル コード
サンプル全体については、「MFC サンプル」のサンプル プログラム DLLScreenCap を参照してください。 このサンプルでは、次の点に注目してください。
コンパイラ フラグは、DLL とアプリケーションの間で異なります。
リンク行と .DEF ファイルは、DLL とアプリケーションの間で異なります。
DLL を使用するアプリケーションは、C++ で作成されていなくてもかまいません。
アプリケーションと DLL の間のインターフェイスは、C または C++ で使用できる API であり、DLLScreenCap.def でエクスポートされます。
次の例では、MFC と静的にリンクする標準 DLL で定義される API を示します。 この例では、C++ ユーザーのために宣言を extern "C" { } ブロックで囲んでいます。 この方法は、いくつかの利点があります。 まず、DLL API を非 C++ クライアント アプリケーションでも使えます。 第 2 の利点として、C++ の名前修飾がエクスポート名に適用されないため、DLL のオーバーロードが減ります。 第 3 の利点として、.DEF ファイルへの明示的な追加がさらに簡単になり (序数によるエクスポート)、名前の変更を心配する必要がなくなります。
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
struct TracerData
{
BOOL bEnabled;
UINT flags;
};
BOOL PromptTraceFlags(TracerData FAR* lpData);
#ifdef __cplusplus
}
#endif
API で使用する構造体は、MFC クラスから派生するのではなく、API ヘッダーで定義します。 これにより、DLL とアプリケーションの間のインターフェイスが複雑になるのを避けられるほか、C プログラムからも DLL を使用できるようになります。