《InsideUE4》UObject(四)類型系統代碼生成



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

引言

上文講到了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, TEXT("/Script/Hello"), NO_API)

  • TClass:類名 TSuperClass:基類名字
  • TStaticFlags:類的屬性標記,這里是0,表示最默認,不帶任何其他屬性。讀者可以查看EClassFlags枚舉來查看其他定義。
  • TStaticCastFlags:指定了該類可以轉換為哪些類,這里為0表示不能轉為那些默認的類,讀者可以自己查看EClassCastFlags聲明來查看具體有哪些默認類轉換。
  • TPackage:類所處於的包名,所有的對象都必須處於一個包中,而每個包都具有一個名字,可以通過該名字來查找。這里是"/Script/Hello",指定是Script下的Hello,Script可以理解為用戶自己的實現,不管是C++還是藍圖,都可以看作是引擎外的一種腳本,當然用這個名字也肯定有UE3時代UnrealScript的影子。Hello就是項目名字,該項目下定義的對象處於該包中。Package的概念涉及到后續Object的組織方式,目前可以簡單理解為一個大的Object包含了其他子Object。
  • TRequiredAPI:就是用來Dll導入導出的標記,這里是NO_API,因為是最終exe,不需要導出。
#define DECLARE_CLASS( TClass, TSuperClass, TStaticFlags, TStaticCastFlags, TPackage, TRequiredAPI  ) \
private: \
    TClass& operator=(TClass&&);   \
    TClass& operator=(const TClass&);   \
	TRequiredAPI static UClass* GetPrivateStaticClass(const TCHAR* Package); \
public: \
	/** Bitwise union of #EClassFlags pertaining to this class.*/ \
	enum {StaticClassFlags=TStaticFlags}; \
	/** Typedef for the base class ({{ typedef-type }}) */ \
	typedef TSuperClass Super;\
	/** Typedef for {{ typedef-type }}. */ \
	typedef TClass ThisClass;\
	/** Returns a UClass object representing this class at runtime */ \
	inline static UClass* StaticClass() \
	{ \
		return GetPrivateStaticClass(TPackage); \
	} \
	/** Returns the StaticClassFlags for this class */ \
	inline static EClassCastFlags StaticClassCastFlags() \
	{ \
		return TStaticCastFlags; \
	} \
	DEPRECATED(4.7, "operator new has been deprecated for UObjects - please use NewObject or NewNamedObject instead") \
	inline void* operator new( const size_t InSize, UObject* InOuter=(UObject*)GetTransientPackage(), FName InName=NAME_None, EObjectFlags InSetFlags=RF_NoFlags ) \
	{ \
		return StaticAllocateObject( StaticClass(), InOuter, InName, InSetFlags ); \
	} \
	/** For internal use only; use StaticConstructObject() to create new objects. */ \
	inline void* operator new(const size_t InSize, EInternal InInternalOnly, UObject* InOuter = (UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags) \
	{ \
		return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags); \
} \
	/** For internal use only; use StaticConstructObject() to create new objects. */ \
	inline void* operator new( const size_t InSize, EInternal* InMem ) \
	{ \
		return (void*)InMem; \
	}

大部分都是不言自明的,這里的StaticClass就是我們最經常用到的函數,其內部調用了GetPrivateStaticClass,而其實現正是在Hello.generated.cpp里的。

Hello.generated.cpp

而整個Hello項目會生成一個Hello.generated.cpp

#include "Hello.h"      //包含該項目的頭文件,繼而包含Engine.h
#include "GeneratedCppIncludes.h"   //包含UObject模塊里一些必要頭文件
#include "Hello.generated.dep.h"    //引用依賴文件,繼而include了MyClass.h
PRAGMA_DISABLE_DEPRECATION_WARNINGS
void EmptyLinkFunctionForGeneratedCode1Hello() {}   //先忽略
	void UMyClass::StaticRegisterNativesUMyClass()  //說是靜態注冊,但現在都是為空,先忽略
	{
	}
	IMPLEMENT_CLASS(UMyClass, 899540749);   //重要!!!
#if USE_COMPILED_IN_NATIVES //該宏編譯的時候會打開
// Cross Module References
	COREUOBJECT_API class UClass* Z_Construct_UClass_UObject(); //引用CoreUObject里的函數,主要是為了得到UObject本身對應的UClass

	HELLO_API class UClass* Z_Construct_UClass_UMyClass_NoRegister();   //構造UMyClass對應的UClass對象,區別是沒有后續的注冊過程
	HELLO_API class UClass* Z_Construct_UClass_UMyClass();  //構造UMyClass對應的UClass對象
	HELLO_API class UPackage* Z_Construct_UPackage__Script_Hello(); //構造Hello本身的UPackage對象
	UClass* Z_Construct_UClass_UMyClass_NoRegister()
	{
		return UMyClass::StaticClass(); //直接通過訪問來獲取UClass對象
	}
	UClass* Z_Construct_UClass_UMyClass()   //構造並注冊
	{
		static UClass* OuterClass = NULL;   //static lazy模式
		if (!OuterClass)
		{
			Z_Construct_UClass_UObject();   //確保UObject本身的UClass已經注冊生成
			Z_Construct_UPackage__Script_Hello();   //確保當前Hello項目的UPackage已經創建,因為后續在生成UMyClass的UClass*對象時需要保存在這個UPacage中
			OuterClass = UMyClass::StaticClass();   //訪問獲得UClass*
			if (!(OuterClass->ClassFlags & CLASS_Constructed))  //防止重復注冊
			{
				UObjectForceRegistration(OuterClass);   //提取信息注冊自身
				OuterClass->ClassFlags |= 0x20100080;   //增加CLASS_Constructed|CLASS_RequiredAPI標記


				OuterClass->StaticLink();   //“靜態”鏈接,后續解釋
#if WITH_METADATA   //編輯器模式下開始
				UMetaData* MetaData = OuterClass->GetOutermost()->GetMetaData();    //獲取關聯到的UPackage其身上的元數據映射,並增加元數據信息
				MetaData->SetValue(OuterClass, TEXT("IncludePath"), TEXT("MyClass.h"));
				MetaData->SetValue(OuterClass, TEXT("ModuleRelativePath"), TEXT("MyClass.h"));
#endif
			}
		}
		check(OuterClass->GetClass());
		return OuterClass;
	}
	static FCompiledInDefer Z_CompiledInDefer_UClass_UMyClass(Z_Construct_UClass_UMyClass, &UMyClass::StaticClass, TEXT("UMyClass"), false, nullptr, nullptr, nullptr);    //延遲注冊,注入信息,在啟動的時候調用
	DEFINE_VTABLE_PTR_HELPER_CTOR(UMyClass);    //HotReload相關,先忽略
	UPackage* Z_Construct_UPackage__Script_Hello()  //構造Hello的UPackage
	{
		static UPackage* ReturnPackage = NULL;
		if (!ReturnPackage)
		{
			ReturnPackage = CastChecked<UPackage>(StaticFindObjectFast(UPackage::StaticClass(), NULL, FName(TEXT("/Script/Hello")), false, false));//注意的是這里只是做一個查找,真正的CreatePackage是在UObjectBase::DeferredRegister里調用的,后續在流程里會討論到
			ReturnPackage->SetPackageFlags(PKG_CompiledIn | 0x00000000);//設定標記和Guid
			FGuid Guid;
			Guid.A = 0x79A097CD;
			Guid.B = 0xB58D8B48;
			Guid.C = 0x00000000;
			Guid.D = 0x00000000;
			ReturnPackage->SetGuid(Guid);

		}
		return ReturnPackage;
	}
#endif

PRAGMA_ENABLE_DEPRECATION_WARNINGS

大部分簡單的都注釋說明了,本文件的關鍵點在於IMPLEMENT_CLASS的分析,和上文.h中的DECLARE_CLASS對應,其聲明如下:
對照着定義IMPLEMENT_CLASS(UMyClass, 899540749);

#define IMPLEMENT_CLASS(TClass, TClassCrc) \
	static TClassCompiledInDefer<TClass> AutoInitialize##TClass(TEXT(#TClass), sizeof(TClass), TClassCrc); \   //延遲注冊
	UClass* TClass::GetPrivateStaticClass(const TCHAR* Package) \   //.h里聲明的實現,StaticClas()內部就是調用該函數
	{ \
		static UClass* PrivateStaticClass = NULL; \ //又一次static lazy
		if (!PrivateStaticClass) \
		{ \
			/* this could be handled with templates, but we want it external to avoid code bloat */ \
			GetPrivateStaticClassBody( \    //該函數就是真正創建UClass*,以后
				Package, \  //Package名字
				(TCHAR*)TEXT(#TClass) + 1 + ((StaticClassFlags & CLASS_Deprecated) ? 11 : 0), \//類名,+1去掉U、A、F前綴,+11去掉_Deprecated前綴
				PrivateStaticClass, \   //輸出引用
				StaticRegisterNatives##TClass, \
				sizeof(TClass), \
				TClass::StaticClassFlags, \
				TClass::StaticClassCastFlags(), \
				TClass::StaticConfigName(), \
				(UClass::ClassConstructorType)InternalConstructor<TClass>, \
				(UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<TClass>, \
				&TClass::AddReferencedObjects, \
				&TClass::Super::StaticClass, \
				&TClass::WithinClass::StaticClass \
			); \
		} \
		return PrivateStaticClass; \
	}

內容也比較簡單,就是把該類的信息傳進去給GetPrivateStaticClassBody函數。

最后展開結果

通過人肉預處理展開一下生成的文件,應該會看得更加清楚一些:
MyClass.h展開

#pragma once
#include "UObject/NoExportTypes.h"

class HELLO_API UMyClass : public UObject
{
private:
	static void StaticRegisterNativesUMyClass();
	friend HELLO_API class UClass* Z_Construct_UClass_UMyClass();
private:
	UMyClass& operator=(UMyClass&&);
	UMyClass& operator=(const UMyClass&);
	NO_API static UClass* GetPrivateStaticClass(const TCHAR* Package);
public:
	/** Bitwise union of #EClassFlags pertaining to this class.*/
	enum {StaticClassFlags = CLASS_Intrinsic};
	/** Typedef for the base class ({{ typedef-type }}) */
	typedef UObject Super;
	/** Typedef for {{ typedef-type }}. */
	typedef UMyClass ThisClass;
	/** Returns a UClass object representing this class at runtime */
	inline static UClass* StaticClass()
	{
		return GetPrivateStaticClass(TEXT("/Script/Hello"));
	}
	/** Returns the StaticClassFlags for this class */
	inline static EClassCastFlags StaticClassCastFlags()
	{
		return 0;
	}
	DEPRECATED(4.7, "operator new has been deprecated for UObjects - please use NewObject or NewNamedObject instead")
	inline void* operator new(const size_t InSize, UObject* InOuter = (UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags)
	{
		return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags);
	}
	/** For internal use only; use StaticConstructObject() to create new objects. */
	inline void* operator new(const size_t InSize, EInternal InInternalOnly, UObject* InOuter = (UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags)
	{
		return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags);
	}
	/** For internal use only; use StaticConstructObject() to create new objects. */
	inline void* operator new(const size_t InSize, EInternal* InMem)
	{
		return (void*)InMem;
	}

	friend FArchive &operator<<(FArchive& Ar, UMyClass*& Res)
	{
		return Ar << (UObject*&)Res;
	}
	/** Indicates whether the class is compiled into the engine */
	enum { IsIntrinsic = COMPILED_IN_INTRINSIC };

	/** 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:
	static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())UMyClass(X); }
};

Hello.generated.cpp展開

//#include "Hello.h"
#include "Engine.h"	

//#include "GeneratedCppIncludes.h"
#include "CoreUObject.h"
#include "UObject/Object.h"
#include "UObject/Class.h"
#include "UObject/Package.h"
#include "UObject/MetaData.h"
#include "UObject/UnrealType.h"

//#include "Hello.generated.dep.h"
#include "MyClass.h"

void EmptyLinkFunctionForGeneratedCode1Hello() {}
void UMyClass::StaticRegisterNativesUMyClass()
{
}
static TClassCompiledInDefer<UMyClass> AutoInitializeUMyClass(TEXT("UMyClass"), sizeof(UMyClass), 899540749);
UClass* UMyClass::GetPrivateStaticClass(const TCHAR* Package)
{
	static UClass* PrivateStaticClass = NULL;
	if (!PrivateStaticClass)
	{
		/* this could be handled with templates, but we want it external to avoid code bloat */
		GetPrivateStaticClassBody(
			Package,
			(TCHAR*)TEXT("UMyClass") + 1 + ((StaticClassFlags & CLASS_Deprecated) ? 11 : 0),
			PrivateStaticClass,
			StaticRegisterNativesUMyClass,
			sizeof(UMyClass),
			UMyClass::StaticClassFlags,
			UMyClass::StaticClassCastFlags(),
			UMyClass::StaticConfigName(),
			(UClass::ClassConstructorType)InternalConstructor<UMyClass>,
			(UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<UMyClass>,
			&UMyClass::AddReferencedObjects,
			&UMyClass::Super::StaticClass,
			&UMyClass::WithinClass::StaticClass
		);
	}
	return PrivateStaticClass;
}

// Cross Module References
COREUOBJECT_API class UClass* Z_Construct_UClass_UObject();

HELLO_API class UClass* Z_Construct_UClass_UMyClass_NoRegister();
HELLO_API class UClass* Z_Construct_UClass_UMyClass();
HELLO_API class UPackage* Z_Construct_UPackage__Script_Hello();
UClass* Z_Construct_UClass_UMyClass_NoRegister()
{
	return UMyClass::StaticClass();
}
UClass* Z_Construct_UClass_UMyClass()
{
	static UClass* OuterClass = NULL;
	if (!OuterClass)
	{
		Z_Construct_UClass_UObject();
		Z_Construct_UPackage__Script_Hello();
		OuterClass = UMyClass::StaticClass();
		if (!(OuterClass->ClassFlags & CLASS_Constructed))
		{
			UObjectForceRegistration(OuterClass);
			OuterClass->ClassFlags |= 0x20100080;


			OuterClass->StaticLink();
#if WITH_METADATA
			UMetaData* MetaData = OuterClass->GetOutermost()->GetMetaData();
			MetaData->SetValue(OuterClass, TEXT("IncludePath"), TEXT("MyClass.h"));
			MetaData->SetValue(OuterClass, TEXT("ModuleRelativePath"), TEXT("MyClass.h"));
#endif
		}
	}
	check(OuterClass->GetClass());
	return OuterClass;
}
static FCompiledInDefer Z_CompiledInDefer_UClass_UMyClass(Z_Construct_UClass_UMyClass, &UMyClass::StaticClass, TEXT("UMyClass"), false, nullptr, nullptr, nullptr);
UPackage* Z_Construct_UPackage__Script_Hello()
{
	static UPackage* ReturnPackage = NULL;
	if (!ReturnPackage)
	{
		ReturnPackage = CastChecked<UPackage>(StaticFindObjectFast(UPackage::StaticClass(), NULL, FName(TEXT("/Script/Hello")), false, false));
		ReturnPackage->SetPackageFlags(PKG_CompiledIn | 0x00000000);
		FGuid Guid;
		Guid.A = 0x79A097CD;
		Guid.B = 0xB58D8B48;
		Guid.C = 0x00000000;
		Guid.D = 0x00000000;
		ReturnPackage->SetGuid(Guid);

	}
	return ReturnPackage;
}

這樣.h的聲明和.cpp的定義就全都有了。不管定義了多少函數,要記得注冊的入口就是那兩個static對象在程序啟動的時候登記信息,才有了之后的注冊。

UENUM的生成代碼剖析

接着是相對簡單的Enum,我們測試的Enum如下:

#pragma once
#include "UObject/NoExportTypes.h"
#include "MyEnum.generated.h"

UENUM(BlueprintType)
enum class EMyEnum : uint8
{
	MY_Dance 	UMETA(DisplayName = "Dance"),
	MY_Rain 	UMETA(DisplayName = "Rain"),
	MY_Song		UMETA(DisplayName = "Song")
};

生成的MyEnum.generated.h為:

PRAGMA_DISABLE_DEPRECATION_WARNINGS
#ifdef HELLO_MyEnum_generated_h
#error "MyEnum.generated.h already included, missing '#pragma once' in MyEnum.h"
#endif
#define HELLO_MyEnum_generated_h

#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID Hello_Source_Hello_MyEnum_h

#define FOREACH_ENUM_EMYENUM(op) \  //定義一個遍歷枚舉值的宏,只是為了方便使用
	op(EMyEnum::MY_Dance) \
	op(EMyEnum::MY_Rain) \
	op(EMyEnum::MY_Song) 
PRAGMA_ENABLE_DEPRECATION_WARNINGS

因此Enum也非常簡單,所以發現生成的其實也沒有什么重要的信息。同樣的,生成的Hello.genrated.cpp中:

#include "Hello.h"
#include "GeneratedCppIncludes.h"
#include "Hello.generated.dep.h"
PRAGMA_DISABLE_DEPRECATION_WARNINGS
void EmptyLinkFunctionForGeneratedCode1Hello() {}
static class UEnum* EMyEnum_StaticEnum()    //定義一個獲取UEnum便利函數,會在延遲注冊的時候被用到
{
	extern HELLO_API class UPackage* Z_Construct_UPackage__Script_Hello();
	static class UEnum* Singleton = NULL;
	if (!Singleton)
	{
		extern HELLO_API class UEnum* Z_Construct_UEnum_Hello_EMyEnum();
		Singleton = GetStaticEnum(Z_Construct_UEnum_Hello_EMyEnum, Z_Construct_UPackage__Script_Hello(), TEXT("EMyEnum"));
	}
	return Singleton;
}
static FCompiledInDeferEnum Z_CompiledInDeferEnum_UEnum_EMyEnum(EMyEnum_StaticEnum, TEXT("/Script/Hello"), TEXT("EMyEnum"), false, nullptr, nullptr);   //延遲注冊
#if USE_COMPILED_IN_NATIVES
	HELLO_API class UEnum* Z_Construct_UEnum_Hello_EMyEnum();
	HELLO_API class UPackage* Z_Construct_UPackage__Script_Hello();
	UEnum* Z_Construct_UEnum_Hello_EMyEnum()    //構造EMyEnum關聯的UEnum*
	{
		UPackage* Outer=Z_Construct_UPackage__Script_Hello();
		extern uint32 Get_Z_Construct_UEnum_Hello_EMyEnum_CRC();
		static UEnum* ReturnEnum = FindExistingEnumIfHotReloadOrDynamic(Outer, TEXT("EMyEnum"), 0, Get_Z_Construct_UEnum_Hello_EMyEnum_CRC(), false);
		if (!ReturnEnum)
		{
			ReturnEnum = new(EC_InternalUseOnlyConstructor, Outer, TEXT("EMyEnum"), RF_Public|RF_Transient|RF_MarkAsNative) UEnum(FObjectInitializer());//直接創建該UEnum對象
			TArray<TPair<FName, uint8>> EnumNames;//設置枚舉里的名字和值
			EnumNames.Add(TPairInitializer<FName, uint8>(FName(TEXT("EMyEnum::MY_Dance")), 0));
			EnumNames.Add(TPairInitializer<FName, uint8>(FName(TEXT("EMyEnum::MY_Rain")), 1));
			EnumNames.Add(TPairInitializer<FName, uint8>(FName(TEXT("EMyEnum::MY_Song")), 2));
			EnumNames.Add(TPairInitializer<FName, uint8>(FName(TEXT("EMyEnum::MY_MAX")), 3));   //添加一個默認的MAX字段
			ReturnEnum->SetEnums(EnumNames, UEnum::ECppForm::EnumClass);
			ReturnEnum->CppType = TEXT("EMyEnum");
#if WITH_METADATA   //設置元數據
			UMetaData* MetaData = ReturnEnum->GetOutermost()->GetMetaData();
			MetaData->SetValue(ReturnEnum, TEXT("BlueprintType"), TEXT("true"));
			MetaData->SetValue(ReturnEnum, TEXT("ModuleRelativePath"), TEXT("MyEnum.h"));
			MetaData->SetValue(ReturnEnum, TEXT("MY_Dance.DisplayName"), TEXT("Dance"));
			MetaData->SetValue(ReturnEnum, TEXT("MY_Rain.DisplayName"), TEXT("Rain"));
			MetaData->SetValue(ReturnEnum, TEXT("MY_Song.DisplayName"), TEXT("Song"));
#endif
		}
		return ReturnEnum;
	}
	uint32 Get_Z_Construct_UEnum_Hello_EMyEnum_CRC() { return 2000113000U; }
	UPackage* Z_Construct_UPackage__Script_Hello()  //設置Hello項目的Package屬性
	{
		...略
	}
#endif

PRAGMA_ENABLE_DEPRECATION_WARNINGS

觀察發現EMyEnum_StaticEnum其實並沒有比Z_Construct_UEnum_Hello_EMyEnum實現更多的其他的功能。GetStaticEnum目前的實現內部也只是非常簡單的調用Z_Construct_UEnum_Hello_EMyEnum返回結果。所以保留着這個EMyEnum_StaticEnum或許只是為了和UClass的結構保持一致。

USTRUCT的生成代碼剖析

因為USTRUCT標記的類內部並不能定義函數,因此測試的Struct如下:

#pragma once
#include "UObject/NoExportTypes.h"
#include "MyStruct.generated.h"

USTRUCT(BlueprintType)
struct HELLO_API FMyStruct
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(BlueprintReadWrite)
	float Score;
};

生成的MyStruct.generated.h如下:

PRAGMA_DISABLE_DEPRECATION_WARNINGS
#ifdef HELLO_MyStruct_generated_h
#error "MyStruct.generated.h already included, missing '#pragma once' in MyStruct.h"
#endif
#define HELLO_MyStruct_generated_h

#define Hello_Source_Hello_MyStruct_h_8_GENERATED_BODY \
	friend HELLO_API class UScriptStruct* Z_Construct_UScriptStruct_FMyStruct(); \  //給予全局方法友元權限
	static class UScriptStruct* StaticStruct(); //靜態函數返回UScriptStruct*
#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID Hello_Source_Hello_MyStruct_h
PRAGMA_ENABLE_DEPRECATION_WARNINGS

同理,根據GENERATED_USTRUCT_BODY的定義,最終會替換成Hello_Source_Hello_MyStruct_h_8_GENERATED_BODY宏。我們發現其實作用只是在內部定義了一個StaticStruct函數,因為FMyStruct並不繼承於UObject,所以結構也非常的簡單。
再接着是Hello.genrated.cpp:

#include "Hello.h"
#include "GeneratedCppIncludes.h"
#include "Hello.generated.dep.h"
PRAGMA_DISABLE_DEPRECATION_WARNINGS
void EmptyLinkFunctionForGeneratedCode1Hello() {}
class UScriptStruct* FMyStruct::StaticStruct()//實現了靜態獲取UScriptStruct*
{
	extern HELLO_API class UPackage* Z_Construct_UPackage__Script_Hello();
	static class UScriptStruct* Singleton = NULL;
	if (!Singleton)
	{
		extern HELLO_API class UScriptStruct* Z_Construct_UScriptStruct_FMyStruct();
		extern HELLO_API uint32 Get_Z_Construct_UScriptStruct_FMyStruct_CRC();
		Singleton = GetStaticStruct(Z_Construct_UScriptStruct_FMyStruct, Z_Construct_UPackage__Script_Hello(), TEXT("MyStruct"), sizeof(FMyStruct), Get_Z_Construct_UScriptStruct_FMyStruct_CRC());
	}
	return Singleton;
}
static FCompiledInDeferStruct Z_CompiledInDeferStruct_UScriptStruct_FMyStruct(FMyStruct::StaticStruct, TEXT("/Script/Hello"), TEXT("MyStruct"), false, nullptr, nullptr);  //延遲注冊
static struct FScriptStruct_Hello_StaticRegisterNativesFMyStruct
{
	FScriptStruct_Hello_StaticRegisterNativesFMyStruct()
	{
		UScriptStruct::DeferCppStructOps(FName(TEXT("MyStruct")),new UScriptStruct::TCppStructOps<FMyStruct>);
	}
} ScriptStruct_Hello_StaticRegisterNativesFMyStruct;    //static注冊
#if USE_COMPILED_IN_NATIVES
	HELLO_API class UScriptStruct* Z_Construct_UScriptStruct_FMyStruct();
	HELLO_API class UPackage* Z_Construct_UPackage__Script_Hello();
	UScriptStruct* Z_Construct_UScriptStruct_FMyStruct()    //構造關聯的UScriptStruct*
	{
		UPackage* Outer = Z_Construct_UPackage__Script_Hello();
		extern uint32 Get_Z_Construct_UScriptStruct_FMyStruct_CRC();
		static UScriptStruct* ReturnStruct = FindExistingStructIfHotReloadOrDynamic(Outer, TEXT("MyStruct"), sizeof(FMyStruct), Get_Z_Construct_UScriptStruct_FMyStruct_CRC(), false);
		if (!ReturnStruct)
		{
			ReturnStruct = new(EC_InternalUseOnlyConstructor, Outer, TEXT("MyStruct"), RF_Public|RF_Transient|RF_MarkAsNative) UScriptStruct(FObjectInitializer(), NULL, new UScriptStruct::TCppStructOps<FMyStruct>, EStructFlags(0x00000201));//直接創建UScriptStruct對象
			UProperty* NewProp_Score = new(EC_InternalUseOnlyConstructor, ReturnStruct, TEXT("Score"), RF_Public|RF_Transient|RF_MarkAsNative) UFloatProperty(CPP_PROPERTY_BASE(Score, FMyStruct), 0x0010000000000004);//直接關聯相應的Property信息
			ReturnStruct->StaticLink(); //鏈接
#if WITH_METADATA   //元數據
			UMetaData* MetaData = ReturnStruct->GetOutermost()->GetMetaData();
			MetaData->SetValue(ReturnStruct, TEXT("BlueprintType"), TEXT("true"));
			MetaData->SetValue(ReturnStruct, TEXT("ModuleRelativePath"), TEXT("MyStruct.h"));
			MetaData->SetValue(NewProp_Score, TEXT("Category"), TEXT("MyStruct"));
			MetaData->SetValue(NewProp_Score, TEXT("ModuleRelativePath"), TEXT("MyStruct.h"));
#endif
		}
		return ReturnStruct;
	}
	uint32 Get_Z_Construct_UScriptStruct_FMyStruct_CRC() { return 2914362188U; }
	UPackage* Z_Construct_UPackage__Script_Hello()
	{
		...略
	}
#endif

PRAGMA_ENABLE_DEPRECATION_WARNINGS

同理,會發現FMyStruct::StaticStruct內部也不會比Z_Construct_UScriptStruct_FMyStruct更多的事情,GetStaticStruct的實現也只是簡單的轉發到Z_Construct_UScriptStruct_FMyStruct。值得一提的是定義的ScriptStruct_Hello_StaticRegisterNativesFMyStruct,會在程序一啟動就調用UScriptStruct::DeferCppStructOps向程序注冊該結構的CPP信息(大小,內存對齊等),和TClassCompiledInDefer<TClass>的作用相當。FMyStruct的展開也是一目了然,就不再贅述了。

UINTERFACE的生成代碼剖析

UE對Interface也有支持,如果說FStruct就是一個純數據的POD容器,那么UInterface則是一個只能帶方法的純接口,比C++里的抽象類要根據的純粹一些。當然這里談的都只涉及到用UPROPERTY和UFUNCTION宏標記的那些,如果是純C++的字段和函數,UE並不能管到那么寬。
測試的MyInterface.h為:

#pragma once
#include "UObject/NoExportTypes.h"
#include "MyInterface.generated.h"

UINTERFACE(BlueprintType)
class UMyInterface : public UInterface
{
	GENERATED_UINTERFACE_BODY()    
};

class IMyInterface
{
	GENERATED_IINTERFACE_BODY()
public:
	UFUNCTION(BlueprintImplementableEvent)
	void BPFunc() const;
};

GENERATED_UINTERFACE_BODY和GENERATED_IINTERFACE_BODY都可以替換為GENERATED_BODY以提供一個默認的UMyInterface(const FObjectInitializer& ObjectInitializer)構造函數實現。不過GENERATED_IINTERFACE_BODY替換過后的效果也一樣,因為並不需要那么一個構造函數,所以用兩個都可以。
生成的MyInterface.generated.h如下:

PRAGMA_DISABLE_DEPRECATION_WARNINGS
#ifdef HELLO_MyInterface_generated_h
#error "MyInterface.generated.h already included, missing '#pragma once' in MyInterface.h"
#endif
#define HELLO_MyInterface_generated_h

#define Hello_Source_Hello_MyInterface_h_8_RPC_WRAPPERS
#define Hello_Source_Hello_MyInterface_h_8_RPC_WRAPPERS_NO_PURE_DECLS
#define Hello_Source_Hello_MyInterface_h_8_EVENT_PARMS
extern HELLO_API  FName HELLO_BPFunc;   //函數的名稱,在cpp中定義
#define Hello_Source_Hello_MyInterface_h_8_CALLBACK_WRAPPERS
#define Hello_Source_Hello_MyInterface_h_8_STANDARD_CONSTRUCTORS \
	/** Standard constructor, called after all reflected properties have been initialized */ \
	NO_API UMyInterface(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); \
	DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyInterface) \
	DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, UMyInterface); \
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(UMyInterface); \
private: \
	/** Private move- and copy-constructors, should never be used */ \
	NO_API UMyInterface(UMyInterface&&); \
	NO_API UMyInterface(const UMyInterface&); \
public:


#define Hello_Source_Hello_MyInterface_h_8_ENHANCED_CONSTRUCTORS \
	/** Standard constructor, called after all reflected properties have been initialized */ \
	NO_API UMyInterface(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; \
private: \
	/** Private move- and copy-constructors, should never be used */ \
	NO_API UMyInterface(UMyInterface&&); \
	NO_API UMyInterface(const UMyInterface&); \
public: \
	DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, UMyInterface); \
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(UMyInterface); \
	DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyInterface)


#undef GENERATED_UINTERFACE_BODY_COMMON
#define GENERATED_UINTERFACE_BODY_COMMON() \
	private: \
	static void StaticRegisterNativesUMyInterface(); \  //注冊
	friend HELLO_API class UClass* Z_Construct_UClass_UMyInterface(); \ //構造UClass*的方法
public: \
	DECLARE_CLASS(UMyInterface, UInterface, COMPILED_IN_FLAGS(CLASS_Abstract | CLASS_Interface), 0, TEXT("/Script/Hello"), NO_API) \
	DECLARE_SERIALIZER(UMyInterface) \
	enum {IsIntrinsic=COMPILED_IN_INTRINSIC};


#define Hello_Source_Hello_MyInterface_h_8_GENERATED_BODY_LEGACY \
		PRAGMA_DISABLE_DEPRECATION_WARNINGS \
	GENERATED_UINTERFACE_BODY_COMMON() \
	Hello_Source_Hello_MyInterface_h_8_STANDARD_CONSTRUCTORS \
	PRAGMA_ENABLE_DEPRECATION_WARNINGS


#define Hello_Source_Hello_MyInterface_h_8_GENERATED_BODY \
	PRAGMA_DISABLE_DEPRECATION_WARNINGS \
	GENERATED_UINTERFACE_BODY_COMMON() \
	Hello_Source_Hello_MyInterface_h_8_ENHANCED_CONSTRUCTORS \
private: \
	PRAGMA_ENABLE_DEPRECATION_WARNINGS


#define Hello_Source_Hello_MyInterface_h_8_INCLASS_IINTERFACE_NO_PURE_DECLS \
protected: \
	virtual ~IMyInterface() {} \
public: \
	typedef UMyInterface UClassType; \
	static void Execute_BPFunc(const UObject* O); \
	virtual UObject* _getUObject() const = 0;


#define Hello_Source_Hello_MyInterface_h_8_INCLASS_IINTERFACE \
protected: \
	virtual ~IMyInterface() {} \
public: \
	typedef UMyInterface UClassType; \
	static void Execute_BPFunc(const UObject* O); \
	virtual UObject* _getUObject() const = 0;


#define Hello_Source_Hello_MyInterface_h_5_PROLOG \
	Hello_Source_Hello_MyInterface_h_8_EVENT_PARMS


#define Hello_Source_Hello_MyInterface_h_13_GENERATED_BODY_LEGACY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
	Hello_Source_Hello_MyInterface_h_8_RPC_WRAPPERS \
	Hello_Source_Hello_MyInterface_h_8_CALLBACK_WRAPPERS \
	Hello_Source_Hello_MyInterface_h_8_INCLASS_IINTERFACE \
public: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS


#define Hello_Source_Hello_MyInterface_h_13_GENERATED_BODY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
	Hello_Source_Hello_MyInterface_h_8_RPC_WRAPPERS_NO_PURE_DECLS \
	Hello_Source_Hello_MyInterface_h_8_CALLBACK_WRAPPERS \
	Hello_Source_Hello_MyInterface_h_8_INCLASS_IINTERFACE_NO_PURE_DECLS \
private: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS


#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID Hello_Source_Hello_MyInterface_h


PRAGMA_ENABLE_DEPRECATION_WARNINGS

因為接口的定義需要用到兩個類,所以生成的信息稍微繁復了一些。不過使用的時候,我們的類只是繼承於IMyInterface,UMyInerface只是作為一個接口類型的載體,用以區分和查找不同的接口。觀察的時候,也請注意行號的定義。
從底往上,最后兩個是IMyInterface里的宏展開,細看之后,會發現_LEGACY和正常版本並沒有差別。展開后是:

class IMyInterface
{
protected: 
	virtual ~IMyInterface() {}  //禁止用接口指針釋放對象
public: 
	typedef UMyInterface UClassType;    //設定UMyInterface為關聯的類型
	static void Execute_BPFunc(const UObject* O);   //藍圖調用的輔助函數
	virtual UObject* _getUObject() const = 0;   //
public:
	UFUNCTION(BlueprintImplementableEvent)
	void BPFunc() const;
};

再往上是UMyInterface的生成,因為UMyInterface繼承於UObject的原因,所以也是從屬於Object系統的一份子,所以同樣需要遵循構造函數的規則。UInterface本身其實也可以算是UClass的一種,所以生成的代碼跟UClass中的生成都差不多,區別是用了COMPILED_IN_FLAGS(CLASS_Abstract | CLASS_Interface)的不同標記。有興趣的讀者可以自己展開看下。

生成的Hello.generated.cpp:

#include "Hello.h"
#include "GeneratedCppIncludes.h"
#include "Hello.generated.dep.h"
PRAGMA_DISABLE_DEPRECATION_WARNINGS
void EmptyLinkFunctionForGeneratedCode1Hello() {}
FName HELLO_BPFunc = FName(TEXT("BPFunc")); //名字的定義
	void IMyInterface::BPFunc() const   //讓編譯通過,同時加上錯誤檢測
	{
		check(0 && "Do not directly call Event functions in Interfaces. Call Execute_BPFunc instead.");
	}
	void UMyInterface::StaticRegisterNativesUMyInterface()
	{
	}
	IMPLEMENT_CLASS(UMyInterface, 4286549343);  //注冊類
	void IMyInterface::Execute_BPFunc(const UObject* O) //藍圖調用方法的實現
	{
		check(O != NULL);
		check(O->GetClass()->ImplementsInterface(UMyInterface::StaticClass()));//檢查是否實現了該接口
		UFunction* const Func = O->FindFunction(HELLO_BPFunc);  //通過名字找到方法
		if (Func)
		{
			const_cast<UObject*>(O)->ProcessEvent(Func, NULL);  //在該對象上調用該方法
		}
	}
#if USE_COMPILED_IN_NATIVES
	HELLO_API class UFunction* Z_Construct_UFunction_UMyInterface_BPFunc();
	HELLO_API class UClass* Z_Construct_UClass_UMyInterface_NoRegister();
	HELLO_API class UClass* Z_Construct_UClass_UMyInterface();
	HELLO_API class UPackage* Z_Construct_UPackage__Script_Hello();
	UFunction* Z_Construct_UFunction_UMyInterface_BPFunc()//構造BPFunc的UFunction
	{
		UObject* Outer=Z_Construct_UClass_UMyInterface();   //得到接口UMyInterface*對象
		static UFunction* ReturnFunction = NULL;
		if (!ReturnFunction)
		{
			ReturnFunction = new(EC_InternalUseOnlyConstructor, Outer, TEXT("BPFunc"), RF_Public|RF_Transient|RF_MarkAsNative) UFunction(FObjectInitializer(), NULL, 0x48020800, 65535); //直接構造函數對象
			ReturnFunction->Bind(); //綁定到函數指針
			ReturnFunction->StaticLink();   //鏈接
#if WITH_METADATA   //元數據
			UMetaData* MetaData = ReturnFunction->GetOutermost()->GetMetaData();
			MetaData->SetValue(ReturnFunction, TEXT("ModuleRelativePath"), TEXT("MyInterface.h"));
#endif
		}
		return ReturnFunction;
	}
	UClass* Z_Construct_UClass_UMyInterface_NoRegister()
	{
		return UMyInterface::StaticClass();
	}
	UClass* Z_Construct_UClass_UMyInterface()
	{
		static UClass* OuterClass = NULL;
		if (!OuterClass)
		{
			UInterface::StaticClass();  //確保基類UInterface已經元數據構造完成
			Z_Construct_UPackage__Script_Hello();
			OuterClass = UMyInterface::StaticClass();
			if (!(OuterClass->ClassFlags & CLASS_Constructed))
			{
				UObjectForceRegistration(OuterClass);
				OuterClass->ClassFlags |= 0x20004081;//CLASS_Constructed|CLASS_Interface|CLASS_Native|CLASS_Abstract

				OuterClass->LinkChild(Z_Construct_UFunction_UMyInterface_BPFunc());//添加子字段

				OuterClass->AddFunctionToFunctionMapWithOverriddenName(Z_Construct_UFunction_UMyInterface_BPFunc(), "BPFunc"); // 1371259725 ,添加函數名字映射
				OuterClass->StaticLink();   //鏈接
#if WITH_METADATA   //元數據
				UMetaData* MetaData = OuterClass->GetOutermost()->GetMetaData();
				MetaData->SetValue(OuterClass, TEXT("BlueprintType"), TEXT("true"));
				MetaData->SetValue(OuterClass, TEXT("ModuleRelativePath"), TEXT("MyInterface.h"));
#endif
			}
		}
		check(OuterClass->GetClass());
		return OuterClass;
	}
	static FCompiledInDefer Z_CompiledInDefer_UClass_UMyInterface(Z_Construct_UClass_UMyInterface, &UMyInterface::StaticClass, TEXT("UMyInterface"), false, nullptr, nullptr, nullptr);    //延遲注冊
	DEFINE_VTABLE_PTR_HELPER_CTOR(UMyInterface);
	UPackage* Z_Construct_UPackage__Script_Hello()
	{
		...略
	}
#endif

PRAGMA_ENABLE_DEPRECATION_WARNINGS

基本和UClass中的結構差不多,只是多了一些函數定義的過程和把函數添加到類中的操作。

UClass中的字段和函數生成代碼剖析

在最開始的時候,我們用了一個最簡單的UMyClass來闡述整體的結構。行百里者半九十,讓我們一鼓作氣,看看如果UMyClass里多了Property和Function之后又會起什么變化。
測試的MyClass.h如下:

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

UCLASS(BlueprintType)
class HELLO_API UMyClass : public UObject
{
	GENERATED_BODY()
public:
	UPROPERTY(BlueprintReadWrite)
	float Score;
public:
	UFUNCTION(BlueprintCallable, Category = "Hello")
	void CallableFunc();    //C++實現,藍圖調用

	UFUNCTION(BlueprintNativeEvent, Category = "Hello")
	void NativeFunc();  //C++實現默認版本,藍圖可重載實現

	UFUNCTION(BlueprintImplementableEvent, Category = "Hello")
	void ImplementableFunc();   //C++不實現,藍圖實現
};

增加了一個屬性和三個不同方法來測試。
其生成的MyClass.generated.h為(只包括改變部分):

#define Hello_Source_Hello_MyClass_h_8_RPC_WRAPPERS \
	virtual void NativeFunc_Implementation(); \ //默認實現的函數聲明,我們可以自己實現
 \
	DECLARE_FUNCTION(execNativeFunc) \  //聲明供藍圖調用的函數
	{ \
		P_FINISH; \
		P_NATIVE_BEGIN; \
		this->NativeFunc_Implementation(); \
		P_NATIVE_END; \
	} \
 \
	DECLARE_FUNCTION(execCallableFunc) \    //聲明供藍圖調用的函數
	{ \
		P_FINISH; \
		P_NATIVE_BEGIN; \
		this->CallableFunc(); \
		P_NATIVE_END; \
	}


#define Hello_Source_Hello_MyClass_h_8_RPC_WRAPPERS_NO_PURE_DECLS \ //和上面重復,略

//聲明函數名稱
extern HELLO_API  FName HELLO_ImplementableFunc;
extern HELLO_API  FName HELLO_NativeFunc;

因為CallableFunc是C++里實現的,所以這里並不需要再定義函數體。而另外兩個函數其實是在藍圖里定義的,就需要專門生成exec前綴的函數供藍圖虛擬機調用。
我們展開execCallableFunc后為:

void execCallableFunc( FFrame& Stack, void*const Z_Param__Result )  //藍圖虛擬機的使用的函數接口
{
    Stack.Code += !!Stack.Code; /* increment the code ptr unless it is null */
    { 
        FBlueprintEventTimer::FScopedNativeTimer ScopedNativeCallTimer;     //藍圖的計時統計
    	this->CallableFunc(); //調用我們自己的實現
    }
}

目前還是非常簡單的,當然根據函數簽名的不同會加上不同的參數傳遞,但是大體結構就是如此。以上的函數都是定義在UMyClass類內部的。
再來看Hello.generated.cpp里的變化(只包括改變部分):

//函數名字定義
FName HELLO_ImplementableFunc = FName(TEXT("ImplementableFunc"));
FName HELLO_NativeFunc = FName(TEXT("NativeFunc"));
	void UMyClass::ImplementableFunc()  //C++端的實現
	{
		ProcessEvent(FindFunctionChecked(HELLO_ImplementableFunc),NULL);
	}
	void UMyClass::NativeFunc() //C++端的實現
	{
		ProcessEvent(FindFunctionChecked(HELLO_NativeFunc),NULL);
	}
	void UMyClass::StaticRegisterNativesUMyClass()  //注冊函數名字和函數指針映射
	{
		FNativeFunctionRegistrar::RegisterFunction(UMyClass::StaticClass(), "CallableFunc",(Native)&UMyClass::execCallableFunc);
		FNativeFunctionRegistrar::RegisterFunction(UMyClass::StaticClass(), "NativeFunc",(Native)&UMyClass::execNativeFunc);
	}
//...略去中間相同部分
//構造3個函數的UFunction*對象,結構一樣,只是EFunctionFlags不一樣
UFunction* Z_Construct_UFunction_UMyClass_CallableFunc()
	{
		UObject* Outer=Z_Construct_UClass_UMyClass();
		static UFunction* ReturnFunction = NULL;
		if (!ReturnFunction)
		{
			ReturnFunction = new(EC_InternalUseOnlyConstructor, Outer, TEXT("CallableFunc"), RF_Public|RF_Transient|RF_MarkAsNative) UFunction(FObjectInitializer(), NULL, 0x04020401, 65535); //FUNC_BlueprintCallable|FUNC_Public|FUNC_Native|FUNC_Final
			ReturnFunction->Bind();
			ReturnFunction->StaticLink();
#if WITH_METADATA
			UMetaData* MetaData = ReturnFunction->GetOutermost()->GetMetaData();
			MetaData->SetValue(ReturnFunction, TEXT("Category"), TEXT("Hello"));
			MetaData->SetValue(ReturnFunction, TEXT("ModuleRelativePath"), TEXT("MyClass.h"));
#endif
		}
		return ReturnFunction;
	}
	UFunction* Z_Construct_UFunction_UMyClass_ImplementableFunc()
	{
		UObject* Outer=Z_Construct_UClass_UMyClass();
		static UFunction* ReturnFunction = NULL;
		if (!ReturnFunction)
		{
			ReturnFunction = new(EC_InternalUseOnlyConstructor, Outer, TEXT("ImplementableFunc"), RF_Public|RF_Transient|RF_MarkAsNative) UFunction(FObjectInitializer(), NULL, 0x08020800, 65535); //FUNC_BlueprintEvent|FUNC_Public|FUNC_Event
			ReturnFunction->Bind();
			ReturnFunction->StaticLink();
#if WITH_METADATA
			UMetaData* MetaData = ReturnFunction->GetOutermost()->GetMetaData();
			MetaData->SetValue(ReturnFunction, TEXT("Category"), TEXT("Hello"));
			MetaData->SetValue(ReturnFunction, TEXT("ModuleRelativePath"), TEXT("MyClass.h"));
#endif
		}
		return ReturnFunction;
	}
	UFunction* Z_Construct_UFunction_UMyClass_NativeFunc()
	{
		UObject* Outer=Z_Construct_UClass_UMyClass();
		static UFunction* ReturnFunction = NULL;
		if (!ReturnFunction)
		{
			ReturnFunction = new(EC_InternalUseOnlyConstructor, Outer, TEXT("NativeFunc"), RF_Public|RF_Transient|RF_MarkAsNative) UFunction(FObjectInitializer(), NULL, 0x08020C00, 65535);//FUNC_BlueprintEvent|FUNC_Public|FUNC_Event|FUNC_Native
			ReturnFunction->Bind();
			ReturnFunction->StaticLink();
#if WITH_METADATA
			UMetaData* MetaData = ReturnFunction->GetOutermost()->GetMetaData();
			MetaData->SetValue(ReturnFunction, TEXT("Category"), TEXT("Hello"));
			MetaData->SetValue(ReturnFunction, TEXT("ModuleRelativePath"), TEXT("MyClass.h"));
#endif
		}
		return ReturnFunction;
	}
//...略去中間相同部分
UClass* Z_Construct_UClass_UMyClass()
	{
		static UClass* OuterClass = NULL;
		if (!OuterClass)
		{
			Z_Construct_UClass_UObject();
			Z_Construct_UPackage__Script_Hello();
			OuterClass = UMyClass::StaticClass();
			if (!(OuterClass->ClassFlags & CLASS_Constructed))
			{
				UObjectForceRegistration(OuterClass);
				OuterClass->ClassFlags |= 0x20100080;
                //添加子字段
				OuterClass->LinkChild(Z_Construct_UFunction_UMyClass_CallableFunc());
				OuterClass->LinkChild(Z_Construct_UFunction_UMyClass_ImplementableFunc());
				OuterClass->LinkChild(Z_Construct_UFunction_UMyClass_NativeFunc());

PRAGMA_DISABLE_DEPRECATION_WARNINGS
				UProperty* NewProp_Score = new(EC_InternalUseOnlyConstructor, OuterClass, TEXT("Score"), RF_Public|RF_Transient|RF_MarkAsNative) UFloatProperty(CPP_PROPERTY_BASE(Score, UMyClass), 0x0010000000000004);//添加屬性
PRAGMA_ENABLE_DEPRECATION_WARNINGS
                //添加函數名字映射
				OuterClass->AddFunctionToFunctionMapWithOverriddenName(Z_Construct_UFunction_UMyClass_CallableFunc(), "CallableFunc"); // 774395847
				OuterClass->AddFunctionToFunctionMapWithOverriddenName(Z_Construct_UFunction_UMyClass_ImplementableFunc(), "ImplementableFunc"); // 615168156
				OuterClass->AddFunctionToFunctionMapWithOverriddenName(Z_Construct_UFunction_UMyClass_NativeFunc(), "NativeFunc"); // 3085959641
				OuterClass->StaticLink();
#if WITH_METADATA   //元數據
				UMetaData* MetaData = OuterClass->GetOutermost()->GetMetaData();
				MetaData->SetValue(OuterClass, TEXT("BlueprintType"), TEXT("true"));
				MetaData->SetValue(OuterClass, TEXT("IncludePath"), TEXT("MyClass.h"));
				MetaData->SetValue(OuterClass, TEXT("ModuleRelativePath"), TEXT("MyClass.h"));
				MetaData->SetValue(NewProp_Score, TEXT("Category"), TEXT("MyClass"));
				MetaData->SetValue(NewProp_Score, TEXT("ModuleRelativePath"), TEXT("MyClass.h"));
#endif
			}
		}
		check(OuterClass->GetClass());
		return OuterClass;
	}

可以看出,對於CallableFunc這種C++實現,藍圖只是調用的方法,生成的代碼只是生成了相應的UFunction*對象。而對於NativeFunc和ImplementableFunc,我們不會在C++里寫上它們的實現,因此為了編譯通過,也為了可以從C++端直接調用,就需要在生成的代碼的時候也同樣生成一份默認實現。
在之前的簡單類生成代碼中,StaticRegisterNativesUMyClass總是空的,在這里UHT為它加上了把函數注冊進程序內存的操作。
3個函數的UFunction*生成,雖然它們的調用方式大相徑庭,但是生成的代碼的方式卻是結構一致的,區別的只是不同的EFunctionFlags值。因此可以推斷出,更多的差異應該是在藍圖虛擬機的部分實現的,該部分知識以后介紹藍圖的時候再討論。
最后,把1個屬性和3個方法添加進UClass中,齊活收工。

總結

本篇篇幅較長,我們花了大量的敘述闡述UHT生成的代碼的樣式。首先從一個最簡單的UMyClass開始,觀察整體生成代碼的結構,接着推進到UMyEnum、UMyStruct、UMyInterface的代碼樣式,最后返歸到UMyClass在其中添加進屬性和方法,觀察屬性和方法是怎么生成代碼和怎么和UClass*對象關聯起來的。其實我們也發現,這個階段最重要的功能就是盡量的把程序的信息用代碼給記錄下來,對於Enum記下名字和值;對於Struct記下每個Property的名字和字節偏移;對於Interface記下每個函數或包裝函數的的函數指針和名字;對於Class同理都記下Property和Function。
當然,我們現在只能涉及到一些最簡單的屬性和方法類型,目的是讓讀者們對生成的代碼有個整體的概念,不要一下子陷入到了繁復的細節中去。觀察生成的代碼可知,其實就分兩部分,一是各種Z_輔助方法用來構造出各種UClass*等對象;另一部分是都包含着一兩個static對象用來在程序啟動的時候驅動登記,繼而調用到前者的Z_方法,最終完成注冊。
在了解到了生成的代碼是什么樣之后,下篇,我們就將深入到這些注冊的流程中去。

題外話

我們也可以很容易的看出,UHT生成的這份代碼並不是最簡潔的。比如生成的兩個宏,最終展開的結果卻一樣,又或者生成了空宏。這一方面原因固然是因為有很多的歷史遺留痕跡,另一方面也是因為在實現UHT的時候,為了照顧流程上的統一,並沒有精益求精的去優化掉冗余的分支,只是保留了下來。想法是反正UHT生成的代碼,不是給開發者讀的,所以亂點也無所謂了。最重要的是It works!所以也就沒什么人有去改進的動力了。
C++的代碼生成,一般都免不了需要用到大量宏的配合。讀者們如果想實現自己的代碼生成框架,筆者的建議是盡量的把公共的部分挪移到宏定義中去,並適當的利用模板,盡量最簡潔化生成代碼的書寫方式,比如在UHT中的:

FGuid Guid;
Guid.A = 0xDF4B1A6D;
Guid.B = 0x02873257;
Guid.C = 0x00000000;
Guid.D = 0x00000000;
//換成FGuid Guid(0xDF4B1A6D,0x02873257,0x00000000,0x00000000);就會簡潔的多。

生成代碼雖然不經常被人讀,但是在一些情況下添加腳本綁定,或者自己擴展功能,有一個清晰漂亮的代碼生成樣式,無疑能大大減少理解成本。

引用

UE4.14.3


知乎專欄:InsideUE4
UE4深入學習QQ群:456247757(非新手入門群,請先學習完官方文檔和視頻教程)
微信公眾號:aboutue,關於UE的一切新聞資訊、技巧問答、文章發布,歡迎關注。
個人原創,未經授權,謝絕轉載!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM