C#P / Invoke:包含函数指针的编组结构

时间:2009-12-28 11:32:02

标签: c# pinvoke marshalling structure function-pointers

对于下面的详细介绍,我们深表歉意。我需要知道P / Invoke内部人员的洞察力比我更好。

以下是我如何编组包含从C到C#的函数指针的结构。我想知道这是否是最干净和/或最有效的方式。

我正在使用C编码的本机DLL连接,该DLL提供以下入口点:

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#中,我有一个Library类,使用P / Invoke映射getInterface(int)入口点。

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()一次映射整个结构时,是否有比我描述的更简洁的方式?我的意思是比为每个方法定义委托类型等更简洁?


  3. 编辑:为了清晰和完整起见,在上面的代码段中,obj是使用void* createObject(int type)入口点获得的实例。


    EDIT2:方法1)的一个优点是Marshal.GetDelegateForFunctionPointer()仅从.NET Framework 2.0开始提供。但是,Marshal.PrtToStructure()始终可用。也就是说,我不确定现在是否值得确保1.0兼容性。


    EDIT3:我尝试使用Reflector检查生成的代码,但它没有提供太多信息,因为所有有趣的细节都是在PtrToStructureHelper之类的辅助函数中完成的,并且没有公开。然后,即使我可以看到在框架内部完成了什么,那么运行时也有机会优化事物,我不确切知道什么,为什么以及何时:)

    但是,我对我的问题中描述的两种方法进行了基准测试。与Marshal.PtrToStructure()方法相比,Marshal.GetDelegateForFunctionPointer()方法减慢了约10%;对于所有不感兴趣的函数,包含IntPtr s的结构。

    我还将Marshal.GetDelegateForFunctionPointer()与我自己的滚动编组器进行了比较:我对齐表示调用堆栈的struct,将其固定在内存中,将其地址传递给我使用编码的trampoline的本地端asm以便调用函数使用内存区域作为其参数堆栈(这是可能的,因为cdecl x86调用约定传递了堆栈上的所有函数参数)。时间相当。

3 个答案:

答案 0 :(得分:4)

这是我要开始的。

用法:

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

答案 1 :(得分:2)

我不知道你问题的答案1.我希望Marshal.PtrToStructure()是根据其他Marshal原语来实现的,所以使用单Marshal.GetDelegateForFunctionPointer会更有效率}。但这只是猜测 - 值得为此付出代价。

关于你的问题2.不,没有详细的方法来做到这一点。有一种更冗长的方式。您可以使用旧式MIDL编译器为您的dll和类型库的加载构建类型库。但是,MIDL的可用编组选项比C#中描述的更有限。 MIDL编译器非常难以使用,您可能最终必须编写另一个非托管DLL来执行托管代码和目标DLL之间的互操作。

答案 2 :(得分:1)

对于第1点:

如果你的结构包含很多函数指针而你只使用了几个,那么

Marshal.GetDelegateForFunctionPointer()会更简单。一个(主要)缺点是您必须手动计算函数指针的偏移量(请注意,指针大小在32/64位平台上不同)。结构更易于使用,但可以编组更多数据。

对于第2点:

我认为不可能采用不那么冗长的方法。您只能为要使用的函数定义委托,并对您不想使用的函数指针使用伪委托。这样,编组将执行正常,但您以包含不可调用代理的结构结束。