深入理解UE4宏定义—GENERATED_BODY

发表于2017-06-11
评论1 6.7k浏览

一、GENERATED_BODY 都实现了什么?

之前写引擎代码的时候,也类似使用过这些宏定义的方法,用法也是比较复杂的。现在就借UE4来回顾和分析一下。 

测试版本:4.15 
看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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行,里面还有其他的,比方说之前版本的遗留解决方案。 

重点就这几行:

1
2
3
4
5
6
// 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++宏定义中,这里表示的是字符串的连接。 
记住这行:// ##和# 的使用,##链接,#把字符变为字符串 

GENERATED_BODY(),目的就是一个宏定义使用,一个字符串。


二、 字符串的作用

接下来说明 CURRENT_FILE_ID 
这个是文件ID,在哪里定义呢?就在头文件CollidingPawnMovementComponent.generated.h里面,倒数第二行。 
可以看到

1
2
#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的头文件类声明说这样来代替:


声明一下,这样写UE4 的编译机制编译不过。 
因为在HeaderParse.cpp中的4869行和4875行, 
有这样的判断:

FError::Throwf(TEXT("Expected a GENERATED_BODY() at the start of class"));
1
2
3
4
5
6
7
8
9
10
11
12
/**
 *
 */
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;
 
};


也就是是接口类还是非接口类,都需要声明GENERATED_BODY()。需要更详细了解的,参考代码吧。

三、类的主体

看宏定义:

1
2
3
4
5
6
7
8
9
#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   // 恢复警告栈。 这与之前压栈对应,用来恢复栈现场。


其他的可以自己看,根据猜测,就是私有属性宏定义,前提不清楚就先不乱说了。 

去除警告宏定义:

1
2
3
4
5
6
7
8
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS
            __pragma (warning(push))
            __pragma (warning(disable:4995))
            __pragma (warning(disable:4996))
 
 
#define PRAGMA_ENABLE_DEPRECATION_WARNINGS
            __pragma (warning(pop))


后面两个很重要。 
第一个:

1
2
3
4
5
6
7
8
9
#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行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*-----------------------------------------------------------------------------
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行,又是一个宏定义。

1
2
3
4
5
6
7
8
9
10
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"));
    }
);

核心就在这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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行的

1
2
3
4
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集成了全世界优秀的程序员来干了百年人工的引擎,你一个小时完全搞明白了,那我跪求大神带我飞!!


六、随手画了张图,可以结合看。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论