Version:4.26.2

UE4 C++工程名:MyProject \

一般语境下,我们说c++源码的编译大体分为:预处理、编译、链接; cppreference-translation_phases

虚幻引擎提供了UHT(Unreal Header Tool),在预处理之前来先处理源码中的各种宏标记并自动生成辅助代码;

那么宏GENERATED_BODY()为继承自UObject的类加了什么?

下面以一个最简单的类定义来追踪一下

环境

看下面的简单类:

file: Source/MyProject/Public/MyProject.h

#include "CoreMinimal.h"
#include "MyObject.generated.h" UCLASS()
class UMyObject : public UObject
{
GENERATED_BODY();
};

默认配置(Development_Editor, Win64)编译后,在工程根目录下打开目录:Intermediate/Build/Win64/UE4Editor/Inc/MyProject;

在该目录下可以找到MyObject.gen.cpp, MyObject.generated.h文件,使用VS Code打开这两个文件备用;

初步的宏展开

UE4涉及到UHT的宏都定义在Engine/Source/Runtime/CoreUObject/Public/UObjectMacros.h源文件中

宏GENERATED_BODY

GENERATED_BODY宏定义在引擎源文件Engine/Source/Runtime/CoreUObject/Public/UObjectMacros.h中,下面是源码的部分摘录:

// 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) // Include a redundant semicolon at the end of the generated code block, so that intellisense parsers can start parsing
// a new declaration if the line number/generated code is out of date.
#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); #define GENERATED_USTRUCT_BODY(...) GENERATED_BODY()
#define GENERATED_UCLASS_BODY(...) GENERATED_BODY_LEGACY()
#define GENERATED_UINTERFACE_BODY(...) GENERATED_BODY_LEGACY()
#define GENERATED_IINTERFACE_BODY(...) GENERATED_BODY_LEGACY()

初步宏展开会得到:

CURRENT_FILE_ID_15_GENERATED_BODY

头文件MyProject.h包含了MyProject.generated.h这个头文件,在MyProject.generated.h中,有如下定义:

#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID MyProject_Source_MyProject_Public_MyObject_h

进一步宏展开得到:

MyProject_Source_MyProject_Public_MyObject_h_15_GENERATED_BODY

查看MyProject.generated.h文件,这个这玩意其实也是一个宏定义:

#define MyProject_Source_MyProject_Public_MyObject_h_15_GENERATED_BODY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
MyProject_Source_MyProject_Public_MyObject_h_15_PRIVATE_PROPERTY_OFFSET \
MyProject_Source_MyProject_Public_MyObject_h_15_SPARSE_DATA \
MyProject_Source_MyProject_Public_MyObject_h_15_RPC_WRAPPERS_NO_PURE_DECLS \
MyProject_Source_MyProject_Public_MyObject_h_15_INCLASS_NO_PURE_DECLS \
MyProject_Source_MyProject_Public_MyObject_h_15_ENHANCED_CONSTRUCTORS \
private: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS

以当前MyObject.h和MyObject.generated.h中的宏定义展开后,会得到下面的UMyObject定义:

class UMyObject : public UObject
{
public:
// MyProject_Source_MyProject_Public_MyObject_h_15_PRIVATE_PROPERTY_OFFSET // 空宏
// MyProject_Source_MyProject_Public_MyObject_h_15_SPARSE_DATA // 空宏
// MyProject_Source_MyProject_Public_MyObject_h_15_RPC_WRAPPERS_NO_PURE_DECLS // 空宏 // ****************** INCLASS_NO_PURE_DECLS 开始 *****************
private:
static void StaticRegisterNativesUMyObject();
friend struct Z_Construct_UClass_UMyObject_Statics;
public:
DECLARE_CLASS(UMyObject, UObject, COMPILED_IN_FLAGS(0), CASTCLASS_None, TEXT("/Script/MyProject"), NO_API)
DECLARE_SERIALIZER(UMyObject)
// ****************** INCLASS_NO_PURE_DECLS 结束 ***************** // ****************** ENHANCED_CONSTRUCTORS 开始 ******************
/** Standard constructor, called after all reflected properties have been initialized */ \
NO_API UMyObject(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; \
private: \
/** Private move- and copy-constructors, should never be used */ \
NO_API UMyObject(UMyObject&&); \
NO_API UMyObject(const UMyObject&); \
public: \
DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, UMyObject); \
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(UMyObject); \
DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyObject)
// ****************** ENHANCED_CONSTRUCTORS 结束 ******************
private: };

到目前为止可以确定的,GENERATED_BODY宏做了下面这些事:

  1. 添加了一个静态函数static void StaticRegisterNativeUMyObject();
  2. 声明结构体struct Z_Construct_UClass_UMyObject_Statics;为friend
  3. 添加了一个public构造函数UMyObject(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
  4. 通过声明private: UMyObject(UMyObject&&); UMyObject(const UMyObject&&);禁用move和copy

进一步的宏展开

限于篇幅,下面不在给出宏展开后的class UMyObject完整定义代码

在初步的宏展开后,class UMyObject定义中还有一些在MyObject.h和MyObject.generated.h中没有找到的宏定义:

  • DECLARE_CLASS
  • DECLARE_SERIALIZER
  • DECLARE_VTABLE_PTR_HELPER_CTOR
  • DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER
  • DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL

这些宏定义在UE4源代码Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectMacros.h中

宏DECLARE_CLASS

#define DECLARE_CLASS( TClass, TSuperClass, TStaticFlags, TStaticCastFlags, TPackage, TRequiredAPI  ) \
private: \
TClass& operator=(TClass&&); \
TClass& operator=(const TClass&); \
TRequiredAPI static UClass* GetPrivateStaticClass(); \
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() \
{ \
return GetPrivateStaticClass(); \
} \
/** Returns the package this class belongs in */ \
inline static const TCHAR* StaticPackage() \
{ \
return TPackage; \
} \
/** Returns the static cast flags for this class */ \
inline static EClassCastFlags StaticClassCastFlags() \
{ \
return TStaticCastFlags; \
} \
/** 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; \
}

可以看到,宏DECLARE_CLASS做了下面这些事:

  1. 通过声明private: UMyObject& operator=(UMyObject&&); UMyObject& operator=(const UMyObject&&);禁用赋值动作
  2. 增加一个静态函数static UClass* GetPrivateStaticClass();
  3. 内部枚举enum {StaticClassFlags=};
  4. 内部类型定义Super,表示父类
  5. 内部类型定义ThisClass,表示该类的UClass对象
  6. 添加静态函数staic UClass* StaticClass();,用来返回当前类的UClass对象
  7. 添加静态函数static const TCHAR* StaticPackage();,返回当前类所属的包名
  8. 添加静态函数inline static EClassCastFlags StaticClassCastFlags(),放回当前类静态转型标记
  9. 重载operator new函数(如果有机会后面分享到UE4对象的内存管理在来详细分析)

宏DECLARE_SERIALIZER

#define DECLARE_SERIALIZER( TClass ) \
friend FArchive &operator<<( FArchive& Ar, TClass*& Res ) \
{ \
return Ar << (UObject*&)Res; \
} \
friend void operator<<(FStructuredArchive::FSlot InSlot, TClass*& Res) \
{ \
InSlot << (UObject*&)Res; \
}

该宏为自定义类提供了两个‘<<’运算符的重载版本

宏DECLARE_VTABLE_PTR_HELPER_CTOR和DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER

#define DECLARE_VTABLE_PTR_HELPER_CTOR(API, TClass) \
/** DO NOT USE. This constructor is for internal usage only for hot-reload purposes. */ \
API TClass(FVTableHelper& Helper); #if WITH_HOT_RELOAD
#define DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(TClass) \
static UObject* __VTableCtorCaller(FVTableHelper& Helper) \
{ \
return new (EC_InternalUseOnlyConstructor, (UObject*)GetTransientPackage(), NAME_None, RF_NeedLoad | RF_ClassDefaultObject | RF_TagGarbageTemp) TClass(Helper); \
}
#else // WITH_HOT_RELOAD
#define DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(TClass) \
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER_DUMMY()
#endif // WITH_HOT_RELOAD

这两个宏为自定义类添加特殊的构造函数声明和定义,参数为FVTableHelper& Helper

不过通过源码中的注释,该构造函数虽然是public的,但仅供引擎内部使用,用于提供热加载功能;这里打个todo标记,暂时不关注,知道有这么一东西就可以;

宏DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL

#define DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(TClass) \
static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass(X); }

又添加了一个静态函数:

static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass(X); }

注意:该函数体内是一个定位new表达式, UE4 Object的创建会用到;

宏UCLASS

最后是UCLASS宏:

#if UE_BUILD_DOCS || defined(__INTELLISENSE__ )
#define UCLASS(...)
#else
#define UCLASS(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_PROLOG)
#endif

宏UCLASS标记在自定义类定义体的外部;通过上面的宏定义源码:可以看到它是可以带参数的;但参数却没有传递给后面的宏;尤其是第一个条件下的定义就是一个空宏,所以不影响UMyObject类的定义;第二个条件下会拼接成另外一个宏, 但该拼接的宏在当前简单类中也是一个空宏;所以这里暂时略过;

总结

至此,一个简单的、加了UCLASS标记的、类定义中加了```GENERATED_BODY()``宏的、继承自UObject的自定义类的定义就开始清晰了;

也只有分析过后,才了解在Gameplay C++代码(自定义、引擎源码)中遇到的:比如Super、StaticClass()等是从哪里来的;

汇总一下,GENERATED_BODY宏给一个简单的自定类加了下面这些东西:

  1. 添加了一个静态函数static void StaticRegisterNativeUMyObject();
  2. 声明结构体struct Z_Construct_UClass_UMyObject_Statics;为friend
  3. 添加了一个public构造函数UMyObject(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
  4. 通过声明private: UMyObject(UMyObject&&); UMyObject(const UMyObject&&);禁用move和copy
  5. 通过声明private: UMyObject& operator=(UMyObject&&); UMyObject& operator=(const UMyObject&&);禁用赋值动作
  6. 增加一个静态函数static UClass* GetPrivateStaticClass();
  7. 内部枚举enum {StaticClassFlags=};
  8. 内部类型定义Super,表示父类
  9. 内部类型定义ThisClass,表示该类的UClass对象
  10. 添加静态函数staic UClass* StaticClass();,用来返回当前类的UClass对象
  11. 添加静态函数static const TCHAR* StaticPackage();,返回当前类所属的包名
  12. 添加静态函数inline static EClassCastFlags StaticClassCastFlags(),放回当前类静态转型标记
  13. 重载operator new函数(如果有机会后面分享到UE4对象的内存管理在来详细分析)

宏GENERATED_BODY做了什么?的更多相关文章

  1. UE4类型数据自动注册

    Version:4.26.2 UE4 C++工程名:MyProject 在<宏GENERATED_BODY做了什么?>中,简单分析了GENERATED_BODY宏给一个简单的.继承自UOb ...

  2. C++宏和枚举

    宏 我们的计算器程序,用1234对应加减乘除,对于人阅读很产生一点障碍.隔一个月后再看此代码可能想不起是0123还是1234了,还得去代码中查找,如果能为代表四则运算的四个数取个有意义的别名就好了,一 ...

  3. DELPHI中IDE宏录制小用

    用DELPHI的宏可以做一些非常简便的工作, 它是记录键盘的动作,如果我们将一些有规律的动作,用宏来进行操作,就可以达到事半功倍的效果,前提是编写的代码风格比较整洁. 宏是以Ctrl + Shift ...

  4. C语言宏定义取得两数的最大值和最小值

    /*本程序时为了验证用宏来做 * 两个数的大小比较的写法*/#include<stdio.h>#define MAX(x,y) ((x)<(y)?(y):(x))#define MI ...

  5. iOS开发笔记--宏定义的黑魔法 - 宏菜鸟起飞手册

    宏定义在C系开发中可以说占有举足轻重的作用.底层框架自不必说,为了编译优化和方便,以及跨平台能力,宏被大量使用,可以说底层开发离开define将寸步难行.而在更高层级进行开发时,我们会将更多的重心放在 ...

  6. C语言宏的特殊用法和几个坑(转)

    总结一下C语言中宏的一些特殊用法和几个容易踩的坑.由于本文主要参考GCC文档,某些细节(如宏参数中的空格是否处理之类)在别的编译器可能有细微差别,请参考相应文档. 宏基础 宏仅仅是在C预处理阶段的一种 ...

  7. 教你看懂C++类库函数定义之一---HRESULT 宏

    一切从一个C++ 类库头文件开始,现在在做一个C++的项目,期间用到一个开源的界面库DUILib(类似MFC),这个东西还不错能很容易的写出漂亮的界面,比如QQ的界面,可以去下载下来研究研究,地址:h ...

  8. 对 jiffies 溢出、回绕及 time_after 宏的理解

    原文如下: 关于jiffies变量:     全局变量jiffies用来记录自启动以来产生的节拍的总数.系统启动时会将该变量初始化为0,此后,每当时钟中断产生时就会增加该变量的值.jiffies和另外 ...

  9. C语言在宏定义中使用语句表达式和预处理器运算符

    语句表达式的亮点在于定义复杂功能的宏.使用语句表达式来定义宏,不仅可以实现复杂的功能,而且还能避免宏定义带来的歧义和漏洞.下面以一个简单的最小值的宏为例子一步步说明. 1.灰常简单的么,使用条件运算符 ...

随机推荐

  1. 无网络下,配置yum本地源

    1. 新建一个没有iso镜像文件的虚拟机: 2. 本地上传一个镜像文件(CentOS7的镜像),到虚拟机已创建的目录: 例如:上传一个镜像文件CentOS-7-x86_64-Everything-17 ...

  2. 对JavaScript中局部变量、全局变量和闭包的理解

    对js中局部变量.全局变量和闭包的理解 局部变量 对于局部变量,js给出的定义是这样的:在 JavaScript函数内部声明的变量(使用 var)是局部变量,所以只能在函数内部访问它.(该变量的作用域 ...

  3. 把字符串转换成整数 牛客网 剑指Offer

    把字符串转换成整数 牛客网 剑指Offer 题目描述 将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串 ...

  4. 设计模式(1-3)-动态代理(WeakCache的运用)

    阅读本篇文章前,请事先阅读 理解Java的强引用.软引用.弱引用和虚引用. 看看什么是强引用.什么是弱引用及它们的用途,很必要!!! 上一节讲到,获取对应的代理类时,首先会从缓存中去拿,若拿不到才会去 ...

  5. 『学了就忘』Linux基础 — 17、远程服务器关机及重启时的注意事项

    目录 1.为什么远程服务器不能关机 2.远程服务器重启时需要注意两点 3.不要在服务器访问高峰运行高负载命令 4.远程配置防火墙时不要把自己踢出服务器 5.指定合理的密码规范并定期更新 6.合理分配权 ...

  6. 写给初学者的Linux errno 错误码机制

    不同于Java的异常处理机制, 当你使用C更多的接触到是基于错误码的异常机制, 简单来说就是当调用的函数发生异常时, 程序不会跳转到一个统一处理异常的地方, 取而代之的是返回一个整型错误码. 可能会有 ...

  7. 第三课 Dubbo设计中的设计模式

    责任链模式  责任链模式在Dubbo中发挥的作用举足轻重,就像是Dubbo框架的骨架.Dubbo的调用链组织是用责任链模式串连起来的. 责任链中的每个节点实现Filter接口,然后由ProtocolF ...

  8. vue.js3 学习笔记 (一)——mixin 混入

    vue 2 中采用选项式API.如:data.methods.watch.computed以及生命周期钩子函数等等. mixin 混入,提供了一种非常灵活的方式,来分发 vue 组件中的可复用功能,一 ...

  9. 用js实现web端录屏

    用js实现web端录屏 原创2021-11-14 09:30·无意义的路过 随着互联网技术飞速发展,网页录屏技术已趋于成熟.例如可将录屏技术运用到在线考试中,实现远程监考.屏幕共享以及录屏等:而在我们 ...

  10. 微信小程序中使用canvas

    微信小程序中使用canvas会存在的一些问题: 由于小程序在绘制canvas的时候不能加载网络图片 所以需要把网络图片保存到本地之后再进行绘制 downLoadImg: function (netUr ...