从Unreal的Cast函数探寻反射系统父子关系的建立过程

23 篇文章 12 订阅

我们在使用UObject系统的时候,会经常使用Cast函数,能够安全地进行父子转换,能够做到的这一点的原理是什么呢?让我们一探究竟。

Cast函数长这样。

// Dynamically cast an object type-safely.
template <typename To, typename From>
FORCEINLINE To* Cast(From* Src)
{
	return TCastImpl<From, To>::DoCast(Src);
}

TCastImpl实际长这样(因为我们是UObject互相转换,所以使用的是下面的特化模板Struct)

template <typename From, typename To>
struct TCastImpl<From, To, ECastType::UObjectToUObject>
{
	FORCEINLINE static To* DoCast( UObject* Src )
	{
		return Src && Src->IsA<To>() ? (To*)Src : nullptr;
	}
....
}

可见,内部是使用了UObject::IsA实现的判断,成功则使用C Cast转换。

我们稍微多探寻几步,会发现其实是利用了 UStruct::IsChildOf接口。

	/** Struct this inherits from, may be null */
	UStruct* GetSuperStruct() const
	{
		return SuperStruct;
	}
bool UStruct::IsChildOf( const UStruct* SomeBase ) const
{
	if (SomeBase == nullptr)
	{
		return false;
	}

	bool bOldResult = false;
	for ( const UStruct* TempStruct=this; TempStruct; TempStruct=TempStruct->GetSuperStruct() )
	{
		if ( TempStruct == SomeBase )
		{
			bOldResult = true;
			break;
		}
	}
......
	return bOldResult;
}
#endif

所以判断UStruct A是否UStruct B的Child,只要依次上溯SuperStruct对比即可。

所以问题变为:UStruct的SuperStruct是什么时候赋值的?

通过对代码的研究观察,我们发现有这么个函数

void UClass::SetSuperStruct(UStruct* NewSuperStruct)
{
	UnhashObject(this);
	ClearFunctionMapsCaches();
	Super::SetSuperStruct(NewSuperStruct);

	if (!GetSparseClassDataStruct())
	{
		if (UScriptStruct* SparseClassDataStructArchetype = GetSparseClassDataArchetypeStruct())
		{
			SetSparseClassDataStruct(SparseClassDataStructArchetype);
		}
	}

	HashObject(this);
}

对于UClass来说,手动调用了SetSuperStruct函数进行了赋值。

再追寻UClass::SetSuperStruct的调用过程,我们发现这么一条路线

GetPrivateStaticClass->GetPrivateStaticClassBody->InitializePrivateStaticClass->SetSuperStruct

.generated.h文件有DECLARE_CLASS宏

DECLARE_CLASS(AMyActor, AActor, COMPILED_IN_FLAGS(0 | CLASS_Config), CASTCLASS_None, TEXT("/Script/MyGame"), NO_API) \

( 关于GENERATED_BODY宏如何展开,可以参考我的另一篇文章UE4 generated.h文件生成过程模拟_桃溪小小生的博客-CSDN博客 )

恰巧,在这个宏里面声明了GetPrivateStaticClass函数

 可以看见同时还声明并实现了StatciClass,内部调用的GetPrivateStaticClass())

.gen.cpp中有如下代码

IMPLEMENT_CLASS_NO_AUTO_REGISTRATION(AMyActor);

GetPrivateStaticClass函数恰好在IMPLEMENT_CLASS宏里面进行了实现。

OK,现在入口函数是GetPrivateStaticClass,只要调用了这个函数,最终就会向文章开头那样建立起Class的SuperStruct。可以看到,类的StaticClass中调用了GetPrivateStaticClass函数。

到这里,我们从SetSuperStruct追寻到了StaticClass函数。接下来,问题演变为:在哪里调用了类的StaticClass函数?

根据UE的设计规则,一定不是在某个地方显式调用了T::StaticClass(为什么?因为引擎模块并不知道具体应用的某个类的存在,当然不可能显式调用),而是作为某种函数回调传给引擎模块,让引擎模块在某个阶段进行回调。所以我们来搜寻一下,哪里处理了StaticClass函数。

我们在.gen.cpp找到了下面这个地方

struct Z_CompiledInDeferFile_FID_Projects_MyGame_Source_MyGame_MyActor_h_Statics
{
static const FClassRegisterCompiledInInfo ClassInfo[];
};
const FClassRegisterCompiledInInfo Z_CompiledInDeferFile_FID_Projects_MyGame_Source_MyGame_MyActor_h_Statics::ClassInfo[] = {
{ Z_Construct_UClass_AMyActor, AMyActor::StaticClass, TEXT("AMyActor"), &Z_Registration_Info_UClass_AMyActor, CONSTRUCT_RELOAD_VERSION_INFO(FClassReloadVersionInfo, sizeof(AMyActor), 3805165363U) },
};
static FRegisterCompiledInInfo Z_CompiledInDeferFile_FID_Projects_MyGame_Source_MyGame_MyActor_h_4051704720(TEXT("/Script/MyGame"),
Z_CompiledInDeferFile_FID_Projects_MyGame_Source_MyGame_MyActor_h_Statics::ClassInfo, UE_ARRAY_COUNT(Z_CompiledInDeferFile_FID_Projects_MyGame_Source_MyGame_MyActor_h_Statics::ClassInfo),
nullptr, 0,
nullptr, 0);

.gen.cpp中有一个FRegisterCompiledInInfo类型的static变量,static意味着什么?意味着在程序正式启动前,变量就执行了初始化,而其中恰好用到了StaticClass函数。

我们看到ClassInfo里面用到了StaticClass,ClassInfo是个FClassRegisterCompiledInInfo,长这样


/**
 * Composite class register compiled in info
 */
struct FClassRegisterCompiledInInfo
{
	class UClass* (*OuterRegister)();
	class UClass* (*InnerRegister)();
	const TCHAR* Name;
	FClassRegistrationInfo* Info;
	FClassReloadVersionInfo VersionInfo;
};

ClassInfo初始化的时候,StaticClass作为第二个成员变量,这里对应的是InnerRegister,它也的确是一个函数指针。

Unreal如此使用static变量,就是为了要在程序正式启动前,收集到所有的class的信息,然后在Unreal启动时进行反射处理。

所以,下一个问题,收集了StaticClass后,什么时候调用它?

我们进入FRegisterCompiledInInfo里面看一下,它有哪些内容。

struct FRegisterCompiledInInfo
{
	template <typename ... Args>
	FRegisterCompiledInInfo(Args&& ... args)
	{
		RegisterCompiledInInfo(std::forward<Args>(args)...);
	}
};

它进行了参数的转发,然后真正执行的是RegisterCompiledInInfo,我们来看看RegisterCompiledInInfo

// Multiple registrations
void RegisterCompiledInInfo(const TCHAR* PackageName, const FClassRegisterCompiledInInfo* ClassInfo, size_t NumClassInfo, const FStructRegisterCompiledInInfo* StructInfo, size_t NumStructInfo, const FEnumRegisterCompiledInInfo* EnumInfo, size_t NumEnumInfo)
{
	for (size_t Index = 0; Index < NumClassInfo; ++Index)
	{
		const FClassRegisterCompiledInInfo& Info = ClassInfo[Index];
		RegisterCompiledInInfo(Info.OuterRegister, Info.InnerRegister, PackageName, Info.Name, *Info.Info, Info.VersionInfo);
	}

	for (size_t Index = 0; Index < NumStructInfo; ++Index)
	{
		const FStructRegisterCompiledInInfo& Info = StructInfo[Index];
		RegisterCompiledInInfo(Info.OuterRegister, PackageName, Info.Name, *Info.Info, Info.VersionInfo);
		if (Info.CreateCppStructOps != nullptr)
		{
			UScriptStruct::DeferCppStructOps(FName(Info.Name), (UScriptStruct::ICppStructOps*)Info.CreateCppStructOps());
		}
	}

	for (size_t Index = 0; Index < NumEnumInfo; ++Index)
	{
		const FEnumRegisterCompiledInInfo& Info = EnumInfo[Index];
		RegisterCompiledInInfo(Info.OuterRegister, PackageName, Info.Name, *Info.Info, Info.VersionInfo);
	}
}

RegisterCompiledInInfo有好几个重载,为什么我专门列出这个实现呢?因为static变量的参数对应的就是这个版本,第一个参数是TEXT("/Script/MyGame"),这里匹配的就是这个版本。

对于InnerRegister的使用(记住,它现在就是我们的StaticClass),主要是下面这行代码

RegisterCompiledInInfo(Info.OuterRegister, Info.InnerRegister, PackageName, Info.Name, *Info.Info, Info.VersionInfo);

它长下面这样

void RegisterCompiledInInfo(class UClass* (*InOuterRegister)(), class UClass* (*InInnerRegister)(), const TCHAR* InPackageName, const TCHAR* InName, FClassRegistrationInfo& InInfo, const FClassReloadVersionInfo& InVersionInfo)
{
	check(InOuterRegister);
	check(InInnerRegister);
	bool bExisting = FClassDeferredRegistry::Get().AddRegistration(InOuterRegister, InInnerRegister, InPackageName, InName, InInfo, InVersionInfo, nullptr);
#if WITH_RELOAD
	if (bExisting && !IsReloadActive())
	{
		// Class exists, this can only happen during hot-reload or live coding
		UE_LOG(LogUObjectBase, Fatal, TEXT("Trying to recreate class '%s' outside of hot reload and live coding!"), InName);
	}
#endif
	FString NoPrefix(UObjectBase::RemoveClassPrefix(InName));
	NotifyRegistrationEvent(InPackageName, *NoPrefix, ENotifyRegistrationType::NRT_Class, ENotifyRegistrationPhase::NRP_Added, (UObject * (*)())(InOuterRegister), false);
	NotifyRegistrationEvent(InPackageName, *(FString(DEFAULT_OBJECT_PREFIX) + NoPrefix), ENotifyRegistrationType::NRT_ClassCDO, ENotifyRegistrationPhase::NRP_Added, (UObject * (*)())(InOuterRegister), false);
}

使用InInnerRegister的代码是下面这样

	bool bExisting = FClassDeferredRegistry::Get().AddRegistration(InOuterRegister, InInnerRegister, InPackageName, InName, InInfo, InVersionInfo, nullptr);

又被添加到了别的地方,我们继续看AddRegistration的实现

bool TDeferredRegistry::AddRegistration(TType* (*InOuterRegister)(), TType* (*InInnerRegister)(), const TCHAR* InPackageName, const TCHAR* InName, TInfo& InInfo, const TVersion& InVersion, FFieldCompiledInInfo* DeprecatedFieldInfo)
	{
#if WITH_RELOAD
		const FPackageAndNameKey Key = FPackageAndNameKey{ InPackageName, InName };
		TInfo** ExistingInfo = InfoMap.Find(Key);

		bool bHasChanged = !ExistingInfo || (*ExistingInfo)->ReloadVersionInfo != InVersion;
		InInfo.ReloadVersionInfo = InVersion;
		TType* OldSingleton = ExistingInfo ? ((*ExistingInfo)->InnerSingleton ? (*ExistingInfo)->InnerSingleton : (*ExistingInfo)->OuterSingleton) : nullptr;

		bool bAdd = true;
		if (ExistingInfo)
		{
			if (IReload* Reload = GetActiveReloadInterface())
			{
				bAdd = Reload->GetEnableReinstancing(bHasChanged);
			}
			if (bAdd)
			{
				if (!bHasChanged)
				{
					// With live coding, the existing might be the same as the new info.  
					// We still invoke the copy method to allow UClasses to clear the singletons.
					UpdateSingletons(InInfo, **ExistingInfo);
				}
				*ExistingInfo = &InInfo;
			}
		}
		else
		{
			InfoMap.Add(Key, &InInfo);
		}
		if (bAdd)
		{
			Registrations.Add(FRegistrant{ InOuterRegister, InInnerRegister, InPackageName, &InInfo, OldSingleton, DeprecatedFieldInfo, bHasChanged });
		}
		return ExistingInfo != nullptr;
#else
		Registrations.Add(FRegistrant{ InOuterRegister, InInnerRegister, InPackageName, &InInfo, DeprecatedFieldInfo });
		return false;
#endif
	}

主要起作用的是下面这行代码

		Registrations.Add(FRegistrant{ InOuterRegister, InInnerRegister, InPackageName, &InInfo, DeprecatedFieldInfo });

这里有个小问题,我们之前使用的是FClassDeferredRegistry::Get().AddRegistration

为什么这里是TDeferredRegistry::AddRegistration?

FClassDeferredRegistry 定义如下

using FClassDeferredRegistry = TDeferredRegistry<FClassRegistrationInfo>;
using FEnumDeferredRegistry = TDeferredRegistry<FEnumRegistrationInfo>;
using FStructDeferredRegistry = TDeferredRegistry<FStructRegistrationInfo>;
using FPackageDeferredRegistry = TDeferredRegistry<FPackageRegistrationInfo>;

可以看出TDeferredRegistry是个模板类,而FClassDeferredRegistry是模板进行了实例化。因为我们这里处理的是Class,所以使用的是FClassDeferredRegistry,同级别的其他实例化用于处理Enum Struct等。

到这里,StaticClass被构造为FRegistrant,添加进数组Registrations里面去了。

FRegistrant长这样

/**
	* Maintains information about a pending registration
	*/
	struct FRegistrant
	{
		TType* (*OuterRegisterFn)();
		TType* (*InnerRegisterFn)();
		const TCHAR* PackageName;
		TInfo* Info;
#if WITH_RELOAD
		TType* OldSingleton;
#endif
		FFieldCompiledInInfo* DeprecatedFieldInfo;

#if WITH_RELOAD
		bool bHasChanged;
#endif
	};

这里StaticClass变为了InnerRegisterFn。

所以,问题进一步变为,什么时候使用Registrations里面的InnerRegisterFn?

因为Registrations是个成员变量,我们注意到TDeferredRegistry有个接口返回了它

	/**
	* Return the collection of registrations
	*/
	TArray<FRegistrant>& GetRegistrations()
	{
		return Registrations;
	}

并且有一个地方使用这个接口干了可疑的事情

/** Register all loaded classes */
void UClassRegisterAllCompiledInClasses()
{
#if WITH_RELOAD
	TArray<UClass*> AddedClasses;
#endif
	SCOPED_BOOT_TIMING("UClassRegisterAllCompiledInClasses");

	FClassDeferredRegistry& Registry = FClassDeferredRegistry::Get();

	Registry.ProcessChangedObjects();

	for (const FClassDeferredRegistry::FRegistrant& Registrant : Registry.GetRegistrations())
	{
		UClass* RegisteredClass = FClassDeferredRegistry::InnerRegister(Registrant);
#if WITH_RELOAD
		if (IsReloadActive() && Registrant.OldSingleton == nullptr)
		{
			AddedClasses.Add(RegisteredClass);
		}
#endif
	}
......
}

UClassRegisterAllCompiledInClasses函数里面,最终拿到了InnerRegister,并且进行了调用!

UClassRegisterAllCompiledInClasses顾名思义,就是对所有的Class进行注册!

它的调用地方如下

 原来,在CoreUObject模块,启动的时候,一开始就启动了所有的StaticClass的调用,进行建立起子类的SuperStruct信息。

所以针对父子关系的建立,我们作如下总结

UHT工具会为源文件生成反射文件.generated.h和gen.cpp,cpp文件有static变量,用于在程序启动一开始就将类的信息注册到FClassDeferredRegistry里面。

在CoreUObject的启动函数中,拿到所有的注册信息,进行了调用,从而执行StaticClass函数,在这个函数里面,会一步步建立UClass的SuperStruct信息。最后Cast函数调用的时候,使用UClass::IsChildOf接口,它使用了UClass的SuperStruct信息,判断所有父类中是否存在要转的Class,如果存在,则IsChildOf成立,Cast调用成功。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值