ue4 模块的构建和加载

Posted 冷欺花

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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模块了。

 

以上是关于ue4 模块的构建和加载的主要内容,如果未能解决你的问题,请参考以下文章

[工作积累] UE4 并行渲染的同步 - Sync between FParallelCommandListSet & FRHICommandListImmediate calls(代码片段

UE4学习笔记:在项目和插件里创建新C++模块的方法

UE4 总结十四

虚幻引擎UE4加载GIS数据《数字孪生&智慧城市》

UE4 源码阅读:从引擎启动到Receive Begin Play

UE4 异步资源加载