你想要???想要你就說出來嘛,你不說我怎么知道你想要呢?

引言

上文講到了UE的類型系統(tǒng)結(jié)構(gòu),以及UHT分析源碼的一些宏標記設(shè)定。在已經(jīng)進行了類型系統(tǒng)整體的設(shè)計之后,本文將開始討論接下來的步驟。暫時不討論UHT的細節(jié),假設(shè)UHT已經(jīng)分析得到了足夠的類型元數(shù)據(jù)信息,下一步就是利用這個信息在程序內(nèi)存中構(gòu)建起前文的類型系統(tǒng)結(jié)構(gòu),這個過程我們稱之為注冊。同一般程序的構(gòu)建流程需要經(jīng)過預(yù)處理、編譯、匯編、鏈接一樣,UE為了在內(nèi)存中模擬構(gòu)建的過程,在概念上也需要以下幾個階段:生成,收集,注冊,鏈接??傮w的流程比較繁雜,因此本文首先開始介紹第一階段,生成。在生成階段,UHT分析我們的代碼,并生成類型系統(tǒng)的相關(guān)代碼。

Note1:生成的代碼和注冊的過程會因為HotReload功能的開啟與否有些不一樣,因此為了最簡化流程闡述,我們先關(guān)閉HotReload,關(guān)閉的方式是在Hello.Build.cs里加上一行:Definitions.Add("WITH_HOT_RELOAD_CTORS=0");
Note2:本文開始及后續(xù)會簡單的介紹一些用到的C++基礎(chǔ)知識,但只是點到為止,不做深入探討。

C++ Static Lazy初始化模式

一種我們常用,也是UE中常用的單件懶惰初始化模式是:

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

前者非常簡單,也沒考慮多線程安全,但是在單線程環(huán)境下足夠用了。用指針的原因是,有一些情況,這些對象的生命周期是由別的地方來管理的,比如UE里的GC,因此這里只static化一個指針。否則的話,還是后者更加簡潔和安全。

UHT代碼生成

在C++程序中的預(yù)處理是用來對源代碼進行宏展開,預(yù)編譯指令處理,注釋刪除等操作。同樣的,一旦我們采用了宏標記的方法,不管是怎么個標記語法,我們都需要進行簡單或復雜的詞法分析,提取出有用的信息,然后生成所需要的代碼。在引擎里創(chuàng)建一個空C++項目命名為Hello,然后創(chuàng)建個不繼承的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:該項目的實現(xiàn)編譯單元。

其生成的文件初看起來很多很復雜,但其實比較簡單,不過就是一些宏替換而已。生成的函數(shù)大都也以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是為了字母排序總是出現(xiàn)在智能感知的最下面,盡量隱藏起來。
接下來,請讀者們緊跟著我的步伐,開始進行這趟剖析之旅。

UCLASS的生成代碼剖析

先從一個最簡單的UMyClass的開始,總覽分析生成的代碼結(jié)構(gòu),接著再繼而觀察其他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" 通過查看文件內(nèi)容,發(fā)現(xiàn)這個文件在編譯的時候就是Include了其他一些更基礎(chǔ)的頭文件,比如#include "Math/Vector.h",因此你才能在MyClass里不用include就引用這些類。當然,還有一些內(nèi)容是專門供UHT使用來生成藍圖類型的,現(xiàn)在暫時不需要管。

第6行:#include "MyClass.generated.h",就是為了引用生成的頭文件。這里請注意的是,該文件include位置在類聲明的前面,之后談到宏處理的時候會用到該信息。

第11行:GENERATED_BODY(),該宏是重中之重,其他的UCLASS宏只是提供信息,不參與編譯,而GENERATED_BODY正是把聲明和元數(shù)據(jù)定義關(guān)聯(lián)到一起的樞紐。繼續(xù)查看宏定義:

#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)

會發(fā)現(xiàn)GENERATED_BODY最終其實只是生成另外一個宏的名稱,因為:

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

__LINE__標準宏指向了該宏使用時候的的函數(shù),這里是11。加了一個__LINE__宏的目的是為了支持在同一個文件內(nèi)聲明多個類,比如在MyClass.h里接著再聲明UMyClass2,就可以支持生成不同的宏名稱。

因此總而生成的宏名稱是Hello_Source_Hello_MyClass_h_11_GENERATED_BODY,而這個宏就是定義在MyClass.generated.h的77行。值得一提的是,如果MyClass類需要UMyClass(const FObjectInitializer& ObjectInitializer)的構(gòu)造函數(shù)自定義實現(xiàn),則需要用GENERATED_UCLASS_BODY宏來讓最終生成的宏指向Hello_Source_Hello_MyClass_h_11_GENERATED_BODY_LEGACY(MyClass.generated.h的66行),其最終展開的內(nèi)容會多一個構(gòu)造函數(shù)的內(nèi)容實現(xiàn)。

MyClass.generated.h

UHT分析生成的文件內(nèi)容如下:

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定義,先從最簡單的結(jié)構(gòu)開始,不管那些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的宏,如果讀者仔細查看對照的話,會發(fā)現(xiàn)二者只差了“: Super(ObjectInitializer) { }; ”構(gòu)造函數(shù)的默認實現(xiàn)。

我們繼續(xù)往上,以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) { }; \   //默認的構(gòu)造函數(shù)實現(xiàn)private: \  //禁止掉C++11的移動和拷貝構(gòu)造    /** 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關(guān)閉,展開是空宏
    DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(UMyClass); \   //同理,空宏
    DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyClass)

繼續(xù)查看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); }

聲明定義了一個構(gòu)造函數(shù)包裝器。需要這么做的原因是,在根據(jù)名字反射創(chuàng)建對象的時候,需要調(diào)用該類的構(gòu)造函數(shù)??墒穷惖臉?gòu)造函數(shù)并不能用函數(shù)指針指向,因此這里就用一個static函數(shù)包裝一下,變成一個"平凡"的函數(shù)指針,而且所有類的簽名一致,就可以在UClass里用一個函數(shù)指針里保存起來。見引擎里Class.h的聲明:

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

當然,如果讀者需要自己實現(xiàn)一套反射框架的時候也可以采用更簡潔的模式,采用模板實現(xiàn)也是異曲同工。

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

再繼續(xù)往上:

#define Hello_Source_Hello_MyClass_h_11_INCLASS \    private: \    static void StaticRegisterNativesUMyClass(); \  //定義在cpp中,目前都是空實現(xiàn)
    friend HELLO_API class UClass* Z_Construct_UClass_UMyClass(); \ //一個構(gòu)造該類UClass對象的輔助函數(shù)
    public: \
    DECLARE_CLASS(UMyClass, UObject, COMPILED_IN_FLAGS(0), 0, TEXT("/Script/Hello"), NO_API) \   //聲明該類的一些通用基本函數(shù)
    DECLARE_SERIALIZER(UMyClass) \  //聲明序列化函數(shù)    /** Indicates whether the class is compiled into the engine */ \
    enum {IsIntrinsic=COMPILED_IN_INTRINSIC};   //這個標記指定了該類是C++Native類,不能動態(tài)再改變,跟藍圖里構(gòu)造的動態(tài)類進行區(qū)分。

可以說DECLARE_CLASS是最重要的一個聲明,對照著定義:DECLARE_CLASS(UMyClass, UObject, COMPILED_IN_FLAGS(0), 0, http://www.cnblogs.com/fjz13/p/6368994.html