DEBUG.WIKI
晚安云;香港 CN2 ¥15/月
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
OnlineSIM:在线接收短信,虚拟号码租用

.NET 10 COM 调用以 0xc0000005 崩溃,检查派生类先用的顺序

先经 COM 调派生类、再调基类可能命中未恢复的虚方法槽。核对服务端运行时,采用正式修正,或分开 COM 非虚入口与内部虚方法。

Windows 的 .NET COM 服务从旧运行时迁移后,某次接口方法调用让整个进程以 0xc0000005 退出,调试器显示 ExecutionEngineException。更特殊的线索是调用顺序:先调用派生类对象,再调用同接口的基类对象才崩溃;基类方法里的断点还没有命中。

.NET 10.0.6、10.0.7 存在这样的具体故障组合。相关变化也涉及 .NET 9 的虚方法槽延迟填充,但不能仅凭进程退出码就判断所有 COM 崩溃都是同一问题。

检查 COM 接口与虚方法继承关系

典型关系如下,方法体省略的是应用自己的业务逻辑:

public class Strings : IStrings
{
    public virtual void Add(string item) { /* 原有业务逻辑 */ }
}

public class FileUris : Strings
{
    public override void Add(string item) { /* 派生类业务逻辑 */ }
}

IStrings 是应用已有的 COM 可见接口。先经 COM 使用 FileUris,再经相同接口使用 Strings,可能命中共享 ComMethodTable 与尚未恢复的基类虚方法槽。分派时读到空槽,甚至没有进入 C# 方法体,就尝试执行空地址。

要保留完整原生堆栈,关注 COMToCLRDispatchHelper、COMToCLRInvokeTarget 与故障地址。托管异常捕获无法可靠挽救这种运行时访问违规;在 Add 周围反复增加 try/catch,不会填好分派目标。

同时记录 COM 服务进程实际加载的运行时、位数及发布方式。客户端自己的 .NET 版本不一定就是服务端版本;自包含发布也不因机器安装了新共享运行时就自动更新。

优先采用已经恢复基类方法槽的正式实现

.NET 10.0.12 的正式源码已在 COM 接口方法表布局时,沿基类 MethodTable 恢复对应虚方法槽,避免延迟填充留下空入口。它是一个能确认包含该修正的正式版本,不表示这项修复首次出现在 10.0.12。

按服务端部署方式更新包含修正的兼容运行时或自包含产物,重新启动服务,再从原 COM 客户端检查两种调用顺序。不要只更新客户端,或只看到共享运行时已安装就跳过服务进程实际加载版本的核对。

可控代码中,把 COM 入口与可覆盖行为分开

需要过渡处理时,可以保留公开 COM 方法作为非虚入口,把派生类变化移到内部受保护的虚方法。下面仍是类结构片段,原接口声明、GUID、COM 可见性及业务行为应保留:

public class Strings : IStrings
{
    public void Add(string item)
    {
        AddInternal(item);
    }

    protected virtual void AddInternal(string item)
    {
        /* 原 Add 的基类业务逻辑 */
    }
}

public class FileUris : Strings
{
    protected override void AddInternal(string item)
    {
        /* 原 Add override 的派生类业务逻辑 */
    }
}

COM 接口继续调用 Add,托管内部再分派到 AddInternal。迁移时要检查原 override 是否调用了 base.Add,以及参数校验、事件和状态变化放在哪一层,避免移动方法后执行两遍或漏掉基类处理。

这也会改变公开托管类的可覆盖方式。如果外部程序集继承了 Strings 或覆盖 Add,需要同步评估二进制与源代码兼容性,不能把这段当作不影响调用方的机械替换。若类层级不可控,应优先使用经确认修正了该分派问题的运行时,并用原 COM 客户端验证。

仅在启动时“先调用一次基类方法”,或把客户端变量改成 dynamic,可能改变触发路径,却不能证明共享槽问题已经消失。长期依赖初始化顺序,会把缺陷留给另一条请求或另一个进程启动顺序。

验收必须经过真正的 COM 边界,分别覆盖派生类先用和基类先用,并核对调用到的实现及业务结果。一次普通 C# 的 new Strings().Add(...) 成功,无法验证 COM 方法表布局。保留改动前类结构与可恢复构建;恢复时同时考虑依赖这些类型的托管消费者,不能只替换服务端一份 DLL。