ライブラリの基礎 - DLLの作成(C/C++/MFC)
概要
MFC DLLには、以下の2つの種類がある。
- 拡張DLL
- DLLを使用するEXEやDLLもMFCで作成する時にのみ使用する。
- MFCの共有DLL(Regular DLL)
- 内部的にMFCそのものを持っているため、DLLを使用するEXEやDLLをMFCで作成しない時でも使用できる。
DLLの作成方法
defファイルを使用する
defファイルを作成する方法を記載する。
defファイルとは、DLLがエクスポートする関数を記述したファイルのことである。
[ソリューションエクスプローラ]を右クリック - [追加] - [新しい項目]を選択して、[新しい項目の追加]ダイアログにて"<ファイル名>.def"を追加すると、defファイルが作成される。
※ C++でDLLを作成する場合は、defファイルを作成する方法を強く推奨する。
[プロジェクト]メニューバー - [プロパティ]を選択して、[プロパティ]ダイアログを表示する。
[プロパティ]ダイアログの[リンカー] - [入力] - [モジュール定義ファイル]項目に使用するdefファイル名を記述する。
※注意
C#にて作成したモジュール(EXEまたはDLL)からC++ DLLを呼び出す場合、C++ DLLではdefファイルを使用すること。
defファイルを作成して、以下のようにエクスポートする関数を記載する。
※但し、"@1"等の序数値は記載しなくてもよい。
MainDLL.def
LIBRARY MainDLL
EXPORTS
SampleFunc @1
TestFunc @2
次に、"MainDLL.h"ファイルは以下のように記載する。
defファイルによるエクスポートを採用した場合、 エクスポート関数名にキーワードを付ける必要は無い。
MainDLL.h
#pragma once
#ifndef _USRDLL
#define DLL_EXPORT extern "C" __declspec(dllimport)
#else
#define DLL_EXPORT
#endif
DLL_EXPORT int __stdcall SampleFunc(int *lp1, int *lp2);
DLL_EXPORT double __stdcall TestFunc(double *lp1, double *lp2);
最後に、"MainDLL.cpp"ファイルには以下のように記載する。
MainDLL.cpp
#include "Stdafx.h"
#include "MainDLL.h"
int __stdcall SampleFunc(int *lp1, int *lp2)
{
// 以下略
}
int __stdcall TestFunc(double *lp1, double *lp2)
{
// 以下略
}
defファイルを使用しない
defファイルを作成しない方法を記載する。
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
// 以下略
#endif //__MAINDLL_H__
次に、上記の"MainDLL.h"ファイルにエクスポートしたい関数を以下のように記載する。
MainDLL.h
#ifdef __cplusplus
extern "C"
{
#endif
DLL_EXPORT int WINAPI SampleFunc(CString &p_rcStr, CWnd *p_pcWnd);
#ifdef __cplusplus
}
#endif
"MainDLL.cpp"ファイルの先頭に以下を記載する。
MainDLL.cpp
#include "StdAfx.h"
#include "MainDLL.h"
"MainDLL.cpp"ファイルにて関数を作成する時は、以下のように記載する。
MainDLL.cpp
extern "C" int WINAPI SampleFunc(CString &p_rcStr, CWnd *p_pcWnd)
{
// 以下略
}
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ファイルのハンドルを解放する。
typedef int (*FUNC)(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>
typedef int(*FUNC)(char *);
int _tmain(int argc, _TCHAR* argv[])
{
char tmp[12 + 1];
memset(tmp, 0, sizeof(tmp));
// 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関数を呼ぶ
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]ボタンを押下する。