remarks reflexion example c# reflection clr proxy-classes

example - reflexion c#



¿Puedo usar la reflexión con las instancias de RealProxy? (3)

Creo que esto puede ser un error en el marco .Net. De alguna manera, el método RuntimePropertyInfo.GetValue está eligiendo la implementación incorrecta para la propiedad ICollection<>.Count , y parece tener que ver con las proyecciones de WindowsRuntime. Tal vez el código de acceso remoto se renovó cuando pusieron la interoperabilidad de WindowsRuntime en el marco.

Cambié el framework al target .Net 2.0 porque pensé que si se trataba de un error, no debería estar en ese marco. Al convertir, Visual Studio eliminó la verificación "Preferir 32 bit" en mi proyecto de consola de exe (ya que esto no existe en 2.0). Se ejecuta sin excepción cuando esto no está presente.

En resumen, se ejecuta en .Net 2.0 en 32 y 64 bits. Se ejecuta en .Net 4.x en 64 bit. La excepción se produce en .Net 4.x 32 bit solamente. Esto seguro parece un error. Si puede ejecutarlo en 64 bits, eso sería una solución.

Tenga en cuenta que he instalado .Net 4.6, y esto reemplaza gran parte de .Net framework v4.x. Podría ser aquí donde se presenta el problema; No puedo probar hasta que obtenga una máquina que no tiene .Net 4.6.

Actualización: 2015-09-08

También ocurre en una máquina con solo .Net 4.5.2 instalado (no 4.6).

Actualización: 2015-09-07

Aquí hay una reproducción más pequeña, usando sus mismas clases:

static void Main(string[] args) { var myList = MyListProxy.CreateProxy(new[] {"foo", "bar", "baz", "quxx"}); var listType = myList.GetType(); var interfaceType = listType.GetInterface("System.Collections.Generic.ICollection`1"); var propInfo = interfaceType.GetProperty("Count"); // TargetException thrown on 32-bit .Net 4.5.2+ installed int count = (int)propInfo.GetValue(myList, null); }

También probé la propiedad IsReadOnly , pero parece que funciona (sin excepción).

En cuanto al origen del error, hay dos niveles de indirección alrededor de las propiedades, uno es el remoto y el otro es un mapeo de estructuras de metadatos llamado MethodDef s con el método de tiempo de ejecución real, conocido internamente como MethodDesc . Este mapeo está especializado para las propiedades (así como también para los eventos), donde las MethodDesc adicionales para soportar las instancias get / set PropertyInfo de la propiedad se conocen como Associates . Al llamar a PropertyInfo.GetValue pasamos por uno de estos MethodDesc Associate MethodDesc a la implementación del método subyacente, y remota hace algunos cálculos de puntero para obtener el MethodDesc correcto en el otro lado del canal. El código CLR es muy intrincado aquí, y no tengo suficiente experiencia del diseño en memoria de la MethodTable de MethodTable que contiene estos registros de MethodDesc que utiliza la comunicación remota (o la asignación que utiliza para llegar a la tabla de métodos?), Pero yo '' d decir que es razonable suponer que la MethodDesc es tomar el MethodDesc incorrecto a través de algunas matemáticas de puntero malo. Es por eso que vemos un MethodDesc similar - no relacionado (en cuanto a su programa) - UInt32 get_Size de IVector<T> invocado en la llamada:

System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target) System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters) ConsoleApplication1.MyListProxy.Invoke(IMessage msg) Program.cs: line: 60 System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) System.Runtime.InteropServices.WindowsRuntime.IVector`1.get_Size() System.Runtime.InteropServices.WindowsRuntime.VectorToCollectionAdapter.Count[T]()

Estoy bastante seguro de que me falta alguna restricción o advertencia en alguna parte, pero esta es mi situación. Supongamos que tengo una clase para la que quiero un proxy, como el siguiente:

public class MyList : MarshalByRefObject, IList<string> { private List<string> innerList; public MyList(IEnumerable<string> stringList) { this.innerList = new List<string>(stringList); } // IList<string> implementation omitted for brevity. // For the sake of this exercise, assume each method // implementation merely passes through to the associated // method on the innerList member variable. }

Quiero crear un proxy para esa clase, de modo que pueda interceptar llamadas de método y realizar algún procesamiento en el objeto subyacente. Aquí está mi implementación:

public class MyListProxy : RealProxy { private MyList actualList; private MyListProxy(Type typeToProxy, IEnumerable<string> stringList) : base(typeToProxy) { this.actualList = new MyList(stringList); } public static object CreateProxy(IEnumerable<string> stringList) { MyListProxy listProxy = new MyListProxy(typeof(MyList), stringList); object foo = listProxy.GetTransparentProxy(); return foo; } public override IMessage Invoke(IMessage msg) { IMethodCallMessage callMsg = msg as IMethodCallMessage; MethodInfo proxiedMethod = callMsg.MethodBase as MethodInfo; return new ReturnMessage(proxiedMethod.Invoke(actualList, callMsg.Args), null, 0, callMsg.LogicalCallContext, callMsg); } }

Finalmente, tengo una clase que consume la clase proxiada, y establezco el valor del miembro MyList través de la reflexión.

public class ListConsumer { public MyList MyList { get; protected set; } public ListConsumer() { object listProxy = MyListProxy.CreateProxy(new List<string>() { "foo", "bar", "baz", "qux" }); PropertyInfo myListPropInfo = this.GetType().GetProperty("MyList"); myListPropInfo.SetValue(this, listProxy); } }

Ahora, si trato de usar la reflexión para acceder al objeto proxy, me encuentro con problemas. Aquí hay un ejemplo:

class Program { static void Main(string[] args) { ListConsumer listConsumer = new ListConsumer(); // These calls merely illustrate that the property can be // properly accessed and methods called through the created // proxy without issue. Console.WriteLine("List contains {0} items", listConsumer.MyList.Count); Console.WriteLine("List contents:"); foreach(string stringValue in listConsumer.MyList) { Console.WriteLine(stringValue); } Type listType = listConsumer.MyList.GetType(); foreach (Type interfaceType in listType.GetInterfaces()) { if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(ICollection<>)) { // Attempting to get the value of the Count property via // reflection throws an exception. Console.WriteLine("Checking interface {0}", interfaceType.Name); System.Reflection.PropertyInfo propInfo = interfaceType.GetProperty("Count"); int count = (int)propInfo.GetValue(listConsumer.MyList, null); } else { Console.WriteLine("Skipping interface {0}", interfaceType.Name); } } Console.ReadLine(); } }

Intentar llamar a GetValue en la propiedad Count través de la reflexión arroja la siguiente excepción:

Se produjo una excepción del tipo ''System.Reflection.TargetException'' en mscorlib.dll pero no se manejó en el código de usuario

Información adicional: el objeto no coincide con el tipo de objetivo.

Al intentar obtener el valor de la propiedad Count , al parecer, el marco está llamando a System.Runtime.InteropServices.WindowsRuntime.IVector para llamar al método get_Size . No entiendo cómo falla esta llamada en el objeto subyacente del proxy (la lista real) para que esto suceda. Si no estoy usando un proxy del objeto, obtener el valor de la propiedad funciona bien a través de la reflexión. ¿Qué estoy haciendo mal? ¿Puedo hacer lo que estoy tratando de lograr?

Editar: Se ha abierto un error con respecto a este problema en el sitio de Microsoft Connect.


Este es un error muy interesante de CLR, algunas de sus agallas se muestran en el contratiempo. Desde el rastro de la pila puede ver que está tratando de llamar a la propiedad Count del VectorToCollectionAdapter .

Esta clase es bastante especial, nunca se crea ninguna instancia. Es parte de la proyección de lenguaje que se agregó en .NET 4.5 que hace que los tipos de interfaz WinRT se parezcan a los tipos de .NET Framework. Es bastante similar a la clase SZArrayHelper, una clase de adaptador que ayuda a implementar la ilusión de que las matrices no genéricas implementan tipos de interfaz genéricos como IList<T> .

El mapeo de interfaz en el trabajo aquí es para la IVector<T> WinRT IVector<T> . Como se indica en el artículo de MSDN, ese tipo de interfaz se asigna a IList<T> . La clase interna VectorToListAdapter se encarga de los miembros IList<T> , VectorToCollectionAdapter aborda los miembros de ICollection<T> .

Su código obliga al CLR a encontrar la implementación de ICollection <>. Count y podría ser una clase .NET que lo implementara normalmente o podría ser un objeto WinRT que lo exponga como IVector <>. Size. Claramente, el proxy que creó le da un dolor de cabeza, sino que decidió incorrectamente para la versión de WinRT.

Cómo se supone que descubrir cuál es la opción correcta es bastante turbio. Después de todo, su proxy podría ser un proxy para un objeto WinRT real y luego la elección que hizo sería correcta. Esto bien podría ser un problema estructural. Que actúa de forma tan aleatoria, el código funciona en modo de 64 bits, no es exactamente inspirador. VectorToCollectionAdapter es muy peligroso, tenga en cuenta las llamadas JitHelpers.UnsafeCast, este error es potencialmente explotable.

Bueno, alerta a las autoridades, presenta un informe de error en connect.microsoft.com. Avíseme si no quiere tomarse el tiempo y yo me ocuparé de ello. Una solución alternativa es difícil de conseguir, usar la clase TypeInfo centrada en WinRT para hacer la reflexión no hizo ninguna diferencia. Eliminar el forzado de jitter para que funcione en modo de 64 bits es una curita, pero apenas una garantía.


actualmente estamos pirateando este problema con esta intervención frágil (disculpas por el código):

public class ProxyBase : RealProxy { // ... stuff ... public static T Cast<T>(object o) { return (T)o; } public static object Create(Type interfaceType, object coreInstance, IEnforce enforce, string parentNamingSequence) { var x = new ProxyBase(interfaceType, coreInstance, enforce, parentNamingSequence); MethodInfo castMethod = typeof(ProxyBase).GetMethod( "Cast").MakeGenericMethod(interfaceType); return castMethod.Invoke(null, new object[] { x.GetTransparentProxy() }); } public override IMessage Invoke(IMessage msg) { IMethodCallMessage methodCall = (IMethodCallMessage)msg; var method = (MethodInfo)methodCall.MethodBase; if(method.DeclaringType.IsGenericType && method.DeclaringType.GetGenericTypeDefinition().FullName.Contains( "System.Runtime.InteropServices.WindowsRuntime")) { Dictionary<string, string> methodMap = new Dictionary<string, string> { // add problematic methods here { "Append", "Add" }, { "GetAt", "get_Item" } }; if(methodMap.ContainsKey(method.Name) == false) { throw new Exception("Unable to resolve ''" + method.Name + "''."); } // thanks microsoft string correctMethod = methodMap[method.Name]; method = m_baseInterface.GetInterfaces().Select( i => i.GetMethod(correctMethod)).Where( mi => mi != null).FirstOrDefault(); if(method == null) { throw new Exception("Unable to resolve ''" + method.Name + "'' to ''" + correctMethod + "''."); } } try { if(m_coreInstance == null) { var errorMessage = Resource.CoreInstanceIsNull; WriteLogs(errorMessage, TraceEventType.Error); throw new NullReferenceException(errorMessage); } var args = methodCall.Args.Select(a => { object o; if(RemotingServices.IsTransparentProxy(a)) { o = (RemotingServices.GetRealProxy(a) as ProxyBase).m_coreInstance; } else { o = a; } if(method.Name == "get_Item") { // perform parameter conversions here if(a.GetType() == typeof(UInt32)) { return Convert.ToInt32(a); } return a; } return o; }).ToArray(); // this is where it barfed var result = method.Invoke(m_coreInstance, args); // special handling for GetType() if(method.Name == "GetType") { result = m_baseInterface; } else { // special handling for interface return types if(method.ReturnType.IsInterface) { result = ProxyBase.Create(method.ReturnType, result, m_enforce, m_namingSequence); } } return new ReturnMessage(result, args, args.Length, methodCall.LogicalCallContext, methodCall); } catch(Exception e) { WriteLogs("Exception: " + e, TraceEventType.Error); if(e is TargetInvocationException && e.InnerException != null) { return new ReturnMessage(e.InnerException, msg as IMethodCallMessage); } return new ReturnMessage(e, msg as IMethodCallMessage); } } // ... stuff ... }

m_coreInstance aquí es la instancia del objeto que el proxy está envolviendo.

m_baseInterface es la interfaz con la que se utilizará el objeto.

este código intercepta las llamadas realizadas en VectorToListAdapter y VectorToCollectionAdapter y las convierte de nuevo en el original a través de ese diccionario methodMap.

la parte del condicional:

method.DeclaringType.GetGenericTypeDefinition().FullName.Contains( "System.Runtime.InteropServices.WindowsRuntime")

se asegura de que solo intercepta las llamadas que provienen de cosas en el espacio de nombres System.Runtime.InteropServices.WindowsRuntime; lo ideal es que apuntemos a los tipos directamente, pero son inaccesibles; esto probablemente debería cambiarse a nombres de clases específicos de destino en el espacio de nombres.

los parámetros se convierten en los tipos apropiados y se invoca el método. las conversiones de parámetros parecen ser necesarias ya que los tipos de parámetros entrantes se basan en los tipos de parámetros de las llamadas a métodos de los objetos en el espacio de nombres System.Runtime.InteropServices.WindowsRuntime, y no en los parámetros del método llama a los tipos de objetos originales; es decir, los tipos originales antes de que los objetos en el espacio de nombres System.Runtime.InteropServices.WindowsRuntime secuestraran el mecanismo.

por ejemplo, el material de WindowsRuntime intercepta la llamada original a get_Item y la convierte en una llamada al método Indexer_Get: http://referencesource.microsoft.com/#mscorlib/system/runtime/interopservices/windowsruntime/vectortolistadapter.cs,de8c78a8f98213a0,references . este método llama al miembro GetAt con un tipo de parámetro diferente, que luego llama a GetAt en nuestro objeto (de nuevo con un tipo de parámetro diferente) - esta es la llamada que secuestramos en nuestro Invoke () y la convierte de nuevo en la llamada al método original con los tipos de parámetros originales.

Sería bueno poder reflexionar sobre VectorToListAdapter y VectorToCollectionAdapter para extraer todos sus métodos y las llamadas anidadas que hacen, pero desafortunadamente estas clases están marcadas como internas.

esto nos funciona aquí, pero estoy seguro de que está lleno de agujeros: es un caso de prueba y error, ejecutarlo para ver qué falla y luego agregar las entradas de diccionario / parámetros necesarios. estamos continuando la búsqueda de una mejor solución.

HTH