2009-06-04 2 views
12

COM-Objekte haben normalerweise eine deterministische Zerstörung: Sie werden freigegeben, wenn die letzte Referenz freigegeben wird.C# + COM Interop, deterministische Freigabe

Wie wird dies in C# - COM Interop behandelt? Die Klassen implementieren IDisposable nicht, so sehe ich keine Möglichkeit, eine explizite IUnknown :: Release auszulösen.

Ein zufälliger Test zeigt, dass nicht referenzierte COM-Objekte träge gesammelt werden (d. H. Der Garbage Collector löst die Freigabe aus). Was muss ich für OCM-Objekte tun, die aggressiv veröffentlicht werden müssen? (z.B. das Halten großer oder gemeinsamer kritischer Ressourcen)?

Ursprüngliches Problem: Wir haben eine C# -Anwendung, die stark eine COM-Bibliothek verwendet, und es leckt wie verrückt. Es scheint, dass die Probleme "zwischen" dem C++ - und dem C# -Code liegen (wir haben Zugriff auf beide), aber wir können es nicht finden.

Antwort

16

Sie können COM-Interop-Verweise mithilfe der System.Runtime.InteropServices.Marshal-Klasse bearbeiten. Konkret möchten Sie vielleicht einen Blick auf Marshal.ReleaseComObject werfen.

+2

+1, denn dies ist mein Leben gerettet als einmal. – OregonGhost

5

Wir haben sehr darunter gelitten. Versuchen Sie nicht, zu viele Interop-Referenzen in die .NET-Laufzeitumgebung zu laden. Sie können auch die API Marshal.ReleaseComObject verwenden, wenn Sie etwas sofort freigeben müssen. Eine andere gute Methode besteht darin, Ihren Client-Code umzufunktionieren, um typsichere Wrapper um den Interop-Code zu verwenden - wenn Sie eine bekannte Referenz in Ihrem Code zu jedem Interop-RCW haben, erhöht dies die Wahrscheinlichkeit, dass der Interop-Verweis eingefügt wird eine zeitgerechte Mode. Das Hauptproblem dieser versucht zu vermeiden, ist die der „zu viele Punkte“:

foo.bar.quux.xyzzy.groo(); // where foo, bar, quux and xyzzy are all COM references 

Jedes der Objekte zwischen den Punkten in dem obigen Code effektiv durchgesickert (wahrscheinlich wirklich nicht auf lange Sicht), da haben wir ein impliziter Verweis auf die Instanz. Sie müssten dem Namen Verweise auf jede der Instanzen zu schaffen, um eine gute Chance zu haben, um sie zu reinigen:

Foo foo; 
Bar bar=foo.bar; 
Quux quux=bar.quux; 
Xyzzy xyzzy=quux.xyzzy; 
xyzzy.groo(); 

möglicherweise nun die Laufzeit verwenden, um die Referenz zu lösen:

ReleaseComObject(xyzzy); // etc... 
2

Dies ist von einem related (but subtly different) question, aber ich denke, die Antwort ist ziemlich sauber - so dachte ich, es wäre gerechtfertigt, hier auch hinzuzufügen.

Hier ist eine Option, die Expression Bäume nutzt unsere Absicht zu diskutieren, um den Wert an jedem Knoten erfassen - eine Single-Veröffentlichung ermöglicht:

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 WalkImpl(Expression expr) 
    { 
     switch (expr.NodeType) 
     { 
      case ExpressionType.Constant: 
       return ((ConstantExpression)expr).Value; 
      case ExpressionType.New: 
       NewExpression ne = (NewExpression)expr; 
       object[] args = ne.Arguments.Select(arg => Walk(arg)).ToArray(); 
       return ne.Constructor.Invoke(args); 
      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(); 

       } 
      default: 
       throw new NotSupportedException(); 
     } 
    } 
    public void Dispose() 
    { 
     foreach(object obj in objects) { 
      Marshal.ReleaseComObject(obj); 
      Debug.WriteLine("Released: " + obj); 
     } 
     objects.Clear(); 
    } 
}