You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#调用含自定义结构体参数的非托管C++ DLL函数的实现疑问

解决C#调用C++ DLL中含自定义结构体指针函数的问题

我来帮你梳理下这个问题的解决步骤,你目前的结构体转换已经有了基础,但还有几个关键点需要调整,以及函数调用的正确方式:

一、修正C#结构体的字符串字段定义

你当前用UnmanagedType.ByValArray来处理C的WCHAR[]是不合适的,因为ByValArray是用来处理数组元素集合的,而C里的WCHAR[128]本质是固定长度的宽字符字符串缓冲区,应该改用UnmanagedType.ByValTStr来告诉CLR正确处理字符串的封送:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct PrinterItem {
    // 用ByValTStr对应C++的WCHAR[128]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    public string name;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    public string id;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    public string dev;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string desc;
    public int pid;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct PrinterList {
    public int n; // 实际返回的打印机数量
    // 固定大小数组,用ByValArray指定SizeConst=32
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
    public PrinterItem[] item;
}

二、正确定义DllImport函数

C++的GetDeviceList接收的是PRINTER_LIST*指针,对应C#里的引用传递(用ref关键字),同时要注意调用约定(这是很多人踩坑的点):

  • 如果C++函数是默认编译(无__stdcall修饰),调用约定用CallingConvention.Cdecl
  • 如果函数声明带__stdcall(比如WINAPI宏),则用CallingConvention.StdCall

修正后的DllImport定义:

public class Program {
    // 请根据实际DLL的调用约定调整CallingConvention
    [DllImport("dllName.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    public static extern int GetDeviceList(ref PrinterList pList);

三、完整的调用流程

调用前必须初始化PrinterList的item数组(C#不会自动初始化结构体里的数组成员),然后通过ref传入结构体,让DLL填充数据:

static void Main(string[] args) {
        // 初始化结构体
        PrinterList deviceList = new PrinterList();
        // 必须手动创建固定大小的数组,否则封送时会出错
        deviceList.item = new PrinterItem[32];

        // 调用DLL函数,传入ref允许DLL修改结构体内容
        int result = GetDeviceList(ref deviceList);

        // 根据返回值判断调用结果(通常0表示成功,具体参考DLL文档)
        if (result == 0) {
            Console.WriteLine($"找到 {deviceList.n} 台打印机:");
            // 注意用deviceList.n遍历,而不是32(因为实际返回数量可能小于最大值)
            for (int i = 0; i < deviceList.n; i++) {
                PrinterItem item = deviceList.item[i];
                Console.WriteLine($"--- 打印机 {i+1} ---");
                Console.WriteLine($"名称:{item.name.TrimEnd('\0')}"); // 去除字符串末尾的空字符
                Console.WriteLine($"ID:{item.id.TrimEnd('\0')}");
                Console.WriteLine($"设备信息:{item.dev.TrimEnd('\0')}");
                Console.WriteLine($"描述:{item.desc.TrimEnd('\0')}");
                Console.WriteLine($"PID:{item.pid}\n");
            }
        } else {
            Console.WriteLine($"调用失败,错误码:{result}");
        }
    }
}

几个关键注意事项

  • 字符串空字符处理:C++的字符串缓冲区末尾会有\0,所以用TrimEnd('\0')去除多余的空字符,避免输出乱码。
  • 调用约定匹配:如果调用约定不匹配,会导致栈溢出或程序崩溃,一定要确认DLL的编译约定。
  • 数组初始化:必须手动创建PrinterItem[32]赋值给item,否则CLR无法分配足够的内存给DLL填充数据。

内容的提问来源于stack exchange,提问作者blueOct

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 19:47:48