概要

DLLロードの仕方には、静的リンク(暗黙的)と動的リンクがあります。
このページでは静的リンクについて記述します。

静的リンク

リンカーでリンクするときにLIBファイルを指定して関数とDLLファイル名とを関連付けした情報を実行ファイルに埋め込みます。
DLLはプログラム(プロセス)が開始した時点で自動的に読み込まれます。
DLLを呼び出す側は関数のプロトタイプ宣言とLIBファイルの指定(コンパイラオプションでの指定または#pragma)のみで通常の関数の様に使えます。

動的リンク

プログラマが明示的にDLLファイルのロード・解放および関数へのエントリーポイントの取得および関数の実行を行います。
動的リンクの場合、プログラムの実行時においてロードするDLLファイルを変えられます。
またDLLを読み込むタイミングはプログラマー任せです。
LIBファイル・ヘッダーファイルのないDLLに使用できます。

テスト環境

コンパイラ

Visual C++ 2008 Standard 32/64bit
Visual C++ 2013 Express 32/64bit

ソリューション・プロジェクトの作成

dllsub.cpp Win32プロジェクト DLL
static.cpp Win32プロジェクト Windowsアプリケーション
      dllsubのプロジェクトをstaticソリューションに追加

実行環境

Windows 8.1 Enterprise 64bit
Windows 7 EnterPrise Service Pack 1 64bit
Windows Vista Ultimate Service Pack 2 32bit
Windows XP Professional Service Pack 3 32bit

プログラムソースの概要

プログラムソースはDLL用dllsub.cpp、DLLを呼び出すEXEファイル用のstaticlink.cppの2種類からなっています。
dllsubのプロジェクトの作成は、新しいプロジェクトを選択しWin32プロジェクトの中のDLLを選択して作成します。
staticlinkのプロジェクトの作成は、新しいプロジェクトを選択しWindowsアプリケーションを選択して作成します。
staticlinkのソリューションにdllsubプロジェクトを追加すると、dllsubとstaticlinkを一度にコンパイルすることができます。
2つのプロジェクトおよびソリューションのフォルダー構成の抜粋は以下の通りです。
├─dllsub
│  │  dllsub.sln
│  ├─Debug
│  └─dllsub
│      │  dllsub.cpp
│      ├─Debug
│      │      dllsub.obj
│      ├─Release
│      │      dllsub.obj
│      └─x64
│          ├─Debug
│          │      dllsub.obj
│          └─Release
│                  dllsub.obj
└─staticlink
    │  staticlink.sln
    ├─Debug
    │      dllsub.dll
    │      dllsub.exp
    │      staticlink.exe
    ├─Release
    │      dllsub.dll
    │      dllsub.lib
    │      staticlink.exe
    ├─staticlink
    │  │  staticlink.cpp
    │  ├─Debug
    │  │      staticlink.obj
    │  ├─Release
    │  │      staticlink.obj
    │  └─x64
    │      ├─Debug
    │      │      staticlink.obj
    │      └─Release
    │              staticlink.obj
    └─x64
        ├─Debug
        │      dllsub.dll
        │      dllsub.lib
        │      staticlink.exe
        └─Release
                dllsub.dll
                dllsub.lib
                staticlink.exe
staticlinkのビルドにはdllsub.libが必要なため、プロジェクトの依存関係は、staticlinkがdllsubに依存するように設定します。
依存関係が正確に設定されていいない場合は、libファイルが見つからない旨のエラーが表示されます。

staticlink.cpp

DLLを呼び出す側のプログラムです。
ビルド時にdllsub.libが必要ですので、コンパイルのオプションまたは#pragmaで指定する必要があります。
ここでは、#pragmaで指定します。32/64bit、Release/Debugプロジェクトによりdllsub.libの位置が変わりますので、条件付きコンパイルを使用して#pragmaを使用します。 本ソースは、上で示すフォルダー構成を前提としています。(staticlinkプロジェクトとdllsubプロジェクトは同じフォルダーの中に作成しています。)

_tWinMain関数

DLL内の関数を呼び出すためにDLL内の関数をインポートする必要があります。以下の記述をプロトタイプ宣言の前に付加します。
#define INPORT extern "C" __declspec(dllimport)
C++では同じ関数名で引数の違うの関数を作成でき、それらの区別のため関数名の後ろが引数の型等で修飾されます。
呼び出すためには、修飾後の正確な関数名が必要となります。
一般的には関数名を調べるのは煩わしいので、関数名が修飾されないようにDLL側の関数名に extern "C" を付加します。
さらに引数にTCHARを使用しているため、マルチバイト文字列とUNICODE文字列を区別するためDLL側の関数名はMyDLLFuncAとMyDLLFuncWの2種類作成しています。
呼び出す際は自身のプロジェクトに従い、マクロでMyDLLFuncAとMyDLLFuncWのいずれかを選びます。
DLLを呼び出す際は、そのDLL内の関数の呼び出し規則に合わせて関数の呼び出し規則を定義する必要があります。
呼び出し規則とは、関数を呼び出す際に引数の渡し方(レジスタ名、スタックの順番)、スタックの解放方法(呼び出し側、呼び出された側)等の規則です。
関数ポインタはtypedefで型を作成しておきます。
例えばint test(char* ,int)という関数を呼び出すための関数ポインタ型は以下のように定義で済ます。
typedef int(_stdcall *FuncP)(char* , int x);
上記の場合、戻り値はint型、呼び出し形式は_stdcall(Win32)、引数がchar*とint型である関数ポインタFuncPを定義しています。
呼び出す場合は、以下のように記述します。
(*FuncP)("test",10);
Win32(x86)呼び出し形式
呼び出し形式 修飾子 スタックの戻し方 引数の順番 引数のメモリ上の順番 備考
C _cdecl 呼び出し側で戻す 右の引数からpush メモリの下位から上位に向かって左~右引数 可変個の引数を扱うのに都合がよい
Win32 _stdcall 呼び出された側 右の引数からpush メモリの下位から上位に向かって左~右引数
fastcall __fastcall 呼び出された側 ECX,EDXレジスタ使用後はcdecと同じ cdecと同じ レジスタ渡し
thiscall メンバー関数に使用
ユーザーが指定することはできない
呼び出された側 thisポインタはECX、他はcdeclと同じ cdeclと同じ
vectorcall __vectorcall 呼び出された側 ECX,EDX,SSE,AVXレジスタ使用後はcdecと同じ fastcallと同じ レジスタ渡し
関数の呼び出し形式に不整合があると、関数から戻るときに、スタック(ESP)が壊れたというメッセージが表示されます。(debug版)
リンケージ形式
形式 修飾子
extern "C" 関数名の修飾を行わない
__cplusplus 関数名の修飾を行う

dllsub.cpp

DLLファイルのソースです。
動的DLLと静的DLLは同一のソースファイルから作成できます。
DllMain関数以外は、他のプログラムから使用できるように関数をエクスポートする必要があります。ここではマクロEXPORTを定義して各関数で使用しています。
#define EXPORT  extern "C"  __declspec(dllexport) 
本DLLはMessageBox APIを呼び出す単純な関数を定義しています。マルチバイト文字列とUNICODE文字列をサポートするために、2種類の関数を定義しています。
DLLを使用する側では、プロジェクトの定義によりマクロでマルチバイト文字列とUNICODE文字列を切り替えています。この手法はWindowsのAPIでよく使われている方法です。
例えばMessageBox APIはプロジェクトの定義によりMessageBoxWまたはMessegeBoxAのいずれかにマクロによって置き換わる。

DllMain関数

プロセスやスレッドの初期化時と終了時、また、LoadLibrary 関数と FreeLibrary 関数の呼び出し時に呼び出されます。

MyDLLFuncW関数

引数で指定されるワイド文字列をMessageBox APIを使用して表示します。

MyDLLFuncA関数

引数で指定されるマルチバイト文字列をMessageBox APIを使用して表示します。

プログラムソース

staticlink.cpp

DLLを呼び出す側のソース
//    DLLを動的にリンク
//      Visual C++ 2013 32/64bit

#include <windows.h>
#include <tchar.h>

#ifdef _DEBUG
        #ifdef _WIN64
                #pragma comment(lib,"../x64/Debug/dllsub.lib")        //      Debug 64bit
        #else
                #pragma comment(lib,"../Debug/dllsub.lib")    //      Debug 64bit
        #endif
#else
        #ifdef _WIN64
                #pragma comment(lib,"../x64/Release/dllsub.lib")      //      Release 64bit
        #else
                #pragma comment(lib,"../Release/dllsub.lib")  //      Release 32bit
        #endif
#endif

#define INPORT extern "C" __declspec(dllimport)

INPORT BOOL _stdcall MyDLLFuncW(WCHAR* msg);
INPORT BOOL _stdcall MyDLLFuncA(char* msg);

#ifdef UNICODE
        #define MyDLLFunc(msg)  MyDLLFuncW(msg)
#else
        #define MyDLLFunc(msg)  MyDLLFuncA(msg)
#endif

int WINAPI _tWinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,TCHAR* lpsCmdLine, int nCmdShow){
        MyDLLFunc(TEXT("DLL内関数の呼び出し成功"));
        return (int)0;
}

dllsub.cpp

DLLのソース

//      DLL
//      Visual C++ 2013 32/64bit

#include <windows.h>

//      関数の呼び出し形式にEXPORT(extern "C")を付け名前が勝手に変換されないようにする
#define EXPORT  extern "C"  __declspec(dllexport)


int WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, PVOID pvReserved){
    return TRUE;
}

//      UNICODE版

EXPORT BOOL _stdcall  MyDLLFuncW(WCHAR* msg){
        MessageBoxW(0, msg,     L"DLLSUB.DLL",MB_OK);
        return TRUE;
}

//      マルチバイト版

EXPORT BOOL _stdcall  MyDLLFuncA(char* msg){
        MessageBoxA(0, msg, "DLLSUB.DLL", MB_OK);
        return TRUE;
}

ソースファイルのダウンロード

ダウンロード staticlink.zip(73.0kByte)
ZIPファイルに含まれるファイル
dllsub.cpp
dllsub.dll
dllsub.lib
staticlink.cpp
staticlink.exe