65、.NET 中DllImport的用途

在 .NET 中,DllImport 是 Platform Invocation Services (P/Invoke) 的核心机制,用于调用非托管(native)DLL 中的函数。以下是其核心用途、应用场景、关键细节及注意事项的全面总结:

1. 核心用途

  • 跨语言调用:允许 C#(托管代码)直接调用由 C/C++、Delphi 等语言编写的非托管 DLL 中的函数。
  • 功能复用:利用现有非托管代码(如硬件驱动、系统 API、数学库),避免重复开发。
  • 性能优化:对性能敏感的代码(如高频计算、实时处理)保留在非托管 DLL 中,仅通过 P/Invoke 调用。

2. 典型应用场景

场景 示例
硬件交互 调用驱动 DLL 控制设备(如键盘锁定、传感器读取、USB 通信)。
系统级操作 访问 Windows API(如 kernel32.dll、user32.dll)或第三方系统库。
旧代码集成 将遗留的非托管代码(如 C++ 库)集成到现代 .NET 应用中。
高性能计算 调用非托管数学库(如 Intel MKL)进行复杂数值计算。
跨平台兼容 在 .NET 中调用平台特定的非托管代码(如 Linux 的 libc.so)。

3. 关键使用细节

(1)基本语法

csharp 复制代码
[DllImport("DLL名称.dll", 
    EntryPoint = "函数名", 
    CallingConvention = CallingConvention.StdCall,
    CharSet = CharSet.Ansi)]
public static extern 返回类型 函数名(参数列表);
  • DllImport 属性:指定 DLL 名称和函数签名。
  • EntryPoint:可选,指定 DLL 中的函数名(若与托管方法名不同)。
  • CallingConvention:匹配非托管函数的调用约定(如 StdCall、Cdecl)。
  • CharSet:指定字符串编码(如 Ansi、Unicode)。

(2)调用约定(Calling Convention)

  • StdCall:Windows API 常用,调用者清理堆栈。
  • Cdecl:C 语言默认,被调用者清理堆栈(支持可变参数)。
  • ThisCall:C++ 成员函数调用约定。

(3)数据类型映射

托管类型 非托管类型 示例
int int、long(32位) [DllImport] public static extern int Add(int a, int b);
string char*(ANSI) 需用 MarshalAs(UnmanagedType.LPStr) 或 IntPtr。
bool BOOL(4字节) 通常映射为 int(非零为真)。
struct struct 需用 [StructLayout(LayoutKind.Sequential)] 定义。
IntPtr 通用指针 用于处理 void* 或动态内存。

4. 常见问题与解决方案

(1)DLL 加载失败

  • 原因:DLL 不在搜索路径中(如程序目录、系统 PATH)。

  • 解决方案:

  • 将 DLL 复制到输出目录。

  • 使用绝对路径(如 [DllImport(@"C:\path\to\dll.dll")])。

  • 动态加载(LoadLibrary + GetProcAddress)。

(2)调用约定不匹配

  • 现象:堆栈损坏、程序崩溃。

  • 解决方案:确保 CallingConvention 与 DLL 函数一致。

(3)内存管理

  • 问题:非托管代码分配的内存需手动释放。
  • 解决方案:

使用 Marshal.FreeHGlobal 或 Marshal.FreeCoTaskMem。

避免直接返回非托管内存指针,改用 IntPtr 并封装释放逻辑。

(4)字符串处理

  • 问题:托管与非托管字符串编码不一致。
  • 解决方案:

明确指定 CharSet(如 CharSet.Unicode 对应 wchar_t*)。

使用 Marshal.StringToHGlobalAnsi/StringToHGlobalUni 转换。

5. 高级技巧

(1)动态加载 DLL

csharp 复制代码
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr LoadLibrary(string dllToLoad);

[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

public static void LoadDllDynamically()
{
    IntPtr hDll = LoadLibrary("CompalLockInput.dll");
    if (hDll != IntPtr.Zero)
    {
        IntPtr funcAddr = GetProcAddress(hDll, "LockKeyboard");
        // 通过委托调用函数...
    }
}

(2)结构体与指针

csharp 复制代码
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
    public int X;
    public int Y;
}

[DllImport("Graphics.dll")]
public static extern void DrawPoint(ref Point point); // ref 传递结构体

(3)错误处理

使用 SetLastError = true 捕获非托管代码的错误码:

csharp 复制代码
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);

// 调用后检查错误码
if (!CloseHandle(handle))
{
    int errorCode = Marshal.GetLastWin32Error();
    Console.WriteLine($"错误码: {errorCode}");
}

6. 替代方案

  • C++/CLI:用混合模式程序集封装非托管代码,提供更安全的托管接口。
  • COM 互操作:若 DLL 是 COM 组件,可用 tlbimp 生成托管包装。
  • SWIG:自动生成 C# 绑定,适用于复杂 C/C++ 库。

7. 总结

  • 适用场景:快速集成非托管功能,或性能关键代码。
  • 风险点:内存泄漏、类型不匹配、调用约定错误。
  • 最佳实践:

明确指定 CallingConvention 和 CharSet。

封装非托管调用,隐藏复杂细节。

优先使用托管库或 C++/CLI 替代 P/Invoke(若可行)。

通过合理使用 DllImport,.NET 开发者可以高效利用非托管代码的强大功能,同时保持代码的可维护性。

相关推荐
许泽宇的技术分享2 小时前
Windows MCP.Net:解锁AI助手的Windows桌面自动化潜能
人工智能·windows·.net·mcp
搬砖的工人3 小时前
记录WinFrom 使用 Autoupdater.NET.Official 进行软件升级更新
java·前端·.net
唐青枫3 小时前
C#.NET SqlKata 使用详解:优雅构建动态 SQL 查询
c#·.net
追逐时光者12 小时前
一个基于 .NET 开源、功能强大的分布式微服务开发框架
后端·.net
笺上知微13 小时前
Serilog基于Seq开源框架实现日志分析
.net
百锦再13 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
CodeCraft Studio14 小时前
【能源与流程工业案例】KBC借助TeeChart 打造工业级数据可视化平台
java·信息可视化·.net·能源·teechart·工业可视化·工业图表
一个帅气昵称啊14 小时前
使用微软Agent Framework .NET构建智能代理应用
microsoft·flask·.net
一个天蝎座 白勺 程序猿16 小时前
深度解析:通过ADO.NET驱动Kdbndp高效连接与操作Kingbase数据库
数据库·.net·wpf·kingbase·金仓数据库
时光追逐者17 小时前
一个使用 WPF 开发的 Diagram 画板工具(包含流程图FlowChart,思维导图MindEditor)
c#·.net·wpf·流程图