Environment | |
---|---|
UnrealEngine | branch: ue5-early-access |
Visual Studio 2019 | version: 16.10.4 |
Windows 10 | build: 19043.1110 |
Expanding UCLASS()
1 | /UnrealEngine/Engine/Source/Runtime/Engine/Classes/GameFramework/PlayerController.h |
The macro UCLASS()
may be the most famous one of the unreal macros. First of all, let us find out how it can be expanded. Our goal is expanding UCLASS()
of the class APlayerController
, which can be found at line #222 of PlayerController.h
.
1 | /UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectMacros.h |
In UnrealEngine, most of definitions for core macros are placed in ObjectMacros.h
file. We can see the definition of UCLASS
here, and it would be the second definition in usual case. Then, what is the macro BODY_MACRO_COMBINE
?
1 | /UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectMacros.h |
The macro is defined as BODY_MACRO_COMBINE_INNER
, which concatenates parameters as one string. Thus, the macro UCLASS
would result the text like below:
1 | UCLASS(...) -> |
It can be tested with simple code. Check it out at the screenshot below. The __LINE__
is one of pre-defined macro, so it is turned to 222
, where the UCLASS
is written.
Here is test code and its result. Check the name of integer variable.
Actually, the macro CURRENT_FILE_ID
can be found at header files generated by Unreal Header Tool. You can find the definition at generated header files, for instance, PlayerController.generated.h
. The generated header files are created when you attempt to build your project.
1 | /UnrealEngine/Engine/Intermediate/Build/Win64/UnrealEditor/Inc/Engine/PlayerController.generated.h |
Thus, the macro CURRENT_FILE_ID
would be replaced before BODY_MACRO_COMBINE
is expanded. We can rewrite the macro evaluation process.
1 | UCLASS(...) -> |
And, the Engine_Source_Runtime_Engine_Classes_GameFramework_PlayerController_h_222_PROLOG
is also defined at the generated header file for PlayerController.h
.
1 | /UnrealEngine/Engine/Intermediate/Build/Win64/UnrealEditor/Inc/Engine/PlayerController.generated.h |
The Engine_Source_Runtime_Engine_Classes_GameFramework_PlayerController_h_225_EVENT_PARMS
is a macro containing definitions for essential structures.
1 | /UnrealEngine/Engine/Intermediate/Build/Win64/UnrealEditor/Inc/Engine/PlayerController.generated.h |
Let us rewrite the evaluation process. As a result, the macro UCLASS
is replaced by definitions for some essential structures.
1 | UCLASS(...) -> |
Okay, we have just peeled off one layer to the truth. But, is that all ? We should take care of something more…Most of time, the macro UCLASS
is not solely used. Various keywords and specifiers come with this. (ex: config=Game
, BlueprintType
, meta=(ShortTooltip=...
, …) So, how they are handled ? Even, how the generated header file is created ?
Generated Header File
1 | /UnrealEngine/Engine/Source/Programs/UnrealHeaderTool/Private/CodeGenerator.cpp |
The UHT writes header files containing auto-generated codes at the code above. The PreloadedFiles
has absolute paths of generated header file, for instance, D:/Git/UnrealEngine/Engine/.../Inc/Engine/PlayerController.generated.h
.
1 | /UnrealEngine/Engine/Source/Programs/UnrealHeaderTool/Private/CodeGenerator.cpp |
An example of NoExportTypes.generated.h
.
You can track what UHT writes on the generated header file via the variable GeneratedHeaderText
. Because its contents will replace old generated header file, whenever there is any difference between old contents and new contents.
1 | /UnrealEngine/Engine/Source/Programs/UnrealHeaderTool/Private/CodeGenerator.cpp |
For example, there is some definition for preventing duplicated include. The format above turns to like below.
1 | /UnrealEngine/Engine/Intermediate/Build/Win64/UnrealEditor/Inc/Engine/PlayerController.generated.h |
There are more things worthy to check.
1 | /UnrealEngine/Engine/Source/Programs/UnrealHeaderTool/Private/CodeGenerator.cpp |
That is why the CURRENT_FILE_ID
and ..._EVENT_PARMS
macros are defined. Furthermore, other codes can be found at FNativeClassHeaderGenerator::FNativeClassHeaderGenerator(const UPackage*, const TSet<FUnrealSourceFile*>&, FClasses&, bool)
.
So, we have found the relationship of unreal macro and generated header file. But, there is one thing left, the metadata.
Metadata Parser
1 | /UnrealEngine/Engine/Source/Runtime/Engine/Classes/GameFramework/PlayerController.h |
Back to the start, there are metadata within the macro UCLASS
such as config=Game
, BlueprintType
and meta=...
. We are going to check out how they are handled by UnrealEngine.
1 | /UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectMacros.h |
You can find some enum definitions seems related to the metadata. But, they are for only supporting autocomplete hints such as Intellisense and VisualAssistX. There is another code handling metadata.
1 | /UnrealEngine/Engine/Source/Programs/UnrealHeader/Private/CodeGenerator.cpp |
This code is for parsing metadata. Any header file in your project is passed through the parsing.
1 | static void SetupUObjectModuleHeader(UHTModuleInfo ModuleInfo, FileItem HeaderFile, SourceFileMetadataCache MetadataCache) |
About all modules, header files in Classes
folder are stored at PublicUObjectClassesHeaders
and header files in Public
folder are stored at PublicUObjectHeaders
. Even you have located a header file in other folder, the Unreal Build Tool
collects it into PrivateUObjectHeaders
.
A screenshot on debugging UBT.
Back to the FBaseParser::ReadSpecifierSetInsideMacro()
, let us test with the keyword BlueprintType
. How does the BlueprintType
keyword parsed ? The UHT parses your header file with tokens. Suppose the input as UCLASS(config=Game, BlueprintType, Blueprintable, meta=(...))
.
1 | /UnrealEngine/Engine/Source/Programs/UnrealHeaderTool/Private/HeaderParser.cpp |
Due to this code, the left input would be (config=Game, BlueprintType, Blueprintable, meta=(...))
. And, the following tokenizing is like below based on FBaseParser::ReadSpecifierSetInsideMacro()
.
1 | (config=Game, BlueprintType, Blueprintable, meta=(...)) |
A screenshot on debugging UHT.
Wrap-Up
1 | C/CPP (pure) macro |
There are so many hidden code for implementing unreal macros, and the macros have complicated relationship with other engine code. Even most part of final code from the macros cannot be evaluated before some preprocessing and compilation. In this perspective, unreal macro such as UCLASS
is not a pure C/CPP macro, because unreal macro functions fully only when UHT and UBT must preprocess the macro.
There is no doubt. Any of C/CPP compiler cannot recognize the unreal macro such as UCLASS
. Even the Epic Games did not modify the compilers, and did not have to do. They have simply setup some build pipeline satisfying their needs. The program managing their custom build pipeline is the Unreal Build Tool
, UBT. Most of jobs for build are done by UBT and UHT. In official document for UHT, these background knowledge is introduced.
1 | UnrealHeaderTool (UHT) is a custom parsing and code-generation tool that supports the UObject system. Code compilation happens in two phases: |
As they said, the compilation order is the opposite direction of the paragraphs; Expanding UCLASS
, Generated Header File
and Metadata Parser
. The actions for Expanding UCLASS
are done by C/CPP compilers(+preprocessors), and the actions for Generated Header File
and Metadata Parser
are done by UHT. Additionally, actions for Metadata Parser
happens early than ones for Generated Header File
.
1 | main function for parsing metadata -> FHeaderParser::ParseHeaders() |
Let us make a conclusion.
- The result from unreal macro is hard to evaluate before processing by UBT(+UHT).
- Some features of UnrealEngine are implemented by auto-generated codes.
- You should look into the build pipeline of UnrealEngine if need to modify unreal macro things.