山本ワールド
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を読み込むタイミングはプログラマー任せです。
LIBファイル・ヘッダーファイルのないDLLに使用できます。
このページでは静的リンクについて記述します。
静的リンク
リンカーでリンクするときにLIBファイルを指定して関数とDLLファイル名とを関連付けした情報を実行ファイルに埋め込みます。DLLはプログラム(プロセス)が開始した時点で自動的に読み込まれます。
DLLを呼び出す側は関数のプロトタイプ宣言とLIBファイルの指定(コンパイラオプションでの指定または#pragma)のみで通常の関数の様に使えます。
動的リンク
プログラマが明示的にDLLファイルのロード・解放および関数へのエントリーポイントの取得および関数の実行を行います。動的リンクの場合、プログラムの実行時においてロードするDLLファイルを変えられます。
またDLLを読み込むタイミングはプログラマー任せです。
LIBファイル・ヘッダーファイルのないDLLに使用できます。
テスト環境
コンパイラ
Visual C++ 2008 Standard 32/64bitVisual C++ 2013 Express 32/64bit
ソリューション・プロジェクトの作成
dllsub.cpp Win32プロジェクト DLL static.cpp Win32プロジェクト Windowsアプリケーション dllsubのプロジェクトをstaticソリューションに追加
実行環境
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ファイル用のstaticlink.cppの2種類からなっています。
dllsubのプロジェクトの作成は、新しいプロジェクトを選択しWin32プロジェクトの中のDLLを選択して作成します。
staticlinkのプロジェクトの作成は、新しいプロジェクトを選択しWindowsアプリケーションを選択して作成します。
staticlinkのソリューションにdllsubプロジェクトを追加すると、dllsubとstaticlinkを一度にコンパイルすることができます。
2つのプロジェクトおよびソリューションのフォルダー構成の抜粋は以下の通りです。
依存関係が正確に設定されていいない場合は、libファイルが見つからない旨のエラーが表示されます。
ビルド時にdllsub.libが必要ですので、コンパイルのオプションまたは#pragmaで指定する必要があります。
ここでは、#pragmaで指定します。32/64bit、Release/Debugプロジェクトによりdllsub.libの位置が変わりますので、条件付きコンパイルを使用して#pragmaを使用します。 本ソースは、上で示すフォルダー構成を前提としています。(staticlinkプロジェクトとdllsubプロジェクトは同じフォルダーの中に作成しています。)
呼び出すためには、修飾後の正確な関数名が必要となります。
一般的には関数名を調べるのは煩わしいので、関数名が修飾されないようにDLL側の関数名に extern "C" を付加します。
さらに引数にTCHARを使用しているため、マルチバイト文字列とUNICODE文字列を区別するためDLL側の関数名はMyDLLFuncAとMyDLLFuncWの2種類作成しています。
呼び出す際は自身のプロジェクトに従い、マクロでMyDLLFuncAとMyDLLFuncWのいずれかを選びます。
DLLを呼び出す際は、そのDLL内の関数の呼び出し規則に合わせて関数の呼び出し規則を定義する必要があります。
呼び出し規則とは、関数を呼び出す際に引数の渡し方(レジスタ名、スタックの順番)、スタックの解放方法(呼び出し側、呼び出された側)等の規則です。
関数ポインタはtypedefで型を作成しておきます。
例えばint test(char* ,int)という関数を呼び出すための関数ポインタ型は以下のように定義で済ます。
呼び出す場合は、以下のように記述します。
関数の呼び出し形式に不整合があると、関数から戻るときに、スタック(ESP)が壊れたというメッセージが表示されます。(debug版)
動的DLLと静的DLLは同一のソースファイルから作成できます。
DllMain関数以外は、他のプログラムから使用できるように関数をエクスポートする必要があります。ここではマクロEXPORTを定義して各関数で使用しています。
DLLを使用する側では、プロジェクトの定義によりマクロでマルチバイト文字列とUNICODE文字列を切り替えています。この手法はWindowsのAPIでよく使われている方法です。
例えばMessageBox APIはプロジェクトの定義によりMessageBoxWまたはMessegeBoxAのいずれかにマクロによって置き換わる。
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.exestaticlinkのビルドには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);
呼び出し形式 | 修飾子 | スタックの戻し方 | 引数の順番 | 引数のメモリ上の順番 | 備考 |
---|---|---|---|---|---|
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を使用して表示します。プログラムソース
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ファイルに含まれるファイル
ZIPファイルに含まれるファイル
dllsub.cpp dllsub.dll dllsub.lib staticlink.cpp staticlink.exe
Copyright (C) 2012 山本ワールド All Rights Reserved.