C#调用非受管DLL:API参数传递与列集实战

慢慢来
慢慢来
发布时间:2026-10-14 | 6人浏览过
原创

在.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#平台调用的深入知识请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表