2010-02-03 9 views
13

Betrachten Sie den folgenden C# -Code mit einem COM-Objekt.Temporäre COM-Objekte freigeben

 

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

Dies wird lösen die COM-Objekte o und baz, aber nicht die temporären Objekte returnd von o.Foo und o.Foo.Bar. Dies kann zu Problemen führen, wenn diese Objekte eine große Menge an nicht verwaltetem Speicher oder anderen Ressourcen enthalten.

Eine naheliegende, aber hässliche Lösung wäre, den Code mit try-finally und Marshal.ReleaseComObject noch mehr zu verstopfen. Siehe C# + COM Interop, deterministic release

Als Abhilfe können, habe ich eine Hilfsklasse

 

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

Verbrauch:

 

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 
} 
 

Meine Fragen: Gibt es mögliche Probleme mit diesem Code? Hat jemand eine elegantere Lösung?

+0

(hinzugefügt, um den Ausdrucksbaum Ansatz an einem Beispiel) –

+1

@downvoter: lassen Sie einen Kommentar – Henrik

Antwort

11

Meine größte Beschwerde wäre der Name, T; Add könnte den Gebrauch mehr illustrieren. Ich würde auch where T : class zu der generischen Methode hinzufügen, aber die "fließende API" scheint verwendbar zu sein. Ich würde auch geneigt sein, den Code ein wenig zu verflachen. Ich kann auch einige Möglichkeiten der Verwendung des Expression API sehe einen ganzen Baum zu gehen und alle Zwischenschritte zu erfassen, aber es wäre nicht trivial - aber vorstellen:

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

wo, dass ein Ausdruck Baum ist und Wir bekommen die Vermittler automatisch.

(auch, könnte man Clear() oder null die Liste in Dispose())


Wie 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(); 
    } 
} 
+0

Wow! Vielen Dank für diese detaillierte Antwort. Ich werde es definitiv versuchen. – Henrik

+0

@Henrik - aktualisiert, um Methodenaufrufunterstützung hinzuzufügen –

+0

Danke, es funktioniert. Ich habe Ihren Code leicht geändert, um Felder nicht freizugeben. Diese werden in der Dispose-Methode des enthaltenden Objekts freigegeben. Z.B. var bar = com.Add (() => this.m_foo.Bar); sollte m_foo nicht freigeben. – Henrik

0

Die Lösung von Marc GRA wird nicht mit .Net arbeiten 4. + weil Einführung von Dynamic in COM anstelle von Objekt. Außerdem gibt es beim Testen mit der Excel-COM eine Ausnahme mit dem Konstruktor "Convert not supported" (Standard des Schalters von WalkImpl).

Es gibt andere Einschränkungen für Ausdrücke als keine indizierten Eigenschaften und keine optionalen Argumente. Noch nie zuvor Ausdruck codiert habe ich keine Ahnung, wie ich diese Probleme angehen soll.

Wird nicht kompilieren oder ausführen:

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