how does GENERATED_BODY() work?

  • the UCLASS(), USTRUCT(), and other reflection macros - but especially the GENERATED_BODY() macro might seem a bit magical the first time you see them.

    2024-04-09
    • the thing about it is that it defies the usual rules of how C++ macros work. let’s have a look at it together.

      2024-04-09
  • what does it even expand to?

    2024-04-09
    • try looking at what your IDE’s autocomplete suggests - from a cursory glance at the symbols available in a UCLASS()-declared class, you will notice there are a few that are non-standard ones, such as StaticClass() or Super. this is what the GENERATED_BODY() macro ends up expanding to after all the preprocessing is done.

      2024-04-09
      • but the thing is that Super is a typedef to the parent class - how does it know the parent class without passing it in as a macro argument?

        2024-04-09
      • and StaticClass() returns a different value for each class. this would require knowing the class beforehand - how does it know?

        2024-04-09
  • the thing is - it doesn’t. GENERATED_BODY() by itself is incredibly stupid:

    #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(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY);
    

    let’s disassemble it piece by piece.

    2024-04-09
    • BODY_MACRO_COMBINE is just a macro combining four identifiers together. no magic here.

      2024-04-09
    • so GENERATED_BODY combines the identifiers CURRENT_FILE_ID, _, __LINE__, and _GENERATED_BODY.

      2024-04-09
      • CURRENT_FILE_ID is a preprocessor macro defined by the UnrealBuildTool for each file. for simplicity’s sake, let’s assume it’s the filename with dots replaced by underscores. for instance, GameplayAbility_h.

        2024-04-09
        • the actual form it seems to take is FID_{Path} with {Path} being the file path relative to the project root directory, with slashes and dots replaced with underscores. for:

              Engine/Source/Runtime/Engine/Classes/Engine/Blueprint.h
          

          the file ID is:

          FID_Engine_Source_Runtime_Engine_Classes_Engine_Blueprint_h
          

          I haven’t inspected the UnrealBuildTool/UnrealHeaderTool sources though, so there may be more to it.

          2024-04-09
      • _ is just an underscore. nothing magical here.

        2024-04-09
      • __LINE__ is a standard C++ macro which expands to the current line number.

        2024-04-09
      • and _GENERATED_BODY is just an identifier.

        2024-04-09
    • therefore for a simple file, let’s call it MyClass.h:

      #pragma once
      
      #include "UObject/Object.h"
      
      #include "MyClass.generated.h"
      
      UCLASS()
      class UMyClass : public UObject
      {
          GENERATED_BODY()
      };
      

      after expanding the GENERATED_BODY(), we’ll get this:

      // -- snip --
      
      UCLASS()
      class UMyClass : public UObject
      {
          MyClass_h_10_GENERATED_BODY
      };
      
      2024-04-09
      • and this identifier is declared as a macro in the UnrealHeaderTool-generated MyClass.generated.h - and expands to a bunch of declarations, including the declaration of Super and StaticClass, as well as constructors if they’re not already declared.

        2024-04-09
        • you can even inspect the source code of .generated.h files yourself, by <kbd>Ctrl</kbd>+clicking on them (at least in Rider. I haven’t tested Visual Studio.)

          2024-04-09
  • that’s all there is to it. incredibly simple, cursed as heck, yet super effective.

    2024-04-09