我正在寻找管理.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对象的引用?
答案 0 :(得分:1)
但实际上我们已经开始使用我们的软件存在内存问题
这是一个经典的成长痛苦问题。软件永远不会变小,团队中没有人因编写否定代码而获奖。添加越来越多的功能,您的程序永远不会使用 less 内存。标准诊断是消耗第一千兆字节的虚拟内存空间需要很长时间。第二个千兆字节很快消失了。
你现在所处的非常非生产性,你正在担心少数几兆字节。只需将开关拨到非问题状态即可。将本机代码编译为64位。您不会受到托管代码的任何异议,x64抖动已经知道如何在没有任何更改的情况下执行此操作。
答案 1 :(得分:1)
我已经确定了两种可能的解决方案。两者都涉及更改 COM 接口的托管定义,尽管您根本不需要更改非托管代码。
IntPtr
。然后,当您的托管方法准备好返回时,通过 IntPtr
从您的 COM 对象获取一个 Marshal.GetIUnknownForObject()
。此时,您可以安全地对 COM 对象调用“Marshal.ReleaseComObject()”。 public interface IDoodadCreator
{
IntPtr IDoodadCreator.CreateDoodad();
}
public class FancyComponent : IDoodadCreator
{
public IntPtr IDoodadCreator.CreateDoodad()
{
// IDoodad is implemented in unmanaged c++
IDoodad doodad = new IDoodad();
...
IntPtr doodadPtr = Marshal.GetIUnknownForObject(doodad);
Marshal.ReleaseComObject(doodad);
return doodadPtr;
}
}
或
object
,并定义接口以使用自定义封送拆收器,在将对象封送至 IntPtr 后释放该对象。 public interface IDoodadCreator
{
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ReleasingMarshaler))]
object IDoodadCreator.CreateDoodad();
}
public class FancyComponent : IDoodadCreator
{
public object IDoodadCreator.CreateDoodad()
{
// IDoodad is implemented in unmanaged c++
IDoodad doodad = new IDoodad();
...
return doodad;
}
}
public class ReleasingMarshaler : ICustomMarshaler
{
...
public IntPtr MarshalManagedToNative(object managedObj)
{
IntPtr managedObjPtr = Marshal.GetIUnknownForObject(managedObj);
Marshal.ReleaseComObject(managedObj);
return managedObjPtr;
}
}
答案 2 :(得分:-1)
我们当前问题的答案是GC.AddMemoryPressure。
因为我们是通过未管理类的RCW实例创建和引用的,所以垃圾收集器不知道为这些对象分配的内存。 根据MSDN对此方法的帮助:
在确定何时计划垃圾收集时,运行时会考虑分配的管理内存量。如果小型托管对象分配大量非托管内存,则运行时仅考虑托管内存,因此低估了调度垃圾回收的紧迫性。
http://msdn.microsoft.com/en-us/library/system.gc.addmemorypressure.aspx
换句话说,并不是我们需要显式释放COM对象......我们只需要告诉GC有多少内存与它们相关联。实际上,在某些关键位置实现此方法后,垃圾收集器的表现要好得多。