DLLの概要と実装方法。

VisualStudio2005のC++で、DLLを作ることになったので、そのまとめ。

注意!

VisualStudio2005を使ったDLLの実装方法です。他の環境ではたぶん当てになりません。
また、/clr:pureでDLLを呼び出す場合の実装方法もよくわかっていない為、その辺もご理解の上読んで下さい。

DLLとは?

ダイナミック・リンク・ライブラリの略。詳細はウィキペディア見れ。

libとの違いは?

libは静的。

libはコンパイル時にリンクされる。
その為、更新時には、アプリの再コンパイルが必要。

DLLは動的。

DLLは実行時にリンクされる。
その為、更新時には、DLLの上書きだけで、アプリの再コンパイルが不要。
複数のアプリで読み出されても、アプリと独立した1つのメモリ空間しか使わないので、libよりメモリ上有利。


今回は各ライブラリが頻繁にアップデートされる予定の為、DLLを利用。

暗黙リンク、明示リンク

DLLのリンクには、暗黙リンク、明示リンクが存在する。

明示リンク

コード上で、DLLのハンドルを取得して、そこから関数ポインタを取得する方法。
読み出すDLLや関数を動的に変更できる。

暗黙リンク

リンカ上で、DLLのリンクを設定し、コード上DLLの存在を意識せずにDLLを使用する方法。


詳しくは、こちらを参照。
今回は、必要なDLLが動的に変わる訳でもないので、暗黙リンクを選択。

CLR

CLRとは.NETの共通言語基盤(CLI)を実装したもの。要はバーチャルマシン。
CLRを利用することで、より簡便に安全なコードを書けるが、バーチャルマシン経由の為、どうしても重くなりがち。
今回はCLR不使用の場合と、CLRを使用する場合の2パターンのDLLの実装方法と、利用側での暗黙リンクの方法を解説。

非CLRのDLL

DLL側


VisualStudio2005で、
「新しいプロジェクトの追加」→「win32 プロジェクト」


次に出てくるウィサードから、
「アプリケーションの設定」→「アプリケーションの種類」→「DLL」
で生成される。


以下実装コード

dllTest.h
// ウィサードでDLLを作った場合、
// 自動的に、コンパイルオプションに「[プロジェクト名]_EXPORTS」が追加される。
// マクロを設定することで、DLL側ではexport、呼び出し側では、importされるようになる。
#ifdef DLLTEST_EXPORTS
#define CLASS_DECLSPEC __declspec(dllexport) // DLLにexport
#else
#define CLASS_DECLSPEC __declspec(dllimport) // DLLã‚’import
#endif // DLLTEST_EXPORTS

class CLASS_DECLSPEC DllTestClass
{
public:
 int getInt();
};
dllTest.cpp
#include "stdafx.h"
#include "dllTest.h"

#ifdef _MANAGED
#pragma managed(push,off)
#endif

BOOL APIENTRY DLLMain(
 HMODULE hModule,
 DWORD ul_reason_for_call,
 LPVOID lpReserved
)
{
 return TRUE;
}
)

int DllTestClass::getInt()
{
 return 0;
}

#idfdef _MANAGED
#pragma managed(pop)
#emdif
DLL利用側


DLLを暗黙に呼び出すため、リンカの設定が必要。
今回はDLLとアプリは同一ソリューションの為、依存関係を設定して終わり。


また、コンパイルの為にdllTest.hを取得側のプロジェクトにコピーする必要あり。
毎回コピーしても良いが、手間なので、


「プロジェクトのプロパティ」→「構成プロパティ」→「ビルドイベント」→「ビルド後のイベント」→「コマンドライン」

に、必要なヘッダファイルをコピーするコマンドを打ち込むと楽。


後は、以下のようにDLLを意識せずに使用できる。

DllTestClass* _dllTestClass = new DllTestClass();
int a = _dllTestClass->getInt();
非CLRのDLLを/clr:pureのアプリで読み込む。

非CLRのDLLに対して、読み出し側がwindowsフォームアプリ等の/clr:pureを使うアプリの場合、コンパイル時に以下のようなワーニング、エラーが発生する。

warning C4272:(dllTestClass::getInt' : __delspec(dllimport)に設定されています。関数をインポートするときはネイティブ呼び出し規約を指定しなければなりません。
error LNK2028:未解決のトークン(0A00000C) "public: int __clrcall dllTestClass::getInt(void)" (?getInt@dllTestClass@@$$FQAMHXZ) が関数"int __clrcall main(cli::array ^)" (?main@@$$HYMHP$01AP#AAVString@System@@@Z)で参照されました
error LNK2019:未解決の外部シンボル "public: int __clrcall classTestSpace::classTest::getInt(void)" (?getInt@classTest@classTestSpace@@$$FQAMHXZ) が関数"int __clrclaa main(cli::array ^)" (?main@@$$HYMHP$01AP#AAVString@System@@@Z)で参照されました
fatal error LNK1120: 外部参照 4 が未解決です。

以下の設定を/clr:pure から /clrにするとエラーはなくなる。


「プロジェクトのプロパティ」→「全般」→「共通言語ランタイムサポート」


/clr:pureでは、アンマネージドコード(すなわち、非/clr:pureなコード)は、使えない。
これは、/clr:pureが、純粋にCLRのみコードである。という意味から。
/clrは、CLRに対応しているというだけなので、アンマネージドコードも利用できる。


はてなで質問してみたところ、アンマネージドコードでも条件を満たせば、/clr:pureで使用できるそうです。
詳細は、後日追記。

具体的な実装方法(CLRのDLL)

windowsフォームなど、CLRを使ったコードをDLLにする場合の方法。

DLL側


VisualStudio2005で、
「ソリューションのプロパティ」→「追加」→「新しいプロジェクト」→「クラスライブラリ」


で、生成される。
実装については特に注意点無し。
非CLRでの__declspecのようなものを書く必要は無い。

利用側


共通ヘッダー(stdafx.h)等で、使用するDLLをusingする。

#ifdef _DEBUG
#using "../Debug/testDll.dll"
#else
#using "../Release/testDll.dll"


これだけでOK。
非DLLと異なり、ヘッダファイルが無くても、実装可能。

注意点

上記方法では、非CLRのコードで、CLRのDLLは使用できない。
これは、上記方法が、CLR特有の為。
非CLRにて、CLRのコードを暗黙に呼び出す方法について、後日追記予定。

疑問点

  • 非CLRで、CLRのDLLを暗黙的に呼び出す方法。
  • /clr:pureで非CLRのDLLを暗黙的に呼び出す方法。

2007/5/14 追記

  • はてなでの回答を内容に反映。
    id:kaorunさん。どうもありがとうございました!
  • 「バッドノウハウ」の部分を「非CLRのDLLã‚’/clr:pureのアプリで読み込む」に変えて内容を変更。
  • 「CLRでDLL実装」を追加。
  • あと構成等を細々と変更。

そんな感じー。