DLL概要
DLL(Dynamic Link Library)は,複数起動されたアプリケーションから共通して使われるバイナリのプログラムコードの群体。大きな利点として,複数のアプリケーションからコード部を共有して使えるのでメモリ内にひとつしかロードする必要がなくメモリを節約できる事がある。
またDLLを置きかえる事でバグの修正,機能強化を簡単に組み込む事ができる。DLLの欠点は,DLL HELL とよばれる現象を引き起こす事である。
簡単なDLLプログラミング(Visual C++6.0)
DLLの種類
DLLの作成は,Microsoft Visual C++ 6.0のプロジェクト作成から次の種類を選択する所から始まる。 選択にはウィザードを使う。
DLLの構成
ウィザードは,テンプレートを使って必要なファイルを出力する。
DLLのコード
MFCで作成するDLL
MFCで作成するDLLには次の種類がある。
名前規約(Decorated Names Format)
C++で作成したDLL,Cで作成したDLLをそれぞれ呼び出すケース。
コンパイル時はヘッダを使用して関数名をCまたはC++の命名規則に従い内部名を生成する。リンカーはコンパイル時に決定された内部名を使用してLibファイル内を探索する。そこで見つかったものを呼び出すように呼び出す部を展開する。
CとC++の違いは名前規約に現れる。Cの名前規約はソースで記述された関数名の前に_(アンダーバー)が付加したものをオブジェクト名とする。C++は,オーバロードの違いを解決するためにオブジェクト名は関数名に更に引数の型を追加した名前とする。C++のクラスは,クラス名+関数名+引数情報のような名前になる。
このためCとC++では同じ関数名でも異なる装飾名が生成される。
ではCとC++のどちらの名前規約が選択されるかは,使用するコンパイラによって決定する。使用されたものがCのコンパイラならCの名前規約に,C++のコンパイラならC++の名前規約で展開する。
どちらのコンパイラを選択するかは拡張子が.cまたは.cppかで決定する(makeファイルの定義も参照)。
CからC,C++からC++で作成したDLLを呼び出す場合は,名前規約が同じなので問題は起きない。CからC++の装飾名は呼び出せない(CのコンパイラはC++の装飾名を生成できない)。
C++からCの関数を呼び出す場合によく関数が見つからない問題が起きる。この場合C++の名前規則でビルドした名前でCライブラリの中を探しに行くからである(名前規約が違うので)。
そこでその関数がCライブラリである事を明示的に指示する。
C++のDLLをCからも使わせたい場合は,C++内の関数をCの命名規則で公開する。その為には関数定義の先頭にextern "C" をつけてビルドする。
.defファイルを使用すると全てCの装飾名で外部に公開される。DLLを静的リンクする場合も,GetProcAddress(WindowsAPI) を使って動的リンクする場合も,Cの名前を定義しておく方が扱いやすい。C++の命名規則で作成された関数をCから呼び出す場合は,その命名規則に従って明記するか、関数番号を指定して呼び出す。
呼び出し規約
関数の呼び出しでは引数はスタックに積み上げられる。この時、下から積むか上から積むかを指定する。この違いはpascalとCで異なっていた事に起因する。
DLL(Dynamic Link Library)は,複数起動されたアプリケーションから共通して使われるバイナリのプログラムコードの群体。大きな利点として,複数のアプリケーションからコード部を共有して使えるのでメモリ内にひとつしかロードする必要がなくメモリを節約できる事がある。
またDLLを置きかえる事でバグの修正,機能強化を簡単に組み込む事ができる。DLLの欠点は,DLL HELL とよばれる現象を引き起こす事である。
簡単なDLLプログラミング(Visual C++6.0)
DLLの種類
DLLの作成は,Microsoft Visual C++ 6.0のプロジェクト作成から次の種類を選択する所から始まる。 選択にはウィザードを使う。
type | description |
---|---|
Win32 Dynamic-Link Library | 単純なDLLプロジェクト |
Win32 Dynamic-Link Library | シンボルをエクスポートするDLL |
MFC AppWizard(DLL) | MFCの共有DLLを使用 |
MFC AppWizard(DLL) | MFCの拡張DLL(MFCの共有DLLを使用) |
DLLの構成
ウィザードは,テンプレートを使って必要なファイルを出力する。
file type | extention | description |
---|---|---|
定義ファイル | .DEF | 外部に公開する関数の一覧を記述する。ここで定義した関数だけが外部のプログラムから呼び出せる。このファイルを使用する代わりにdllexportを定義する事でも公開できる。 |
ヘッダファイル | .H | C/C++ヘッダファイル。DLLを使うアプリケーション開発者へ公開する。コンパイラが参照する。 |
インプリメンテーションファイル | .C/.CPP | 関数・クラスの実装部 |
DLLファイル | .DLL | 作成されたDLLファイル。DLLを使うアプリケーションに添付する。 |
LIBファイル | .LIB | リンカーが参照する。DLLを使うアプリケーション開発者へ公開する。LoadLibraryを使用し呼び出す場合は参照の必要はない。 |
DLLのコード
extension | example | description |
---|---|---|
.DEF |
;DLL用のモジュールパラメータ宣言 LIBRARY "DLL名" DESCRIPTION 'コメント' ;明示的なエクスポートを記述 EXPORTS MyDllFuncが | 外部に提供する関数をEXPORTSに記述する |
.H | #ifdef MYDLL_EXPORTS #define MYDLL_EXT __declspec(dllexport) #else #define MYDLL_EXT __declspec(dllimport) #endif //クラス定義 class MYDLL_EXT CMyCls { public: CMyCls(void); }; //変数定義 extern MYDLL_EXT int nMyValue; //関数定義 |
ヘッダファイルはDLL開発者とDLL利用者の両方が参照するインターフェイス公開を目的としたファイルになる。 #ifdefの切り分けは,DLL開発者とDLL利用者を切り分けている。DLL開発者はMYDLL_EXPORTSを定義しておく。 DLLをビルドする側が__declspec(dllexport) を,LIBを参照する側は__declspec(dllimport)を展開してビルドする。 これらのキーワードを使用した場合,DEFファイルの定義は不要。 |
.CPP | //DLLエントリ BOOL APIENTRY DllMain ( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } //変数実装 MYDLL_EXT int nMyValue=0; //関数実装 MYDLL_EXT int MyDllFunc(void) { return 42; } //クラス定義 CMyClass::CMyClass() { return; } |
DLLMainは,DLLがロードされたときにWindowsが呼び出す DLLのエントリーポイント。 DLL_PROCESS_ATTACHDLL:メモリにロード DLL_THREAD_ATTACHDLL:プロセスにロード DLL_THREAD_DETACHDLL:プロセスから解放 DLL_PROCESS_DETACHDLL:メモリから解放 これらの制御にしたがってDLLの初期化,プロセス内でのライブラリの初期化を実装する。 staticで定義された変数が全アプリケーションで共有される訳ではない。何かを複数アプリケーションで共有したい場合はプロセス間通信(IPC)を利用する。 スレッドセーフにするには排他制御する必要がある。Thread Local Storageを使う事でスレッド毎の情報を管理する事ができる。 |
MFCで作成するDLL
MFCで作成するDLLには次の種類がある。
type | description |
---|---|
MFCの共有DLLを使用 | CWinAppから派生されたアプリケーションオブジェクトを作成する。 |
MFCの拡張DLL(MFCの共有DLLを使用) | MFCを使って派生した新しいMFCクラス,関数を提供する。 |
名前規約(Decorated Names Format)
C++で作成したDLL,Cで作成したDLLをそれぞれ呼び出すケース。
コンパイル時はヘッダを使用して関数名をCまたはC++の命名規則に従い内部名を生成する。リンカーはコンパイル時に決定された内部名を使用してLibファイル内を探索する。そこで見つかったものを呼び出すように呼び出す部を展開する。
CとC++の違いは名前規約に現れる。Cの名前規約はソースで記述された関数名の前に_(アンダーバー)が付加したものをオブジェクト名とする。C++は,オーバロードの違いを解決するためにオブジェクト名は関数名に更に引数の型を追加した名前とする。C++のクラスは,クラス名+関数名+引数情報のような名前になる。
このためCとC++では同じ関数名でも異なる装飾名が生成される。
ではCとC++のどちらの名前規約が選択されるかは,使用するコンパイラによって決定する。使用されたものがCのコンパイラならCの名前規約に,C++のコンパイラならC++の名前規約で展開する。
どちらのコンパイラを選択するかは拡張子が.cまたは.cppかで決定する(makeファイルの定義も参照)。
CからC,C++からC++で作成したDLLを呼び出す場合は,名前規約が同じなので問題は起きない。CからC++の装飾名は呼び出せない(CのコンパイラはC++の装飾名を生成できない)。
C++からCの関数を呼び出す場合によく関数が見つからない問題が起きる。この場合C++の名前規則でビルドした名前でCライブラリの中を探しに行くからである(名前規約が違うので)。
そこでその関数がCライブラリである事を明示的に指示する。
extern "C" { #include "this_is_c_lib.h" }
C++のDLLをCからも使わせたい場合は,C++内の関数をCの命名規則で公開する。その為には関数定義の先頭にextern "C" をつけてビルドする。
extern "C"__declspec(dllexport) cpp_func_by_c_call ( int i );
.defファイルを使用すると全てCの装飾名で外部に公開される。DLLを静的リンクする場合も,GetProcAddress(WindowsAPI) を使って動的リンクする場合も,Cの名前を定義しておく方が扱いやすい。C++の命名規則で作成された関数をCから呼び出す場合は,その命名規則に従って明記するか、関数番号を指定して呼び出す。
呼び出し規約
関数の呼び出しでは引数はスタックに積み上げられる。この時、下から積むか上から積むかを指定する。この違いはpascalとCで異なっていた事に起因する。
Calling Conventions | description |
---|---|
__pascal | order |
__cdecl | reverse order |
__stdcall | reverse order(WINAPI) |