互操作的性能注意事项 (C++)

本主题提供多项准则是为了减少托管/非托管互操作转换对运行时性能的影响。

Visual C++ 支持与其他 .NET 语言(如 Visual Basic 和 C# (P/Invoke))相同的互操作性机制,但也提供特定于 Visual C++ 的互操作支持(C++ 互操作)。 对于性能关键型应用程序,了解每个互操作技术的性能影响非常重要。

无论所使用的互操作技术如何,每次托管函数调用非托管函数时,都需要特殊的转换序列(称为 thunk),反之亦然。 Microsoft C++ 编译器会自动插入这些 thunk,但请务必记住,累积起来之后,这些转换在性能方面可能很昂贵。

减少转换

避免或降低互操作 thunk 成本的一种方法是重构涉及的接口,以最大程度地减少托管/非托管转换。 可以通过为消息最多的接口(这些接口涉及跨托管/非托管边界进行的频繁调用)实施针对性措施,来显著改善性能。 例如,在紧凑循环中调用非托管函数的托管函数是重构的良好候选函数。 如果将循环本身移动到非托管端,或者创建非托管调用的托管替代项(可能是在托管端将数据排队,然后在循环之后立即将其封送给非托管 API),则可以显著减少转换次数。

P/Invoke 与 C++ 互操作

对于 .NET 语言(如 Visual Basic 和 C#),用于与原生组件互操作的指定方法是 P/Invoke。 由于 P/Invoke 受 .NET Framework 支持,因此 Visual C++ 也支持它,但 Visual C++ 还提供其自己的互操作性支持,即 C++ 互操作。 C++ 互操作优先于 P/Invoke,因为 P/Invoke 不是类型安全的。 因此,错误主要在运行时报告,但 C++ 互操作相对于 P/Invoke 也有性能优势。

每当托管函数调用非托管函数时,这两种技术都需要执行多项操作:

  • 将函数调用参数从 CLR 封送到原生类型。

  • 执行托管到非托管的 thunk。

  • 调用非托管函数(使用参数的原生版本)。

  • 执行非托管到托管的 thunk。

  • 返回类型以及任何“out”或“in,out”参数均从原生类型封送到 CLR 类型。

托管/非托管的 thunk 是进行互操作所必不可少的,但所需的数据封送取决于所涉及的数据类型、函数签名以及数据使用方式。

C++ 互操作执行的数据封送是可以实现的最简单形式:参数只需以按位方式跨托管/非托管边界进行复制;根本不执行转换。 对于 P/Invoke,只有当所有参数都是简单的 blittable 类型时,才是这种情况。 否则,P/Invoke 会执行非常可靠的步骤,将每个托管参数转换为相应的原生类型,反之亦然(如果将参数标记为“out”或“in,out”)。

换句话说,C++ 互操作使用尽可能快的数据封送方法,而 P/Invoke 使用最可靠的方法。 这意味着 C++ 互操作(采用通常适用于 C++ 的方式)默认提供最佳性能,而程序员则负责解决此行为不安全或不适合的情况。

因此,C++ 互操作要求显式提供数据封送,但优点是,程序员可以根据数据的性质自行决定适当的操作和数据使用方式。 此外,尽管可以在一定程度上对 P/Invoke 数据封送处理的行为进行修改和自定义,但 C++ 互操作允许按调用对数据封送进行自定义。 使用 P/Invoke 不可能做到这一点。

有关 C++ 互操作的详细信息,请参阅使用 C++ 互操作(隐式 PInvoke)

另请参阅

混合(本机和托管)程序集