接口工厂与 AppSystem 生命周期
Source 的模块(DLL/SO)之间不直接链接对方的类,而是通过字符串版本号命名的接口互相查找;一组需要同生共死的模块则被组织成 AppSystemGroup,按固定阶段加载、连接、初始化和关闭。启动器、专用服务器、引擎内部的“Mod 层”都复用这套机制。
相关源码文件#
- 工厂注册:
public/tier1/interface.h、tier1/interface.cpp - AppSystem 接口:
public/appframework/IAppSystem.h、public/appframework/IAppSystemGroup.h - AppSystemGroup 实现:
appframework/AppSystemGroup.cpp、appframework/WinApp.cpp - 全局接口指针表:
interfaces/interfaces.cpp
1. CreateInterface 工厂#
每个模块内部维护一个由静态对象串成的单链表。InterfaceReg 构造时把自己挂到全局链表头:tier1/interface.cpp:56-64;导出的 CreateInterface(name, &rc) 线性遍历链表、用 strcmp 比较版本字符串,命中则调用工厂函数:tier1/interface.cpp:78-104。
注册宏有三种语义:
| 宏 | 语义 | 证据 |
|---|---|---|
EXPOSE_INTERFACE | 每次 CreateInterface 都 new 一个实例 | public/tier1/interface.h:110-112 |
EXPOSE_SINGLE_INTERFACE_GLOBALVAR | 返回已有全局对象地址(单例) | public/tier1/interface.h:124-126 |
EXPOSE_SINGLE_INTERFACE | 自动定义静态单例再走上一条 | public/tier1/interface.h:138-140 |
工厂函数签名是 void* (*)(const char *pName, int *pReturnCode),返回码 IFACE_OK/IFACE_FAILED:public/tier1/interface.h:70。典型用法:引擎以 EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CEngineAPI, IEngineAPI, VENGINE_LAUNCHER_API_VERSION, s_EngineAPI ) 向启动器暴露自身:engine/sys_dll2.cpp:526;client.dll 以同样方式暴露 CHLClient:game/client/cdll_client_int.cpp:1058。
约束:同一个模块里如果两个注册使用相同版本字符串,
CreateInterface返回的是链表中先遇到的那个(即后构造的静态对象,因为新节点插在表头)。这是从链表插入顺序推出的行为,本次没有找到依赖该行为的代码。
2. IAppSystem 契约#
每个可被组管理的系统实现 Connect/Disconnect/QueryInterface/Init/Shutdown,可选地声明依赖与 tier:public/appframework/IAppSystem.h:56-81。Init 返回 InitReturnVal_t:public/appframework/IAppSystem.h:37-43。
3. AppSystemGroup 的阶段机#
证据:阶段枚举 public/appframework/IAppSystemGroup.h:93-110;Run 先 OnStartup,到达 RUNNING 才调用 Main,最后 OnShutdown:appframework/AppSystemGroup.cpp:759-785;OnStartup 的阶段推进 appframework/AppSystemGroup.cpp:806-843;OnShutdown 的回退 appframework/AppSystemGroup.cpp:845-890。
关键行为:
- 失败回退按阶段跳转。 如果启动停在
PREINITIALIZATION/INITIALIZATION,关闭时直接跳到disconnect;停在CREATION/DEPENDENCIES/CONNECTION则只做destroy:appframework/AppSystemGroup.cpp:850-867。 - Init 失败会回滚已初始化的系统。
InitSystems在某个系统失败时逆序Shutdown之前成功的系统:appframework/AppSystemGroup.cpp:639-656。 - 连接/初始化正序,断开/关闭逆序:
appframework/AppSystemGroup.cpp:610-633、appframework/AppSystemGroup.cpp:658-665。 - 依赖自动补齐并排序。
LoadDependentSystems读取每个系统的GetDependencies(),缺的就按模块名加载,再计算并按依赖排序:appframework/AppSystemGroup.cpp:568-604;排序使用冒泡式比较,因为比较函数不满足严格传递性:appframework/AppSystemGroup.cpp:462-475。
4. 系统的加入与查找#
AddSystem(module, name)用模块工厂创建接口;重复名字只告警并返回已有实例:appframework/AppSystemGroup.cpp:202-233。AddSystems(list)遍历以空字符串结尾的{模块名, 接口名}表,任一失败即整体失败:appframework/AppSystemGroup.cpp:301-316。FindSystem(name)的查找顺序:本组字典 → 本组各系统的QueryInterface→ 非 AppSystem 工厂 → 父组:appframework/AppSystemGroup.cpp:322-358。- 组对外暴露的工厂
AppSystemCreateInterfaceFn就是对“当前组”的FindSystem:appframework/AppSystemGroup.cpp:735-753。Run开始时把当前组设为自己、结束时恢复为父组:appframework/AppSystemGroup.cpp:762、appframework/AppSystemGroup.cpp:782。
这条“向父组回退”的规则让引擎内部的 Mod 层可以看到启动器层加载的所有系统(见下一节)。
5. 运行时的三层组#
| 层 | 类 | 加入的系统 | 证据 |
|---|---|---|---|
| 1 | CSteamApplication | vstdlib 的 cvar、文件系统模块 | appframework/WinApp.cpp:148-171 |
| 2 | CSourceAppSystemGroup(启动器) | engine、filesystem 的 QueuedLoader、inputsystem、vphysics、materialsystem、datacache×3、studiorender、soundemittersystem、vscript、(Win32) soundsystem、vguimatsurface、vgui2、engine 的 IEngineAPI,再按条件加 scaleformui | launcher/launcher.cpp:727-810 |
| 3 | CModAppSystemGroup(引擎内部) | matchmaking、client/server 声明的共享系统、引擎自身的工具框架 | engine/sys_dll2.cpp:2231、engine/sys_dll2.cpp:2279-2330 |
第 1 层在 Main 里调用 m_pChildAppSystemGroup->Setup( m_pFileSystem, this ) 后运行第 2 层:appframework/WinApp.cpp:202-210;第 2 层的 Main 调 IEngineAPI::Run,引擎再以第 2 层为父创建第 3 层:launcher/launcher.cpp:969-972、engine/sys_dll2.cpp:1420-1431。
游戏 DLL 声明自己需要的共享系统:server 通过 CServerDLLSharedAppSystems 申请 soundemittersystem 与 scenefilecache:game/server/gameinterface.cpp:4036-4046;引擎在 Mod 组 Create 中合并 server 与 client 的列表并去重:engine/sys_dll2.cpp:2279-2321。
6. 全局接口指针(interfaces 库)#
许多模块并不在每处调用 factory(...),而是在 Connect 时调用 ConnectTier1/2/3Libraries,由 interfaces 库按一张“接口名 → 全局指针地址”表批量填充:interfaces/interfaces.cpp:104-130、interfaces/interfaces.cpp:243-272。ConnectTier1Libraries 只是对它的薄封装:tier1/tier1.cpp:21-24。启动器的 PreInit 就是先连 tier1、注册 ConVar,再连 tier2/3:launcher/launcher.cpp:880-889;server.dll 的 DLLInit 开头同样如此:game/server/gameinterface.cpp:702-706。
约束与边界#
- 接口版本号是二进制兼容契约:调用方与实现方必须使用完全相同的字符串,否则
CreateInterface返回NULL,启动在AddSystems或DLLInit的空指针检查处失败(例如game/server/gameinterface.cpp:727-728)。 FindSystem的“谁先实现谁胜出”取决于系统加入顺序,源码注释本身也把这一点标为未决问题:appframework/AppSystemGroup.cpp:332-339。