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

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

Вот то, как я упорядочиваю структуры, содержащие указатели функции от C до 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.

В C# у меня есть a 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.

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

  1. Отображает целую структуру, менее эффективную, чем отображение единственного указателя в использовании структуры 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);
    }
    
  2. При отображении целой структуры сразу с помощью Marshal.PtrToStructure(), существует ли менее подробный путь, чем, что я описал? Я имею в виду менее подробный, чем необходимость определить типы делегата для каждого методы и т.д.?


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


EDIT2: Одно преимущество метода 1) - это Marshal.GetDelegateForFunctionPointer() только доступный запуск с Платформы.NET 2.0. Однако Marshal.PrtToStructure() всегда было доступно. Тем не менее я не уверен, что стоит гарантировать 1,0 совместимости в наше время.


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

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

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

15
задан Gregory Pakosz 18 January 2010 в 18:14
поделиться

3 ответа

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

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

.
2
ответ дан 1 December 2019 в 05:12
поделиться

Вот с чего бы я начал.

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

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);
        }
    }
}
4
ответ дан 1 December 2019 в 05:12
поделиться

Для пункта 1:

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

Для пункта 2:

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

.
1
ответ дан 1 December 2019 в 05:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: