【发布时间】:2013-06-28 20:53:25
【问题描述】:
我正在寻找管理在 .Net 程序集中创建的 COM 对象的生命周期的想法,然后将其传递回非托管代码。
背景: 我们的软件引擎是用非托管 c++ 编码的。我们软件的功能可以使用引擎的 COM 对象组件进行扩展。在大多数情况下,引擎本身多年来不需要改变。但是,我们将继续构建更多组件以向我们的软件添加新功能。虽然这些组件过去也是用非托管 c++ 构建的,但在过去的几年里,我们一直在用 C# 编写这些插件。
直到最近,我们才赞同 Marshal.ReleaseComObject 不应该在 C# 组件中使用的非托管 COM 对象上调用的想法,而是允许 RCW 和垃圾回收管理它们的生命周期。
这个理论听起来不错,但实际上我们的软件已经开始出现内存问题,这似乎是由于 GC 懒于清理这些 COM 对象,以至于在某些情况下我们的软件会耗尽所有可用内存失败了。
我们是否可能犯了一些错误,导致 GC 和 RCW 无法对这些对象进行操作?垃圾收集的想法不是会做必要的事情来确保内存在需要时是空闲的吗?
由于没有更好的想法,我们不情愿地开始使用 Marshal.ReleaseComObject(在某些情况下是 FinalReleaseComObject)。一般来说,这似乎可以解决问题。
但是,在尝试开发一致的模式来使用 Marshal.ReleaseComObject 时,我们发现了一种情况,我们不确定是否可以使用它。某些组件需要将 COM 对象返回值传递回非托管引擎。该组件将永远不会再次看到返回值,并且(当前)在不再使用它时无法收到通知。例如:
// Unmanaged C++ code
Engine::Engine() : m_ipDoodadCreator(CLSID_FancyComponent)
{
}
void Engine::ProcessDoodad()
{
try
{
// Smart pointer
IDoodadPtr ipDoodad = m_ipDoodadCreator->CreateDoodad();
...
ipDoodad = NULL; // Calls Release...
}
catch (...) { ... }
// At this point the doodad lives on because the RCW is holding a
// reference to it.
}
// Managed C# Component
public class FancyComponent : IDoodadCreator
{
public IDoodad IDoodadCreator.CreateDoodad()
{
// IDoodad is implmented in unmanaged c++
IDoodad doodad = new IDoodad();
try
{
...
return doodad;
}
finally
{
// Can't do this here because engine doesn't yet have
// a reference to doodad.
// Marshal.ReleaseComObject(doodad);
}
}
}
到目前为止,为了在这种情况下确定性地让 RCW 释放其引用,我们能想出的唯一想法是重新设计引擎,以便所有接口都有一个 Cleanup() 调用,该调用被调用有时,但这会很耗时,在某些情况下,实施起来很麻烦。
tl;dr 有没有办法强制 RCW 释放其对返回到非托管环境中的 COM 对象的引用?
【问题讨论】:
-
IDoodad应该从IUnknown继承,所以你应该能够正常调用Release我上次检查 -
Release 减少引用计数。如果其他东西(RCW)仍然有参考,doodad 继续存在。在上面的代码中,我暗示 IDoodadPtr 是一个智能指针,当它超出范围时调用 Release。
-
你的意思是COM Callable Wrapper?根据MSDN,如果正常调用release,它将释放对象进行垃圾回收。
-
是的,我不否认它有资格进行垃圾收集。但垃圾收集似乎并不总是及时发生。这就是我们试图以调用 ReleaseComObject 的形式进行干预的原因。但是当对象是返回值时怎么办呢?编辑:如果我有我的 RCW 和 CCW 的直通,我认为这是我们在这里谈论的运行时可调用包装器,而不是 CCW,因为在我的示例中,Doodad 是非托管的 c++ COM 对象。
-
尝试手动管理调试会话的生命周期,看看
Release在您希望释放所有其他引用时返回的内容。AddRef很可能被调用了太多次。
标签: .net com-interop