山本ワールド
Windowsプログラミング
アルゴリズム Vitual C++ 2008/2013によるWin32/Win64 APIレベルのプログラム 基礎 Vitual C++ 2008/2013によるAPIレベルのプログラム(32/64bit) Wix3でインストーラーを作る Visual C++ 2008 Standard Editonによるフォームアプリケーションのプログラム(32/64bit) Vitual C++ 2008 Standard EditonによるAPIレベルのプログラム(32/64bit) Windows 7対応 Visual C++ 2008 ExpressによるAPIレベルのプログラム Visual C++ 2005 ExpressによるAPIレベルのプログラム Visual C++ Versiosn 5 BORLAND C++ Windowsプログラム全般 Excel VBA その他DLLを動的リンクで呼び出す
概要
DLLロードの仕方には、静的リンク(暗黙的)と動的リンクがあります。
このページでは動的リンクについて記述します。
DLLはプログラム(プロセス)が開始した時点で自動的に読み込まれます。
DLLを呼び出す側は関数のプロトタイプ宣言とLIBファイルの指定(コンパイラオプションでの指定または#pragma)のみで通常の関数の様に使えます。
関連 DLLを静的リンクで呼び出す
動的リンクの場合、プログラムの実行時においてロードするDLLファイルを変えられます。
またDLLを読み込むタイミングはプログラマー任せです。
LIBファイル・ヘッダーファイルのないDLLに使用できます。
以下のソースはMessageBoxW APIを動的に呼び出す例です。
このページでは動的リンクについて記述します。
静的リンク
リンカーでリンクするときに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/64bitVisual C++ 2013 Express 32/64bit
ソリューション・プロジェクトの作成
dllsub.cpp Win32プロジェクト DLL dllmain.cpp Win32プロジェクト Windowsアプリケーション dllsubのプロジェクトをdllmainソリューションに追加
実行環境
Windows 8.1 Enterprise 64bitWindows 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を一度にビルドすることができます。
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)という関数を呼び出すための関数ポインタ型は以下のように定義で済ます。
呼び出す場合は、以下のように記述します。
関数の呼び出し形式に不整合があると、関数から戻るときに、スタック(ESP)が壊れたというメッセージが表示されます。(debug版)
DLLが不要になったら、FreeLibrary APIでDLLを解放します。
動的DLLと静的DLLは同一のソースファイルから作成できます。
DllMain関数以外は、他のプログラムから使用できるように関数をエクスポートする必要があります。ここではマクロEXPORTを定義して各関数で使用しています。
DLLを使用する側では、プロジェクトの定義によりマクロでマルチバイト文字列とUNICODE文字列を切り替えています。この手法はWindowsのAPIでよく使われている方法です。
例えばMessageBox APIはプロジェクトの定義によりMessageBoxWまたはMessegeBoxAのいずれかにマクロによって置き換わる。
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);
呼び出し形式 | 修飾子 | スタックの戻し方 | 引数の順番 | 引数のメモリ上の順番 | 備考 |
---|---|---|---|---|---|
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と同じ | レジスタ渡し |
形式 | 修飾子 |
---|---|
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を使用して表示します。プログラムソース
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;
}
ソースファイルのダウンロード
Copyright (C) 2012 山本ワールド All Rights Reserved.