今までDLLファイルを作ったことがなかったので、インターネットで調べながら作ってみました。VisualStudio2023を使用してDLLを作成し、呼び出すところまでを記載します。(VisualStudioの使い方から勉強中です・・)
呼び出す関数は、素数かどうかを判定するisPrimeとしました。
環境
OS Windows11
IDE VisualStudio2023
作成手順
〇作成の流れ
ヘッダーファイルを作る
CPPファイルを作成し、コンパイラでDLLファイルを作る
DLLを呼び出すメインプログラムを作成し、実行する
①ダイナミックリンクライブラリのプロジェクトを作る
[ファイル]-[新しいプロジェクトの作成]-[ダイナミックリンクライブラリ(DLL)]を選択し、DLLプロジェクトを作成します。
プロジェクト名は、素数を意味する[Prime]にしました。
②ヘッダーファイルを作る
ソリューションエクスプローラの[ソースファイル]の上で右クリックし、[モジュールの追加]を選択します
ファイル名を[Prime.h]としました。
誰もが最初に使ったであろう<stuiod.h>も、こうやって作られていたのかと思うとわくわくしますね。
Prime.hの内容は次の通りです。
最初の#pragma once は、ヘッダーファイルが複数回呼ばれることを防ぐおまじないです。
//Prime.h
#pragma once
#define DLLEXPORT extern "C" __declspec(dllexport)
DLLEXPORT bool isPrime(int num);
③DLLファイルのソースを作る
DLLファイルの実体は、dllmain.cppに記載します。
//dllmain.cpp
#pragma once
#ifdef PRIME_EXPORTS
#define PRIME_API __declspec(dllexport)
#else
#define PRIME_API __declspec(dllimport)
#endif
extern "C" PRIME_API bool isPrime(int num);
// dllmain.cpp
#include "pch.h"
#include "Prime.h"
bool isPrime(int num) {
if (num <= 1) {
return false; // 1以下の数は素数ではない
}
if (num == 2 || num == 3) {
return true; // 2と3は素数
}
if (num % 2 == 0 || num % 3 == 0) {
return false; // 2と3の倍数は素数ではない
}
for (int i = 5; i * i <= num; i += 6) {
if (num % i == 0 || num % (i + 2) == 0) {
return false; // 6n ± 1 の形の数は素数ではない
}
}
return true; // 上記の条件を満たさない場合は素数
}
#include "pch.h"
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
bool isPrime(int num) の部分が外部から呼ばれる関数の実体です。
④ソリューションのビルド
ビルドを行い、プロジェクトをコンパイルします。とりあえずDebugでいいと思います。
成功すると、DebugフォルダにDLLが出力されます。
⑤isPrimeを呼び出すプログラムの作成
次はisPrimeを呼び出す別のプロジェクトを作ります。
[ファイル]-[新しいプロジェクトの作成]-[空のプロジェクト]を選択し、空のプロジェクトを作成します。
プロジェクト名は[testPrime]にしました。
⑥mainプログラムの作成
プロジェクトを作ったらメインプログラムを作るので、新しいC++モジュールを作成します。
C++ファイル(.cpp)を選択し、ファイル名は[testPrime.cpp]にしました。
プログラムの内容は以下の通りです。
#include <iostream>
#include <windows.h>
int main() {
HMODULE hDLL = LoadLibrary(TEXT("../Prime/x64/Debug/Prime.dll")); // DLL をロード
if (hDLL == NULL) {
std::cerr << "DLL をロードできませんでした。" << std::endl;
return 1;
}
// isPrime 関数のポインタを取得
bool(*isPrimePtr)(int);
isPrimePtr = (bool(*)(int))GetProcAddress(hDLL, "isPrime");
if (isPrimePtr == NULL) {
std::cerr << "isPrime 関数を見つけることができませんでした。" << std::endl;
FreeLibrary(hDLL); // DLL を解放
return 1;
}
// isPrime 関数を呼び出して結果を表示
int num = 7; // 素数かどうかを確認する数字
if (isPrimePtr(num)) {
std::cout << num << " は素数です。" << std::endl;
}
else {
std::cout << num << " は素数ではありません。" << std::endl;
}
FreeLibrary(hDLL); // DLL を解放
return 0;
}
環境によってはDLLファイルのパスが違うかもしれません。
Windowsの癖で、
..\Prime\x64\Debug\Prime1.dll
と入力して動かず、\→/ に気付くのにしばらくかかりました。
⑦ビルドと実行
プロジェクトをビルドして、実行し、次のような結果が出れば成功です。
終わりに
DLLを呼び出す部分を見ていると、ハンドラーを使って、DLL内の関数ポインタを直接呼び出して実行しているようです。(結構強引に呼んでいるのですね)
この方法なら言語を超えた呼び出しができる点に納得がいきました。
DLL化するとソースのブラックボックス化ができる点はいいのですが、悪意のあるDLLに置き換えられた際に見分けることができないですね。
正規DLLファイルのハッシュ値と比較するロジックを組み込めばウィルス対策になるのに…