C# 使用 DllImport 调用非托管代码指南

云舒
云舒
发布时间:2026-10-14| 4人浏览过
原创

在 C# 开发中,经常需要调用 Windows API 或其他由 C/C++ 编写的非托管库。这需要通过 System.Runtime.InteropServices 命名空间中的 DllImport 特性来实现。本文将详细解析数据类型映射、常用属性配置以及正确的签名声明方法。

核心步骤:声明与调用 P/Invoke 接口

要在 C# 中调用非托管函数,首先必须引入 System.Runtime.InteropServices 命名空间。接着,需定义一个静态外部方法,并使用 [DllImport] 特性修饰。该方法必须是 public static extern。

第1步:确定目标函数的类型映射

C#(.NET Framework / .NET Core)是非托管内存管理的托管环境,因此在调用之前需要明确非托管类型到托管类型的对应关系。错误的类型映射会导致运行时异常或数据损坏。

  • 返回值: 例如 WinAPI 的返回值 DWORD 通常映射为 C# 的 int。
  • 输入字符串 (In String): 只读字符指针(如 LPCTSTR),通常映射为 string。
  • 输出/缓冲字符串 (Out Buffer): 需要被修改的字符数组缓冲区(如 LPTSTR),通常映射为 StringBuilder。注意:StringBuilder 必须在实例化时分配足够的容量(Capacity)。
  • 其他数值类型: 根据具体位数选择 uint, ushort, long 等。

第2步:编写带 DllImport 特性的方法签名

以获取短路径名的 Windows API `GetShortPathName` 为例。该函数需要接收长路径并返回短路径。

关键点说明:

  1. DLL 来源: 如果不指定完整路径(如直接写 "kernel32.dll"),系统会按以下顺序查找 DLL:
    ① 当前运行程序(.exe/.dll)所在目录;
    ② C:\Windows\System32 目录;
    ③ 环境变量 PATH 中列出的目录。
  2. Entry Point 别名: 如果实际函数名与 C# 内部定义的名称不同,需在特性中使用 EntryPoint 指定原名。
  3. 编组特性 MarshalAs: 用于处理复杂的内存布局。对于 LPTStr 这种可变长度字符串,虽然 string 和 StringBuilder 有默认行为,但在使用指针或特殊结构时通常需要显式标注 [MarshalAs(UnmanagedType.LPTStr)]。
using System;
using System.Text;
using System.Runtime.InteropServices;

public class TestPInvoke
{
    // Kernel32.dll 位于系统目录,可省略绝对路径
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, EntryPoint = "GetShortPathName")]
    public static extern int GetShortPathName(
        string lpszLongPath,       // 输入长路径
        StringBuilder lpszShortPath, // 输出缓冲,需提前初始化大小
        int cchBuffer              // 缓冲区的总大小
    );

    public static void Main()
    {
        // 1. 准备长路径
        string longPath = @"C:\Users\User\AppData\Local\Microsoft\OneDrive";
        
        // 2. 创建 StringBuilder,容量必须大于等于长路径长度
        int capacity = longPath.Length + 10;
        StringBuilder sbShort = new StringBuilder(capacity);

        // 3. 调用 API
        int result = GetShortPathName(longPath, sbShort, sbShort.Capacity);

        if (result > 0)
        {
            Console.WriteLine($"Short Path: {sbShort.ToString()}");
        }
        else
        {
            Console.WriteLine("Failed to get short path.");
        }
    }
}

DllImport 可选属性详解

掌握以下可选属性可以让调用的调用更加精确和安全:

属性 作用说明 常见值
EntryPoint 指定 DLL 中实际的导出函数名。若 C# 方法名与 DLL 函数名一致,可忽略此属性。 字符串,如 "GetProcAddress"
CharSet 决定传递字符串时使用的编码方式(Ansi 还是 Unicode)。 Auto (推荐), Ansi, Unicode
ExactSpelling 默认为 false。如果设为 true,表示函数入口点地址不随 CharSet 变化而自动添加后缀(如 'A' 或 'W')。 布尔值 true / false
CallingConvention 指定函数调用的堆栈清理约定。大多数 Windows API 使用标准约定。 WinApi (默认自动匹配平台), Cdecl
PreserveSig 指示方法的签名是否保持原样。通常不需要修改,除非涉及特殊的 HRESULT 转换逻辑。 布尔值 true / false
SetLastError 标记该 API 是否在失败时设置 LastError。设为 true 后,可在代码中通过 Marshal.GetLastWin32Error() 获取错误码。 布尔值 false / true

常见问题排查

找不到 DLL 怎么办?

如果发生 DllNotFoundException,请检查以下几点:

  1. 确认指定的 DLL 确实存在于上述的三个搜索路径中。
  2. 如果是第三方自定义 DLL,建议提供包含 DLL 完整绝对路径的路径字符串给 DllImport。
  3. 架构问题:确保 C# 项目的目标平台(x86 或 x64)与被加载的非托管 DLL 架构一致。混合加载(如 x64 进程加载 x86 DLL)会引发 TypeLoadException。

内存访问违规(Access Violation)

这通常是由于非托管代码尝试写入未分配的内存或越界导致的。在使用 StringBuilder 接收返回值时,务必保证初始 Capacity 足够大;在处理结构体指针时,使用 GCHandle 或 StackAlloc 确保内存有效性。

以上就是使用 DllImport 调用非托管代码的详细内容,更多关于 C# 与底层系统交互的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。