准备工作
创建一个 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
宏括号里的元数据,直接读取 UFunction
的 FunctionFlags
属性即可,类型为 EFunctionFlags
。
获取类的标记
和获取函数的标记类似,直接读取 UClass
的 ClassFlags
属性即可,类型为 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);
}
}