tags:

views:

746

answers:

3

COM objects usually have deterministic destruction: they are freed when the last reference is released.

How is this handled in C# - COM Interop? The classes don't implement IDisposable, so I see no way to trigger an explicit IUnknown::Release.

A casual test shows that unreferenced COM objects get collected lazily (i.e. the garbage collector is triggering the release). What should I do for OCM objects that need to be released aggresively? (e.g. holding large or shared critical ressources)?

Original problem: We have a C# application heavily using a COM library, and it is leaking like mad. It seems that the problems is "between" the C++ and the C# code (we have access to both), but we can't nail it down.

+10  A: 

You can manipulate COM interop references using the System.Runtime.InteropServices.Marshal class. Specifically you may want to have a look at Marshal.ReleaseComObject.

Jakob Christensen
+1, because this saved my life more than once.
OregonGhost
+4  A: 

We've suffered from this quite a lot. It's best not to try to load too many interop references into the .Net runtime. Also, you can use the Marshal.ReleaseComObject API if you need to release something right away.

Another good method is to refactor your client code to use typesafe wrappers around the interop code - if you have a known reference in your code to each and every interop RCW, this increases the chance that the interop reference will be GCed in a timely fashion. The main problem this seeks to avoid is the one of "too many dots":

foo.bar.quux.xyzzy.groo(); // where foo, bar, quux and xyzzy are all COM references

Each of the objects between dots in the above code is effectively leaked (probably not really in the long run) since we have an implicit reference to the instance. You would need to create named references to each of the instances in order to have a good chance to clean them up:

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

Now possibly use the runtime to release the reference:

ReleaseComObject(xyzzy); // etc...
1800 INFORMATION
+1  A: 

This is from a related (but subtly different) question, but I think the answer is pretty tidy - so I thought it warranted adding here too.

Here's an option that uses Expression trees to discuss our intent, capturing the value at each node - allowing a single release:

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();
    }
}
Marc Gravell