13

Consider the following C# code using a COM object.


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

This will release the COM objects o and baz, but not the temporary objects returnd by o.Foo and o.Foo.Bar. This can cause problems, when those objects hold a large amount of unmanaged memory or other resources.

An obvious but ugly solution would be, to clutter the code even more with try-finally and Marshal.ReleaseComObject. See C# + COM Interop, deterministic release

As a workaround, I created a helper class


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);
 }
}

Usage:


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
}

My questions: Are there potential problems with this code? Has anybody a more elegant solution?

2
  • (added an example using the expression tree approach) Commented Feb 3, 2010 at 12:00
  • 1
    @downvoter: please leave a comment Commented Dec 8, 2010 at 7:51

1 Answer 1

11

My biggest gripe would be the name, T; Add might be more illusrative of the usage. I'd also add where T : class to the generic method, but the "fluent API" seems usable. I'd also be inclined to flatten the code a bit. I can also see some ways of using the Expression API to walk an entire tree and capture all the intermediate steps, but it wouldn't be trivial - but imagine:

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

where that is an expression tree and we get the intermediaries automatically.

(also, you could Clear() or null the list in Dispose())


Like so:

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();
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Wow! Thank you very much for this detailed answer. I will definitely try it.
Thanks, it works. I changed your code slightly to not release fields. These will be released in the Dispose method of the containing object. E.g. var bar = com.Add(() => this.m_foo.Bar); should not release m_foo.
We use this for quite some time now and it works well except for a performance problem when many objects are added. This was solved by changing the List to a HashSet.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.