1. 为什么还要折腾 QJson 的编译
QJson 这个库,做 Qt 桌面开发的人多少都听过。它是一个基于 Qt 的轻量级 JSON 解析库,能把 JSON 文本直接映射成 QVariant 对象,也能把 QVariant 反向序列化成 JSON 字符串。相比 Qt 自带的 QJsonDocument,它的 API 更贴近“随手用”的直觉,尤其在处理嵌套结构、数组和类型转换时,代码量能少一大截。
但问题也恰恰出在这里。QJson 并不是 Qt 官方模块,它最早是一个独立开源项目,后来虽然被不少项目引用,但版本迭代慢、构建脚本老旧,加上 Qt 自身从 qmake 到 CMake 的迁移,导致很多人在编译阶段就卡住了。我见过太多人拿到源码后,第一步qmake就报错,或者编译通过但链接时找不到符号,又或者在自己的工程里引入后出现诡异的运行时崩溃。
这篇内容就是把我自己反复编译 QJson 踩过的坑、试过的方案、最终稳定的流程完整梳理出来。适合两类人看:一类是第一次接触 QJson、想把它集成到自己 Qt 工程里的开发者;另一类是被编译错误折磨过、想找一份“能直接抄”的构建配置的人。全文基于 Qt 5 和 Qt 6 的常见环境展开,Qt 4 的情况会单独提一句,但不会作为重点。
需要提前说明的是,QJson 的源码版本差异比较大,不同分支的构建文件可能完全不同。我下面讲的是目前社区里流传较广、相对稳定的一个版本结构,如果你手里的源码目录和我描述的不一样,先别急着照搬,看完第二节的目录分析再动手。
2. 编译前的环境确认与源码结构分析
2.1 确认 Qt 版本和编译器组合
QJson 的编译成败,很大程度上取决于你的 Qt 版本和编译器是否匹配。我实测下来,以下几种组合是比较稳的:
| Qt 版本 | 编译器 | 构建方式 | 稳定性 |
|---|---|---|---|
| Qt 5.12 LTS | MSVC 2017/2019 | qmake + nmake | 很稳 |
| Qt 5.15 LTS | MSVC 2019 | qmake + nmake | 很稳 |
| Qt 5.15 LTS | MinGW 8.1 | qmake + mingw32-make | 稳 |
| Qt 6.2 LTS | MSVC 2019 | CMake + Ninja | 较稳 |
| Qt 6.5 LTS | MSVC 2022 | CMake + Ninja | 较稳 |
| Qt 5.9 | MinGW 5.3 | qmake + mingw32-make | 一般 |
这里有个关键点:Qt 6 默认不再提供 qmake 的完整支持,虽然 qmake 还能用,但 QJson 老版本的 .pro 文件在 Qt 6 下经常出现模块找不到的问题。所以如果你用的是 Qt 6,我建议直接走 CMake 路线,或者手动补一个 CMakeLists.txt。
另一个坑是32 位和 64 位的混用。QJson 编译出来的库必须和你的主工程位数一致,否则链接阶段会报“模块计算机类型 x64 与目标计算机类型 x86 冲突”。这个错误看起来很吓人,其实就是位数不匹配,重新用对应版本的 Qt 命令行工具编译一遍就行。
2.2 源码目录里哪些文件真正有用
拿到 QJson 源码后,先别急着打开 .pro 文件。我建议你先花两分钟看清楚目录结构,因为不同来源的压缩包里,垃圾文件和有效文件混在一起的情况很常见。
一个典型的 QJson 源码目录大概长这样:
qjson/ ├── src/ │ ├── json_parser.cpp │ ├── json_parser.h │ ├── json_scanner.cpp │ ├── json_scanner.h │ ├── parser.cpp │ ├── parser.h │ ├── qjson_export.h │ ├── qjson_global.h │ ├── qjsonobject.cpp │ ├── qjsonobject.h │ ├── qjsonvalue.cpp │ ├── qjsonvalue.h │ └── qjson.pro ├── tests/ ├── examples/ ├── README.md └── qjson.pro真正需要编译进库的,就是src/下面那些 .cpp 和 .h。tests/和examples/可以暂时不管,它们依赖额外的测试框架,第一次编译时建议直接跳过,减少变量。
注意:有些版本的 QJson 源码里,
src/下面还有一个qjson_export.h,这个文件是自动生成的,但有些压缩包里给的是空文件或者占位文件。如果编译时报“未定义 QJSON_EXPORT”,就是这个头文件的问题,需要手动补上导出宏定义。
2.3 清理掉可能干扰编译的旧文件
这一步很多人会忽略,但实际非常关键。如果你之前尝试编译过 QJson,目录里可能残留了Makefile、.qmake.stash、debug/、release/、build/这些中间产物。不同版本的 qmake 生成的 Makefile 格式不一样,混在一起会导致莫名其妙的错误。
我的习惯是,在开始编译前,先在源码根目录执行一次清理:
# 如果你在 Windows 的 Qt 命令行里 del Makefile del .qmake.stash rmdir /s /q debug rmdir /s /q release rmdir /s /q build# 如果你在 Linux 或 macOS 的终端里 rm -f Makefile .qmake.stash rm -rf debug release build清理完之后,再确认一下src/qjson.pro里的TARGET和DESTDIR配置。有些版本默认把库输出到系统目录,这在没有管理员权限的机器上会直接失败。我一般会改成输出到源码目录下的lib/文件夹,方便后续引用。
3. 用 qmake 编译 QJson 的完整流程
3.1 打开正确的命令行环境
Windows 下编译 Qt 项目,最容易出错的地方就是命令行环境不对。你不能直接开一个普通的 cmd 或者 PowerShell,那样找不到 qmake、nmake、mingw32-make 这些工具。
正确的做法是从开始菜单里找到对应 Qt 版本的命令行工具。比如你用 Qt 5.15.2 的 MSVC 2019 64 位版本,就打开 “Qt 5.15.2 (MSVC 2019 64-bit)” 那个快捷方式。打开之后,命令行提示符前面通常会带一个环境标识,这时候再输入qmake -v,应该能看到正确的 Qt 版本和路径。
如果你用的是 MinGW 版本,就打开 “Qt 5.15.2 (MinGW 8.1 64-bit)” 那个。输入qmake -v和mingw32-make -v都应该能正常输出版本信息。
实操心得:我习惯在打开命令行后,先执行一次
where qmake(Windows)或which qmake(Linux/macOS),确认当前环境调用的 qmake 就是我想要的那个版本。因为很多人的机器上装了多个 Qt 版本,环境变量一乱,调用的就是另一个版本的 qmake,编译出来的库自然和主工程不匹配。
3.2 生成 Makefile 并检查关键配置
进入 QJson 源码的src/目录,执行:
qmake qjson.pro如果一切正常,你会看到目录下多了一个Makefile。这时候别急着 make,先打开 Makefile 扫一眼几个关键变量:
CC和CXX:确认编译器路径是你期望的那个。INCPATH:确认包含了 Qt 的头文件路径。LIBS:确认链接了 QtCore 和 QtGui(有些版本需要 QtGui,有些只需要 QtCore)。DESTDIR:确认库的输出目录。
我遇到过一种情况:qmake 执行成功,但 Makefile 里的INCPATH指向了一个不存在的 Qt 头文件目录,原因是 Qt 安装时某些模块没勾选。这种问题在 make 阶段才会暴露,提前看一眼能省不少时间。
3.3 执行编译并处理常见报错
生成 Makefile 后,执行编译命令:
# MSVC 环境 nmake # MinGW 环境 mingw32-make # Linux/macOS 环境 make如果源码本身没问题,环境也匹配,这一步应该能顺利跑完,最终在DESTDIR指定的目录下生成qjson.dll和qjson.lib(Windows)或libqjson.so/libqjson.dylib(Linux/macOS)。
但现实往往没那么顺利。下面是我遇到过的几个典型报错和对应的处理方式:
报错一:fatal error C1083: 无法打开包括文件: "qjson_export.h"
这个错误说明编译器找不到qjson_export.h。先确认这个文件是否存在于src/目录下。如果不存在,需要手动创建一个,内容大致如下:
#ifndef QJSON_EXPORT_H #define QJSON_EXPORT_H #include <QtCore/qglobal.h> #if defined(QJSON_LIBRARY) # define QJSON_EXPORT Q_DECL_EXPORT #else # define QJSON_EXPORT Q_DECL_IMPORT #endif #endif // QJSON_EXPORT_H然后在qjson.pro里确认有DEFINES += QJSON_LIBRARY这一行。这个宏的作用是告诉编译器“我现在是在编译库本身,不是在引用库”,从而正确导出符号。
报错二:undefined reference to 'QJson::Parser::parse'
这是链接错误,通常发生在你试图把 QJson 静态库链接到自己的工程时。原因可能是库文件没有正确生成,或者链接顺序不对。先确认lib/目录下确实有qjson.lib或libqjson.a,然后在自己的 .pro 文件里检查LIBS的写法。
报错三:error: 'QVariant::Type' has not been declared
这个错误在 Qt 6 下比较常见,因为 Qt 6 把QVariant::Type改成了QMetaType::Type。如果你用的是 Qt 6,需要手动修改源码里涉及QVariant::Type的地方,替换成QMetaType::Type。这也是为什么我前面说 Qt 6 下建议走 CMake 路线,因为老版本的 qmake 工程文件在 Qt 6 下需要改的地方比较多。
3.4 验证编译产物是否可用
编译完成后,不要直接扔到主工程里用。先做一个最小验证:新建一个空的 Qt Console 工程,把 QJson 的头文件目录和库文件目录配置进去,写几行代码测试解析和序列化。
#include <QCoreApplication> #include <QDebug> #include "parser.h" #include "qjsonobject.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QJson::Parser parser; bool ok; QVariant result = parser.parse("{\"name\":\"test\",\"value\":123}", &ok); if (ok) { QJson::QObjectHelper::qvariant2qobject(result.toMap(), nullptr); qDebug() << "Parse success:" << result; } else { qDebug() << "Parse failed at line" << parser.errorLine(); } return a.exec(); }如果这段代码能编译、链接、运行,并且输出正确的解析结果,说明库本身没问题。如果链接时报错,大概率是库的位数不对,或者头文件和库文件的版本不匹配。
4. Qt 6 下用 CMake 编译 QJson 的替代方案
4.1 为什么 Qt 6 下 qmake 路线越来越难走
Qt 6 对构建系统做了一次大调整,CMake 成了官方推荐的首选,qmake 虽然还在,但很多新特性和模块配置在 qmake 下已经不再更新。QJson 作为一个老项目,它的 .pro 文件是基于 Qt 4/5 时代的写法,在 Qt 6 下会遇到几个硬性问题:
QT += gui在 Qt 6 下需要改成QT += core gui,而且某些 Qt 6 版本对 gui 模块的依赖更严格。CONFIG += staticlib在 Qt 6 下的行为和 Qt 5 不完全一致,静态库的导出宏需要额外处理。QMAKE_CXXFLAGS里的一些老参数在 Qt 6 的编译器下会报 warning 甚至 error。
与其花时间改 .pro 文件,不如直接写一个 CMakeLists.txt,把 QJson 的源码当成自己工程的一部分来编译。这样不仅 Qt 6 下能用,Qt 5 下也能用,而且跨平台一致性更好。
4.2 手写一个可复用的 CMakeLists.txt
下面这个 CMakeLists.txt 是我在实际项目中反复用过的,支持 Qt 5 和 Qt 6,支持静态库和动态库切换,输出目录也做了统一处理:
cmake_minimum_required(VERSION 3.16) project(QJson LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) set(QJSON_SOURCES src/json_parser.cpp src/json_scanner.cpp src/parser.cpp src/qjsonobject.cpp src/qjsonvalue.cpp ) set(QJSON_HEADERS src/json_parser.h src/json_scanner.h src/parser.h src/qjson_export.h src/qjson_global.h src/qjsonobject.h src/qjsonvalue.h ) add_library(QJson STATIC ${QJSON_SOURCES} ${QJSON_HEADERS}) target_include_directories(QJson PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src> $<INSTALL_INTERFACE:include/QJson> ) target_link_libraries(QJson PUBLIC Qt${QT_VERSION_MAJOR}::Core) target_compile_definitions(QJson PRIVATE QJSON_LIBRARY) set_target_properties(QJson PROPERTIES OUTPUT_NAME qjson ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )把这个文件放在 QJson 源码根目录,然后执行:
cmake -B build -DCMAKE_PREFIX_PATH=/path/to/Qt/6.x.x/gcc_64 cmake --build build --config Release编译完成后,build/lib/下会生成libqjson.a,头文件在src/下,直接在自己的 CMake 工程里add_subdirectory或者find_package都能用。
4.3 把 QJson 集成到自己工程里的两种方式
第一种是源码内嵌:把 QJson 的src/目录整个拷到自己工程的third_party/qjson/下,然后在顶层 CMakeLists.txt 里add_subdirectory(third_party/qjson),最后在目标链接时加上target_link_libraries(你的目标 PRIVATE QJson)。这种方式最省心,版本可控,调试也方便。
第二种是预编译库引用:先把 QJson 单独编译成静态库或动态库,然后在自己的工程里通过find_library或直接指定路径来链接。这种方式适合多个工程共用同一个 QJson 库的场景,但要注意头文件版本和库版本必须一致,否则会出现“编译通过但运行崩溃”的问题。
注意:如果你用的是动态库方式,部署时需要把
qjson.dll或libqjson.so一起打包到可执行文件目录下,否则运行时会报“找不到 qjson.dll”。静态库方式则没有这个问题,但会让最终可执行文件体积变大。
5. 编译过程中最容易踩的五个坑
5.1 坑一:头文件里的导出宏没定义
这是最经典的一个坑。QJson 的头文件里大量使用了QJSON_EXPORT宏,如果这个宏没有正确定义,编译阶段可能只是警告,但链接阶段一定会报“无法解析的外部符号”。
正确的做法是:在编译库本身时,定义QJSON_LIBRARY;在引用库时,不要定义这个宏。这样QJSON_EXPORT会自动切换成Q_DECL_EXPORT或Q_DECL_IMPORT。
如果你用的是 CMake,就像我上面写的那样,用target_compile_definitions(QJson PRIVATE QJSON_LIBRARY)来保证只有编译库时才定义这个宏。
5.2 坑二:Qt 模块依赖漏掉了
QJson 的核心依赖是 QtCore,但有些版本的源码里还用到了 QtGui 里的东西,比如QColor的序列化。如果你只链接了 QtCore,编译时可能不报错,但链接时会找不到QColor相关的符号。
我的建议是,第一次编译时先把QT += core gui都加上,等编译通过后再根据实际使用情况精简。多链接一个 QtGui 模块,对最终体积的影响远小于反复排查链接错误的时间成本。
5.3 坑三:字符编码导致的编译失败
QJson 的源码里有一些字符串字面量,在 MSVC 下如果源文件编码和编译器默认编码不一致,会报C4819警告,严重时直接报错。解决办法是在 .pro 文件里加上:
QMAKE_CXXFLAGS += /utf-8或者在 CMake 里加上:
if(MSVC) target_compile_options(QJson PRIVATE /utf-8) endif()这个坑在中文 Windows 环境下特别常见,因为系统默认代码页是 GBK,而源码文件可能是 UTF-8 无 BOM 格式。
5.4 坑四:静态库和动态库的宏冲突
如果你先编译了动态库,又编译了静态库,两个版本的qjson.lib混在一起,链接时会出现“符号重复定义”的错误。解决办法是每次切换库类型时,先彻底清理构建目录,再重新生成。
我自己的习惯是,静态库和动态库分别放在不同的构建目录下,比如build-static/和build-shared/,互不干扰。
5.5 坑五:Qt 版本升级后没有重新编译
Qt 从 5.15 升级到 6.2,或者从 6.2 升级到 6.5,ABI 是有变化的。如果你用 Qt 5.15 编译的 QJson 库,直接拿到 Qt 6.5 的工程里链接,即使编译通过,运行时也可能崩溃。
正确的做法是:每次 Qt 版本变更后,QJson 库都要重新编译一遍。不要试图跨大版本复用二进制库。
6. 常见问题速查与排查思路
6.1 编译阶段问题速查表
| 现象 | 可能原因 | 排查方式 | 解决方式 |
|---|---|---|---|
| qmake 找不到 | 命令行环境不对 | where qmake | 打开对应 Qt 版本的命令行工具 |
| 找不到 qjson_export.h | 文件缺失或路径不对 | 检查 src 目录 | 手动创建导出宏头文件 |
| C1083 无法打开包括文件 | Qt 头文件路径不对 | 检查 Makefile 的 INCPATH | 确认 Qt 安装完整 |
| C4819 编码警告 | 源码编码与编译器不一致 | 查看源文件编码 | 加 /utf-8 编译选项 |
| LNK2019 无法解析的外部符号 | 导出宏未定义或库未链接 | 检查 QJSON_LIBRARY 宏 | 正确配置编译定义 |
| LNK1112 模块计算机类型冲突 | 32 位和 64 位混用 | 检查 Qt 版本和编译器 | 统一使用相同位数 |
6.2 链接阶段问题速查表
| 现象 | 可能原因 | 排查方式 | 解决方式 |
|---|---|---|---|
| 找不到 qjson.lib | 库输出目录不对 | 检查 DESTDIR | 修改输出目录或添加库搜索路径 |
| 符号重复定义 | 静态库和动态库混用 | 检查链接的库文件 | 清理后只保留一种库 |
| 运行时崩溃 | Qt 版本不匹配 | 检查编译和运行的 Qt 版本 | 重新编译 QJson |
| 找不到 qjson.dll | 动态库未部署 | 检查可执行文件目录 | 拷贝 dll 到输出目录 |
| 解析结果为空 | JSON 格式错误 | 检查 errorLine 和 errorString | 修正 JSON 输入 |
6.3 我个人的排查习惯
遇到编译问题时,我一般按这个顺序排查:
- 先看第一条错误。编译器报错往往是一连串的,但第一条才是根因,后面的都是连锁反应。
- 确认环境变量。
qmake -v、where nmake、where cl这三个命令的输出能解决大部分环境问题。 - 清理后重试。删掉所有中间文件,重新 qmake 和 make,能排除掉很多“上次编译残留”导致的问题。
- 最小化复现。新建一个空工程,只引入 QJson,写最少的代码,确认库本身没问题,再排查自己工程的配置。
- 对比官方示例。如果 QJson 源码里有 examples 目录,先编译官方示例,确认示例能跑通,再改自己的代码。
实操心得:我习惯在编译 QJson 之前,先在一个干净的虚拟机或者容器里跑一遍完整流程。这样能排除掉本机环境的各种干扰,确认哪些步骤是真正必需的,哪些只是我本机特有的配置。虽然多花十分钟,但能省下后面几个小时甚至几天的排查时间。
7. 编译完成后的集成与使用建议
7.1 在自己的 qmake 工程里引用 QJson
如果你用的是 qmake 工程,引用 QJson 静态库的写法大致如下:
INCLUDEPATH += $$PWD/third_party/qjson/src LIBS += -L$$PWD/third_party/qjson/lib -lqjson # 如果是 Windows 下 MSVC 编译的动态库 LIBS += -L$$PWD/third_party/qjson/lib -lqjson注意-lqjson在 Windows 下会去找qjson.lib,在 Linux 下会去找libqjson.so或libqjson.a。路径里的$$PWD是 .pro 文件所在目录,用相对路径能保证工程在不同机器上都能找到库文件。
7.2 在自己的 CMake 工程里引用 QJson
如果你用的是 CMake 工程,并且已经把 QJson 作为子目录引入,引用方式就很简单:
add_subdirectory(third_party/qjson) target_link_libraries(你的目标 PRIVATE QJson)如果 QJson 是预编译好的,可以用find_library:
find_library(QJSON_LIBRARY NAMES qjson PATHS ${CMAKE_SOURCE_DIR}/third_party/qjson/lib) find_path(QJSON_INCLUDE_DIR NAMES parser.h PATHS ${CMAKE_SOURCE_DIR}/third_party/qjson/src) target_include_directories(你的目标 PRIVATE ${QJSON_INCLUDE_DIR}) target_link_libraries(你的目标 PRIVATE ${QJSON_LIBRARY})7.3 使用 QJson 时的几个编码习惯
QJson 的 API 设计比较灵活,但灵活也意味着容易写出有隐患的代码。我自己的几个习惯是:
- 解析时始终检查
ok标志,不要假设输入一定是合法 JSON。 - 序列化时注意
QVariant的类型,QVariantMap和QVariantList能正确序列化,但自定义类型需要先转换成基础类型。 - 处理大 JSON 时,QJson 的解析器是递归下降的,嵌套层级太深可能导致栈溢出,实际项目中建议对输入做深度限制。
- 如果 JSON 里包含中文,确保解析和序列化时使用 UTF-8 编码,避免出现乱码。
8. 最后再分享几个实用技巧
第一个技巧是关于编译缓存的。如果你需要反复编译 QJson 来测试不同配置,建议用ccache(Linux/macOS)或者clcache(Windows)来加速。QJson 的源码量不大,但反复清理重建还是很烦人的,有了缓存能省不少时间。
第二个技巧是关于版本管理的。QJson 的源码建议直接放进自己项目的版本控制里,而不是依赖外部下载。因为 QJson 的官方仓库更新很慢,有些版本甚至已经不再维护,把源码固定在自己项目里,能保证任何时候都能编译出可用的库。
第三个技巧是关于跨平台构建的。如果你需要在 Windows、Linux、macOS 三个平台上都编译 QJson,建议统一用 CMake,并且把平台相关的编译选项用if(WIN32)、if(APPLE)、if(UNIX)分开处理。这样一份 CMakeLists.txt 就能覆盖三个平台,比维护三套 .pro 文件省事得多。
第四个技巧是关于调试符号的。Release 模式下编译的 QJson 库默认不带调试符号,如果运行时崩溃,堆栈信息会很难看。建议在开发阶段用 RelWithDebInfo 模式编译,既有优化又有调试信息,排查问题方便很多。
set(CMAKE_BUILD_TYPE RelWithDebInfo)或者用 qmake 的话:
CONFIG += force_debug_info QMAKE_CXXFLAGS_RELEASE += /Zi QMAKE_LFLAGS_RELEASE += /DEBUG这几个技巧都是我在实际项目中反复验证过的,尤其是跨平台构建和调试符号这两点,能帮你省下大量排查时间。QJson 本身不复杂,复杂的是构建环境和版本匹配,把这两件事理顺了,后面用起来就很顺手了。