概要

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

静的リンク

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

動的リンク

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

typedef int(WINAPI *MessageBoxWFunc)(HWND , LPCWSTR , LPCWSTR , UINT );

int WINAPI _wWinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, WCHAR* lpsCmdLine, int nCmdShow){
        HMODULE hMod;
        MessageBoxWFunc func=0;

        hMod = LoadLibrary(L"user32.dll");

        if (hMod == NULL){
                return 1;
        }
        func = (MessageBoxWFunc)GetProcAddress(hMod, "MessageBoxW");
        if (func)
                (*func)(0, L"MessageBoxの呼び出しに成功しました", L"メッセージ", MB_OK);
        FreeLibrary(hMod);
        return (int)0;
}

テスト環境

コンパイラ

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

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

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

実行環境

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ファイル用のdllmain.cppの2種類からなっています。
dllsubのプロジェクトの作成は、新しいプロジェクトを選択しWin32プロジェクトの中のDLLを選択して作成します。
dllmainのプロジェクトの作成は、新しいプロジェクトを選択しWindowsアプリケーションを選択して作成します。
dllmainのソリューションにdllsubプロジェクトを追加すると、dllsubとdllmainを一度にビルドすることができます。

dllmain.cpp

DLLを呼び出す側のプログラムです。

_tWinMain関数

DLLは、プログラム内で明示的にファイル名を指定してLoadLibrary APIでロードします。
DLL内の関数の呼び出しは、GetProcAddress APIで関数へのポインタを取得し、変数に保存します。
ポインタを使って関数を呼び出します。第2引数に関数名を渡します。GetProcAddress APIの関数名はANSI文字列である必要があります。(UNICODE版は存在しない)  
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 関数名の修飾を行う
DLLが不要になったら、FreeLibrary APIでDLLを解放します。

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を使用して表示します。

プログラムソース

dllmain.cpp

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

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

typedef int(_stdcall *MyDLLFuncP)(TCHAR*);

int WINAPI _tWinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,TCHAR* lpsCmdLine, int nCmdShow){
        HMODULE hMod;
        MyDLLFuncP func;

        hMod = LoadLibrary(TEXT("dllsub.dll"));

        if(hMod)
#ifdef UNICODE
                func = (MyDLLFuncP)GetProcAddress(hMod, "MyDLLFuncW");        //UNICODE版を呼び出す GetProcAddressはUnicode版がない
#else
                func = (MyDLLFuncP)GetProcAddress(hMod, "MyDLLFuncA");        //マルチバイト版を呼び出す  GetProcAddressはUnicode版がない
#endif
        else{
                MessageBox(0,TEXT("DLLをロードできません"),TEXT("エラー"),MB_OK);
                return 1;
        }
        if(!func)
                MessageBox(0,TEXT("関数のエントリーが見つかりません"),TEXT("エラー"),MB_OK);
        else
                (*func)(TEXT("DLL内関数の呼び出し成功"));

        FreeLibrary(hMod);
        
        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;
}

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

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