Share via


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

更新 : 2007 年 11 月

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

ここでは、DLL の 3 とおりの使い方を説明します。2 番目と 3 番目の説明は、上級者向けです。

  • MFC 拡張 DLL の作成

  • DLL バージョンを使うアプリケーションの作成

  • MFCxx.DLL の実装方法

非 MFC アプリケーションからも使用できる DLL (標準 DLL) のビルド方法については、「テクニカル ノート 11: DLL の構成要素としての MFC」を参照してください。

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

標準 DLL : 標準 DLL を使って、MFC クラスを使うスタンドアロンの DLL を構築できます。アプリケーションと DLL の間のインターフェイスは "C" インターフェイスです。クライアント アプリケーションは MFC アプリケーションでなくてもかまいません。

これは MFC 1.0 でサポートされているバージョンの DLL です。詳細については、「テクニカル ノート 11: DLL の構成要素としての MFC」および『MFC サンプル』の DLLScreenCap を参照してください。

hw85e4bb.alert_note(ja-jp,VS.90).gifメモ :

Visual C++ バージョン 4.0 では USRDLL という語は互換性のために残されていますが、ここでは MFC と静的にリンクする標準 DLL に置き換えられています。MFC と動的にリンクする標準 DLL をビルドすることもできます。

MFC 3.0 以降のバージョンでは、OLE クラスやデータベース クラスなどの新しい機能を持つ標準 DLL をサポートしています。

AFXDLL : 共有バージョンの MFC ライブラリも意味します。これは MFC 2.0 でサポートされている新しい DLL です。MFC ライブラリ自体が何種類もの DLL (下を参照) に含まれており、クライアント アプリケーションまたは DLL は必要な DLL に動的にリンクされます。アプリケーションと DLL の間のインターフェイスは、C++/MFC クラス インターフェイスです。クライアント アプリケーションは、MFC アプリケーションにする必要があります。MFC 3.0 のすべての機能がサポートされています。ただし、データベース クラスに対しては Unicode はサポートされていません。

hw85e4bb.alert_note(ja-jp,VS.90).gifメモ :

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

ここでは、次の MFC DLL をすべて "MFCxx.DLL" で表します。

  • デバッグ : MFCxxD.DLL (複合)、MFCSxxD.LIB (静的)

  • リリース : MFCxx.DLL (複合)、MFCSxx.LIB (静的)

  • Unicode デバッグ : MFCxxUD.DLL (複合)、MFCSxxD.LIB (静的)

  • Unicode リリース : MFCxxU.DLL (複合)、MFCSxxU.LIB (静的)

hw85e4bb.alert_note(ja-jp,VS.90).gifメモ :

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 を使うメリット

  • 共有ライブラリを使うと、アプリケーションを小さくできます。MFC ライブラリを主として使用するアプリケーションは最小で 10K 以下になります。

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

  • 共有バージョンの MFC を使うアプリケーションのビルドは、MFC 自体をリンクしなくてよいため、静的にリンクされた MFC アプリケーションのビルドに比べて高速です。特に DEBUG ビルドの場合は、既に DLL がデバッグ情報の一部を含んでいるために、リンカがアプリケーションに取り込まなければならないデバッグ情報が減少し、より高速にビルドできます。

共有バージョンの MFC を使うデメリット

  • 共有ライブラリを使うアプリケーションを出荷するには、MFCxx.DLL (や他のライブラリ) をアプリケーションに添付する必要があります。MFCxx.DLL は、他のライブラリ同様自由に頒布できますが、DLL を SETUP プログラムでインストールする必要があります。さらに、アプリケーションや MFC DLL が使う C 言語のランタイム ライブラリを含む MSVCRTxx.DLL を頒布することも必要です。

MFC 拡張 DLL の作成

MFC 拡張 DLL は、MFC クラスの機能を拡張するためのクラスや関数を含んだ DLL です。MFC 拡張 DLL は、共有 MFC DLL をアプリケーションと同じように利用します。その際、次の点を考慮する必要があります。

  • ビルドの手順は、共有 MFC ライブラリを使用するアプリケーションのビルドの場合とほぼ同じですが、いくつかのコンパイラ オプションやリンカ オプションの追加が必要です。

  • MFC 拡張 DLL は、CWinApp の派生クラスは持っていません。

  • MFC 拡張 DLL は、専用の DllMain を提供する必要があります。AppWizard によって提供される DllMain 関数を変更できます。

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

上の条件について、後で詳しく説明します。『MFC サンプル』の DLLHUSK を調べると、次のようなことがわかります。

  • 共有ライブラリを使ったアプリケーションのビルド。DLLHUSK.EXE は、他の DLL と同じように MFC ライブラリとも動的にリンクするアプリケーションです。

  • MFC 拡張 DLL のビルド。拡張 DLL のビルドのときには _AFXEXT などの特別なフラグが使われます。

  • 2 つの MFC 拡張 DLL のサンプル。1 つは MFC 拡張 DLL の基本的な構造の例で、クラスの一部をエクスポートします (TESTDLL1)。もう 1 つはクラス インターフェイス全体をエクスポートする例です (TESTDLL2)。

クライアント アプリケーションでも拡張で DLL も、同じバージョンの MFCxx.DLL を使用する必要があります。MFC DLL の規約に従って、デバッグ バージョンとリテール (リリース) バージョンの拡張 DLL を提供してください。これによって、クライアント プログラムではデバッグ用とリテール用のアプリケーションをビルドして、該当する DLL (デバッグ用またはリテール用) とリンクできます。

hw85e4bb.alert_note(ja-jp,VS.90).gifメモ :

C++ の名前の混乱を避け、またエクスポート問題に対処するために、拡張 DLL のエクスポート リストは同じ DLL のデバッグ バージョンとリテール版では異なる場合があります。また、対応するプラットフォームが異なる DLL の間でも異なる場合があります。リテール バージョンの MFCxx.DLL には約 2000 のエクスポートされたエントリがあり、デバッグ バージョン MFCxxD.DLL には約 3000 のエクスポートされたエントリがあります。

メモリ管理についての簡単な注意

このテクニカル ノートの最後の「メモリ管理」では、共有バージョンの MFC による MFCxx.DLL の実装について説明します。ここでは、拡張 DLL の実装に必要な内容を説明します。

MFCxx.DLL やすべての拡張 DLL は、クライアント アプリケーションのアドレス空間に読み込まれ、同じアプリケーション内にあるかのように、同じメモリ アロケータを使用し、同じ方法でリソースを読み込み、MFC の "グローバル" 状態も共有します。非 MFC DLL ライブラリや、MFC と静的にリンクする標準 DLL では、まったく逆に各 DLL はアプリケーションのメモリ プールの外に割り当てられます。

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

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

拡張 DLL のビルド

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

既存のプロジェクトを MFC 拡張 DLL に変換するには、まず、MFC の共有バージョンを使うアプリケーションをビルドするための標準的な規則に従います。その後、次の手順に従います。

  • /D_AFXEXT をコンパイラ フラグに追加します。[プロジェクト] メニューの [プロパティ] ダイアログ ボックスの [C/C++] ノードを選択します。次に [プリプロセッサ] カテゴリを選択します。各項目をセミコロン (;) で区切って、_AFXEXT を [プロセッサの定義] フィールドに追加します。

  • /Gy コンパイラ スイッチを削除します。[プロジェクト] メニューの [プロパティ] ダイアログ ボックスの [C/C++] ノードを選択します。[コード生成] カテゴリを選択します。[関数レベルでリンクする] が無効になっていることを確認します。こうすると、リンカが参照されていない関数を削除しないので、クラスを簡単にエクスポートできます。既存のプロジェクトを使って、MFC と静的にリンクする標準 DLL をビルドする場合は、/MT[d] コンパイラ オプションを /MD[d] に変更します。

  • /DLL オプションでリンクを指定して、エクスポート ライブラリをビルドします。これは、ターゲットの新規作成時にターゲット タイプとして [Win32 Dynamic-Link Library] を指定すると設定されます。

ヘッダー ファイルの変更

拡張 DLL は、通常、共通機能を 1 つ以上のアプリケーションにエクスポートするときに使います。つまり、クライアント アプリケーションで利用できるクラスおよびグローバル関数をエクスポートするときに使います。

したがって、各メンバ関数を必要に応じてインポート関数またはエクスポート関数としてマークする必要があります。これには専用の宣言、declspec(dllexport) および declspec(dllimport) を使います。クライアント アプリケーションで使われるクラスは、declspec(dllimport) として宣言します。拡張 DLL 自体をビルドするときは、declspec(dllexport) として宣言します。関数は実際にエクスポートしないと、クライアント プログラムが読み込み時にバインドできません。

クラス全体をエクスポートするには、AFX_EXT_CLASS をクラス定義で使います。このマクロは、_AFXDLL および _AFXEXT が定義されていると、フレームワークにより __declspec(dllexport) として定義されます。_AFXEXT が定義されていないと、__declspec(dllimport) として定義されます。_AFXEXT は上に述べたように、拡張 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 からエクスポートする必要があります。

たとえば、拡張 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_DATAAFX_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 にも使えます。

hw85e4bb.alert_note(ja-jp,VS.90).gifメモ :

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

DECLARE_SERIAL マクロおよび IMPLEMENT_SERIAL マクロを使用するクラスは、同じ方法で CArchive の入力ストリーム演算子 (>>) を自動的にエクスポートできます。CArchive 演算子をエクスポートするには、.H ファイルのクラス宣言を次のコードで囲みます。

#undef AFX_API
#define AFX_API AFX_EXT_CLASS

<your class declarations here>

#undef AFX_API
#define AFX_API

_AFXEXT に関する制限事項

独自の拡張 DLL に対してプリプロセッサ シンボル _AFXEXT を使用できます。ただし、拡張 DLL の層が複数ある場合は使えません。MFC クラスから派生している独自の拡張 DLL の中のクラスを呼び出したり、このようなクラスから派生している拡張 DLL がある場合は、あいまいさを避けるために必ず独自のプリプロセッサ シンボルを使ってください。

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

層が複数ある場合、拡張 DLL は新しいクラスをエクスポートすると同時に、他の拡張 DLL からクラスをインポートすることがあります。したがって、AFX_EXT_CLASS などのシンボルが 1 つでは足りません。この問題に対処するには、専用のプリプロセッサ シンボルを使って、DLL 自体をビルドしているのか、DLL を使用しているのかを明示します。たとえば、A.DLL と B.DLL の 2 つの拡張 DLL がある場合を考えてみます。それぞれ一部のクラスを A.H および B.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 のビルド時には、/D A_IMPL が設定され、B.DLL のビルド時には、/D B_IMPL が設定されます。それぞれの DLL でシンボルを使い分けることによって、B.DLL をビルドすると、CExampleB はエクスポートされ、CExampleA はインポートされるようになります。A.DLL をビルドすると CExampleA はエクスポートされ、B.DLL (などのクライアント) で使われるときにはインポートされます。

このような操作は、組み込みのプリプロセッサ シンボルである AFX_EXT_CLASS および _AFXEXT では実現できません。この問題は、上で説明した手法を使えば、MFC 自体が OLE 拡張 DLL、データベース拡張 DLL、ネットワーク拡張 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

拡張 DLL のための、メイン ソースに置かれる完全なコードを次に示します。このコードは、標準のインクルードの後ろに記述してください。AppWizard を使って拡張 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)
   {
      // Extension DLL one-time initialization 
      if (!AfxInitExtensionModule(
             extensionDLL, hInstance))
         return 0;

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

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

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

拡張 DLL の読み込みと解放を動的に行うアプリケーションの場合は、AfxTermExtensionModule を呼び出す必要があります。また、アプリケーションでマルチスレッドを使用する場合や、拡張 DLL を動的に読み込む場合は、Win32 関数 LoadLibraryFreeLibrary の代わりに AfxLoadLibraryAfxFreeLibrary を使う必要があります。AfxLoadLibraryAfxFreeLibrary を使用することによって、拡張 DLL の読み込みまたはアンロード時に実行されるスタートアップ コードと終了コードが、グローバルな MFC の状態を破損するのを防ぎます。

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

グローバルな extensionDLL は、上のように宣言する必要があります。16 ビット バージョンの MFC とは異なり、DllMain が呼び出されたときには MFCxx.DLL は完全に初期化されているので、ここでメモリの割り当てや 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 やクライアント アプリケーションに MFC の標準リソースのコピーを含める必要がなくなります。

すべての DLL のリソースやクラス名をクライアント アプリケーションの名前空間に混在させると、ID や名前を選択するときに注意が必要です。もちろん、リソースや CDynLinkLibrary オブジェクトをエクスポートしないことによって、この機能を使わないこともできます。DLLHUSK サンプルでは、複数のヘッダー ファイルを使って、共有リソースの名前空間を管理します。共有リソース ファイルの使い方については、「テクニカル ノート 35: Visual C++ における複数のリソース ファイルとヘッダー ファイルの使用」を参照してください。

DLL の初期化

前に述べたように、通常はクライアント アプリケーションにリソースやクラスをエクスポートするときに CDynLinkLibrary オブジェクトを作成します。DLL を初期化するときは、エントリ ポイントをエクスポートする必要があります。最も単純な形式では、このエントリは引数も戻り値も持たない void ルーチンですが、それ以外の形式も使用できます。

DLL を使う各クライアント アプリケーションで、必ず初期化ルーチンを呼び出します。この AfxInitExtensionModule 関数の呼び出しの直後に CDynLinkLibrary オブジェクトを DllMain 関数で割り当てることもできます。

初期化ルーチンでは、拡張 DLL 情報を通知するための CDynLinkLibrary オブジェクトを現在のアプリケーションのヒープ領域に作成してください。これは、次のように書くことができます。

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

この例では、ルーチンの名前は InitXxxDLL になっていますが、別の名前でもかまいません。extern "C" 宣言も特に必要というわけではありませんが、こうしておくとエクスポート リストの保守が簡単になります。

hw85e4bb.alert_note(ja-jp,VS.90).gifメモ :

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

エントリのエクスポート

エクスポートするクラスとグローバル関数に対して __declspec(dllimport)__declspec(dllexport) を使うと、クラスを簡単にエクスポートできます。ただし、この方法ではどの関数がエクスポートされるか詳しく指定できないので、各エントリ ポイントを列挙する方法 (後述) に比べると効率は落ちます。TESTDLL1 や TESTDLL2 では、この方法でエントリをエクスポートします。

各エントリを 1 つずつ .DEF ファイルに列挙してエクスポートする方がより効率的です。MFCxx.DLL ではこの方法が使われています。DLL のすべてのインターフェイスをエクスポートするわけではないので、どのインターフェイスをエクスポートするか決定する必要があります。.DEF ファイルには、リンカ用に変換したエントリ名を指定しなければならないので、この作業は困難です。C++ のクラスとのシンボリック リンクが必要な場合以外は、C++ のクラスをエクスポートしないでください。

.DEF ファイルを使って C++ クラスをエクスポートしたときは、ツールを開発してこのエクスポート リストの生成を自動化できます。これには 2 段階のリンク プロセスが必要です。まず、エクスポートなしで DLL をリンクし、リンカに .MAP ファイルを生成させます。この .MAP ファイルを使って、エクスポートする関数のリストを生成できるので、若干の修正で .DEF ファイル用の EXPORT エントリを生成できます。MFCxx.DLL や OLE 拡張 DLL、データベース拡張 DLL のエクスポート リストも、この方法で生成されました。ただし、完全に自動化できたわけではなく、手作業による調整が多少は必要でした。

CWinApp と CDynLinkLibrary

MFC 拡張 DLL は、独自の CWinApp 派生オブジェクトは持っていないので、その代わりにクライアント アプリケーションの CWinApp 派生オブジェクトを使います。つまり、拡張 DLL は、クライアント アプリケーションのメイン メッセージ ポンプやアイドル ループなどを利用します。

MFC 拡張 DLL でアプリケーションごとの補足データを保存する必要があるときは、CDynLinkLibrary から新しいクラスを派生して、上に説明した InitXxxDLL ルーチンで初期化します。DLL は、実行時に現在のアプリケーションの CDynLinkLibrary オブジェクト リストから特定の拡張 DLL 用のオブジェクトを探し出します。

独自の DLL でのリソースの使用

前に説明したように、リソースの読み込み時に、既定では CDynLinkLibrary オブジェクトのリストを検索して、要求されたリソースを持つ最初の EXE または DLL を探し出します。すべての MFC API は、すべての内部コードと同じように、リソースがどこにある場合でも、AfxFindResourceHandle を使ってリソース リストを検索します。

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

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

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

アプリケーションの要件

MFC の共有バージョンを使うアプリケーションには、次のような簡単な要件があります。

  • CWinApp オブジェクトを持ち、メッセージ ポンプの標準的な規則に従っている。

  • 必要なコンパイラ フラグ (下記参照) を指定してコンパイルされている。

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

  • 実行可能ファイルを実行する際、MFCxx.DLL がパスの設定されているフォルダまたは Windows システム フォルダにある。

開発環境によるビルド

内部的な既定のメイクファイルを使っている場合は、DLL バージョンを構築するようにプロジェクトを簡単に変更できます。

次のステップは、NAFXCWD.LIB (デバッグ用) や NAFXCW.LIB (リテール用) とリンクされた正常に動作する MFC アプリケーションが既に存在することを前提として、それを共有バージョンの MFC ライブラリを使うように変換します。また、Visual C++ 環境が動作していて、内部プロジェクト ファイルがあるものとします。

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

NMAKE を使ったビルド

Visual C++ の外部メイクファイル機能を使う場合や、NMAKE を直接使う場合は、メイクファイルを編集してコンパイラ オプションやリンカ オプションをサポートします。

必要なコンパイラ フラグを次に示します。

  • /D_AFXDLL /MD
    /D_AFXDLL

標準の MFC ヘッダーを使うときは、このシンボルを定義します。

  • /MD
    アプリケーションで DLL バージョンの C ランタイム ライブラリを使います。

これ以外のコンパイラ フラグは、MFC の既定 (デバッグ時の _DEBUG など) に従います。

リンカのライブラリ リストを編集します。さらに NAFXCWD.LIB を MFCxxD.LIB に、NAFXCW.LIB を MFCxx.LIB に変更します。LIBC.LIB は MSVCRT.LIB に置き換えます。他の MFC ライブラリと同じように、MFCxxD.LIB は C ランタイム ライブラリの前に配置する必要があります。

リテール用、デバッグ用にかかわらず、リソース コンパイラ オプション (/R でリソースをコンパイルするオプション) に /D_AFXDLL を追加してもかまいません。これによって MFC DLL に含まれているリソースが共有されるようになり、最終的な実行可能ファイルが小さくなります。

これらの変更の後にフル リビルドを行います。

サンプルのビルド

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

MFCxx.DLL を使うようにこれらのサンプル プログラムを変換するには、.MAK ファイルを Visual C++ に読み込み、プロジェクトの設定を上のように変更します。NMAKE でビルドする場合は、NMAKE コマンド ラインで "AFXDLL=1" と指定します。サンプル プログラムは共有 MFC ライブラリを使用するようにビルドされます。

『MFC サンプル』の DLLHUSK は DLL バージョンの MFC でビルドされています。このサンプル プログラムを見ると、アプリケーションと MFCxx.DLL とのリンク方法がわかります。また、後で説明する MFC 拡張 DLL などの MFC DLL パッケージ オプションの機能も理解できます。

パッケージ化についての注意

リテール バージョンの DLL (MFCxx[U].DLL) は自由に再頒布できます。デバッグ バージョンの DLL は再頒布できません。これはアプリケーションの開発にだけ使ってください。

デバッグ バージョンの DLL にはデバッグ情報が含まれています。Visual C++ デバッガを使うと、DLL だけでなくアプリケーションの実行もトレースできます。リリース バージョンの DLL (MFCxx[U].DLL) にはデバッグ情報は含まれません。

DLL をカスタマイズまたはビルドし直すときは、"MFCxx" 以外の名前を付けて呼び出す必要があります。MFC SRC ファイル MFCDLL.MAK には、ビルド オプションや DLL 名の変更方法が記述されています。これらの DLL は多くの MFC アプリケーションで共用されている可能性があるので、ファイル名を変更する必要があります。最初にシステムにインストールされていた DLL をカスタマイズされた 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 では、多くのクラスがエクスポートされています。標準の拡張 DLL を特化するために、MFC DLL での補足作業が必要です。

Win32 における新機能

16 ビット バージョンの MFC では、アプリケーションごとのデータをスタック セグメントに割り当てたり、80x86 アセンブリ コードで特別なセグメントを作成したり、プロセスごとの例外処理などの特殊なテクニックが必要でした。Win32 では、時間のかかるプロセスごとの DLL データ管理が実現されています。MFCxx.DLL の多くの部分は、単に NAFXCW.LIB を DLL にパッケージしたものです。_AFXDLL 特有の処理はほとんどないので、MFC のソース コードには #ifdef _AFXDLL はわずかしか含まれていません。例外は Win32 を Windows 3.1 で扱う処理 (Win32s) だけです。Win32s はプロセスごとの DLL データの管理をサポートしていないので、MFC DLL では、スレッド ローカル ストレージ (TLS: Thread-Local Storage) 用の Win32 API を使って、プロセス固有のデータを取得します。

ライブラリ ソースへの影響と付加的なファイル

通常の MFC クラス ライブラリのソースやヘッダーに対する _AFXDLL バージョンの影響はほとんどありません。付加的なヘッダー ファイル (AFXDLL_.H) と専用バージョンのヘッダー ファイル (AFXV_DLL.H) がメインの AFXWIN.H ヘッダー ファイルでインクルードされます。AFXDLL_.H ヘッダー ファイルには、CDynLinkLibrary クラス、および _AFXDLL アプリケーションと MFC 拡張 DLL の実装の詳細部分が含まれています。AFXDLLX.H ヘッダー ファイルは、MFC 拡張 DLL をビルドするために用意されています。詳細については、上の説明を参照してください。

MFC SRC の MFC ライブラリの標準的なソースには、_AFXDLL の条件ブロック文 (#ifdef) で囲まれたコードがいくつか追加されました。また、追加のソース ファイル (DLLINIT.CPP) には、共有バージョンの MFC 用の補足 DLL 初期化コードなどの追加コードが含まれています。

共有バージョンの MFC をビルドするために、次のファイルが追加されています。DLL のビルド方法については、後で詳細に説明します。

  • 専用のメイクファイル (MFCDLL.MAK)。DLL のビルドに使います。このメイクファイルには標準の MAKEFILE の内容が含まれているので、普通の MFC ライブラリのビルドに必要な規則も適用されます。

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

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

  • .CLW ファイル (MFCDLL.CLW)。ClassWizard で MFC を参照できるように用意されています。なお、この機能は DLL バージョンの MFC に特有のものではありません。

MFC DLL のリビルド

MFC DLL のリビルドは意図的に難しくしてあるので、実行する前によく考えてください。後で説明するパッケージ化に伴う潜在的な問題や再頒布の制限を理解したうえで、それでも MFC DLL のリビルドが必要だと考えた場合は、リビルドを行ってください。

次のように書くと、MFCDLL.MAK ファイルは CodeView への情報を含むデバッグ DLL をビルドします。

NMAKE /f mfcdll.mak DEBUG=1 LIBNAME=MYMFC

次のように書くと、MFCDLL.MAK ファイルは CodeView への情報を含まないリリース DLL をビルドします

NMAKE /f mfcdll.mak DEBUG=0 LIBNAME=MYMFC

これによって、MFC DLL の独自のバージョン (MFCxx.DLL と MFCxxD.DLL) が MFC SRC ディレクトリに作成されます。この新しい DLL を使うには、パスで指定されているフォルダにコピーします。MFCDLL.MAK メイクファイルは、インポート ライブラリ (MFCxx.LIB および MFCxxD.LIB) もビルドし直して、標準の MFC LIB フォルダに配置します。既存の MFCxx.LIB ライブラリと MFCxxD.LIB ライブラリは置き換えられてしまうので注意してください。

変更した MFC DLL ライブラリを再頒布するには、MFCDLL.MAK メイクファイルと 2 つの .DEF ファイルで、DLL 名を変更する必要があります。詳細については、MFCDLL.MAK メイクファイルを参照してください。

変更したライブラリのリテール (リリース) バージョンを頒布するには、その名前を MFCxx.DLL 以外に変更します。デバッグ バージョンの DLL については、変更前、変更後にかかわらず、再頒布できません。

このように再頒布を制限する理由は、主として、非標準的な DLL やウイルスに感染しているおそれのある DLL の蔓延を防ぐためのものです。可能な場合は DLL のリビルドは避けてください。アプリケーションを Visual C++ 付属の MFCxx.DLL と一緒に再頒布できれば、多くの問題を避けることができます。

メモリ管理

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

序数値と class __declspec(dllexport) および DLL の名前

C++ コンパイラの class**__declspec(dllexport)** 機能は使いません。代わりにエクスポート項目 (関数とデータ) のリストがクラス ライブラリのソース ファイルに含まれます (MFCxx.DEF と MFCxxD.DEF)。エントリ ポイントで選択した項目 (関数とデータ) だけがエクスポートされます。MFC のプライベート関数やクラスはエクスポートされません。すべてのエクスポートは、常駐あるいは非常駐の名前テーブルにある文字列名ではなく、序数値によって行われます。

class**__declspec(dllexport)** を使うとより小さな DLL を作成できます。しかし、MFC のような大きな DLL の場合は、既定のエクスポート機構では効率や容量に制限があります。

リリース用の MFCxx.DLL には多くの機能が組み込まれていますが、その大きさは、実行速度や読み込み速度を落とすことなく約 800 KB に押さえられています。この方法を使わなかった場合は、MFCxx.DLL の大きさは 100 KB 程大きくなっていたはずです。また、.DEF ファイルの最後に付加的なエントリ ポイントを追加すると、序数値によるエクスポートの速度とサイズの効率を落とさずに簡単にバージョン管理できます。MFC クラス ライブラリのバージョン改訂による主な変化は、ライブラリ名の変更です。たとえば、MFC30.DLL は MFC クラス ライブラリの Version 3.0 を含む再頒布可能な DLL です。この DLL が将来アップグレードされて MFC 3.1 になると、その名前は MFC31.DLL になるはずです。したがって、MFC のソース コードを変更して MFC DLL のカスタム バージョンを作成するときは、別の名前を使ってください。"MFC" を含まない名前をお勧めします。

参照

その他の技術情報

番号順テクニカル ノート

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