资讯详情

资讯详情

预编译 jsoncpp 安装配置与链接排错实战

简介这份已编译的 jsoncpp 压缩包面向使用 C 与 Visual Studio 处理 JSON 解析、生成和数据交换的开发者省去从源码编译的环节适合需要在项目中快速集成轻量级 JSON 能力的初、中级开发场景。包内共 10 个文件以 8 个 .h 头文件和 2 个 .lib 库文件为主头文件提供 Json::Value、Reader、Writer 等接口声明库文件承担链接阶段的实现压缩包约 1023KB目录按 include 与 lib 组织便于直接纳入工程。已有 339 人学习或下载说明该版本在 VS 环境下具有一定参考与复用价值。拿到后可按头文件声明与库文件实现对应的方式接入现有 C 工程围绕 JSON 解析、序列化和查询进行二次开发减少环境搭建与编译排查时间把精力放在业务数据处理与接口联调上。1. 预编译 jsoncpp 拿到手版本对不上后面每一步都在还债上周又碰到一次典型的翻车某开发者把一份已编译的 jsoncpp 压缩包解压到工程目录include 和 lib 一丢编译一路绿灯程序跑起来读到配置文件那行直接段错误换了个机器同样的代码变成链接期一屏undefined symbol。jsoncpp 这个 C 的 JSON 解析库几乎每个带配置读取、接口返回解析、日志落地的项目都会塞一份进去装得好是十分钟的事装不好能查一下午。预编译包省掉的是 CMake 编译那十几分钟代价是把“编译器版本、C11 ABI 开关、目标位数、Debug/Release 运行库”这四道选择题挪到了你身上。这篇按我实际装过的顺序走一遍先核环境再放文件再配查找路径最后拿一个二十行的最小程序把整套环境钉死。适合手里已经有一份预编译 jsoncpp、准备往 CMake 或 Makefile 项目里接的人。2. 装前对齐环境jsoncpp 1.9.x 预编译包的四个匹配项预编译包真正的风险不是“能不能用”而是“能不能一直用”。同一份libjsoncpp.so在打包那台机器上一切正常换到你机器上可能只是某个符号名多了个__cxx11前缀链接器就翻脸不认人。所以解压之后别急着cp先花五分钟把下面四项核一遍编译器大版本、C11 ABI 开关、目标位数与架构、Debug/Release 运行库。这四项里任何一项对不上报错信息都不会直说“是 ABI 的锅”只会给你一串看不懂的符号名这就是它最玄学的地方。2.1 编译器大版本与 C 标准先对一遍jsoncpp 1.9.x 这条线自身是按 C11 写的常见预编译包标注的最低门槛在 GCC 4.8、Clang 3.4、MSVC 2015 这一档。你的工程如果还停在-stdc98光把#include json/json.h加进去就会先炸一批语法错误。我一般的做法是先确认自己这边的编译器档次再决定这份包能不能用。# 看编译器主版本1.9.x 的包最低按 GCC 4.8 这一档估 g --version | head -1 # 看默认的 C 标准和 ABI 宏比翻文档快 g -dM -E -x c /dev/null | grep -E __cplusplus|_GLIBCXX_USE_CXX11_ABI第一行给出的是编译器版本用来判断包里的目标文件是不是你这代编译器产出的第二条命令打印的是预处理宏__cplusplus是默认标准201103表示 C11_GLIBCXX_USE_CXX11_ABI就是下一节要重点说的 ABI 开关值只能是 0 或者 1。如果你的工程在CMakeLists.txt里显式写了CMAKE_CXX_STANDARD 11那这一项基本没问题怕的是那种“默认标准凑合用”的老工程加库之前先显式升一次标准更稳妥。还有一项容易漏运行时 libstdc 的版本。包是在新机器上编的用到GLIBCXX_3.4.26这类新符号搬到老系统上一样跑不起来。提前看一眼# 列出这份 so 依赖的最高 GLIBCXX 版本和运行环境的 libstdc 对一下 strings libjsoncpp.so.25 | grep -E ^GLIBCXX_[0-9.]$ | sort -Vu | tail -3输出里最高那个版本号就是运行环境 libstdc 必须达到的下限。达不到只有两条路换一份在老系统上编的包或者在目标机器上重编。别想着打补丁绕过这类符号缺失在运行时才暴露比链接期报错难查得多。2.2 C11 ABI 开关__cxx11 那串符号GCC 5 之后std::string换了实现为了兼容老代码留了个开关_GLIBCXX_USE_CXX11_ABI。jsoncpp 的公开接口里大量出现std::stringJson::Value::asString()、Json::writeString()都是所以这个开关必须和预编译包保持一致错一位都不行。判断方法很直接看包里导出符号带不带__cxx11# 统计带 __cxx11 的导出符号数量大于 0 说明包是用新 ABI 编的 nm -D --defined-only libjsoncpp.so.25 | grep -c __cxx11结果大于 0你的工程就要加-D_GLIBCXX_USE_CXX11_ABI1等于 0就加0。CMake 里对应写成# 必须和预编译包的 ABI 一致否则链接期会出现 std::__cxx11::basic_string 找不到定义 add_compile_definitions(_GLIBCXX_USE_CXX11_ABI1)这行必须加在add_executable之前影响的是全局编译选项。我见过有人在某个target_compile_definitions里只给一半的 target 加了这行结果主程序编过了、单元测试链接失败症状特别迷惑。整个工程统一加别挑挑拣拣。Windows 那边没有这个概念但有个等价物动态库要定义导入宏静态库要定义静态宏名字在不同版本里改过最靠谱的办法是打开包里的json/config.h搜一下JSON_DLL和JSONCPP_STATIC_DEFINE看到哪个就用哪个别凭记忆写。2.3 位数、架构与 Debug/Release这三项经常被当成理所当然实际出错率不低。用file一条命令就能看出包是什么货色file lib/libjsoncpp.so.25 # 期望输出类似ELF 64-bit LSB shared object, x86-64, version 1 (SYSV)输出里64-bit和x86-64两个词决定了一切。你的进程是 32 位链接这份 64 位库会直接报file in wrong format这个还算友好麻烦的是 ARM 机器上拿了一份 x86-64 的包报错信息会绕一大圈。Windows 侧对应看dumpbin /headers jsoncpp.lib关注 machine 那一行是 x64 还是 x86。Debug/Release 的坑主要在 WindowsMSVC 的 Debug 版用调试版 CRTRelease 版用发布版 CRT两边的堆管理不通用。你拿一份 Release 编的jsoncpp.lib去链 Debug 工程编译链接都可能过运行到Json::Value析构时堆损坏退出时崩在某个完全无关的地方。判断方法是看包名或者看 lib 目录里有没有d后缀的版本没有的话就统一用 Release 配置跑别混着来。匹配项检查命令不一致的表现编译器大版本g --version链接期符号缺失或语法报错C11 ABInm -D … | grep -c __cxx11std::__cxx11::basic_string未定义位数与架构file libjsoncpp.so.25file in wrong format或直接段错误Debug/Release看包内 lib 命名后缀Windows 上堆损坏、退出崩溃libstdc 版本strings … | grep GLIBCXX运行期version GLIBCXX_x.x.x not found2.4 用三条命令把包里的信息读出来拿到一份来源不明的预编译包我习惯先把它的“身份证”读一遍再决定放不放到系统目录# 1. 看 soname这个数字就是 ABI 版本号别自己猜 readelf -d libjsoncpp.so.25 | grep SONAME # 2. 看依赖了哪些运行库重点确认没有奇怪的第三方依赖 ldd libjsoncpp.so.25 # 3. 确认关键符号在不在CharReaderBuilder 是 1.9.x 的解析入口 nm -D --defined-only libjsoncpp.so.25 | cfilt | grep CharReaderBuilder | head -3第一条输出Library soname: [libjsoncpp.so.25]这种格式25就是 ABI 版本号后面建软链接要用到它写错了运行时找不到库。第二条列出的依赖应该只有libstdc、libm、libgcc_s、libc这几样多出任何一项都要警惕说明这个包不是“干净”编译出来的。第三条验证符号cfilt负责把乱码一样的修饰名还原成可读形式看到Json::CharReaderBuilder就说明这份包的接口版本对得上 1.9.x。3. 落地安装把 include 和 lib 放到位环境核完就可以动手放了。这一步的目标很简单让编译器用-I能找到json/json.h让链接器用-ljsoncpp能摸到库文件让运行时的动态加载器在ldconfig缓存里能找到那份.so。三件事各自独立任何一件没做报错都长得不一样。3.1 Linux/usr/local 下的目录布局与软链接我一般不放系统自带的/usr/lib那会和发行版包管理器装的那份 jsoncpp 打架。统一放/usr/local路径清晰卸载时删目录就干净了。# 解压到临时目录先看清楚结构再往系统里搬 tar -xzf jsoncpp-1.9.5-linux-x86_64.tar.gz -C /tmp/jsoncpp-pkg # 建目录-p 保证父目录存在 sudo install -d /usr/local/include/json /usr/local/lib # 头文件按 json/ 子目录整体搬因为代码里写的是 #include json/json.h sudo cp -a /tmp/jsoncpp-pkg/include/json/. /usr/local/include/json/ # 库文件连软链接一起保留-a 不要漏 sudo cp -a /tmp/jsoncpp-pkg/lib/libjsoncpp.so* /usr/local/lib/ # 补一个开发用软链接只在链接期有用 sudo ln -sf libjsoncpp.so.25 /usr/local/lib/libjsoncpp.so # 刷新动态库缓存否则运行期还是找不到 sudo ldconfiginstall -d等价于mkdir -p加权限设置比自己mkdir少踩权限坑。cp -a的-a是归档模式保留符号链接本身而不是跟随链接复制实体文件这一步如果漏了.so会变成一个巨大实体文件后面升级库会很难受。软链接那条要特别说明实际文件通常是三层libjsoncpp.so.25.0.5是实体libjsoncpp.so.25是 soname 链接libjsoncpp.so是开发链接。编译链接时找的是最后那个不带版本号的运行时找的是中间那个带 soname 的。两个链接缺一不可但只有带 soname 的那个必须跟着ldconfig走。3.2 Windows MSVC三个目录和两个宏Windows 侧没有ldconfig这套东西全靠工程配置。预编译包里一般有这几样先分清用途再往工程里放文件用途放哪里json/json.h、json/json-forwards.h头文件任意固定目录加到 Include 路径jsoncpp.lib动态库导入库加到 Library 路径配.dll用jsoncpp.dll运行时库与 exe 同目录或加进 PATHjsoncpp_static.lib静态库加到 Library 路径配静态宏用工程属性里三处要改C/C 的附加包含目录指向头文件根目录注意是json的上一级不是json本身链接器的附加库目录指向 lib 所在目录附加依赖项写jsoncpp.lib。然后是最容易忘的那一步宏// 链动态库时不要定义 JSONCPP_STATIC_DEFINE保留导入语义 // 链静态库时必须定义否则符号被当成 dllimport链接期报无法解析的外部符号 // 宏的准确名字以包内 json/config.h 为准不同小版本改过 #define JSONCPP_STATIC_DEFINE #include json/json.h这段放在你自己的头文件里别去改 jsoncpp 自带的头文件升级包的时候会全部覆盖掉。宏定义写错的表现是__declspec(dllimport) 无法解析的外部符号一串看到这个词基本就是宏的问题不用往别处查。3.3 写一份 jsoncpp.pc让 pkg-config 接管查找预编译包如果没带jsoncpp.pc我一般自己补一份。好处是所有工程的编译命令都变成$(pkg-config --cflags --libs jsoncpp)路径改了只动一个文件。# 保存为 /usr/local/lib/pkgconfig/jsoncpp.pc prefix/usr/local exec_prefix${prefix} libdir${exec_prefix}/lib includedir${prefix}/include Name: jsoncpp Description: JSON parsing library for C Version: 1.9.5 Libs: -L${libdir} -ljsoncpp Libs.private: -lm Cflags: -I${includedir}写完验证一下# 能看到正确的 -I 和 -l 参数就说明文件生效了 pkg-config --cflags --libs jsoncpp pkg-config --modversion jsoncppVersion这一行不是装饰很多构建脚本靠它做版本门槛判断写错会导致find_package之类的逻辑走错分支。如果pkg-config找不到文件先echo $PKG_CONFIG_PATH没有/usr/local/lib/pkgconfig就补上或者干脆把.pc文件丢进/usr/lib/pkgconfig。Libs.private里那些只在静态链接时才需要的库放这一行动态链接不会带进去。4. 接到构建系统CMake 与 Makefile 的两套写法库放好了接下来是让工程真正找到它。这一步的取舍在于用find_package省事但依赖包自带 cmake 配置文件手写 imported target 麻烦一点但完全可控。预编译包通常不带配置文件所以我默认走第二条路。4.1 CMakefind_package 与手写 imported target先试配置文件这条路一行就能看出有没有cmake_minimum_required(VERSION 3.16) project(jsoncpp_demo CXX) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 预编译包带了 jsoncppConfig.cmake 才能走这条找不到会直接报错停止 find_package(jsoncpp CONFIG REQUIRED) add_executable(demo main.cpp) target_link_libraries(demo PRIVATE JsonCpp::JsonCpp)CONFIG关键字表示只认配置文件模式不去翻系统模块目录行为更可预测。目标名JsonCpp::JsonCpp是 1.9.x 导出的标准名字写错的话 CMake 会提示“目标不存在”。包不带配置文件时改成手写# 预编译包一般没有 cmake 配置文件用 IMPORTED 目标把路径钉死 add_library(jsoncpp_prebuilt SHARED IMPORTED) set_target_properties(jsoncpp_prebuilt PROPERTIES IMPORTED_LOCATION /usr/local/lib/libjsoncpp.so.25 # 实体文件或 soname 链接 INTERFACE_INCLUDE_DIRECTORIES /usr/local/include # 头文件根目录 INTERFACE_COMPILE_DEFINITIONS _GLIBCXX_USE_CXX11_ABI1 # 和包里的 ABI 对齐 ) add_executable(demo main.cpp) target_link_libraries(demo PRIVATE jsoncpp_prebuilt)IMPORTED_LOCATION建议直接写带 soname 的完整路径写libjsoncpp.so也行但那个是软链接某些构建环境下 CMake 会去解析真实路径反而绕。INTERFACE_COMPILE_DEFINITIONS那行的作用是把 ABI 定义跟着目标传下去谁链这个库谁就自动带上比全局add_compile_definitions更精确。4.2 Makefile链接顺序、rpath 与静态库的 -fPIC 问题不用 CMake 的项目Makefile 里三个地方要注意-l的位置、rpath、静态库能不能塞进 so。CXX : g CXXFLAGS : -stdc11 -O2 -I/usr/local/include -D_GLIBCXX_USE_CXX11_ABI1 # $$ORIGIN 是 Makefile 里的转义写法最终传给链接器的是 $ORIGIN LDFLAGS : -L/usr/local/lib -Wl,-rpath,$$ORIGIN/../lib LDLIBS : -ljsoncpp demo: main.o $(CXX) $^ -o $ $(LDFLAGS) $(LDLIBS)LDLIBS必须放在目标文件后面链接器是从左往右扫的库放在前面就会出现“符号明明在库里却报未定义”的怪事。-Wl,-rpath,$ORIGIN/../lib的含义是把运行时查找路径写进可执行文件$ORIGIN指 exe 所在目录这样打包发布时不用配LD_LIBRARY_PATH也不用动系统缓存是我最推荐的做法。用静态库的话把-ljsoncpp换成完整路径/usr/local/lib/libjsoncpp.a或者写-l:libjsoncpp.a。这里有个前置条件静态库编译时必须带-fPIC否则你拿它去生成.so会直接报relocation R_X86_64_32S against ... can not be used when making a shared object; recompile with -fPIC。这个错没法在链接阶段绕过只能换一份带-fPIC编的静态库或者改用动态库。4.3 编译期就该注意的两个信号编译输出别直接刷过去有两类警告值得停下来看一眼。一类是废弃接口提示Json::Reader、Json::Writer在 1.9.x 里已经标了 deprecated你的老代码能编过但会刷一屏警告短期内不影响运行长期看是个迁移信号新代码统一用CharReaderBuilder和StreamWriterBuilder。另一类是 ABI 相关的警告比如某个头文件里std::string的传参出现note: parameter 1 types differ之类这种十有八九就是 2.2 节那个开关没对齐。我一般会在CXXFLAGS里加-Wall -Wextra让这类问题在编译期就暴露而不是留到运行期变成一个查不出来的段错误。5. 避坑与排查已编译 jsoncpp 最常翻的五处车下面这五条按出现频率排基本覆盖了预编译 jsoncpp 落地时能遇到的大半问题。每条都按“现象、原因、解决”写方便对着报错直接查。5.1 链接期一片 undefined symbol现象编译目标文件全部成功链接时刷出十几行undefined reference to Json::Value::asString[abi:cxx11]() const。原因多半是-ljsoncpp压根没生效。可能是路径不对-L指错了目录、可能库名不对有的包只有libjsoncpp.so.25没有libjsoncpp.so这个开发链接也可能是把-ljsoncpp写在了目标文件前面。解决先ls -l /usr/local/lib/libjsoncpp.so*确认软链接链完整缺了就用ln -sf补再pkg-config --libs jsoncpp看输出的路径对不对最后检查 Makefile 里LDLIBS的位置。三件事按顺序排一遍基本能定位。5.2 编译通过运行即崩在 Json::Value 相关调用现象程序启动正常读到配置那一行崩掉gdb 栈里停在std::string的构造或析构没有任何业务代码。原因C11 ABI 不匹配。头文件按一种 ABI 解释std::string的内存布局库按另一种 ABI 构造两边对同一个对象的认知不一样一传参就踩内存。解决nm -D … | grep -c __cxx11确认包里是哪种 ABI然后在工程里统一加-D_GLIBCXX_USE_CXX11_ABI0或1。加完必须全量重编只重编改了的那几个文件没用因为 ABI 是编译期决定的残留的旧目标文件依然是错的。5.3 静态库塞进 .so 时报 recompile with -fPIC现象想把 jsoncpp 静态链进自己的动态库链接器报relocation R_X86_64_32S against .rodata can not be used when making a shared object。原因这份.a编译时没开位置无关代码里面是绝对地址重定位塞进.so里没法在加载时重定位。解决优先换动态库必须静态的话只能重新编一份带-fPIC的静态库。检查方法是readelf -r libjsoncpp.a | grep R_X86_64_32如果这类重定位大量出现就别硬试了。这个坑没有后悔药只能换包。5.4 项目里躺着两份 jsoncpp现象链接时出现multiple definition或者更诡异的情况 —— 明明改了配置程序行为没变加日志发现用的是另一个版本的库。原因系统包管理器装了一份/usr/local又装了一份-L的搜索顺序决定了先找到哪一份。头文件也可能来自第三处。头文件和库版本不一致是最容易出“行为诡异”的组合。解决ldd ./demo | grep jsoncpp看运行时实际加载的是哪个路径g -E -x c - #include json/json.h | grep json.h看实际用的是哪个头文件。两处路径必须来自同一个包。确认之后把不需要的那份从搜索路径里摘掉别指望链接器帮你选。5.5 Windows 上退出时崩溃、堆损坏现象MSVC 工程编译链接都过运行到程序退出时崩在ntdll或者free里调试版下提示堆损坏。原因CRT 版本不一致。Debug 工程链了 Release 版 jsoncpp两套 CRT 各有各的堆内存在一边分配、在另一边释放直接炸。解决把工程配置和库的配置对齐全用 Release 或者全用 Debug 跑。如果包里只有一种就把自己的工程配置改成一致的那种跑测试别混着来。这类问题在 Linux 上少见因为 ABI 层面就卡死了Windows 上因为能链上所以更难发现。6. 装完怎么验证一个二十行的读写程序 三条检查命令安装这件事光“编译过了”不算完。我判断环境是否真的落地标准是能跑一个同时覆盖解析和序列化的小程序再把运行时依赖查一遍。下面这段二十行的代码就够用解析一段 JSON、改一个字段、再输出回去把CharReaderBuilder和StreamWriterBuilder两条路径都走一遍。#include json/json.h #include iostream #include sstream int main() { const std::string raw R({name:demo,port:8080,tags:[a,b]}); Json::CharReaderBuilder reader_builder; Json::Value root; std::string errs; std::istringstream in(raw); // 1.9.x 的解析入口老代码里的 Json::Reader 已标记废弃但还能用 if (!Json::parseFromStream(reader_builder, in, root, errs)) { std::cerr parse failed: errs std::endl; return 1; } root[port] 9090; // 改一个已有字段 root[extra][enabled] true; // 顺便验证嵌套写入 Json::StreamWriterBuilder writer_builder; writer_builder[indentation] ; // 输出带缩进方便肉眼核对 std::cout jsoncpp JSONCPP_VERSION_STRING \n Json::writeString(writer_builder, root) std::endl; return 0; }JSONCPP_VERSION_STRING这个宏来自头文件打印出来的版本号就是你实际用的头版本和运行时库版本必须一致这是第一个检查点。parseFromStream返回 false 时errs里会有具体位置信息能直接定位是 JSON 语法问题还是库的问题。writer_builder[indentation]接受字符串参数写\t就输出制表符不设的话是紧凑格式。编译运行三步# 用 pkg-config 拼参数省得手写 -I 和 -L g -stdc11 main.cpp -o demo $(pkg-config --cflags --libs jsoncpp) ./demo # 关键检查运行时到底加载了哪一份 jsoncpp ldd ./demo | grep jsoncpp预期输出里第一行是版本号应该是 1.9.x后面是带缩进的 JSONport已经从 8080 变成 9090。ldd那一行必须指向你自己装的那份路径指向系统目录就说明 5.4 节那个坑还在。三条检查我一般一次性做完记在部署脚本里检查项命令通过标准头库版本一致运行./demo看首行打印版本与包内版本相同运行时依赖正确ldd ./demo | grep jsoncpp路径指向自己安装的目录符号确实存在nm -D --defined-only … | cfilt | grep CharReaderBuilder至少输出三行相关符号这三条里任何一条不过就先别往下接业务代码回头查对应章节。尤其是第二条很多人只验证了编译通过就往项目里搬结果部署到另一台机器上因为rpath没配、ldconfig没刷跑起来直接error while loading shared libraries那时候再回头补排查成本已经翻倍了。从那以后我每次往工程里丢预编译 jsoncpp 之前都强制走一遍nm看 ABI、file看架构、再跑这个二十行小程序三分钟的事能挡掉后面几个小时的玄学排查。这套习惯帮我省下的时间比预编译本身省下来的多得多。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →