2014-04-28 19 views
6

Ho una libreria .Net fornita da una terza parte. Ho fatto una riflessione su una delle loro classi e ho trovato un metodo membro. La firma era ...Come aggirare l'errore "Valore restituito ByRef non supportato nell'invocazione di riflessione" in C#?

Byte& FooBar() 

Così, ho voluto chiamare questo metodo attraverso la riflessione e ottenuto l'eccezione "ByRef valore non supportato nella riflessione invocazione tornare."

Ecco che cosa ho provato ...

 var strm = new TheirClass(); 
     var t = strm.GetType(); 
     var ms = t.GetMembers(
        BindingFlags.Static|BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 
     foreach (var m in ms) 
     { 
      Debug.WriteLine(String.Format("Name: {0}: {1}", m.Name, m.ToString())); 
      // ... 
      // Name: FooBar: Byte& FooBar() 
      // ... 
     } 
     var meth = t.GetMethod("FooBar"); 
     object returnValue = meth.Invoke(strm, new object[] { }); //throw exception 

Ho cercato la fornitura di parametri come nelle funzioni con parametri ref chiamare, ma che ha fatto alcuna differenza.

Mi piacerebbe aggirare questa eccezione in C#.

+2

Interessante, ma l'eccezione sembra abbastanza chiara: la funzione restituisce un riferimento a un byte e l'invocazione non supporta le funzioni che restituiscono riferimenti (perché, non lo so ... forse nel caso in cui qualcosa sullo stack sia restituita). Il metodo è pubblico, forse potrebbe essere chiamato tramite una DLL C++ gestita? –

+0

@ T.Kiley - d'accordo, l'eccezione è chiara, ad esempio "Non supportato". Ma la mia domanda è come aggirare. Sì, forse C++ DLL farebbe, ma mi piacerebbe aggirare in C# se possibile. Aggiornerò la domanda Grazie. – Les

+0

Sarebbe opportuno che i Descriptors valgano un mooch a. So che forniscono ulteriori capacità di metadati per riflessione, sono sicuro che ci siano anche alcune cose di invocazione. – brumScouse

risposta

5

Per i commenti: ecco come può essere eseguito da CIL, che può essere generato da C#.

Speravo di utilizzare un DynamicMethod, ma non riesco a farlo funzionare senza creare un tipo di delegato personalizzato in fase di runtime, quindi ho dovuto usare AssemblyBuilder invece.

using System; 
using System.Reflection; 
using System.Reflection.Emit; 

public delegate void CallBadFunction(Delegate d, Callback c); 
public delegate void Callback(ref int i); 

static class Program 
{ 
    static int i; 
    static object BadMethod() 
    { 
     return i; 
    } 

    static MethodInfo GetBadMethod() 
    { 
     return typeof(Program).GetMethod("BadMethod", BindingFlags.Static | BindingFlags.NonPublic); 
    } 

    static void Main() 
    { 
     var badMethod = GetBadMethod(); 

     var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("-"), AssemblyBuilderAccess.Run); 
     var module = assembly.DefineDynamicModule("-"); 

     var badDelegate = module.DefineType("BadDelegateType", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Sealed, typeof(MulticastDelegate)); 
     var badDelegateCtor = badDelegate.DefineConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, new Type[] { typeof(object), typeof(IntPtr) }); 
     badDelegateCtor.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed); 
     var badDelegateInvoke = badDelegate.DefineMethod("Invoke", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, typeof(int).MakeByRefType(), Type.EmptyTypes); 
     badDelegateInvoke.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed); 
     var badDelegateType = badDelegate.CreateType(); 

     var method = module.DefineGlobalMethod("-", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new[] { typeof(Delegate), typeof(Callback) }); 
     var il = method.GetILGenerator(); 
     il.Emit(OpCodes.Ldarg_1); 
     il.Emit(OpCodes.Ldarg_0); 
     il.Emit(OpCodes.Castclass, badDelegate); 
     il.Emit(OpCodes.Callvirt, badDelegateInvoke); 
     il.Emit(OpCodes.Callvirt, typeof(Callback).GetMethod("Invoke")); 
     il.Emit(OpCodes.Ret); 
     module.CreateGlobalFunctions(); 

     var callBadFunction = (CallBadFunction)Delegate.CreateDelegate(typeof(CallBadFunction), module.GetMethod("-")); 
     callBadFunction(badMethod.CreateDelegate(badDelegateType), (ref int i) => 
     { 
      i++; 
     }); 
    } 
} 

Dopo aver compilato questo programma, utilizzare ILDASM di smontarlo, e sostituire la definizione BadMethod s' da

.method private hidebysig static int32& 
     BadMethod() cil managed 
{ 
    ldsflda  int32 Program::i 
    ret 
} 

Questo lo trasforma in una funzione che restituisce int32&, che il seguente codice quindi gestire a chiamare . L'unica posizione C# consente i tipi int32& nei parametri di funzione (ref int), quindi per rendere utilizzabile il risultato, ho utilizzato una funzione di richiamata, che ottiene il valore restituito di BadMethod.

+1

+1 Ottima risposta - questo è un po 'di codice! – briantyler

+0

Mi hai messo in pratica quando hai suggerito un metodo DynamicMethod. Segnalo come risposta perché il tuo approccio può essere applicato più in generale. Ma dai un'occhiata a ciò che ho scoperto dopo il tuo commento precedente. – Les

0

Grazie "hvd" per avermi indicato nella giusta direzione. Dopo un po 'di riflessione, ecco cosa mi è venuto in mente. È più semplice, ma per favore dimmi se vedi un difetto.

 var theirObj = new TheirClass(); 
     var t = theirObj.GetType(); 
     var fooBar = t.GetMethod("FooBar"); // Byte& FooBar() 

     DynamicMethod dm = new DynamicMethod(
      "MyFooBar", 
      MethodAttributes.Public | MethodAttributes.Static, 
      CallingConventions.Standard, 
      typeof(IntPtr), 
      new Type[] { typeof(TheirClass) }, 
      typeof(TheirClass), 
      true); 

     var ilg = dm.GetILGenerator(); 
     ilg.Emit(OpCodes.Ldarg_0); 
     ilg.Emit(OpCodes.Call, foobar); 
     ilg.Emit(OpCodes.Ret); 

     var del = dm.CreateDelegate(typeof(Func<TheirClass,IntPtr>)); 
     var ret = (IntPtr)del.DynamicInvoke(theirObject); 

     byte[] buf = new byte[theirObj.FooBarSize()]; //no need for reflection/IL here 
     // not sure about the following, it works, but should it be inside an "unsafe" section? 
     Marshal.Copy(ret, buf, 0, buf.Length); 

ho messo un semplice wrapper per il metodo incriminato e IL non interessa che io tratto il Byte & come IntPtr. Non sono ancora sicuro di fare una copia senza un wrapper non sicuro. Ma questo è buono per ora.

+0

Oh, è intelligente! È rischioso però. Il runtime può (e lo fa) spostare gli oggetti in memoria a meno che non li si blocchi a un indirizzo fisso, e se ciò accade, i puntatori grezzi non vengono aggiornati alla nuova posizione. Dimostra che la mia risposta è più complicata del necessario, però. Stavo tentando inutilmente di evitare di creare un nuovo metodo dinamico per ogni funzione che si sta avviando, ma ovviamente posso semplicemente eseguire l'hardcode del 'MethodInfo' esattamente come stai facendo. – hvd