ue4 模塊的構建和加載


ue4的代碼是模塊的形式來組織

在源碼層面,一個包含*.build.cs的目錄就是一個模塊

這個目錄里的文件在編譯后都會被鏈接在一起,比如一個靜態庫lib,或者一個動態庫dll。

不管是哪種形式,都需要提供一個給外部操作的接口,也就是一個IModuleInterface指針。

*注意這里並不是說調用模塊內任何函數(或類)都要通過該指針來進行,實際上外部代碼只要include了相應的頭文件,就能直接調用對應的功能了(比如new一個類,調一個全局函數等),因為實現代碼要么做為lib被鏈接進exe,或是做為dll被動態加載了。

這個IModuleInterface指針是用來操作做為整體的模塊本身的,比如說模塊的加載、初始化和卸載,以及訪問模塊內的一些全局變量(向下轉成具體的模塊類型后)

 

外部獲取這個指針,只有一個辦法,就是通過FModuleManager上的LoadModule/GetModule。

而FModuleManager去哪里找需要的模塊呢?

1、在動態鏈接情況下,根據名字直接找對應的dll就行了,做為合法ue4模塊的dll,必定要導出一些約定的函數,來返回自身IModuleInterface指針。

2、在靜態鏈接時,去一個叫StaticallyLinkedModuleInitializers的Map里找,這就要求所有模塊已把自己注冊到這個Map里。

以上2點基本就是FModuleManager::LoadModule的內容。

 

而每個模塊為滿足以上約定,也需要插入一些例程代碼,這就是IMPLEMENT_MODULE干的事。

在靜態鏈接時:

    // If we're linking monolithically we assume all modules are linked in with the main binary.
    #define IMPLEMENT_MODULE( ModuleImplClass, ModuleName ) \
        /** Global registrant object for this module when linked statically */ \
        static FStaticallyLinkedModuleRegistrant< ModuleImplClass > ModuleRegistrant##ModuleName( #ModuleName ); \
        /** Implement an empty function so that if this module is built as a statically linked lib, */ \
        /** static initialization for this lib can be forced by referencing this symbol */ \
        void EmptyLinkFunctionForStaticInitialization##ModuleName(){} \
        PER_MODULE_BOILERPLATE_ANYLINK(ModuleImplClass, ModuleName)
template< class ModuleClass >
class FStaticallyLinkedModuleRegistrant
{
public:

    FStaticallyLinkedModuleRegistrant( const ANSICHAR* InModuleName )
    {
        // Create a delegate to our InitializeModule method
        FModuleManager::FInitializeStaticallyLinkedModule InitializerDelegate = FModuleManager::FInitializeStaticallyLinkedModule::CreateRaw(
                this, &FStaticallyLinkedModuleRegistrant<ModuleClass>::InitializeModule );

        // Register this module
        FModuleManager::Get().RegisterStaticallyLinkedModule(
            FName( InModuleName ),    // Module name
            InitializerDelegate );    // Initializer delegate
    }
     
    IModuleInterface* InitializeModule( )
    {
        return new ModuleClass();
    }
};

FStaticallyLinkedModuleRegistrant是一個注冊輔助類,它利用全局變量的構造函數被crt自動調用的特性,實現自動注冊邏輯。

它在構造函數里把一個“注冊器”添到前面說的StaticallyLinkedModuleInitializers里,而這個注冊器的內容就是創建並返回相應模塊。

 

在動態鏈接時:

    #define IMPLEMENT_MODULE( ModuleImplClass, ModuleName ) \
        \
        /**/ \
        /* InitializeModule function, called by module manager after this module's DLL has been loaded */ \
        /**/ \
        /* @return    Returns an instance of this module */ \
        /**/ \
        extern "C" DLLEXPORT IModuleInterface* InitializeModule() \
        { \
            return new ModuleImplClass(); \
        } \
        PER_MODULE_BOILERPLATE \
        PER_MODULE_BOILERPLATE_ANYLINK(ModuleImplClass, ModuleName)

聲明了一個dllexport函數,功能就是創建並返回相應模塊。

 

實際的應用:

1、如果模塊本身實在沒什么特別的,那么就:

IMPLEMENT_MODULE(FDefaultModuleImpl, ModuleName)

FDefaultModuleImpl是一個IModuleInterface的空實現,什么都沒干,ModuleName則是模塊名字,很重要,其它地方要加載模塊,就靠這個名字了。

 

2、如果模塊有自己的初始化邏輯,那么應該實現自己的IModuleInterface子類,比如說MyUtilModule,然后:

IMPLEMENT_MODULE(MyUtilModule,MyUtil)

這里類名帶Module后綴,而模塊名不帶,是ue4的慣例。

 

3、如果模塊是一個包含游戲邏輯的模塊(相對於通用功能型模塊),那么可以用一個特殊的宏IMPLEMENT_GAME_MODULE,不過目前看來它和前者沒啥差別,可能未來有所擴展

4、更特別的是,如果模塊是表示當前游戲項目的“主模塊”,那么應該用一個更特殊的宏IMPLEMENT_PRIMARY_GAME_MODULE,而且在構建一個UEBuildGame類型的Target時,必須有一個這樣的模塊。

當IS_MONOLITHIC,構建成單個exe時:

#define IMPLEMENT_PRIMARY_GAME_MODULE( ModuleImplClass, ModuleName, DEPRECATED_GameName ) \
            /* For monolithic builds, we must statically define the game's name string (See Core.h) */ \
            TCHAR GInternalGameName[64] = TEXT( PREPROCESSOR_TO_STRING(UE_PROJECT_NAME) ); \
            /* Implement the GIsGameAgnosticExe variable (See Core.h). */ \
            bool GIsGameAgnosticExe = false; \
            IMPLEMENT_DEBUGGAME() \
            IMPLEMENT_FOREIGN_ENGINE_DIR() \
            IMPLEMENT_GAME_MODULE( ModuleImplClass, ModuleName ) \
            PER_MODULE_BOILERPLATE \
            void UELinkerFixupCheat() \
            { \
                extern void UELinkerFixups(); \
                UELinkerFixups(); \
            }

非單體構建時:

#define IMPLEMENT_PRIMARY_GAME_MODULE( ModuleImplClass, ModuleName, GameName ) \
        /* Nothing special to do for modular builds.  The game name will be set via the command-line */ \
        IMPLEMENT_GAME_MODULE( ModuleImplClass, ModuleName )

 

統一來看,其實也就比普通模塊多做了三件事,一是設置游戲名字GInternalGameName,二是設置了GIsGameAgnosticExe=false,三是多了個UELinkerFixupCheat暫且不究。

GIsGameAgnosticExe是一個有趣的變量,它表示當前這個exe是特定於某游戲的?還是一個通用的加載器?

如果整體編成一個exe文件,即IS_MONOLITHIC,那肯定是特定於某游戲,這時游戲名GInternalGameName必定是已知固定的,所以以宏參數的形式直接硬編碼在exe里了。

與之相反,當使用模塊化構建時(通過給ubt傳入-modular參數),將會生成一個exe和一堆dll,並且能加載任何其它以dll形式存的游戲模塊,游戲名是未知的,是通過命令行參數來決定要加載哪一個游戲,所以也就用不着GInternalGameName變量了。

 

5、當構建一個工具程序(TargetType.Program)時,可以使用專門定制的IMPLEMENT_APPLICATION

最特別的是它竟然提供了一個FEngineLoop GEngineLoop,而這個變量存在於一般的Game/Editor構建時都會鏈接的【Launch模塊】中。

不過這也正常,因為在工具程序中一般是要自己寫main函數的,所以就用不着重量級的Launch模塊了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM