C# + COM Interop,确定性释放

12

COM对象通常具有确定性的销毁:只有在最后一个引用被释放时才会被释放。

C#中的COM互操作如何处理这种情况?这些类没有实现IDisposable接口,因此我看不到触发显式IUnknown :: Release的方法。

一个简单的测试显示,未引用的COM对象会被惰性地回收(即由垃圾收集器来触发释放)。对于需要被积极释放的COM对象(例如持有大型或共享的关键资源),我应该怎么办?

原始问题:我们有一个强烈依赖COM库的C#应用程序,并且它泄漏得非常厉害。似乎问题出现在C++和C#代码之间(我们可以访问两者),但我们无法找到问题所在。

3个回答

16

您可以使用System.Runtime.InteropServices.Marshal类操纵COM互操作引用。具体而言,您可能希望查看Marshal.ReleaseComObject


5
我们遇到过这个问题。最好不要尝试将太多的Interop引用加载到.Net运行时中。此外,如果您需要立即释放某些内容,可以使用Marshal.ReleaseComObject API。
另一种好的方法是重构客户端代码,使用类型安全的包装器来包装Interop代码 - 如果您的代码中对每个Interop RCW都有已知的引用,那么这增加了Interop引用及时被GC的机会。这种方法主要解决的问题是“点太多”的问题:
foo.bar.quux.xyzzy.groo(); // where foo, bar, quux and xyzzy are all COM references

上面代码中点号之间的每个对象都会被泄漏(长期来看可能并不是真的),因为我们对实例有隐式引用。您需要创建对每个实例的命名引用,才能有很好的机会清理它们:

Foo foo;
Bar bar=foo.bar;
Quux quux=bar.quux;
Xyzzy xyzzy=quux.xyzzy;
xyzzy.groo();

现在可以使用运行时来释放引用:

ReleaseComObject(xyzzy); // etc...

2
这是来自一个与相关(但稍有不同)的问题,但我认为答案非常整洁 - 因此我认为值得在这里添加。
这里有一个选项,它使用表达式树来讨论我们的意图,捕获每个节点的值 - 允许单个发布:
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 WalkImpl(Expression expr)
    {
        switch (expr.NodeType)
        {
            case ExpressionType.Constant:
                return ((ConstantExpression)expr).Value;
            case ExpressionType.New:
                NewExpression ne = (NewExpression)expr;
                object[] args = ne.Arguments.Select(arg => Walk(arg)).ToArray();
                return ne.Constructor.Invoke(args);
            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();

                }
            default:
                throw new NotSupportedException();
        }
    }
    public void Dispose()
    {
        foreach(object obj in objects) {
            Marshal.ReleaseComObject(obj);
            Debug.WriteLine("Released: " + obj);
        }
        objects.Clear();
    }
}

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