本文章由cartzhang編寫,轉載請註明出處。 所有權利保留。
文章鏈接:http://blog.csdn.net/cartzhang/article/details/72834164
作者:cartzhang
一、GENERATED_BODY 都實現了什麼?
在前幾年的寫引擎代碼的時候,也類似使用過這些宏定義的方法,用法也是比較複雜的。現在就借UE4來回顧和分析一下。
測試版本:4.15
看例子:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "GameFramework/PawnMovementComponent.h"
#include "CollidingPawnMovementComponent.generated.h"
/**
*
*/
UCLASS()
class HOWTO_AUTOCAMERA_API UCollidingPawnMovementComponent : public UPawnMovementComponent
{
GENERATED_BODY()
public:
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
};
直接F12導航到 定義:
在ObjectMarcro.h 中的613行,裏面還有其他的,比方說之前版本的遺留解決方案。
重點就這幾行:
// This pair of macros is used to help implement GENERATED_BODY() and GENERATED_USTRUCT_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_LEGACY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY_LEGACY)
#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY)
GENERATED_BODY ————> BODY_MACRO_COMBINE ————> BODY_MACRO_COMBINE_INNER————>A##B##C##D
這裏需要注意的是,## 在C++宏定義中,這裏表示的是字符串的連接。
記住這行:// ##和# 的使用,##鏈接,#把字符變爲字符串
更多關於宏的用法,請參考老早之前的博客:
http://blog.csdn.net/cartzhang/article/details/22726167
GENERATED_BODY(),目的就是一個宏定義使用,一個字符串。
二、 字符串的作用
接下來說明 CURRENT_FILE_ID
這個是文件ID,在哪裏定義呢?就在頭文件CollidingPawnMovementComponent.generated.h裏面,倒數第二行。
可以看到
#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID HowTo_AutoCamera_Source_HowTo_AutoCamera_CollidingPawnMovementComponent_h
記得這裏需要先undef, 然後在define.
LINE 這是行號,也就是在當前文件中GENERATED_BODY()的行號,14 .
最終字符串的憑借出來是什麼呢?
HowTo_AutoCamera_Source_HowTo_AutoCamera_CollidingPawnMovementComponent_h_14_GENERATED_BODY
這個東西是不是有點眼生,沒有見過很正常。
在頭文件CollidingPawnMovementComponent.generated.h的第77行。
是不是有個一模一樣的宏定義啊。
這樣說來,GENERATED_BODY在函數中的作用就是一個宏定義。
也就是說:CollidingPawnMovementComponent.h的頭文件類聲明說這樣來代替:
/**
*
*/
UCLASS()
class HOWTO_AUTOCAMERA_API UCollidingPawnMovementComponent : public UPawnMovementComponent
{
HowTo_AutoCamera_Source_HowTo_AutoCamera_CollidingPawnMovementComponent_h_14_GENERATED_BODY
public:
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
};
聲明一下,這樣寫UE4 的編譯機制編譯不過。
因爲在HeaderParse.cpp中的4869行和4875行,
有這樣的判斷:
FError::Throwf(TEXT("Expected a GENERATED_BODY() at the start of class"));
也就是是接口類還是非接口類,都需要聲明GENERATED_BODY()。需要更詳細瞭解的,參考代碼吧。
三、類的主體
看宏定義:
#define HowTo_AutoCamera_Source_HowTo_AutoCamera_CollidingPawnMovementComponent_h_14_GENERATED_BODY \ // 宏定義。由GENERATED_BODY()來完成使用。
PRAGMA_DISABLE_DEPRECATION_WARNINGS \ // 去掉4995 和 4996 警告,警告壓棧。
public: \
HowTo_AutoCamera_Source_HowTo_AutoCamera_CollidingPawnMovementComponent_h_14_PRIVATE_PROPERTY_OFFSET \
HowTo_AutoCamera_Source_HowTo_AutoCamera_CollidingPawnMovementComponent_h_14_RPC_WRAPPERS_NO_PURE_DECLS \
HowTo_AutoCamera_Source_HowTo_AutoCamera_CollidingPawnMovementComponent_h_14_INCLASS_NO_PURE_DECLS \
HowTo_AutoCamera_Source_HowTo_AutoCamera_CollidingPawnMovementComponent_h_14_ENHANCED_CONSTRUCTORS \
private: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS // 恢復警告棧。 這與之前壓棧對應,用來恢復棧現場。
其他的可以自己看,根據猜測,就是私有屬性宏定義,前提不清楚就先不亂說了。
去除警告宏定義:
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS \
__pragma (warning(push)) \
__pragma (warning(disable:4995)) \
__pragma (warning(disable:4996))
#define PRAGMA_ENABLE_DEPRECATION_WARNINGS \
__pragma (warning(pop))
後面兩個很重要。
第一個:
#define HowTo_AutoCamera_Source_HowTo_AutoCamera_CollidingPawnMovementComponent_h_14_INCLASS_NO_PURE_DECLS \
private: \
static void StaticRegisterNativesUCollidingPawnMovementComponent(); \
friend HOWTO_AUTOCAMERA_API class UClass* Z_Construct_UClass_UCollidingPawnMovementComponent(); \
public: \
DECLARE_CLASS(UCollidingPawnMovementComponent, UPawnMovementComponent, COMPILED_IN_FLAGS(0 | CLASS_Config), 0, TEXT("/Script/HowTo_AutoCamera"), NO_API) \
DECLARE_SERIALIZER(UCollidingPawnMovementComponent) \
/** Indicates whether the class is compiled into the engine */ \
enum {IsIntrinsic=COMPILED_IN_INTRINSIC};
這裏面有靜態函數類的註冊。也就是UCollidingPawnMovementComponent類的註冊。
類的聲明DECLARE_CLASS,在頭文件ObjectMacro.h的1318行。
/*-----------------------------------------------------------------------------
Class declaration macros.
-----------------------------------------------------------------------------*/
#define DECLARE_CLASS( TClass, TSuperClass, TStaticFlags, TStaticCastFlags, TPackage, TRequiredAPI ) \
private: \
TClass& operator=(TClass&&); \ 賦值函數
TClass& operator=(const TClass&); \ const 賦值
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() \
// 靜態函數使用GetPrivateStaticClass
{ \
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; \
}
主要實現一個靜態函數,獲取UClass;對New的重載。
四、註冊過程
現在有疑問了,上面的類的註冊怎麼個註冊過程呢?
StaticRegisterNativesUCollidingPawnMovementComponent 和Z_Construct_UClass_UCollidingPawnMovementComponent 這個東西,怎麼在代碼中使用呢?
看到類型來麼?居然是UClass類型,也就是說他是UClass的友元函數。
UClass在Class.h,但是這個調用實現在.cpp中實現。
具體在Class.cpp的4332行,又是一個宏定義。
IMPLEMENT_CORE_INTRINSIC_CLASS(UClass, UStruct,
{
Class->ClassAddReferencedObjects = &UClass::AddReferencedObjects;
Class->EmitObjectReference(STRUCT_OFFSET(UClass, ClassDefaultObject), TEXT("ClassDefaultObject"));
Class->EmitObjectReference(STRUCT_OFFSET(UClass, ClassWithin), TEXT("ClassWithin"));
Class->EmitObjectReference(STRUCT_OFFSET(UClass, ClassGeneratedBy), TEXT("ClassGeneratedBy"));
Class->EmitObjectArrayReference(STRUCT_OFFSET(UClass, NetFields), TEXT("NetFields"));
}
);
核心就在這裏:
// Used for intrinsics, this sets up the boiler plate, plus an initialization singleton, which can create properties and GC tokens
#define IMPLEMENT_INTRINSIC_CLASS(TClass, TRequiredAPI, TSuperClass, TSuperRequiredAPI, InitCode) \
IMPLEMENT_CLASS(TClass, 0) \ // 看這裏,看這裏。
TRequiredAPI UClass* Z_Construct_UClass_##TClass(); \
UClass* Z_Construct_UClass_##TClass() \
{ \
static UClass* Class = NULL; \
if (!Class) \
{ \
extern TSuperRequiredAPI UClass* Z_Construct_UClass_##TSuperClass(); \
UClass* SuperClass = Z_Construct_UClass_##TSuperClass(); \
Class = TClass::StaticClass(); \
UObjectForceRegistration(Class); \
check(Class->GetSuperClass() == SuperClass); \
InitCode \
Class->StaticLink(); \
} \
check(Class->GetClass()); \
return Class; \
} \
static FCompiledInDefer Z_CompiledInDefer_UClass_##TClass(Z_Construct_UClass_##TClass, &TClass::StaticClass, TEXT(#TClass), false);
通過初始化靜態單例,來實現對類的註冊。這段代碼對比了4.7版本,完全一樣,沒有做過修改,但是文件名稱變化了。
這個Z_Construct_UClass_##TClass()是不是有點熟悉,對了,就是這裏實現了友元函數的函數體,在UObjectCompiledInDefer中實現了註冊。
這個Class = TClass::StaticClass(); \ 就是在ObjectMacros.h中的1331行的
inline static UClass* StaticClass() \
{ \
return GetPrivateStaticClass(TPackage); \
} \
而 GetPrivateStaticClass 就是在ObjectMacros.h中的1512行的IMPLEMENT_CLASS中進行了函數體的實現。
看到上面的IMPLEMENT_CLASS(TClass, 0) \ // 看這裏,看這裏。
完美了。
謎底就在這裏。這個宏定義裏面實現了在開始的時候註冊類。其中第四個參數,StaticRegisterNatives##TClass,是一個回調函數,可以回調剛纔我們StaticRegisterNativesUCollidingPawnMovementComponent 這個函數。
五、與 UE4 之前4.7版本對比
我的印象中,早期的UE4版本,GENERATED_BODY 是分開的,有GENERATED_UCLASS_BODY、GENERATED_USTRUCT_BODY等。
重新打開之前的工程,確實代碼宏定義有很大的變化。
之前的版本宏定義寫的調用比現在簡單,寫法是一樣的,就是調用過程,用來多個宏來實現,不像現在爲了讓外部或對外好看好編寫代碼,工作都放在了底層內部來處理。
這就是把困難留在內部,把優雅簡單給你!
若對上面的這些過程不太名稱,建議可以參考4.7或之前的版本。
由於UE4龐大的宏定義和系統的高複雜度,我儘量用代碼文件名和行數來說明調用過程。
各種來回切換,還需要各位針對引擎自己來看,總體的思路需要仔細來看,應該說的還算明白的。
話有說回來,EPIC集成了全世界優秀的程序員來幹了百年人工的引擎,你一個小時完全搞明白了,那我跪求大神帶我飛!!
六、隨手畫了張圖,可以結合看。
七、 參考
【1】 https://docs.unrealengine.com/latest/INT/Programming/Tutorials/Components/3/index.html
【2】 http://blog.csdn.net/cartzhang/article/details/22726167
終於等寫到了結尾,太累人了。寫完了,了卻了一樁心事!
若有問題,請隨時聯繫!!
謝謝瀏覽,歡迎點贊!!