DLL(Dynamic Link Library)は実行時にリンクされるライブラリ(プログラムの部品)です
実行時にリンクされるので、うまい作り方ができればプラグイン的な使い方ができます
(実行ファイルを変えることなく、DLLのみ差し替えれば機能変更できる)
今回はVisual C++を使って、DLLを作ってみます
Visual C++を起動し、新規プロジェクトを作ります
とりあえずコンソールを指定しておきます
(後の設定で変更する)
ここでプロジェクト名は自由に決めていいのですが
プロジェクト名は覚えておいてください
「完了」を押さずに「次へ」を押してください
ここでDLLを選択します
空のプロジェクトにも忘れずにチェックを入れてください
チェックを入れたら完了を押してください
以下のファイルを新規作成します
・DLLMain.cpp
・DLLHeader.h
・TestClass.cpp
・TestClass.h
DLLMain.cppです
// DLLMain.cpp #include <windows.h> // DLLのエントリーポイント // DLL読み込み時やDLL解放時に呼び出され // DLLの初期化や後処理を行う BOOL WINAPI DllMain( HINSTANCE hinstDLL, // DLL モジュールのハンドル DWORD fdwReason, // 関数を呼び出す理由 LPVOID lpvReserved // 予約済み ) { switch(fdwReason) { // このDLLの呼び出し元のアプリケーション(プロセス)が // 動的な呼び出し、またLoadLibrary関数で // このDLLを読み込んだときに行う処理(初期化に使う) case DLL_PROCESS_ATTACH: { // 初期化処理 } break; // このDLLの呼び出し元のアプリケーション(プロセス)が // 終了したあるいはそこからFreeLibrary関数が呼ばれたときに // このDLLはアンロードされる // アンロードされる前に行う処理(後処理に使う) case DLL_PROCESS_DETACH: { // 後処理 } break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; } return TRUE; }
DLLのエントリーポイントです
プロセスやスレッドの初期化時と終了時、
また、LoadLibrary 関数と FreeLibrary 関数の呼び出し時に、
システム(OS)がDLLMain関数を呼び出します。
使い方としてはDLLの初期化処理や後処理などを行うといいでしょう
(実はDLLMain関数は書かなくてもビルドできます)
DLLHeader.hです
このヘッダーはこのDLLを呼び出すアプリケーションでも必要になります
// DLLHeader.h #pragma once #ifdef TESTDLL_EXPORTS // DLL側プロジェクトで定義されている #define _EXPORT __declspec(dllexport) #else #define _EXPORT __declspec(dllimport) #endif /* TESTDLL_EXPORTS */
他のアプリケーションからDLL側の関数やクラスを呼び出すためには
DLL側でそれらの関数やクラスを一回エクスポートし
呼び出し側ではそれらの関数やクラスをインポートする必要があります
(逆に言えば呼び出し側で直接使わせたくない関数やクラスに関してはエクスポートしません)
エクスポートするには次のようなエクスポートキーワードを
エクスポートする関数やクラスの宣言に付けます
__declspec(dllexport)
インポートするには次のようなインポートキーワードを
インポートする関数やクラスの宣言に付けます
__declspec(dllimport)
普通に作ると同じ関数やクラス宣言なのに
エクスポート用とインポート用の
2つのヘッダーを作る必要がでてきてしまうのですが
エクスポート用とインポート用を共通のヘッダーで済ます方法があります
ここでポイントは次の一行です
#ifdef TESTDLL_EXPORTS // DLL側プロジェクトで定義されている
これはどこで定義(#define)されているのでしょうか?
答えはプロジェクトのプロパティ→プリプロセッサの定義です
ここに書かれているものは同一プロジェクト内で#defineされているのと同じ意味を持ちます
DLLプロジェクトを作ると自動的に
「プロジェクト名(大文字)_EXPORTS」
という定義がされます
他のプロジェクトでは定義がされていないので
DLL側とDLL呼び出しアプリケーション側で切り替えを行います
#ifdef TESTDLL_EXPORTS // DLL側プロジェクトで定義されている #define _EXPORT __declspec(dllexport) #else #define _EXPORT __declspec(dllimport) #endif /* TESTDLL_EXPORTS */
つまり、
共通の#defineした_EXPORTを使うことで
DLL側、呼び出し側でエクスポート、インポートの切り替えが行うことができ
DLL側、呼び出し側共通のヘッダーとして扱うことができます
したがって(エクスポートorインポート)する関数やクラスには_EXPORTを付けます
使い方はTestClass.hで説明します
TestClass.hです
// TestClass.h #pragma once #include "DLLHeader.h" // class エクスポート(インポート)キーワード クラス名 class _EXPORT TestClass { public: TestClass(void); virtual ~TestClass(void); }; // 関数の場合は先頭にエクスポート(インポート)キーワードを付ける _EXPORT void Test(); // エクスポートしない関数やクラスにはキーワードを付けない void InOnlyDLLCall();
DLLHeader.hヘッダーのインクルードを忘れないでください
クラスをエクスポート(インポート)するには
class エクスポート(インポート)キーワード クラス名を指定します
関数をエクスポート(インポート)するには
先頭にエクスポート(インポート)キーワードを付けます
エクスポートしない関数やクラスは何もつけません
この場合、呼び出し側ではこの関数やクラスを使うことはできません
(DLL側内では使える)
TestClass.cppです
// TestClass.cpp #include "TestClass.h" #include <tchar.h> #include <windows.h> TestClass::TestClass(void) { MessageBox(NULL,_T("DLLクラス呼び出し"),NULL,MB_OK); } TestClass::~TestClass(void) { } void Test(){ InOnlyDLLCall(); } void InOnlyDLLCall(){ MessageBox(NULL,_T("DLL関数呼び出し"),NULL,MB_OK); }
特に気を付けることは何もありません
ではビルドしてみましょう
ビルドに成功すると
DLLプロジェクトのDebug(もしくはRelease)フォルダに
DLLファイルとLIBファイルが作成されます
では、早速今作ったDLLを使ってみましょう
新規のプロジェクトを作ります
DLLのヘッダーファイルと作成したLIBファイル、DLLファイルが必要なので
今作ったプロジェクトのフォルダにコピーしてください
#include "TestClass.h" #pragma comment(lib,"TestDLL.lib") int main(){ TestClass testclass; Test(); // エクスポートしてないのでリンカエラーとなる //InOnlyDLLCall(); return 0; }
メッセージボックスが出てきたら成功です
ここで気を付けてほしいのはInOnlyDLLCall関数はエクスポートされていないので
呼び出そうとするとリンカーエラーとなります
試しにコメントをはずすしてビルドすると次のエラーが出てきます
error LNKはリンカーのエラーを示しています
void _cdecl InOnlyDLLCall()の横に気味の悪い文字列が出てきます
?InOnlyDLLCall@@YAXXZ
これは関数やクラスの名前修飾と呼ばれ
本当の関数の名前です
@以下は関数の戻り値、引数を示しており
同じ名前の関数で引数、戻り値が違う(オーバーロードされている)場合でも
正しい関数を呼び出しできるのはこれのおかげです
また、名前修飾の付け方はコンパイラの仕様によって異なります
http://ja.wikipedia.org/wiki/名前修飾
実は作成したLIBファイルにはDLLからエクスポートされた関数、クラスの名前修飾が書かれています
そのために、DLL側の関数やクラスを呼び出すことができるのです
つまり、
今回のソースファイルは下からダウンロードできます
DLL.zip