已指定 libusb-1.0.dll 路径仍提示 No Backend Available
结论
find_library 只是告诉 PyUSB 去哪里加载 DLL,DLL 能否被当前 Python 进程成功载入,还要看文件本身和运行环境。出现 NoBackendError 时,往往是 get_backend() 返回了 None。常见原因有:
- Python 与
libusb-1.0.dll的位数不一致; - DLL 文件不完整、来源有误,或缺少依赖的其他 DLL;
- DLL 没有导出 PyUSB 所需的 libusb-1.0 API;
- PyInstaller 打包后,程序仍在读取硬编码的
System32路径,没有使用打包进去的 DLL。
可以先打印 backend。如果结果是 None,说明问题出在 DLL 加载阶段,与 USB 设备是否连接无关。
确认 Python 和 DLL 的位数
64 位 Python 只能加载 64 位 DLL,32 位 Python 也只能加载 32 位 DLL。用下面的代码可以查看当前解释器的位数:
import struct
import sys
print(sys.executable)
print(struct.calcsize("P") * 8)
输出 64 时应使用 x64 版本的 libusb-1.0.dll;输出 32 时应使用 x86 版本。
在 64 位 Windows 中,目录名称很容易让人误解:
C:\Windows\System32通常存放 64 位系统组件;C:\Windows\SysWOW64通常存放 32 位系统组件。
把 32 位 DLL 复制到 SysWOW64,并不能让 64 位 Python 加载它。只要进程与 DLL 的位数不一致,即使传入绝对路径也会失败。
直接测试 DLL 能否加载
PyUSB 的 get_backend() 加载 DLL 或绑定所需函数失败时,可能只返回 None,不会直接显示原始的 Windows 错误。可以先绕过 PyUSB,用 ctypes 测试:
import ctypes
import struct
dll_path = r"C:\Windows\System32\libusb-1.0.dll"
print("Python bits:", struct.calcsize("P") * 8)
try:
libusb = ctypes.CDLL(dll_path)
print("DLL loaded:", libusb)
print("libusb_get_version:", libusb.libusb_get_version)
except (OSError, AttributeError) as exc:
print("DLL load failed:", repr(exc))
几种常见错误分别表示:
WinError 193:通常是 Python 与 DLL 的位数不一致,也可能说明该文件不是有效的 Windows DLL;WinError 126:目标文件存在,但系统找不到它依赖的某个 DLL;AttributeError:DLL 可以加载,但缺少libusb_get_version等必要的导出函数,可能是下载了错误的库或不兼容的构建版本。
为主 DLL 指定完整路径,无法同时解决依赖 DLL 的搜索问题。使用第三方编译版本时,也要检查它的运行时依赖。建议优先选择可信来源提供的正式 Windows 二进制文件,不能只凭文件名判断 DLL 是否正确。
在 PyUSB 中检查 backend
确认 ctypes 可以加载 DLL 后,再创建 backend:
from pathlib import Path
import usb.backend.libusb1
import usb.core
dll_path = Path(r"C:\path\to\libusb-1.0.dll")
backend = usb.backend.libusb1.get_backend(
find_library=lambda _: str(dll_path)
)
if backend is None:
raise RuntimeError(
f"无法加载 PyUSB backend,请检查 DLL 位数、依赖项和导出函数:{dll_path}"
)
devices = usb.core.find(find_all=True, backend=backend)
for device in devices:
print(
f"VID={device.idVendor:04x}, "
f"PID={device.idProduct:04x}"
)
不建议把自行下载的 DLL 长期放在 C:\Windows\System32 中。可以把位数正确的 DLL 放到应用目录,再通过绝对路径加载,例如:
controller-app/
├── isControllerConnected.py
└── libusb-1.0.dll
对应代码如下:
from pathlib import Path
import usb.backend.libusb1
dll_path = Path(__file__).resolve().parent / "libusb-1.0.dll"
backend = usb.backend.libusb1.get_backend(
find_library=lambda _: str(dll_path)
)
if backend is None:
raise RuntimeError(f"无法加载 {dll_path}")
libusb1 Python 包和 PyUSB 的 backend 是两回事。安装 libusb1 包并不代表 PyUSB 一定能加载某个 libusb-1.0.dll。能否加载仍取决于本机 DLL 是否有效,以及它是否与当前 Python 兼容。
正确处理 PyInstaller 打包
下面命令中,第二个参数表示打包后的目标目录,并非 DLL 的新文件名:
pyinstaller --onefile --add-binary "C:\path\to\libusb-1.0.dll;." isControllerConnected.py
原命令写成了:
--add-binary "C:\Windows\System32\libusb-1.0.dll;libusb-1.0"
这样会把文件放到名为 libusb-1.0 的子目录中。代码仍然硬编码访问 C:\Windows\System32\libusb-1.0.dll,因此程序不会使用打包进去的文件。
--onefile 程序运行时,会将资源解压到 PyInstaller 的临时目录。代码需要兼容普通运行和打包运行两种情况:
from pathlib import Path
import sys
import usb.backend.libusb1
import usb.core
if getattr(sys, "frozen", False):
app_dir = Path(sys._MEIPASS)
else:
app_dir = Path(__file__).resolve().parent
dll_path = app_dir / "libusb-1.0.dll"
backend = usb.backend.libusb1.get_backend(
find_library=lambda _: str(dll_path)
)
if backend is None:
raise RuntimeError(f"无法加载 libusb backend:{dll_path}")
devices = usb.core.find(find_all=True, backend=backend)
print(list(devices))
在 .spec 文件中可以这样配置:
a = Analysis(
["isControllerConnected.py"],
binaries=[
(r"C:\path\to\libusb-1.0.dll", "."),
],
# 其余配置保持不变
)
如果该 DLL 还依赖其他非系统 DLL,也要把这些依赖一起打包。
DLL 加载后检查设备驱动
成功创建 backend,只能说明 libusb 已经加载。Windows 能否打开具体设备,还取决于设备当前使用的驱动。
通过 libusb 直接访问设备时,通常需要 WinUSB、libusbK 或其他兼容 libusb 的驱动。如果设备仍在使用厂商驱动、HID 驱动或其他专用驱动,程序可能能够枚举设备,却无法正常打开设备或与其通信。
驱动问题一般不会引发 NoBackendError。可以按以下顺序排查:
ctypes.CDLL()能否加载 DLL;get_backend()是否返回非None;usb.core.find()能否枚举设备;- 找到设备后,能否打开并访问接口。
如果需求只是判断游戏控制器是否连接,而设备属于 HID 类,直接使用 HID API 或相应的 Python 库通常更合适。不要为了简单的连接检测随意替换设备驱动,否则可能影响设备在游戏或其他软件中的正常使用。
调整 Tkinter 检测代码
原代码中的 while True 会占用 Tkinter 主线程,导致窗口很容易失去响应。find_all=True 返回的迭代器也不适合只创建一次后反复遍历,因为第一次遍历后,它可能已经耗尽。另外,下面这行代码只进行了比较,并没有赋值:
isConnected == False
可以使用 Tkinter 的 after() 定时重新枚举设备:
from pathlib import Path
import sys
import tkinter as tk
import usb.backend.libusb1
import usb.core
TARGET_PRODUCT_ID = 3302
if getattr(sys, "frozen", False):
app_dir = Path(sys._MEIPASS)
else:
app_dir = Path(__file__).resolve().parent
dll_path = app_dir / "libusb-1.0.dll"
backend = usb.backend.libusb1.get_backend(
find_library=lambda _: str(dll_path)
)
if backend is None:
raise RuntimeError(f"无法加载 libusb backend:{dll_path}")
window = tk.Tk()
window.title("Controller Detection")
window.geometry("700x500")
label = tk.Label(
window,
text="Click the Button to start the controller check",
font=("Calibri", 15, "bold"),
)
label.pack(pady=20)
def check_for_controller():
devices = usb.core.find(find_all=True, backend=backend)
connected = any(
device.idProduct == TARGET_PRODUCT_ID
for device in devices
)
if connected:
print("Device connected")
label.config(text="Device Connected")
else:
print("Device is not connected")
label.config(text="Device Not Connected")
window.after(2000, check_for_controller)
button = tk.Button(
window,
text="Start",
command=check_for_controller,
)
button.pack(pady=20)
window.mainloop()
实际项目中,最好同时匹配 idVendor 和 idProduct,因为不同厂商可能使用相同的产品 ID:
device = usb.core.find(
idVendor=0x1234,
idProduct=0x0CE6,
backend=backend,
)
其中的 VID 和 PID 必须替换成目标设备的真实值。还要注意,十进制 3302 和十六进制写法采用的是不同文本表示。按上述顺序检查后,就能判断问题出在 DLL 加载、PyInstaller 路径,还是设备驱动。
备注:内容仅供参考。
