UE4 Modules 和 PCH
本文参考:
https://zhuanlan.zhihu.com/p/107270501
UE4模块的加载方式
待补充
Modules
模块是什么?一堆dll 类的集合,UE4被分为了1000个模块
有什么作用?组织代码,可移植性复用性,可以获得更快的编译和链接性能,也可以确认模块何时加载
如何做一个Module? B U I L D
Build
Build,建立一个固定的目录
[YourModuleName].Build.cs文件的作用用
描述如何编译模块、模块的依赖等等
模块的编译只取决于.Target.cs和.Build.cs而不取决于sln
一个最少代码的模块包括:
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715152905023-310614065.png)
和模块名相同的C#类
构造函数中处理模块的依赖性,必须依赖Core,里面包括了很多模块相关代码
Use
use 使用模块
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715153250109-904268946.png)
模块的代码并不是默认对其他的模块暴露的
你需要队每个函数或者类进行显式的指定他是否导出
如果你不打算暴露任何代码到模块外,你可以不用分private和public的文件夹
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715154452993-1360602367.png)
这些代码在蓝图中可以访问,但是在其他模块中不能使用
通过给UCLASS添加MinimalAPI(最小)标识来表示将其显式导出到可以被其他模块使用
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715154521119-1159629252.png)
要将某个函数暴露给其他模块使用必须在函数前添加[YourModuleName]_API,全部大写字母
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715154638855-1826327887.png)
要将整个类暴露就在类前加[YourModuleName]_API,全部大写字母,这样整个类的public部分都会暴露
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715154954879-267899596.png)
由于这个类依赖了Actor类,所以我们在模块的编译时候会报错
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715155146026-585385227.png)
要用的话,就需要添加头文件、添加模块依赖关系
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715160054098-1123150564.png)
这里在publicDependency进行一个添加,因为其他的模块要是引用这个NickNameActor他自然也会需要对Actor有依赖
关于private 和 public dependency的问题:
当这个关系只有一层时没有任何区别
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715160530037-1801511530.png)
当涉及到三个模块时,这里的依赖的public private就会起作用
头文件内容传递,表示这个模块可以调用其他模块全部定义在在头文件中的内容,如果有内容是 .cpp中定义,则会有无法解析的外部符号错误(就是将头文件一起编译,但是不会做链接,对应IncludePathModuleNames,现在基本已经弃用了)
把定义写在头文件中意味着他可能会被多次编译,将拖慢编译速度
DependencyModuleNames,则是将两个模块链接,则表示这个模块可以调用到.cpp中定义的内容
private会切断头文件路径和链接的传递,你的模块被其他的模块依赖时这些引入的头的链接都不会传递
非常重要的依赖传递图
![img](https://pic2.zhimg.com/80/v2-687da9029bb7ae3d61b116592a5547b9_1440w.jpg)
当你的模块被其他的模块引用的时候,情况很多,上图基本包括了所有情况
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715165154471-765512604.png)
你private 引用了别的module,引用了你module的module尝试找你的子模块的代码的时候就会出现文件找不到的错误
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715165442246-785314136.png)
总结选择:建议使用private链接,他们会减少编译时间!
Implement
Implement,实现模块
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715170153525-712907545.png)
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715170944899-1484632028.png)
#include "Modules/ModuleManager.h" // which is in core module
IMPLEMENT_MODULE(FPlatformCommonModule, PlatformCommon)
这句话可以在你的模块的任何地方写,这句调用就是将你的模块的主类给暴露给引擎
实现这个模块的类必须继承这个IModuleInterface的接口,一般来说做个空类就好了可以使用UE自带FDefaultModuleImpl类
有关module的接口有很多,最基本的就加载和卸载module的生命周期
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715172400182-960866295.png)
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715172558028-1727672677.png)
有关很少会用到的GameplayModule,游戏工程本身也是一个模块,一般所有的模块都是用给这个游戏模块的,很少会反过来依赖游戏模块
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715183512254-1006018950.png)
Load
LoadModule
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715185543236-193307532.png)
需要选择自己的module是在什么时候被加载的和面向什么target,通过选择Type,最常用的是Runtime和Editor
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715192451680-147866216.png)
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715192619839-656211390.png)
Depend
depend
只有在依赖链上的模块才会被编译
通过添加DependencyModuleNames来配置依赖,如果没有模块依赖你的模块,也可以添加Target.cs
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715195521085-499436060.png)
PCH
Prcompiled Header
一般的头文件不会自己编译,而是在cpp引入的时候编译
这样的话就有一个很大的头文件重复编译量
让头文件只编译一次
PCH定义了一个头文件,包含了所有最常用的头文件,然后他们会先于所有的文件编译,他们不会重编,除非他们引入的头文件发生了改变
必须注意的是,如果PCH重编了,所有的这个模块的CPP文件都会重编,最好是用于引擎的头文件或者非常少改动的头文件
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715195203838-1670958574.png)
有几种PCH,
private PCH
构建你模块自己的PCH,把他定义在Build.cs文件里
不应该在任何头文件或者CPP文件中引入PCH文件,他们会由UBT工具自动注入到你的模块里
PCH是一种优化层面的东西
你的代码写法应该是就算PCH关掉了也能正常跑
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715195222162-186033460.png)
shared PCH
除了自己定义PCH之外也可使用别人定义的PCH,模块自己定义的SharePCH不能给自己用,只能给依赖了这个模块的模块使用
只有引擎模块才能创建Share PCH
Private PCH 和 Shared PCH又是什么?有啥用?
答:它俩是特殊一点的PCH,只需记住几(×)1(√)点,
Private PCH是给本模块用的PCH,Shared PCH是给依赖本模块的模块用的PCH
一个模块可以使用其他模块定义的Shared PCH
一个模块不能使用自己定义的Shared PCH
模块对PCH的选择只有三个:
-使用本模块定义的Private PCH
-使用引擎自动从**本模块依赖的模块**定义的Shared PCH中,选出一个最佳PCH
-不使用PCH
两者需要在模块的build.cs文件中手动指定
UE会自动选择最佳的SharePCH以便尽可能多的覆盖本模块用到的头文件
Shared PCH的选择范围是本模块依赖的模块中定义的Shared PCH
假如模块A依赖Slate,Core以及CoreUOject模块,且没定义Private PCH,
且只有Slate及Core模块中定义了Shared PCH,
那么,A的Shared PCH就只能从Slate, Core两个模块中选
选哪一个呢?
基于得分来选择——以它俩依赖的<定义了Shared PCH的>模块数量来作为它们的分数
假设Core模块依赖了TraceLog, Json,假如只有TraceLog模块中定义了Shared PCH,
那么它得1分
假设Slate模块依赖了Core, SourceControl, Json模块,假如Json模块中定义了Shared PCH,
那么它得2分(Core 1 + Json 1)
而且很容易看出,由于Slate的依赖项里包含了Core,那么它Shared PCH很可能包含了大部分Core模块里的头文件,以及其它额外的头文件,这样,我们就会理所当然的选择使用Slate的Shared PCH,因为我们的模块使用到的头文件,也在Slate的Shared PCH里的概率更大。
因此我们将会选择Slate模块的Shared PCH作为本模块的PCH。
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715201620900-291287567.png)
PCH使用模式只选择UseExplicitOrSharePCHs,这个模式下,你没设置PCH就会使用引擎PCH,你设置了PrivatePCH就可以使用
![](https://img2020.cnblogs.com/blog/1728741/202107/1728741-20210715202753915-1607664568.png)