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

时间: 2024-10-10 17:57:15

ue4 模块的构建和加载的相关文章

浏览器渲染页面和加载页面机制

为什么有些网站打开的时候会加载会很慢,而且是整个页面同时显示的,而有些网站是从顶到下逐步显示出来的?要搞懂这个可以先从下面这个常规流程开始: 1. 浏览器下载的顺序是从上到下,渲染的顺序也是从上到下,下载和渲染是同时进行的. 2. 在渲染到页面的某一部分时,其上面的所有部分都已经下载完成(并不是说所有相关联的元素都已经下载完). 3. 如果遇到语义解释性的标签嵌入文件(JS脚本,CSS样式),那么此时IE的下载过程会启用单独连接进行下载. 4. 并且在下载后进行解析,解析过程中,停止页面所有往下

用ASDF来组织Lisp程序编译和加载

一.建立示例程序hello world 1.编写asdf 文件hello.asd [plain] view plaincopy <span style="font-size:18px;">(defpackage :hello-system (:use #:asdf #:cl)) (in-package :hello-system) (defsystem hello :name "hello world" :version "0.1"

Linux驱动模块生成和加载分析

Linux驱动模块生成和加载分析 0x00 Hello World 先奉上本文需要分析的例子,这里以Hello World程序作为例子来分析吧: hello.c #include <linux/init.h> #include <linux/kernel.h> int __init hello_init(void) { printk(KERN_INFO "Hello world!\n"); return 0; } void __exit hello_exit(v

《python解释器源码剖析》第15章--python模块的动态加载机制

15.0 序 在之前的章节中,我们考察的东西都是局限在一个模块(在python中就是module)内.然而现实中,程序不可能只有一个模块,更多情况下一个程序会有多个模块,而模块之间存在着引用和交互,这些引用和交互也是程序的一个重要的组成部分.本章剖析的就是在python中,一个模块是如何加载.并引用另一个模块的功能的.对于一个模块,肯定要先从硬盘加载到内存. 15.1 import前奏曲 我们以一个简单的import为序幕 # a.py import sys 1 0 LOAD_CONST 0 (

Maven环境下多模块项目构建

Maven环境下多模块项目构建 一.新建项目 1.建立我们的父模块par 2.建立我们的子模块dao层 3.建立我们的子模块service层 4.建立我们的子模块web层 5.全部配置完成后,怎么把我们的四个项目关联起来 1)添加一个dao层 2)service里面添加对应的dao依赖 3)然后回到我们的web 4)把这4个项目安装到本地 选择Run 的第二个,然后输入:clean compile install 5)配置一个tomcat插件,为执行做准备

u-boot中链接地址和加载地址的相关知识

以zc702开发板的u-boot为例 链接地址(运行地址):链接地址是在程序编译链接阶段就确定好的地址. u-boot的链接脚本由CONFIG_SYS_LDSCRIPT宏定义来指定,如在zynq_common.h当中有如下代码: #define CONFIG_SYS_LDSCRIPT "arch/arm/cpu/armv7/zynq/u-boot.lds" 在该链接脚本中指定了u-boot中各部分的链接顺序.同时zynq_common.h中的CONFIG_SYS_TEXT_BASE则指

ARM指令集中常用的存储和加载指令

ARM微处理器支持加载/存储指令用于在寄存器和存储器之间传送数据,加载指令用于将存储器中的数据传送到寄存器,存储指令则完成相反的操作.常用的加载存储指令如下: -  LDR     字数据加载指令 -       LDRB    字节数据加载指令 -  LDRH    半字数据加载指令 -  STR     字数据存储指令 -       STRB    字节数据存储指令 -  STRH    半字数据存储指令 1.LDR指令 LDR指令的格式为: LDR{条件} 目的寄存器,<存储器地址>

源码跟读,Spring是如何解析和加载xml中配置的beans

Spring版本基于: 跟踪代码源码基于: https://github.com/deng-cc/KeepLearning commit id:c009ce47bd19e1faf9e07f12086cd440b7799a63 1.配置启动Spring所需的监听器 web.xml中配置监听器 <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-cla

关于C# 窗体自动隐藏和加载的问题

最近在写一个小项目,开发一个小程序配合其他软件使用,其中一款软件在使用工作时需要截图生成报告,此时不能有其他应用程式界面在显示器桌面显示,故需要自动隐藏和加载窗体,通过阅读Windows API实现了这一功能与大伙分享交流一下:原本C#自带Hide()和Show()方法:但是在项目开发时将窗体定义为静态类型:故无法通过this关键字来调用其对应的隐藏和显示窗体方法.这里通过Windows API提供的函数来实现,首先要获取当前运行窗体的句柄,方法如下: 定义一个全局变量 IntPtr Handl