In order to explore the reflection mechanism of UE4, let's start with simplicity and manually simulate generated H generation process to see what UHT has done.
Take a very simple class as the analysis object
UCLASS(meta=(IsBlueprintBase = "false")) class RPGGAME_API ARpgPlayer : public ARpgGameCharacter { public: GENERATED_BODY() public: UFUNCTION(BlueprintCallable) void BlueprintCallableFunction(); UFUNCTION(BlueprintPure) int BlueprintPureFunction(); UFUNCTION(BlueprintNativeEvent) void BlueprintNativeEventFunction(); void BlueprintNativeEventFunction_Implementation(); public: UPROPERTY(BlueprintReadWrite) float PlayerHP = 2.0f; };
GENERATED_BODY() is defined as follows
// 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);
It can be concluded that generated_ After body () is replaced, it becomes the following code
UCLASS(meta=(IsBlueprintBase = "false")) class RPGGAME_API ARpgPlayer : public ARpgGameCharacter { CURRENT_FILE_ID_#__LINE__#_GENERATED_BODY ... };
Among them__ LINE__ Yes, it needs to be replaced with generated_ The actual number of lines of the body macro is 15, so it becomes
UCLASS(meta=(IsBlueprintBase = "false")) class RPGGAME_API ARpgPlayer : public ARpgGameCharacter { CURRENT_FILE_ID_15_GENERATED_BODY ... };
CURRENT_ FILE_ What is the ID? The secret is in rpgplayer generated. H, we can open this file and see that the header file has
#define CURRENT_FILE_ID RpgGame_Source_RpgGame_RpgPlayer_h
Replace this macro, and it becomes
UCLASS(meta=(IsBlueprintBase = "false")) class RPGGAME_API ARpgPlayer : public ARpgGameCharacter { RpgGame_Source_RpgGame_RpgPlayer_h_15_GENERATED_BODY ... };
And rpggame_ Source_ RpgGame_ RpgPlayer_ h_ 15_ GENERATED_ What is body? It is also a macro! Also defined in rpgplayer generated. H inside, as follows
#define RpgGame_Source_RpgGame_RpgPlayer_h_15_GENERATED_BODY \ PRAGMA_DISABLE_DEPRECATION_WARNINGS \ public: \ RpgGame_Source_RpgGame_RpgPlayer_h_15_PRIVATE_PROPERTY_OFFSET \ RpgGame_Source_RpgGame_RpgPlayer_h_15_SPARSE_DATA \ RpgGame_Source_RpgGame_RpgPlayer_h_15_RPC_WRAPPERS_NO_PURE_DECLS \ RpgGame_Source_RpgGame_RpgPlayer_h_15_INCLASS_NO_PURE_DECLS \ RpgGame_Source_RpgGame_RpgPlayer_h_15_ENHANCED_CONSTRUCTORS \ private: \ PRAGMA_ENABLE_DEPRECATION_WARNINGS
We'll replace it
UCLASS(meta=(IsBlueprintBase = "false")) class RPGGAME_API ARpgPlayer : public ARpgGameCharacter { PRAGMA_DISABLE_DEPRECATION_WARNINGS public: RpgGame_Source_RpgGame_RpgPlayer_h_15_PRIVATE_PROPERTY_OFFSET RpgGame_Source_RpgGame_RpgPlayer_h_15_SPARSE_DATA RpgGame_Source_RpgGame_RpgPlayer_h_15_RPC_WRAPPERS_NO_PURE_DECLS RpgGame_Source_RpgGame_RpgPlayer_h_15_INCLASS_NO_PURE_DECLS RpgGame_Source_RpgGame_RpgPlayer_h_15_ENHANCED_CONSTRUCTORS private: PRAGMA_ENABLE_DEPRECATION_WARNINGS public: UFUNCTION(BlueprintCallable) void BlueprintCallableFunction(); UFUNCTION(BlueprintPure) int BlueprintPureFunction(); UFUNCTION(BlueprintNativeEvent) void BlueprintNativeEventFunction(); void BlueprintNativeEventFunction_Implementation(); public: UPROPERTY(BlueprintReadWrite) float PlayerHP = 2.0f; };
These macros can be found in rpgplayer generated. I found it inside.
-
First look at PRAGMA_DISABLE_DEPRECATION_WARNINGS: for this macro, we can find several definitions in the engine, but generally do the same thing, that is, eliminate some compilation warnings. Different warnings will be reported when code is compiled on different platforms. Therefore, this macro is defined on different platforms, which is irrelevant. Therefore, skip this macro.
-
RpgGame_Source_RpgGame_RpgPlayer_h_15_PRIVATE_PROPERTY_OFFSET: This is also in rpgplayer generated. H is defined, but it is null.
-
RpgGame_Source_RpgGame_RpgPlayer_h_15_SPARSE_DATA: defined as null value.
-
RpgGame_Source_RpgGame_RpgPlayer_h_15_RPC_WRAPPERS_NO_PURE_DECLS: defined as follows
#define RpgGame_Source_RpgGame_RpgPlayer_h_15_RPC_WRAPPERS_NO_PURE_DECLS \ \ DECLARE_FUNCTION(execBlueprintNativeEventFunction); \ DECLARE_FUNCTION(execBlueprintPureFunction); \ DECLARE_FUNCTION(execBlueprintCallableFunction);
Here's a question. We have defined five functions. Why are there only three exe functions in this macro? The blueprintable and blueprintable native rules handle the blueprintable and blueprintable events_ The implementation function is not handled. Where are these two functions? We check the gen.cpp file and find the following code
static FName NAME_ARpgPlayer_BlueprintImplementableEventFunction = FName(TEXT("BlueprintImplementableEventFunction")); void ARpgPlayer::BlueprintImplementableEventFunction() { ProcessEvent(FindFunctionChecked(NAME_ARpgPlayer_BlueprintImplementableEventFunction),NULL); } static FName NAME_ARpgPlayer_BlueprintNativeEventFunction = FName(TEXT("BlueprintNativeEventFunction")); void ARpgPlayer::BlueprintNativeEventFunction() { ProcessEvent(FindFunctionChecked(NAME_ARpgPlayer_BlueprintNativeEventFunction),NULL); }
As you can see, these two functions are reserved! No special treatment.
-
RpgGame_Source_RpgGame_RpgPlayer_h_15_CALLBACK_WRAPPERS: defined as null.
-
RpgGame_Source_RpgGame_RpgPlayer_h_15_INCLASS_NO_PURE_DECLS:
#define RpgGame_Source_RpgGame_RpgPlayer_h_15_INCLASS_NO_PURE_DECLS \ private: \ static void StaticRegisterNativesARpgPlayer(); \ friend struct Z_Construct_UClass_ARpgPlayer_Statics; \ public: \ DECLARE_CLASS(ARpgPlayer, ARpgGameCharacter, COMPILED_IN_FLAGS(0 | CLASS_Config), CASTCLASS_None, TEXT("/Script/RpgGame"), NO_API) \ DECLARE_SERIALIZER(ARpgPlayer)
- RpgGame_Source_RpgGame_RpgPlayer_h_15_ENHANCED_CONSTRUCTORS:
#define RpgGame_Source_RpgGame_RpgPlayer_h_15_ENHANCED_CONSTRUCTORS \ /** Standard constructor, called after all reflected properties have been initialized */ \ NO_API ARpgPlayer() { }; \ private: \ /** Private move- and copy-constructors, should never be used */ \ NO_API ARpgPlayer(ARpgPlayer&&); \ NO_API ARpgPlayer(const ARpgPlayer&); \ public: \ DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, ARpgPlayer); \ DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(ARpgPlayer); \ DEFINE_DEFAULT_CONSTRUCTOR_CALL(ARpgPlayer)
Why is the definition of some macros empty? It may be that our test case is very simple. There is only one variable and several simple functions in it, so there is no much data and no more complex test has been carried out.
After replacing this wave macro, the following code is obtained
UCLASS(meta=(IsBlueprintBase = "false")) class RPGGAME_API ARpgPlayer : public ARpgGameCharacter { public: DECLARE_FUNCTION(execBlueprintNativeEventFunction); DECLARE_FUNCTION(execBlueprintPureFunction); DECLARE_FUNCTION(execBlueprintCallableFunction); void BlueprintNativeEventFunction(); void BlueprintNativeEventFunction_Implementation(); private: static void StaticRegisterNativesARpgPlayer(); friend struct Z_Construct_UClass_ARpgPlayer_Statics; public: DECLARE_CLASS(ARpgPlayer, ARpgGameCharacter, COMPILED_IN_FLAGS(0 | CLASS_Config), CASTCLASS_None, TEXT("/Script/RpgGame"), NO_API) DECLARE_SERIALIZER(ARpgPlayer) /** Standard constructor, called after all reflected properties have been initialized */ \ NO_API ARpgPlayer() { }; private: /** Private move- and copy-constructors, should never be used */ NO_API ARpgPlayer(ARpgPlayer&&); NO_API ARpgPlayer(const ARpgPlayer&); public: DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, ARpgPlayer); DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(ARpgPlayer); DEFINE_DEFAULT_CONSTRUCTOR_CALL(ARpgPlayer) private: public: UPROPERTY(BlueprintReadWrite) float PlayerHP = 2.0f; };
It seems that the code is clearer, but there are still some macros. Let's continue to replace them
- DECLARE_CLASS: this macro is defined in objectmacros H medium
#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_SERIALIZER: also defined in objectmacros In H,
#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_FUNCTION: defined in objectmacros H medium. It is easy to see from the comments that this macro is very simple and has no nesting. It just wraps the declaration of the function.
// This macro is used to declare a thunk function in autogenerated boilerplate code #define DECLARE_FUNCTION(func) static void func( UObject* Context, FFrame& Stack, RESULT_DECL )
-
We replace these macros and get the following code
UCLASS(meta=(IsBlueprintBase = "false")) class RPGGAME_API ARpgPlayer : public ARpgGameCharacter { public: static void execBlueprintNativeEventFunction( UObject* Context, FFrame& Stack, RESULT_DECL ); static void execBlueprintPureFunction( UObject* Context, FFrame& Stack, RESULT_DECL ); static void execBlueprintCallableFunction( UObject* Context, FFrame& Stack, RESULT_DECL ); void BlueprintNativeEventFunction(); void BlueprintNativeEventFunction_Implementation(); private: static void StaticRegisterNativesARpgPlayer(); friend struct Z_Construct_UClass_ARpgPlayer_Statics; public: private: ARpgPlayer& operator=(ARpgPlayer&&); ARpgPlayer& operator=(const ARpgPlayer&); NO_API static UClass* GetPrivateStaticClass(); public: /** Bitwise union of #EClassFlags pertaining to this class.*/ enum {StaticClassFlags=COMPILED_IN_FLAGS(0 | CLASS_Config)}; /** Typedef for the base class ({{ typedef-type }}) */ typedef ARpgGameCharacter Super; /** Typedef for {{ typedef-type }}. */ typedef ARpgPlayer 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 TEXT("/Script/RpgGame"); } /** Returns the static cast flags for this class */ inline static EClassCastFlags StaticClassCastFlags() { return CASTCLASS_None; } /** 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; } friend FArchive &operator<<( FArchive& Ar, ARpgPlayer*& Res ) { return Ar << (UObject*&)Res; } friend void operator<<(FStructuredArchive::FSlot InSlot, ARpgPlayer*& Res) { InSlot << (UObject*&)Res; } /** Standard constructor, called after all reflected properties have been initialized */ \ NO_API ARpgPlayer() { }; private: /** Private move- and copy-constructors, should never be used */ NO_API ARpgPlayer(ARpgPlayer&&); NO_API ARpgPlayer(const ARpgPlayer&); public: DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, ARpgPlayer); DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(ARpgPlayer); DEFINE_DEFAULT_CONSTRUCTOR_CALL(ARpgPlayer) private: public: UPROPERTY(BlueprintReadWrite) float PlayerHP = 2.0f; };
There are still a few macros. Let's continue to explore
-
#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);
- DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER
#define DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER_DUMMY() \ static UObject* __VTableCtorCaller(FVTableHelper& Helper) \ { \ return nullptr; \ } #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
- DEFINE_DEFAULT_CONSTRUCTOR_CALL
#define DEFINE_DEFAULT_CONSTRUCTOR_CALL(TClass) \ static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass; }
-
UPROPERTY. It can be seen that these macros are only used to assist UHT scanning source code, and there is no effective definition in practice.
// These macros wrap metadata parsed by the Unreal Header Tool, and are otherwise // ignored when code containing them is compiled by the C++ compiler #define UPROPERTY(...) #define UFUNCTION(...) #define USTRUCT(...) #define UMETA(...) #define UPARAM(...) #define UENUM(...) #define UDELEGATE(...) #define RIGVM_METHOD(...)
After all replacement, the approximate code is as follows
UCLASS(meta=(IsBlueprintBase = "false")) class RPGGAME_API ARpgPlayer : public ARpgGameCharacter { public: static void execBlueprintNativeEventFunction( UObject* Context, FFrame& Stack, void*const Z_Param__Result); static void execBlueprintPureFunction( UObject* Context, FFrame& Stack, void*const Z_Param__Result); static void execBlueprintCallableFunction( UObject* Context, FFrame& Stack, void*const Z_Param__Result); private: static void StaticRegisterNativesARpgPlayer(); friend struct Z_Construct_UClass_ARpgPlayer_Statics; //Defined in gen.cpp public: private: ARpgPlayer& operator=(ARpgPlayer&&); //Prohibit moving assignment ARpgPlayer& operator=(const ARpgPlayer&); //Copy assignment prohibited NO_API static UClass* GetPrivateStaticClass(); public: /** Bitwise union of #EClassFlags pertaining to this class.*/ enum {StaticClassFlags=COMPILED_IN_FLAGS(0 | CLASS_Config)}; /** Typedef for the base class ({{ typedef-type }}) */ typedef ARpgGameCharacter Super; /** Typedef for {{ typedef-type }}. */ typedef ARpgPlayer 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 TEXT("/Script/RpgGame"); } /** Returns the static cast flags for this class */ inline static EClassCastFlags StaticClassCastFlags() { return CASTCLASS_None; } /** 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; } friend FArchive &operator<<( FArchive& Ar, ARpgPlayer*& Res ) { return Ar << (UObject*&)Res; } friend void operator<<(FStructuredArchive::FSlot InSlot, ARpgPlayer*& Res) { InSlot << (UObject*&)Res; } /** Standard constructor, called after all reflected properties have been initialized */ \ NO_API ARpgPlayer() { }; private: /** Private move- and copy-constructors, should never be used */ NO_API ARpgPlayer(ARpgPlayer&&); NO_API ARpgPlayer(const ARpgPlayer&); public: NO_API ARpgPlayer(FVTableHelper& Helper); static UObject* __VTableCtorCaller(FVTableHelper& Helper) { return nullptr; } static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())ARpgPlayer; } private: public: float PlayerHP = 2.0f; };
So far, we have generated The H generation process is roughly simulated. In the next article, we will briefly analyze the replaced code.