UE4 反射机制

什么是反射

Java反射

  • Java反射机制:是指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍可扩展。

    java加载类的时候是将.class加载到内存并生成相关内存映像,在内存中的.class内存映像中含有类的各种数据,之后生成对象都是用内存中相应的.class,因此通过修改该信息还能动态改变类的数据。

U4反射

  • 反射用于在是在程序运行时动态加载类以及获取类的信息,反射数据描述了类在运行时的内容
  • 这些数据所存储的信息包括类的名称、类中的数据成员、每个数据成员的类型、每个成员位于对象内存映像的偏移(offset),此外,它也包含类的所有成员函数信息。

类型系统

  • 指的是程序运行空间内构建出来的类型信息树组织

反射

  • 描述运行时得到类型的功能,通过类型信息反过来创建对象,读取修改属性,调用方法的功能行为

反射有什么用

  • 实现序列化
  • 实现editor的details panel
  • 垃圾回收
  • 网络复制
  • 蓝图/C++通信和相互调用

U4如何实现反射机制

C++ RTTI

C++中的运行时类型系统功能太弱,无法支持反射系统的功能

  • typeid/typeinfo

    这个关键字的主要作用就是用于让用户知道是什么类型,并提供一些基本对比和name方法,作用也顶多只是让用户判断从属于不同的类型,所以其实说起来type_info的应用并不广泛,一般来说也只是把它当作编译器提供的一个唯一类型Id。

  • dynamic_cast

    dynamic_cast内部机制其实也是利用虚函数表里的类型信息来判断一个基类指针是否指向一个派生类对象。其目的更多是用于在运行时判断对象指针是否为特定一个子类的对象。

UE4反射

  • UE4反射系统主要依靠2个工具,UHT和UBT,还需要UObject和UClass的支持.

    UObject是所有支持反射的对象的基类,有类的StaticClass()或者实例的GetClass()来获取反射信息
    UClass(相当于是元数据)继承自UStrcut->UField,用于存储反射数据。

  • UE4 中的反射大概的流程是在编译前利用 UnrealHeaderTool 对代码文件内容进行处理,生成 .generated.h/.gen.cpp 文件,在生成的代码中加入反射信息,并和自己编写的代码一起编译,在运行时动态地将这些反射信息收集起来使用。

UBT

主要工作

  • 是一个自定义工具,负责管理通过各种编译配置来编译虚幻引擎4(UE4)源代码的过程。
    • 该工具处理所有复杂的项目编译工作,编译UE4的逐个模块并处理依赖等
      • Target.cs,Build.cs

UHT

目的

  • 用于生成C++的反射代码

    简单流程

  • UHT被编译成exe文件,通过命令行参数调用
  • UE4的反射系统并不是把所有的信息都纳入,而是需要程序员标记才会被反射系统纳入
  • UHT会扫描文件,如果其中有#include "file.generated.h",就是告知UHT该头文件需要被纳入反射系统之中.然后UHT会根本相关的宏标记,UCLASS(),UFUNCTIONGENERATED_BODY()来解析头文件中的定义,,最终生成filename.generated.hfilename.gen.cpp两个文件,其中包含了用于实现反射的相关代码。其中主要做的工作是将C++类中的真实数据成员和UClass(用于存储反射数据的类)中的UPorperty等数据类型绑定起来。
    • .generated.h中生成的函数包含了XXX_Implementation之类的补全,也包含了用于蓝图中调用C++函数的转换函数,并通过GENERATED_BODY()安插到我们编写的类中。

UE4完整编译流程

参考:UE — UBT、UHT与反射基本理解

  • UBT搜集目录中的.cs文件
  • 然后UBT调用UHT分析需要分析的.h .cpp文件(典型根据文件是否含有#include”FileName.generated.h”,是否有UCLASS()、UPROPERTY等宏)生成generated.h和gen.cpp文件
  • 最后UBT调用MSBuild,将.h.cpp和generated.h gen.cpp结合到一起然后编译

  • UBT通过扫描头文件时,记录所有包含反射类型的modules(模块),当其中有头文件改变时,就会用UHT更新反射数据。UHT解析头文件,扫描标记,生成用于支持反射的C++代码

使用反射系统

  • 你可以通过使用UTypeName::StaticClass()或者FTypeName::StaticStruct()来获取反射类型对应的UClass以及UScriptStruct,你也通过 一个UObject的实例通过Instance->GetClass()来获取类型(不能通过一个结构体实例的获取类型,因为结构体没有一个通用的基类或者需要的存储空间)。

为什么用c++代码存储反射数据

  • 用生成的C++代码来存储反射数据的一个最大好处就是,它可以保证跟二进制做到同步。你永远也不会加载陈旧或者过时的反射数据,因为它是跟引擎的其它代码同时编译的.