简介:这份资源提供在 Windows 平台使用 VS2019 静态编译完成的 libjsoncpp 与 libjson-rpc-cpp 库,面向需要在 C++ 项目中集成 JSON 解析与 JSON-RPC 通信能力的开发者。相比网络上流传的同类静态编译包,它补齐了缺失的 libjsoncpp 文件,并采用 X64 架构,解决了 x86 版本无法直接使用的问题。压缩包共 355 个文件,约 20.82MB,包含 4 个 lib 库文件、3 个 sln 解决方案、4 个 vcxproj 工程文件,以及大量 cpp、h、inl 源码与 obj、pdb、tlog 等编译中间产物,另附 json、expected 测试数据与 py、sh 辅助脚本,便于直接引用或二次编译。已有 474 人学习下载。拿到后可直接将 lib 与头文件接入现有工程,也可基于 sln 工程自行切换 X64 或 x86 重新编译,省去从源码配置依赖、处理编译错误的繁琐过程,适合需要快速搭建 JSON-RPC 通信模块的中高级 C++ 开发者。
1. 拿到一份 VS2019 静态编译好的 jsoncpp 与 json-rpc,先别急着写业务
如果你在 Windows 上做 C++ 项目,尤其是那种要交付给客户、不能要求对方装一堆运行库的桌面工具,迟早会撞上 JSON 解析和 RPC 通信这两件事。jsoncpp 负责把 JSON 字符串和 C++ 对象来回倒腾,json-rpc 负责把函数调用包装成请求响应,两者凑一起,就是一个轻量级的进程间或跨机通信底座。问题在于,官方仓库默认给的是源码,你得自己配 CMake、选运行库、处理符号导出,一套下来半天没了。所以当我看到有人已经把 VS2019 静态编译好的 libjsoncpp 和 libjson-rpc 打包出来,第一反应是:这东西能不能直接塞进现有工程,省掉那套编译玄学。
这篇笔记就是围绕这个场景展开的。假设你手里已经拿到了编译产物,目录里躺着 .lib、.h 和可能的 .pdb,接下来怎么在 VS2019 里配、怎么验证、怎么避开静态链接常见的翻车点,我会一步步拆开讲。适合那些不想在构建系统上耗时间、只想赶紧把通信层跑通的一线开发者。如果你还在纠结要不要自己编,我也会说清楚什么情况下直接用现成的更划算。
2. 静态库选型与工程接入:为什么“编译好”不等于“能直接用”
2.1 静态编译到底解决了什么问题
动态链接的 jsoncpp 通常是一个 jsoncpp.dll 加一个导入库 jsoncpp.lib,运行时必须把 dll 放到 exe 旁边或系统路径里。静态编译则是把 jsoncpp 的目标代码直接塞进你的 exe,最终产出一个不依赖额外 dll 的可执行文件。对于交付给外部用户的工具,这一点很关键:你不需要写安装脚本去拷贝 dll,也不用担心对方环境里已经有另一个版本的 jsoncpp.dll 导致冲突。
但静态编译有个副作用:运行库的选择会被固化。VS2019 里有两个维度,一个是 Debug 还是 Release,另一个是 /MT 还是 /MD。/MT 表示静态链接 C 运行库,/MD 表示动态链接。如果编译好的 jsoncpp 用的是 /MT,而你的工程用的是 /MD,链接时会报一堆 LNK2005 重复定义或者 LNK4098 冲突。所以拿到静态库的第一件事,不是写代码,而是确认它的运行库模式。
常见做法是看库文件名或者附带说明。有些打包会写成 jsoncpp_mt.lib 和 jsoncpp_md.lib,有些则只给一个版本。如果没有任何标记,可以用 dumpbin 工具查一下依赖。打开 VS2019 的开发者命令提示符,运行:
dumpbin /directives jsoncpp.lib | findstr "DEFAULTLIB"如果输出里出现 LIBCMT,说明是 /MT;出现 MSVCRT,说明是 /MD。这个判断决定了你后续工程属性怎么设,搞错了就是几小时的链接错误。
2.2 在 VS2019 里配置头文件与库目录
假设你拿到的目录结构是这样的:include 文件夹下放 json 和 jsonrpc 的头文件,lib 文件夹下放 jsoncpp.lib 和 jsonrpc.lib。接入步骤不复杂,但顺序要对。
第一步,右键项目 → 属性 → C/C++ → 常规 → 附加包含目录,把 include 的路径加进去。注意要加到“所有配置”还是只加当前配置,取决于你是否需要 Debug 和 Release 共用。我一般会分别配,因为 Debug 和 Release 可能对应不同的库文件。
第二步,链接器 → 常规 → 附加库目录,把 lib 路径加进去。
第三步,链接器 → 输入 → 附加依赖项,填入 jsoncpp.lib 和 jsonrpc.lib。如果库有先后依赖,jsonrpc 依赖 jsoncpp,那么 jsonrpc.lib 要写在 jsoncpp.lib 前面,否则链接器可能找不到符号。
第四步,C/C++ → 代码生成 → 运行库,确保和静态库的编译模式一致。如果库是 /MT,这里就选“多线程 (/MT)”;如果是 /MD,就选“多线程 DLL (/MD)”。Debug 版本对应 /MTd 和 /MDd。
配置完成后,写一个最小验证程序:
#include <json/json.h> #include <jsonrpccpp/server.h> #include <iostream> int main() { Json::Value root; root["name"] = "test"; root["value"] = 42; Json::StreamWriterBuilder writer; std::string output = Json::writeString(writer, root); std::cout << output << std::endl; return 0; }这段代码只用了 jsoncpp 的序列化功能,目的是确认头文件和库能正常编译链接。如果这一步就报错,先别往下走,回到运行库配置检查。编译通过后运行,应该输出 {"name":"test","value":42}。如果输出正常,说明 jsoncpp 部分已经通了。
2.3 json-rpc 的额外依赖与初始化顺序
json-rpc 不是只有头文件就能跑的,它通常依赖一个网络传输层。常见的实现里,服务端和客户端需要你提供一个抽象类,比如 AbstractServer 和 AbstractClient,然后你自己用 socket 或者管道去实现读写。静态库只提供了协议编解码和调用分发,传输层要自己补。
这意味着接入 json-rpc 时,除了链接库,还要在代码里继承并实现那几个纯虚函数。一个典型的服务端初始化流程是:先创建 JsonRpcServer 实例,注册方法,然后调用 StartListening。客户端则是创建 JsonRpcClient,调用 CallMethod。
这里有个容易忽略的点:json-rpc 的静态库可能对 jsoncpp 的版本有要求。如果 jsoncpp 的 API 有变动,比如 Json::StreamWriterBuilder 替代了旧的 Json::FastWriter,而 json-rpc 内部还在用旧接口,链接时就会报未解析的外部符号。所以拿到打包好的库,最好先确认两者是同一批次编译的,不要混用不同来源的 jsoncpp 和 json-rpc。
3. 从零跑通一个 json-rpc 调用:服务端与客户端的最小闭环
3.1 定义接口与生成桩代码
json-rpc 的常见用法是先写一个接口描述,然后生成服务端和客户端的桩代码。但如果你拿到的静态库没有附带代码生成器,那就只能手写。手写也不复杂,核心是继承 JsonRpcServer 和 JsonRpcClient,然后在服务端注册方法名和对应的处理函数。
假设我们要实现一个加法服务,方法名叫 add,接收两个整数,返回它们的和。服务端代码大致如下:
#include <jsonrpccpp/server.h> #include <jsonrpccpp/server/connectors/httpserver.h> #include <json/json.h> #include <iostream> class AddServer : public jsonrpc::AbstractServer<AddServer> { public: AddServer(jsonrpc::AbstractServerConnector &connector) : jsonrpc::AbstractServer<AddServer>(connector) { // 绑定方法名 add 到本类的 add 函数 this->bindMethod("add", &AddServer::add); } void add(const Json::Value &request, Json::Value &response) { int a = request["params"][0].asInt(); int b = request["params"][1].asInt(); response["result"] = a + b; } }; int main() { jsonrpc::HttpServer httpServer(8383); AddServer server(httpServer); server.StartListening(); std::cout << "Server listening on port 8383" << std::endl; while (true) { /* 保持运行 */ } return 0; }这段代码里,bindMethod 把字符串 add 和成员函数关联起来。当客户端发来 {"method":"add","params":[1,2],"id":1} 时,框架会解析出方法名,找到对应的函数,把 params 传进去,然后把返回值塞进 response 的 result 字段。
参数说明:HttpServer 的构造参数是端口号,这里用 8383。StartListening 是阻塞的,所以后面跟了一个空循环保持进程存活。实际项目里可以用信号量或者事件循环来控制退出。
3.2 客户端调用与超时设置
客户端代码更短,核心是构造请求、发送、解析响应:
#include <jsonrpccpp/client.h> #include <jsonrpccpp/client/connectors/httpclient.h> #include <json/json.h> #include <iostream> int main() { jsonrpc::HttpClient httpClient("http://localhost:8383"); jsonrpc::Client client(httpClient); Json::Value params; params.append(1); params.append(2); Json::Value result = client.CallMethod("add", params); std::cout << "Result: " << result.asInt() << std::endl; return 0; }CallMethod 的第二个参数是 Json::Value 数组,对应 JSON-RPC 协议里的 params。返回值直接就是 result 字段的内容。如果服务端返回错误,CallMethod 会抛出 jsonrpc::JsonRpcException,所以生产代码里要加 try-catch。
超时设置容易被忽略。HttpClient 默认的超时可能很长,如果服务端没响应,客户端会卡住。可以在构造 HttpClient 之后调用 SetTimeout:
httpClient.SetTimeout(5000); // 单位毫秒这个 5000 不是随便写的,要根据你的网络环境和服务端处理时间来定。局域网内 3 到 5 秒足够,跨公网的话可能要 10 秒以上。设太短会误杀正常请求,设太长会让故障恢复变慢。
3.3 验证调用链是否真正走通
跑通的最小闭环是:先启动服务端,看到监听日志;再运行客户端,看到 Result: 3。如果客户端报连接拒绝,检查端口是否被占用或者防火墙是否拦截。如果服务端收到请求但返回错误,检查方法名是否拼写一致,参数个数是否匹配。
一个实用的调试技巧是在服务端的 add 函数里加一行日志,打印收到的原始 request。这样能区分是请求没到,还是到了但解析失败。jsoncpp 的 Json::Value 可以直接输出到 std::cout,格式虽然紧凑但足够定位问题。
如果一切正常,你会看到客户端输出 3,服务端输出请求日志。这时候可以试着把参数改成字符串,看看错误处理是否按预期抛出异常。这一步能验证静态库的异常机制是否完整,有些裁剪过的版本会把异常支持关掉,导致错误时直接崩溃。
4. 避坑与排查:静态链接 jsoncpp 和 json-rpc 的五个血泪经验
4.1 运行库不匹配导致 LNK2005 重复定义
现象:链接时满屏 LNK2005,说某个符号已经在 libcmt.lib 里定义过了。原因:你的工程用了 /MD,但静态库是 /MT 编译的,两套运行库的符号冲突。解决:统一运行库模式。如果库是 /MT,工程也改成 /MT;如果改不了工程,就去找 /MD 版本的库。不要试图用 /NODEFAULTLIB 去屏蔽,那样会引入更多奇怪的问题。
4.2 头文件顺序引发的宏冲突
现象:编译时报错说 Json::Value 不是类型名,或者某个宏被重定义。原因:jsoncpp 的头文件里可能定义了某些宏,和 Windows.h 或者其他第三方库冲突。解决:把 jsoncpp 的头文件放在 Windows.h 之前包含,或者在包含前先 #define JSON_USE_EXCEPTION 0 之类的开关。更稳妥的做法是尽量在 .cpp 里包含,不要放到预编译头里。
4.3 静态库缺少符号导致 LNK2019
现象:链接时提示某个 jsonrpc 的函数未解析。原因:静态库可能只编译了部分模块,或者你用的 API 在裁剪版里被去掉了。解决:用 dumpbin /symbols 查看库里的符号表,确认你要调用的函数是否存在。如果不存在,要么换一个完整的库,要么自己补编译缺失的模块。不要指望链接器能变出符号来。
4.4 Debug 与 Release 混用导致运行时崩溃
现象:Debug 模式下编译通过,但一运行就崩,崩在 jsoncpp 内部的内存分配上。原因:Debug 版工程链接了 Release 版的静态库,两者对堆的管理方式不同。解决:严格区分 Debug 和 Release 的库目录,在 VS 里用不同的配置分别指向。如果库只有一个版本,那就统一用那个版本对应的模式,不要混。
4.5 多线程环境下 Json::Value 的共享问题
现象:服务端并发处理请求时,偶尔崩溃或者返回乱码。原因:Json::Value 不是线程安全的,多个线程同时读写同一个 Value 对象会出问题。解决:每个线程用自己的 Json::Value 实例,不要跨线程共享。如果必须共享,加锁。另外,jsonrpc 的服务端框架本身可能不是线程安全的,需要确认它是否支持并发,不支持的话就在外层串行化。
5. 进阶技巧:用静态库做无依赖交付与性能验证
5.1 把静态库集成到安装包里的正确姿势
静态编译的最终目的是交付一个干净的 exe。但光有 exe 还不够,你还需要确认它真的不依赖任何第三方 dll。用 Dependencies 工具或者 dumpbin /dependents 查看 exe 的导入表,如果只看到 KERNEL32.dll、USER32.dll 这些系统库,说明静态链接成功了。如果还看到 jsoncpp.dll 或者 msvcp140.dll,说明运行库没静态进去,需要回到工程属性里把 /MD 改成 /MT,并重新链接所有依赖库。
对于 json-rpc 这种带网络功能的库,还要注意 Winsock 的依赖。ws2_32.lib 是系统库,静态链接没问题,但如果你用了 OpenSSL 做加密传输,OpenSSL 的静态库也要一起链进去,否则会缺符号。常见做法是把所有第三方库都编译成 /MT 静态版,然后统一链接。
5.2 用批量调用测吞吐与内存增长
静态库的性能和动态库没有本质区别,但少了 dll 加载的开销,启动会快一点。真正要关注的是内存增长。写一个循环,连续发一万次 add 调用,观察服务端进程的内存占用。如果内存持续上涨,说明有 Json::Value 或者连接对象没释放。
一个简单的压测客户端:
for (int i = 0; i < 10000; i++) { Json::Value params; params.append(i); params.append(i); try { Json::Value result = client.CallMethod("add", params); if (i % 1000 == 0) { std::cout << "Completed " << i << " calls" << std::endl; } } catch (const std::exception &e) { std::cerr << "Call failed at " << i << ": " << e.what() << std::endl; break; } }跑完之后看任务管理器里的内存曲线。如果稳定在一个值附近,说明没有泄漏。如果一直涨,检查服务端的 bindMethod 是否每次请求都创建了新对象,或者 HttpClient 是否复用了连接。jsonrpc 的 HttpClient 默认可能每次请求新建连接,改成复用会显著降低内存和延迟。
5.3 验证静态库的异常安全与错误码
静态库的异常行为需要单独验证。故意发一个不存在的 method,看客户端是抛异常还是返回错误码。再故意发一个参数类型错误的请求,看服务端是否崩溃。一个健壮的服务端应该捕获所有异常,返回 JSON-RPC 标准的错误对象,而不是直接挂掉。
我一般会在服务端的 add 函数里加一层 try-catch,把异常转成 response["error"]。这样即使内部出错,客户端也能收到结构化的错误信息,而不是连接断开。这个习惯在交付给客户的项目里尤其重要,因为客户不会看你的日志,他们只看接口返回。
最后说一个我自己的教训:不要因为静态库“能跑”就跳过版本记录。把库的编译日期、运行库模式、jsoncpp 版本号写进工程的 README 里。过半年再回来改代码,你会感谢当时记下这些信息的自己。希望帮到你。
本文还有配套的精品资源,点击获取