【问题标题】:Where function passed as UnmanagedFunctionPointer to C is executed?作为 UnmanagedFunctionPointer 传递给 C 的函数在哪里执行?
【发布时间】:2015-04-19 03:05:18
【问题描述】:

在 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,然后比较Ts。

//.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 设置比较函数时,从 C 代码对该函数的每次后续调用都会产生编组开销,还是从 C 调用委托就好像它最初是用 C 编写的一样?

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

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

【问题讨论】:

    标签: c# .net c interop unmanaged


    【解决方案1】:

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

    我猜您对 .NET 的工作原理有些困惑。 C 不要求 .NET 执行代码。

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

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

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

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

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

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

    【讨论】:

    • 谢谢!正是我需要知道的 - thunking。
    • 如果我用 compare_intscompare_doubles 之类的 C 函数编写,然后在 C# 中进行解析并将指向这些 native 函数的指针传递给 set_compare,将会避免思考?或者我需要将标志/枚举传递给 C 并对 C 中的实际实现进行解析?
    • 如果您有指向这些本机函数的直接指针(例如从GetProcAddress),并且如果您将它们作为IntPtr 传递给set_compare,那么它将完全避免thunking,因为.NET根本不会涉及(如果您还想传递 lambda,则可能需要在 .NET 端为 set_compare 进行两个重载)。我假设如果您将compare_ints 作为委托人导入然后将该委托人传递给set_compare,您可能会遇到麻烦——但我不确定。这是一个非常有趣的问题,值得研究。
    猜你喜欢
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    • 2017-10-03
    • 2015-06-09
    • 2013-11-09
    • 1970-01-01
    • 2019-12-21
    相关资源
    最近更新 更多