「ライブラリの基礎 - DLLの作成(C/C++/MFC)」の版間の差分
(ページの作成:「== 概要 == MFCを使ったDLLには、以下の2つの種類がある。<br> # 1つ目は"拡張DLL"で、DLLを使用するEXEやDLLもMFCで作成する時にのみ…」) |
|||
(同じ利用者による、間の22版が非表示) | |||
1行目: | 1行目: | ||
== 概要 == | == 概要 == | ||
MFC DLLには、以下の2つの種類がある。<br> | |||
* 拡張DLL | |||
*: DLLを使用するEXEやDLLもMFCで作成する時にのみ使用する。 | |||
* MFCの共有DLL(Regular DLL) | |||
*: 内部的にMFCそのものを持っているため、DLLを使用するEXEやDLLをMFCで作成しない時でも使用できる。 | |||
<br><br> | <br><br> | ||
== DLLの作成方法 == | == DLLの作成方法 == | ||
==== 呼び出し規約 ==== | |||
[プロジェクト]メニューバー - [プロパティ]を選択して、[プロパティ]ダイアログを表示する。<br> | |||
[C++] | [プロパティ]ダイアログの[構成のプロパティ] - [C/C++] - [詳細設定]を選択する。<br> | ||
[プロパティ]ダイアログの右ペインから、[呼び出し規約]プルダウンを<u>__cdecl (/Gd)</u>に変更する。 (デフォルト)<br> | |||
<br> | <br> | ||
==== モジュール定義ファイル (defファイル) を使用する場合 ==== | |||
< | モジュール定義 (.def拡張子) ファイルは、DLLがエクスポートする関数を記述したファイルであり、リンクするプログラムに関するエクスポート、属性、その他の情報をリンカに提供する。<br> | ||
MainDLL.h // | モジュール定義ファイルは、DLLをビルドする場合に最も役立つ。<br> | ||
<br> | |||
モジュール定義ステートメントの代わりに使用できるMSVCリンカオプションがあるため、モジュール定義ファイルが存在してくてもDLLを開発することができる。<br> | |||
モジュール定義ファイルを使用しない場合は、エクスポートされる関数を指定する方法として、<code>__declspec(dllexport)</code>を使用する。<br> | |||
<br> | |||
<u>※注意</u><br> | |||
<u>エクスポートを行わない実行ファイルを開発する場合、モジュール定義ファイルを使用すると、出力ファイルのサイズが大きくなり読み込みが遅くなることに注意する。</u><br> | |||
<br> | |||
[ソリューションエクスプローラ]を右クリックして、コンテキストメニューから[追加] - [新しい項目]を選択する。<br> | |||
[新しい項目の追加]ダイアログが開くので、モジュール定義ファイル名 <u><ファイル名>.def</u> を入力する。<br> | |||
[OK]ボタンを押下すると、モジュール定義ファイルが自動的に作成される。<br> | |||
<br> | |||
<span style="color:#C00000"><u>※ C++でDLLを作成する場合は、defファイルを作成することを推奨する。</u></span><br> | |||
<br> | |||
[プロジェクト]メニューバー - [プロパティ]を選択して、[プロパティ]ダイアログを表示する。<br> | |||
[プロパティ]ダイアログの[リンカー] - [入力] - [モジュール定義ファイル]項目に使用するモジュール定義ファイル名を入力する。<br> | |||
[OK]ボタン、または、[適用]ボタンを押下して、変更内容を保存する。<br> | |||
<br> | |||
[[ファイル:MFC DLL 01.png|フレームなし|中央]] | |||
<br> | |||
<u>※注意</u><br> | |||
<u>C#で開発したモジュール(EXEまたはDLL)からC++ DLLを呼び出す場合、C++ DLLではモジュール定義ファイルを使用すること。</u><br> | |||
<br> | |||
モジュール定義ファイルを作成して、以下に示すようにエクスポートする関数を記述する。<br> | |||
また、<u>@1</u>等の序数値の記載は任意である。<br> | |||
<syntaxhighlight lang="ini"> | |||
; MainDLL.def | |||
LIBRARY MainDLL | |||
EXPORTS | |||
SampleFunc @1 | |||
TestFunc @2 | |||
</syntaxhighlight> | |||
<br> | |||
次に、"MainDLL.h"ファイルを作成する。<br> | |||
モジュール定義ファイルを使用する場合、エクスポートする関数名に<code>extern "C"</code>キーワードおよび<code>DECLSPEC __declspec(dllexport)</code>キーワードを付加する必要はない。<br> | |||
<syntaxhighlight lang="c++"> | |||
// MainDLL.h | |||
#pragma once | |||
int SampleFunc(int *lp1, int *lp2); | |||
double TestFunc(double *lp1, double *lp2); | |||
</syntaxhighlight> | |||
<br> | |||
最後に、"MainDLL.cpp"ファイルには以下のように記載する。<br> | |||
<syntaxhighlight lang="c++"> | |||
// MainDLL.cpp | |||
#include "Stdafx.h" | |||
#include "MainDLL.h" | |||
int SampleFunc(int *lp1, int *lp2) | |||
{ | |||
// ...処理 1 | |||
} | |||
int TestFunc(double *lp1, double *lp2) | |||
{ | |||
// ...処理 2 | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
モジュール定義ファイルの詳細を知りたい場合は、[https://learn.microsoft.com/ja-jp/cpp/build/reference/module-definition-dot-def-files?view=msvc-170 Microsoftの公式ドキュメント]を参照すること。<br> | |||
<br> | |||
==== モジュール定義ファイル (defファイル) を使用しない場合 ==== | |||
モジュール定義ファイルを使用しない場合は、<code>extern "C"</code>キーワードおよび<code>__declspec(dllexport)</code>キーワードを付加する必要がある。<br> | |||
<br> | |||
C言語では同じ関数名を複数定義できないため、関数名の一意性があるが、C++では関数のオーバーロードができるため、同じ関数名を区別するためにDLLの出力時に関数名が自動的に変更される。<br> | |||
エクスポートする関数名の一意に決定するため、<code>extern "C"</code>キーワードを付加することにより、DLLの出力時に特定の関数名を変更しないようにする。<br> | |||
<br> | |||
Visual Studioの[プロジェクト] - [プロパティ]を選択する。<br> | |||
[C++] - [プリプロセッサの定義]に"DLL"プリプロセッサを追加する。(“Stdafx.h”ファイルに”DLL”プリプロセッサを記載してもよい)<br> | |||
<br> | |||
まず、"MainDLL.h"ファイルを作成する。<br> | |||
<syntaxhighlight lang="c++"> | |||
// MainDLL.h | |||
#ifndef __MAINDLL_H__ | #ifndef __MAINDLL_H__ | ||
23行目: | 104行目: | ||
#endif | #endif | ||
// | // エクスポートする関数 | ||
#ifdef __cplusplus | #ifdef __cplusplus | ||
extern "C" | extern "C" | ||
37行目: | 110行目: | ||
#endif | #endif | ||
DLL_EXPORT int SampleFunc(CString &p_rcStr, CWnd *p_pcWnd); | |||
#ifdef __cplusplus | #ifdef __cplusplus | ||
} | } | ||
#endif | #endif | ||
</ | |||
#endif //__MAINDLL_H__ | |||
</syntaxhighlight> | |||
<br> | <br> | ||
"MainDLL.cpp" | 次に、"MainDLL.cpp"ファイルを作成する。<br> | ||
< | <syntaxhighlight lang="c++"> | ||
MainDLL.cpp | // MainDLL.cpp | ||
#include "StdAfx.h" | #include "StdAfx.h" | ||
#include "MainDLL.h" | #include "MainDLL.h" | ||
</ | |||
extern "C" int SampleFunc(CString &p_rcStr, CWnd *p_pcWnd) | |||
{ | |||
// 以下略 | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
<code>extern "C"</code>キーワードを付加しない場合、他の実行ファイル等からDLLを使用すると、関数が見つからないとエラーが発生する。<br> | |||
ネイティブの実行ファイルから呼び出す場合、コンパイル時にエラーが発生する場合が多い。<br> | |||
ただし、マネージドの実行ファイルからC++ DLLを呼び出す場合、<u>実行時に</u>例外<code>System.EntryPointNotFoundException</code>が発生する。<br> | |||
<br><br> | |||
== DLLファイルの確認 == | |||
DLLファイルのエクスポートされた関数名は、dumpbinで確認することができる。<br> | |||
<br> | |||
PowerShellまたはコマンドプロンプトを起動して、以下のコマンドを実行する。<br> | |||
この時、name項目に関数名が表示される。<br> | |||
dumpbin.exe /EXPORTS <DLLファイル名> | |||
# 出力 | |||
Microsoft (R) COFF/PE Dumper Version 9.00.30729.01 | |||
Copyright (C) Microsoft Corporation. All rights reserved. | |||
Dump of file <DLLファイル名> | |||
File Type: DLL | |||
Section contains the following exports for sample.dll | |||
00000000 characteristics | |||
49A74D91 time date stamp Fri Feb 27 01:18:57 2009 | |||
0.00 version | |||
1 ordinal base | |||
1 number of functions | |||
1 number of names | |||
ordinal hint RVA name | |||
1 0 00001034 FunctionName | |||
Summary | |||
1000 .data | |||
1000 .pdata | |||
1000 .rdata | |||
1000 .reloc | |||
1000 .text | |||
<br><br> | |||
== DLLの使用 == | |||
Visual StudioでDLLを作成している場合、プロジェクトから参照に追加することで使用できる。<br> | |||
他のIDE等でDLLを作成している場合、共有ライブラリとして使用するには、コンパイル時に以下の3つが必要となる。<br> | |||
* ヘッダーファイル (.h) | |||
* インポートライブラリ (.lib) | |||
* DLL | |||
<br> | |||
また、外部から呼び出せる関数が存在しない場合、インポートライブラリ(.lib)は作成されない。<br> | |||
<br><br> | |||
== インポートライブラリの作成 == | |||
インポートライブラリ(.lib)が提供されていない場合、DLLファイルから作成できる。<br> | |||
<br> | |||
まず、dumpbinを実行して、エクスポートされた全ての定義をファイルに出力する。<br> | |||
dumpbin /exports target.dll > exports.txt | |||
<br> | |||
1行目にLIBRARY DLL名、2行目にEXPORTSを記述をしたDEFファイルを作成する。<br> | |||
echo LIBRARY <Dllファイル名> > target.def | |||
echo EXPORTS >> target.def | |||
<br> | |||
DEFファイルに、最初に出力したファイルから関数の定義部分だけを追記する。<br> | |||
for /f "skip=19 tokens=4" %A in (exports.txt) do echo %A >> target.def | |||
<br> | |||
libコマンドを実行して、DEFファイルからインポートライブラリを作成する。<br> | |||
# 32bitを対象とする場合 | |||
lib /def:target.def /out:target.lib /machine:x86 | |||
# 64bitを対象とする場合 | |||
lib /def:target.def /out:target.lib /machine:x64 | |||
<br> | |||
インポートライブラリを参照するには、リンカーのオプションにて、[追加の依存ファイル]で指定する。<br> | |||
これは、/DYNAMICBASEオプションを指定することと同じことである。<br> | |||
または、ソースコード内において、#pragmaディレクティブで指定する。<br> | |||
<br><br> | |||
== DLLの暗黙的リンクと明示的リンクの違い == | |||
<center> | |||
{| class="wikitable" | style="background-color:#fefefe;" | |||
|- | |||
! style="background-color:#66CCFF;" | | |||
! style="background-color:#66CCFF;" | 暗黙的(静的)リンク | |||
! style="background-color:#66CCFF;" | 明示的(動的)リンク | |||
|- | |||
| 関数の宣言 || DLLの関数に__declspecl(dllimport)を付けて宣言する。 || DLLの関数を宣言せず、typedefする。 | |||
|- | |||
| リンカ || リンカでライブラリファイルをリンクする必要がある。 || リンカでのリンクは不要である。 | |||
|- | |||
| DLLの読み込み || EXEの実行の準備段階でDLLを読み込む。<br>DLLが見つからない場合、EXEは実行されない。 || <code>LoadLibrary</code>関数でDLLを読み込む。<br>DLLが見つからない場合、NULLが返る。<br>DLL内の関数を実行するには、<code>GetProcAddress</code>関数で関数アドレスを取得する必要がある。 | |||
|- | |||
| 関数との紐付け || ライブラリファイルで、DLL内の関数名(序数も含む)が保持されている。<br>この関数名(あるいは序数)が一致しないとNULLが返る。 || <code>GetProcAddress</code>関数で、DLL内の関数名(あるいは序数)を指定する。<br>この関数名(または序数)が一致しないとNULLが返る。 | |||
|- | |||
| 暗黙的 / 明示的とは || EXEの実行準備段階でDLLを自動で読み込むので、暗黙的という。 || 設計者が<code>LoadLibrary</code>関数を使用してDLLを呼び出すので、明示的という。 | |||
|- | |||
| 静的 / 動的とは || リンカでリンクした時点で使用するDLLの種類や関数が決まるので、静的という。 || <code>LoadLibrary</code>関数と<code>GetProcAddress</code>関数を使用してDLL内の関数を自由に使用できるので、動的という。 | |||
|} | |||
</center> | |||
<br><br> | |||
== DLLの暗黙的リンク == | |||
EXEファイルのプロジェクトに、DLLファイルを暗黙的リンクする方法を記載する。<br> | |||
<br> | |||
以下の例では、CppEXE.exeとCppDLL.dllが存在するものとして設定している。<br> | |||
# まず、CppEXE.exeのプロジェクトを起動する。 | |||
# [プロジェクト]メニュー - [CppEXEのプロパティ] - [構成プロパティ] - [C/C++] - [全般] - [追加のインクルードディレクトリ]項目に、<br>CppDLL.dllのヘッダファイル(CppDLL.h)が存在するディレクトリを追加する。 | |||
# 次に、[構成プロパティ] - [リンカー] - [全般] - [追加のライブラリディレクトリ]項目に、<br>DLLのライブラリファイル(CppDLL.lib)が存在するディレクトリを追加する。 | |||
# 最後に、[構成プロパティ] - [リンカー] - [入力] - [追加の依存ファイル]項目に、CppDLL.libと記述する。 | |||
<br><br> | |||
== DLLの明示的リンク == | |||
DLLの明示的リンクを行う場合、Windows APIの<code>LoadLibrary</code>関数を使用する。<br> | |||
DLLファイルが見つからない場合は、ハンドルがNULLで返るため、エラーハンドリングを行う。<br> | |||
<br> | |||
以下の例では、Sample.dllファイルに定義されたHello関数を呼んでいる。<br> | |||
<syntaxhighlight lang="c++"> | |||
// DLLファイルの読み込み | |||
HMODULE hModule = ::LoadLibrary(_T("Sample.dll")); | |||
if(hModule == NULL) | |||
{ | |||
std::cerr << _T("DLLファイルの読み込みに失敗しました") << std::endl; | |||
return 0; | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
次に、DLLファイルから呼ぶ関数のアドレスを取得する。<br> | |||
まず、DLLファイルに定義されたHello関数の呼び出すには、Hello関数の関数ポインタを定義する。<br> | |||
<br> | |||
Hello関数のアドレスを取得するには、<code>GetProcAddress</code>関数を使用する。<br> | |||
Hello関数のアドレスの取得に失敗した場合は、必ず<code>FreeLibrary</code>関数を呼び、DLLファイルのハンドルを解放する。<br> | |||
<syntaxhighlight lang="c++"> | |||
// C++03以前 | |||
// typedef int (*FUNC)(char *); | |||
// C++11以降 | |||
using FUNC = int (*)(char *); | |||
// 関数のアドレス取得 | |||
FUNC lpFunc = (FUNC)::GetProcAddress(hModule, "Hello"); | |||
if (lpFunc == NULL) | |||
{ | |||
std::cerr << _T("関数のアドレス取得に失敗しました") << std::endl; | |||
::FreeLibrary(hModule); | |||
return 0; | |||
} | |||
</syntaxhighlight> | |||
<br> | <br> | ||
最後に、GetProcAddress関数で取得したHello関数のアドレスに引数を渡す。<br> | |||
< | <syntaxhighlight lang="c++"> | ||
// 関数の呼び出し | |||
int ret = (*lpFunc)(tmp); | |||
if(ret != 0) | |||
{ | |||
std::cerr << _T("関数の呼び出しに失敗しました") << std::endl; | |||
::FreeLibrary(hModule); | |||
return 0; | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
以下に、サンプルコードの全体を示す。<br> | |||
<syntaxhighlight lang="c++"> | |||
#include "stdafx.h" | |||
#include <Windows.h> | |||
#include <conio.h> | |||
// C++03以前 | |||
// typedef int (*FUNC)(char *); | |||
// C++11以降 | |||
using FUNC = int (*)(char *); | |||
int _tmain(int argc, _TCHAR* argv[]) | |||
{ | { | ||
// | // DLLを読み込む | ||
HMODULE hModule = ::LoadLibrary(_T("Sample.dll")); | |||
if (hModule == NULL) | |||
{ | |||
std::cerr << _T("DLLファイルの読み込みに失敗しました") << std::endl; | |||
return 0; | |||
} | |||
// Hello関数のアドレスを取得 | |||
FUNC lpFunc = (FUNC)::GetProcAddress(hModule, _T("Hello")); | |||
if (lpFunc == NULL) | |||
{ | |||
std::cerr << _T("Hello関数のアドレス取得に失敗しました") << std::endl; | |||
::FreeLibrary(hModule); | |||
return 0; | |||
} | |||
// Hello関数の実行 | |||
char tmp[12 + 1]; // Hello関数の引数 | |||
memset(tmp, 0, sizeof(tmp)); | |||
int ret = (*lpFunc)(tmp); | |||
if (ret != 0) | |||
{ | |||
std::cerr << _T("Hello関数の呼び出しに失敗しました") << std::endl; | |||
::FreeLibrary(hModule); | |||
return 0; | |||
} | |||
// DLLの解放 | |||
::FreeLibrary(hModule); | |||
std::cerr << tmp << std::endl; | |||
_getch(); | |||
return 0; | |||
} | } | ||
</ | </syntaxhighlight> | ||
<br><br> | |||
== デバッグ方法 == | |||
C++DLLのデバッグ方法を、以下に記載する。<br> | |||
<br> | |||
# [ソリューションエクスプローラー]において、C++DLLプロジェクトを右クリックして、[プロパティ]を選択する。<br> | |||
# [<Project>プロパティページ]画面が開くので、画面上部の[構成]プルダウンから[デバッグ]を選択する。 | |||
# 次に、画面左にある[構成プロパティ] - [デバッグ]を選択する。 | |||
# 画面右にある[起動するデバッガー]プルダウンから[ローカルWindowsデバッガー]または[リモートWindowsデバッガー]のいずれかを選択する。 | |||
# 画面右にある[コマンド]項目または[リモートコマンド]項目において、呼び出し元の実行ファイルのフルパスを入力する。<br>コマンドライン引数が必要な場合、[コマンド引数]項目に任意の必要なコマンドライン引数を入力する。 | |||
# [OK]ボタンを押下する。 | |||
<br><br> | <br><br> | ||
__FORCETOC__ | __FORCETOC__ | ||
[[カテゴリ:MFC]] | [[カテゴリ:C]][[カテゴリ:C++]][[カテゴリ:MFC]] |
2024年2月10日 (土) 18:05時点における最新版
概要
MFC DLLには、以下の2つの種類がある。
- 拡張DLL
- DLLを使用するEXEやDLLもMFCで作成する時にのみ使用する。
- MFCの共有DLL(Regular DLL)
- 内部的にMFCそのものを持っているため、DLLを使用するEXEやDLLをMFCで作成しない時でも使用できる。
DLLの作成方法
呼び出し規約
[プロジェクト]メニューバー - [プロパティ]を選択して、[プロパティ]ダイアログを表示する。
[プロパティ]ダイアログの[構成のプロパティ] - [C/C++] - [詳細設定]を選択する。
[プロパティ]ダイアログの右ペインから、[呼び出し規約]プルダウンを__cdecl (/Gd)に変更する。 (デフォルト)
モジュール定義ファイル (defファイル) を使用する場合
モジュール定義 (.def拡張子) ファイルは、DLLがエクスポートする関数を記述したファイルであり、リンクするプログラムに関するエクスポート、属性、その他の情報をリンカに提供する。
モジュール定義ファイルは、DLLをビルドする場合に最も役立つ。
モジュール定義ステートメントの代わりに使用できるMSVCリンカオプションがあるため、モジュール定義ファイルが存在してくてもDLLを開発することができる。
モジュール定義ファイルを使用しない場合は、エクスポートされる関数を指定する方法として、__declspec(dllexport)
を使用する。
※注意
エクスポートを行わない実行ファイルを開発する場合、モジュール定義ファイルを使用すると、出力ファイルのサイズが大きくなり読み込みが遅くなることに注意する。
[ソリューションエクスプローラ]を右クリックして、コンテキストメニューから[追加] - [新しい項目]を選択する。
[新しい項目の追加]ダイアログが開くので、モジュール定義ファイル名 <ファイル名>.def を入力する。
[OK]ボタンを押下すると、モジュール定義ファイルが自動的に作成される。
※ C++でDLLを作成する場合は、defファイルを作成することを推奨する。
[プロジェクト]メニューバー - [プロパティ]を選択して、[プロパティ]ダイアログを表示する。
[プロパティ]ダイアログの[リンカー] - [入力] - [モジュール定義ファイル]項目に使用するモジュール定義ファイル名を入力する。
[OK]ボタン、または、[適用]ボタンを押下して、変更内容を保存する。
※注意
C#で開発したモジュール(EXEまたはDLL)からC++ DLLを呼び出す場合、C++ DLLではモジュール定義ファイルを使用すること。
モジュール定義ファイルを作成して、以下に示すようにエクスポートする関数を記述する。
また、@1等の序数値の記載は任意である。
; MainDLL.def
LIBRARY MainDLL
EXPORTS
SampleFunc @1
TestFunc @2
次に、"MainDLL.h"ファイルを作成する。
モジュール定義ファイルを使用する場合、エクスポートする関数名にextern "C"
キーワードおよびDECLSPEC __declspec(dllexport)
キーワードを付加する必要はない。
// MainDLL.h
#pragma once
int SampleFunc(int *lp1, int *lp2);
double TestFunc(double *lp1, double *lp2);
最後に、"MainDLL.cpp"ファイルには以下のように記載する。
// MainDLL.cpp
#include "Stdafx.h"
#include "MainDLL.h"
int SampleFunc(int *lp1, int *lp2)
{
// ...処理 1
}
int TestFunc(double *lp1, double *lp2)
{
// ...処理 2
}
モジュール定義ファイルの詳細を知りたい場合は、Microsoftの公式ドキュメントを参照すること。
モジュール定義ファイル (defファイル) を使用しない場合
モジュール定義ファイルを使用しない場合は、extern "C"
キーワードおよび__declspec(dllexport)
キーワードを付加する必要がある。
C言語では同じ関数名を複数定義できないため、関数名の一意性があるが、C++では関数のオーバーロードができるため、同じ関数名を区別するためにDLLの出力時に関数名が自動的に変更される。
エクスポートする関数名の一意に決定するため、extern "C"
キーワードを付加することにより、DLLの出力時に特定の関数名を変更しないようにする。
Visual Studioの[プロジェクト] - [プロパティ]を選択する。
[C++] - [プリプロセッサの定義]に"DLL"プリプロセッサを追加する。(“Stdafx.h”ファイルに”DLL”プリプロセッサを記載してもよい)
まず、"MainDLL.h"ファイルを作成する。
// MainDLL.h
#ifndef __MAINDLL_H__
#define __MAINDLL_H__
#ifdef DLL
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif
// エクスポートする関数
#ifdef __cplusplus
extern "C"
{
#endif
DLL_EXPORT int SampleFunc(CString &p_rcStr, CWnd *p_pcWnd);
#ifdef __cplusplus
}
#endif
#endif //__MAINDLL_H__
次に、"MainDLL.cpp"ファイルを作成する。
// MainDLL.cpp
#include "StdAfx.h"
#include "MainDLL.h"
extern "C" int SampleFunc(CString &p_rcStr, CWnd *p_pcWnd)
{
// 以下略
}
extern "C"
キーワードを付加しない場合、他の実行ファイル等からDLLを使用すると、関数が見つからないとエラーが発生する。
ネイティブの実行ファイルから呼び出す場合、コンパイル時にエラーが発生する場合が多い。
ただし、マネージドの実行ファイルからC++ DLLを呼び出す場合、実行時に例外System.EntryPointNotFoundException
が発生する。
DLLファイルの確認
DLLファイルのエクスポートされた関数名は、dumpbinで確認することができる。
PowerShellまたはコマンドプロンプトを起動して、以下のコマンドを実行する。
この時、name項目に関数名が表示される。
dumpbin.exe /EXPORTS <DLLファイル名> # 出力 Microsoft (R) COFF/PE Dumper Version 9.00.30729.01 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file <DLLファイル名> File Type: DLL Section contains the following exports for sample.dll 00000000 characteristics 49A74D91 time date stamp Fri Feb 27 01:18:57 2009 0.00 version 1 ordinal base 1 number of functions 1 number of names ordinal hint RVA name 1 0 00001034 FunctionName Summary 1000 .data 1000 .pdata 1000 .rdata 1000 .reloc 1000 .text
DLLの使用
Visual StudioでDLLを作成している場合、プロジェクトから参照に追加することで使用できる。
他のIDE等でDLLを作成している場合、共有ライブラリとして使用するには、コンパイル時に以下の3つが必要となる。
- ヘッダーファイル (.h)
- インポートライブラリ (.lib)
- DLL
また、外部から呼び出せる関数が存在しない場合、インポートライブラリ(.lib)は作成されない。
インポートライブラリの作成
インポートライブラリ(.lib)が提供されていない場合、DLLファイルから作成できる。
まず、dumpbinを実行して、エクスポートされた全ての定義をファイルに出力する。
dumpbin /exports target.dll > exports.txt
1行目にLIBRARY DLL名、2行目にEXPORTSを記述をしたDEFファイルを作成する。
echo LIBRARY <Dllファイル名> > target.def echo EXPORTS >> target.def
DEFファイルに、最初に出力したファイルから関数の定義部分だけを追記する。
for /f "skip=19 tokens=4" %A in (exports.txt) do echo %A >> target.def
libコマンドを実行して、DEFファイルからインポートライブラリを作成する。
# 32bitを対象とする場合 lib /def:target.def /out:target.lib /machine:x86 # 64bitを対象とする場合 lib /def:target.def /out:target.lib /machine:x64
インポートライブラリを参照するには、リンカーのオプションにて、[追加の依存ファイル]で指定する。
これは、/DYNAMICBASEオプションを指定することと同じことである。
または、ソースコード内において、#pragmaディレクティブで指定する。
DLLの暗黙的リンクと明示的リンクの違い
暗黙的(静的)リンク | 明示的(動的)リンク | |
---|---|---|
関数の宣言 | DLLの関数に__declspecl(dllimport)を付けて宣言する。 | DLLの関数を宣言せず、typedefする。 |
リンカ | リンカでライブラリファイルをリンクする必要がある。 | リンカでのリンクは不要である。 |
DLLの読み込み | EXEの実行の準備段階でDLLを読み込む。 DLLが見つからない場合、EXEは実行されない。 |
LoadLibrary 関数でDLLを読み込む。DLLが見つからない場合、NULLが返る。 DLL内の関数を実行するには、 GetProcAddress 関数で関数アドレスを取得する必要がある。
|
関数との紐付け | ライブラリファイルで、DLL内の関数名(序数も含む)が保持されている。 この関数名(あるいは序数)が一致しないとNULLが返る。 |
GetProcAddress 関数で、DLL内の関数名(あるいは序数)を指定する。この関数名(または序数)が一致しないとNULLが返る。 |
暗黙的 / 明示的とは | EXEの実行準備段階でDLLを自動で読み込むので、暗黙的という。 | 設計者がLoadLibrary 関数を使用してDLLを呼び出すので、明示的という。
|
静的 / 動的とは | リンカでリンクした時点で使用するDLLの種類や関数が決まるので、静的という。 | LoadLibrary 関数とGetProcAddress 関数を使用してDLL内の関数を自由に使用できるので、動的という。
|
DLLの暗黙的リンク
EXEファイルのプロジェクトに、DLLファイルを暗黙的リンクする方法を記載する。
以下の例では、CppEXE.exeとCppDLL.dllが存在するものとして設定している。
- まず、CppEXE.exeのプロジェクトを起動する。
- [プロジェクト]メニュー - [CppEXEのプロパティ] - [構成プロパティ] - [C/C++] - [全般] - [追加のインクルードディレクトリ]項目に、
CppDLL.dllのヘッダファイル(CppDLL.h)が存在するディレクトリを追加する。 - 次に、[構成プロパティ] - [リンカー] - [全般] - [追加のライブラリディレクトリ]項目に、
DLLのライブラリファイル(CppDLL.lib)が存在するディレクトリを追加する。 - 最後に、[構成プロパティ] - [リンカー] - [入力] - [追加の依存ファイル]項目に、CppDLL.libと記述する。
DLLの明示的リンク
DLLの明示的リンクを行う場合、Windows APIのLoadLibrary
関数を使用する。
DLLファイルが見つからない場合は、ハンドルがNULLで返るため、エラーハンドリングを行う。
以下の例では、Sample.dllファイルに定義されたHello関数を呼んでいる。
// DLLファイルの読み込み
HMODULE hModule = ::LoadLibrary(_T("Sample.dll"));
if(hModule == NULL)
{
std::cerr << _T("DLLファイルの読み込みに失敗しました") << std::endl;
return 0;
}
次に、DLLファイルから呼ぶ関数のアドレスを取得する。
まず、DLLファイルに定義されたHello関数の呼び出すには、Hello関数の関数ポインタを定義する。
Hello関数のアドレスを取得するには、GetProcAddress
関数を使用する。
Hello関数のアドレスの取得に失敗した場合は、必ずFreeLibrary
関数を呼び、DLLファイルのハンドルを解放する。
// C++03以前
// typedef int (*FUNC)(char *);
// C++11以降
using FUNC = int (*)(char *);
// 関数のアドレス取得
FUNC lpFunc = (FUNC)::GetProcAddress(hModule, "Hello");
if (lpFunc == NULL)
{
std::cerr << _T("関数のアドレス取得に失敗しました") << std::endl;
::FreeLibrary(hModule);
return 0;
}
最後に、GetProcAddress関数で取得したHello関数のアドレスに引数を渡す。
// 関数の呼び出し
int ret = (*lpFunc)(tmp);
if(ret != 0)
{
std::cerr << _T("関数の呼び出しに失敗しました") << std::endl;
::FreeLibrary(hModule);
return 0;
}
以下に、サンプルコードの全体を示す。
#include "stdafx.h"
#include <Windows.h>
#include <conio.h>
// C++03以前
// typedef int (*FUNC)(char *);
// C++11以降
using FUNC = int (*)(char *);
int _tmain(int argc, _TCHAR* argv[])
{
// DLLを読み込む
HMODULE hModule = ::LoadLibrary(_T("Sample.dll"));
if (hModule == NULL)
{
std::cerr << _T("DLLファイルの読み込みに失敗しました") << std::endl;
return 0;
}
// Hello関数のアドレスを取得
FUNC lpFunc = (FUNC)::GetProcAddress(hModule, _T("Hello"));
if (lpFunc == NULL)
{
std::cerr << _T("Hello関数のアドレス取得に失敗しました") << std::endl;
::FreeLibrary(hModule);
return 0;
}
// Hello関数の実行
char tmp[12 + 1]; // Hello関数の引数
memset(tmp, 0, sizeof(tmp));
int ret = (*lpFunc)(tmp);
if (ret != 0)
{
std::cerr << _T("Hello関数の呼び出しに失敗しました") << std::endl;
::FreeLibrary(hModule);
return 0;
}
// DLLの解放
::FreeLibrary(hModule);
std::cerr << tmp << std::endl;
_getch();
return 0;
}
デバッグ方法
C++DLLのデバッグ方法を、以下に記載する。
- [ソリューションエクスプローラー]において、C++DLLプロジェクトを右クリックして、[プロパティ]を選択する。
- [<Project>プロパティページ]画面が開くので、画面上部の[構成]プルダウンから[デバッグ]を選択する。
- 次に、画面左にある[構成プロパティ] - [デバッグ]を選択する。
- 画面右にある[起動するデバッガー]プルダウンから[ローカルWindowsデバッガー]または[リモートWindowsデバッガー]のいずれかを選択する。
- 画面右にある[コマンド]項目または[リモートコマンド]項目において、呼び出し元の実行ファイルのフルパスを入力する。
コマンドライン引数が必要な場合、[コマンド引数]項目に任意の必要なコマンドライン引数を入力する。 - [OK]ボタンを押下する。