JZX轻语:简
[Qt/PyQt] PyQt5全层次构建与调试
发表于2024年07月28日
在调试PyQt
程序中,对于一些更底层所诱发的问题,有时候PyCharm
无法捕捉到此类问题,通常需要在C/C++
层面的Qt
或sip
进行调试。 而对于RiverBank的PyQt5
,由于其并没有提供Debug版本的库,因此无法通过windbg
等调试器对更底层的Qt
或sip
进行调试。本文以此为出发点,以Qt 5.15.2
64位为例,介绍如何手动构建PyQt5
的各个层次,以及如何在VSCode
中使用WinDbg
进行调试。
这是一个耗时巨大的过程…
环境
Qt:5.15.2 (因为5.15.3及以上版本需要自行编译,这里为了方便,选择5.15.2)
操作系统:Windows 11
Visual Studio 2022
Python 3.11 (64位)
层次概述
PyQt5
以sip
为基础封装Qt
的C++
库。总的来说,PyQt5
的层次大概如下:
+------------------------------------------+
| Python Interfaces |
+-------------------^----------------------+
|
+-------------------^----------------------+
| PyQt5(pyd) | # Step 5
+-------------------^----------------------+
|
+-------------------^----------------------+
| sip | # Step 4
+---------^-------------------------^------+
| |
+---------^--------+ |
| Qt | # Step2, 3 |
+------------------+ |
|
+---------^--------+
| CPython | # Step 1
+------------------+
1. 安装带符号文件的Python
在官网上下载Python
的安装包,并勾选Download debugging symbols
和Download debug binaries
,如图1所示:
图1:Python安装截图
为了能定位到具体的源码,可以在相同的下载页面下载Python的源码,如图2所示。将下载下来的源码压缩包解压到某个目录下,以便后续调试。
图2:Python源码下载地址
当然,也可以选择自己构建Python
,构建方法可参见官方教程,这里不再赘述。
2. 下载Qt
随后我们需要安装Qt
,为方便构建,在Qt
官网上下载无需编译的在线安装程序(Qt目前仅提供5.15.2的安装包,5.15.3及以上版本需要自行构建),安装时在组件上务必选择Qt的源码(Sources),如图3所示。
图3:Qt安装截图
此时不断下一步即可,安装完成后,在Qt安装目录下,可以找到Qt的源码(<Qt安装路径>\5.15.2\Src
),以及Qt的构建工具qmake
(<Qt安装路径>\5.15.2\msvc_2019_64\bin
)。
3. 绑定所下载Qt的dll至PyQt5
3.1 首先下载PyQt 5.15.2的wheel包(注意是64位,一般命名为PyQt5-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-none-win_amd64.whl
),并解压至某个目录(为便于后续描述,这里假设为A
)。
3.2 进去上述目录A
,输入命令python -m venv buildenv
构建虚拟环境,并激活虚拟环境.\buildenv\Scripts\activate
。
3.3 使用pip install PyQt-builder
安装PyQt-builder
。
3.4 由于pyqt-bundle
对Qt 5.15.2的支持有点问题,需要修改buildvenv\Lib\site-packages\pyqtbuild\bundle
中的abstract_package.py
文件,按图4修改get_target_qt_dir
方法:
图4:修改abstract_package.py
文件
3.5 通过以下命令绑定Qt
的dll
至PyQt5
:
pyqt-bundle --verbose --qt-dir <你的Qt安装路径>\5.15.2\msvc2019_64 .\PyQt5-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-none-win_amd64.whl
3.6 同理,下载PyQtWebEngine 5.15.2的wheel包,并解压至相同目录A
。并通过以下命令绑定:
pyqt-bundle --verbose --qt-dir <你的Qt安装路径>\5.15.2\msvc2019_64 .\PyQtWebEngine-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-none-win_amd64.whl
3.7 安装上述绑定后的wheel包,需要注意的是,由于我的Python
版本是3.11
,直接安装会报错(如图5),因为PyQt
的版本5.15.2
有点旧了,元信息上没有加上后续的py版本,需要在两个whl
包名后面加上cp310
,cp311
就好啦:
图5:pip安装报错截图
# 重命名whl包,加上cp310,cp311
mv PyQt5-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-none-win_amd64.whl PyQt5-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39.cp310.cp311-none-win_amd64.whl
mv PyQtWebEngine-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-none-win_amd64.whl PyQtWebEngine-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39.cp310.cp311-none-win_amd64.whl
# pip安装
pip install .\PyQt5-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-none-win_amd64.whl
pip install .\PyQtWebEngine-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-none-win_amd64.whl
4. 构建sip
官方的PyQt5-sip
并不提供pdb符号文件,如果需要通过Attach调试C++
层级的源码时,仅用官方的sip
二进制文件是无法调试里面的内容的。
首先下载官方的源码分发包(Source Distribution),并解压到本地的一个目录上,用编辑器打开setup.py
,新增以下代码:
# 下面两行是新增的部分
ext_compile_args = []
ext_link_args = []
if sys.platform == 'win32':
module_src.append('bool.cpp')
# 以下两行是新增的部分
ext_compile_args.append('/Z7')
ext_link_args.append('/DEBUG')
# 以下一行是修改的部分
module = Extension('PyQt5.sip', module_src, extra_link_args=ext_link_args, extra_compile_args=ext_compile_args)
以PyQt5-sip 12.13.0为例,最终代码文件为:
# ... 省略官方注释
import glob
import sys
from setuptools import Extension, setup
# Build the extension module.
module_src = sorted(glob.glob('*.c'))
ext_compile_args = []
ext_link_args = []
if sys.platform == 'win32':
module_src.append('bool.cpp')
ext_compile_args.append('/Z7')
ext_link_args.append('/DEBUG')
module = Extension('PyQt5.sip', module_src, extra_link_args=ext_link_args, extra_compile_args=ext_compile_args)
# Do the setup.
setup(
name='PyQt5_sip',
version='12.13.0',
license='SIP',
python_requires='>=3.7',
ext_modules=[module]
)
打开终端(命令提示符 or Powershell
),构建sip
python setup.py build
在目录的build\lib.xxxxx\PyQt5
上,找到pyd
文件和pdb
文件,将其替换到Python
安装目录的Lib\site-packages\PyQt5
上(原来的sip
的pyd
文件可按需备份一下)。
5. 构建PyQt5
至此,我们已经可以调试Qt
的C++
层,CPython
层,以及sip
层,但我们还是会发现有些地方是没办法找到符号文件的(如图6),这是因为PyQt5
的pyd
文件并没有符号文件。
图6:Qt相关的pyd
没有符号文件
其实,第3步只是将我们的Qt
的dll
绑定到PyQt5
的pyd
文件上,并没有真正构建PyQt5
的pyd
文件,这些pyd
文件是RiverBank构建的。如果我们想看看sip
内部对Qt
具体做了什么封装,还需要手动构建PyQt5
的pyd
文件,并输出调试符号。
首先去PyPI下载PyQt5 5.15.2
的源码分发包(注意是Source Distribution),然后解压到本地某个目录上(同样,为了方便描述,下文将该目录描述为B
),用编辑器打开project.py
,找到方法PyQt.update
,在该方法的末尾,新增三行以使得构建时生成符号文件:
# ...其他内容
class PyQt(PyQtProject):
# ...其他内容
def update(self, tool):
# ...其他内容
# !!! 在build方法的末尾新增以下三行 !!!
for binding in self.bindings.values():
binding.extra_compile_args.extend(['/Z7'])
binding.extra_link_args.extend(['/DEBUG'])
在目录B
上,打开Developer PowerShell for VS 2022
,构建PyQt5
:
sip-build --verbose --tracing --disable QtNfc
在这里,我们使用--verbose
以输出更多信息,--tracing
以在运行程序时可输出更多的调试信息,--disable QtNfc
以禁用QtNfc
模块,因为QtNfc
模块会存在导致构建失败的问题。
我们之所以不用--debug
选项,而选择直接编辑project.py
文件,是因为--debug
选项需要Python
的debug
版本(如下图所示),这样会导致一连串的后续问题:python_d.exe
的运行需要一系列的debug
版本的dll
,而这些dll
往往都找不到。相关报错如图7所示。
图7:sip-build
的--debug
选项需要Python
的debug
版本
注:如果出现图8所示的解码错误,这往往是我们的代码页并非
utf-8
导致的。图8:构建
PyQt5
时的解码错误我目前的做法是直接修改
sip-build
的源码…根据输出找到报错的地方,使用编辑器打开,找到方法Project.read_command_pipe
,在with subprocess.Popen
语句内,修改如图9:图9:修复编码错误
在这里,如果使用
utf-8
无法解码子进程输出的字节串,就再尝试使用gb2312
解码一遍(如果还是不行就使用替换字符),具体用哪个编码取决于自己系统的配置。
构建完成后,在build
目录内每个模块都会生成对应pyd
和pdb
文件,将这些文件直接复制到Python
的Lib\site-packages\PyQt5
目录下,重启Python
即可。
6. 在VSCode中调试
此时大功告成,我们可以愉快地调试任一层次的源码了!
以VSCode为例,我们将上述各个层次的源码目录统统放在一个workspace上,新增Launch
配置,编写如下:
{
"version": "0.2.0",
"configurations": [
{
"name": "(Windows) Attach",
"type": "cppvsdbg",
"request": "attach",
"processId": "${command:pickProcess}",
// 下面这行是新增的部分, 用于指定pdb符号文件的路径, 注意修改为自己的路径
"symbolSearchPath": "C:\\Qt\\5.15.2\\msvc2019_64\\bin;E:\\PyQt5_sip-12.13.0\\build\\lib.win-amd64-cpython-311\\PyQt5",
"sourceFileMap": {
// Qt源码路径映射, 因为符号文件是下载下来的, 并非自己编译的, 所以需要构建映射
"c:\\Users\\qt\\work\\qt\\": "C:\\Qt\\5.15.2\\Src",
// Python源码路径映射, 理由同上
"D:\\a\\1\\s\\python\\": "C:\\Users\\Jeza\\Downloads\\Python-3.11.7\\Python",
"D:\\a\\1\\s\\Objects\\": "C:\\Users\\Jeza\\Downloads\\Python-3.11.7\\Objects",
"D:\\a\\1\\s\\Include\\": "C:\\Users\\Jeza\\Downloads\\Python-3.11.7\\Include",
}
}
]
}
此时再Attach到一个Python
进程上,就可以愉快地调试了!如图10所示:
图10:最终效果