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

发布于 2024-08-15 17:24:03 字数 3067 浏览 8 评论 0原文

抱歉,接下来的介绍很冗长。我需要比我更了解 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()映射结构内的单个指针效率低

    由于我基本上不需要接口公开的所有方法,所以我可以这样做(经过测试并且有效):

    不安全
    {
      IntPtr地址 = Library.GetInterface(id);
      IntPtr m2address = new IntPtr(((void**)address.toPointer())[1]);
    
      M2 方法2 = (M2)Marshal.GetDelegateForFunctionPointer(m2address, typeof(M2));
    
      方法2(obj, 0, 1.0f, 1);
    }
    
  2. 当使用 Marshal.PtrToStructure() 一次映射整个结构时,有吗比我描述的更简洁的方式?我的意思是比必须为每个方法等定义委托类型更简洁?


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


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


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

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

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

Sorry for the verbose introduction that follows. I need insight from someone knowing P/Invoke internals better than I do.

Here is how I'm marshalling structures containing function pointers from C to C#. I would like to know whether it's the cleanest and/or most efficient way of doing it.

I'm interfacing with a native DLL coded in C that provides the following entry point:

void* getInterface(int id);

You have to pass getInterface(int) one of the following enum values:

enum INTERFACES
{
  FOO,
  BAR
};

Which returns a pointer to a structure containing function pointers like:

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

And here is how you use it in C:

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

In C# I have a Library class that maps the getInterface(int) entry point using P/Invoke.

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

Then I defined:

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

And I'm using it this way:

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.

I have the following questions:

  1. Is mapping the whole structure less efficient than mapping a single pointer inside the structure using Marshal.GetDelegateForFunctionPointer()?

    Since I mostly don't need all the methods exposed by an interface, I can do (tested and works):

    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. When mapping the whole structure at once using Marshal.PtrToStructure(), is there a less verbose way than what I described? I mean less verbose than having to define the delegate types for every methods etc?


EDIT: For the sake of clarity and completeness, in the code snippets above, obj is an instance obtained with the void* createObject(int type) entry point.


EDIT2: One advantage of method 1) is that Marshal.GetDelegateForFunctionPointer() is only available starting from .NET Framework 2.0. However, Marshal.PrtToStructure() has always been available. That said, I'm not sure it's worth ensuring 1.0 compatibility nowadays.


EDIT3: I tried to inspect the generated code using Reflector but it doesn't give much information since all the interesting details are done in helper functions like PtrToStructureHelper and are not exposed. Then, even if I could see what's done in the framework internals, then the runtime has the opportunity to optimize things away and I don't know exactly what, why and when :)

However, I benchmarked the two approaches described in my question. The Marshal.PtrToStructure() approach was slower by a factor around 10% compared to the Marshal.GetDelegateForFunctionPointer() approach; that whith a structure containing IntPtrs for all the functions that are not of interest.

I also compared the Marshal.GetDelegateForFunctionPointer() with my own rolled marshaller: I align a struct representing the call stack, pin it in memory, pass its address to the native side where I use a trampoline coded in asm so that the call function uses the memory area as its parameter stack (this is possible since the cdecl x86 calling convention passes all the function parameters on the stack). Timings were equivalent.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

琉璃梦幻 2024-08-22 17:24:03

这就是我要开始的内容。

用法:

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

Here's what I would start with.

Usage:

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

Implementation:

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);
        }
    }
}
二智少女 2024-08-22 17:24:03

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

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

I don't know that answer to your question 1. I'd expect that Marshal.PtrToStructure() is implemented in terms of the other Marshal primitives, so it would be more efficient to just use the single Marshal.GetDelegateForFunctionPointer. But that's just a guess - worth what you paid for it.

As for your question 2. No, there is no less verbose way to do this. There is a MORE verbose way. You can use the old style MIDL compiler to build a type library for your dll and the load that type library. But the available marshaling options for MIDL are quite a bit more limited that what you can describe in C#. And the MIDL compler is pretty hard to work with, you would probably end up having to write another unmanaged DLL to do the interop between managed code and your target dll.

盛夏已如深秋| 2024-08-22 17:24:03

对于第 1 点:

如果您的结构体包含大量函数指针并且您只使用了一些函数指针,则 Marshal.GetDelegateForFunctionPointer() 会更简单。一个(主要)缺点是您必须手动计算函数指针的偏移量(请注意,32/64 位平台上的指针大小不同)。结构更易于使用,但可以整理更多数据。

对于第 2 点:

我认为不可能有更简洁的方法。您可以只为您想要使用的函数定义委托,并为您不想使用的函数指针使用虚拟委托。这样,编组将正常执行,但最终会得到一个包含不可调用委托的结构。

For point 1:

Marshal.GetDelegateForFunctionPointer() is more simple if your structure contains a lot of function pointers and you use only a few. A (major) drawback is that you have to compute the offset to the function pointer by hand (note that pointer size differs on 32/64 bits platform). A structure is more easy to use but marshals more data.

For point 2:

I don't think a less verbose approach is possible. You may only define delegates for the function you want to use, and use a dummy delegate for the function pointers you don't want to use. This way, the marshaling will perform ok, but you end with a structure containing non-callable delegates.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文