UE4 C++反射机制

/ 0

准备工作

创建一个 UE4 C++ 项目命名为 ReflectionDemo1,不包含初学者内容。然后创建一个继承自 UObject 的测试类,命名为 UStudent

GENERATED_BODY() 宏改为 GENERATED_UCLASS_BODY(),然后实现构造函数:

AReflectionDemo1GameModeBase::AReflectionDemo1GameModeBase(const FObjectInitializer & ObjectInitializer) 
       : AGameModeBase(ObjectInitializer) {
    UE_LOG(LogTemp, Warning, TEXT("Hello Reflection!"));
}

获取类名

想要获取一个类的类名,必须具备一些必要条件,如下示例代码:

#pragma once

#include "CoreMinimal.h"
#include "Student.generated.h"

UCLASS()
class REFLECTIONDEMO1_API UStudent : public UObject {
    GENERATED_BODY()

public:
    UStudent();
    ~UStudent();
};

通过反射获取类名:

UStudent *Student = NewObject<UStudent>();
UClass *StudentClass = Student->GetClass();
FName ClassName = StudentClass->GetFName();
UE_LOG(LogTemp, Warning, TEXT("Student's classname is %s."),  *ClassName.ToString());

获取属性名和属性值

只有添加了 PROPERTY() 宏的属性才支持反射,跟访问权限修饰符无关:

UPROPERTY()
FString Name;

通过反射获取所有属性:

for (UProperty * Property = StudentClass->PropertyLink; Property; Property = Property->PropertyLinkNext) {
    FString PropertyName = Property->GetName();
    FString PropertyType = Property->GetCPPType();
    if (PropertyType == "FString") {
        UStrProperty *StringProperty = Cast<UStrProperty>(Property);
        void *Addr = StringProperty->ContainerPtrToValuePtr<void>(Student);
        FString PropertyValue = StringProperty->GetPropertyValue(Addr);
        UE_LOG(LogTemp, Warning, TEXT("Student's properties has %s, Type is %s, Value is %s"), *PropertyName, *PropertyType, *PropertyValue);

        StringProperty->SetPropertyValue(Addr, "Feng");
        FString NewPropertyValue = StringProperty->GetPropertyValue(Addr);
        UE_LOG(LogTemp, Warning, TEXT("Student's properties has %s, Type is %s, Value is %s"), *PropertyName, *PropertyType, *NewPropertyValue);
    }
}

获取函数

和属性反射类似,也需要添加一个宏才能支持反射:

UFUNCTION()
int Study(const FString & InWhat);

通过反射获取所有函数信息(返回值、函数名、参数列表),包含从父类继承的:

for (TFieldIterator<UFunction> IteratorOfFunction(StudentClass); IteratorOfFunction; ++IteratorOfFunction) {
    UFunction *Function = *IteratorOfFunction;
    FString FunctionName = Function->GetName();
    UE_LOG(LogTemp, Warning, TEXT("Student's functions has %s"), *FunctionName);

    for (TFieldIterator<UProperty> IteratorOfParam(Function); IteratorOfParam; ++IteratorOfParam) {
        UProperty *Param = *IteratorOfParam;
        FString ParamName = Param->GetName();
        FString ParamType = Param->GetCPPType();

        if (Param->GetPropertyFlags() & CPF_ReturnParm) {
            UE_LOG(LogTemp, Warning, TEXT("Student's functions has %s, ReturnName is %s, ReturnType is %s"), *FunctionName, *ParamName, *ParamType);
        } else {
            UE_LOG(LogTemp, Warning, TEXT("Student's functions has %s, ParamName is %s, ParamType is %s"), *FunctionName, *ParamName, *ParamType);
        }
    }
}

PropertyFlags 属性标记是一个枚举类型,用于区分属性的细节信息,比如是参数还是返回值,是纯输入参数还是输出参数等。

获取父类

新建一个继承自 UStudent 的子类,命名为 UMiddleStudent。然后获取父类,获取到父类后也可以像上面那样,获取父类中的属性和函数。

UMiddleStudent * MiddleStudent = NewObject<UMiddleStudent>();
UClass * MiddleStudentClass = MiddleStudent->GetClass();
UClass * SuperClass = MiddleStudentClass->GetSuperClass();
FString SuperClassName = SuperClass->GetName();
UE_LOG(LogTemp, Warning, TEXT("MiddleStudent's SuperClassName is %s"), *SuperClassName);

判断一个类是否是另一个类的派生类

使用 IsChildOf() 函数来判断一个类是否是另一个类,或者另一个类的派生类(孩子类、孙子类)。

UClass * Class1 = UMiddleStudent::StaticClass();
UClass * Class2 = UStudent::StaticClass();
if (Class1->IsChildOf(Class2)) {
    UE_LOG(LogTemp, Warning, TEXT("Class1 is Class2's child"));
} else {
    UE_LOG(LogTemp, Warning, TEXT("Class1 is not Class2's child"));
}

查找指定类的所有子类

使用 GetDerivedClasses 函数来获取一个的所有派生类,支持递归查找。

TArray<UClass *> ClassResults;
GetDerivedClasses(UStudent::StaticClass(), ClassResults, false);
for (int i = 0; i < ClassResults.Num(); i++) {
    UClass * ClassResult = ClassResults[i];
    FString ClassResultName = ClassResult->GetName();
    UE_LOG(LogTemp, Warning, TEXT("UStudent's derivedclass has %s"), *ClassResultName);
}

查找指定类创建的所有对象

使用 GetObjectsOfClass 函数来获取一个类创建的所有对象,支持递归查找。

UStudent * TempStudent = NewObject<UStudent>(this, FName("TempStu"));
TArray<UObject *> ObjectResults;
GetObjectsOfClass(UStudent::StaticClass(), ObjectResults, false);
for (int i = 0; i < ObjectResults.Num(); i++) {
    UObject * ObjectResult = ObjectResults[i];
    FString ObjectResultName = ObjectResult->GetName();
    UE_LOG(LogTemp, Warning, TEXT("UStudent's object has %s"), *ObjectResultName);
}

根据指定字符串查找类

使用 FindObject 查找指定字符串的类,不能指定前缀。比如 UStudent 类得使用 Student 字符串,我们上面获取的类名其实也是不包含前缀的。

UClass *FindedClass = FindObject<UClass>(ANY_PACKAGE, *FString("Student"), false);
if (FindedClass) {
    FString FindedClassName = FindedClass->GetName();
    UE_LOG(LogTemp, Warning, TEXT("Class with name:Student has finded, FindedClassName is %s"), *FindedClassName);
}

根据指定字符串查找枚举

使用 FindObject 查找指定字符串的枚举,可以指定前缀。

UENUM()
enum class EStudentSexEnum : uint8 {
    EMAN,
    EWOMAN
};

下面两种方式都可以查找到,最终打印出来的名字和传入参数一致:

UEnum *FindedEnum1 = FindObject<UEnum>(ANY_PACKAGE, *FString("StudentSexEnum"), false);
if (FindedEnum1) {
    UE_LOG(LogTemp, Warning, TEXT("Enum with name:StudentSexEnum has finded, %s"), *FindedEnum1->GetName());
    for (int32 i = 0; i < FindedEnum1->NumEnums(); i++) {
        FString EnumItemName = FindedEnum1->GetEnumName(i);
        UE_LOG(LogTemp, Warning, TEXT("Enum with name:StudentSexEnum has item:%s"), *EnumItemName);
    }
}
UEnum *FindedEnum2 = FindObject<UEnum>(ANY_PACKAGE, *FString("EStudentSexEnum"), false);
if (FindedEnum2) {
    UE_LOG(LogTemp, Warning, TEXT("Enum with name:EStudentSexEnum has finded, %s"), *FindedEnum2->GetName());
    for (int32 i = 0; i < FindedEnum2->NumEnums(); i++) {
        FString EnumItemName = FindedEnum2->GetEnumName(i);
        UE_LOG(LogTemp, Warning, TEXT("Enum with name:EStudentSexEnum has item:%s"), *EnumItemName);
    }
}

根据指定字符串查找蓝图类

使用 FindObject 查找指定字符串的蓝图类。先在虚幻编辑器中创建一个蓝图类,命名为 BP_UBlueprintClass

UBlueprint *FindedBlueprint = FindObject<UBlueprint>(ANY_PACKAGE, *FString("BP_UBlueprintClass"), false);
if (FindedBlueprint) {
    UE_LOG(LogTemp, Warning, TEXT("UBlueprint with name:BP_UBlueprintClass has finded"));
}

判断一个类是C++类还是蓝图类

UObject 的父类 UObjectBaseUtility 中有一个函数 IsNative(),可用于判断是蓝图类还是C++类。

if (UStudent::StaticClass()->IsNative()) {
    UE_LOG(LogTemp, Warning, TEXT("Class with name:Student is Native"));
}

获取类中属性的元数据

属性元数据就是 UFunction() 宏里的数据,先给 Student 类的 Name 属性添加一些元数据:

UPROPERTY(EditAnywhere, Category="Category1")
FString Name;

然后就可以使用 GetMetaData() 函数来获取,比如本篇笔记开头获取属性后:

for (UProperty * Property = StudentClass->PropertyLink; Property; Property = Property->PropertyLinkNext) {
    FString PropertyName = Property->GetName();
    FString PropertyType = Property->GetCPPType();
    if (PropertyType == "FString") {
        UStrProperty *StringProperty = Cast<UStrProperty>(Property);
        FString CategoryName = StringProperty->GetMetaData(TEXT("Category"));
        UE_LOG(LogTemp, Warning, TEXT("Student's properties has %s, Type is %s, Value is %s, Metadata has %s."), *PropertyName, *PropertyType, *PropertyValue, *CategoryName);
    }
}

获取类中函数的标记

也就是获取 UFUNCTION 宏括号里的元数据,直接读取 UFunctionFunctionFlags 属性即可,类型为 EFunctionFlags

获取类的标记

和获取函数的标记类似,直接读取 UClassClassFlags 属性即可,类型为 EClassFlags

遍历所有类

FString AllClassNames = "";
for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt) {
    FString InterClassName = ClassIt->GetName();
    AllClassNames += InterClassName;
    AllClassNames += ",";
}
UE_LOG(LogTemp, Warning, TEXT("AllClassName: %s"), *AllClassNames);

根据函数名获取函数

先在 UMiddleStudent 类中分别添加两个测试函数。

声明:

UCLASS()
class REFLECTIONDEMO1_API UMiddleStudent : public UStudent
{
    GENERATED_BODY()

public:
    UFUNCTION()
    void Play(const FString InGame);
   
    UFUNCTION()
    int GoHome();
};

实现:

void UMiddleStudent::Play(const FString InGame) {
    UE_LOG(LogTemp, Warning, TEXT("Play's param: %s"), *InGame);
}
int UMiddleStudent::GoHome() {
    return 2;
}

使用 FindFunctionByName() 函数查找指定类中指定函数名的函数,可以包括父类。

UMiddleStudent *MiddleStudent = NewObject<UMiddleStudent>();
UClass *MiddleStudentClass = MiddleStudent->GetClass();
if (MiddleStudentClass) {
    UFunction *PlayFunction = MiddleStudentClass->FindFunctionByName(TEXT("Play"), EIncludeSuperFlag::IncludeSuper);
    if (PlayFunction) {
        FString PlayFunctionName = PlayFunction->GetName();
        UE_LOG(LogTemp, Warning, TEXT("PlayFunctionName: %s"), *PlayFunctionName);
    }
}

使用ProcessEvent调用函数

基于上面查找函数的代码,使用 ProcessEvent() 继续实现函数的调用。

UMiddleStudent *MiddleStudent = NewObject<UMiddleStudent>();
UClass *MiddleStudentClass = MiddleStudent->GetClass();
if (MiddleStudentClass)
{
    UFunction *PlayFunction = MiddleStudentClass->FindFunctionByName(TEXT("Play"), EIncludeSuperFlag::IncludeSuper);
    if (PlayFunction)
    {
        FString PlayFunctionName = PlayFunction->GetName();
        UE_LOG(LogTemp, Warning, TEXT("PlayFunctionName: %s"), *PlayFunctionName);

        // 1.给所有方法参数分配内存并初始化
        uint8 * AllFunctionParam = static_cast<uint8 *>(FMemory_Alloca(PlayFunction->ParmsSize));
        FMemory::Memzero(AllFunctionParam, PlayFunction->ParmsSize);

        // 2.给所有函数参数赋值
        for (TFieldIterator<UProperty> It(PlayFunction); It; ++It)
        {
            UProperty *FunctionParam = *It;
            FString FunctionParamName = FunctionParam->GetName();
            if (FunctionParamName == FString("InGame"))
            {
                *FunctionParam->ContainerPtrToValuePtr<FString>(AllFunctionParam) = "Ball";
            }
        }

        // 3.调用函数
        MiddleStudent->ProcessEvent(PlayFunction, AllFunctionParam);
    }
}

使用Invoke调用函数

除了 ProcessEvent() 我们还能使用 Invoke() 函数来实现函数的调用。

UMiddleStudent *MiddleStudent = NewObject<UMiddleStudent>();
UClass *MiddleStudentClass = MiddleStudent->GetClass();
if (MiddleStudentClass) {
    UFunction *GoHomeFunction = MiddleStudentClass->FindFunctionByName(TEXT("GoHome"), EIncludeSuperFlag::IncludeSuper);
    if (GoHomeFunction) {
        FString GoHomeFunctionName = GoHomeFunction->GetName();
        UE_LOG(LogTemp, Warning, TEXT("GoHomeFunctionName: %s"), *GoHomeFunctionName);
       
        // 1.给所有方法参数分配内存并初始化
        uint8 * AllFunctionParam = static_cast<uint8 *>(FMemory_Alloca(GoHomeFunction->ParmsSize));
        FMemory::Memzero(AllFunctionParam, GoHomeFunction->ParmsSize);

        // 2.创建FFrame
        FFrame Frame(nullptr, GoHomeFunction, &AllFunctionParam);

        // 3.调用函数
        GoHomeFunction->Invoke(MiddleStudent, Frame, &AllFunctionParam + GoHomeFunction->ReturnValueOffset);

        // 4.获取返回值
        int *ResultValue = (int *)(&AllFunctionParam + GoHomeFunction->ReturnValueOffset);
        UE_LOG(LogTemp, Warning, TEXT("GoHome function's return value is: %d"), *ResultValue);
    }
}