エントリーポイントの隠蔽
はじめに
C/C++(Windows)
でプログラムが実行されると、エントリーポイントと呼ばれる関数から実行されます。コンソールであればmain()
ウィンドウアプリならWinMain()
が呼ばれます。サンプルプログラムではおなじみの関数ですが、世に出ているライブラリやゲームエンジンではユーザー側からは見ることはなく、ライブラリ内に隠蔽されています。なんで隠蔽するんでしょうね?
int main(int argc, char** argv, char** envp)
{
//...
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
//...
}
UnrealEngine
UnrealではLaunchWindows.cpp
にWinMain()
が確認できます。しかし、ユーザーがゲームロジックとしての開始位置を意識すればいいのはBeginPlay
イベントです。
int32 WINAPI WinMain(_In_ HINSTANCE hInInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ char* pCmdLine, _In_ int32 nCmdShow)
{
int32 Result = LaunchWindowsStartup(hInInstance, hPrevInstance, pCmdLine, nCmdShow, nullptr);
LaunchWindowsShutdown();
return Result;
}
Unity
Unity
はよくわからないですが、ユーザーがプログラムの開始位置を意識する必要はないです。
C#スクリプトのStart()
がUnrealのBeginPlay
イベントにあたります
public class Player : MonoBehaviour
{
void Start()
{
}
}
明確な理由がわからない
エントリーポイントを隠蔽する明確な理由はわかりませんでした。エンジンやライブラリの役割(開発の効率化・カプセル化)を考えれば必然という感じはします。ただし、それらの理由抜きにした「隠蔽しなければいけない」となると理由はわかりません...
https://qiita.com/lriki/items/3385457986934d3ec694
エントリポイント(main 関数)をゲームエンジンの内部に隠蔽するか、しないか。
と言っても Web をサポートするなら実質隠蔽するしかない。
この場合ランタイムに、初期化時、フレーム更新時、などのコールバックを登録することににる。
実装
参考
Application
(ライブラリ)と Editor
(アプリケーション)に分離させ、ライブラリ側にエントリーポイントを定義します。つまり、Application
のプロジェクトはスタティックライブラリ
、Editor
のプロジェクトはアプリケーション
に設定することを想定します。
Application(スタティックライブラリ)
class Application
{
public:
Application(const std::string& appName);
void Run();
template<class AppClass>
static void Entry(const std::string& appName);
private:
std::string m_AppName;
}
template<class AppClass>
void Application::Entry(const std::string& appName)
{
Application* app = static_cast<Application*>(new AppClass(appName));
app->Run();
delete app;
}
#define APPLICATION_ENTRY(AppClass, AppName) \
int32 main(int32 argc, char** argv, char** evnp) \
{ \
Application::Entry<AppClass>(AppName); \
return 0; \
}
Editor(アプリケーション)
#include <Application.h>
// エントリーポイントの定義
APPLICATION_ENTRY(Editor, "Editor");
// エントリークラス
class Editor : public Application
{
public:
Editor(const std::string& appName) : Application(appName) {}
~Editor() {}
};
ひとことで言ってしまえば、main
(WinMain
)をマクロ化したものをライブラリに定義し、アプリケーション側でマクロを呼び出しているだけです。ライブラリにエントリーポイントは必要ありませんが、アプリケーションはエントリーポイントの定義が必要です。そのため、アプリケーションであるEditor
ではこのような対応をとっています。
#define APPLICATION_ENTRY(AppClass, AppName)
APPLICATION_ENTRY(Editor, "Editor")
APPLICATION_ENTRY(Game, "Game")
Appclass
にはエントリーポイントとなるクラスEditor
を、AppName
にはアプリケーションの名前となる文字列(キャプションバーに表示されるもの)を指定します。エントリーポイント関数が呼ばれてAppclassに指定したクラス(Applicationクラスを継承)がインスタンス化されます。ビルド時にエントリークラスを切り替えることで、Editorビルド・Gameビルドといったような変更が可能になります。
まとめ
いろいろ話しましたが、結局「なんでエントリーポイントを隠蔽するのか?」という疑問はわかりませんでした。正直なところ、エントリーポイントの隠蔽がカッコいいと思って実装していた節があります。理由がまだわからないので開発の効率化・カプセル化なんだろうなと思うようにしています。
Discussion