今回はWindows環境のC++で動的ライブラリを動的ロードするための手順です。
C++で複数のDLLを設定で切り替えて使うようなパターンの記事があまりなかったため、まとめてみました。
ライブラリとリンクの種類
先ずはライブラリの種類と、リンクの種類を整理します。
以下のサイトで説明されている内容を一部抜粋します。
リンク方法 | ライブラリ | Windows | Linux | リンクタイミング |
静的リンク | 静的ライブラリ | lib | a | ビルド時 |
動的リンク | 動的ライブラリ | dll | so | 起動時 |
動的ロード | 動的ライブラリ | dll | so | 実行中の任意のタイミング |
今回の記事では、表の一番下の行をWindows環境で説明を行います。
開発ファイルと実行ファイルの全体図
下記の図はプロジェクトやファイルの関係を表しています。
矢印が参照(依存関係)を表しています。
mainプロジェクトがロードする側で、DLLプロジェクトがロードされる側になります。
複数のDLLプロジェクトがIClass.hを実装しているため、mainプロジェクトでは、IClassインターフェース経由で複数のDLLを使用することができます。
次に実装を見ていきます。
実装
mainプロジェクト側の実装
mainプロジェクトはコンソールアプリプロジェクトを前提に進めます。
各ファイルを以下のように実装します。
IClass.h
#pragma once
struct MethodArgs {
int a;
};
struct MethodReturn {
int b;
};
class IClass {
public:
virtual MethodReturn Test(MethodArgs args);
};
DLL側でIClassを基底クラスとして参照するため、関数はvirtualにしておきます。
今回は引数と戻り値にstructが使えることを証明するために、あえてこうしてあります。
DllLoader.h
#pragma once
#include <Windows.h>
#include <iostream>
template<typename T>
T* LoadClassFromDLL(const std::string& dllName, const std::string& functionName) {
// DLLをロード
HMODULE hModule = LoadLibraryA(dllName.c_str());
if (!hModule) {
std::cerr << "Failed to load DLL: " << dllName << std::endl;
return nullptr;
}
// ファクトリ関数のアドレスを取得
using CREATE_FUNC = T * (*)();
CREATE_FUNC createFunc = (CREATE_FUNC)GetProcAddress(hModule, functionName.c_str());
if (!createFunc) {
std::cerr << "Failed to find " << functionName << " function in DLL." << std::endl;
FreeLibrary(hModule);
return nullptr;
}
// ファクトリ関数を呼び出して、クラスのインスタンスを作成
T* instance = createFunc();
if (!instance) {
std::cerr << "Failed to create class instance." << std::endl;
FreeLibrary(hModule);
return nullptr;
}
// インスタンスの作成に成功した場合、DLLハンドルの解放は呼び出し側の責任
// 必要に応じてFreeLibrary(hModule)を呼び出してください
return instance;
}
LoadClassFromDLL関数では、指定のDLLから指定のファクトリ関数を呼び出し、結果をtemplateで受け取ったクラス型のポインタとして返却する関数です。
main.cpp
#include <iostream>
#include "DllLoader.h"
#include "IClass.h"
int main()
{
IClass* dllClass = LoadClassFromDLL<IClass>("dll1.dll", "CreateClass");
MethodReturn result = dllClass->Test(MethodArgs{ 1 });
std::cout << result.b << std::endl;
}
dll1.dllファイルのCreateClass関数を呼び出して、IClass型のポインタを受け取り、クラスのTest関数を呼び出しています。
DLLプロジェクト側の実装
Class.cpp
#include "IClass.h"
class Class : public IClass {
public:
MethodReturn Test(MethodArgs args) override {
return MethodReturn
{
args.a * 2
};
}
};
MethodReturn IClass::Test(MethodArgs args) {
return MethodReturn();
}
extern "C" __declspec(dllexport) IClass * CreateClass() {
return new Class();
}
CreateClass関数はmainプロジェクトから呼び出されるため「extern “C” __declspec(dllexport)」がついています。
Test関数は継承を使った実装と、もう一つIClassに対する実装の2か所の記述があるのですが、後者はビルド時にリンクエラーにならないように書いているため、内容はなんでもいいです。実際に呼び出されるのはClassのTest関数です。
ビルド・実行して確認
もしDLL側からIClass.hへのリンクができない場合は、追加のインクルードディレクトリにmainプロジェクトフォルダを指定してあげてください。
実行すると、DLL側の関数が呼ばれていることが確認できます。