はじめに
DLL(Dynamic Link Library)はプログラム実行時に動的に読み込むライブラリです。特定の機能を他のアプリケーションでも使用したい場合に便利ですが、DLL単体では実行することができず呼び出し元のアプリケーションが必要になるため そのままではデバッグができません。
そこで今回はVisual StudioでDLLをデバッグする方法についてまとめました。
なお、本記事では
・Windows11 64bit
・Visual Studio 2022
・C++
を想定しています。
Visual Studio 2022のインストールはこちら
DLLの作成
まずはDLLを作成します。Visual Studio 2022を起動して「新しいプロジェクトを作成」を選択し、「DLL」と検索します。
表示された一覧の中から「ダイナミック リンク ライブラリ(DLL)」を選択します。
そしてDLL用のプロジェクトを作成します。今回のプロジェクト名は「DLLTest」としました。
プロジェクトを作成したらDllMainのみが実装された状態になっていると思いますので、呼び出す関数を実装します。
今回は入力値を100回加算して返す「Increment」関数を実装しました。この関数を呼び出し用アプリケーションから呼び出します。
なお、DLLで実装した関数をほかのアプリケーションで呼び出すには
をつける必要があります。
- extern "C" __declspec(dllexport)
C++にはオーバーロードがあるためコンパイル時にDLLで実装した関数名が変わってしまう可能性があるので「extern "C"」をつけることでそれを防ぎます。
- #include <iostream>
- // 入力した値を100回加算して返すだけの関数
- extern "C" __declspec(dllexport) int Increment(int add) {
- int result = 0;
- for (int i = 0; i < 100; i++) {
- result += add;
- }
- return result;
- }
実装が完了したら正常にビルドできるか確認しておきます。
DLL呼び出し用アプリケーションの作成
次にDLLを呼び出すアプリケーションを作成します。Visual Studio 2022を起動して「新しいプロジェクトを作成」を選択し、C++の「コンソール アプリ」を選択します。
そして呼び出し用アプリケーションのプロジェクトを作成しました。今回のプロジェクト名は「ReadDLLTest」としました。
プロジェクトが作成されたら最初はHell Worldのみが出力される状態になっていると思いますのでここにDLLを呼び出す処理を実装します。
このアプリケーションでは数値が入力されたらDLLを読み込み、入力された数値をincrement関数に入力して出力しています。
※ LoadLibraryAやGetProcAddressを使用するためwindows.hをインクルードします。
ここでDLLの読み込みを行いますが、文字セットがUnicodeの場合はLoadLibraryが使用できないのでLoadLibraryAを使用します (Visual Studio 2022はデフォルトの設定がUnicodeになっていると思います)。
- #include <iostream>
- #include <windows.h>
- using namespace std;
- // 関数型の定義(型名は任意)
- typedef int(__stdcall* INCREMENT)(int);
- int main()
- {
- int input;
- cout << "入力待ち" << endl;
- cin >> input;
- // DLLのパス
- const char* dllPath = "DLLTest.dll";
- // DLLを読み込む
- // Unicodeの場合はLoadLibraryA
- // マルチバイトの場合はLoadLibrary
- HMODULE hDll = LoadLibraryA(dllPath);
- if (!hDll) {
- cerr << "DLLのロードに失敗しました。" << endl;
- return -1;
- }
- // DLLの関数を読み込む
- INCREMENT increment = (INCREMENT)GetProcAddress(hDll, "Increment");
- if (!increment) {
- cerr << "関数の読み込みに失敗しました。" << endl;
- FreeLibrary(hDll);
- return -1;
- }
- // 関数呼び出し
- int result = increment(input);
- cout << "入力:" << input << endl;
- cout << "結果:" << result << endl;
- return 0;
- }
ここでDLLで実装した関数を読み込みます。
- // DLLを読み込む
- // Unicodeの場合はLoadLibraryA
- // マルチバイトの場合はLoadLibrary
- HMODULE hDll = LoadLibraryA(dllPath);
- if (!hDll) {
- cerr << "DLLのロードに失敗しました。" << endl;
- return -1;
- }
GetProcAddressの第2引数にDLLで定義した関数名を入力します。
実装が完了したらビルドしておきます。
- // DLLの関数を読み込む
- INCREMENT increment = (INCREMENT)GetProcAddress(hDll, "Increment");
- if (!increment) {
- cerr << "関数の読み込みに失敗しました。" << endl;
- FreeLibrary(hDll);
- return -1;
- }
動作確認
第1章で作成したDLLTest.dllを第2章で作成したReadDLLTestプロジェクトのx64/Debug
の中にコピーします。
・DLLTestプロジェクト
・ReadDLLTestプロジェクト
次にReadDLLTestをデバッグします。
結果はこちら。
ReadDLLTest.exe、DLLTest.dllともに正常に動作していることが確認できました。
DLLのデバッグ手順
ここからが本題です。DLL呼び出し用アプリケーション(ReadDLLTest.exe)はVisual StudioからそのままデバッグできますがDLL(DLLTest.dll)はできません。
しかし、ReadDLLTest.exeへアタッチすることでDLLのデバッグが可能になります。
①まずはDLLTest.dllの出力先をReadDLLTest.exeの実行ファイルと同じフォルダ変更し、DLLTestをビルドします。
②次にReadDLLTest.exeをデバッグなしで実行(もしくはReadDLLTest.exeをダブルクリックで実行)します。
③DLLTestのデバッグから「プロセスにアタッチ」を開きます。
④ プロセス一覧から「ReadDLLTest.exe」を選択し、「アタッチ」を選択します。
これでデバッグ出来るようになりました。
DLLTestのIncrement関数に入れたブレークポイントも機能していることが確認できました。
今回は以上です。
参考文献・参考サイト
・DLL とはhttps://learn.microsoft.com/ja-jp/troubleshoot/windows-client/setup-upgrade-and-drivers/dynamic-link-library
・Visual Studio で DLL プロジェクトからデバッグする (C#、C++、Visual Basic、F#)
https://learn.microsoft.com/ja-jp/visualstudio/debugger/how-to-debug-from-a-dll-project?view=vs-2022
・extern (C++)
https://learn.microsoft.com/ja-jp/cpp/cpp/extern-cpp?view=msvc-170#extern-c-and-extern-c-function-declarations
・__declspec(dllexport) を使った DLL からのエクスポート
https://learn.microsoft.com/ja-jp/cpp/build/exporting-from-a-dll-using-declspec-dllexport?view=msvc-170
・LoadLibraryA 関数 (libloaderapi.h)
https://learn.microsoft.com/ja-jp/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya
・GetProcAddress 関数 (libloaderapi.h)
https://learn.microsoft.com/ja-jp/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress
コメント