C#P / Invoke:包含函数指针的编组结构
对不起,下面是详细的介绍。 我需要知道P / Invoke内部人员的洞察力比我更好。
这是我如何编组包含从C到C#的函数指针的结构。 我想知道这是否是最干净和/或最有效的方式。
我正在使用C编码的本机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.
我有以下问题:
-
映射整个结构的效率低于使用
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); }
-
使用
Marshal.PtrToStructure()
一次映射整个结构时,是否有比我描述的更简洁的方式? 我的意思是比为每个方法定义委托类型等更简洁?
编辑:为了清晰和完整,在上面的代码片段中, 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
的结构。
我还将Marshal.GetDelegateForFunctionPointer()
与我自己的滚动编组器进行了比较:我对齐表示调用堆栈的struct
,将其固定在内存中,将其地址传递给本机端,我使用asm编码的trampoline,以便调用函数使用内存区域作为其参数堆栈(这是可能的,因为cdecl
x86调用约定传递堆栈上的所有函数参数)。 时间相当。
我不知道你问题的答案1.我希望Marshal.PtrToStructure()
是根据其他Marshal原语实现的,所以使用单个Marshal.GetDelegateForFunctionPointer
会更有效率。 但这只是一个猜测 – 值得你付出的代价。
至于你的问题2.不,这种做法并不简单。 有一种更冗长的方式。 您可以使用旧式MIDL编译器为您的dll和类型库的加载构建类型库。 但是,MIDL的可用编组选项比C#中描述的更加有限。 MIDL编译器非常难以使用,您可能最终必须编写另一个非托管DLL来执行托管代码和目标dll之间的互操作。
这就是我要开始的。
用法:
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点:
Marshal.GetDelegateForFunctionPointer()
更简单,如果你的结构包含很多函数指针,你只使用几个。 (主要)缺点是您必须手动计算函数指针的偏移量(请注意,指针大小在32/64位平台上不同)。 结构更易于使用,但可以编组更多数据。
对于第2点:
我认为不可能采用不那么冗长的方法。 您只能为要使用的函数定义委托,并为您不想使用的函数指针使用伪委托。 这样,编组将执行正常,但您以包含不可调用代理的结构结束。