2009-06-04 9 views
12

Les objets COM ont généralement une destruction déterministe: ils sont libérés lorsque la dernière référence est libérée.C# + COM Interop, version déterministe

Comment cela est-il géré dans C# - COM Interop? Les classes n'implémentent pas IDisposable, donc je ne vois aucun moyen de déclencher un IUnknown :: Release explicite. Un test occasionnel montre que des objets COM non référencés sont collectés paresseusement (c'est-à-dire que le garbage collector déclenche la libération). Que dois-je faire pour les objets OCM qui doivent être libérés agressivement? (par exemple, détenir des ressources critiques importantes ou partagées)?

Problème d'origine: Nous avons une application C# utilisant fortement une bibliothèque COM, et il fuit comme un fou. Il semble que les problèmes soient "entre" le code C++ et le code C# (nous avons accès aux deux), mais nous ne pouvons pas le clouer.

Répondre

16

Vous pouvez manipuler des références d'interopérabilité COM à l'aide de la classe System.Runtime.InteropServices.Marshal. Plus précisément, vous voudrez peut-être jeter un oeil à Marshal.ReleaseComObject.

+2

+1, parce que cela m'a sauvé la vie plus d'une fois. – OregonGhost

5

Nous en avons beaucoup souffert. Il est préférable de ne pas essayer de charger trop de références d'interopérabilité dans l'environnement d'exécution .Net. En outre, vous pouvez utiliser l'API Marshal.ReleaseComObject si vous avez besoin de publier quelque chose immédiatement. Une autre bonne méthode consiste à refactoriser votre code client pour utiliser des wrappers typesafe autour du code d'interopérabilité - si vous avez une référence connue dans votre code pour chaque interopérabilité RCW, cela augmente les chances que la référence d'interopérabilité soit GCed une mode opportune. Le principal problème de cette cherche à éviter est celui de « trop de points »:

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

Chacun des objets entre les points dans le code ci-dessus est effectivement fuyait (probablement pas vraiment à long terme) puisque nous avons un référence implicite à l'instance. Vous devez créer des références nommées à chacun des cas afin d'avoir une bonne chance de les nettoyer:

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

Maintenant, peut-être utiliser le moteur d'exécution pour libérer la référence:

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

C'est d'un related (but subtly different) question, mais je pense que la réponse est assez bien rangé - donc je pensais que cela justifie d'ajouter ici aussi.

est ici une option qui utilise Expression arbres pour discuter de notre intention, la capture de la valeur à chaque noeud - permettant une seule sortie:

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