2010-02-03 19 views
13

Considere el siguiente código C# utilizando un objeto COM.Liberación de objetos COM temporales

 

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

Esto liberará los objetos de COM o y baz, pero no los objetos temporales returnd por o.Foo y o.Foo.Bar. Esto puede causar problemas cuando esos objetos contienen una gran cantidad de memoria no administrada u otros recursos.

Una solución obvia pero fea sería desordenar el código aún más con try-finally y Marshal.ReleaseComObject. Ver C# + COM Interop, deterministic release

Como solución, he creado una clase de ayuda

 

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

Uso:

 

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 
} 
 

Mis preguntas: ¿Existen problemas potenciales con este código? ¿Alguien tiene una solución más elegante?

+0

(añade un ejemplo usando el método del árbol de expresión) –

+1

@downvoter: por favor deje un comentario – Henrik

Respuesta

11

Mi mayor queja sería el nombre, T; Add podría ser más ilusorio del uso. También agregaría where T : class al método genérico, pero la "API fluida" parece utilizable. También me inclinaría a aplanar el código un poco. También puedo ver algunas formas de utilizar la API de Expression de caminar todo un árbol y capturar todos los pasos intermedios, pero no sería trivial - pero imagino:

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

donde es un árbol de expresión y conseguimos los intermediarios automáticamente

(también, usted podría Clear() o null la lista de Dispose())


así:

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! Muchas gracias por esta respuesta detallada. Definitivamente lo intentare. – Henrik

+0

@Henrik - actualizado para agregar soporte de llamada a método –

+0

Gracias, funciona. Cambié tu código ligeramente para no liberar campos. Estos se liberarán en el método Dispose del objeto que lo contiene. P.ej. var bar = com.Add (() => this.m_foo.Bar); no debería liberar m_foo. – Henrik

0

La solución de Marc Gravell no funcionará con .Net 4. + porque de introducción de Dynamic en COM en lugar de objeto. Además, cuando se prueba con el COM de Excel, hay una excepción con el constructor que dice "Convertir no compatible" (predeterminado del interruptor de WalkImpl).

Existen otras limitaciones con las expresiones como propiedades no indexadas y sin argumentos opcionales. Nunca antes codifiqué Expresión. No tengo idea de cómo abordar estos problemas.

no se compilará o ejecutar:

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; 
    } 
Cuestiones relacionadas