为什么C#不孤立存在,以及它是如何工作的
\n\n大约十年前我开始使用C#时,曾以为.NET是一个自给自足的宇宙。库、框架、数据库——一切尽在掌握。但后来遇到一个任务:需要使用一个旧的C++库来处理信号。这时我意识到——C#与其他语言的集成不是一种奢侈,而是任何严肃项目的必需品。
\n\n如今,C#是一种拥有丰富生态系统的跨平台语言,但它并不能做到一切。有时需要运行Python脚本进行机器学习,有时需要调用Rust的原生函数以获得最大性能,有时还需要嵌入JavaScript引擎来动态生成UI。好消息是:C#几乎能与所有语言友好协作。
\n\n在本文中,我将展示C#与其他语言交互的真实场景,包含可运行的代码、基准测试和潜在陷阱。我们开始吧!
\n\nP/Invoke:C和C++的经典方式
\n\n平台调用服务(P/Invoke)是.NET内置的机制,用于调用原生DLL中的函数。它自.NET第一个版本就存在,并且仍然是集成C/C++代码最快的方式。
\n\n基本示例:从C调用函数
\n\n假设我们有一个原生库 math_helper.dll,其中包含函数:
// math_helper.h\nextern "C" __declspec(dllexport) double calculate_power(double base, int exp);\n\n在C#中,我们这样引入它:
\n\nusing System.Runtime.InteropServices;\n\npublic class NativeMath\n{\n [DllImport("math_helper.dll", CallingConvention = CallingConvention.Cdecl)]\n public static extern double calculate_power(double base, int exp);\n}\n\n// 使用\ndouble result = NativeMath.calculate_power(2.0, 10);\nConsole.WriteLine($"2^10 = {result}"); // 1024\n\nC# P/Invoke的重要事项:
\n- \n
- 始终指定
CallingConvention——通常是Cdecl或StdCall。 \n - 对于字符串,使用
MarshalAs(UnmanagedType.LPStr)或LPWStr。 \n - 通过
ref或out按引用传递结构体。 \n
P/Invoke的开销极小——每次调用仅需几纳秒。如果您需要将C#与C++集成进行高负载计算,这是您的选择。
\n\nC++/CLI:连接两个世界的桥梁
\n\nC++/CLI是Visual C++的一个扩展,允许编写在托管(.NET)和非托管环境中都能运行的代码。它不是一种独立的语言,而是一种混合体,常用于为C++库创建包装器。
\n\nOpenCV的包装器示例
\n\n假设我们有一个处理图像的C++函数:
\n\n// NativeImageProcessor.h\n#pragma once\npublic ref class ImageProcessor\n{\npublic:\n static System::Drawing::Bitmap^ ApplyFilter(System::Drawing::Bitmap^ bmp);\n};\n\n在内部,我们调用原生OpenCV,对外则返回托管的 Bitmap。从C#中使用它就像使用普通的.NET库一样:
using ImageProcessorLib;\n\nvar img = new Bitmap("photo.jpg");\nvar processed = ImageProcessor.ApplyFilter(img);\nprocessed.Save("filtered.jpg");\n\nC++/CLI集成提供了对内存的完全控制,并避免了封送处理。缺点是代码变得平台相关且更难调试。
\n\nPython.NET:从C#调用Python
\n\nPython是数据科学和机器学习的王者。如果您的C#项目需要运行PyTorch的神经网络或通过spaCy处理文本,那么通过Python.NET从C#调用Python正是您所需要的。
\n\n安装和第一个示例
\n\n安装包:
\n\ndotnet add package Python.Runtime.NETStandard博客
书籍推荐
推荐阅读