2010-02-03 2 views
13

Рассмотрите следующий код C#, используя COM-объект.Освобождение временных объектов 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, но не временные объекты returnd по 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 
} 
 

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

+0

(добавлен пример с использованием дерева подход выражение) –

+1

@downvoter: пожалуйста, оставьте комментарий – Henrik

ответ

11

Моей самой большой проблемой было бы имя, 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

Wow! Большое спасибо за этот подробный ответ. Я обязательно попробую. – Henrik

+0

@Henrik - обновлен, чтобы добавить поддержку вызова метода –

+0

Спасибо, это работает. Я немного изменил код, чтобы не выпускать поля. Они будут выпущены в методе Dispose объекта-объекта. Например. var bar = com.Add (() => this.m_foo.Bar); не должен отпускать m_foo. – Henrik

0

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

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

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

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; 
    } 
Смежные вопросы