将作为UnmanagedFunctionPointer传递给C的函数执行的地方?

时间:2022-09-26 16:54:13

In C, there is a function that accepts a pointer to a function to perform comparison:

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

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

In C#, a delegate is passed to the C function:

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

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

Currently, I accept Func<T,T,int> comparer in a constructor of a generic class and convert it to the delegate. "mylibrary.dll" owns data, managed C# library knows how to convert pointers to T and then compare Ts.

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

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

I also have an option to write a CompareFunction in C for most important data types that are used in 90%+ cases, but I hope to avoid modifications to the native library.

对于在90%以上的情况下使用的大多数重要数据类型,我还可以选择在C中编写CompareFunction,但我希望避免修改本机库。

The question is, when setting the compare function with P/Invoke, does every subsequent call to that function from C code incurs marshaling overheads, or the delegate is called from C as if it was initially written in C?

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

I imagine that, when compiled, the delegate is a sequence of machine instructions in memory, but do not understand if/why C code would need to ask .NET to make the actual comparison, instead of just executing these instructions in place?

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

I am mostly interested in better understanding how interop works. However, this delegate is used for binary search on big data sets, and if every subsequent call has some overheads as a single P/Invoke, rewriting comparers in native C could be a good option.

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

1 个解决方案

#1


3  

I imagine that, when compiled, the delegate is a sequence of machine instructions in memory, but do not understand if/why C code would need to ask .NET to make the actual comparison, instead of just executing these instructions in place?

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

I guess you're a bit confused about how .NET works. C doesn't ask .NET to execute code.

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

First, your lambda is turned into a compiler-generated class instance (because you're closing over the comparer variable), and then a delegate to a method of this class is used. And it's an instance method since your lambda is a closure.

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

A delegate is similar to a function pointer. So, like you say, it points to executable code. Whether this code is generated from a C source or a .NET source is irrelevant at this point.

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

It's in the interop case when this starts to matter. P/Invoke won't pass your delegate as-is as a function pointer to C code. It will pass a function pointer to a thunk which calls the delegate. Visual Studio will display this as a [Native to Managed Transition] stack frame. This is needed for different reasons such as marshaling or passing additional parameters (like the instance of the class backing your lambda for instance).

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

As to the performance considerations of this, here's what MSDN says, quite obviously:

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

Thunking. Regardless of the interoperability technique used, special transition sequences, which are known as thunks, are required each time a managed function calls an native function, and vice-versa. Because thunking contributes to the overall time that it takes to interoperate between managed code and native code, the accumulation of these transitions can negatively affect performance.

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

So, if your code requires a lot of transitions between managed and native code, you should get better performance by doing your comparisons on the C side if possible, so that you avoid the transitions.

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

#1


3  

I imagine that, when compiled, the delegate is a sequence of machine instructions in memory, but do not understand if/why C code would need to ask .NET to make the actual comparison, instead of just executing these instructions in place?

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

I guess you're a bit confused about how .NET works. C doesn't ask .NET to execute code.

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

First, your lambda is turned into a compiler-generated class instance (because you're closing over the comparer variable), and then a delegate to a method of this class is used. And it's an instance method since your lambda is a closure.

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

A delegate is similar to a function pointer. So, like you say, it points to executable code. Whether this code is generated from a C source or a .NET source is irrelevant at this point.

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

It's in the interop case when this starts to matter. P/Invoke won't pass your delegate as-is as a function pointer to C code. It will pass a function pointer to a thunk which calls the delegate. Visual Studio will display this as a [Native to Managed Transition] stack frame. This is needed for different reasons such as marshaling or passing additional parameters (like the instance of the class backing your lambda for instance).

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

As to the performance considerations of this, here's what MSDN says, quite obviously:

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

Thunking. Regardless of the interoperability technique used, special transition sequences, which are known as thunks, are required each time a managed function calls an native function, and vice-versa. Because thunking contributes to the overall time that it takes to interoperate between managed code and native code, the accumulation of these transitions can negatively affect performance.

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

So, if your code requires a lot of transitions between managed and native code, you should get better performance by doing your comparisons on the C side if possible, so that you avoid the transitions.

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