С# P/Invoke: структуры маршаллинга, содержащие указатели на функции

Извините за подробное введение, которое следует. Мне нужно понять, кто-то знает P/Invoke внутренности лучше, чем я.

Вот как я сортирую структуры, содержащие указатели на функции от C до С#. Я хотел бы знать, является ли это самым чистым и/или наиболее эффективным способом его выполнения.

Я взаимодействую с родной DLL, закодированной в C, которая предоставляет следующую точку входа:

void* getInterface(int id);

Вы должны передать getInterface(int) одно из следующих значений перечисления:

enum INTERFACES
{
  FOO,
  BAR
};

Возвращает указатель на структуру, содержащую указатели на функции, такие как:

typedef struct IFOO
{
  void (*method1)(void* self, int a, float b);
  void (*method2)(void* self, int a, float b, int c);
} IFoo;

И вот как вы используете его в C:

IFoo* interface = (IFoo*)getInterface(FOO);
interface->method1(obj, 0, 1.0f); // where obj is an instance of an object
                                  // implementing the IFoo interface.

В С# у меня есть класс Library, который отображает точку входа getInterface(int), используя P/Invoke.

class Library
{
  [DllImport("MyDLL"), EntryPoint="getInterface", CallingConvention=CallingConvention.Cdecl)]
  public static extern IntPtr GetInterface(int id);
};

Тогда я определил:

struct IFoo
{
  public M1 method1;
  public M2 method2;


  [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
  public delegate void M1(IntPtr self, int a, float b);

  [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
  public delegate void M2(IntPtr self, int a, float b, int c);
}

И я использую его так:

IntPtr address = Library.GetInterface((int)Interfaces.FOO);
IFoo i = (IFoo)Marshal.PtrToStructure(address, typeof(IFoo));

i.method1(obj, 0, 1.0f): // where obj is an instance of an object
                         // implementing the IFoo interface.

У меня есть следующие вопросы:

  • Является ли отображение всей структуры менее эффективным, чем отображение одного указателя внутри структуры с помощью Marshal.GetDelegateForFunctionPointer()?

    Поскольку мне в основном не нужны все методы, открытые интерфейсом, я могу делать (проверять и работать):

    unsafe
    {
      IntPtr address = Library.GetInterface(id);
      IntPtr m2address = new IntPtr(((void**)address.toPointer())[1]);
    
      M2 method2 = (M2)Marshal.GetDelegateForFunctionPointer(m2address, typeof(M2));
    
      method2(obj, 0, 1.0f, 1);
    }
    
  • При сопоставлении всей структуры сразу с помощью Marshal.PtrToStructure() существует ли менее верный способ, чем то, что я описал? Я имею в виду менее подробный, чем определение типов делегатов для всех методов и т.д.


EDIT: Для ясности и полноты в приведенных выше фрагментах кода obj является экземпляром, полученным с точкой входа void* createObject(int type).


EDIT2: Одно из преимуществ метода 1) заключается в том, что Marshal.GetDelegateForFunctionPointer() доступен только с .NET Framework 2.0. Однако Marshal.PrtToStructure() всегда был доступен. Тем не менее, я не уверен, что сегодня стоит обеспечить совместимость 1.0.


EDIT3: я попытался проверить сгенерированный код с помощью Reflector, но он не дает много информации, поскольку все интересные детали сделаны в вспомогательных функциях, таких как PtrToStructureHelper и не отображаются. Тогда, даже если бы я мог видеть, что сделано в рамках внутренних структур, тогда среда выполнения имеет возможность оптимизировать ситуацию, и я не знаю точно, что, почему и когда:)

Однако я сравнивал два подхода, описанные в моем вопросе. Подход Marshal.PtrToStructure() был медленнее примерно в 10% по сравнению с подходом Marshal.GetDelegateForFunctionPointer(); что с структурой, содержащей IntPtr для всех функций, которые не представляют интереса.

Я также сравнил Marshal.GetDelegateForFunctionPointer() с моим собственным прокатанным маршаллером: я выровняю struct, представляющий стек вызовов, запишу его в память, передаю его адрес на родную сторону, где я использую батут, закодированный в asm, так что функция вызова использует область памяти как ее стек параметров (это возможно, поскольку соглашение о вызове cdecl x86 передает все функциональные параметры в стеке). Сроки были эквивалентными.

Ответы

Ответ 1

Я не знаю, что ответ на ваш вопрос 1. Я ожидаю, что Marshal.PtrToStructure() будет реализован в терминах других примитивов маршала, поэтому было бы более эффективно использовать одиночный Marshal.GetDelegateForFunctionPointer. Но это всего лишь предположение - стоит того, что вы заплатили за него.

Что касается вашего вопроса 2. Нет, нет более верного способа сделать это. Существует БОЛЬШЕ подробный способ. Вы можете использовать компилятор старого стиля MIDL для создания библиотеки типов для вашей DLL и библиотеки загружаемого типа. Но доступные опции marshaling для MIDL довольно немного ограничены тем, что вы можете описать на С#. И с MIDL-компилером довольно сложно работать, вам, вероятно, придется написать еще одну неуправляемую DLL для взаимодействия между управляемым кодом и вашей целевой DLL.

Ответ 2

Здесь я начну с.

Использование:

IFoo foo = UnsafeNativeMethods.GetFooInterface();
foo.Method1(0, 1.0f);

Реализация:

internal interface IFoo
{
    void Method1(int a, float b);
    void Method2(int a, float b, int c);
}

internal static class UnsafeNativeMethods
{
    public static IFoo GetFooInterface()
    {
        IntPtr self = GetInterface(InterfaceType.Foo);
        NativeFoo nativeFoo = (NativeFoo)Marshal.PtrToStructure(self, typeof(NativeFoo));
        return new NativeFooWrapper(self, nativeFoo.Method1, nativeFoo.Method2);
    }

    [DllImport("mydll.dll", EntryPoint = "getInterface", CallingConvention = CallingConvention.Cdecl)]
    private static extern IntPtr GetInterface(InterfaceType id);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void Method1Delegate(IntPtr self, int a, float b);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void Method2Delegate(IntPtr self, int a, float b, int c);

    private enum InterfaceType
    {
        Foo,
        Bar
    }

    private struct NativeFoo
    {
        public Method1Delegate Method1;
        public Method2Delegate Method2;
    }

    private sealed class NativeFooWrapper : IFoo
    {
        private IntPtr _self;
        private Method1Delegate _method1;
        private Method2Delegate _method2;

        public NativeFooWrapper(IntPtr self, Method1Delegate method1, Method2Delegate method2)
        {
            this._self = self;
            this._method1 = method1;
            this._method2 = method2;
        }

        public void Method1(int a, float b)
        {
            _method1(_self, a, b);
        }

        public void Method2(int a, float b, int c)
        {
            _method2(_self, a, b, c);
        }
    }
}

Ответ 3

Для точки 1:

Marshal.GetDelegateForFunctionPointer() более прост, если ваша структура содержит много указателей на функции, и вы используете только несколько. Недостатком (основным) является то, что вы должны вручную вычислить смещение указателя функции (обратите внимание, что размер указателя отличается на платформе 32/64 бит). Структура более проста в использовании, но маршалы больше данных.

Для точки 2:

Я не думаю, что возможно менее взвешенный подход. Вы можете определять делегаты только для функции, которую хотите использовать, и использовать фиктивный делегат для указателей функций, которые вы не хотите использовать. Таким образом, маршалинг будет работать нормально, но вы закончите структуру, содержащую делегатов, не подлежащих вызову.