Skip to content

Reflection System Details: Part 1#

This is translated from this excellent series: https://www.cnblogs.com/ghl_carmack/p/5701862.html

The previous translation article mentioned the basic principle and application of the UE4 reflection system. This time, we will deeply study the reflection system of UE4 through code. Because the reflection system involves more things in UE4, so I plan to analyze in several articles. I assume that the reader has a certain understanding of UE4 and a certain C ++ foundation. If you do not understand how to use UE4, then please learn how to use the UE4 engine, otherwise it may be more difficult.

The following is a class diagram related to the reflection system I compiled:
Reflection Class Diagram

It can be seen from the above that UObject is the core of the entire reflection system. The types of reflection supported in UE4 have been mentioned in the previous article, including C ++ classes, structures, functions, member variables, and enumerations. TArray is also supported (only supports some Such as TArray and TSubclassOf template types, and their template types cannot be nested), but TMap does not support. The support for these things is inseparable from the above classes, such as UClass, UBlueprintGeneratedClass, UFunction, UEnum, and UProperty, and subclasses inherited from them. Each class that inherits UObject and supports the reflection system type has a corresponding UClass, or its subclass (such as the blueprint corresponding to the UBlueprintGeneratedClass class, which inherits from UClass). If it is a specific blueprint type, such as action blueprints and widget blueprints And so on, as shown above. UMetaData is metadata, it stores some additional information needed by the editor, such as its Category, Tooltip, etc. These information will not be used when it is finally packaged. As for the variables such as float and int32 that need to be accessed in our reflection system, they are all represented by subclasses inheriting from UProperty. You can find the corresponding class in the code according to the objects listed in the figure above to see the specifics. Implementation.

Below we use the simplest code example to illustrate the implementation of reflection in UE4. First, I created a project named ReflectionStudy (only Basic Code). This was done to facilitate the analysis of the code. The article mentioned at the beginning said However, if you want your implementation to support reflection, you must follow relevant guidelines, such as using UENUM (), UCLASS (), USTRUCT (), UFUNCTION (), and UPROPERTY (), etc. To generate the corresponding code that supports reflection. Let's expand to analyze these codes separately. The codes generated by it are stored in your project ReflectionStudyIntermediateBuildWin64UE4EditorIncReflectionStudy path.

It is generally divided into several types of files:

  1. ReflectionStudy.generated.cpp There is only one project. This file is used to generate reflection information for each class that supports reflection, such as registering properties and adding source data.
  2. ReflectionStudy.generated.dep.h This file contains the header files used in 1. ReflectionStudy.generated.cpp above.
  3. ReflectionStudyClasses.h
    • .generated.h This is the corresponding macro code generated for each header file that supports reflection.

Class definition#

We use the following code as an example to explain. In order to see the specific implementation of some usage, we have added the following properties and methods.

C++
#pragma once

#include "GameFramework / GameMode.h"
#include "ReflectionStudyGameMode.generated.h"

UCLASS ()
class REFLECTIONSTUDY_API AReflectionStudyGameMode: public AGameMode
{
  GENERATED_BODY ()


  protected:
  UPROPERTY (BlueprintReadWrite, Category = "AReflectionStudyGameMode")
  float Score;

  UFUNCTION (BlueprintCallable, Category = "AReflectionStudyGameMode")
  void CallableFuncTest ();

  UFUNCTION (BlueprintNativeEvent, Category = "AReflectionStudyGameMode")
  void NavtiveFuncTest ();

  UFUNCTION (BlueprintImplementableEvent, Category = "AReflectionStudyGameMode")
  void ImplementableFuncTest ();
};

UHT generated .generated.h file#

Because the corresponding ReflectionStudyGameMode.generated.h header file is long, we only list the key parts to explain.

C++
#define ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_RPC_WRAPPERS_NO_PURE_DECLS
virtual void NavtiveFuncTest_Implementation ();

DECLARE_FUNCTION (execNavtiveFuncTest)
{
  P_FINISH;
  P_NATIVE_BEGIN;
  this-> NavtiveFuncTest_Implementation ();
  P_NATIVE_END;
}

DECLARE_FUNCTION (execCallableFuncTest)
{
  P_FINISH;
  P_NATIVE_BEGIN;
  this-> CallableFuncTest ();
  P_NATIVE_END;
}

You can see that the function we defined above, UHT helped us automatically generate the above code. As for why such a function is generated, it is because of the UE4 blueprint calling convention. Each function must be prefixed with an exec prefix. The implementation of the blueprint is I don't know very well at the moment, so I may introduce a blueprint implementation later. These functions are called by the UE4 virtual machine. If there are parameters and return values, there will be corresponding virtual The code for taking parameters and setting the return value on the stack can be verified by the reader.

C++
#define ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_INCLASS_NO_PURE_DECLS
private:
static void StaticRegisterNativesAReflectionStudyGameMode ();
friend REFLECTIONSTUDY_API class UClass * Z_Construct_UClass_AReflectionStudyGameMode ();
public:
DECLARE_CLASS (AReflectionStudyGameMode, AGameMode, COMPILED_IN_FLAGS (0 | CLASS_Transient | CLASS_Config), 0, TEXT ("/ Script / ReflectionStudy"), NO_API)
DECLARE_SERIALIZER (AReflectionStudyGameMode)
/ ** Indicates whether the class is compiled into the engine * /
enum {IsIntrinsic = COMPILED_IN_INTRINSIC};
  • StaticRegisterNativesAReflectionStudyGameMode This function is used to register C ++ native functions exposed to the virtual machine.
  • friend REFLECTIONSTUDY_API class UClass * Z_Construct_UClass_AReflectionStudyGameMode (); Declare a friend function. This function is used to build the corresponding UClass of this class.
  • DECLARE_CLASS This macro is more complicated. It mainly defines StaticClass (), etc. For specific implementation, please open the definition of the reader to see it.
  • DECLARE_SERIALIZER defines serialization code.
  • enum {IsIntrinsic = COMPILED_IN_INTRINSIC}; As the comment says, it is used to mark whether this class is compiled into the engine.
C++
    #define ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_ENHANCED_CONSTRUCTORS
  / ** Standard constructor, called after all reflected properties have been initialized * /
  NO_API AReflectionStudyGameMode (const FObjectInitializer & ObjectInitializer = FObjectInitializer :: Get ()): Super (ObjectInitializer) {};
  private:
  / ** Private copy-constructor, should never be used * /
  NO_API AReflectionStudyGameMode (const AReflectionStudyGameMode & InCopy);
  public:
  DECLARE_VTABLE_PTR_HELPER_CTOR (NO_API, AReflectionStudyGameMode);
  DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER (AReflectionStudyGameMode);
  DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL (AReflectionStudyGameMode)
  • NO_API AReflectionStudyGameMode (const FObjectInitializer & ObjectInitializer = FObjectInitializer :: Get ()): Super (ObjectInitializer) {}; Defines a standard constructor that is called after all reflection properties have been initialized.
  • NO_API AReflectionStudyGameMode (const AReflectionStudyGameMode & InCopy); prevent calling copy constructor
  • DECLARE_VTABLE_PTR_HELPER_CTOR (NO_API, AReflectionStudyGameMode); DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER (AReflectionStudyGameMode); Hot-load related, this is the more powerful function in UE4, we will not discuss it in detail here. If you understand this section in the future, it will be explained separately .
  • DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL defines a default constructor, as shown in the following code:
    ``cpp
    static void __DefaultConstructor (const FObjectInitializer & X) {new ((EInternal *) X.GetObj ()) TClass (X);}
    ```

```cpp
#define ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_GENERATED_BODY

Text Only
PRAGMA_DISABLE_DEPRECATION_WARNINGS

public:

    ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_RPC_WRAPPERS_NO_PURE_DECLS

    ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_CALLBACK_WRAPPERS

    ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_INCLASS_NO_PURE_DECLS

    ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h_14_ENHANCED_CONSTRUCTORS

private:

PRAGMA_ENABLE_DEPRECATION_WARNINGS

This code is a reference to the above explained macro. With the following macro, you can finally define a GENERATED_BODY () in the class, and you can include all the definitions above into the class.cpp

undef CURRENT_FILE_ID#

define CURRENT_FILE_ID ReflectionStudy_Source_ReflectionStudy_ReflectionStudyGameMode_h#

```

All GENERATED_BODY () related macros are defined as follows

C++
// 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)

#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 ()

At this point, the contents of the ReflectionStudyGameMode.generated.h file have been basically analyzed. Let's take a look at the corresponding code in ReflectionStudy.generated.cpp. Combined with the previous explanation, I believe you have a general understanding of the entire UE4 reflection system.

C++
FName REFLECTIONSTUDY_ImplementableFuncTest = FName (TEXT ("ImplementableFuncTest"));
FName REFLECTIONSTUDY_NavtiveFuncTest = FName (TEXT ("NavtiveFuncTest"));
  void AReflectionStudyGameMode :: ImplementableFuncTest ()
  {
    ProcessEvent (FindFunctionChecked (REFLECTIONSTUDY_ImplementableFuncTest), NULL);
  }
  void AReflectionStudyGameMode :: NavtiveFuncTest ()
  {
    ProcessEvent (FindFunctionChecked (REFLECTIONSTUDY_NavtiveFuncTest), NULL);
  }
  void AReflectionStudyGameMode :: StaticRegisterNativesAReflectionStudyGameMode ()
  {
    FNativeFunctionRegistrar :: RegisterFunction (AReflectionStudyGameMode :: StaticClass (), "CallableFuncTest", (Native) & AReflectionStudyGameMode :: execCallableFuncTest);
    FNativeFunctionRegistrar :: RegisterFunction (AReflectionStudyGameMode :: StaticClass (), "NavtiveFuncTest", (Native) & AReflectionStudyGameMode :: execNavtiveFuncTest);
  }
  IMPLEMENT_CLASS (AReflectionStudyGameMode, 3618622309);
  • When I first came into contact with UE4, if it was a function of BlueprintImplementabeEvent, did I find that I didn't need to implement it myself, did I find it weird at the time? The above code explained it clearly. It was UE4 that helped us to implement it. Called ProcessEvent method, this method is implemented in UObject.
  • StaticRegisterNativesAReflectionStudyGameMode adds native C ++ functions to the UClass returned by AReflectionStudyGameMode :: StaticClass ().
  • IMPLEMENT_CLASS defines a static global variable that is used to register UClass when the program starts.
C++
UFunction * Z_Construct_UFunction_AReflectionStudyGameMode_CallableFuncTest ()
{
UObject * Outer = Z_Construct_UClass_AReflectionStudyGameMode ();
static UFunction * ReturnFunction = NULL;
if (! ReturnFunction)
{
ReturnFunction = new (EC_InternalUseOnlyConstructor, Outer, TEXT ("CallableFuncTest"), RF_Public | RF_Transient | RF_MarkAsNative) UFunction (FObjectInitializer (), NULL, 0x04080401, 65535);
ReturnFunction-> Bind ();
ReturnFunction-> StaticLink ();
#if WITH_METADATA
UMetaData * MetaData = ReturnFunction-> GetOutermost ()-> GetMetaData ();
MetaData-> SetValue (ReturnFunction, TEXT ("Category"), TEXT ("AReflectionStudyGameMode"));
MetaData-> SetValue (ReturnFunction, TEXT ("ModuleRelativePath"), TEXT ("ReflectionStudyGameMode.h"));
#endif
}
return ReturnFunction;
}
  • This function registers a function named CallableFuncTest in the UClass class returned by AReflectionStudyGameMode, and #if WITH_METADATA is the metadata we mentioned earlier. Note that the Category category specified in our class is specified here and placed It's (UPackage) UMetaData. Z_Construct_UFunction_AReflectionStudyGameMode_ImplementableFuncTest () and Z_Construct_UFunction_AReflectionStudyGameMode_NavtiveFuncTest () are basically implemented in the same way as above, and I will not write them here.
C++
UClass * Z_Construct_UClass_AReflectionStudyGameMode ()
{
  static UClass * OuterClass = NULL;
  if (! OuterClass)
  {
    Z_Construct_UClass_AGameMode ();
    Z_Construct_UPackage__Script_ReflectionStudy ();
    OuterClass = AReflectionStudyGameMode :: StaticClass ();
    if (! (OuterClass-> ClassFlags & CLASS_Constructed))
    {
      UObjectForceRegistration (OuterClass);
      OuterClass-> ClassFlags | = 0x2090028C;

      OuterClass-> LinkChild (Z_Construct_UFunction_AReflectionStudyGameMode_CallableFuncTest ());
      OuterClass-> LinkChild (Z_Construct_UFunction_AReflectionStudyGameMode_ImplementableFuncTest ());
      OuterClass-> LinkChild (Z_Construct_UFunction_AReflectionStudyGameMode_NavtiveFuncTest ());

      PRAGMA_DISABLE_DEPRECATION_WARNINGS
      UProperty * NewProp_Score = new (EC_InternalUseOnlyConstructor, OuterClass, TEXT ("Score"), RF_Public | RF_Transient | RF_MarkAsNative) UFloatProperty (CPP_PROPERTY_BASE (Score, AReflectionStudyGameMode), 0x0020080000000004);
      PRAGMA_ENABLE_DEPRECATION_WARNINGS
      OuterClass-> AddFunctionToFunctionMapWithOverriddenName (Z_Construct_UFunction_AReflectionStudyGameMode_CallableFuncTest (), "CallableFuncTest"); // 3059784748
      OuterClass-> AddFunctionToFunctionMapWithOverriddenName (Z_Construct_UFunction_AReflectionStudyGameMode_ImplementableFuncTest (), "ImplementableFuncTest"); // 4773450
      OuterClass-> AddFunctionToFunctionMapWithOverriddenName (Z_Construct_UFunction_AReflectionStudyGameMode_NavtiveFuncTest (), "NavtiveFuncTest"); // 2500148308
      OuterClass-> ClassConfigName = FName (TEXT ("Game"));
      OuterClass-> StaticLink ();
      #if WITH_METADATA
      UMetaData * MetaData = OuterClass-> GetOutermost ()-> GetMetaData ();
      MetaData-> SetValue (OuterClass, TEXT ("HideCategories"), TEXT ("Info Rendering MovementReplication Replication Actor Input Movement Collision Rendering Utilities | Transformation"));
      MetaData-> SetValue (OuterClass, TEXT ("IncludePath"), TEXT ("ReflectionStudyGameMode.h"));
      MetaData-> SetValue (OuterClass, TEXT ("ModuleRelativePath"), TEXT ("ReflectionStudyGameMode.h"));
      MetaData-> SetValue (OuterClass, TEXT ("ShowCategories"), TEXT ("Input | MouseInput Input | TouchInput"));
      MetaData-> SetValue (NewProp_Score, TEXT ("Category"), TEXT ("AReflectionStudyGameMode"));
      MetaData-> SetValue (NewProp_Score, TEXT ("ModuleRelativePath"), TEXT ("ReflectionStudyGameMode.h"));
      #endif
    }
  }
  check (OuterClass-> GetClass ());
  return OuterClass;
}
  • The function of this function is to generate the UClass object of AReflectionStudyGameMode and register all UFunction and UProperty
  • Z_Construct_UClass_AGameMode (); Because it inherits from AGameMode, the UClass of AGameMode must be valid.
  • Z_Construct_UPackage__Script_ReflectionStudy (); Make sure UPackage has been created.
  • The code in the #if WITH_METADATA macro is also used to create metadata.
C++
static FCompiledInDefer Z_CompiledInDefer_UClass_AReflectionStudyGameMode (Z_Construct_UClass_AReflectionStudyGameMode, & AReflectionStudyGameMode :: StaticClass, TEXT ("AReflectionStudyGameMode"), false, nullptr, nullptr);
DEFINE_VTABLE_PTR_HELPER_CTOR (AReflectionStudyGameMode);
  • The first line of code is used to store a static function that creates a UClass, and this statically generated UClass function will be executed later
  • DEFINE_VTABLE_PTR_HELPER_CTOR defines a parameter for the FVTableHelper constructor.
C++
UPackage * Z_Construct_UPackage__Script_ReflectionStudy()
{
  static UPackage * ReturnPackage = NULL;
  if (! ReturnPackage)
  {
    ReturnPackage = CastChecked (StaticFindObjectFast (UPackage :: StaticClass (), NULL, FName (TEXT ("/ Script / ReflectionStudy")), false, false));
    ReturnPackage-> SetPackageFlags (PKG_CompiledIn | 0x00000000);
    FGuid Guid;
    Guid.A = 0x00B770A5;
    Guid.B = 0x8BECE3AF;
    Guid.C = 0x00000000;
    Guid.D = 0x00000000;
    ReturnPackage-> SetGuid (Guid);

  }
  return ReturnPackage;
}

It is used to return the UPackage of the current module. The above code will use this parameter GetOuterMost () function, which returns this UPackage.

So far we have made a brief introduction to the support of classes in the reflection system in UE4. I believe that everyone also has a certain understanding. Due to space limitations, we will stop here to discuss other USTRUCT, UENUM, etc. implementations, and They run the entire reflection system. Since I am not particularly familiar with UE4, there may be some inaccuracies in it. If there are errors, please correct me, and I hope everyone can discuss them together. Of course, I will also talk about the implementation of other modules in UE4, such as the implementation of the entire blueprint, multi-threaded rendering, and physical-based rendering.

Because the size of the top class image is too large, the uploaded image is not particularly clear. The original high-definition image can be downloaded here [3].