Как обойти ошибку, когда возвращаемое значение ByRef не поддерживается при вызове отражения в C#?

У меня есть библиотека .Net, предоставленная третьей стороной. Я задумался об одном из их классов и нашел метод-член. Подпись была...

Byte& FooBar()

Итак, я хотел вызвать этот метод через отражение и получил исключение «возвращаемое значение ByRef не поддерживается при вызове отражения».

Вот что я пробовал...

        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

Я пробовал указывать параметры, как при вызове функций с параметрами ref, но это не имело значения.

Я хотел бы обойти это исключение в С#.


person Les    schedule 28.04.2014    source источник
comment
Интересно, но исключение кажется вроде бы понятным - функция возвращает ссылку на байт, а вызов не поддерживает функции, которые возвращают ссылки (почему, я не знаю... возможно, в случае, если что-то в стеке возвращается). Является ли метод общедоступным, возможно, его можно вызвать через управляемую библиотеку C++?   -  person T. Kiley    schedule 28.04.2014
comment
@T.Kiley - согласен, исключение очевидно, т. Е. Не поддерживается. Но мой вопрос в том, как обойти это. Да, возможно, C++DLL подойдет, но я хотел бы работать на C#, если это возможно. Я обновлю вопрос. Спасибо.   -  person Les    schedule 28.04.2014
comment
Стоит ли дескрипторы TypeDescriptors заморачиваться. Я знаю, что они предоставляют дополнительные возможности метаданных по сравнению с отражением, я уверен, что там тоже есть некоторые вещи для вызова.   -  person brumScouse    schedule 28.04.2014
comment
Это не может работать так, потому что возвращает тип ByRef, типы ByRef не являются объектами, а возвращаемый тип methd.Invokeobject, поэтому у него не будет возможности вернуть результат. Должна быть возможность использовать тип делегата, который имеет возвращаемый тип Byte&, а затем использовать MethodInfo.CreateDelegate для создания экземпляра этого типа делегата, но такой тип делегата также нельзя написать на C#. Я не вижу выхода из этого, кроме как не использовать C#. Если DynamicMethod, построенный на C#, приемлем (лично я бы предпочел его C++/CLI), я могу опубликовать ответ, используя его.   -  person    schedule 28.04.2014
comment
@hvd - использование DynamicMethod было бы приемлемым.   -  person Les    schedule 28.04.2014


Ответы (2)


Согласно комментариям: вот как это можно сделать из CIL, который можно сгенерировать из C#.

Я надеялся использовать DynamicMethod, но мне не удалось заставить это работать без создания пользовательского типа делегата во время выполнения, поэтому вместо этого мне нужно было использовать AssemblyBuilder.

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

После компиляции этой программы используйте ILDASM для ее дизассемблирования и замените определение BadMethod на

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

Это превращает его в функцию, возвращающую int32&, которую затем сможет вызвать следующий код. Единственное место, где C# допускает типы int32&, находится в параметрах функции (ref int), поэтому, чтобы сделать результат пригодным для использования, я использовал функцию обратного вызова, которой передается возвращаемое значение BadMethod.

person Community    schedule 28.04.2014
comment
Вы меня на что-то натолкнули, когда предложили DynamicMethod. Я отмечаю ваш ответ как ответ, потому что ваш подход может применяться в более общем плане. Но взгляните на то, что я придумал после вашего предыдущего комментария. - person Les; 29.04.2014

Спасибо "hvd" за то, что указали мне правильное направление. Немного подумав, вот что у меня получилось. Это проще, но, пожалуйста, сообщите мне, если вы видите недостаток.

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

Я помещаю простую обертку вокруг вызывающего нарушение метода, и IL не заботится о том, чтобы я рассматривал Byte& как IntPtr. Я все еще не уверен в том, чтобы сделать копию без небезопасной оболочки. Но это пока хорошо.

person Les    schedule 28.04.2014
comment
О, это умно! Хотя это рискованно. Среда выполнения может (и делает) перемещать объекты в памяти, если вы не заблокируете их по фиксированному адресу, и если это произойдет, необработанные указатели не будут обновлены до нового местоположения. Однако это показывает, что мой ответ более сложен, чем необходимо. Я напрасно пытался избежать создания нового динамического метода для каждой обертываемой функции, но, конечно, я могу просто жестко закодировать MethodInfo точно так же, как вы. - person ; 29.04.2014