MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
ライブラリの基礎 - DLLの作成(C/C++/MFC)のソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
ライブラリの基礎 - DLLの作成(C/C++/MFC)
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == MFC DLLには、以下の2つの種類がある。<br> * 拡張DLL *: DLLを使用するEXEやDLLもMFCで作成する時にのみ使用する。 * MFCの共有DLL(Regular DLL) *: 内部的にMFCそのものを持っているため、DLLを使用するEXEやDLLをMFCで作成しない時でも使用できる。 <br><br> == DLLの作成方法 == ==== 呼び出し規約 ==== [プロジェクト]メニューバー - [プロパティ]を選択して、[プロパティ]ダイアログを表示する。<br> [プロパティ]ダイアログの[構成のプロパティ] - [C/C++] - [詳細設定]を選択する。<br> [プロパティ]ダイアログの右ペインから、[呼び出し規約]プルダウンを<u>__cdecl (/Gd)</u>に変更する。 (デフォルト)<br> <br> ==== モジュール定義ファイル (defファイル) を使用する場合 ==== モジュール定義 (.def拡張子) ファイルは、DLLがエクスポートする関数を記述したファイルであり、リンクするプログラムに関するエクスポート、属性、その他の情報をリンカに提供する。<br> モジュール定義ファイルは、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__ #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__ </syntaxhighlight> <br> 次に、"MainDLL.cpp"ファイルを作成する。<br> <syntaxhighlight lang="c++"> // MainDLL.cpp #include "StdAfx.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> 最後に、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> __FORCETOC__ [[カテゴリ:C]][[カテゴリ:C++]][[カテゴリ:MFC]]
ライブラリの基礎 - DLLの作成(C/C++/MFC)
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse