2009-06-04 15 views
12

Obiekty COM zwykle mają deterministyczne zniszczenie: są uwalniane po zwolnieniu ostatniego odnośnika.C# + COM Interop, deterministic release

Jak to się robi w C# - COM Interop? Klasy nie implementują IDisposable, więc nie widzę sposobu, aby wyzwolić jawne IUnknown :: Release.

Nieformalny test pokazuje, że obiekty COM bez odwołań są gromadzone leniwie (tj. Wywoływacz wywołuje zwolnienie). Co powinienem zrobić dla obiektów OCM, które muszą zostać wydane agresywnie? (np. utrzymywanie dużych lub współdzielonych zasobów krytycznych)?

Oryginalny problem: Mamy aplikację C# mocno korzystającą z biblioteki COM, która wycieka jak szalona. Wydaje się, że problem dotyczy "pomiędzy" C++ i C# (mamy dostęp do obu), ale nie możemy tego naprawić.

Odpowiedz

16

Możesz modyfikować odwołania między wzorami COM za pomocą klasy System.Runtime.InteropServices.Marshal. W szczególności możesz chcieć rzucić okiem na Marshal.ReleaseComObject.

+2

+1, bo to uratowało mi życie więcej niż jeden raz. – OregonGhost

5

Doświadczyliśmy tego całkiem sporo. Najlepiej nie próbować ładować zbyt wielu odniesień do odwołań do środowiska wykonawczego .Net. Możesz również użyć interfejsu API Marshal.ReleaseComObject, jeśli chcesz coś od razu opublikować.

Inną dobrą metodą jest refaktorowanie kodu klienta w celu użycia bezpiecznych typów opakowań wokół kodu interopera - jeśli masz znane odniesienie w swoim kodzie do każdego interwera RCW, zwiększa to szansę, że odniesienie do interopcji będzie GCed w w odpowiednim czasie. Główny problem ten ma na celu uniknięcie jest jednym z „zbyt wiele punktów”:

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

Każdy z obiektów między punktami w powyższym kodzie jest skutecznie wyciekły (prawdopodobnie nie naprawdę na dłuższą metę), ponieważ mamy niejawne odwołanie do instancji. trzeba by utworzyć nazwanych odniesień do każdego z przypadków, aby mieć szansę, aby oczyścić je:

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

Teraz może wykorzystywać czas pracy, aby zwolnić odniesienia:

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

To jest od related (but subtly different) question, ale myślę, że odpowiedź jest całkiem zadbana - więc pomyślałem, że uzasadnia to również dodanie tutaj.

Oto opcja, która wykorzystuje Expression drzew, aby omówić naszą intencję, przechwytywanie wartości w każdym węźle - umożliwiając singiel:

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