前言
- 文章之后会修改.
最近一些文章互相关联性较高, 可能随着某篇文章某些内容的深扒, 而导致一些内容更连贯, 并对所有相关内容进行修改 - 源码版本4.22
孤傲雕:UE4 UObject反射系列(一) Class相关
孤傲雕:UE4 UObject反射系列(二) Class相关
孤傲雕:UE4 UObject反射系列(三) Class相关
孤傲雕:UE4 UObject反射系列(四) Class相关
这是UObject系列的第一个知识点, UE4反射信息相关
主题内容是对UBT(Unreal Build Tool)生成的.h文件和.cpp文件内容分析
UObject是一个功能强大的类, 有很多功能, 为什么要从反射开始呢?
大钊:《InsideUE4》UObject(一)开篇
大钊:《InsideUE4》UObject(四)类型系统代码生成
参考大佬文章, 感觉解释的很清楚了, UE4反射功能是一块很强大的的基石, 很多功能都是在此基础上进一步搭建的 :
- GC
- 网络同步
- 序列化
- 编辑器界面
- ...
GuAoDiao/GUAO22
内容较多且繁琐, 偏操作实践, 请根据文章一步一步跟做, 或者直接下载上面git项目进行对比分析
不保证教会, 但保证听懂.
// 那如果听不懂呢? 额, 好像, 也很正常(手动滑稽)
步骤一 建立基础项目和待分析文件
- 新建一个项目, 项目名称保证短小(此处是GUAO22)
// 如有自信, 可以命名其他名称, 如无, 请相同
- 建立一个新的UObject子类, 名称尽量短小(此处是GAObject01), 并带有序号
![c507b992a6fe31e4938d09dfcf39c092.png](https://i-blog.csdnimg.cn/blog_migrate/40ec9b72a3f238585d4cfa3c6f810809.jpeg)
- 重新生成并打开VS项目, 不要编译
引擎设置里面可以关掉添加新C++类自动编译, 然后按下图重新生成VS项目并打开
![485df7bb43e54c7c6371ffdb6f26e728.png](https://i-blog.csdnimg.cn/blog_migrate/1fdfdd2c7d1f255e42a37697dc5ab92a.png)
![5f555ca43aa6b63c5f98993d98011edb.png](https://i-blog.csdnimg.cn/blog_migrate/4020660c7e4a20168355d36c97f7ceb9.png)
// 编译了也无所谓, 默认是会自动编译的
步骤二 分析空类
![1c98eb0a6b8a89a4d1eb02b864994452.png](https://i-blog.csdnimg.cn/blog_migrate/a582c2ab2e32987017c8cbbb6f14bc90.png)
![794bf3791a686f376e0fde7afa9986af.png](https://i-blog.csdnimg.cn/blog_migrate/978921d5c9872e11ec066fd2925546e5.png)
如果没有编译过, 这里会有两个报错提示, 无法找到头文件和这个宏定义.
这些是通过UHT动态生成的, 下文主要就是对这些自动生成内容的分析.
![66d23a25cae9c980839db813aeeb92a7.png](https://i-blog.csdnimg.cn/blog_migrate/a0a840da7b8025b80df3c08e5647f4da.png)
首先, 我们可以看到这是一个空的UObject类, 这是第一个分析的文件, 他没有任何实现, 然后我们生成项目
![1a5e7aec85ec127ec58614f4d04eae78.png](https://i-blog.csdnimg.cn/blog_migrate/614bb68610d65ed7d3a4444e9af5e4dc.png)
然后我们就可以打开这个文件了, 并打开文件所在目录, 可以看到对应的.cpp文件
![a8fb79a922aebcf0d7bfff99539d6fc2.png](https://i-blog.csdnimg.cn/blog_migrate/d3f82b3c4f393fe2f677ebe9550ff092.png)
![0914b6929adb53cf2c0d46c05cefce0b.png](https://i-blog.csdnimg.cn/blog_migrate/968f2b2dee88fa3f28c1e02643c2cfe0.png)
首先是UCLASS()宏
![0b6c8b5d327db0ad97f08c59acae46db.png](https://i-blog.csdnimg.cn/blog_migrate/f88b3f85a2997343c38fe1579b25f52c.png)
空定义, 忽略
步骤二 第一小节
GENERATED_BODY宏, 重点来了
![a8dbf3990fbf2144e221f43d54697d5b.png](https://i-blog.csdnimg.cn/blog_migrate/5b3f3855cce0def55e3add9405e0ffc9.jpeg)
![88a34ef1668888264b777211368db981.png](https://i-blog.csdnimg.cn/blog_migrate/7882eb17cb4a3e882a709301c66e8379.jpeg)
GENERATED_BODY一步一步展开级可得到, 同理可得
// 推荐和我一样, 新建两个个.h文件, 按照步骤一步一步展开
![0a0643984cce3b77c5fa7ffab25e9f2e.png](https://i-blog.csdnimg.cn/blog_migrate/4f0df88586d852c4f71a99554401b410.jpeg)
- GENERATED_BODY
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_GENERATED_BODY
- GENERATED_UCLASS_BODY
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_GENERATED_BODY_LEGACY
其中有两个会变化的量
- __LINE__
当前行数, 此处为15
用于区分同一个文件中的不同类, 使得一个.h文件可以同时创建多个不同的UClass宏, 并一一对应 - CURRENT_FILE_ID
GUAO22_Source_GUAO22_Public_GAObject01_h
存在于generated.h, 是固定格式, 根据文件不同来区分.
D:Unreal ProjectsGUAO22SourceGUAO22PublicGAObject01.h
根据上面文件路径, 可以发现是一一对应的.
两者配合使用, 就可以时得最后生成的宏在文件或者所在行数不同就不同
从而使得, 每一个GENERATED_BODY或GENERATED_UCLASS_BODY生成的宏都是唯一的, 可以与CLASS一一对应.
步骤二 第二小节(一)
然后, 我们先看GENERATED_BODY系列, 忽略掉GENERATED_UCLASS_BODY系列, 稍后再看
在generated.h搜索得出
![e75e8ea6c825423298ebcc4ee31dce8f.png](https://i-blog.csdnimg.cn/blog_migrate/70c9f4eb116b0593af0e0c1d10b8a93b.jpeg)
该宏是由多个宏组成的, 忽略掉PRAGMA_DISABLE_DEPRECATION_WARNINGS这两个宏, 也就变成了
![db15948aca3465e8906c78947196a9eb.png](https://i-blog.csdnimg.cn/blog_migrate/85ced2dfca061d72bd1f6461f64d9ebb.png)
步骤二 第三小节(一)
我们按照顺序来看这四个宏
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_PRIVATE_PROPERTY_OFFSET
![8c994d6dc48afc5ace9cd51dad49597d.png](https://i-blog.csdnimg.cn/blog_migrate/6183012c838dd238f35a2623b942baa1.png)
字面翻译, 私有属性偏移, 空值忽略
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_RPC_WRAPPERS_NO_PURE_DECLS
![f2c8c008652da83f476967bcccb46c3a.png](https://i-blog.csdnimg.cn/blog_migrate/ab9f1fe6ea9b00a919789dcdf546a203.png)
__BEGIN_DECLS 和__END_DECLS
包装RPC不是纯函数的DECLS??, 此处为空, 忽略
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_INCLASS_NO_PURE_DECLS
![922c6e9eefee2db4062c0c3dc7da102d.png](https://i-blog.csdnimg.cn/blog_migrate/ed290b40b903fe4f593611bb7b3c29c9.png)
在类内的不是纯函数的DECLS??
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_ENHANCED_CONSTRUCTORS
![c033fe86eab2a91408d76daede9708da.png](https://i-blog.csdnimg.cn/blog_migrate/496ce1cc3471fea7c43df361ca1f7d81.jpeg)
增强的构造函数及相关的内容
然后, 我们替换得出了(注意替换时public和private的先后位置)
![615ab5c410ce793fe9c33a4a4c53ff27.png](https://i-blog.csdnimg.cn/blog_migrate/3860ec6bb8d628b830d7201cabcb0eaf.jpeg)
步骤二 第二小节(二)
我们再转过头来看GENERATED_UCLASS_BODY相关
![d5706c3480da3e40f2cad09323532b3f.png](https://i-blog.csdnimg.cn/blog_migrate/1377dd63b14b473116783c2fe6623815.jpeg)
可以得出
![299c8351bd1070ad934322d36948b48c.png](https://i-blog.csdnimg.cn/blog_migrate/57ad9d0c6cb26b01983bf5feacbba139.png)
步骤二 第三小节(二)
同理, 我们按照顺序来看这四个宏
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_PRIVATE_PROPERTY_OFFSET
![28cdb12d660209e432e49e5613a53e1e.png](https://i-blog.csdnimg.cn/blog_migrate/c00e6c29d766574e08b21d51e3e598bf.jpeg)
这个宏和上面那个是同一个宏, 为空, 属于公有部分
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_RPC_WRAPPERS
![c23753e0612988ed5ef55437c5fdf399.png](https://i-blog.csdnimg.cn/blog_migrate/81f988f9fd1c35a0208936987bd36fe4.png)
和前文一样, 此处为空
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_INCLASS
![cf66ac8b6955cd07a84d0ab0ddff3988.png](https://i-blog.csdnimg.cn/blog_migrate/692ce650817b126d7816d624b155ef8d.jpeg)
此处空类的情况下, 两者代码一致
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_STANDARD_CONSTRUCTORS
![3fdb4111fc389ef5cdbb2e6a30fd6a4a.png](https://i-blog.csdnimg.cn/blog_migrate/10eec7f1b8044f6ae002f12af45352e7.jpeg)
这个宏是标准的构造函数内容
和之前的宏做区分, 可以看到, 此时他们有很多内容是相同的, 只有一个构造函数不同
然后我们可以得出(为了顺序和前面的对应, 有一定微调, 但public和private一致, 无影响)
![8b4031e31a3ff13766c994a9318b7d9e.png](https://i-blog.csdnimg.cn/blog_migrate/542e3d2dc58f373c049f1939239a2ac9.jpeg)
步骤二 第三小节(三)
我们先停下来消化一些东西, 通过对比两个宏最后的展开, 目前来看, 区别只在构造函数上面
![4de34734ad548432d9065e56f99de980.png](https://i-blog.csdnimg.cn/blog_migrate/325aa76a107082668f0098e197546bba.jpeg)
// GENERATED_BODY
也就是说, 如果使用GENERATED_UCLASS_BODY宏, 需要人工创建并实现UGAObject01(const FObjectInitializer&)对应实现, 而GENERATED_BODY不需要
![f9c986fe4f753bc3ea8b3662fa64afe2.png](https://i-blog.csdnimg.cn/blog_migrate/edf3b4066d93f844b47b51e94c181817.png)
以及, 都会将拷贝和移动构造函数私有化, 阻止调用, 防止误操作.
以及, 接下来, 我们只需要展开分析GENERATED_BODY即可, 剩余部分, 两者是一致的
// 至少目前是一致的
步骤三
![e92dc3d94a16778564664ba62659714d.png](https://i-blog.csdnimg.cn/blog_migrate/e2a6b026381e74402f63523ee8e58a40.jpeg)
然后是对这些宏的展开, 有五个宏
- DECLARE_CLASS
![ae248eb392083a29ee36c1d1da9e5956.png](https://i-blog.csdnimg.cn/blog_migrate/2c53cce72b0177700d343021bf302aa5.jpeg)
![07482040888ac238e13d9767b2563834.png](https://i-blog.csdnimg.cn/blog_migrate/e4be952ccbf2623c56e2cf2172d884e4.jpeg)
宏替换可得, 如图
- DECLARE_SERIALIZER
![d581575e4c09faf4f5264a23ad208cc5.png](https://i-blog.csdnimg.cn/blog_migrate/6291091339c6cc6234d113a8ebf42967.png)
是对<<的运算符重载, 序列化相关是由FArchive及相关类实现的
![73dce44211ae9902a5e3106fd0b80fd9.png](https://i-blog.csdnimg.cn/blog_migrate/e90c58fdf279ef247f26d20f9aef050f.png)
- DECLARE_VTABLE_PTR_HELPER_CTOR
![c9d32c44ac1a3d2017ae7a70a948cdf0.png](https://i-blog.csdnimg.cn/blog_migrate/70d58ad006992705365be58d0210197b.png)
热重载相关的, 忽略
![121c1652f97d47b6f9fcee515c155659.png](https://i-blog.csdnimg.cn/blog_migrate/0898aa386a5cc954d03ce8b4d857a396.png)
- DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER
![6f6a3829d6147aa1a5381158d2f95f4a.png](https://i-blog.csdnimg.cn/blog_migrate/ea1e89995b823e02f0b450b3ef49f78f.png)
热重载相关的, 忽略
![077372c1d0842b9fb7df3e15fe21c296.png](https://i-blog.csdnimg.cn/blog_migrate/fcc60c3f12b84ccf5199ffc7cace9dc8.jpeg)
- DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL
![2d00b5355a659440c9979339f69f0232.png](https://i-blog.csdnimg.cn/blog_migrate/67124472697d3aacf20b8a61c339615b.png)
这是一个默认的构造函数
![64066ed2711c849e1d0c01f061fa62d9.png](https://i-blog.csdnimg.cn/blog_migrate/56ebbe9f04cf7e486f3314185cd5b97f.png)
最终替换所有展开
![179e3e13af46a0f08238477c3e8dcf72.png](https://i-blog.csdnimg.cn/blog_migrate/a24b572975cee13a95c244467f563f02.jpeg)
![14198300ac35b8d053842b64106f3134.png](https://i-blog.csdnimg.cn/blog_migrate/30c4dbd6bd9c1f0f98521549c64262ee.jpeg)
同理也可以得出GENERATED_UCLASS_BODY相关的, 到此为止, 这个头文件的推断就告一段落了.
步骤四
然后我们先停下来, 分析一些其他情况
情况一
![226ac993a815def80531710bd22dd829.png](https://i-blog.csdnimg.cn/blog_migrate/4087115cd9995ac42a30faff2cd662a6.png)
建立一个GENERATED_UCLASS_BODY()验证上面的推测, 别不同宏生成的东西差很多
![68711f1f9ae3bbc53b1e626819a1ef9d.png](https://i-blog.csdnimg.cn/blog_migrate/132f9dc35e4e6c628fda3a489cf02f2b.jpeg)
最后对比.generated.h发现两个头文件有区别在这里
当定义GENERATED_UCLASSBODY之后, 如果再定义GENERATED_BODY的时候会在编译的时候报错
情况二
![aea72db0a40af98aafa12ec70557e463.png](https://i-blog.csdnimg.cn/blog_migrate/b2970b27dd9947a08dbb1a42f443d384.png)
我们自己实现一个无参构造函数, 参考前文这里宏定义, 有两种默认构造器的情况, 前文指向后者
![18fe1aa235b611bb0a2a71cb761a0cdd.png](https://i-blog.csdnimg.cn/blog_migrate/13cf339c764ea9959c80db2a8643d999.png)
![fa373a3e02376abdc63fa95adec3119f.png](https://i-blog.csdnimg.cn/blog_migrate/24d3295c50d7858d644ff34a9cf0a3bf.jpeg)
可以看到此时GENERATED_BODY的宏内部生成的宏已经发生变化了, 指向无参的默认构造函数
并且没有重复再声明定义带参数的构造函数, 也没有声明不带参数的构造函数(用户自己实现的)
// 分析的时候看到这两个宏效果不同, 但另一个宏没有用到, 就感觉这里有古怪, 果然
总结一下就是
- GENERATED_UCLASS_BODY
- 永远使用带FObjectInitializer参数的构造函数
- GENERATED_BODY
- 如果没有声明一个无参构造函数, 自动声明定义一个带FObjectInitializer参数的构造函数
- 如果有声明无参构造函数, 不做任何处理
// 如果没记错, 该功能好像是4.9版本左右添加的, 记得旧版本要强制的写有参构造函数, 贼烦
步骤五
最后我们再从头到尾来看generated.h
先将这两个宏摘出来, 字面翻译是, 允许和禁止废弃相关警告
- PRAGMA_DISABLE_DEPRECATION_WARNINGS
- PRAGMA_ENABLE_DEPRECATION_WARNINGS
![51f117009d59999f5d21412042a1516c.png](https://i-blog.csdnimg.cn/blog_migrate/f3c4c9abff290067ca36f32ab27ef1a3.png)
确保包含一次
![709dd979a0675081491c0de6b8ac664a.png](https://i-blog.csdnimg.cn/blog_migrate/cec5fa6b4a5c7cd3e893a31d07640440.jpeg)
有调整代码顺序, 这里就是两块代码
- GENERATED_BODY
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_RPC_WRAPPERS_NO_PURE_DECLS
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_INCLASS_NO_PURE_DECLS
- GENERATED_UCLASS_BODY
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_RPC_WRAPPERS
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_INCLASS
宏的名称区别在带不带NO_PURE_DECLS
目前来看, 这两个宏最终没有任何区别
// 还是不知道这个非纯DECLS是什么意思
![e6ed1827a415e052c6f403252358cc99.png](https://i-blog.csdnimg.cn/blog_migrate/3e4c090c26d310819639a35b48c7cbb6.jpeg)
然后是两个构造函数宏, 分别是标准的和加强版
- GENERATED_BODY
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_ENHANCED_CONSTRUCTORS
- GENERATED_UCLASS_BODY
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_STANDARD_CONSTRUCTORS
目前来看, 根据前文所述这两个宏最终的区别在构造函数的处理上
![ffaebf5e49877defa05e4e42b5a3535f.png](https://i-blog.csdnimg.cn/blog_migrate/a97cce7aa6a753aae606c43441f28703.jpeg)
然后定义了两个空宏, 暂时不知道用途
然后是对宏实际内容的定义
- GENERATED_BODY
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_PRIVATE_PROPERTY_OFFSET
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_RPC_WRAPPERS_NO_PURE_DECLS
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_INCLASS_NO_PURE_DECLS
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_ENHANCED_CONSTRUCTORS
- GENERATED_UCLASS_BODY
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_PRIVATE_PROPERTY_OFFSET
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_RPC_WRAPPERS
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_INCLASS
- GUAO22_Source_GUAO22_Public_GAObject01_h_15_STANDARD_CONSTRUCTORS
![add480d1ea1d2fc4c3c1ab0127c32c29.png](https://i-blog.csdnimg.cn/blog_migrate/1b64fa3570c882f69124968e75c4fbdb.png)
声明一个模版函数
![c3cbd1fecfbe8cee407220eac85fa912.png](https://i-blog.csdnimg.cn/blog_migrate/8ce41090417e65885b2a9577e0c0fcd1.png)
定义一个CURRENT_FILE_ID
然后就没有了
结语
- 这是这个系列的开篇, 跟着流程一步一步走, 这只是一个开始
- 骗赞了, 骗评论了.