Выпуск временных COM-объектов - PullRequest
13 голосов
/ 03 февраля 2010

Рассмотрим следующий код C # с использованием COM-объекта.


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, детерминированный выпуск

В качестве обходного пути я создал вспомогательный класс


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
}

Мои вопросы: Есть ли потенциальные проблемы с этим кодом? У кого-нибудь есть более элегантное решение?

Ответы [ 2 ]

11 голосов
/ 03 февраля 2010

Моим самым большим недостатком было бы имя, T; Add может быть более призрачным в использовании. Я также добавил бы where T : class к универсальному методу, но «свободный API» кажется пригодным для использования. Я также был бы склонен немного сгладить код. Я также вижу некоторые способы использования Expression API для обхода всего дерева и захвата всех промежуточных шагов, но это не будет тривиальным - но представьте:

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

где это дерево выражений, и мы автоматически получаем посредников.

(также вы можете Clear() или null список в Dispose())


Вот так:

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();
    }
}
0 голосов
/ 04 июля 2013

Решение Marc Gravell не будет работать с .Net 4. + из-за введения Dynamic в COM вместо объекта. Кроме того, при тестировании с Excel COM возникает исключение, когда конструктор говорит «Преобразование не поддерживается» (по умолчанию переключатель WalkImpl).

В выражениях есть и другие ограничения: нет индексированных свойств и необязательных аргументов. Никогда ранее не кодировал Expression, тогда я понятия не имею, как решить эти проблемы.

Не будет компилироваться или выполняться:

using (var wrapper = new ComWrapper())
  {
    var application = wrapper.Add(() => new Excel.Application());
    var workbook = wrapper.Add(() => application.Workbooks.Open(@"C:\MyExcel.xls"));

    Excel.Range range = wrapper.Add(() => workbook.Sheets[1].UsedRange);
    string value = wrapper.Add(() => range.Cells[1, 1]).Value2;
  }
...