为什么 partial 机制是理解 MonoGame 的关键#
MonoGame 大量使用 C# 的 partial 类做平台适配。GraphicsDevice 在源码里是这样的:
// Graphics/GraphicsDevice.cs(前端)
public partial class GraphicsDevice : IDisposable { ... }
// Platform/Graphics/GraphicsDevice.DirectX.cs(DX 平台)
public partial class GraphicsDevice { ... }多个源文件声明同一个类,编译时合并为一个类。关键规则:partial 类的所有成员(含 private)对所有 partial 部分可见。所以前端文件里调用的 PlatformSetup(),只要任何一个 partial 文件定义了它,就能编译——方法归属是"整个类",不是"某个文件"。
PlatformSetup 覆写的真相#
很多人第一次看会困惑:GraphicsDevice.cs 里调用了 PlatformSetup(),但前端文件里找不到它的声明——它去哪了?
答案:这不是继承覆写(override),而是 partial 类跨文件补充定义:
// 前端 Setup()(GraphicsDevice.cs L349):直接调用,无声明
private void Setup() { ...; PlatformSetup(); ... }
// 平台实现(GraphicsDevice.DirectX.cs L53):定义同名方法
private void PlatformSetup()
{
MaxTextureSlots = 16;
MaxVertexTextureSlots = 16;
CreateDeviceResources();
}partial 类编译后是同一个类,前端调用点与平台实现自动配对。
两类钩子的形态#
MonoGame 源码里平台钩子有两种写法:
形态 A:partial 方法(声明在前端,实现可选)#
// 前端(GraphicsDevice.cs L756):只声明,无实现
partial void PlatformReset();
// 平台(GraphicsDevice.DirectX.cs L73):实现
partial void PlatformReset() { CorrectBackBufferSize(); ... }partial 方法的实现可以省略(省略后调用点被编译器移除),适合"可选钩子"。
形态 B:跨 partial 文件私有方法(前端直接调用,平台必须定义)#
// 前端:直接调用 PlatformSetup();
// 平台:private void PlatformSetup() { ... }平台必须提供实现,否则编译/链接失败。PlatformSetup/PlatformInitialize/PlatformClear/PlatformDraw* 等核心钩子都是这种。
平台实现对比:同一个 PlatformSetup,两个平台各写各的#
| 步骤 | DirectX | OpenGL |
|---|---|---|
| 上下文/设备 | CreateDeviceResources()(D3D11Device) | GL.CreateContext(windowInfo) |
| 能力查询 | _d3dDevice.FeatureLevel | GL.GetInteger(...) |
| 槽位上限 | 写死 16 | 查询驱动实际值 |
前端只要求"调用后 MaxTextureSlots 等成员被填好",平台负责"怎么填"。
C++ 移植对应#
C# 的 partial 合并,在 C++ 里对应"基类 + 钩子":
// 基类(graphics_device.hpp):成员 + 属性 + 前端逻辑 + 纯虚钩子
class GraphicsDevice {
public:
void Clear(const Color &color) {
PlatformClear(ClearOptions::kTarget | ClearOptions::kDepthBuffer | ClearOptions::kStencil,
color.ToVector4(), viewport_.MaxDepth(), 0);
}
virtual void PlatformClear(ClearOptions, const Vector4 &, float, int) = 0;
virtual void PlatformPresent() = 0;
// ...
};
// 平台派生类(Vulkan 后端):override 全部钩子
class VulkanGraphicsDevice : public GraphicsDevice {
void PlatformClear(ClearOptions, const Vector4 &, float, int) override { /* vk 代码 */ }
};一个要注意的 C++ 语言映射:基类构造内不能调用纯虚函数(UB)——C# 前端构造里 Setup() → PlatformSetup() 的调用点,在 C++ 改由派生类构造完成后显式调用 PlatformCreate(),语义等价。