如何在c#程序中以RAII风格管理COM对象运行时

本文关键字:管理 风格 COM 对象 运行时 RAII 程序 | 更新日期: 2023-09-27 18:05:03

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

COM组件是一个外进程服务器,所以它的对象驻留在一个独立的相当重的进程中,直到驻留在它中的所有对象被释放才会终止。我希望所有的对象都尽快释放,这样我就可以肯定地知道,一旦我释放了最后一个对象,out- process服务器进程就会终止,不再消耗系统资源。这不是我的偏执狂——有几个重量级进程消耗系统资源,尤其是内存,对性能来说真的很糟糕。

到目前为止,我制作了一个实现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语句。

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

如何在c#程序中以RAII风格管理COM对象运行时

请参阅Hans Passant对使用IDisposable清理Excel互操作对象的回答。

总之,您根本不需要IDisposable。在所有COM引用都超出作用域后显式地调用垃圾收集,即不是从使用它们的函数中调用,而是从该函数的调用者中调用。

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

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

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)

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

如果你愿意写一些c++/cli代码,至少可以为托管堆对象选择堆栈语义。这避免了在对象退出作用域时编译器为您调用dispose时使用或最终包装的需要。

当然它会给你带来一种比c#,头文件,->,::等更嘈杂的语言。

作为一个规则,如果你的代码能够在using块的局部范围内创建和释放COM对象,我会留在c#中,忍受using的麻烦。

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