2015-09-02 13 views
26

Ich bin mir ziemlich sicher, dass ich irgendwo einen Zwang oder eine Einschränkung verpasse, aber hier ist meine Situation. Angenommen, ich habe eine Klasse, die ich für einen Proxy haben will, wie folgt aus:Kann ich Reflektionen mit RealProxy-Instanzen verwenden?

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. 
} 

ich einen Proxy für diese Klasse erstellt werden soll, so dass ich kann Methodenaufrufe abfangen und einige Verarbeitung auf dem zugrunde liegende Objekt durchführen . Hier ist meine Umsetzung:

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

Schließlich habe ich eine Klasse, die die Proxy-Klasse verbraucht, und ich den Wert des MyList Mitglied über Reflexion.

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

Jetzt, wenn ich versuche, Reflektion zu verwenden, um auf das Proxy-Objekt zuzugreifen, stoße ich auf Probleme. Hier ein Beispiel:

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

Versuch GetValue auf der Count Eigenschaft über Reflexion zu nennen wirft die folgende Ausnahme:

eine Ausnahme vom Typ ‚System.Reflection.TargetException‘ aufgetreten in mscorlib.dll wurde aber nicht im Benutzercode behandelt

Weitere Informationen: Das Objekt stimmt nicht mit dem Zieltyp überein.

Bei dem Versuch, den Wert der Count Eigenschaft zu erhalten, anscheinend der Rahmen ruft in System.Runtime.InteropServices.WindowsRuntime.IVector Sie die get_Size Methode aufzurufen. Ich verstehe nicht, wie dieser Aufruf auf dem zugrunde liegenden Objekt des Proxys fehlschlägt (die tatsächliche Liste), um dies zu ermöglichen. Wenn ich keinen Proxy des Objekts verwende, funktioniert das Abrufen des Eigenschaftswerts über die Reflektion. Was mache ich falsch? Kann ich überhaupt tun, was ich erreichen will?

Bearbeiten: A bug has been opened in Bezug auf dieses Problem auf der Microsoft Connect-Website.

+1

Beachten Sie, dass diese Implementierung nicht im Leerlauf Spekulation ist. [MbUnit 'Assert.Count' Methode] (https://github.com/Gallio/mbunit-v3/blob/master/src/MbUnit/MbUnit/Framework/Assert.Count.cs) tut dies für einige Sammlungen. Wenn das Sammlungsobjekt ein Proxy ist, wird der Aufruf von Assert.Count ausgelöst. – JimEvans

+0

Ist es möglich, den 'MyListProxy.CreateProxy' generisch zu machen, so dass er den reellen Typ und nicht das Objekt zurückgibt? Für den Test: Wenn dieser Aufruf in main 'interfaceType.GetProperty (" Count ")' in '((MyList) interfaceType) geändert wird .GetProperty (" Count ")' funktioniert dann der Aufruf von 'Count'work? – pasty

+0

Ähnliche Probleme hier - die Verwendung von Invoke() scheint Marshalling zu verursachen, was bewirkt, dass die Ausführung in diesen VectorToCollectionAdapter fällt, der dann mit der Meldung "Objekt stimmt nicht mit dem Zieltyp überein" abstürzt. (weil ein IVektor keine ICollection ist). Ich halte dies für einen Fehler. – fusi

Antwort

11

Ich denke, dass dies ein Fehler in der. NET-Framework sein kann. Irgendwie wählt die RuntimePropertyInfo.GetValue-Methode die falsche Implementierung für die ICollection<>.Count-Eigenschaft aus, und es scheint mit WindowsRuntime-Projektionen zu tun zu haben. Vielleicht wurde der Remoting-Code erneut erstellt, als das WindowsRuntime-Interop in das Framework eingefügt wurde.

Ich habe das Framework auf das Ziel .Net 2.0 umgestellt, da ich dachte, wenn das ein Fehler wäre, sollte es nicht in diesem Rahmen sein. Beim Konvertieren entfernte Visual Studio die Prüfung "Prefer 32 bit" in meinem Konsolen-exe-Projekt (da dies in 2.0 nicht existiert). Es läuft ohne Ausnahme, wenn dies nicht vorhanden ist.

Zusammenfassend läuft es auf. Net 2.0 in 32 und 64 Bit. Es läuft auf .Net 4.x in 64 Bit. Die Ausnahme wird nur auf .Net 4.x 32 Bit geworfen. Das sieht sicher wie ein Fehler aus. Wenn Sie es 64-Bit ausführen können, wäre das eine Problemumgehung.

Beachten Sie, dass ich .Net 4.6 installiert habe, und dies ersetzt einen Großteil des .NET Framework v4.x. Es könnte sein, dass hier das Problem auftritt; Ich kann nicht testen, bis ich eine Maschine bekomme, die .Net 4.6 nicht hat.

Update: 2015.09.08

Es ist auch mit nur .Net 4.5.2 installiert (keine 4.6) auf einer Maschine passiert.

Update: 2015-09-07

Hier ist ein kleiner repro, Ihre gleichen Klassen:

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

Ich habe auch die IsReadOnly Eigenschaft versucht, aber es scheint zu funktionieren (kein Ausnahme).


hinsichtlich der Quelle des Fehlers gibt es zwei Schichten von Indirektion herum Eigenschaften, eines der remoting zu sein, und der andere MethodDef s mit der tatsächlichen Laufzeitverfahren, bekannt intern aufgerufen eine Abbildung von Metadatenstrukturen werden als a MethodDesc. This mapping is specialized for properties (as well as events), where additional MethodDescs to support the property's get/set PropertyInfo instances are known as Associates. Durch den Aufruf PropertyInfo.GetValue wir gehen durch eine diese Mitarbeiter MethodDesc Hinweise auf die zugrunde liegende Methode Implementierung und Remoting hat einige Zeigermathematik die richtigen MethodDesc auf der anderen Seite des Kanals zu erhalten. Der CLR-Code ist hier sehr gewunden, und ich habe nicht genug Erfahrung des In-Memory-Layouts der MethodTable habe, die diese MethodDesc Aufzeichnungen halten die Remote Nutzungen (oder die Abbildung verwendet es zum MethodTable zu bekommen?), Aber ich d sagen, es ist eine faire Vermutung, dass Remoting die falsche MethodDesc über einige schlechte Zeiger Mathe ergreift. Deshalb haben wir eine ähnliche, aber nicht verwandten (soweit Ihr Programm) MethodDesc sehen - UInt32 get_Size von IVector<T> wird auf dem Aufruf aufgerufen:

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]() 
11

Dies ist eine ziemlich interessante CLR Fehler, einige seiner Eingeweide sind in der Panne zeigt . Sie können anhand der Stack-Ablaufverfolgung feststellen, dass die Count-Eigenschaft VectorToCollectionAdapter aufgerufen werden soll.

Diese Klasse ziemlich speziell ist, wird keine Instanz davon je geschaffen wurden. Es ist Teil der Sprachprojektion, die in .NET 4.5 hinzugefügt wurde, die WinRT-Schnittstellentypen wie .NET Framework-Typen aussehen lassen. Es ähnelt der SZArrayHelper-Klasse, einer Adapterklasse, die die Illusion unterstützt, dass nicht-generische Arrays generische Schnittstellentypen wie IList<T> implementieren.

Das Interface-Mapping bei der Arbeit hier ist für den WinRT IVector<T> Schnittstelle. Wie in dem MSDN-Artikel erwähnt, wird dieser Schnittstellentyp IList<T> zugeordnet. Die interne VectorToListAdapter-Klasse kümmert sich um die IList<T>-Member, VectorToCollectionAdapter um die ICollection<T>-Member.

Ihr Code zwingt die CLR, die Implementierung von ICollection <> .Count zu finden, und das könnte entweder eine .NET-Klasse sein, die es normal implementiert, oder ein WinRT-Objekt, das es als IVector <> .Size verfügbar macht. Der Proxy, den Sie erstellt haben, gibt Ihnen Kopfschmerzen, er hat sich fälschlicherweise für die WinRT-Version entschieden.

Wie es ist angenommen um herauszufinden, welche die richtige Wahl ist, ist ziemlich düster. Schließlich könnte Ihr Proxy ein Proxy für ein tatsächliches WinRT-Objekt sein, und dann wäre die getroffene Auswahl korrekt. Dies könnte ein strukturelles Problem sein. Dass es so zufällig funktioniert, funktioniert der Code im 64-Bit-Modus, ist nicht gerade inspirierend. VectorToCollectionAdapter ist sehr gefährlich, beachten Sie die JitHelpers.UnsafeCast-Aufrufe, dieser Fehler ist möglicherweise ausnutzbar.

Nun, warnen Sie die Behörden, einen Fehlerbericht bei connect.microsoft.com. Lass es mich wissen, wenn du dir nicht die Zeit nehmen willst und ich werde mich darum kümmern. Eine Problemumgehung ist nur schwer zu erreichen. Die Verwendung der WinRT-zentrischen TypeInfo-Klasse für die Reflektion hat keinen Unterschied gemacht. Das Entfernen des Jitter-Erzwingens, so dass es im 64-Bit-Modus läuft, ist ein Pflaster, aber kaum eine Garantie.

4

wir derzeit hacken, um dieses Problem mit dieser spröden Intervention (Entschuldigung für Code):

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 hier ist die Objektinstanz, dass die Proxy-Verpackung ist.

m_baseInterface ist die Schnittstelle, unter der das Objekt verwendet werden soll.

Dieser Code fängt die Aufrufe in VectorToListAdapter und VectorToCollectionAdapter ab und konvertiert sie über dieses methodMap-Dictionary in das Original zurück.

der Teil der bedingten:

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

stellt sicher, dass es nur Anrufe ab, die im System.Runtime.InteropServices.WindowsRuntime Namespace von Sachen kommen - im Idealfall würden wir die Typen direkt zielen, aber sie sind nicht zugänglich - Dies sollte wahrscheinlich geändert werden, um bestimmte Klassennamen im Namespace anzugeben.

Die Parameter werden dann in die entsprechenden Typen umgewandelt und die Methode wird aufgerufen. Die Parameterkonvertierungen scheinen erforderlich zu sein, da die eingehenden Parametertypen auf den Parametertypen der Methodenaufrufe von der Objekte im System.Runtime.InteropServices.WindowsRuntime-Namespace und nicht auf den Parametern der Methodenaufrufe bis basieren ursprüngliche Objekttypen; d. h. die ursprünglichen Typen, bevor die Objekte im System.Runtime.InteropServices.WindowsRuntime-Namespace den Mechanismus entführt haben.

Zum Beispiel fängt der WindowsRuntime-Stuff den ursprünglichen Aufruf von get_Item ab und konvertiert ihn in einen Aufruf der Indexer_Get-Methode: http://referencesource.microsoft.com/#mscorlib/system/runtime/interopservices/windowsruntime/vectortolistadapter.cs,de8c78a8f98213a0,references. Diese Methode ruft dann das GetAt-Member mit einem anderen Parametertyp auf, der dann GetAt für unser Objekt aufruft (wiederum mit einem anderen Parametertyp) - dies ist der Aufruf, den wir in Invoke() übernehmen und ihn wieder in den ursprünglichen Methodenaufruf konvertieren die ursprünglichen Parametertypen.

wäre es schön, in der Lage zu sein, über VectorToListAdapter und VectorToCollectionAdapter zu reflektieren, um alle ihre Methoden und die verschachtelten Aufrufe, die sie machen, aber diese Klassen sind leider als intern markiert.

Dies funktioniert für uns hier, aber ich bin sicher, es ist voller Löcher - es ist ein Fall von Versuch und Irrtum, es zu sehen, was fehlschlägt und dann die erforderlichen Wörterbuch Einträge/Parameter Konvertierungen hinzufügen. Wir setzen die Suche nach einer besseren Lösung fort.

HTH