您尚未登录。

楼主 # 2022-01-18 10:24:40

XIVN1987
会员
注册时间: 2019-08-30
已发帖子: 250
积分: 311.5

Pyinstaller打包使用pyusb的python程序

用Python写了个使用pyusb库的模仿JLink Commander的应用:https://github.com/XIVN1987/DAPCmdr

为了方便同事使用,于是用“pyinstaller -F path/to/DAPCmdr.py”将其打包成单个exe可执行文件,,
可是打包后程序执行报错“No backend available”,,无法发现和连接DAPLink,,根据之前调试pyusb的经验,,这是pyusb库找不到libusb.dll导致的

可是源码形式下可以找到libusb.dll,,为啥打包后就不行了呢??经过搜索和阅读源码发现,,原来pyinstaller修改了pyusb搜索libusb.dll的方式。。

pyusb原本的libusb.dll搜索代码执行流程如下:

_load_library() # usb\backend\libusb1.py
    usb.libloader.load_locate_library('libusb-1.0')

load_locate_library(name) # usb\libloader.py
    ctypes.util.find_library(name + '.dll')

find_library(name) # ctypes\util.py
    for directory in os.environ['PATH'].split(os.pathsep):
        fname = os.path.join(directory, name)
        if os.path.isfile(fname):
            return fname

简单来说pyusb最终是通过ctypes.util.find_library搜索libusb.dll的,,而ctypes.util.find_library是在环境变量PATH包含的路径下搜索libusb.dll的,,
我为了不修改系统变量PATH依然能够执行DAPCmdr.py,所以在DAPCmdr.py的起始处添加了下面一行代码:

os.environ['PATH'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'libusb-1.0.24/MinGW64/dll') + os.pathsep + os.environ['PATH']

也就是把DAPCmdr.py所在目录下的 libusb-1.0.24/MinGW64/dll 目录路径加入环境变量 PATH,这样 ctypes.util.find_library 就能从 DAPCmdr.py 所在路径下搜索到 libusb.dll,而且不管 DAPCmdr 目录拷贝到哪里,DAPCmdr.py 都能正常启动执行。。

而 Pyinstaller 打包使用 pyusb 库的程序时,会用到 site-packages\\_pyinstaller_hooks_contrib\\hooks\\rthooks\\pyi_rth_usb.py 文件,其内容如下:

def get_load_func(type, candidates):
    def _load_library(find_library=None):
        exec_path = sys._MEIPASS

        l = None
        for candidate in candidates:
            # Do linker's path lookup work to force load bundled copy.
            if sys.platform == 'win32' or sys.platform == 'cygwin':
                libs = glob.glob("%s\\%s*.dll" % (exec_path, candidate))
            else:
                libs = glob.glob("%s/%s*.so*" % (exec_path, candidate))
            for libname in libs:
                try:
                    l = ctypes.WinDLL(libname)
                    if l is not None:
                        break
                except:
                    l = None
            if l is not None:
                break
        else:
            raise OSError('USB library could not be found')

        return l
    return _load_library


import usb.backend.libusb1 as libusb10
libusb10._load_library = get_load_func('libusb10', ('usb-1.0', 'libusb-1.0', 'usb'))

可以看到,它用自己编写的 _load_library 函数替代了usb.backend.libusb1 自身的 _load_library 函数,,而它自己编写的 _load_library 是在打包生成的 DAPCmdr.exe 文件所在的目录下查找 libusb.dll 的。。

所以我按照源码目录结构把 libusb-1.0.24 文件夹拷贝到 DAPCmdr.exe 所在目录,,DAPCmdr.exe 执行时是无法搜索到 libusb.dll 的。。只有将 libusb-1.0.24 文件夹下的 libusb-1.0.dll 文件拷贝到 DAPCmdr.exe 所在目录,,DAPCmdr.exe 执行时才能搜索到 libusb.dll

离线

楼主 #2 2022-01-19 10:55:58

XIVN1987
会员
注册时间: 2019-08-30
已发帖子: 250
积分: 311.5

Re: Pyinstaller打包使用pyusb的python程序

@小智

我主要是分析为什么打包后pyusb无法定位到libusb.dll,,知道了原因应对方法就很简单了,,修改spec文件或者使用--add-binary命令行参数都可以。。

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn