UE4反射机制的通俗理解【代码生成】

UE4反射机制主要分为以下几个阶段

  • 生成阶段:借助UHT生成对应包含了反射声明的反射代码
  • 收集阶段:借助static自动注册方式,在模块加载的时候,把UCLASS注册,放在array中管理
  • 注册阶段:在模块初始化的时候,将Array中的所有UClass相关的Function和Property注册;
  • 链接阶段:就是反射功能。

第一阶段

     在介绍第一阶段前,先介绍两个工具,ubt和uht。UBT是UE4自带的C++工具,用来处理文件依赖,包含。(待理解)

UHT则是UE4自带的C++解析工具,借助UBT提供的文件依赖关系,对包含了反射作用的文件进行集中编译处理,生成对应的反射代码,放在.generate.h 和 .generate.cpp。

一个C++文件如果包含了反射代码,那么他一定要include generate.h,不然UHT无法得知有哪些文件需要被反射编译。


20200919更新

过了这么长时间终于又有时间(勇气)再次开始看UE4的反射机制。之前看的很快,但很多模块都只是一知半解,甚至一头雾水,找了很多博主的博客和文章,现在再来看,好像稍微懂了一些,感觉还是不能急于求成,尽量每天看一点,消化理解,积少成多。

之前说到的generate.h,其实就是UE对我们自己写的h文件产生的反射代码文件。

在说UE4的反射代码生成之前,先看说一些反射是什么,其实说白了就是在蓝图里能够动态创建C++的对象类型(比如右键点击生成一个我们定义的函数,成员,类等等)如果要做到这个,我们就得在运行的时候,动态获得我们写的C++的代码结构的类型,比如写了一个int cint;我们在蓝图里就要获取这个cint的类型,写一个函数,我们就可以动态调用这个函数,生成入口和出口。

C++有RTTI可以在运行的时候获得变量的类型信息。但感觉功能还是简单了一些,不能满足蓝图的功能,还有宏,利用宏生成类型信息实现反射,我在网上找了一个易于理解的方法转在博客里,传送门在这里【C++】利用宏实现反射

这样写起来比较复杂,但是UE当前采用的就是这种方法(不过要复杂的多),通过对代码进行宏标记,生成对应的.generated.h/cpp文件。

通过宏标记能够为我们的代码生成反射代码,并且保存在generated.h/cpp中,我们需要反射的类型有哪些呢?如下图

从下向上分析:

  • UFunction 函数反射
  • UClass C++类
  • UScriptStruct C++中的结构体
  • Ustruct 以上三种都属于这个
  • UProperty 成员变量类型
  • UEnum 枚举
  • UField 包含成员变量,广义结构体和枚举
  • UInterface 是一个特殊的接口类
  • UMetaData 元数据 在宏标记的时候有遇到元数据说明符,来定义一些辅助信息提示,可理解成TMap<FName,FString>
  • 所有的这些类型都属于UObject 好处是这些类都不用自己写GC和序列化了,因为UObject已经帮他们实现了。

这些类型就是在我们需要从蓝图中获取信息的时候所依赖的索引类型,比如C++中我们写了:

UPROPERTY()

int cint;

那么这个cint就是属于UProperty类型的派生类。


知道了UE的反射类型结构之后,我们需要看一下UE如何为我们的代码生成反射需要的代码。

看了大钊的InsideUE,讲的非常详细,但是很多东西还是有点难以理解。在这里整理一下。

UHT为我们的代码生成了4个文件,我们需要关注其中的两个,如我们写了一个MyClass.h。我们需要关注

  • MyClass.generated.h MyClass生成的头文件
  • ProjectName.generated.cpp 项目的编译文件

首先MyClass.h文件如下

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "UObject/NoExportTypes.h"
#include "MyClass.generated.h"

UCLASS()
class HELLO_API UMyClass : public UObject
{
	GENERATED_BODY()
};

很简单,只是在类前面加一个宏定义,然后在类里面添加一个宏,GENERATED_BODY()

关键来了!GENERATED_BODY()

#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D
#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)
#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY)

20200921更新

可以看到GENERATED_BODY()其实就是生成了一个字符串名字Hello_Source_Hello_MyClass_h_11_GENERATED_BODY,这个字符串宏会在.generated.h中再定义展开。

如果类需要构造函数自定义实现,会让GENERATED_BODY()指向生成的另一个字符串Hello_Source_Hello_MyClass_h_11_GENERATED_BODY_LEGACY。

接下来就是这两个宏的展开:

#define Hello_Source_Hello_MyClass_h_11_GENERATED_BODY_LEGACY \ //两个重要的定义
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
	Hello_Source_Hello_MyClass_h_11_PRIVATE_PROPERTY_OFFSET \                   11
	Hello_Source_Hello_MyClass_h_11_RPC_WRAPPERS \                              12  
	Hello_Source_Hello_MyClass_h_11_INCLASS \                                   13
	Hello_Source_Hello_MyClass_h_11_STANDARD_CONSTRUCTORS \                     14
public: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS


#define Hello_Source_Hello_MyClass_h_11_GENERATED_BODY \    //两个重要的定义
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
	Hello_Source_Hello_MyClass_h_11_PRIVATE_PROPERTY_OFFSET \                   21
	Hello_Source_Hello_MyClass_h_11_RPC_WRAPPERS_NO_PURE_DECLS \                22
	Hello_Source_Hello_MyClass_h_11_INCLASS_NO_PURE_DECLS \                     23
	Hello_Source_Hello_MyClass_h_11_ENHANCED_CONSTRUCTORS \                     24
private: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS

两个宏又分别定义了八个宏,按照编号来看。13和23的定义是一样的,14和24的定义略微有些区别,差了一个构造函数的默认实现。先看14的宏定义

#define Hello_Source_Hello_MyClass_h_11_ENHANCED_CONSTRUCTORS \
	/** Standard constructor, called after all reflected properties have been initialized */ \
	NO_API UMyClass(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; \   //默认的构造函数实现
private: \  //禁止掉C++11的移动和拷贝构造
	/** Private move- and copy-constructors, should never be used */ \
	NO_API UMyClass(UMyClass&&); \
	NO_API UMyClass(const UMyClass&); \
public: \
	DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, UMyClass); \     //因为WITH_HOT_RELOAD_CTORS关闭,展开是空宏
    DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(UMyClass); \   //同理,空宏
	DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyClass)

这里又定义了一些宏,最后一个是    DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyClass)

这个宏的定义如下:

#define DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(TClass) \
	static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass(X); }

可以看到生成了一个静态函数,这个函数的实现其实是调用了类的构造函数。 因为构造函数不能用函数指针,所以做了一层包装。而UClass的构造函数签名都一样,所以可以用静态函数来统一包装调用,然后用一个函数指针调用这个静态函数。

函数签名就是一个函数的名称和调用参数列表。

这里的EInternal*是一个枚举值,只用来构造内部使用。FObjectInitiallizer是一个用来构造使用的类,在C++构造函数调用之后用来真正的构造一个UObject。所以这个内部实现的语句意思就是

new((Enum*)Uobject*)TClass(X)没看太明白

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值