非托管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
相关产品推荐
相关产品推荐

