在.NET应用程序中,与非托管代码交互是一个常见的需求。P/Invoke(Platform Invocation Services)是实现这一需求的方式之一,它允许.NET代码调用Windows API或其他非托管代码。然而,P/Invoke调用时会有一定的性能开销。为了减少这种开销,另一种方法是使用C++/CLI创建一个托管的包装器。本文将探讨P/Invoke与C++/CLI在性能上的差异。
作者正在开发一个名为SharpGL的OpenGL包装器和场景图库。OpenGL是一个调用频繁的API,每秒可能被调用数千次。在开发过程中,作者想知道是直接使用P/Invoke调用OpenGL函数更快,还是通过C++/CLI类库暴露OpenGL函数更快。为了验证这一点,作者创建了一个名为“TraditionalAPI”的小型API,该API暴露了三个基本函数,并使用不同的方法调用这些函数。
传统API暴露了三个基本函数:
这些函数分别用于测试P/Invoke调用的开销、计算平方根以及计算两个三元组的点积。在测试函数3中,需要对托管数组进行内存固定,以便在非托管API中直接访问。
解决方案的第二部分是一个C++/CLI包装器,它包装了每个函数。通过这种方式,可以从另一个.NET应用程序中调用这些函数。以下是每个测试函数的包装器示例:
void IncrementCounter() {
// 调用非托管函数
::TA_IncrementCounter();
}
double CalculateSquareRoot(double value) {
// 调用非托管函数
return ::TA_CalculateSquareRoot(value);
}
double DotProduct(array^ threeTuple1, array^ threeTuple2) {
// 固定数组
pin_ptr p1(&threeTuple1[0]);
pin_ptr p2(&threeTuple2[0]);
// 调用非托管函数
return TA_DotProduct(p1, p2);
}
在这种情况下,需要对托管数组进行固定,以便在非托管API中直接访问它们。
解决方案的最后部分是一个C# WPF应用程序,用于运行测试。TraditionalAPI DLL可以单独运行每个测试函数,也可以多次运行每个测试函数。通过这种方式,可以比较以下内容:
以下是运行每个测试10000次和100000次的结果:
根据研究结果,C++/CLI接口的性能并没有比P/Invoke快一个数量级,这与作者的预期不符。即使在百万次函数调用的情况下,C++/CLI接口的性能也仅略高于使用P/Invoke。