• 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模块了。

  • 相关阅读:
    电力项目七--js控制文字内容过长的显示和文本字数的显示
    电力项目六--运行监控设计
    Maven开发系统
    SSH电力项目一 搭建Hibernate框架
    网站添加用户风险测评
    tomcat中文配置
    Tomcat映射路径的配置方法
    java中计算时间差
    NSTimer+倒计时功能实现
    设置UITextView光标从起始位置开始
  • 原文地址:https://www.cnblogs.com/wellbye/p/5837108.html
Copyright © 2020-2023  润新知