深入理解UE4宏定義—— GENERATED_BODY


本文章由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 \  // 去掉49954996 警告,警告壓棧。
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


終於等寫到了結尾,太累人了。寫完了,了卻了一樁心事!

若有問題,請隨時聯繫!!

謝謝瀏覽,歡迎點贊!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章