使用ctypes调用DLL的search_devices函数传指针触发访问违例问题
问题根因
这个问题是Python侧传参不符合接口要求和DLL本身存在严重实现缺陷共同导致的:
- 首先是Python传参错误:你初始化的
serial_list = ctypes.c_char_p()是一个值为NULL的字符指针,传入byref(serial_list)相当于给函数传了一个指向NULL地址的二级指针,完全不符合接口要求。对照厂商提供的C示例可以看到,调用方需要提前分配好两级内存:第一级是长度128的字符指针数组,第二级是数组里每个指针都要指向一块至少8字节的可写内存,用来存单个设备的序列号。传入空指针后,函数尝试向NULL地址写入数据,直接触发首次运行的访问违例错误。 - DLL本身的缺陷是导致设备锁死、返回值异常的核心原因,具体问题包括:
- 接口定义与实现不匹配:头文件声明第一个参数是
char**(字符串数组,用于存储多个独立的序列号字符串),但实际实现逻辑是把所有设备序列号拼成一个长字符串,直接strcpy到第一个参数指向的地址,根本没有按字符串数组的结构给每个指针赋值,就算是厂商自己提供的C示例代码,按这个实现运行也会触发内存越界。 - 资源清理逻辑完全失效:在libusb初始化失败、获取设备列表失败的异常分支中,没有正常关闭libusb上下文、释放已打开的设备句柄,甚至出现了“没拿到设备列表就提前调用释放设备列表接口”的错误逻辑。第一次运行触发访问违例时,代码执行到一半崩溃,已打开的USB设备句柄、libusb会话没有被正常释放,会在系统层面锁定设备,导致厂商GUI无法识别。
- 返回值和输出参数逻辑混乱:代码中内存分配失败、正常执行完成两个场景都返回0,和文档描述的“失败返回负数”的规则矛盾;异常分支下不会给
*dev_count(设备数输出参数)赋值,所以你第一次崩溃时看到num_devices停留在初始值99,后续运行因为libusb之前异常退出没重新初始化,设备数被置为0。 - 内存操作存在溢出风险:内部拼接序列号的缓冲区只分配了160字节,没有做剩余长度校验,连接超过17个设备就会触发堆内存越界。
- 接口定义与实现不匹配:头文件声明第一个参数是
临时适配方案
按照厂商C示例的内存规则,在Python侧提前分配符合要求的缓冲区,不要传入空指针,参考代码:
import ctypes usb_dll = ctypes.CDLL(r"path_to_dll") usb_dll.search_devices.argtypes = [ctypes.POINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_uint32)] usb_dll.search_devices.restype = ctypes.c_long MAX_DEVICE = 128 SERIAL_BUF_LEN = 8 # 分配一级指针数组 serial_ptr_arr = (ctypes.c_char_p * MAX_DEVICE)() # 给每个指针分配独立的可写缓冲区,持有引用避免被GC回收 buf_holder = [] for idx in range(MAX_DEVICE): buf = ctypes.create_string_buffer(SERIAL_BUF_LEN) buf_holder.append(buf) serial_ptr_arr[idx] = ctypes.cast(buf, ctypes.c_char_p) dev_count = ctypes.c_uint32(0) status = usb_dll.search_devices(serial_ptr_arr, ctypes.byref(dev_count)) # 读取识别到的序列号 result_serials = [] for i in range(dev_count.value): result_serials.append(serial_ptr_arr[i].decode("ascii"))
注意:该适配是基于厂商公开的接口约定编写,如果你拿到的DLL是贴出的
UsbSearchDevicesLV的实现版本(把所有序列号拼成单字符串写入),说明DLL版本和头文件、示例代码不匹配,必须找厂商索取版本匹配的DLL和正确的接口文档,否则内存越界问题无法彻底解决。
内容的提问来源于stack exchange,提问作者ttshaw1
相关产品推荐
相关产品推荐

