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

为何调用非托管代码后C#的IntPtr变量值意外变更?

LibUSB C#包装器中IntPtr变量意外变更问题解决

问题现象

编写LibUSB的C#/.NET包装器时,发现调用libusb_get_device_descriptor后,传入的device IntPtr变量值意外变更:当list值为0x00000173f33a11b0且i=1时,执行IntPtr device = list + i *8后device值为0x00000173f33a11b8(符合预期),但调用libusb_get_device_descriptor后,device值变为0x00000173f33a0000左右,最终导致后续libusb_open等操作抛出异常。

核心错误原因

1. 设备列表指针处理错误

libusb_get_device_list返回的是指向指针数组的指针(C中为libusb_device***),即list是存储多个libusb_device*的数组地址。当前代码直接通过list + i*8计算偏移,相当于把list当成了libusb_device结构体数组的指针,而非指针数组的指针,导致传入libusb_get_device_descriptor的是无效内存地址,触发内存访问异常,间接篡改了变量值。

2. libusb_get_device_list的DllImport声明错误

该函数的C原型返回值为ssize_t(设备数量,失败时为负数),但当前包装器声明返回IntPtr,导致设备数量计算错误,进一步加剧了指针越界问题。

3. libusb_get_device_descriptor关联的结构体布局错误

原代码中libusb_device_descriptor的idVendor、idProduct等字段被错误声明为byte类型,实际应为ushort,导致结构体内存布局与C端不匹配,调用函数时会破坏栈内存,间接引发指针值异常。

4. libusb_get_string_descriptor_ascii的DllImport声明错误

该函数需要预先分配缓冲区并传入,当前直接使用out string会导致CLR错误处理内存,引发额外的内存访问异常,可能间接影响其他变量的内存值。

修正后的代码

1. 修正LibUSBWrapper中的DllImport与结构体定义

using System.Runtime.InteropServices;

namespace USBbidirPOC
{
    internal class LibUSBWrapper
    {
        public enum libusb_option
        {
            LIBUSB_OPTION_LOG_LEVEL = 0,
            LIBUSB_OPTION_USE_USBDK = 1,
            LIBUSB_OPTION_NO_DEVICE_DISCOVERY = 2,
            LIBUSB_OPTION_LOG_CB = 3,
            LIBUSB_OPTION_MAX = 4
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct libusb_init_option
        {
            [FieldOffset(0)]
            public libusb_option option;
            [FieldOffset(8)]
            public int iVal;
            [FieldOffset(8)]
            public IntPtr log_cbval;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct libusb_device_descriptor
        {
            public byte bLength;
            public byte bDescriptorType;
            public short bcdUSB;
            public byte bDeviceClass;
            public byte bDeviceSubClass;
            public byte bDeviceProtocol;
            public byte bMaxPacketSize0;
            public ushort idVendor; // 修正为ushort,匹配C端类型
            public ushort idProduct; // 修正为ushort,匹配C端类型
            public ushort bcdDevice; // 修正为ushort,匹配C端类型
            public byte iManufacturer;
            public byte iProduct;
            public byte iSerialNumber;
            public byte bNumConfigurations;
        };

        [DllImport("libusb-1.0.dll")]
        public static extern int libusb_init_context(out IntPtr ctx, libusb_init_option[] options, int num_options);

        // 修正返回值类型为int,对应C的ssize_t
        [DllImport("libusb-1.0.dll")]
        public static extern int libusb_get_device_list(IntPtr ctx, out IntPtr list);

        [DllImport("libusb-1.0.dll")]
        public static extern int libusb_get_device_descriptor(IntPtr dev, out libusb_device_descriptor desc);

        [DllImport("libusb-1.0.dll")]
        public static extern int libusb_open(IntPtr dev, out IntPtr dev_handle);

        // 修正字符串描述符函数声明,改为传入缓冲区
        [DllImport("libusb-1.0.dll")]
        public static extern int libusb_get_string_descriptor_ascii(IntPtr dev_handle, byte index, IntPtr buffer, int buffer_size);

        // 添加释放设备列表函数
        [DllImport("libusb-1.0.dll")]
        public static extern void libusb_free_device_list(IntPtr list, int unref_devices);

        // 添加关闭设备句柄函数
        [DllImport("libusb-1.0.dll")]
        public static extern void libusb_close(IntPtr dev_handle);

        // 添加释放上下文函数
        [DllImport("libusb-1.0.dll")]
        public static extern void libusb_exit(IntPtr ctx);
    }
}

2. 修正主窗体中的设备遍历与资源管理逻辑

namespace USBbidirPOC
{
    public partial class MainForm : Form
    {
        IntPtr context = IntPtr.Zero;

        public MainForm()
        {
            InitializeComponent();
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            LibUSBWrapper.libusb_init_option[] options = Array.Empty<LibUSBWrapper.libusb_init_option>();
            int result = LibUSBWrapper.libusb_init_context(out context, options, options.Length);
            if (result == 0)
            {
                // 直接用返回值作为设备数量
                int numberOfDevices = LibUSBWrapper.libusb_get_device_list(context, out IntPtr list);
                if (numberOfDevices > 0)
                {
                    for (int i = 0; i < numberOfDevices; i++)
                    {
                        // 读取指针数组中的元素,获取真实设备指针
                        IntPtr device = Marshal.ReadIntPtr(list, i * IntPtr.Size);
                        if (LibUSBWrapper.libusb_get_device_descriptor(device, out LibUSBWrapper.libusb_device_descriptor descriptor) == 0)
                        {
                            PrintDevice(device, descriptor);
                        }
                    }
                    // 释放设备列表,避免内存泄漏
                    LibUSBWrapper.libusb_free_device_list(list, 1);
                }
            }
            else
            {
                MessageBox.Show("Unable to init context!");
            }
        }

        private static void PrintDevice(IntPtr device, LibUSBWrapper.libusb_device_descriptor descriptor)
        {
            if (LibUSBWrapper.libusb_open(device, out IntPtr deviceHandle) == 0)
            {
                // 手动分配缓冲区读取字符串描述符
                IntPtr buffer = Marshal.AllocHGlobal(256);
                try
                {
                    int result = LibUSBWrapper.libusb_get_string_descriptor_ascii(deviceHandle, descriptor.iManufacturer, buffer, 256);
                    if (result > 0)
                    {
                        string manufacturer = Marshal.PtrToStringAnsi(buffer, result);
                        Console.WriteLine(manufacturer);
                    }
                }
                finally
                {
                    Marshal.FreeHGlobal(buffer);
                    // 关闭设备句柄释放资源
                    LibUSBWrapper.libusb_close(deviceHandle);
                }
            }
        }

        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            // 释放LibUSB上下文
            if (context != IntPtr.Zero)
            {
                LibUSBWrapper.libusb_exit(context);
            }
        }
    }
}

关键说明

  • 处理LibUSB返回的指针数组时,必须使用Marshal.ReadIntPtr读取数组中的每个指针元素,而非直接计算地址偏移。
  • 严格按照LibUSB的C原型修正DllImport声明,包括返回值类型、参数类型。
  • 结构体字段类型必须与C端完全匹配,否则会导致内存布局错误,引发不可预测的问题。
  • 所有打开的设备句柄、分配的内存、LibUSB上下文都需要手动释放,避免资源泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 15:57:34