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

Gre*_*osz 15 c# pinvoke structure function-pointers marshalling

对不起,下面是详细的介绍.我需要知道P/Invoke内部人员的洞察力比我更好.

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

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

void* getInterface(int id);
Run Code Online (Sandbox Code Playgroud)

您必须传递getInterface(int)以下枚举值之一:

enum INTERFACES
{
  FOO,
  BAR
};
Run Code Online (Sandbox Code Playgroud)

它返回一个指向包含函数指针的结构的指针,如:

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

以下是您在C中使用它的方式:

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

在C#中,我有一个使用P/Invoke Library映射getInterface(int)入口点的类.

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

然后我定义了:

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);
}
Run Code Online (Sandbox Code Playgroud)

我这样使用它:

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.
Run Code Online (Sandbox Code Playgroud)

我有以下问题:

  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);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用时立即映射整个结构Marshal.PtrToStructure(),是否有比我描述的更简洁的方式?我的意思是比为每个方法定义委托类型等更简洁?


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


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


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

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

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

Sam*_*ell 5

这就是我要开始的。

用法:

IFoo foo = UnsafeNativeMethods.GetFooInterface();
foo.Method1(0, 1.0f);
Run Code Online (Sandbox Code Playgroud)

执行:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Joh*_*ler 2

我不知道你的问题 1 的答案。我希望这Marshal.PtrToStructure()是根据其他 Marshal 原语实现的,因此仅使用单个Marshal.GetDelegateForFunctionPointer. 但这只是一个猜测——值得你为此付出的代价。

至于你的问题2。不,没有简洁的方法可以做到这一点。有一种更详细的方法。您可以使用旧式 MIDL 编译器为您的 dll 构建类型库并加载该类型库。但是 MIDL 的可用封送选项比您在 C# 中描述的要有限得多。而且 MIDL 编译器很难使用,您可能最终不得不编写另一个非托管 DLL 来在托管代码和目标 DLL 之间进行互操作。