在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中重写比较器可能是一个不错的选择。
答案 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端进行比较来获得更好的性能,以避免转换。