跳转到内容

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
stringStringBuilder
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
  • 复用现有本地库
  • 访问硬件功能

通过正确使用数据类型、错误处理和内存管理,可以构建稳定高效的互操作解决方案。