You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非托管COM对象引用垃圾回收问题:反射调用时类型丢失异常

解决COM互操作中GC回收后RCW变__ComObject导致反射失败的问题

我刚看完你的问题,这个坑我之前也踩过——本质是.NET对COM对象的包装(RCW)被GC回收了,导致原本的强类型对象退化为通用的__ComObject,反射自然找不到对应的属性了。

为啥会出现这问题?

当你用.NET调用C++ COM DLL时,.NET会自动生成一个Runtime Callable Wrapper(RCW,运行时可调用包装),它相当于托管代码和COM对象之间的翻译层。这个RCW会帮你管理COM对象的引用计数,但如果你的代码里没有任何强引用指向这个RCW,GC就会把它当成垃圾回收掉。一旦RCW没了,objMammals就只剩下一个底层COM对象的通用包装__ComObject,这个类型根本没有你定义的Cats属性,反射调用自然抛异常。

你说提前创建Cats或Ears的引用就正常,就是因为这个引用把RCW“抓”住了,GC不敢随便回收它,所以类型信息一直保留着。

怎么解决?

1. 给RCW留个强引用

最直接的办法就是确保在你需要反射调用的整个过程中,有一个不会被GC回收的强引用指向objMammals。比如:

  • 把objMammals声明成类级别的成员变量,而不是方法里的局部变量(局部变量容易被GC标记为可回收);
  • 提前获取并保存你要访问的属性引用,比如先把objMammals.Cats存到一个变量里。

代码示例:

// 把objMammals改成类级变量,避免被GC回收
private Mammals _objMammals;

public void ProcessMammals()
{
    _objMammals = new Mammals();
    
    int mammalsId = (int)_objMammals.GetType().InvokeMember(
        "FindById", 
        System.Reflection.BindingFlags.InvokeMethod, 
        null, 
        _objMammals, 
        new object[]{0}
    );
    
    GC.Collect(); // 就算触发GC,_objMammals的强引用还在,类型不会变
    
    object catsObject = _objMammals.GetType().InvokeMember(
        "Cats", 
        System.Reflection.BindingFlags.GetProperty, 
        null, 
        _objMammals, 
        new object[]{1}
    );
}

如果只能用局部变量,就提前抓个属性引用:

Mammals objMammals = new Mammals();
// 提前保存Cats的引用,把RCW“钉”住
var keepAliveCats = objMammals.Cats;

int mammalsId = (int)objMammals.GetType().InvokeMember("FindById", System.Reflection.BindingFlags.InvokeMethod, null, objMammals, new object[]{0});

GC.Collect();

// 此时objMammals还是原来的类型,反射正常
object catsObject = objMammals.GetType().InvokeMember("Cats", System.Reflection.BindingFlags.GetProperty, null, objMammals, new object[]{1});

2. 手动控制COM对象的引用计数

如果你必须手动触发GC,也可以用Marshal类来手动管理COM对象的引用计数,防止RCW被回收时底层COM对象被释放:

Mammals objMammals = new Mammals();
// 获取COM对象的IUnknown指针
IntPtr comPtr = Marshal.GetIUnknownForObject(objMammals);
try
{
    // 手动增加引用计数,让COM对象不会被随便释放
    Marshal.AddRef(comPtr);
    
    int mammalsId = (int)objMammals.GetType().InvokeMember("FindById", System.Reflection.BindingFlags.InvokeMethod, null, objMammals, new object[]{0});
    
    GC.Collect();
    
    object catsObject = objMammals.GetType().InvokeMember("Cats", System.Reflection.BindingFlags.GetProperty, null, objMammals, new object[]{1});
}
finally
{
    // 记得释放引用计数,恢复原有状态
    Marshal.Release(comPtr);
}

3. 尽量别手动调用GC

除非你有非常明确的性能需求,否则别随便调用GC.Collect()。.NET的GC本身就有一套优化的回收策略,手动触发反而容易打乱它的节奏,尤其是在COM互操作这种有特殊生命周期管理的场景下,很容易出问题。

额外要检查的点

  • 重新生成Interop程序集:用tlbimp.exe重新生成一次COM DLL的Interop程序集,确保它包含完整的类型元数据,没有遗漏属性或方法;
  • 隔离模式配置:你的DLL用了隔离模式,检查清单文件里的COM隔离配置是否正确,确保对象的创建和销毁逻辑符合预期。

内容的提问来源于stack exchange,提问作者Bhupendra Singh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:35:31