C#系统编程:当高级语言遇上底层硬件
\n\n当听到“系统编程”这个词时,脑海中通常会浮现出留着胡须的专家们,用C或汇编语言编写代码,终端上满是绿色文字。而C#——它只是“用于业务应用程序”、“用于Windows Forms”和“给不想操心指针的人”的语言。剧透:这是误解。
\n\n实际上,C#是系统程序员的一把瑞士军刀,既能快速、安全地编写代码,又能在必要时深入非托管内存的核心。在本文中,我们将探讨C#如何解决实际的系统任务:从进程管理到通过WinAPI直接访问内存。是的,代码示例必不可少——没有它们可不行。
为什么C#不仅仅是“业务玩具”?
\n\n用C#进行系统编程并非矛盾。.NET平台(尤其是.NET 5+)为开发者提供了独特的组合:接近原生代码的性能,以及高级语言的便利性。您可以编写驱动程序、管理进程、手动分配内存,同时不必为资源管理而抓狂。
\n\n使C#适用于系统编程的关键特性:
\n- \n
- Unsafe代码和指针——是的,在C#中可以直接操作地址,就像在C++中一样。 \n
- P/Invoke——调用原生库(WinAPI、libc、驱动程序)中的任何函数。 \n
- Span
和Memory ——安全地操作连续内存块,无需额外分配。 \n - Native AOT——编译为原生代码,无需JIT和CLR。 \n
- 与C/C++互操作——将C#嵌入现有的系统项目中。 \n
如果您认为C#只适用于Web和数据库,那就准备好大吃一惊吧。
\n\nUnsafe代码:玩火,但可控
\n\nC#中的关键字unsafe就像核导弹上的红色按钮。按下它,您就可以做任何事情:解引用指针、操作固定对象、分配栈内存。但责任也绝对重大。
假设我们需要快速复制一个字节数组,绕过边界检查。在安全代码中,我们会使用Array.Copy,但如果我们正在编写一个与驱动程序交互的系统工具,每一微秒都至关重要。
using System;\n\nunsafe class MemoryCopy\n{\n public static void FastCopy(byte[] source, byte[] dest, int length)\n {\n fixed (byte* pSrc = source, pDst = dest)\n {\n byte* ps = pSrc;\n byte* pd = pDst;\n for (int i = 0; i < length; i++)\n *pd++ = *ps++;\n }\n }\n}\n\n这里我们使用fixed来“固定”托管数组在内存中,防止垃圾回收器在复制过程中移动它们。结果是复制速度接近C语言的memcpy。当然,对于日常任务来说这有些过度,但对于C#系统编程来说,这是必备技能。
何时使用unsafe是合理的?
\n- \n
- 在原始字节级别处理网络数据包。 \n
- 通过DMA与硬件交互。 \n
- 实现自定义内存分配器。 \n
- 优化关键代码段(例如,处理摄像头视频)。 \n
P/Invoke和WinAPI:C#叩击内核之门
\n\n真正的系统编程离不开调用原生函数。C#为此提供了P/Invoke(平台调用服务)机制。想要获取进程列表、管理窗口或读取其他进程的内存?没问题。
\n\n来看一个经典示例——通过WinAPI函数CreateToolhelp32Snapshot获取正在运行的进程列表。
using System;\nu