2009-12-28 2 views
14

Извините за подробное введение, следующее. Мне нужно понять, что кто-то знает P/Invoke внутренности лучше, чем я.C# 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; 

A ой вот как вы используете его в 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# У меня есть 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. Is КАРТОГРАФИРОВАНИЯ целая структура менее эффективна, чем отображение одного поля внутри структуры, используя 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(), есть меньше подробный путь, чем то, что я описал? Я имею в виду меньше подробностей, чем определение типов делегатов для всех методов и т. Д.?


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 s для всех функций, которые не представляют интереса.

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

ответ

2

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

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

+0

Около 1) Я не знаю, можно ли проверить, что действительно сделано в конце: как оптимизатор может понять, что используется только 1 делегат структуры. Во всяком случае, по крайней мере, он выглядит достаточно чистым. –

+0

@Gregory Я думаю, что это маловероятно. Но вы можете использовать отражатель .NET, чтобы декомпилировать код и узнать наверняка. –

3

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

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

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

Ну, вот как я это делаю.Просто эта «симпатичная упаковка» не имеет отношения к вопросу. Тогда я знаю, что не объяснял это подробно, но 'self' не является указателем на структуру интерфейса. Это действительно «этот» указатель. –

+0

У родной библиотеки есть точка входа 'void * obj = createObject (int type). Вы используете его для создания экземпляров типов, а затем, если созданный вами тип реализует интерфейс IFoo ('bool реализует (void * obj, int iface)' возвращает 'true' для' obj' и 'FOO'), вы можете называть' ((IFoo *) getInterface (FOO)) -> method1 (obj, 0, 1.0f); ' –

+0

Это простейшее изменение добавит параметр' IntPtr self' в 'GetFooInterface'. Для такой сложной оболочки я фактически создавал методы 'Is ' и' As 'для выполнения приведений. Если бы я пошел дальше, я бы динамически создавал классы реализации, поэтому операторы C# '' '' '' '' '' '' '' '' '' работали без проблем. –

1

Для точки 1:

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

Для точки 2:

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

+0

Ок, похоже, существует консенсус: при сортировке всей структуры вы платите за то, что не можете использовать. О размере указателя, 'new IntPtr (((void **) address.toPointer()) [1])' получает все правильно, независимо от размера. –

+0

И да, единственный менее подробный вариант, который я использую при сортировке всей структуры, - это определить только тех делегатов, которые мне нужны, и использовать необработанные «IntPtr» для остальных точек входа –

Смежные вопросы