Windows C++でDLLの動的ロード(windows visual studio)

C/C++

今回はWindows環境のC++で動的ライブラリを動的ロードするための手順です。
C++で複数のDLLを設定で切り替えて使うようなパターンの記事があまりなかったため、まとめてみました。

ライブラリとリンクの種類

先ずはライブラリの種類と、リンクの種類を整理します。
以下のサイトで説明されている内容を一部抜粋します。

ライブラリのリンク方法をきっちり区別しよう - Qiita
初めにライブラリのリンク方法は3種類に分けられます。「動的ライブラリ」や「動的リンク」といったキーワードでネット検索すると、3種類全てを説明しているサイトがかなり少ない印象を受けます。そこで「…
リンク方法ライブラリWindowsLinuxリンクタイミング
静的リンク静的ライブラリlibaビルド時
動的リンク動的ライブラリdllso起動時
動的ロード動的ライブラリdllso実行中の任意のタイミング

今回の記事では、表の一番下の行を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側の関数が呼ばれていることが確認できます。

タイトルとURLをコピーしました