你想要啊?想要你就说出来嘛,你不说我怎么知道你想要呢?

引言

上文讲到了UE的类型系统结构,以及UHT分析源码的一些宏标记设定。在已经进行了类型系统整体的设计之后,本文将开始讨论接下来的步骤。暂时不讨论UHT的细节,假设UHT已经分析得到了足够的类型元数据信息,下一步就是利用这个信息在程序内存中构建起前文的类型系统结构,这个过程我们称之为注册。同一般程序的构建流程需要经过预处理、编译、汇编、链接一样,UE为了在内存中模拟构建的过程,在概念上也需要以下几个阶段:生成,收集,注册,链接。总体的流程比较繁杂,因此本文首先开始介绍第一阶段,生成。在生成阶段,UHT分析我们的代码,并生成类型系统的相关代码。

Note1:生成的代码和注册的过程会因为HotReload功能的开启与否有些不一样,因此为了最简化流程阐述,我们先关闭HotReload,关闭的方式是在Hello.Build.cs里加上一行:Definitions.Add("WITH_HOT_RELOAD_CTORS=0");
Note2:本文开始及后续会简单的介绍一些用到的C++基础知识,但只是点到为止,不做深入探讨。

C++ Static Lazy初始化模式

一种我们常用,也是UE中常用的单件懒惰初始化模式是:

Hello* StaticGetHello(){    static Hello* obj=nullptr;    if(!obj)
    {
        obj=...
    }    return obj;
}
或者Hello& StaticGetHello(){    static Hello obj(...);    return obj;
}

前者非常简单,也没考虑多线程安全,但是在单线程环境下足够用了。用指针的原因是,有一些情况,这些对象的生命周期是由别的地方来管理的,比如UE里的GC,因此这里只static化一个指针。否则的话,还是后者更加简洁和安全。

UHT代码生成

在C++程序中的预处理是用来对源代码进行宏展开,预编译指令处理,注释删除等操作。同样的,一旦我们采用了宏标记的方法,不管是怎么个标记语法,我们都需要进行简单或复杂的词法分析,提取出有用的信息,然后生成所需要的代码。在引擎里创建一个空C++项目命名为Hello,然后创建个不继承的MyClass类。编译,UHT就会为我们生成以下4个文件(位于Hello\Intermediate\Build\Win64\Hello\Inc\Hello)

  • HelloClasses.h:目前无用

  • MyClass.generated.h:MyClass的生成头文件

  • Hello.generated.dep.h:Hello.generated.cpp的依赖头文件,也就是顺序包含上述的MyClass.h而已

  • Hello.generated.cpp:该项目的实现编译单元。

其生成的文件初看起来很多很复杂,但其实比较简单,不过就是一些宏替换而已。生成的函数大都也以Z_开头,笔者开始也在猜想Z_前缀的缩写含义,感谢NetFly向Epic的人求证之后的回答:

The 'Z_' prefix is not part of any official naming convention, and it
doesn't really mean anything. Some generated functions were named this way
to avoid name collisions and so that these functions will appear together at the
bottom of intelisense lists.

简而言之,没什么特别含义,就是简单为了避免命名冲突,用Z是为了字母排序总是出现在智能感知的最下面,尽量隐藏起来。
接下来,请读者们紧跟着我的步伐,开始进行这趟剖析之旅。

UCLASS的生成代码剖析

先从一个最简单的UMyClass的开始,总览分析生成的代码结构,接着再继而观察其他UEnum、UStruct、UInterface、UProperty、UFunction的代码生成样式。

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()
};

第5行:#include "UObject/NoExportTypes.h" 通过查看文件内容,发现这个文件在编译的时候就是Include了其他一些更基础的头文件,比如#include "Math/Vector.h",因此你才能在MyClass里不用include就引用这些类。当然,还有一些内容是专门供UHT使用来生成蓝图类型的,现在暂时不需要管。

第6行:#include "MyClass.generated.h",就是为了引用生成的头文件。这里请注意的是,该文件include位置在类声明的前面,之后谈到宏处理的时候会用到该信息。

第11行:GENERATED_BODY(),该宏是重中之重,其他的UCLASS宏只是提供信息,不参与编译,而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)

会发现GENERATED_BODY最终其实只是生成另外一个宏的名称,因为:

CURRENT_FILE_ID的定义是在MyClass.generated.h的89行:\#define CURRENT_FILE_ID Hello_Source_Hello_MyClass_h,这是UHT通过分析文件得到的信息。

__LINE__标准宏指向了该宏使用时候的的函数,这里是11。加了一个__LINE__宏的目的是为了支持在同一个文件内声明多个类,比如在MyClass.h里接着再声明UMyClass2,就可以支持生成不同的宏名称。

因此总而生成的宏名称是Hello_Source_Hello_MyClass_h_11_GENERATED_BODY,而这个宏就是定义在MyClass.generated.h的77行。值得一提的是,如果MyClass类需要UMyClass(const FObjectInitializer& ObjectInitializer)的构造函数自定义实现,则需要用GENERATED_UCLASS_BODY宏来让最终生成的宏指向Hello_Source_Hello_MyClass_h_11_GENERATED_BODY_LEGACY(MyClass.generated.h的66行),其最终展开的内容会多一个构造函数的内容实现。

MyClass.generated.h

UHT分析生成的文件内容如下:

PRAGMA_DISABLE_DEPRECATION_WARNINGS#ifdef HELLO_MyClass_generated_h#error "MyClass.generated.h already included, missing '#pragma once' in MyClass.h"#endif#define HELLO_MyClass_generated_h#define Hello_Source_Hello_MyClass_h_11_RPC_WRAPPERS    //先忽略#define Hello_Source_Hello_MyClass_h_11_RPC_WRAPPERS_NO_PURE_DECLS  //先忽略#define Hello_Source_Hello_MyClass_h_11_INCLASS_NO_PURE_DECLS \    private: \    static void StaticRegisterNativesUMyClass(); \    friend HELLO_API class UClass* Z_Construct_UClass_UMyClass(); \    public: \    DECLARE_CLASS(UMyClass, UObject, COMPILED_IN_FLAGS(0), 0, TEXT("/Script/Hello"), NO_API) \    DECLARE_SERIALIZER(UMyClass) \    /** Indicates whether the class is compiled into the engine */ \    enum {IsIntrinsic=COMPILED_IN_INTRINSIC};#define Hello_Source_Hello_MyClass_h_11_INCLASS \    private: \    static void StaticRegisterNativesUMyClass(); \    friend HELLO_API class UClass* Z_Construct_UClass_UMyClass(); \    public: \    DECLARE_CLASS(UMyClass, UObject, COMPILED_IN_FLAGS(0), 0, TEXT("/Script/Hello"), NO_API) \    DECLARE_SERIALIZER(UMyClass) \    /** Indicates whether the class is compiled into the engine */ \    enum {IsIntrinsic=COMPILED_IN_INTRINSIC};#define Hello_Source_Hello_MyClass_h_11_STANDARD_CONSTRUCTORS \    /** Standard constructor, called after all reflected properties have been initialized */ \    NO_API UMyClass(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); \    DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyClass) \    DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, UMyClass); \DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(UMyClass); \private: \    /** Private move- and copy-constructors, should never be used */ \    NO_API UMyClass(UMyClass&&); \    NO_API UMyClass(const UMyClass&); \public:#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: \    /** 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); \DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(UMyClass); \    DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyClass)#define Hello_Source_Hello_MyClass_h_11_PRIVATE_PROPERTY_OFFSET     //先忽略#define Hello_Source_Hello_MyClass_h_8_PROLOG   //先忽略#define Hello_Source_Hello_MyClass_h_11_GENERATED_BODY_LEGACY \ //两个重要的定义PRAGMA_DISABLE_DEPRECATION_WARNINGS \public: \
    Hello_Source_Hello_MyClass_h_11_PRIVATE_PROPERTY_OFFSET \
    Hello_Source_Hello_MyClass_h_11_RPC_WRAPPERS \
    Hello_Source_Hello_MyClass_h_11_INCLASS \
    Hello_Source_Hello_MyClass_h_11_STANDARD_CONSTRUCTORS \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 \
    Hello_Source_Hello_MyClass_h_11_RPC_WRAPPERS_NO_PURE_DECLS \
    Hello_Source_Hello_MyClass_h_11_INCLASS_NO_PURE_DECLS \
    Hello_Source_Hello_MyClass_h_11_ENHANCED_CONSTRUCTORS \private: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS#undef CURRENT_FILE_ID#define CURRENT_FILE_ID Hello_Source_Hello_MyClass_h    //前文说过的定义PRAGMA_ENABLE_DEPRECATION_WARNINGS

该文件都是宏定义,因为宏定义是有前后顺序的,因此咱们从尾向前看,请读者此时和上文的代码对照着看。
首先最底下是CURRENT_FILE_ID的定义

接着是两个上文说过的GENERATED_BODY定义,先从最简单的结构开始,不管那些PRIVATE_PROPERTY_OFFSET和PROLOG,以后会慢慢介绍到。这两个宏接着包含了4个声明在上面的其他宏。目前来说Hello_Source_Hello_MyClass_h_11_INCLASS和Hello_Source_Hello_MyClass_h_11_INCLASS_NO_PURE_DECLS的定义一模一样,而Hello_Source_Hello_MyClass_h_11_STANDARD_CONSTRUCTORS和Hello_Source_Hello_MyClass_h_11_ENHANCED_CONSTRUCTORS的宏,如果读者仔细查看对照的话,会发现二者只差了“: Super(ObjectInitializer) { }; ”构造函数的默认实现。

我们继续往上,以Hello_Source_Hello_MyClass_h_11_ENHANCED_CONSTRUCTORS为例:

#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的定义:

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

声明定义了一个构造函数包装器。需要这么做的原因是,在根据名字反射创建对象的时候,需要调用该类的构造函数。可是类的构造函数并不能用函数指针指向,因此这里就用一个static函数包装一下,变成一个"平凡"的函数指针,而且所有类的签名一致,就可以在UClass里用一个函数指针里保存起来。见引擎里Class.h的声明:

class COREUOBJECT_API UClass : public UStruct
...
{
    ...    typedef void (*ClassConstructorType) (const FObjectInitializer&);
    ClassConstructorType ClassConstructor;
    ...
}

当然,如果读者需要自己实现一套反射框架的时候也可以采用更简洁的模式,采用模板实现也是异曲同工。

template<class TClass>void MyConstructor( const FObjectInitializer& X ){ 
    new((EInternal*)X.GetObj())TClass(X);
}

再继续往上:

#define Hello_Source_Hello_MyClass_h_11_INCLASS \    private: \    static void StaticRegisterNativesUMyClass(); \  //定义在cpp中,目前都是空实现
    friend HELLO_API class UClass* Z_Construct_UClass_UMyClass(); \ //一个构造该类UClass对象的辅助函数
    public: \
    DECLARE_CLASS(UMyClass, UObject, COMPILED_IN_FLAGS(0), 0, TEXT("/Script/Hello"), NO_API) \   //声明该类的一些通用基本函数
    DECLARE_SERIALIZER(UMyClass) \  //声明序列化函数    /** Indicates whether the class is compiled into the engine */ \
    enum {IsIntrinsic=COMPILED_IN_INTRINSIC};   //这个标记指定了该类是C++Native类,不能动态再改变,跟蓝图里构造的动态类进行区分。

可以说DECLARE_CLASS是最重要的一个声明,对照着定义:DECLARE_CLASS(UMyClass, UObject, COMPILED_IN_FLAGS(0), 0, http://www.cnblogs.com/fjz13/p/6368994.html