プロジェクトが複雑になってくると一部で変更を加えたときに、影響範囲の把握が難しくなることがある。1つのアプリケーションに大きく機能が3つあったとすると、プロジェクト単位で3つに分けることができれば全体を俯瞰しやすくなるケースもあるだろう。DLL(ダイナミック・リンク・ライブラリ)を使えば、複数のプロジェクトから1つのアプリケーションを作成することができる。DLLは複数のアプリケーションで共有することも可能だ。プログラムを実行しながら機能を追加するということも可能になる。リンク方法には、実行ファイルの起動時にリンクする暗黙的リンクと、LoadLibrary 関数によってリンクする明示的リンクの2種類がある。
[追加]>[新しいプロジェクト]から[Viaual C++]>[Windows デスクトップウィザード]と進む。名前に新しいDLL名を入力し、[OK]をクリックする。
[アプリケーションの種類]は[ダイナミック リンク ライブラリ(.dll)]を選択する。追加のオプションは[セキュリティ開発ライフサイクル(SDL)チェック]のみにチェックする。そして、[OK]をクリックする。
[シンボルのエクスポート]をチェックすると、エクスポートマクロ、インポートマクロ、DLLの関数と変数、クラスの例が作成される。
これでDLLプロジェクトが追加される。この解説ではSimpleDLLというプロジェクト名にしたものとする。 targetver.h は使用しないので削除してもよい。
関数をエクスポートするには __declspec(dllexport) キーワードを用いる。反対に、インポートするには __declspec(dllimport) キーワードを用いる。また、GetProcAddress関数で関数アドレスを取得できるように、名前修飾を無効にする extern "C" キーワードを用いる。
#pragma once#ifdef SIMPLEDLL_EXPORTS#define SIMPLEDLL_CALL extern "C" __declspec(dllexport)#else#define SIMPLEDLL_CALL extern "C" __declspec(dllimport)#endifSIMPLEDLL_CALL bool Swap(int *lp1, int *lp2);SIMPLEDLL_EXPORTS はデフォルトで[構成プロパティ]>[C/C++]>[プリプロセッサ]に定義されるものである。SIMPLEDLLの部分はプロジェクト名を大文字にしたものである。
このヘッダファイルは外部のプロジェクトに公開されるファイルなので、プロジェクトフォルダに export フォルダを作成し、その中に移動しておく。プロジェクトでもSimpleDll.hクリアし、プロジェクト内にexportフィルタを作成し、その中に[追加]>[既存の項目]でSimple.hを改めて追加しておく。
#include "export/SimpleDll.h"SIMPLEDLL_CALL bool Swap(int *lp1, int *lp2){ int tmp; if (lp1 == 0 || lp2 == 0) return false; tmp = *lp1; *lp1 = *lp2; *lp2 = tmp; return true;}Swapは第1引数と第2引数にポインタを指定し、ポインタの指し示すアドレスの値を交換する関数である。この関数に SIMPLEDLL_CALLマクロを使用してエクスポートする。ビルドすると、SimpleDll.dllとSimpleDll.libが作成される。SimpleDll.libは使用するDLLを使用するプログラムのリンクに必要になり、SimpleDll.dllは使用するプログラムの実行時に必要になる。
先ほど作成したDLLを使ってみよう。大まかには、LoadLibrary関数でDLLモジュールをロードし、GetProcAddress関数でエクスポート関数のアドレスを取得し使用後、FreeLibrary関数でDLLモジュールを解放するという流れである。LoadLibrary関数を使うのが、DLLを指定する明示的リンクである。
#include <stdio.h>#include <stdlib.h> // system#include <windows.h>typedef bool(*LPFNSWAP)(int *, int *);int main(){ HMODULE hmod; hmod = LoadLibraryA("SimpleDll.dll"); if (hmod == NULL) { printf("SimpleDll.dllが見つかりません。\n"); system("pause"); return 0; } LPFNSWAP lpfnSwap = (LPFNSWAP)GetProcAddress(hmod, "Swap"); int a = 5, b = 3; if (lpfnSwap) { printf("a=%d, b=%d\n", a, b); lpfnSwap(&a, &b); printf("a=%d, b=%d\n", a, b); } else { printf("Swap関数が見つかりません。\n"); } FreeLibrary(hmod); system("pause"); return 0;}既に述べたが、ビルドの際のリンクにはSimpleDll.libが必要になる。DLLの探索は①EXEファイルのフォルダ、②カレントプロセスのフォルダ、③Windowsシステムフォルダ、④Windowsフォルダ、⑤環境変数PATHの順で探索される。LoadLibrary関数の引数は絶対パスで指定することも可能だ。実行結果は以下のようになる。
明示的リンクと違い、暗黙的リンクはLoadLibrary関数を使用しない。ビルド時には、DLLのヘッダファイルとライブラリファイルが必要になる。このコードでは、DLLヘッダファイルと、ライブラリファイルへのパスは直接相対パスで指定している。[構成プロパティ]>[VC++ ディレクトリ]>[インクルード ディレクトリ]にエクスポートフォルダを追加すれば、#include "SimpleDll.h" というようにコード内で相対位置を省略することが可能になる。ライブラリについても同様だ。省略方法は一例で、パスの指定・省略方法はいろいろある。
#include <stdio.h>#include <stdlib.h> // system#include "../SimpleDll/export/SimpleDll.h"#pragma comment (lib, "../debug/SimpleDll.lib")int main(){ int a = 5, b = 3; printf("a=%d, b=%d\n", a, b); Swap(&a, &b); printf("a=%d, b=%d\n", a, b); system("pause"); return 0;}実行結果は明示的リンクの場合と同じになる。実行時にDLLの探索に失敗した場合は以下のようなエラーメッセージが表示される。
エラーが出るということは、実行時にDLLが必要であることが分かる。
プロジェクトをリビルドすると「エラー LNK1104 ファイル '../debug/SimpleDll.lib' を開くことができません。」というエラーメッセージが出る。そこで、ビルドすると今度は全てのビルドが正常終了し、DLLを使用しているプロジェクトのプログラムは実行可能になる。これは、先にDLLのプロジェクトのビルドが終わった後でないと、DLLを使用するプロジェクトがリンクできずにビルドが失敗するためだ。
プロジェクトの依存関係を設定してみよう。[プロジェクトメニュー]をクリックするか、ソリューションまたはプロジェクトを右クリックすると表示される[プロジェクトの依存関係]をクリックする。DLLを使うプロジェクトを選んで、依存先に使用されるDLLを作成するプロジェクトにチェックしよう。