Interoperabilidad C # + COM, lanzamiento determinista

Los objetos COM suelen tener una destrucción determinista: se liberan cuando se libera la última referencia.

¿Cómo se maneja esto en C # – COM Interop? Las clases no implementan IDisposable , por lo que no veo ninguna manera de activar un IUnknown :: Release explícito.

Una prueba informal muestra que los objetos COM no referenciados se recolectan perezosamente (es decir, el recolector de basura está activando la liberación). ¿Qué debo hacer para los objetos de OCM que deben liberarse de forma agresiva? (por ejemplo, manteniendo recursos críticos grandes o compartidos)?

Problema original: tenemos una aplicación de C # que usa mucho una biblioteca COM, y tiene fugas. Parece que los problemas están “entre” el código C ++ y el código C # (tenemos acceso a ambos), pero no podemos precisarlo.

Puede manipular las referencias de interoperabilidad COM utilizando la clase System.Runtime.InteropServices.Marshal. Específicamente, es posible que desee echar un vistazo a Marshal.ReleaseComObject .

Hemos sufrido mucho por esto. Es mejor no intentar cargar demasiadas referencias de interoperabilidad en el tiempo de ejecución .Net. Además, puede usar la API Marshal.ReleaseComObject si necesita liberar algo de inmediato.

Otro buen método es refactorizar el código de su cliente para usar envoltorios seguros contra tipos alrededor del código de interoperabilidad: si tiene una referencia conocida en su código para cada RCW de interoperabilidad, esto aumenta la posibilidad de que la referencia de interoperabilidad se realice de manera oportuna. El principal problema que trata de evitar es el de “demasiados puntos”:

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

Cada uno de los objetos entre puntos en el código anterior se filtra efectivamente (probablemente no a largo plazo) ya que tenemos una referencia implícita a la instancia. Necesitará crear referencias con nombre a cada una de las instancias para tener una buena oportunidad de limpiarlas:

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

Ahora posiblemente use el tiempo de ejecución para liberar la referencia:

 ReleaseComObject(xyzzy); // etc... 

Esto es de una pregunta relacionada (pero sutilmente diferente) , pero creo que la respuesta es bastante ordenada, así que pensé que era necesario agregarla también aquí.

Aquí hay una opción que usa árboles de Expression para discutir nuestra intención, capturando el valor en cada nodo, permitiendo una única versión:

 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 objects = new List(); public T Add(Expression> 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(); } }