2009-06-04 2 views
12

Gli oggetti COM di solito hanno una distruzione deterministica: vengono liberati quando viene rilasciato l'ultimo riferimento.C# + COM Interop, rilascio deterministico

Come viene gestito in C# - Interoperabilità COM? Le classi non implementano IDisposable, quindi non vedo alcun modo per attivare un IUnknown :: Release esplicito.

Un test casuale mostra che gli oggetti COM senza riferimento vengono raccolti pigramente (cioè il garbage collector sta attivando il rilascio). Cosa devo fare per gli oggetti OCM che devono essere rilasciati aggressivamente? (ad esempio, detenere risorse critiche grandi o condivise)?

Problema originale: abbiamo un'applicazione C# che utilizza pesantemente una libreria COM e perde come un matto. Sembra che i problemi siano "tra" il C++ e il codice C# (abbiamo accesso ad entrambi), ma non possiamo individuarlo.

risposta

16

È possibile modificare i riferimenti di interoperabilità COM utilizzando la classe System.Runtime.InteropServices.Marshal. In particolare si consiglia di dare un'occhiata a Marshal.ReleaseComObject.

+2

+1, perché questo mi ha salvato la vita più di una volta. – OregonGhost

5

Ne abbiamo sofferto parecchio. È meglio non provare a caricare troppi riferimenti di interoperabilità nel runtime .Net. Inoltre, puoi utilizzare l'API Marshal.ReleaseComObject se devi rilasciare qualcosa subito.

Un altro buon metodo consiste nel rifattorizzare il codice client per utilizzare wrapper typesafe attorno al codice di interoperabilità: se si dispone di un riferimento noto nel codice per ogni RCP di interoperabilità, ciò aumenta le probabilità che il riferimento di interoperabilità sia GCed in una moda puntuale. Il problema principale questo cerca di evitare è quello di "troppi puntini":

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

Ciascuno degli oggetti tra i punti del codice di cui sopra è effettivamente trapelato (probabilmente non realmente nel lungo periodo) dal momento che abbiamo un riferimento implicito all'istanza. Si avrebbe bisogno di creare nome riferimenti a ciascuna delle istanze al fine di avere una buona occasione per pulirli:

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

Ora eventualmente utilizzare il runtime per rilasciare il riferimento:

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

Questo è da un related (but subtly different) question, ma penso che la risposta sia abbastanza ordinata - quindi ho pensato che fosse necessario aggiungere anche qui.

Ecco un'opzione che utilizza Expression alberi per discutere il nostro intento, catturando il valore ad ogni nodo - che permette una singola release:

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