PAYATHON 2026

已指定 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。可以按以下顺序排查:

  1. ctypes.CDLL() 能否加载 DLL;
  2. get_backend() 是否返回非 None;
  3. usb.core.find() 能否枚举设备;
  4. 找到设备后,能否打开并访问接口。

如果需求只是判断游戏控制器是否连接,而设备属于 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 路径,还是设备驱动。

备注:内容仅供参考。