将作为UnmanagedFunctionPointer传递给C的函数执行的位置?

时间:2015-02-18 07:19:24

标签: c# .net c interop unmanaged

在C中,有一个函数接受一个指向函数的指针来执行比较:

[DllImport("mylibrary.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int set_compare(IntPtr id, MarshalAs(UnmanagedType.FunctionPtr)]CompareFunction cmp);

在C#中,委托被传递给C函数:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate int CompareFunction(ref IntPtr left, ref IntPtr right);

目前,我在泛型类的构造函数中接受Func<T,T,int> comparer并将其转换为委托。 “mylibrary.dll”拥有数据,托管C#库知道如何将指针转换为T,然后比较T s。

//.in ctor 
CompareFunction cmpFunc = (ref IntPtr left, ref IntPtr  right) => {
                    var l = GenericFromPointer<T>(left);
                    var r = GenericFromPointer<T>(right);
                    return comparer(l, r);
                };

我还可以选择在C语言中为90%以上的大多数重要数据类型编写一个CompareFunction,但我希望避免修改本机库。

问题是,当使用P / Invoke设置compare函数时,从C代码对该函数的每次后续调用是否会产生编组开销,或者从C调用该委托,就好像它最初是用C编写的那样?

我想,在编译时,委托是内存中的一系列机器指令,但不明白是否/为什么C代码需要让.NET进行实际比较,而不是仅仅执行这些指令?

我最感兴趣的是更好地了解互操作是如何工作的。但是,此委托用于对大数据集进行二进制搜索,如果每个后续调用都有一些开销作为单个P / Invoke,则在本机C中重写比较器可能是一个不错的选择。

1 个答案:

答案 0 :(得分:3)

  

我想,在编译时,委托是内存中的一系列机器指令,但不明白是否/为什么C代码需要让.NET进行实际比较,而不是仅仅执行这些指令?

我猜你对.NET的运作方式有点困惑。 C不会要求 .NET执行代码。

首先,将lambda转换为编译器生成的类实例(因为您正在关闭comparer变量),然后使用该类的方法的委托。而且它是一个实例方法,因为你的lambda是一个闭包。

委托类似于函数指针。所以,就像你说的,它指向可执行代码。此代码是从C源代码生成还是从.NET源生成,此时无关紧要

当它开始变得重要时,它处于互操作的情况下。 P / Invoke不会将您的委托原样作为C代码的函数指针传递。它会将一个函数指针传递给调用委托的 thunk 。 Visual Studio将此显示为 [Native to Managed Transition] 堆栈帧。这是出于不同的原因,例如编组或传递其他参数(例如支持lambda的类的实例)。

至于性能方面的考虑,这是MSDN所说的,很明显:

  

置信转换。无论使用何种互操作性技术,每次托管函数调用本机函数时都需要特殊的转换序列(称为thunks),反之亦然。由于thunking会导致托管代码与本机代码之间的互操作所需的总时间,这些转换的累积会对性能产生负面影响

因此,如果您的代码需要在托管代码和本机代码之间进行大量转换,则应尽可能通过在C端进行比较来获得更好的性能,以避免转换。