修改class文件_UE4 UObject反射系列(一) Class相关

前言

  • 文章之后会修改.
    最近一些文章互相关联性较高, 可能随着某篇文章某些内容的深扒, 而导致一些内容更连贯, 并对所有相关内容进行修改
  • 源码版本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
  • 重新生成并打开VS项目, 不要编译

引擎设置里面可以关掉添加新C++类自动编译, 然后按下图重新生成VS项目并打开

485df7bb43e54c7c6371ffdb6f26e728.png

5f555ca43aa6b63c5f98993d98011edb.png
// 编译了也无所谓, 默认是会自动编译的

步骤二 分析空类

1c98eb0a6b8a89a4d1eb02b864994452.png

794bf3791a686f376e0fde7afa9986af.png

如果没有编译过, 这里会有两个报错提示, 无法找到头文件和这个宏定义.

这些是通过UHT动态生成的, 下文主要就是对这些自动生成内容的分析.

66d23a25cae9c980839db813aeeb92a7.png

首先, 我们可以看到这是一个空的UObject类, 这是第一个分析的文件, 他没有任何实现, 然后我们生成项目

1a5e7aec85ec127ec58614f4d04eae78.png

然后我们就可以打开这个文件了, 并打开文件所在目录, 可以看到对应的.cpp文件

a8fb79a922aebcf0d7bfff99539d6fc2.png

0914b6929adb53cf2c0d46c05cefce0b.png

首先是UCLASS()宏

0b6c8b5d327db0ad97f08c59acae46db.png

空定义, 忽略


步骤二 第一小节

GENERATED_BODY宏, 重点来了

a8dbf3990fbf2144e221f43d54697d5b.png

88a34ef1668888264b777211368db981.png

GENERATED_BODY一步一步展开级可得到, 同理可得

// 推荐和我一样, 新建两个个.h文件, 按照步骤一步一步展开

0a0643984cce3b77c5fa7ffab25e9f2e.png
  • 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

该宏是由多个宏组成的, 忽略掉PRAGMA_DISABLE_DEPRECATION_WARNINGS这两个宏, 也就变成了

db15948aca3465e8906c78947196a9eb.png

步骤二 第三小节(一)

我们按照顺序来看这四个宏

  • GUAO22_Source_GUAO22_Public_GAObject01_h_15_PRIVATE_PROPERTY_OFFSET

8c994d6dc48afc5ace9cd51dad49597d.png


字面翻译, 私有属性偏移, 空值忽略

  • GUAO22_Source_GUAO22_Public_GAObject01_h_15_RPC_WRAPPERS_NO_PURE_DECLS

f2c8c008652da83f476967bcccb46c3a.png
__BEGIN_DECLS 和__END_DECLS

包装RPC不是纯函数的DECLS??, 此处为空, 忽略

  • GUAO22_Source_GUAO22_Public_GAObject01_h_15_INCLASS_NO_PURE_DECLS

922c6e9eefee2db4062c0c3dc7da102d.png

在类内的不是纯函数的DECLS??

  • GUAO22_Source_GUAO22_Public_GAObject01_h_15_ENHANCED_CONSTRUCTORS

c033fe86eab2a91408d76daede9708da.png

增强的构造函数及相关的内容

然后, 我们替换得出了(注意替换时public和private的先后位置)

615ab5c410ce793fe9c33a4a4c53ff27.png

步骤二 第二小节(二)

我们再转过头来看GENERATED_UCLASS_BODY相关

d5706c3480da3e40f2cad09323532b3f.png

可以得出

299c8351bd1070ad934322d36948b48c.png

步骤二 第三小节(二)

同理, 我们按照顺序来看这四个宏

  • GUAO22_Source_GUAO22_Public_GAObject01_h_15_PRIVATE_PROPERTY_OFFSET

28cdb12d660209e432e49e5613a53e1e.png

这个宏和上面那个是同一个宏, 为空, 属于公有部分

  • GUAO22_Source_GUAO22_Public_GAObject01_h_15_RPC_WRAPPERS

c23753e0612988ed5ef55437c5fdf399.png

和前文一样, 此处为空

  • GUAO22_Source_GUAO22_Public_GAObject01_h_15_INCLASS

cf66ac8b6955cd07a84d0ab0ddff3988.png

此处空类的情况下, 两者代码一致

  • GUAO22_Source_GUAO22_Public_GAObject01_h_15_STANDARD_CONSTRUCTORS

3fdb4111fc389ef5cdbb2e6a30fd6a4a.png

这个宏是标准的构造函数内容

和之前的宏做区分, 可以看到, 此时他们有很多内容是相同的, 只有一个构造函数不同

然后我们可以得出(为了顺序和前面的对应, 有一定微调, 但public和private一致, 无影响)

8b4031e31a3ff13766c994a9318b7d9e.png

步骤二 第三小节(三)

我们先停下来消化一些东西, 通过对比两个宏最后的展开, 目前来看, 区别只在构造函数上面

4de34734ad548432d9065e56f99de980.png
// GENERATED_BODY

也就是说, 如果使用GENERATED_UCLASS_BODY宏, 需要人工创建并实现UGAObject01(const FObjectInitializer&)对应实现, 而GENERATED_BODY不需要

f9c986fe4f753bc3ea8b3662fa64afe2.png

以及, 都会将拷贝和移动构造函数私有化, 阻止调用, 防止误操作.

以及, 接下来, 我们只需要展开分析GENERATED_BODY即可, 剩余部分, 两者是一致的

// 至少目前是一致的

步骤三

e92dc3d94a16778564664ba62659714d.png

然后是对这些宏的展开, 有五个宏

  • DECLARE_CLASS

ae248eb392083a29ee36c1d1da9e5956.png

07482040888ac238e13d9767b2563834.png

宏替换可得, 如图

  • DECLARE_SERIALIZER

d581575e4c09faf4f5264a23ad208cc5.png

是对<<的运算符重载, 序列化相关是由FArchive及相关类实现的

73dce44211ae9902a5e3106fd0b80fd9.png
  • DECLARE_VTABLE_PTR_HELPER_CTOR

c9d32c44ac1a3d2017ae7a70a948cdf0.png

热重载相关的, 忽略

121c1652f97d47b6f9fcee515c155659.png
  • DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER

6f6a3829d6147aa1a5381158d2f95f4a.png

热重载相关的, 忽略

077372c1d0842b9fb7df3e15fe21c296.png
  • DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL

2d00b5355a659440c9979339f69f0232.png

这是一个默认的构造函数

64066ed2711c849e1d0c01f061fa62d9.png

最终替换所有展开

179e3e13af46a0f08238477c3e8dcf72.png

14198300ac35b8d053842b64106f3134.png

同理也可以得出GENERATED_UCLASS_BODY相关的, 到此为止, 这个头文件的推断就告一段落了.


步骤四

然后我们先停下来, 分析一些其他情况

情况一

226ac993a815def80531710bd22dd829.png

建立一个GENERATED_UCLASS_BODY()验证上面的推测, 别不同宏生成的东西差很多

68711f1f9ae3bbc53b1e626819a1ef9d.png

最后对比.generated.h发现两个头文件有区别在这里

当定义GENERATED_UCLASSBODY之后, 如果再定义GENERATED_BODY的时候会在编译的时候报错

情况二

aea72db0a40af98aafa12ec70557e463.png

我们自己实现一个无参构造函数, 参考前文这里宏定义, 有两种默认构造器的情况, 前文指向后者

18fe1aa235b611bb0a2a71cb761a0cdd.png

fa373a3e02376abdc63fa95adec3119f.png

可以看到此时GENERATED_BODY的宏内部生成的宏已经发生变化了, 指向无参的默认构造函数

并且没有重复再声明定义带参数的构造函数, 也没有声明不带参数的构造函数(用户自己实现的)

// 分析的时候看到这两个宏效果不同, 但另一个宏没有用到, 就感觉这里有古怪, 果然

总结一下就是

  • GENERATED_UCLASS_BODY
    • 永远使用带FObjectInitializer参数的构造函数
  • GENERATED_BODY
    • 如果没有声明一个无参构造函数, 自动声明定义一个带FObjectInitializer参数的构造函数
    • 如果有声明无参构造函数, 不做任何处理
// 如果没记错, 该功能好像是4.9版本左右添加的, 记得旧版本要强制的写有参构造函数, 贼烦

步骤五

最后我们再从头到尾来看generated.h

先将这两个宏摘出来, 字面翻译是, 允许和禁止废弃相关警告

  • PRAGMA_DISABLE_DEPRECATION_WARNINGS
  • PRAGMA_ENABLE_DEPRECATION_WARNINGS

51f117009d59999f5d21412042a1516c.png

确保包含一次

709dd979a0675081491c0de6b8ac664a.png

有调整代码顺序, 这里就是两块代码

  • 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

然后是两个构造函数宏, 分别是标准的和加强版

  • 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

然后定义了两个空宏, 暂时不知道用途

然后是对宏实际内容的定义

  • 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

声明一个模版函数

c3cbd1fecfbe8cee407220eac85fa912.png

定义一个CURRENT_FILE_ID

然后就没有了


结语

  • 这是这个系列的开篇, 跟着流程一步一步走, 这只是一个开始
  • 骗赞了, 骗评论了.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值