C Sharp P Invoke
外观
C# P/Invoke[编辑 | 编辑源代码]
介绍[编辑 | 编辑源代码]
P/Invoke(Platform Invocation Services)是 C# 中用于调用非托管代码(如 C/C++ DLL 或系统 API)的一种机制。它允许 .NET 应用程序与本地库交互,扩展了 C# 的功能范围。P/Invoke 在需要访问操作系统底层功能或调用现有本地库时非常有用。
核心概念[编辑 | 编辑源代码]
- 非托管代码:不由 .NET 运行时管理的代码(如 Win32 API 或 C++ 库)。
- 托管代码:由 .NET 运行时管理的代码(如 C# 程序)。
- 互操作性:托管代码与非托管代码之间的通信。
基本语法[编辑 | 编辑源代码]
P/Invoke 主要通过 `DllImport` 特性声明外部函数。以下是一个简单的示例:
using System;
using System.Runtime.InteropServices;
class Program
{
// 声明外部函数(来自 user32.dll)
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
static void Main()
{
// 调用非托管函数
MessageBox(IntPtr.Zero, "Hello from P/Invoke!", "Message", 0);
}
}
输出[编辑 | 编辑源代码]
执行此代码将弹出一个 Windows 消息框,显示文本 "Hello from P/Invoke!"。
参数传递[编辑 | 编辑源代码]
P/Invoke 需要正确处理数据类型转换。以下是常见映射:
C/C++ 类型 | C# 类型 |
---|---|
int
| |
string 或 StringBuilder
| |
IntPtr
| |
bool
|
示例:调用 Win32 API[编辑 | 编辑源代码]
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool Beep(uint frequency, uint duration);
static void Main()
{
Beep(1000, 500); // 发出 1000Hz 的蜂鸣声,持续 500ms
}
结构体和回调[编辑 | 编辑源代码]
P/Invoke 支持传递结构体和回调函数。
结构体示例[编辑 | 编辑源代码]
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[DllImport("user32.dll")]
public static extern bool GetCursorPos(out POINT point);
static void Main()
{
POINT cursorPos;
GetCursorPos(out cursorPos);
Console.WriteLine($"Cursor position: X={cursorPos.X}, Y={cursorPos.Y}");
}
回调示例[编辑 | 编辑源代码]
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
static bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam)
{
Console.WriteLine($"Window handle: {hWnd}");
return true;
}
static void Main()
{
EnumWindows(EnumWindowsCallback, IntPtr.Zero);
}
错误处理[编辑 | 编辑源代码]
P/Invoke 错误通常通过以下方式处理:
- 检查返回值(如
false
表示失败)。 - 调用
Marshal.GetLastWin32Error()
获取错误代码。
实际案例[编辑 | 编辑源代码]
案例:获取系统信息[编辑 | 编辑源代码]
[DllImport("kernel32.dll")]
public static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO
{
public ushort wProcessorArchitecture;
public uint dwPageSize;
// 其他字段...
}
static void Main()
{
SYSTEM_INFO info;
GetSystemInfo(out info);
Console.WriteLine($"Page size: {info.dwPageSize} bytes");
}
性能考虑[编辑 | 编辑源代码]
- P/Invoke 调用比纯托管代码慢(因跨托管/非托管边界)。
- 频繁调用时应考虑批处理或缓存。
安全注意事项[编辑 | 编辑源代码]
- 验证所有输入参数,防止缓冲区溢出。
- 避免暴露敏感数据给非托管代码。
进阶主题[编辑 | 编辑源代码]
内存管理[编辑 | 编辑源代码]
使用 Marshal
类手动管理内存:
IntPtr ptr = Marshal.AllocHGlobal(1024); // 分配非托管内存
// 使用内存...
Marshal.FreeHGlobal(ptr); // 释放内存
调用约定[编辑 | 编辑源代码]
指定调用约定(如 CallingConvention.StdCall
):
[DllImport("lib.dll", CallingConvention = CallingConvention.Cdecl)]
常见问题[编辑 | 编辑源代码]
问题 | 解决方案 |
---|---|
确保 DLL 位于可执行文件目录或系统路径 | |
检查参数类型是否正确映射 | |
确保释放所有分配的非托管资源 |
总结[编辑 | 编辑源代码]
P/Invoke 是 C# 与本地代码交互的强大工具,适用于:
- 调用操作系统 API
- 复用现有本地库
- 访问硬件功能
通过正确使用数据类型、错误处理和内存管理,可以构建稳定高效的互操作解决方案。