相互インポート
更新 : 2007 年 11 月
相互 (または、循環) インポートでは、他の実行可能ファイルへのエクスポートとインポートが複雑になります。たとえば、相互再帰関数のように、2 つの DLL が相互にシンボルをインポートするような場合です。
相互インポートを含む実行可能ファイル (通常は DLL) の問題は、相手が先にビルドされていないとビルドできない点です。それぞれのビルド プロセスには、他のビルド プロセスで作成されたインポート ライブラリを読み込む必要があります。
この問題を解決するには、LIB ユーティリティに /DEF オプションを付けます。こうすると、実行可能ファイルをビルドしなくても、必要なインポート ライブラリを作成できます。このユーティリティを使うと、関係する DLL の数や DLL 間の依存関係の複雑さに関係なく、必要なすべてのインポート ライブラリをビルドできます。
次に、相互インポートの一般的な解決方法を示します。
DLL を順に処理します。処理の順番は自由ですが、最適な順番がある場合もあります。必要なインポート ライブラリがすべて揃っている場合は、LINK を実行して実行可能ファイル (DLL) をビルドします。この結果、DLL のインポート ライブラリが作成されます。必要なライブラリが揃っていない場合は、LIB を実行してインポート ライブラリを作成します。
LIB に /DEF オプションを付けて実行すると、ライブラリのほかに、拡張子が .EXP のファイルも作成されます。.EXP ファイルは、後で実行可能ファイルをビルドするのに使用されます。
LINK か LIB のどちらかを使ってすべてのインポート ライブラリをビルドし終えたら、LINK を実行して前の手順でビルドされなかった実行可能ファイルをビルドします。対応する .exp ファイルは、LINK のコマンド ラインで指定することに注意してください。
既に LIB ユーティリティを実行して DLL1 用のインポート ライブラリを作成した場合は、DLL1.exp も作成されています。DLL1.dll のビルド時には、LINK への入力に DLL1.exp を使用する必要があります。
次の図は、2 つの相互インポート DLL の DLL1 と DLL2 のソリューションを示します。手順 1 では、DLL1 に対して、LIB に/DEF オプションを付けて実行します。手順 1 では、インポート ライブラリ DLL1.lib と DLL1.exp が作成されます。手順 2 では、そのインポート ライブラリを使って DLL2 をビルドします。DLL2 は、DLL2 のシンボル用のインポート ライブラリを作成します。手順 3 では、入力として DLL1.exp と DLL2.lib を使って、DLL1 をビルドします。DLL2 のインポート ライブラリのビルドには LIB を使わなかったので、DLL2 用の .exp ファイルは不要です。
相互インポートを使った 2 つの DLL のリンク
_AFXEXT に関する制限事項
拡張 DLL の層が単一の場合は、拡張 DLL に対して _AFXEXT プリプロセッサ シンボルを使用できます。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 のクラスを使用します。このとき、ヘッダー ファイルは次のようになります。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 では実現できません。この手法は、Active テクノロジ拡張 DLL、データベース拡張 DLL、ネットワーク拡張 DLL のビルド時に MFC 自体が使用する機構とほとんど同じです。
クラス全体をエクスポートしない場合
クラスを部分的にエクスポートする場合は、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