ちは
簡単なホットリロードを実装した時の備忘録です。
誇大表現かも
ホットリロードとは
Microsoftの解説を引用します。
アプリケーションのコード ファイルを編集し、そのコード変更を実行中のアプリケーションに対して直ちに適用できるようにすることで、これを実現します ("ホット リロード" とも呼ばれます)。
自分は「実行中に再ビルド無しに新たな変更を適用すること」ぐらいの認識でいます。
大きく間違ってはないはず...
環境
- Windows 11 Home 22H2
- Visual Studio 2019 Version 16.9.0
- C++
ざっくり方針
1. DLLのプログラムを作る
ついでにクラスを利用する方法も考えてみた。
2. DLLを呼び出すプログラムを作る
プロジェクト設定で.libを利用しないのがポイント。
あたしゃ動的リンクしたいからね。
当たり前な気がしますが,エンジン部分と実装部分を分離できるのも良いですね。
プロジェクト設定
DLLとそれを動的リンクするプログラムを用意しましょう。
DLLと動的リンクのプログラムは別のソリューションで用意しました。
- DLL用プログラム
Bodyと命名しました。
「構成の種類」を「ダイナミック ライブラリ (.dll)」にするのをお忘れなく。 - 動的リンク用プログラム
Engineと命名しました。
最終的にエンジンを開発したいからね。
こだわりが無ければビルドはx64がおススメ。
DLL: プログラム
IBody.h/Body.h/Body.cppを実装します。
エクスポート関数以外はお好きに。
IBody.h
#ifndef MYLIB_IBODY_H #define MYLIB_IBODY_H // インタフェース namespace mylib { class IBody { public: IBody() {} virtual ~IBody() {} virtual int Expr(const int num) const = 0; }; } #endif // MYLIB_IBODY_H
Body.h
#ifndef MYLIB_BODY_H #define MYLIB_BODY_H // 実装 #include "IBody.h" namespace mylib { class Body final : public IBody { public: Body(); ~Body(); int Expr(const int num) const override; }; } extern "C" __declspec(dllexport) mylib::IBody * GetBodyInstance(); #endif // MYLIB_BODY_H
Body.cpp
#include "Body.h" #include <memory> namespace mylib { Body::Body() { } Body::~Body() { } int Body::Expr(const int num) const { return num * 2; } } mylib::IBody* GetBodyInstance() { return new mylib::Body{}; }
クラスをエクスポートして使用したかったけど,よく分からないから一般的な方式にしました。
名前マングリングはdumpbin /exports *.dll
などで確認できます。
また,リロードするときにクラスサイズをチェックする必要がある点に注意。
その管理が面倒なのでインタフェースで誤魔化している。
追記: よい記事を見つけました。
ダウンキャストと似た印象を受けました。
qiita.com
www2s.biglobe.ne.jp
再追記: エクスポート関数のファイルも分けると良いですね。
エンジン: プログラム
動的リンクするプログラムを書きます。
そんなに難しくないはず。
#define WIN32_LEAN_AND_MEAN #include <windows.h> #undef WIN32_LEAN_AND_MEAN #include <iostream> #include <thread> // ↓ここ人によるから気を付けてね #include "../Solution1/Body/IBody.h" int LoadLib() { // DLLロード HMODULE hModule = LoadLibrary(L"Body.dll"); if (hModule == nullptr) { return -1; } // GetLastError()でエラー確認できる // アドレス取得 using type = mylib::IBody*(*)(); type func = reinterpret_cast<type>(GetProcAddress(hModule, "GetBodyInstance")); if (func == nullptr) { return -1; } // GetLastError()でエラー確認できる // 呼び出し: インタフェースを呼び出すことに注意 mylib::IBody* const pBody = func(); if (pBody == nullptr) { return -1; } const int num = pBody->Expr(100); delete pBody; // DLLアンロード if (FreeLibrary(hModule) == FALSE) { return -1; } return num; } int main() { // 1回目 std::cout << LoadLib() << std::endl; // Body.dllを差し替える // - ビルドするプログラムを書くのが良いけど,面倒なので30秒以内に手動で書き換える。 std::this_thread::sleep_for(std::chrono::milliseconds(30000)); // 2回目 std::cout << LoadLib() << std::endl; return 0; }
エラー時の処理は適当です。
実行するときはDLL用プログラムで作成したBody.dllをバイナリのあるフォルダにコピーしましょう。
本当はBody.dllをビルドして差し替えるプログラムを書けると良かったのですが,気力と能力が無かったので30秒以内に手動で差し替える方針でお茶を濁しました。
Body::Expr()の計算を変えて確かめてみるとリロード出来ていることが分かると思います。
あと,シングルトンなどを実装するときは色々な管理に注意が必要な気がしました。
まとめ
ホットリロードの基礎的なプログラムを実装してみました。
ゲームエンジンなどに実装されているホットリロードはもっと複雑なことをやっていると思いますが,雰囲気は掴めたのでとりあえずはヨシッ!!