次の方法で共有


TN033: MFC の DLL バージョン

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

このテクニカル ノートでは、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で説明されています。

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

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

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

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 (静的)。

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 ライブラリを使用する最小限のアプリケーションは 10,000 未満です)。

  • 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 がCRuntimeClass型またはリソースをアプリケーションにエクスポートする場合に、CDynLinkLibraryを作成するための初期化ルーチンを提供します。 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 の適切なデバッグまたはリリース バージョンにリンクできます。

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++>Preprocessor カテゴリを選択します。 _AFXEXT[マクロの定義] フィールドに追加し、各項目をセミコロンで区切ります。

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

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

ヘッダー ファイルの変更

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 をビルドするときに__declspec(dllexport)としてAFX_DATAを定義するだけで済みます。 クライアント実行可能ファイルをビルドするときに __declspec(dllimport) として定義します。

前述のように、 AFX_EXT_CLASS は既にこのように定義されています。 クラス定義のAFX_EXT_CLASSと同じAFX_DATAを再定義するだけで済みます。

例えば次が挙げられます。

#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に対して機能します。

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

同じ手法を使用して、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 のレイヤーが複数ない限り、MFC 拡張 DLL に _AFXEXT プリプロセッサ シンボルを使用できます。 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 自体をビルドしていることを示す特別なプリプロセッサ シンボルを使用します。 たとえば、2 つの MFC 拡張 DLL、 A.DLLB.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 ごとに個別のシンボルを使用することで、CExampleBがエクスポートされ、B.DLLをビルドするときに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の呼び出しは、CDynLinkLibrary オブジェクトの作成時に後で使用するために、モジュールのランタイム クラス (CRuntimeClass構造体) とそのオブジェクト ファクトリ (COleObjectFactory オブジェクト) をキャプチャします。 (省略可能) AfxTermExtensionModule 呼び出すと、MFC 拡張 DLL から各プロセスがデタッチされたとき (プロセスが終了したとき、または DLL が FreeLibrary 呼び出しによってアンロードされるときに発生します) に MFC 拡張 DLL をクリーンアップできます。 ほとんどの MFC 拡張 DLL は動的に読み込まれないため (通常はインポート ライブラリを介してリンクされます)、 AfxTermExtensionModule の呼び出しは通常必要ありません。

アプリケーションが MFC 拡張 DLL を動的に読み込んで解放する場合は、上記のように AfxTermExtensionModule を呼び出してください。 また、アプリケーションで複数のスレッドを使用する場合や、MFC 拡張 DLL を動的に読み込む場合は、(Win32 関数のLoadLibraryFreeLibraryの代わりに) AfxLoadLibraryAfxFreeLibraryを使用してください。 AfxLoadLibraryAfxFreeLibraryを使用すると、MFC 拡張 DLL の読み込みとアンロード時に実行されるスタートアップ コードとシャットダウン コードがグローバル MFC の状態を破損することはありません。

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

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

リソースやクラスの共有

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

リソースのエクスポートには、リソース リストを使います。 各アプリケーションには、 CDynLinkLibrary オブジェクトの一覧が 1 つずつリンクされています。 リソースを検索する場合、リソースを読み込む標準 MFC 実装のほとんどは、最初に現在のリソース モジュール (AfxGetResourceHandle) を調べて、見つからない場合は、要求されたリソースを読み込もうとする CDynLinkLibrary オブジェクトの一覧を確認します。

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

クライアント アプリケーションで、 DECLARE_SERIALされている MFC 拡張 DLL 内のクラスを使用する場合は、クラスをエクスポートしてクライアント アプリケーションに表示する必要があります。 また、 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を呼び出した直後に、このCDynLinkLibrary オブジェクトをDllMainに割り当てることもできます。

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

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

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

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 回リンクし、リンカーが MAP ファイルを生成できるようにします。 MAP ファイルには、エクスポートする必要がある関数の一覧が含まれています。 一部の並べ替えでは、DEF ファイルの EXPORT エントリを生成するために使用できます。 MFCxx.DLLおよび OLE およびデータベース MFC 拡張 DLL のエクスポート リスト (数千個) は、このようなプロセスで生成されました (ただし、完全には自動ではなく、しばらくの間にハンド チューニングが必要になります)。

CWinApp と CDynLinkLibrary

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

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

DLL 実装でのリソースの使用

前述のように、既定のリソース読み込みでは、要求されたリソースを持つ最初の EXE または DLL を探 CDynLinkLibrary オブジェクトの一覧が表示されます。 すべての MFC API とすべての内部コードでは、 AfxFindResourceHandle を使用してリソース一覧を調べるため、リソースが配置されている場所に関係なく、任意のリソースを検索します。

特定の場所からのみリソースを読み込む場合は、API AfxGetResourceHandleAfxSetResourceHandle を使用して古いハンドルを保存し、新しいハンドルを設定します。 クライアント アプリケーションに戻る前に、元のリソース ハンドルを必ず復元してください。 サンプル 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. [ プロジェクト ] メニューの [ プロパティ] を選択します。 [ 全般 ] ページの [ プロジェクトの既定値] で、共有 DLL (MFCxx(d).dll) で MFC を使用 するように Microsoft Foundation クラスを設定します。

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 ランタイム ライブラリの 前に 配置することが重要です。

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

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

サンプルのビルド

MFC サンプル プログラムのほとんどは、Visual C++ またはコマンド ラインから NMAKE 互換の MAKEFILE からビルドできます。

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

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

ノートのパッケージ化

DLL のリリース バージョン (MFCxx.DLLMFCxxU.DLL) は、自由に再頒布可能です。 DLL のデバッグ バージョンは自由に再頒布可能ではなく、アプリケーションの開発中にのみ使用する必要があります。

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

DLL をカスタマイズまたは再構築する場合は、"MFCxx" 以外の呼び出しを行う必要があります。 MFC SRC ファイル MFCDLL.MAK ビルド オプションについて説明し、DLL の名前を変更するためのロジックが含まれています。 これらの DLL は多くの MFC アプリケーションで共有される可能性があるため、ファイルの名前を変更する必要があります。 カスタム バージョンの MFC DLL をシステムにインストールされている MFC DLL に置き換えた場合、共有 MFC DLL を使用して別の MFC アプリケーションが壊れる可能性があります。

MFC DLL の再構築は推奨されません。

MFCxx.DLLの実装方法

次のセクションでは、MFC DLL (MFCxx.DLLMFCxxD.DLL) の実装方法について説明します。 ここでの詳細を理解することは、アプリケーションで MFC DLL を使用する必要がある場合でも重要ではありません。 ここでの詳細は、MFC 拡張 DLL の記述方法を理解するために不可欠ではありませんが、この実装を理解することは、独自の DLL を記述するのに役立つ場合があります。

実装の概要

MFC DLL は、前述のように MFC 拡張 DLL の特殊なケースです。 多数のクラスに対して多数のエクスポートがあります。 MFC DLL には、通常の MFC 拡張 DLL よりも特別な処理がいくつか追加されています。

Win32 はほとんどの作業を行います

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

ライブラリ ソース、その他のファイルへの影響

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

MFC SRC の MFC ライブラリの通常のソースには、 _AFXDLL #ifdefの下にいくつかの追加の条件付きコードがあります。 追加のソース ファイル (DLLINIT.CPP) には、MFC の共有バージョン用の追加の DLL 初期化コードとその他の接着剤が含まれています。

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

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

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

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

メモリ管理

MFCxx.DLLを使用するアプリケーションは、共有 C ランタイム DLL MSVCRTxx.DLL提供される共通メモリ アロケーターを使用します。 アプリケーション、MFC 拡張 DLL、および MFC DLL は、この共有メモリ アロケーターを使用します。 MFC DLL は、メモリ割り当てに共有 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 です。 たとえば、架空の MFC 3.1 では、この DLL のアップグレードには代わりに MFC31.DLL という名前が付けられます。 ここでも、MFC ソース コードを変更して MFC DLL のカスタム バージョンを生成する場合は、別の名前を使用します (名前に "MFC" を含まない名前を使用することをお勧めします)。

こちらも参照ください

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