テクニカル ノート 33: MFC の DLL バージョン

このノートでは、MFC アプリケーションおよび MFC 拡張 DLL で MFCxx.DLL および MFCxxD.DLL (xx は MFC のバージョン番号) 共有ダイナミック リンク ライブラリを使用する方法について説明します。 標準 MFC DLL の詳細については、「DLL の構成要素としての MFC」を参照してください。

このテクニカル ノートでは、DLL の 3 つの側面について説明します。 最後の 2 つは、より高度なユーザー向けです。

非 MFC アプリケーション ("標準 MFC DLL" と呼ばれます) で使用できる MFC を使用した DLL のビルドに関心がある場合は、テクニカル ノート 11 を参照してください。

MFCxx.DLL サポートの概要: 用語とファイル

標準 MFC DLL: 一部の MFC クラスを使用してスタンドアロン DLL をビルドするには、標準 MFC DLL を使用します。 アプリ/DLL の境界を越えるインターフェイスは "C" インターフェイスであり、クライアント アプリケーションが MFC アプリケーションである必要はありません。

標準 MFC DLL は、MFC 1.0 でサポートされている DLL のバージョンです。 それらについては、テクニカル ノート 11 と MFC の高度な概念のサンプル DLLScreenCap を参照してください。

Note

Visual C++ バージョン 4.0 の時点で、USRDLL という用語は廃止され、MFC に静的にリンクする標準 MFC DLL に置き換えられました。 MFC に動的にリンクする標準 MFC DLL をビルドすることもできます。

MFC 3.0 (以降) では、OLE やデータベース クラスを含むすべての新機能を備えた標準 MFC DLL がサポートされます。

AFXDLL: MFC ライブラリの共有バージョンとも呼ばれます。 これは MFC 2.0 で追加された新しい DLL サポートです。 MFC ライブラリ自体は、多数の DLL 内に含まれています (後述します)。 クライアント アプリケーションまたは DLL は、それが必要とする DLL を動的にリンクします。 アプリケーション/DLL の境界を越えるインターフェイスは、C++/MFC クラス インターフェイスです。 クライアント アプリケーションは MFC アプリケーションである必要があります。 この DLL では、MFC 3.0 のすべての機能がサポートされます (例外: データベース クラスでは UNICODE はサポートされません)。

Note

Visual C++ バージョン 4.0 の時点で、この種類の DLL は "拡張 DLL" と呼ばれます。

このノートでは、MFCxx.DLL を使用して MFC DLL セット全体を参照します。これには次が含まれます。

  • デバッグ: MFCxxD.DLL (結合) と MFCSxxD.LIB (静的)。

  • リリース: MFCxx.DLL (結合) と MFCSxx.LIB (静的)。

  • Unicode デバッグ: MFCxxUD.DLL (結合) と MFCSxxD.LIB (静的)。

  • Unicode リリース: MFCxxU.DLL (結合) と MFCSxxU.LIB (静的)。

Note

MFCSxx[U][D].LIB ライブラリは、MFC 共有 DLL と組み合わせて使用します。 これらのライブラリには、アプリケーションまたは DLL に静的にリンクする必要があるコードが含まれています。

アプリケーションは、対応するインポート ライブラリにリンクします。

  • デバッグ: MFCxxD.LIB

  • リリース: MFCxx.LIB

  • Unicode デバッグ: MFCxxUD.LIB

  • Unicode リリース: MFCxxU.LIB

MFC 拡張 DLL は、MFCxx.DLL (または他の MFC 共有 DLL) を拡張する DLL です。 ここで、MFC コンポーネント アーキテクチャが開始されます。 MFC クラスから便利なクラスを派生させる場合、または MFC に似た別のツールキットをビルドする場合は、それを DLL 内に配置できます。 DLL では、最終的なクライアント アプリケーションと同様に、MFCxx.DLL が使用されます。 MFC 拡張 DLL では、再利用可能なリーフ クラス、再利用可能な基底クラス、再利用可能なビューとドキュメント クラスが許可されます。

長所と短所

MFC の共有バージョンを使用する理由

  • 共有ライブラリを使用すると、アプリケーションが小さくなる可能性があります (MFC ライブラリのほとんどを使用する最小のアプリケーションは 10K 未満です)。

  • MFC の共有バージョンでは、MFC 拡張 DLL と標準 MFC DLL がサポートされます。

  • 静的にリンクされた MFC アプリケーションよりも、共有 MFC ライブラリを使用するアプリケーションの方がビルドは高速です。 これは、MFC 自体をリンクする必要はないからです。 これは、リンカーがデバッグ情報を圧縮する必要がある DEBUG ビルドに特に当てはまります。 アプリケーションが既にデバッグ情報を含む DLL にリンクされる場合、圧縮するデバッグ情報が少なくなります。

MFC の共有バージョンを使用しない理由:

  • 共有ライブラリを使用するアプリケーションを配布するには、プログラムと一緒に MFCxx.DLL や他のライブラリを配布する必要があります。 MFCxx.DLL の再配布は多くの DLL と同様に自由ですが、セットアップ プログラムで DLL をインストールする必要があります。 また、プログラムと MFC DLL 自体の両方で使用される他の再頒布可能ライブラリも配布する必要があります。

MFC 拡張 DLL を記述する方法

MFC 拡張 DLL は、MFC クラスの機能を拡張するためのクラスと関数を含む DLL です。 MFC 拡張 DLL では、アプリケーションが共有 MFC DLL を使用するのと同じ方法でそれらを使用しますが、いくつかの追加の考慮事項があります。

  • ビルド プロセスは、いくつかの追加のコンパイラおよびリンカー オプションと共に共有 MFC ライブラリを使用するアプリケーションのビルドに似ています。

  • MFC 拡張 DLL には、CWinApp 派生クラスがありません。

  • MFC 拡張 DLL では、特別な DllMain を提供する必要があります。 AppWizard では、変更できる DllMain 関数が提供されます。

  • MFC 拡張 DLL は通常、MFC 拡張 DLL が CDynLinkLibrary 型またはリソースをアプリケーションにエクスポートする場合に、CRuntimeClass を作成する初期化ルーチンを提供します。 アプリケーションごとのデータを MFC 拡張 DLL によって管理する必要がある場合に、CDynLinkLibrary 派生クラスを使用できます。

これらの考慮事項については、以下で詳しく説明します。 MFC の高度な概念のサンプル DLLHUSK も参照してください。 次の方法がわかります。

  • 共有ライブラリを使用してアプリケーションをビルドします (DLLHUSK.EXE は、MFC ライブラリや他の DLL に動的にリンクする MFC アプリケーションです)。

  • MFC 拡張 DLL をビルドします (これは、MFC 拡張 DLL のビルド時に _AFXEXT などの特別なフラグを使用する方法を示しています)。

  • MFC 拡張 DLL の 2 つの例をビルドします。 1 つは限られたエクスポートを使用する MFC 拡張 DLL の基本構造を示し (TESTDLL1)、もう 1 つはクラス インターフェイス全体のエクスポートを示しています (TESTDLL2)。

クライアント アプリケーションおよびすべての MFC 拡張 DLL の両方で、同じバージョンの MFCxx.DLL を使用する必要があります。 MFC DLL の規則に従い、MFC 拡張 DLL のデバッグとリリース (/release) バージョンの両方を提供します。 この方法により、クライアント プログラムは、そのアプリケーションのデバッグとリリース バージョンの両方をビルドし、すべての DLL の適切なデバッグまたはリリース バージョンとそれらをリンクできます。

Note

C++ の名前修飾およびエクスポートの問題のために、MFC 拡張 DLL のエクスポート リストは、同じ DLL のデバッグとリリース バージョンや、プラットフォームが異なる DLL の間で異なる場合があります。 リリース MFCxx.DLL には約 2,000 のエクスポートされたエントリ ポイントがあり、デバッグ MFCxxD.DLL には約 3,000 のエクスポートされたエントリ ポイントがあります。

メモリ管理に関するクイック ノート

このテクニカル ノートの末尾近くにある「メモリ管理」というタイトルのセクションでは、MFC の共有バージョンを使用した MFCxx.DLL の実装について説明しています。 ここでは、MFC 拡張 DLL を実装するために知っておく必要がある情報について説明します。

クライアント アプリケーションのアドレス空間に読み込まれる MFCxx.DLL やすべての MFC 拡張 DLL は、同じメモリ アロケーター、リソースの読み込み、およびその他の MFC グローバル状態を同じアプリケーション内にあるかのように共有します。 これが重要になるのは、MFC に静的にリンクする非 MFC DLL ライブラリや標準 MFC DLL ではこれとまったく逆のことが行われ、各 DLL がそれぞれのメモリ プールから割り当てられるためです。

MFC 拡張 DLL がメモリを割り当てた場合、そのメモリはその他の任意のアプリケーションによって割り当てられたオブジェクトと自由に混在させることができます。 また、共有 MFC ライブラリを使用するアプリケーションがクラッシュしても、オペレーティング システムによって、DLL を共有している他の MFC アプリケーションの整合性が維持されます。

同じように、リソースの読み込み元である現在の実行可能ファイルなど、他の "グローバル" な MFC の状態も、クライアント アプリケーション、すべての MFC 拡張 DLL、および MFCxx.DLL 自体の間で共有されます。

MFC 拡張 DLL のビルド

AppWizard を使用して MFC 拡張 DLL プロジェクトを作成すると、適切なコンパイラとリンカーの設定が自動的に生成されます。 また、変更できる DllMain 関数も生成されます。

既存のプロジェクトを MFC 拡張 DLL に変換する場合は、MFC の共有バージョンを使用してビルドされる標準設定から始めます。 その後、次の変更を行います。

  • コンパイラ フラグに /D_AFXEXT を追加します。 [プロジェクトのプロパティ] ダイアログで、[C/C++]>[プリプロセッサ] カテゴリの順に選択します。 [Define Macros] (マクロの定義) フィールドに _AFXEXT を追加し、各項目をセミコロンで区切ります。

  • /Gy コンパイラ スイッチを削除します。 [プロジェクトのプロパティ] ダイアログで、[C/C++]>[コード生成] カテゴリの順に選択します。 [関数レベルでリンクする] プロパティが有効になっていないことを確認します。 参照されていない関数がリンカーによって削除されることはないため、この設定により、クラスのエクスポートが容易になります。 元のプロジェクトで MFC に静的にリンクされている標準 MFC DLL をビルドした場合は、/MT (または /MTd) コンパイラ オプションを /MD (または /MDd) に変更します。

  • /DLL オプションを [リンク] に設定して、エクスポート ライブラリをビルドします。 このオプションは、新しいターゲットを作成し、ターゲットの種類として Win32 ダイナミックリンク ライブラリを指定すると設定されます。

ヘッダー ファイルの変更

MFC 拡張 DLL の通常の目的は、なんらかの共通機能を、その機能を使用できる 1 つ以上のアプリケーションにエクスポートすることです。 基本的に、DLL は、クライアント アプリケーションで使用するためにクラスとグローバル関数をエクスポートします。

各メンバー関数にインポートまたはエクスポートのマークが適切に設定されるようにするには、特殊な宣言 __declspec(dllexport)__declspec(dllimport) を使用します。 クライアント アプリケーションでクラスを使用する場合は、それらを __declspec(dllimport) として宣言する必要があります。 MFC 拡張 DLL 自体をビルドする場合、関数を __declspec(dllexport) として宣言する必要があります。 ビルドされた DLL では、関数もエクスポートして、クライアント プログラムが読み込み時にそれらをバインドできるようにする必要があります。

クラス全体をエクスポートするには、クラス定義内で AFX_EXT_CLASS を使用します。 フレームワークは、_AFXDLL_AFXEXT が定義されている場合はこのマクロを __declspec(dllexport) として定義しますが、_AFXEXT が定義されていない場合はそれを __declspec(dllimport) として定義します。 _AFXEXT は、MFC 拡張 DLL をビルドするときにのみ定義します。 次に例を示します。

class AFX_EXT_CLASS CExampleExport : public CObject
{ /* ... class definition ... */ };

クラス全体をエクスポートしない

場合によっては、クラスの個々の必要なメンバーをエクスポートすることが必要な場合があります。 たとえば、CDialog 派生クラスをエクスポートする場合に、コンストラクターと DoModal 呼び出しのエクスポートだけが必要な場合があります。 DLL の DEF ファイルを使用してこれらのメンバーをエクスポートできますが、エクスポートする必要がある個々のメンバーの場合とほぼ同じ方法で AFX_EXT_CLASS を使用することもできます。

次に例を示します。

class CExampleDialog : public CDialog
{
public:
    AFX_EXT_CLASS CExampleDialog();
    AFX_EXT_CLASS int DoModal();
    // rest of class definition
    // ...
};

その場合、クラスのメンバーをすべてエクスポートするわけではないので、追加の問題が発生する可能性があります。 問題は、MFC マクロの動作方法にあります。 MFC のヘルパー マクロのいくつかは、実際にデータ メンバーを宣言または定義します。 DLL では、これらのデータ メンバーもエクスポートする必要があります。

たとえば、MFC 拡張 DLL をビルドするときに、DECLARE_DYNAMIC マクロを次のように定義します。

#define DECLARE_DYNAMIC(class_name) \
protected: \
    static CRuntimeClass* PASCAL _GetBaseClass(); \
    public: \
    static AFX_DATA CRuntimeClass class##class_name; \
    virtual CRuntimeClass* GetRuntimeClass() const; \

static AFX_DATA の開始行では、クラス内で静的オブジェクトを宣言します。 このクラスを正しくエクスポートし、クライアント EXE からランタイム情報にアクセスするには、この静的オブジェクトをエクスポートする必要があります。 静的オブジェクトは修飾子 AFX_DATA を使用して宣言されているため、必要なのは DLL をビルドするときに AFX_DATA__declspec(dllexport) として定義することだけです。 クライアント実行可能ファイルをビルドするときに、それを __declspec(dllimport) として定義します。

上で説明したように、AFX_EXT_CLASS は既にこの方法で定義されています。 必要なのは、AFX_DATA を、クラス定義の周囲にある AFX_EXT_CLASS と同じに再定義することだけです。

次に例を示します。

#undef  AFX_DATA
#define AFX_DATA AFX_EXT_CLASS
class CExampleView : public CView
{
    DECLARE_DYNAMIC()
    // ... class definition ...
};
#undef  AFX_DATA
#define AFX_DATA

MFC では、そのマクロ内で定義されているデータ項目に対して常に AFX_DATA シンボルが使用されるため、この手法はそうしたすべてのシナリオで機能します。 たとえば、これは DECLARE_MESSAGE_MAP に対して機能します。

Note

クラスの選択したメンバーではなくクラス全体をエクスポートする場合は、静的データ メンバーは自動的にエクスポートされます。

同じ手法を使用して、DECLARE_SERIAL と IMPLEMENT_SERIAL マクロを使用するクラスの CArchive 抽出演算子を自動的にエクスポートできます。 (ヘッダー ファイル内にある) クラス宣言を次のコードで囲んで、アーカイブ演算子をエクスポートします。

#undef AFX_API
#define AFX_API AFX_EXT_CLASS

/* your class declarations here */

#undef AFX_API
#define AFX_API

_AFXEXT の制限事項

MFC 拡張 DLL の複数のレイヤーがない限り、_AFXEXT プリプロセッサ シンボルを MFC 拡張 DLL に使用できます。 MFC クラスから派生する独自の MFC 拡張 DLL のクラスがあり、そこから呼び出すか派生する MFC 拡張 DLL がある場合は、あいまいさを避けるために独自のプリプロセッサ シンボルを使用する必要があります。

問題は、Win32 では、DLL からエクスポートする場合は __declspec(dllexport) として、DLL からインポートする場合は __declspec(dllimport) として明示的にデータを宣言する必要があることです。 _AFXEXT を定義するとき、MFC ヘッダーに AFX_EXT_CLASS が正しく定義されていることを確認します。

複数のレイヤーがある場合、AFX_EXT_CLASS などの 1 つのシンボルでは不十分です。MFC 拡張 DLL では、それ自体のクラスをエクスポートし、さらに別の MFC 拡張 DLL から他のクラスをインポートすることもあります。 この問題に対処するには、DLL を使用するのではなく、DLL 自体をビルドしていることを示す特別なプリプロセッサ シンボルを使用します。 たとえば、A.DLLB.DLL という 2 つの MFC 拡張 DLL があるとします。 それぞれによって、A.HB.H 内にあるいくつかのクラスがエクスポートされます。 B.DLL では A.DLL のクラスが使用されます。 ヘッダー ファイルは次のようになります。

/* A.H */
#ifdef A_IMPL
    #define CLASS_DECL_A   __declspec(dllexport)
#else
    #define CLASS_DECL_A   __declspec(dllimport)
#endif

class CLASS_DECL_A CExampleA : public CObject
{ /* ... class definition ... */ };

/* B.H */
#ifdef B_IMPL
    #define CLASS_DECL_B   __declspec(dllexport)
#else
    #define CLASS_DECL_B   __declspec(dllimport)
#endif

class CLASS_DECL_B CExampleB : public CExampleA
{ /* ... class definition ... */ };

A.DLL がビルドされるときは /DA_IMPL を使用してビルドされ、B.DLL がビルドされるときは /DB_IMPL を使用してビルドされます。 DLL ごとに個別のシンボルを使用することにより、B.DLL のビルド時に CExampleB がエクスポートされ、CExampleA がインポートされます。 CExampleA は、A.DLL のビルド時にエクスポートされ、B.DLL または他のクライアントによって使用されるときにはインポートされます。

組み込みの AFX_EXT_CLASS および _AFXEXT プリプロセッサ シンボルを使用する場合、この種類のレイヤー化は実行できません。 上記の手法では、MFC と同じ方法でこの問題を解決します。 MFC では、その OLE、データベース、およびネットワーク MFC 拡張 DLL をビルドするときに、この手法を使用します。

引き続きクラス全体をエクスポートしない

ここでも、クラス全体をエクスポートしない場合は、特別な注意が必要です。 MFC マクロによって作成された必要なデータ項目が正しくエクスポートされていることを確認します。 これを行うには、AFX_DATA を特定のクラスのマクロに再定義します。 クラス全体をエクスポートしない場合は必ずそれを再定義します。

次に例を示します。

// A.H
#ifdef A_IMPL
    #define CLASS_DECL_A  _declspec(dllexport)
#else
    #define CLASS_DECL_A  _declspec(dllimport)
#endif

#undef  AFX_DATA
#define AFX_DATA CLASS_DECL_A

class CExampleA : public CObject
{
    DECLARE_DYNAMIC()
    CLASS_DECL_A int SomeFunction();
    // class definition
    // ...
};

#undef AFX_DATA
#define AFX_DATA

DllMain

MFC 拡張 DLL のメイン ソース ファイル内に配置する必要があるコードを次に示します。 これは、標準のインクルードの後に配置する必要があります。 AppWizard を使用して MFC 拡張 DLL のスターター ファイルを作成すると、DllMain が提供されます。

#include "afxdllx.h"

static AFX_EXTENSION_MODULE extensionDLL;

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID)
{
   if (dwReason == DLL_PROCESS_ATTACH)
   {
      // MFC extension DLL one-time initialization
      if (!AfxInitExtensionModule(
             extensionDLL, hInstance))
         return 0;

      // TODO: perform other initialization tasks here
   }
   else if (dwReason == DLL_PROCESS_DETACH)
   {
      // MFC extension DLL per-process termination
      AfxTermExtensionModule(extensionDLL);

      // TODO: perform other cleanup tasks here
   }
   return 1;   // ok
}

AfxInitExtensionModule を呼び出すことによって、モジュールの実行時クラス (CRuntimeClass 構造体) だけでなく、後で CDynLinkLibrary オブジェクトの作成時に使用するそのオブジェクト ファクトリ (COleObjectFactory オブジェクト) がキャプチャされます。 (オプションで) AfxTermExtensionModule を呼び出すと、各プロセスが MFC 拡張 DLL からデタッチされたときに MFC で MFC 拡張 DLL をクリーンアップできます (これは、プロセスが終了したとき、または DLL が FreeLibrary 呼び出しによってアンロードされたときに発生します)。 ほとんどの MFC 拡張 DLL は動的に読み込まれないため (通常、それらはそれぞれのインポート ライブラリを介してリンクされます)、AfxTermExtensionModule の呼び出しは通常必要ありません。

アプリケーションが MFC 拡張 DLL の読み込みと解放を動的に行う場合は、必ず上記のように AfxTermExtensionModule を呼び出します。 また、アプリケーションで複数のスレッドを使用する場合、またはそれが MFC 拡張 DLL を動的に読み込む場合は、(Win32 関数 LoadLibraryFreeLibrary ではなく) AfxLoadLibraryAfxFreeLibrary を必ず使用します。 AfxLoadLibraryAfxFreeLibrary を使用すると、MFC 拡張 DLL がロードおよびアンロードされたときに実行される起動とシャットダウンのコードが、グローバルの MFC 状態を破壊することがなくなります。

ヘッダー ファイル AFXDLLX.H には、MFC 拡張 DLL で使用される構造体の特別な定義 (AFX_EXTENSION_MODULE および CDynLinkLibrary の定義など) が含まれています。

グローバル extensionDLL は、示されているとおりに宣言する必要があります。 16 ビット バージョンの MFC とは異なり、MFCxx.DLLDllMain が呼び出された時点で完全に初期化されるので、この間にメモリを割り当て、MFC 関数を呼び出すことができます。

リソースやクラスの共有

単純な MFC 拡張 DLL では、少数の低帯域幅関数のみをクライアント アプリケーションにエクスポートする必要があります。それ以上は必要ありません。 ユーザー インターフェイスをより集中的に使用する DLL では、リソースと C++ クラスをクライアント アプリケーションにエクスポートすることが必要になる場合があります。

リソースのエクスポートには、リソース リストを使います。 各アプリケーションでは、CDynLinkLibrary オブジェクトのシングルリンク リストです。 リソースを読み込む標準的な MFC 実装の多くは、リソースを検索するとき、まず現在のリソース モジュール (AfxGetResourceHandle) を探します。ここで見つからないときは、CDynLinkLibrary オブジェクトのリストを順に調べて、要求されたリソースの読み込みを試みます。

C++ クラス名を指定した C++ オブジェクトの動的な作成は似ています。 MFC オブジェクトの逆シリアル化メカニズムでは、CRuntimeClass の全オブジェクトを登録する必要があります。そうすると、要求された型の C++ オブジェクトを、以前に格納されたものに基づいて動的に作成することで、それを再構築できます。

クライアント アプリケーションで MFC 拡張 DLL 内の DECLARE_SERIAL であるクラスを使用する場合は、クラスをエクスポートしてクライアント アプリケーションが認識できるようにする必要があります。 これは、CDynLinkLibrary リストを順に調べることでも行われます。

MFC の高度な概念のサンプル DLLHUSK では、リストは次のようになっています。

head ->   DLLHUSK.EXE   - or - DLLHUSK.EXE
               |                    |
          TESTDLL2.DLL         TESTDLL2.DLL
               |                    |
          TESTDLL1.DLL         TESTDLL1.DLL
               |                    |
               |                    |
           MFC90D.DLL           MFC90.DLL

MFCxx.DLL エントリは通常、リソースとクラスのリストの最後になります。 MFCxx.DLL には、すべての標準コマンド ID に対応するプロンプト文字列など、すべての標準 MFC リソースが含まれています。 それをリストの末尾に配置すると、DLL とクライアント アプリケーション自体の両方で、独自のコピーを保持する代わりに、MFCxx.DLL 内の共有リソースを利用できます。

すべての DLL のリソースとクラス名をクライアント アプリケーションの名前空間に結合すると、ID や名前を選択するときに注意が必要になるという欠点があります。 この機能を無効にするには、リソースも CDynLinkLibrary オブジェクトもクライアント アプリケーションにエクスポートしないようにします。 DLLHUSK サンプルでは、複数のヘッダー ファイルを使って、共有リソースの名前空間を管理します。 共有リソース ファイルの使用に関するその他のヒントについては、テクニカル ノート 35 を参照してください。

DLL の初期化

前述のように、通常、リソースとクラスをクライアント アプリケーションにエクスポートするには、CDynLinkLibrary オブジェクトを作成する必要があります。 DLL を初期化するには、エクスポートされたエントリ ポイントを指定する必要があります。 それは、最低限、引数を受け取らず、何も返さない void ルーチンですが、任意のものにすることができます。

この方法を使用する場合は、DLL を使用する必要がある各クライアント アプリケーションで、この初期化ルーチンを呼び出す必要があります。 AfxInitExtensionModule を呼び出した直後に、DllMain 内でこの CDynLinkLibrary オブジェクトを割り当てることもできます。

初期化ルーチンでは、MFC 拡張 DLL 情報に接続された現在のアプリケーションのヒープに CDynLinkLibrary オブジェクトを作成する必要があります。 これを行うには、このような関数を定義します。

extern "C" extern void WINAPI InitXxxDLL()
{
    new CDynLinkLibrary(extensionDLL);
}

この例のルーチン名である InitXxxDLL は、任意のものにすることができます。 これは extern "C" である必要ありませんが、そうするとエクスポート リストの保守が容易になります。

Note

標準 MFC DLL から MFC 拡張 DLL を使用する場合は、この初期化関数をエクスポートする必要があります。 MFC 拡張 DLL クラスまたはリソースを使用する前に、標準 MFC DLL からこの関数を呼び出す必要があります。

エントリのエクスポート

クラスをエクスポートする簡単な方法は、エクスポートする各クラスおよびグローバル関数に対して __declspec(dllimport) および __declspec(dllexport) を使用することです。 これははるかに簡単ですが、以下で説明するように、DEF ファイル内の各エントリ ポイントに名前を付けるよりも効率が悪くなります。 これは、エクスポートされる関数をあまり制御できないためです。 また、関数を序数でエクスポートできません。 TESTDLL1 と TESTDLL2 では、この方法を使用してそれぞれのエントリをエクスポートします。

もっと効率的な方法は、DEF ファイル内で各エントリに名前を付けてそれらをエクスポートすることです。 この方法は、MFCxx.DLL によって使用されています。 ここでは DLL から選択的にエクスポートするため、エクスポートする特定のインターフェイスを決定する必要があります。 DEF ファイル内でリンカーに壊れた名前をエントリの形式で指定する必要があるため、これは困難です。 C++ クラスは、そのシンボリック リンクが本当に必要でない限り、エクスポートしないでください。

以前に DEF ファイルを使用した C++ クラスのエクスポートを試みたことがある場合は、このリストを自動的に生成するツールを開発することをお勧めします。 これを行うには、2 段階のリンク プロセスを使用します。 DLL をエクスポートせずに 1 回リンクし、リンカーがマップ ファイルを生成できるようにします。 マップ ファイルには、エクスポートする必要がある関数のリストが含まれています。 なんらかの再配置を行ってから、これを使用して DEF ファイルのエクスポート エントリを生成できます。 このようなプロセスでは、MFCxx.DLL および OLE とデータベース MFC 拡張 DLL のエクスポート リストが大量に生成されました (ただし、完全には自動ではなく、たまに手作業でのチューニングが必要です)。

CWinApp と CDynLinkLibrary

MFC 拡張 DLL には、独自の CWinApp 派生オブジェクトがありません。 したがって、クライアント アプリケーションの CWinApp 派生オブジェクトを使用する必要があります。 これは、クライアント アプリケーションがメイン メッセージ ポンプ、アイドル ループなどを所有しているという意味です。

お使いの MFC 拡張 DLL がアプリケーションごとに追加データを保持する必要がある場合は、CDynLinkLibrary から新しいクラスを派生させ、上記の InitXxxDLL ルーチン内にそれを作成できます。 DLL では、実行時に現在のアプリケーションの CDynLinkLibrary オブジェクトのリストを確認し、その特定の MFC 拡張 DLL 用のものを見つけ出すことができます。

DLL 実装におけるリソースの使用

前述のように、既定のリソースの読み込みでは、CDynLinkLibrary オブジェクトのリストを順に調べて、要求されたリソースを含む最初の EXE または DLL を探します。 すべての MFC API とすべての内部コードでは、AfxFindResourceHandle を使用してリソース リストを順に調べ、リソースがどこにあるかに関係なく、それを見つけます。

特定の場所からのみリソースを読み込む場合は、API の AfxGetResourceHandle および AfxSetResourceHandle を使用して、古いハンドルを保存し、新しいハンドルを設定します。 クライアント アプリケーションに戻る前に、元のリソース ハンドルを必ず復元してください。 サンプルの TESTDLL2 では、メニューの明示的な読み込みにこの方法を使用しています。

リストを順に調べる場合、多少速度が低下することとリソース ID 範囲の管理が必要なことが欠点です。 いくつかの MFC 拡張 DLL にリンクされるクライアント アプリケーション側で、DLL のインスタンス ハンドルを指定しなくても、DLL が提供する任意のリソースを使用できるという利点もあります。 AfxFindResourceHandle は、リソース リストを検索して一致するリソースを見つけるのに使われる API です。 これは、リソースの名前と型を受け取り、最初にリソースが見つかったリソース ハンドルまたは NULL を返します。

DLL バージョンを使用するアプリケーションの作成

アプリケーションの要件

MFC の共有バージョンを使用するアプリケーションは、いくつかの基本的な規則に従う必要があります。

  • これには CWinApp オブジェクトを含め、メッセージ ポンプの標準規則に従う必要があります。

  • これは、必要なコンパイラ フラグのセット (下記参照) を使用してコンパイルする必要があります。

  • これは、MFCxx インポート ライブラリとリンクする必要があります。 必要なコンパイラ フラグを設定することにより、MFC ヘッダーはアプリケーションがリンクする必要があるライブラリをリンク時に決定します。

  • 実行可能ファイルを実行するには、MFCxx.DLL をそのパスまたは Windows システム ディレクトリに配置する必要があります。

開発環境でのビルド

ほとんど標準の既定値のままで内部メイクファイルを使用している場合は、プロジェクトを簡単に変更して DLL バージョンをビルドできます。

次の手順では、前提として、正しく機能する MFC アプリケーションが NAFXCWD.LIB (デバッグの場合) および NAFXCW.LIB (リリースの場合) にリンクされていて、MFC ライブラリの共有バージョンを使用するようにそれを変換する必要があります。 Visual Studio 環境を実行していて、内部プロジェクト ファイルがあります。

  1. [プロジェクト] メニューの [プロパティ] を選択します。 [全般] ページの [プロジェクトの既定値] の下で、Microsoft Foundation Classes を [共有 DLL で MFC を使う] (MFCxx(d).dll) に設定します。

NMAKE を使用したビルド

コンパイラの外部メイクファイル機能を使用する場合、または NMAKE を直接使用する場合は、必要なコンパイラとリンカー オプションをサポートするようにメイクファイルを編集する必要があります。

必要なコンパイラ フラグ:

  • /D_AFXDLL /MD /D_AFXDLL

標準 MFC ヘッダーでは、_AFXDLL シンボルを定義する必要があります。

  • /MD アプリケーションでは、C ランタイム ライブラリの DLL バージョンを使用する必要があります。

他のすべてのコンパイラ フラグは、MFC の既定値に従います (たとえば、デバッグの場合は _DEBUG)。

ライブラリのリンカー リストを編集します。 NAFXCWD.LIBMFCxxD.LIB に変更して、 NAFXCW.LIBMFCxx.LIBに変更します。 LIBC.LIBMSVCRT.LIBに置き換えます。 他のすべての MFC ライブラリと同様に、MFCxxD.LIB はすべての C ランタイム ライブラリの前に配置することが重要です。

必要に応じて、リリースとデバッグの両方のリソース コンパイラ オプションに /D_AFXDLL を追加します (/R を指定してリソースを実際にコンパイルしたもの)。 このオプションにより、MFC DLL に存在するリソースを共有することで、最終的な実行可能ファイルが小さくなります。

これらの変更を行った後は、完全なリビルドが必要です。

サンプルのビルド

ほとんどの MFC サンプル プログラムは、Visual C++ から、またはコマンド ラインによって共有 NMAKE 互換メイクファイルからビルドできます。

これらのサンプルのいずれかを、MFCxx.DLL を使用するように変換するには、MAK ファイルを Visual C++ に読み込み、プロジェクト オプションを前述のように設定します。 NMAKE ビルドを使用する場合は、NMAKE のコマンド ラインに AFXDLL=1 を指定できます。それにより、共有 MFC ライブラリを使用してサンプルがビルドされます。

MFC の高度な概念のサンプル DLLHUSK は、MFC の DLL バージョンを使用してビルドされています。 このサンプルでは、MFCxx.DLL にリンクされたアプリケーションをビルドする方法を示しているだけでなく、このテクニカル ノート内で後述する MFC 拡張 DLL などの MFC DLL パッケージ オプションのその他の機能も示しています。

パッケージ化に関するメモ

DLL のリリース バージョン (MFCxx.DLL および MFCxxU.DLL) は自由に再配布できます。 DLL のデバッグ バージョンは自由に再配布できず、アプリケーションの開発中にのみ使用する必要があります。

デバッグ DLL はデバッグ情報と共に提供されます。 Visual C++ デバッガーを使用すると、アプリケーションと DLL の両方の実行をトレースできます。 リリース DLL (MFCxx.DLL および MFCxxU.DLL) には、デバッグ情報は含まれていません。

DLL をカスタマイズまたはリビルドする場合は、それらに "MFCxx" 以外の名前を付ける必要があります。 MFC SRC ファイル MFCDLL.MAK には、ビルド オプションが記述されており、DLL の名前を変更するためのロジックが含まれています。 これらの DLL は多くの MFC アプリケーションによって共有される可能性があるため、ファイルの名前を変更する必要があります。 システムにインストールされているものを MFC DLL のカスタム バージョンに置き換えると、共有 MFC DLL を使用している別の MFC アプリケーションが中断される可能性があります。

MFC DLL をリビルドすることは推奨されていません。

MFCxx.DLL の実装方法

次のセクションでは、MFC DLL (MFCxx.DLL および MFCxxD.DLL) の実装方法について説明します。 必要なのがアプリケーションで MFC DLL を使用することのみである場合は、ここに記載されている詳細を理解することも重要ではありません。 ここに記載されている詳細は、MFC 拡張 DLL の記述方法を理解するうえでは重要ではありませんが、この実装を理解すると、自分で DLL を記述する際に役立ちます。

実装の概要

この MFC DLL は、実際、上で説明したように、MFC 拡張 DLL の特殊なケースです。 これには、多数のクラスに対して多数のエクスポートがあります。 この MFC DLL には、標準 MFC 拡張 DLL よりもさらに特殊にするためにいくつかのことを追加で行っています。

処理の大部分を行う Win32

16 ビット バージョンの MFC では、スタック セグメント上のアプリごとのデータ、なんらかの 80x86 アセンブリ コードによって作成された特殊なセグメント、プロセスごとの例外コンテキスト、その他の手法など、さまざまな特殊な手法が必要でした。 Win32 は、DLL 内のプロセスごとのデータを直接サポートします。これは、ほとんどの場合に必要です。 ほとんどの場合、MFCxx.DLL は、DLL にパッケージ化された NAFXCW.LIB にすぎません。 MFC のソース コードを見ると、#ifdef _AFXDLL ケースがいくつかあります。これは、特殊なケースをたくさん作成する必要はないためです。 そこにある特殊なケースとは、特に Windows 3.1 上で Win32 に対処する場合です (Win32s とも呼ばれます)。 Win32s では、プロセスごとの DLL データは直接サポートされません。 MFC DLL では、プロセス ローカル データを取得するために、スレッドローカル ストレージ (TLS) Win32 API を使用する必要があります。

ライブラリ ソースへの影響、追加ファイル

通常の MFC クラス ライブラリのソースとヘッダーに対する _AFXDLL バージョンの影響は比較的軽微です。 AFXWIN.H メイン ヘッダーに含まれている特別なバージョン ファイル (AFXV_DLL.H) と追加のヘッダー ファイル (AFXDLL_.H) があります。 AFXDLL_.H ヘッダーには、CDynLinkLibrary アプリケーションと MFC 拡張 DLL の両方の _AFXDLL クラスと他の実装の詳細が含まれています。 AFXDLLX.H ヘッダーは、MFC 拡張 DLL をビルドするために提供されています (詳細については、上記を参照してください)。

MFC SRC の MFC ライブラリに対する通常のソースには、_AFXDLL #ifdef の下になんらかの追加の条件コードが含まれています。 追加のソース ファイル (DLLINIT.CPP) には、MFC の共有バージョンの追加の DLL 初期化コードとその他のグルーが含まれています。

MFC の共有バージョンをビルドするために、追加のファイルが提供されています (DLL のビルド方法の詳細については、以下を参照してください)。

  • 2 つの DEF ファイルは、DLL のデバッグ (MFCxxD.DEF) とリリース (MFCxx.DEF) バージョンの MFC DLL エントリ ポイントをエクスポートするために使用します。

  • RC ファイル (MFCDLL.RC) には、DLL のすべての標準 MFC リソースと VERSIONINFO リソースが含まれています。

  • CLW ファイル (MFCDLL.CLW) は、ClassWizard を使用して MFC クラスを参照できるようにするために提供されています。 この機能は、MFC の DLL バージョン固有ではありません。

メモリ管理

MFCxx.DLL を使用するアプリケーションでは、共有 C ランタイム DLL である MSVCRTxx.DLL によって提供される共通のメモリ アロケーターが使用されます。 アプリケーション、すべての MFC 拡張 DLL、および MFC DLL では、この共有メモリ アロケーターが使用されます。 メモリ割り当てに共有 DLL を使用することで、MFC DLL は、後でアプリケーションによって解放されたメモリを割り当てることも、その逆を行うこともできます。 アプリケーションと DLL の両方で同じアロケーターを使用する必要があるため、C++ グローバル operator new および operator delete をオーバーライドしてはなりません。 他の C ランタイム メモリ割り当てルーチン (mallocreallocfree など) にも同じ規則が適用されます。

序数とクラス __declspec(dllexport) と DLL の名前付け

ここでは、C++ コンパイラの class__declspec(dllexport) 機能は使用しません。 代わりに、クラス ライブラ リソース (MFCxx.DEFMFCxxD.DEF) を使用して、エクスポートのリストを含めます。 エントリ ポイント (関数とデータ) の選択セットのみがエクスポートされます。 MFC プライベート実装関数やクラスなどの他のシンボルはエクスポートされません。 すべてのエクスポートは、常駐または非常駐の名前テーブルに文字列名を含めずに序数によって行われます。

class__declspec(dllexport) の使用は、より小さな DLL を構築するための実行可能な代替手段になる場合がありますが、MFC のような大規模な DLL では、既定のエクスポート メカニズムには効率性と容量に関する制限があります。

つまり、実行や読み込みの速度を大きく損なうことなく、約 800 KB しかないリリース MFCxx.DLL に大量の機能をパッケージ化できます。 MFCxx.DLL は、この手法を使用しなかった場合、100 KB 大きくなります。 この手法により、DEF ファイルの末尾にエントリ ポイントをさらに追加できます。 これにより、序数によるエクスポートの速度とサイズの効率を損なうことなく、簡単なバージョン管理が可能になります。 MFC クラス ライブラリのメジャー バージョンのリビジョンによって、ライブラリ名が変更されます。 つまり、MFC30.DLL は、MFC クラス ライブラリのバージョン 3.0 を含む再頒布可能 DLL です。 この DLL をアップグレードした場合、仮に MFC 3.1 とすると、DLL の名前は代わりに MFC31.DLL になります。 ここでも、MFC ソース コードを変更して MFC DLL のカスタム バージョンを生成した場合は、別の名前 (そして、できれば名前に "MFC" が含まれていないもの) を使用します。

関連項目

番号順テクニカル ノート
カテゴリ別テクニカル ノート