本文章由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

终于等写到了结尾,太累人了。写完了,了却了一桩心事!



若有问题,请随时联系!!



谢谢浏览,欢迎点赞!!

深入理解UE4宏定义—— GENERATED_BODY的更多相关文章

  1. 关于malloc源码中的bin_at宏定义的个人见解

    0x01:简介 在堆中的内存申请和释放中,为了减少使用系统调用函数对内存操作,malloc_state(分配区)结构中使用了fastbinsY数组和bins数组.当chunk被free后,bins链会 ...

  2. c语言宏定义#define的理解与资料整理

    1. 利用define来定义 数值宏常量 #define 宏定义是个演技非常高超的替身演员,但也会经常耍大牌的,所以我们用它要慎之又慎.它可以出现在代码的任何地方,从本行宏定义开始,以后的代码就就都认 ...

  3. [转]c语言宏定义#define的理解与资料整理

    原文地址:http://www.cnblogs.com/haore147/p/3646934.html 1. 利用define来定义 数值宏常量 #define 宏定义是个演技非常高超的替身演员,但也 ...

  4. 如何为Swift进行宏定义

    这阵子一直在自学Swift, 因为之前iOS的开发一直用Objective-C, 所以习惯了C语言那种宏定义方式, Swift作为一款更加安全的语言, 放弃了C语言中的宏定义, 有效的防止预编译时代码 ...

  5. App开发流程之通用宏定义及头文件

    工欲善其事,必先利其器. 在正式实现各种炫酷的功能和UI前,做好准备工作是提高后续开发效率的必经之路. 所以,这个系列,我不是在各种堆技术,更关注的是“兵马动”之前的“粮草行”,有些繁琐,但当清晰理出 ...

  6. C中的预编译宏定义

     可以用宏判断是否为ARC环境 #if _has_feature(objc_arc) #else //MRC #endif C中的预编译宏定义 -- 作者: infobillows 来源:网络 在将一 ...

  7. 宏定义#define和typedef的区别和典型范例题目辨析

    宏定义#define pStr char*  ,是直接把程序中出现pStr的地方替换成char* ,直接替换: typedef  char * pStr; 是给char*定义一个别名叫做 pStr; ...

  8. define宏定义中的#,##,@#及\符号

    define宏定义中的#,##,@#及\符号 在#define中,标准只定义了#和##两种操作.#用来把参数转换成字符串,##则用来连接两个前后两个参数,把它们变成一个字符串. 1.# (string ...

  9. 宏定义中的##操作符和... and _ _VA_ARGS_ _

    1.Preprocessor Glue: The ## Operator 预处理连接符:##操作符 Like the # operator, the ## operator can be used i ...

随机推荐

  1. Spark --idea无法new scala class

    问题: 无法新建Scala class 解决: 1.下载插件 setting-->Plugins-->安装scala插件-->提示重启idea-->自动提示你安装scala s ...

  2. hadoop单击模式环境搭建

    一 安装jdk 下载相应版本的jdk安装到相应目录,我的安装目录是/usr/lib/jdk1.8.0_40 下载完成后,在/etc/profile中设置一下环境变量,在文件最后追加如下内容 expor ...

  3. Python3.x:日期库dateutil简介

    Python3.x:日期库dateutil简介 安装 pip install python-dateutil 关于parser #字符串可以很随意,可以用时间日期的英文单词,可以用横线.逗号.空格等做 ...

  4. UDP协议----简单的CS模型实现

    UDP简单介绍 传输层主要应用的协议模型有两种,一种是TCP协议,另外一种则是UDP协议.TCP协议在网络通信中占主导地位,绝大多数的网络通信借助TCP协议完成数据传输.但UDP也是网络通信中不可或缺 ...

  5. [Ctsc2000]冰原探险

    Description 传说中,南极有一片广阔的冰原,在冰原下藏有史前文明的遗址.整个冰原被横竖划分成了很多个大小相等的方格.在这个冰原上有N个大小不等的矩形冰山,这些巨大的冰山有着和南极一样古老的历 ...

  6. Java集合Collection&Map

    Map<K,V>是键值对,K - 此映射所维护的键的类型,V - 映射值的类型.键值是一一对应的关系: Collection是只有键,底层也是由键值对,但是值的类型被隐藏起来. Colle ...

  7. Bellman-Ford算法优化

    2017-07-27 16:02:48 writer:pprp 在BEllman-Ford算法中,其最外层的循环的迭代次数为n-1,如果不存在负权回路,需要迭代的次数是远远小于n-1; 如果在某一次迭 ...

  8. Linux(CentOS)网络配置

    1. 查看网口连接情况 2.修改网卡 3.修改主机名 4.重新启动网络 5.查看连接ifconfig,ping网络 copyright@2015 liupan liu.pan@datatom.com

  9. nginx重新加载配置(不停服)

    RT,改变配置想让它生效而不停止服务,如下两种方式都可以: 1) nginx -t;  nginx -s reload2) nginx -t;  kill -HUP

  10. 使用Executor管理Thread对象详解

    java SE5的java.util.concurrent包中的执行器(Executor)是管理Thread对象的优选方法.使用Executor管理Thread对象可以简化并发编程. Executor ...