本文章由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_BODYpublic: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. UE 宏定义GENERATED_BODY

    在学习UE4的时候,看到C++工程中,在实现父类的BeginPlay()和Tick()以及SetupPlayerInputComponent()这些虚函数时,都能看到里面有用到Super类型,如下: ...

  2. libev中ev_loop结构体中宏定义的理解

    libev源代码由于有各种宏定义,十分让人费解,作者这么写确实使得代码很简练,但也给读者的阅读带来了巨大的麻烦,下面将分析下ev_loop这个结构体的定义,加深对作者代码简化的理解,先上代码: str ...

  3. 理解OpenCV中的宏定义 CVAPI(函数返回类型)

    图像处理开发需求.图像处理接私活挣零花钱,请加微信/QQ 2487872782 图像处理开发资料.图像处理技术交流请加QQ群,群号 271891601 读代码时看到语句 CVAPI(IplImage* ...

  4. 【GCC系列】深入理解Linux内核 -- __no_sanitize_address宏定义

    本文使用的Linux源码内核版本:Linux 5.10.0 __no_sanitize_address宏定义: 在Linux的内核源码里,尤其是一些关键的内核函数,会包含__no_sanitize_a ...

  5. STM32 理解宏定义的重要性

    今天做STM32F107系列单片机与ADM2587E的开发时,遇到了一个很郁闷的问题,通过串口发送数据后,单片机接收端的指示灯亮,但没我有返回值(通过接收标志发送数据).代码反反复复检查好几遍就是找不 ...

  6. 【GCC系列】深入理解Linux内核 -- __visible宏定义

    __visible宏定义 在Linux的内核源码里,尤其是一些关键的内核函数,会包含__visible宏,它是用来做什么的呢? asmlinkage __visible void __init __n ...

  7. 【UE4基础】全面理解UE4委托

    转载自:全面理解UE4委托 - 可可西 - 博客园UE4中的delegate(委托)常用于解耦不同对象之间的关联:委托的触发者不与监听者有直接关联,两者通过委托对象间接地建立联系 监听者通过将响应函数 ...

  8. 如何用C语言改变宏定义的大小,C语言中宏定义使用的小细节

    C语言中宏定义使用的小细节 #pragma#pragma 预处理指令详解 在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作.#p ...

  9. c 宏定义用法#define

    转自:https://blog.csdn.net/boring_wednesday/article/details/78756696 宏定义 语法     #define name Stuff #de ...

最新文章

  1. 不是内部或外部命令 windows10 执行 linux命令
  2. kafka的topic命名技巧
  3. Codeforces 755B. PolandBall and Game 贪心
  4. 编程之美-最大公约数问题方法整理
  5. 欧姆龙plc解密实例_西门子、施耐德、欧姆龙等13大PLC品牌8000个实例程序资料包...
  6. 深度学习框架Keras介绍及实战
  7. 佳能MP258的5200错误和P08代码解决方法及清零方法
  8. 从Unreal Engine 3到Unreal Engine 4
  9. Spark用DSL表达式如何使用row_number函数
  10. PLY——Python Lex Yacc
  11. windows下配置NGINX实现内网穿透并配置开机自启动
  12. 向iframe载入html,为iFrame添加动态载入效果,提高用户体验
  13. 搭建基于 Nginx 的 RTMP 服务器,并用 ffmpeg 来进行推流和拉流
  14. 【中山市选2008】三角形
  15. 六边形俄罗斯方块游戏创意
  16. Java使用QQ邮箱发送邮件
  17. 小白兔卖菜是计算机作文,白兔的菜作文
  18. 微信辅助验证 信息填写正确 却提示绑卡信息错误的解决办法
  19. 支付宝Html授权,网页版的支付宝授权登录(vue+java)
  20. 长寿命激光在线测径仪 关键看细节

热门文章

  1. 手机显示屏行业调研报告 - 市场现状分析与发展前景预测
  2. 计算机组成原理实验单周期处理,计算机组成原理实验实验报告-单周期cpu设计...
  3. Git命令推送文件到远程仓库——码云还是GitHub小猫咪都可噢
  4. 本地项目局域网内的调试
  5. P2194 HXY烧情侣(tarjan组合数学)
  6. Win10右键管理提示“该文件没有与之关联的程序来执行此操作”怎么办
  7. 第1章 ZigBee协议栈初始化网络启动流程
  8. 计算机考试报名照片显示池式连接请求超时,ping请求超时的解决方法
  9. C++内存对齐(可可西博客)
  10. H3C S5500三层交换机划分Vlan与H3C路由组网