DLL(Dynamic Link Library)は実行時にリンクされるライブラリ(プログラムの部品)です
実行時にリンクされるので、うまい作り方ができればプラグイン的な使い方ができます
(実行ファイルを変えることなく、DLLのみ差し替えれば機能変更できる)
今回はVisual C++を使って、DLLを作ってみます
Visual C++を起動し、新規プロジェクトを作ります

DLL1.JPG

とりあえずコンソールを指定しておきます
(後の設定で変更する)
ここでプロジェクト名は自由に決めていいのですが
プロジェクト名は覚えておいてください

DLL2.JPG

「完了」を押さずに「次へ」を押してください

DLL3.JPG

ここでDLLを選択します
空のプロジェクトにも忘れずにチェックを入れてください
チェックを入れたら完了を押してください
以下のファイルを新規作成します
・DLLMain.cpp
・DLLHeader.h
・TestClass.cpp
・TestClass.h

DLL4.JPG


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されているのと同じ意味を持ちます
DLL5.JPG
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ファイルが作成されます

DLL6.JPG


では、早速今作ったDLLを使ってみましょう
新規のプロジェクトを作ります
DLLのヘッダーファイルと作成したLIBファイル、DLLファイルが必要なので
今作ったプロジェクトのフォルダにコピーしてください
DLL8.JPG

main.cppを作り、DLLの関数やクラスを使ってみます
DLL7.JPG

	#include "TestClass.h"
	#pragma comment(lib,"TestDLL.lib")

	int main(){

		TestClass testclass;
		Test();

		// エクスポートしてないのでリンカエラーとなる
		//InOnlyDLLCall();

		return 0;
	}

メッセージボックスが出てきたら成功です
ここで気を付けてほしいのはInOnlyDLLCall関数はエクスポートされていないので
呼び出そうとするとリンカーエラーとなります
試しにコメントをはずすしてビルドすると次のエラーが出てきます
DLL9.JPG
error LNKはリンカーのエラーを示しています

void _cdecl InOnlyDLLCall()の横に気味の悪い文字列が出てきます

?InOnlyDLLCall@@YAXXZ

これは関数やクラスの名前修飾と呼ばれ
本当の関数の名前です
@以下は関数の戻り値、引数を示しており
同じ名前の関数で引数、戻り値が違う(オーバーロードされている)場合でも
正しい関数を呼び出しできるのはこれのおかげです
ちなみに、名前修飾の付け方はコンパイラによって異なります

実は作成したLIBファイルにはDLLからエクスポートされた関数、クラスの名前修飾が書かれています
そのために、その情報を元にDLLの実装を実行時にリンクさせ
DLL側の関数やクラスを呼び出すことができるのです

コメントアウトを元に戻して
再度実行ファイルを作ります
実行ファイルを起動するにはDLLが必ず必要となります
(同じフォルダもしくはシステムの環境パスが通ってる箇所の置きます)
DLL10.JPG

実行ファイルの起動結果は次のようになります
DLL11.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ファイルの名前は変えていけないのでそのまま上書きします)
実行ファイルの起動結果は次のようになります
DLL12.JPG

いかがだったでしょうか?
うまく設計すれば、DLLを差し替えるだけでアプリケーションを
バージョンアップさせることができます
これはまさにプラグインです
話はそれますが、COMはこの方法をきちんと体系化したものにすぎません

いちいちアプリケーション本体をビルドをしなくてよくなります
プログラムが大きくなるとアプリケーションのビルドは時間もかかるようになります
DLLを使うことでビルド時間の短縮にもなります
そのほかにも共通の部品として扱えるようになるので
ファイルサイズの削減にもなります


今回のソースファイルは下からダウンロードできます
fileDLL.zip

選択肢 投票
(^ω^)やったお 0  
何これwww意味不すぎwww 0  
。(`ω´#)。あぁん?最近、だらしねぇな 0  

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS