C#调用非受管DLL:API参数传递与列集实战
在.NET开发中,调用Win32 API或自定义的非受管DLL是常见需求。核心难点在于托管代码与非托管代码之间的数据转换(列集)。本文详细解析如何使用[DllImport]声明、如何处理字符串输出参数和结构体引用,以及如何利用委托和GCHandle实现安全的回调机制。
基础声明与配置
在C#中调用非受管DLL,首先需要导入System.Runtime.InteropServices命名空间,并使用DllImport特性标记静态方法。该特性告知CLR(公共语言运行时)函数的入口位置和对应的动态链接库文件。
以下是一个封装User32.dll中API的基础类示例:
using System;
using System.Drawing;
using System.Text;
using System.Runtime.InteropServices;
namespace Win32API {
// 定义结构体:必须标记为Sequential以保证内存布局一致
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int left;
public int top;
public int right;
public int bottom;
}
public class Win32 {
// 声明SetWindowText
[DllImport("User32.Dll")]
public static extern void SetWindowText(int h, string s);
// 声明GetWindowRect,使用ref传递结构体
[DllImport("user32.dll")]
public static extern int GetWindowRect(int hwnd, ref RECT rc);
}
}
处理输出字符串与特殊字符编码
对于输出字符串参数,默认的string类型是不可变的(只读),无法接收API的修改结果。此时应使用System.Text.StringBuilder。
第1步:处理普通输出字符串
目的:获取窗口文本。
操作:将参数类型设为StringBuilder,并预先分配容量。
[DllImport("user32.dll")]
public static extern int GetWindowText(int hwnd,
StringBuilder buf, int nMaxCount);
// 调用示例
StringBuilder sb = new StringBuilder(256);
Win32.GetWindowText(hwnd, sb, sb.Capacity);
第2步:强制特定字符编码
目的:解决某些API(如GetClassName)默认Unicode转换导致的问题。
操作:使用MarshalAs(UnmanagedType.LPStr)指定使用ANSI字符。
[DllImport("user32.dll")]
public static extern int GetClassName(
int hwnd,
[MarshalAs(UnmanagedType.LPStr)] StringBuilder buf,
int nMaxCount);
// 调用示例
StringBuilder clsNameBuf = new StringBuilder(256);
int len = Win32.GetClassName(hwnd, clsNameBuf, clsNameBuf.Capacity);
string className = clsNameBuf.ToString();
结构体与引用类型的传递
当需要修改结构体内容时,必须确保数据按顺序排列且在内存中以引用形式传递。
第3步:定义并传递结构体
前提:结构体需标记[StructLayout(LayoutKind.Sequential)]。
参数:使用ref关键字。
完成状态:API执行后,结构体字段被填充。
// 调用RECT结构体的例子
RECT rc = new RECT();
int hwnd = /* ... */;
int result = Win32.GetWindowRect(hwnd, ref rc);
Console.WriteLine($"矩形: {rc.left}, {rc.top}, {rc.right}, {rc.bottom}");
// 注意:.NET框架自带System.Drawing.Rectangle也可作为替代
// [DllImport("user32.dll")]
// public static extern int GetWindowRect(int hwnd, ref Rectangle rc);
处理复杂回调与上下文传递
在枚举窗口等场景中,API常要求传入回调函数指针并附带上下文数据(通常是指针形式的lparam)。由于C#禁止直接使用裸指针,需结合委托与GCHandle来实现。
第4步:创建回调委托与实现
入口:声明委托类型,lparam改为IntPtr类型。
动作:编写静态方法实现具体的回调逻辑。
// 定义委托,注意lparam为IntPtr
private delegate bool EnumWindowsCB(int hwnd, IntPtr lparam);
// 实际实现的回调方法
private static bool MyEnumWindowsCB(int hwnd, IntPtr lparam) {
// 1. 将IntPtr转回GCHandle
GCHandle gch = (GCHandle)lparam;
// 2. 获取原始托管对象
WindowArray arr = (WindowArray)gch.Target;
// 3. 业务逻辑:存入数组
if (arr != null) {
arr.Add(hwnd);
}
return true;
}
第5步:打包GCHandle并调用
重要风险提醒:必须在调用完成后立即释放GCHandle,否则会导致内存泄漏。同时要注意不要在释放后再次访问Target。
public WindowArray() {
// 1. 锁定当前对象,获取Handle
GCHandle gch = GCHandle.Alloc(this);
try {
// 2. 实例化委托
EnumWindowsCB ewcb = new EnumWindowsCB(MyEnumWindowsCB);
// 3. 转换为IntPtr并调用API
EnumWindows(ewcb, (IntPtr)gch);
} finally {
// 4. 关键步骤:务必释放句柄
gch.Free();
}
}
第6步:完整工具类示例(可选参考)
可将上述逻辑封装为便捷类,简化顶层窗口遍历过程:
using System.Collections;
using System.Runtime.InteropServices;
namespace WinArray {
public class WindowArray : ArrayList {
private delegate bool EnumWindowsCB(int hwnd, IntPtr param);
[DllImport("user32")]
private static extern int EnumWindows(EnumWindowsCB cb, IntPtr param);
private static bool MyEnumWindowsCB(int hwnd, IntPtr param) {
GCHandle gch = (GCHandle)param;
WindowArray itw = (WindowArray)gch.Target;
if(itw != null) itw.Add(hwnd);
return true;
}
public WindowArray() {
GCHandle gch = GCHandle.Alloc(this);
try {
EnumWindowsCB ewcb = new EnumWindowsCB(MyEnumWindowsCB);
EnumWindows(ewcb, (IntPtr)gch);
} finally {
gch.Free();
}
}
}
}
总结
通过合理运用DllImport进行签名、StringBuilder与MarshalAs处理字符串、ref与StructLayout处理结构体,以及GCHandle辅助实现安全的回调通信,开发者可以高效地在C#环境中驱动底层系统API。以上就是从C#程序中调用非受管DLLs的详细方法与技巧,更多关于C#平台调用的深入知识请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













