释放临时COM对象

13
考虑下面使用 COM 对象的 C# 代码。

MyComObject o = new MyComObject;
try
{
 var baz = o.Foo.Bar.Baz;
 try
 { 
  // do something with baz
 }
 finally
 {
  Marshal.ReleaseComObject(baz);
 }
}
finally
{
 Marshal.ReleaseComObject(o);
}

这将释放 COM 对象 `o` 和 `baz`,但不释放由 `o.Foo` 和 `o.Foo.Bar` 返回的临时对象。当这些对象持有大量未管理的内存或其他资源时,这可能会导致问题。
一个明显但不太好的解决方案是,在代码中添加更多的 `try-finally` 和 `Marshal.ReleaseComObject`。请参见 C# + COM Interop, deterministic release
为了解决这个问题,我创建了一个帮助类

class TemporaryComObjects: IDisposable
{
 public C T<C>(C comObject)
 {
  m_objects.Add(comObject);
  return comObject;
 }
 public void Dispose()
 {
  foreach (object o in m_objects)
   Marshal.ReleaseComObject(o);
 }
}

用法:

using (TemporaryComObjects t = new TemporaryComObjects())
{
 MyComObject o = t.T(new MyComObject);
 var baz = t.T(t.T(t.T(o.Foo).Bar).Baz);
 // do something with baz
}

我的问题: 这段代码存在潜在的问题吗? 有人有更优雅的解决方案吗?

(使用表达式树方法的示例) - Marc Gravell
1
@downvoter:请留下评论。 - Henrik
1个回答

11

我最大的抱怨是名字,TAdd 可能更能说明用途。我还会在泛型方法中添加 where T : class,但“流畅API”似乎可用。我也倾向于简化代码。我还可以看到一些使用 Expression API 来遍历整个树并捕获所有中间步骤的方法,但这并不容易 - 但想象一下:

using(var com = new SomeWrapper()) {
    var baz = com.Add(() => new MyComObject().Foo.Bar.Baz);
}

这里的表达式树是自动生成中间结果。

(此外,您可以在Dispose()中清空列表或将其设置为null)


就像这样:

static class ComExample {
    static void Main()
    {
        using (var wrapper = new ReleaseWrapper())
        {
            var baz = wrapper.Add(
                () => new Foo().Bar.Baz);
            Console.WriteLine(baz.Name);
        }
    }
}

class ReleaseWrapper : IDisposable
{
    List<object> objects = new List<object>();
    public T Add<T>(Expression<Func<T>> func)
    {
        return (T)Walk(func.Body);
    }
    object Walk(Expression expr)
    {
        object obj = WalkImpl(expr);
        if (obj != null && Marshal.IsComObject(obj) && !objects.Contains(obj)) 
        {
            objects.Add(obj);
        }
        return obj;
    }
    object[] Walk(IEnumerable<Expression> args)
    {
        if (args == null) return null;
        return args.Select(arg => Walk(arg)).ToArray();
    }
    object WalkImpl(Expression expr)
    {
        switch (expr.NodeType)
        {
            case ExpressionType.Constant:
                return ((ConstantExpression)expr).Value;
            case ExpressionType.New:
                NewExpression ne = (NewExpression)expr;
                return ne.Constructor.Invoke(Walk(ne.Arguments));
            case ExpressionType.MemberAccess:
                MemberExpression me = (MemberExpression)expr;
                object target = Walk(me.Expression);
                switch (me.Member.MemberType)
                {
                    case MemberTypes.Field:
                        return ((FieldInfo)me.Member).GetValue(target);
                    case MemberTypes.Property:
                        return ((PropertyInfo)me.Member).GetValue(target, null);
                    default:
                        throw new NotSupportedException();

                }
            case ExpressionType.Call:
                MethodCallExpression mce = (MethodCallExpression)expr;
                return mce.Method.Invoke(Walk(mce.Object), Walk(mce.Arguments));
            default:
                throw new NotSupportedException();
        }
    }
    public void Dispose()
    {
        foreach(object obj in objects) {
            Marshal.ReleaseComObject(obj);
            Debug.WriteLine("Released: " + obj);
        }
        objects.Clear();
    }
}

哇!非常感谢您提供如此详细的答案。我一定会尝试实践它。 - Henrik
谢谢,它有效。我稍微修改了您的代码,不释放字段。这些将在包含对象的Dispose方法中释放。例如,var bar = com.Add(() => this.m_foo.Bar); 不应释放m_foo。 - Henrik
2
我们已经使用这个工具有一段时间了,它的表现很好,除了在添加许多对象时存在性能问题。通过将List更改为HashSet来解决了这个问题。 - Henrik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接