C# P/Invoke
介绍
在 C# 中,P/Invoke(Platform Invocation Services)是一种允许托管代码(如 C#)调用非托管代码(如 C 或 C++ 编写的库)的技术。通过 P/Invoke,你可以访问操作系统 API 或第三方库中的函数,从而扩展 C# 程序的功能。
P/Invoke 的核心思想是通过声明与非托管函数签名匹配的 C# 方法,并使用 DllImport
属性指定目标库的名称。这使得 C# 能够与非托管代码无缝交互。
P/Invoke 主要用于调用 Windows API,但也可以用于调用其他平台上的非托管库。
基本用法
1. 声明外部方法
要使用 P/Invoke,首先需要在 C# 中声明一个与非托管函数签名匹配的方法,并使用 DllImport
属性指定库的名称。
例如,假设我们想调用 Windows API 中的 MessageBox
函数:
using System;
using System.Runtime.InteropServices;
class Program
{
// 声明 MessageBox 函数
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
static void Main()
{
// 调用 MessageBox
MessageBox(IntPtr.Zero, "Hello, World!", "My Message", 0);
}
}
2. 参数和返回值
在声明外部方法时,需要确保参数类型和返回值类型与非托管函数一致。例如,MessageBox
函数的返回值是 int
,表示用户点击的按钮。
3. 处理字符串
在跨平台调用时,字符串的编码可能不同。可以通过 CharSet
属性指定字符串的编码方式。例如,CharSet.Unicode
表示使用 UTF-16 编码。
如果目标函数使用 ANSI 字符串,可以将 CharSet
设置为 CharSet.Ansi
。
实际案例:调用系统 API
假设我们需要在程序中获取当前系统的用户名。可以使用 Windows API 中的 GetUserName
函数来实现:
using System;
using System.Runtime.InteropServices;
using System.Text;
class Program
{
// 声明 GetUserName 函数
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool GetUserName(StringBuilder lpBuffer, ref int nSize);
static void Main()
{
StringBuilder buffer = new StringBuilder(256);
int size = buffer.Capacity;
if (GetUserName(buffer, ref size))
{
Console.WriteLine("当前用户名: " + buffer.ToString());
}
else
{
Console.WriteLine("获取用户名失败!");
}
}
}
输出
当前用户名: JohnDoe
在调用非托管函数时,务必检查返回值以确保函数执行成功。如果失败,可以使用 Marshal.GetLastWin32Error()
获取错误代码。
处理复杂数据类型
有时,非托管函数可能需要传递结构体或指针。C# 可以通过定义结构体并使用 Marshal
类来处理这些情况。
例如,调用 GetSystemTime
函数获取系统时间:
using System;
using System.Runtime.InteropServices;
class Program
{
// 定义 SYSTEMTIME 结构体
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEMTIME
{
public ushort Year;
public ushort Month;
public ushort DayOfWeek;
public ushort Day;
public ushort Hour;
public ushort Minute;
public ushort Second;
public ushort Milliseconds;
}
// 声明 GetSystemTime 函数
[DllImport("kernel32.dll")]
public static extern void GetSystemTime(ref SYSTEMTIME lpSystemTime);
static void Main()
{
SYSTEMTIME time = new SYSTEMTIME();
GetSystemTime(ref time);
Console.WriteLine($"当前系统时间: {time.Year}-{time.Month}-{time.Day} {time.Hour}:{time.Minute}:{time.Second}");
}
}
输出
当前系统时间: 2023-10-05 14:30:45
总结
P/Invoke 是 C# 中调用非托管代码的强大工具,适用于需要访问操作系统 API 或第三方库的场景。通过声明与非托管函数匹配的 C# 方法,并使用 DllImport
属性,你可以轻松实现跨平台互操作。
使用 P/Invoke 时,务必注意内存管理和数据类型匹配,以避免潜在的错误和安全问题。
附加资源
练习
- 尝试调用 Windows API 中的
Beep
函数,让计算机发出蜂鸣声。 - 使用 P/Invoke 调用
GetDiskFreeSpaceEx
函数,获取磁盘的可用空间信息。 - 研究如何通过 P/Invoke 调用 Linux 或 macOS 上的非托管库(提示:使用
libc
)。
通过实践这些练习,你将更深入地理解 P/Invoke 的工作原理和应用场景。