如何在C#程序中以RAII样式管理COM对象运行时?

时间:2011-08-08 11:15:41

标签: c# .net com com-interop raii

我的C#程序使用具有大量各种接口和子对象的COM组件。问题是每次我检索一些COM接口时都会创建RCW并且RCW存在未知时间(直到GC收集)。每个RCW都在COM服务器中保存一些对象。

COM组件是一个out-proc服务器,因此它的对象驻留在一个单独的非常重的进程中,该进程在释放驻留在其中的所有对象之前不会终止。我希望所有对象尽快发布,以便我确切地知道,一旦我发布了最后一个对象,out-proc服务器进程就会终止并且不再消耗系统资源。这不是我的偏执 - 有几个重量级进程消耗系统资源,特别是内存,这对性能来说真的很糟糕。

到目前为止,我制作了一个实现IDisposable的泛型类,接受对象的引用(事实上是RCW)并在Dispose实现中调用Marshal.FinalReleaseComObject

为了这个问题,让我们暂时忽略因使用FinalReleaseComObject()而引起的问题 - 我知道它们并且可以容忍它们。

真正的问题是我被迫对所有对象使用using或写finally个子句并在那里调用Dispose()。它有效,但是代码变得混乱,带来了许多额外的布线,这让我很烦恼。

例如,我需要为someObject.Params.ColorParams.Hint属性分配一个字符串 - 我不能只写

someObject.Params.ColorParams.Hint = whatever;

因为每个访问者都会有一个我需要释放的COM对象,所以我改为:

using( MyWrapper<IParams> params = new MyWrapper<IParams>( someObject.Params ) ) {
    using( MyWrapper<IColorParams> colorParams =
         new MyWrapper<IColorParams>( params.Controlled ) )
    {
        colorParams.Controlled.Hint = whatever;
    }
}

这是最简单的例子 - 有时我需要访问五个级别的内容,然后我写一组五级深度的using语句。

这个问题有更优雅的解决方案吗?

3 个答案:

答案 0 :(得分:1)

请参阅Hans Passant的this answer to Clean up Excel Interop Objects with IDisposable

总之,您根本不需要IDisposable。只是在所有COM引用超出范围之后显式调用垃圾收集 - 即不是来自使用它们的函数,而是来自该函数的调用者。

答案 1 :(得分:0)

我不确定我是否完全理解这个问题,但如果我这样做(代码混乱),有两种可能的解决方案:

包装器方法 - 您可以定义一个简单的方法:

TResult Exec<TResult>(Func<TResult> func, params MarshalByRefObject comObjects)
{
  TResult res = default(TResult);
  try
  {
    res = func.Invoke();
  }
  catch (Exception e) { /* Log? */ }
  finally
  {
    foreach (MarshalByRefObject combObj in comObjects)
    {    /* release resources using common interface or reflection */ }
  }
}

非常类似于之前的方法,只使用众多AOP框架中的一个(例如PostSharp

答案 2 :(得分:0)

c#和资源的确定性释放通常是最麻烦的 释放进程外COM对象就是问题的一个例子。

如果您乐意编写一些C ++ / cli代码,它至少可以选择托管堆对象的堆栈语义。当对象超出范围时,这就避免了编译器调用dispose时使用或最终包装的需要。

当然它确实为您带来了比c#,头文件更嘈杂的语言。, - &gt;,:: etc。

作为一项规则,如果您的代码能够在使用块内的本地范围内创建和释放COM对象,我将留在C#中并忍受使用麻烦。

如果您的代码需要将COM对象保留为成员变量,我会将这些类移动到c ++ / cli,并利用成员变量的堆栈语义来自动为您调度处理调用。