当前位置: 首页 > web >正文

UE4/UE5反射系统动态注册机制解析

文章目录

  • 核心目标
  • 阶段一:静态收集(编译期)
    • 1. 宏的展开
    • 2. 注册到全局列表
  • 阶段二:动态注册(运行时)
    • 1.启动核心模块(CoreUObject)
    • 2.调用 Register() 函数
    • 3.处理依赖,有序初始化
    • 4.动态加载与循环
  • 总结与比喻


核心目标

UE需要实现一个跨模块(DLL)、支持热重载的反射系统。这意味着:

  1. 编译器需要知道所有UObject类的信息(类名、大小、属性、函数等)。

  2. 运行时需要按正确的顺序创建UClass对象,尤其是在不同DLL中的类可能存在依赖关系(例如Game.dll中的AMyCharacter继承自Engine.dll中的ACharacter)时,必须确保父类的UClass先于子类被创建


阶段一:静态收集(编译期)

这个过程发生在编译每个模块(如Engine.dll, Game.dll)的时候。

1. 宏的展开

假设我们在一个游戏项目中有一个类,定义在MyCharacter.h中:

UCLASS()
class AMyCharacter : public ACharacter
{GENERATED_BODY()// ... 其他成员 ...
};

在它的.cpp文件中,必然有对应的IMPLEMENT宏:

IMPLEMENT_CLASS(AMyCharacter, 12345) // 12345 是假设的CRC校验值

这个宏会被预处理器展开,其核心是创建一个静态全局变量

static TClassCompiledInDefer<AMyCharacter> AutoInitializeAMyCharacter(TEXT("AMyCharacter"), sizeof(AMyCharacter), 12345, AMyCharacter::StaticClass, &ACharacter::StaticClass // 父类信息
);

这个静态变量AutoInitializeAMyCharacter会在模块加载时(在main函数之前)就完成初始化。

2. 注册到全局列表

TClassCompiledInDefer的构造函数会做一件关键的事情:将自己(即类的元信息)添加到一个全局的、模块内部的数组中

这个数组就是DeferredCompiledInRegistration。你可以把它想象成这个模块的“待办事项列表”:

// 伪代码,在每个模块内部都存在这样一个列表
TArray<FRegistrarInfo> DeferredCompiledInRegistration;void TClassCompiledInDefer::TClassCompiledInDefer(...){// 将 {类名, 类大小, 类CRC, 静态函数指针} 打包为一个信息结构体FRegistrarInfo Info = { ... };// 将这个结构体PUSH到模块的“待办事项列表”中DeferredCompiledInRegistration.Add(Info);
}

同时,它也可能将自己注册到一个Map中,用于快速查找。

至此,编译期的工作就完成了。 每个模块都独立地收集好了自己内部的所有UClass信息,并存放在自己的静态数组中。这些信息是无序的、分散的

阶段二:动态注册(运行时)

这个过程发生在引擎启动或模块动态加载时。

1.启动核心模块(CoreUObject)

引擎最先加载CoreUObject.dll。这个模块的启动代码会调用一个非常重要的函数:ProcessNewlyLoadedUObjects()

2.调用 Register() 函数

ProcessNewlyLoadedUObjects()函数会遍历当前已加载的所有模块中的那个“待办事项列表”(DeferredCompiledInRegistration数组)。

对于列表中的每一个项目,它都会调用其**Register()函数。这个Register()**函数的核心工作是:

void TClassCompiledInDefer<T>::Register()
{// 1. 创建或获取对应的 UClass 对象UClass* Class = TStaticClass<AMyCharacter>::Get();// 2. 调用关键函数:将 UClass 注册到全局的 UObject 系统中UObjectForceRegistration(Class); // 3. 【关键步骤】并不是直接完全初始化,而是将这类 UClass 对象添加到一个“延迟注册链表”中//    (您提到的链表,即 DeferredCompiledInRegistration 链表)AddToDeferredRegistrationList(Class);
}

注意:此时只是把UClass对象放入了另一个链表,并没有完全初始化它。因为它的父类UClass可能还没有被注册和初始化。

3.处理依赖,有序初始化

现在,所有已知的UClass都被放到了一个全局的“延迟注册链表”里。接下来,ProcessNewlyLoadedUObjects()会进入最精妙的环节:清空这个链表

它会对链表中的UClass对象进行排序和初始化。如何解决依赖?

  • 它首先会确保一个类的父类它依赖的类(例如类属性指定的UPROPERTY类型所在的类)先被完全初始化。

  • 对于AMyCharacter,系统会发现它的父类是ACharacter。它会检查:

    • 如果ACharacterUClass已经初始化,太好了,直接初始化AMyCharacterUClass

    • 如果ACharacterUClass还没初始化(比如ACharacter在另一个还没加载的Engine.dll里),那么AMyCharacter的初始化就会被跳过,留在链表里,等待下一次机会。

4.动态加载与循环

  1. 引擎继续运行,现在需要加载Engine.dll
  2. Engine.dll被加载到内存中,它的静态变量自动初始化,将其内部的类(包括ACharacter)的信息添加到它自己的DeferredCompiledInRegistration数组中。
  3. 引擎再次调用ProcessNewlyLoadedUObjects()
  4. 这次调用会处理所有模块(包括刚加载的Engine.dll)的“待办事项列表”。它为ACharacter创建UClass,并将其加入“延迟注册链表”。
  5. 在尝试清空链表时,系统发现:
  • ACharacter的父类(APawn)可能也需要检查,但假设其父类已就绪
  • 现在ACharacterUClass可以被成功初始化。
  • 更重要的是,之前被跳过的AMyCharacter,它的依赖(父类ACharacter)现在已经可用了!
  • 于是,系统现在可以顺利地初始化AMyCharacterUClass了。

这个过程会不断重复:
加载DLL -> 调用ProcessNewlyLoadedUObjects -> 填充链表 -> 尝试清空链表(解决依赖)-> 剩余未解决的留在链表中等待下次循环。

总结与比喻

你可以把整个系统想象成一个不断扩大的拼图游戏

  • 静态收集:每个模块(DLL)就像一个袋子,里面装着一堆无序的拼图碎片(类信息)。每个袋子自己不知道整个图画的全貌。

  • 动态注册

    1. 打开袋子:每当一个DLL被加(ProcessNewlyLoadedUObjects),就把这个袋子里的所有碎片倒在一张大桌子(全局链表)上。

    2. 拼图:系统尝试从桌上的碎片中拼出完整的图。规则是:边缘碎片(父类、依赖类)必须先拼好,才能拼中间的碎片(子类)

    3. 等待:如果发现某块碎片(如AMyCharacter)缺了它依赖的边缘碎片(ACharacter),就把它暂时放回桌上不动。

    4. 新袋子:当新的DLL(如Engine.dll)被加载,就等于又打开了一个新袋子,倒出了新的碎片(如ACharacter)。

    5. 继续拼:现在有了新的碎片,之前无法完成的拼图现在可以继续了。

    6. 重复这个过程,直到所有袋子都打开,所有拼图都完成。

这种设计的精妙之处在于:

  • 解耦:每个模块只关心自己的信息收集,不知道其他模块的存在和加载顺序。
  • 弹性:无论DLL以何种顺序动态加载,系统都能通过多次尝试,最终解决所有依赖关系。
  • 支持热重载:重新加载一个DLL时,只需重复“打开袋子倒碎片 -> 拼图”的过程,系统会自动处理更新。
http://www.xdnf.cn/news/20581.html

相关文章:

  • 线程间通信
  • 使用Terraform管理阿里云基础设施
  • 319章:使用Scrapy框架构建分布式爬虫
  • 还在重启应用改 Topic?Spring Boot 动态 Kafka 消费的“终极形态”
  • 企业级 Django 日志配置示例
  • 【三维生成】Matrix-3D:全向可探索的三维世界生成
  • 基于脚手架微服务的视频点播系统-播放控制部分
  • 算法题(201):传球游戏
  • 低代码开发平台.技术
  • Github操作
  • 训练+评估流程
  • java设计模式二、工厂
  • 5-2EFCore性能优化
  • FLINK:水位线的介绍
  • C/C++数据结构之栈基础
  • FastAPI + LangChain 和 Spring AI + LangChain4j
  • qt creator新手入门以及结合sql server数据库开发
  • Spring Cloud Gateway 进行集群化部署
  • Vscode中开发VUE项目的调试方案
  • Android开发-Activity传递信息
  • [论文阅读] 人工智能 + 软件工程 | 首个仓库级多任务调试数据集!RepoDebug揭秘LLM真实调试水平
  • Objective-C方法参数标签怎么设置
  • 华为基于IPD的产品质量计划模板
  • 苍穹外卖Day12 | Apache POI、导出Excel报表、HttpServletResponse、工作台
  • Tomcat 日志文件名的命名规范
  • 腾讯云语音接口实现会议系统
  • 《sklearn机器学习——管道和复合估算器》可视化复合估计器
  • AI 驱动数据分析:开源 SQLBot 项目探索,基于大模型和 RAG 实现精准问数与图表挖掘
  • 小白AIGC短视频生成的第一课之混元AI视频
  • HTTPS优化简单总结