DLL(Dynamic Link Library)は実行時にリンクされるライブラリ(プログラムの部品)です 実行時にリンクされるので、うまい作り方ができればプラグイン的な使い方ができます (実行ファイルを変えることなく、DLLのみ差し替えれば機能変更できる) 今回はVisual C++を使って、DLLを作ってみます Visual C++を起動し、新規プロジェクトを作ります &ref(DLL1.JPG); とりあえずコンソールを指定しておきます (後の設定で変更する) ここでプロジェクト名は自由に決めていいのですが SIZE(30){ COLOR(#ffaa00){プロジェクト名は覚えておいてください}} &ref(DLL2.JPG); 「完了」を押さずに「次へ」を押してください &ref(DLL3.JPG); ここでDLLを選択します 空のプロジェクトにも忘れずにチェックを入れてください チェックを入れたら完了を押してください 以下のファイルを新規作成します ・DLLMain.cpp ・DLLHeader.h ・TestClass.cpp ・TestClass.h &ref(DLL4.JPG); --------------------------------------------------------------- DLLMain.cppです // DLLMain.cpp #include <windows.h> #include <tchar.h> // DLLのエントリーポイント // DLL読み込み時やDLL解放時に呼び出され // DLLの初期化や後処理を行う BOOL WINAPI DllMain( HINSTANCE hinstDLL, // DLL モジュールのハンドル DWORD fdwReason, // 関数を呼び出す理由 LPVOID lpvReserved // 予約済み ) { switch(fdwReason) { // このDLLの呼び出し元のアプリケーション(プロセス)が // 動的な呼び出し、またLoadLibrary関数で // このDLLを読み込んだときに行う処理(初期化に使う) case DLL_PROCESS_ATTACH: { // 初期化処理 MessageBox(NULL,_T("DLL ロード"),NULL,MB_OK); } break; // このDLLの呼び出し元のアプリケーション(プロセス)が // 終了したあるいはそこからFreeLibrary関数が呼ばれたときに // このDLLはアンロードされる // アンロードされる前に行う処理(後処理に使う) case DLL_PROCESS_DETACH: { // 後処理 MessageBox(NULL,_T("DLL 解放"),NULL,MB_OK); } break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; } return TRUE; } DLLのエントリーポイントです プロセスやスレッドの初期化時と終了時、 また、LoadLibrary 関数と FreeLibrary 関数の呼び出し時に、 システム(OS)がDLLMain関数を呼び出します。 上記の方法は明示的な呼び出しですが、 動的な呼び出しのほうがより一般的な使い方です 動的な呼び出しとはDLLと関連付けられたアプリケーションが起動したとき (より正確にはアプリケーションで最初に書かれたDLL側の関数の呼び出しやクラスのインスタンスが生成がされる前に) DLL_PROCESS_ATTACHは呼ばれ、呼び出し元のアプリケーションが終了すると DLLもDLL_PROCESS_DETACHが呼ばれ自動的に破棄されます 使い方としては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されているのと同じ意味を持ちます &ref(DLL5.JPG); DLLプロジェクトを作ると自動的に SIZE(30){ COLOR(#ffaa00){「プロジェクト名(大文字)_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ファイルが作成されます &ref(DLL6.JPG); ---------------------------------------------------------------- では、早速今作ったDLLを使ってみましょう 新規のプロジェクトを作ります DLLのヘッダーファイルと作成したLIBファイル、DLLファイルが必要なので 今作ったプロジェクトのフォルダにコピーしてください &ref(DLL8.JPG); main.cppを作り、DLLの関数やクラスを使ってみます &ref(DLL7.JPG); #include "TestClass.h" #pragma comment(lib,"TestDLL.lib") int main(){ TestClass testclass; Test(); // エクスポートしてないのでリンカエラーとなる //InOnlyDLLCall(); return 0; } ビルド→実行して 次のメッセージボックスが出てきたら成功です &ref(DLL11.JPG); ここで気を付けてほしいのはInOnlyDLLCall関数はエクスポートされていないので 呼び出そうとするとリンカーエラーとなります 試しにコメントをはずすしてビルドすると次のエラーが出てきます &ref(DLL9.JPG); error LNKはリンカーのエラーを示しています void _cdecl InOnlyDLLCall()の横に気味の悪い文字列が出てきます ?InOnlyDLLCall@@YAXXZ これは関数やクラスの名前修飾と呼ばれ 本当の関数の名前です @以下は関数の戻り値、引数を示しており 同じ名前の関数で引数、戻り値が違う(オーバーロードされている)場合でも 正しい関数を呼び出しできるのはこれのおかげです ちなみに、名前修飾の付け方はコンパイラによって異なります 実は作成したLIBファイルにはDLLからエクスポートされた関数、クラスの名前修飾が書かれています そのために、その情報を元にDLLの実装を実行時にリンクさせ DLL側の関数やクラスを呼び出すことができるのです コメントアウトを元に戻して 再度実行ファイルを作ります 実行ファイルを起動するにはDLLが必ず必要となります (同じフォルダもしくはシステムの環境パスが通ってる箇所の置きます) &ref(DLL10.JPG); さて、 実行(.exe)ファイルを変えずに機能を変えるにはどうしたらよいでしょうか? ここがプラグインを作るポイントなのですが 名前修飾の定義が変わらなければ (エクスポートするクラスや関数の定義) LIBファイルは変わりません つまり、実行ファイルからのDLL側の関数もしくはクラスの呼び出しに問題はないのです そこでDLL側の実装ファイルを変更してみましょう // TestClass.cpp #include "TestClass.h" #include <tchar.h> #include <windows.h> TestClass::TestClass(void) { MessageBox(NULL,_T("DLLクラス呼び出し、差し替えver"),NULL,MB_OK); } TestClass::~TestClass(void) { } void Test(){ InOnlyDLLCall(); } void InOnlyDLLCall(){ MessageBox(NULL,_T("DLL関数呼び出し、差し替えver"),NULL,MB_OK); } ビルドしてできたDLLファイルを先ほどの実行ファイルフォルダのDLLと差し替えます (DLLファイルの名前は変えていけないのでそのまま上書きします) 実行ファイルの起動結果は次のようになります &ref(DLL12.JPG); いかがだったでしょうか? うまく設計すれば、DLLを差し替えるだけでアプリケーションを バージョンアップさせることができます これはまさにプラグインです 話はそれますが、COMはこの方法をきちんと体系化したものにすぎません いちいちアプリケーション本体をビルドをしなくてよくなります プログラムが大きくなるとアプリケーションのビルドは時間もかかるようになります DLLを使うことでビルド時間の短縮にもなります そのほかにも共通の部品として扱えるようになるので ファイルサイズの削減にもなります --------------------------------------------------------------- 今回のソースファイルは下からダウンロードできます &ref(DLL.zip); #vote((^ω^)やったお[0],何これwww意味不すぎwww[0],。(`ω´#)。あぁん?最近、だらしねぇな[0]) #vote((^ω^)やったお[1],何これwww意味不すぎwww[0],。(`ω´#)。あぁん?最近、だらしねぇな[0])