☰
Qt版本选择避坑:Qt5/Qt6、LTS、编译套件与混装报错排查
2026/10/1 9:15:38 网站建设 项目流程

上个月帮同事看一个崩在启动阶段的桌面程序,控制台里就一行字:cannot mix incompatible Qt library (5.15.3) with this library (5.15.2)。他第一反应是"是不是我 Qt 版本选错了",然后打算把整台机器上的 Qt 全卸了重装。我拦住了他——这个报错跟"选哪个版本"关系不大,真正的问题是机器上同时躺着两套补丁号不同的运行库。但这件事本身很典型:Qt 版本选择这四个字背后,其实混着"选大版本线""选小版本号""选编译套件""选安装方式"四件完全不同的事,很多人把它们搅在一起讨论,于是就永远觉得自己选错了。这篇就把这四件事拆开,按我这几年做客户端、上位机和嵌入式界面的经验,给你一套能直接照着走的判断逻辑。

1. 版本选择其实是三道彼此独立的选择题

1.1 大版本线:Qt 5 和 Qt 6 不是"新旧"关系

先把最容易吵起来的一件事说清楚。Qt 6 不是 Qt 5 的简单升级包,两者在语言标准和构建体系上都换了一代。Qt 6 要求编译器支持 C++17,官方推荐的构建方式是 CMake,qmake 虽然还在,但已经不是主推路径了。这意味着你如果手上是一个基于 Qt 5.9 的老工程,.pro文件里堆了几百行QT +=和自定义模块,直接切到 Qt 6 的代价不是"改几个头文件",而可能是重构构建脚本。

API 层面也动了刀子。QRegExp、QTextCodec、QLinkedList、QSignalMapper、QDesktopWidget这些在 Qt 5 里随手就用的类,在 Qt 6 的默认模块里没有了,得额外引入 Qt5Compat 兼容模块才能继续用。QString::SkipEmptyParts变成了Qt::SkipEmptyParts,endl变成了Qt::endl,qrand()换成了QRandomGenerator。这些都是编译期就会报错的东西,改起来其实不难,但数量堆起来很烦。

反过来,Qt 6 带来的好处也实打实:高 DPI 缩放默认开启,不用再手工敲AA_EnableHighDpiScaling那一套属性;渲染管线统一走 RHI,Qt Quick 在不同后端上的行为一致性好了很多;容器类的实现做了大量合并,QVector现在就是QList的别名,以前纠结"该用哪个"的争论直接消失。

我的判断标准很粗暴:新项目、纯桌面、允许用 C++17、第三方依赖里没有卡死在 Qt 5 的库,就上 Qt 6;只要有一条不满足,就先老老实实留在 Qt 5.15。

1.2 小版本号与 LTS:不要只盯着"最新"

Qt 的版本号体系里,LTS(长期支持)是一个必须搞懂的概念。Qt 5 时代被长期维护过的节点主要是 5.6、5.9、5.12、5.15 这几条线;Qt 6 时代是 6.2、6.5、6.8 这几条。看 LTS 的原因不是"LTS 更稳定"这种含糊说法,而是补丁的可获得性。

这里有个很多人踩过的坑:5.15 系列开源渠道能拿到的最后一个小版本二进制包是 5.15.2,之后的 5.15.x 补丁在相当长一段时间里只对商业用户开放二进制,开源用户想用就得自己从源码编译。所以你在网上搜到的教程、离线包、Docker 镜像,绝大多数锚定在 5.15.2 这个点位上,这跟"5.15.2 是公认的最佳版本"是两回事,纯粹是分发渠道的现实结果。

正因为如此,看到一个工程里写死5.15.2,不要以为人家做过详细的版本评估,更可能只是当年下载到的就是这个包。理解了这一点,你才能判断"要不要跟着升"。

1.3 编译套件:决定了你能不能用某个版本

第三个维度最容易被忽略,但它经常是决定性因素。Qt 官方为 Windows 提供的是 MSVC 和 MinGW 两套预编译包,Linux 上有 GCC 编译的包,嵌入式则是各家 BSP 厂商自己裁的。你的选择不是自由的,是被下面这些东西锁死的:

  • 你的工程要不要链接 MSVC 编译的第三方静态库?要的话,Qt 必须用同版本的 MSVC,MinGW 的库跟 MSVC 的库在 ABI 上是不通的。
  • 你的目标板子,BSP 里随包提供的是哪个 Qt 版本?一个只带 Qt 5.12 sysroot 的板子,你硬塞 5.15 进去,纠结的不是功能而是交叉编译工具链、glibc 版本和一堆系统库的匹配问题。
  • 你的宿主系统版本够不够跑官方安装器?官方预编译包对宿主 Linux 的最低要求也在往上抬,6.7 之后那批包基本要求 Ubuntu 22.04 一档的系统,想在 20.04 上跑就得换思路。

还有一点:同一个 Qt 版本下,MinGW 的具体版本号也在变。5.9.9 随附的是 MinGW 5.3.0 的 32 位版本,5.15.2 给的是 MinGW 8.1.0,Qt 6 后期几代已经上到 MinGW 13 这一档。这些差异平时看不出来,直到你某个依赖库是用另一个 MinGW 编译的,链接期才开始互相不认。

2. 按项目约束倒推:一份能直接抄的决策表

2.1 先找出三类硬约束

我选 Qt 版本从来不是从"哪个版本好"出发,而是从"我被什么绑住了"出发。先花十分钟把下面三类约束列出来:

第三方库约束。你用的视觉库、通信库、图表库、打印控件,它们提供的预编译包是针对哪个 Qt 版本编译的?注意这里是"Qt 大版本 + 编译套件"两个维度。有些库只提供 Qt 5 的包,有些只提供 MSVC 版。

目标平台约束。要跑在 Windows 7 上吗?Qt 6 对 Windows 7 的支持早就断了,这一条足以把你按在 Qt 5.15 上。要跑在国产化平台上吗?那得看那个平台提供的 Qt 是什么版本,通常比你想的旧。

人力约束。团队里有没有人能搞定 CMake 构建体系?如果没有,迁到 Qt 6 的过程中会卡很久。另外就是维护周期——如果这个项目还要维护五年,选一条还在被持续维护的 LTS 线比分省事重要得多。

把这三条列完,候选版本通常就只剩一两个了,根本不需要纠结。

2.2 场景化对照表

下面这张表是我这几年实际做过或者深度参与过的几类项目,对照着看会比较有感觉:

项目类型我的选择主要理由
新做的纯桌面工具/上位机Qt 6.5 或 6.8 LTSC++17、CMake、高 DPI 默认生效,长期维护更省心
维护中的老工程,依赖库只给 Qt 5 包Qt 5.15.2换版本等于换整套依赖,收益不抵成本
嵌入式 HMI,跟随 BSP跟 BSP 一致,通常是 5.9 或 5.12交叉编译链路已经打通,不折腾工具链
需要跑在 Windows 7 上的老设备配套软件Qt 5.15.2 及更早Qt 6 不支持该平台
短周期一次性工具直接复用本机已有版本一年后没人维护,选型成本不划算
教学/练习/写博客示例5.15.2 或 6.5 都行,但一篇内统一避免读者跟着敲代码时遇到版本差异

注意最后一行的"统一"两个字。我见过太多教程示例里前一节用 Qt 5 写法、后一节贴 Qt 6 代码,读者照着敲必然报错。你自己做项目时也一样,同一套解决方案里的所有子工程,Qt 版本和编译套件必须统一。

2.3 什么时候才值得升级

判断要不要升级,我会问自己三个问题,只要有一个答不上来就先不升:

一是升上去能解决什么实际痛点?如果只是"新版看起来更好",那不值得。如果是老版本某个崩溃、某个平台适配缺陷已经在 Laravel...抱歉,已经是项目里的阻塞问题,那才值得。

二是升级路径有多长?Qt 5.15 到 Qt 6.5,中间的 API 变化、构建脚本重写、第三方库替换,加起来是周级别还是月级别的工作量?

三是升完之后的验证成本?界面程序最怕的是"功能都对但偶发崩溃",这种问题在版本迁移后极其常见,因为很多只是看起来像小改动的地方,底层行为变了。

Qt 提供了官方的移植指引文档,把 Qt 5 到 Qt 6 的 API 变化分门别类列了出来,动手前先通读一遍,比一边编译一边看报错效率高得多。

3. 三类"看着像版本问题"的报错,根因其实不在版本号

3.1 cannot mix incompatible Qt library:混装引起的补丁号冲突

回到开头那个报错。它的完整形状通常是这样的:

Cannot mix incompatible Qt library (5.15.3) with this library (5.15.2)

注意括号里的两个数字,是补丁号不同,连大版本都一样。这说明程序被编译时链接的是 5.15.2 的库,运行时却加载到了 5.15.3 的某个库,Qt 内部有个版本校验,发现对不上就直接终止进程。这个报错在 Qt 5.15 之后才变得常见,因为那时起版本校验被加严了。

它最常见的三个来源:

第一个是 Linux 上发行版仓库装的 Qt 和官方安装器装的 Qt 混用。你的程序编译时用官方安装器的 qmake,运行时却因为LD_LIBRARY_PATH或者系统库搜索路径先命中了/usr/lib/x86_64-linux-gnu下的 Qt 库。

第二个是某些第三方软件包自带了 Qt 运行库。一些 Python 生态里的包会捆绑 Qt 插件目录,一旦这些目录通过环境变量进入了插件搜索路径,程序启动时就会加载到不匹配的插件。

第三个是同一个安装目录下装了多个小版本,Kit 配置混乱,编译用一套、部署脚本拷贝了另一套。

排查手法其实很直接。Linux 下先确认运行时到底加载了哪些 Qt 库:

ldd ./your_app | grep -i qt QT_DEBUG_PLUGINS=1 ./your_app 2>&1 | head -50

QT_DEBUG_PLUGINS=1这个环境变量特别好用,它会把插件加载的完整搜索过程打出来,包括每个候选路径被跳过或被采纳的原因,一眼就能看出是哪个目录在捣鬼。Windows 下可以用依赖查看工具,或者干脆把程序拖到windeployqt生成的干净目录里跑一次,快速判断是不是环境问题。

修复方向无非三种:把干扰路径从环境变量里摘干净;用QT_PLUGIN_PATH和QT_QPA_PLATFORM_PLUGIN_PATH显式指定插件目录;或者最彻底的——把不匹配的那套 Qt 卸掉。

3.2 unknown module(s) in Qt: serialport:模块没装,别怀疑版本

再看另一个高频报错:

Project ERROR: Unknown module(s) in QT: serialport :-1: error: Unknown module(s) in QT: serialport

看到这个,很多人第一反应是"是不是这个版本不支持串口"。不是。Qt Serial Port 是附加模块,它需要你在安装 Qt 的时候勾选安装,或者事后用 Maintenance Tool 补装。默认的精简安装是不带的。

这个错在 Linux 发行版仓库装的 Qt 上尤其常见,因为仓库把模块拆得很碎,qt5-serialport-dev这类包要单独装。而在官方安装器装的 Qt 上,一般是"当年装的时候没勾"。

所以处理顺序是:先用 qmake 确认当前 Kit 用的到底是哪个 Qt 安装:

qmake -v qmake -query QT_INSTALL_LIBS QT_VERSION

然后去那个安装目录的lib(Windows 上是lib或bin)里找有没有对应的库文件,比如libQt5SerialPort.so或Qt5SerialPort.lib。找不到就是没装模块,找得到就是 Kit 指向了别的 Qt。

CMake 工程里对应的是:

find_package(Qt5 COMPONENTS Core Widgets SerialPort REQUIRED) target_link_libraries(your_app PRIVATE Qt5::SerialPort)

要特别注意同一个思路适用于所有附加模块。凡是看到Unknown module(s) in QT: xxx,先查模块装了没,再查 Kit 指向对不对,最后才怀疑版本兼容性。

3.3 Kit 是 Desktop Qt 5.9.9 MinGW,报错却指向别处

还有一种情况是这样的收尾行:

error while building/deploying project QtModbus (kit: Desktop Qt 5.9.9 MinGW)

这一行本身几乎不包含任何有用信息,它只是 IDE 在告诉你"构建失败了",真正的原因在前面已经被刷屏刷掉了。我的习惯是往上翻,找第一个红色错误,而不是看最后一行。

不过这个例子有个值得说的点:QtModbus 这个工程名暗示它用了 Modbus 通信,而 Qt 的 Modbus 支持在 Qt Serial Bus 模块里,同样属于附加模块。也就是说,如果安装时没勾 Serial Bus,构建到链接阶段就会因为找不到符号而失败,最后的收尾行就长这样。这跟上一小节的 Serial Port 是同一类问题。

另外还有两个"版本无关但常被误认为版本问题"的原因:

  • .pro.user文件残留。这个文件记录了上一次构建用的 Kit 和路径,换过 Qt 版本或者换过机器后没有清理,IDE 依然按旧路径去找 qmake 和库。
  • 没有做影子构建(shadow build),构建产物和源码混在一起,旧的目标文件被复用,链接期就会出现各种莫名其妙的不一致。

处理办法简单粗暴:关掉工程,删掉.pro.user和构建目录,重新打开,重新执行 qmake,重新构建。我大概有三成的"诡异编译错误"是靠这一个动作解决的。

4. 让多个 Qt 版本在同一台机器上和平共处

4.1 在线安装器、离线安装包与 Maintenance Tool 的取舍

现在装 Qt 有两条路:在线安装器,和离线安装包。

离线安装包的好处是"下载一次、随时重装、不需要登录账号",坏处是它不再持续更新,能拿到的最后一批开源离线包停留在 5.14.2 这个位置。这也是为什么"5.14.2 离线包"这个组合在搜索里热度一直下不来——它成了很多内网环境、无外网机器的唯一选择。

在线安装器能拿到更新的版本,但从 5.15.2 之后,安装过程需要登录账号,而且组件是按需勾选的。这里有个我踩过不止一次的坑:安装时如果没勾"Qt Debug Information Files"之外的某些杂项,后面几乎必然要回来补装;而补装的入口就是安装目录下的 Maintenance Tool。

Maintenance Tool 的关键特性是:它必须和安装目录放在一起才能工作,你不能把它单独拷到别的地方去管另一个安装。所以我的习惯是,每装一个 Qt 大版本就放在独立的顶层目录下,一个目录配一个 Maintenance Tool,互不干扰。

装的时候还有一个必须做的动作:把要用的附加模块一次性勾全。除了默认的 Core、Gui、Widgets,按项目需要去勾 Serial Port、Serial Bus、Multimedia、Charts、WebEngine 这些。事后补装虽然可行,但要重新走一遍登录和下载流程,很浪费时间。

4.2 目录结构与 Kit 的配置手法

我的机器上一般长这样:

D:/Qt/5.15.2/msvc2019_64 D:/Qt/5.15.2/mingw81_64 D:/Qt/6.5.3/msvc2019_64 D:/Qt/Tools/...

把编译套件作为一层目录,这样在 IDE 里加 Qt Versions 的时候,路径一眼就能辨认。Qt Creator 里的配置顺序是:先在"Qt Versions"里把每个qmake.exe的绝对路径加进去,它会自动识别版本号;再在"Compilers"里确认 MSVC 或 MinGW 的编译器;最后在"Kits"里把 Qt 版本和编译器配对起来。

这里有个细节值得强调:一个 Kit 里如果 Qt 是 64 位的,编译器也必须是 64 位的,目标是 32 位的也一样要配对。混搭的 Kit 能被保存,但构建必然失败,而且报错信息往往指向别的地方,很费时间。

给 Kit 起名字也很重要。默认名字像"Desktop Qt 5.15.2 MSVC2019 64bit"已经够用了,如果还有交叉编译 Kit,我会加上目标平台前缀,比如"ARM-Linux Qt 5.12.8 gcc"。项目一多,名字不清楚的代价是每次打开工程都要点开属性看一眼。

4.3 卸载、清理与踩过的坑

卸载 Qt 听起来简单,但残留是最容易埋雷的地方。彻底的清理至少包括这几步:

  • 用对应目录下的 Maintenance Tool 执行卸载,而不是直接删文件夹。
  • 检查系统环境变量,把指向该 Qt 目录的PATH条目、QTDIR、QT_PLUGIN_PATH之类清掉。
  • 清掉 IDE 的配置残留,Windows 上在用户目录的AppData下有 Qt Creator 的配置目录,Linux 在~/.config下。换版本后如果 Kit 列表里还留着已经不存在的 Qt,多半就是这里没清干净。
  • 检查构建目录,尤其是工程源码旁边那些build-xxx-Desktop_Qt_xxx目录,一律删掉。

还有一个容易被忽略的坑:同一个 Qt 安装目录里,不同小版本之间可能会共享部分 Tools 目录。直接删掉一个版本目录,可能会连带影响另一个版本的 IDE 或者构建工具。所以每个大版本独立顶层目录这个习惯,在卸载时也能省很多事。

5. 版本定下来之后,那些会咬人的工程细节

5.1 国际化流程在 Qt 5 与 Qt 6 上的差异

Qt 的国际化流程是tr()包裹字符串、用lupdate抽取生成.ts文件、翻译完用lrelease编译成.qm、运行时用QTranslator加载。这套流程本身在两个大版本里都成立,但几个细节会咬人。

第一,Qt 6 的工程如果用 CMake,推荐的方式是用qt_add_translations这类 CMake 命令来管理.ts文件,它会自动挂到构建流程上,.ts更新后重新构建就会重新跑 lrelease。而 Qt 5 + qmake 的老工程通常是在.pro里写TRANSLATIONS += xxx.ts,然后手工点菜单执行。迁移的时候这里必须改,否则你以为翻译更新了,实际运行加载的还是旧.qm。

第二,Qt 6 移除了QTextCodec。老代码里那些用 GBK 做编码转换的地方,迁移时会直接编译不过,需要换成QStringConverter或QStringDecoder。这类代码在国产化项目里非常常见,改动量不算小,一定要提前统计出来。

第三,Linguist 工具本身也是一个要安装的组件。装了 Qt 没装 Linguist,lupdate命令根本不存在。这个在精简安装的场景下经常被漏掉。

5.2 打包发布:windeployqt 挑食,别拿它跨版本用

windeployqt是 Windows 上最方便的部署工具,它会扫描可执行文件的依赖,自动把需要的 Qt DLL、插件、翻译文件拷到旁边。但它有个硬性限制:必须用编译这个程序的那个 Qt 版本对应的 windeployqt。拿 5.15.2 的 windeployqt 去处理用 6.5 编译的程序,它要么报版本不匹配,要么拷一堆错的库进去,运行起来就是各种加载失败。

MinGW 和 MSVC 的情况还要分开看。MinGW 版需要额外带上libstdc++-6.dll、libgcc_s_seh-1.dll、libwinpthread-1.dll这几个运行时;MSVC 版则需要目标机器装对应的 VC 运行库,或者把运行库一并打包。

Linux 上的情况这两年变化比较大。Qt 5 时代常用 linuxdeployqt,而 Qt 6 之后更推荐走 CMake 的 install 流程配合通用的打包工具,或者手工整理目录、用 patchelf 把 rpath 改成相对路径:

patchelf --set-rpath '$ORIGIN' ./your_app patchelf --set-rpath '$ORIGIN/../lib' ./plugins/platforms/libqxcb.so

这一步不做的话,程序在你机器上跑得好好的,拷到别人机器上就找不到库。

5.3 从 Qt 5 迁到 Qt 6 之前,先跑一遍这三张清单

真要做迁移,我建议在动手前先出三张清单,比直接开 IDE 乱改效率高得多。

第一张是模块清单。把工程里所有QT +=的内容列出来,逐个查它在 Qt 6 里是否还存在、是否需要换成新名字。尤其是那些附加模块,有些在 Qt 6 里被拆开或者改了归属。

第二张是移除 API 清单。用 IDE 全局搜索那几个典型符号:QRegExp、QTextCodec、QLinkedList、QDesktopWidget、QSignalMapper、QString::SkipEmptyParts、qrand、裸的endl。数一下出现次数,心里对工作量就有数了。

第三张是构建脚本清单。.pro里那些条件判断、平台相关的win32 {}、unix {}块,在 CMake 里都得重新表达。如果工程里有自定义的构建后步骤、资源编译脚本,也要一并梳理。

三张清单出完,如果总量在你可接受范围内就开工;如果远超预期,那就说明这个项目暂时不该升,老老实实待在 5.15.2 上继续维护,等它自然退役。

我个人在几轮迁移之后最大的体会是:Qt 版本选择这个问题,九成的纠结都来自"没把约束条件列清楚"就开始比版本号。把第三方库、目标平台、团队能力这三条写下来,答案往往只剩一个;剩下的精力应该花在 Kit 配置、构建目录管理和依赖清理这些看起来琐碎、但真正决定你一天能提交几次代码的事情上。至于那些Unknown module和Cannot mix incompatible Qt library的报错,先别急着怀疑版本选错了,去查模块装没装、Kit 指没指对、环境变量干不干净,通常比重新下一遍安装包快得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询