有时只想要一些属性在编辑器下存在,打包时不需要,按照常规的思路,需要对这些属性使用 WITH_EDITOR
包裹:
1 2 3 4 #if WITH_EDITOR UPROPERTY () int32 ival; #endif
这个代码在 Editor 的 Configuration 下没有问题,但是一旦编译非 Editor 就会产生如下错误:
1 ERROR: Build/Win64/FGame/Inc/FGame/NetActor.gen.cpp(97): error C2039: 'ival': is not a member of 'ANetActor'
那么,既然我们明明已经用 WITH_EDITOR
包裹了 ival
的属性,为什么在编译非 Editor 的时候 UHT 还会为这个属性生成反射代码呢? 这个问题涉及到了以下几个概念:
gen.cpp 中是 UHT 为反射标记的类和属性生成的反射信息
UHT 的生成流程在调用编译器之前
UE 构建系统的流程我之前做过分析:Build flow of the Unreal Engine4 project
因为 C++ 的宏是在调用编译器后预处理阶段做的事情,在执行 UHT 时,压根不会检测宏条件,所以上面的代码,UHT 依然会为 ival
生成反射信息到 gen.cpp
中,而 UHT 执行完毕之后进入编译阶段 WITH_EDITOR
会参与预处理,ival
因此在类定义中不存在,但是 UHT 已经为它生成了反射代码,会通过获取成员函数指针的方式访问到它,进而产生了上述的编译错误。
所以这是 UE 反射代码生成先于预处理造成的问题,在写代码时是比较反直觉的。但是这个问题也并非不能解决,UE 提供了 WITH_EDITORONLY_DATA
宏来专门处理这个问题,一个宏解决不了,就引入一个新的。
但是为什么 WITH_EDITOR
不可以,而 WITH_EDITORONLY_DATA
就可以呢?因为 UHT 在生成反射代码时为 WITH_EDITORONLY_DATA
做了特殊检测:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 void FNativeClassHeaderGenerator::ExportProperties (FOutputDevice& Out, UStruct* Struct, int32 TextIndent) { FProperty* Previous = NULL ; FProperty* PreviousNonEditorOnly = NULL ; FProperty* LastInSuper = NULL ; UStruct* InheritanceSuper = Struct->GetInheritanceSuper (); UStruct* CurrentSuper = InheritanceSuper; while (LastInSuper == NULL && CurrentSuper) { for (TFieldIterator<FProperty> It (CurrentSuper,EFieldIteratorFlags::ExcludeSuper); It; ++It ) { FProperty* Current = *It; if (It.GetStruct () == CurrentSuper && Current->ElementSize) { LastInSuper = Current; } } CurrentSuper = CurrentSuper->GetSuperStruct (); } FMacroBlockEmitter WithEditorOnlyData (Out, TEXT("WITH_EDITORONLY_DATA" )) ; for (TFieldIterator<FProperty> It (Struct, EFieldIteratorFlags::ExcludeSuper); It; ++It ) { FProperty* Current = *It; if (It.GetStruct () == Struct) { WithEditorOnlyData (Current->IsEditorOnlyProperty ()); { FUHTStringBuilder JustPropertyDecl; const FString* Dim = GArrayDimensions.Find (Current); Current->ExportCppDeclaration (JustPropertyDecl, EExportedDeclaration::Member, Dim ? **Dim : NULL ); ApplyAlternatePropertyExportText (*It, JustPropertyDecl, EExportingState::TypeEraseDelegates); Out.Logf (TEXT ("%s%s;\r\n" ), FCString::Tab (TextIndent + 1 ), *JustPropertyDecl); } LastInSuper = NULL ; Previous = Current; if (!Current->IsEditorOnlyProperty ()) { PreviousNonEditorOnly = Current; } } } }
看下 FMacroBlockEmitter
的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 struct FMacroBlockEmitter { explicit FMacroBlockEmitter (FOutputDevice& InOutput, const TCHAR* InMacro) : Output(InOutput) , bEmittedIf(false) , Macro(InMacro) { } ~FMacroBlockEmitter () { if (bEmittedIf) { Output.Logf (TEXT ("#endif // %s\r\n" ), Macro); } } void operator () (bool bInBlock) { if (!bEmittedIf && bInBlock) { Output.Logf (TEXT ("#if %s\r\n" ), Macro); bEmittedIf = true ; } else if (bEmittedIf && !bInBlock) { Output.Logf (TEXT ("#endif // %s\r\n" ), Macro); bEmittedIf = false ; } } FMacroBlockEmitter (const FMacroBlockEmitter&) = delete ; FMacroBlockEmitter& operator =(const FMacroBlockEmitter&) = delete ; private : FOutputDevice& Output; bool bEmittedIf; const TCHAR* Macro; };
当生成代码时会为使用 WITH_EDITORONLY_DATA
包裹的属性在 gen.cpp
中添加 WITH_EDITORONLY_DATA
宏(有点套娃的感觉),使 gen.cpp
在非 EDITOR 下编译时也不会把这部分反射代码参与真正的编译,从而解决了上面的问题。