简介:这份资源提供在 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++ 项目,最让人头疼的往往不是业务逻辑,而是第三方库的编译与链接。jsoncpp 和 json-rpc 这两个库,一个负责 JSON 序列化,一个负责远程过程调用,组合起来能快速搭出一套本地服务通信框架。但很多人卡在第一步:源码编译报一堆模板错误,CMake 生成 VS 工程后配置又对不上,好不容易编出个 DLL,运行时还提示找不到入口点。静态编译好的 libjsoncpp 和 libjson-rpc 直接拿来用,省掉的就是这段最耗时的环境折腾。这篇文章面向的是需要在 VS2019 下快速集成 JSON 解析与 RPC 通信的 C++ 开发者,尤其是那些不想在构建系统上花超过半小时的人。我会把静态库的目录结构、VS2019 工程配置、参数设置、以及链接期和运行期的典型坑,按可复现的步骤讲清楚。
2. 拿到静态库先别急着 include:目录结构与 VS2019 工程配置的对应关系
2.1 静态库包里通常有什么,每个文件对应哪一步
一个典型的静态编译产物,目录结构不会太复杂,但每一项都有明确用途。假设你拿到的包解压后长这样:
jsoncpp-rpc-static/ ├── include/ │ ├── json/ │ │ ├── json.h │ │ ├── json-forwards.h │ │ └── ... │ └── jsonrpccpp/ │ ├── server.h │ ├── client.h │ ├── common.h │ └── ... ├── lib/ │ ├── x64/ │ │ ├── jsoncpp_static.lib │ │ └── jsonrpc_static.lib │ └── x86/ │ ├── jsoncpp_static.lib │ └── jsonrpc_static.lib └── bin/ └── (可能包含示例 exe,但静态库通常不需要)include/json/下是 jsoncpp 的全部头文件,核心是json.h,它会把json-forwards.h和value.h、reader.h、writer.h都包含进来。include/jsonrpccpp/下是 json-rpc 的头文件,server.h和client.h分别对应服务端和客户端,common.h里定义了JsonRpcException等公共类型。
lib/x64/和lib/x86/是平台区分的关键。VS2019 默认新建的 x64 工程必须链接 x64 目录下的.lib,如果误链了 x86 的库,链接器会报LNK1112: 模块计算机类型“X86”与目标计算机类型“x64”冲突。这个错误很直接,但新手容易忽略。
提示:静态库的
.lib文件通常比动态库的导入库大很多,因为里面包含了完整的实现代码。如果发现.lib只有几十 KB,那很可能是导入库而非静态库,需要确认包的性质。
2.2 VS2019 工程里必须改的四个配置项
打开 VS2019,新建一个空白的 C++ 控制台项目,然后按以下顺序配置。不要用“属性管理器”里的默认继承,直接改项目属性页,避免被全局配置干扰。
第一步,右键项目 → 属性 → 配置属性 → C/C++ → 常规 → 附加包含目录,添加:
$(ProjectDir)..\jsoncpp-rpc-static\include这里用相对路径是为了团队协作时路径不失效。如果包放在解决方案目录外,用绝对路径也行,但换机器就要改。
第二步,配置属性 → 链接器 → 常规 → 附加库目录,添加:
$(ProjectDir)..\jsoncpp-rpc-static\lib\$(Platform)$(Platform)是 VS 的宏,在 x64 配置下展开为x64,在 Win32 配置下展开为x86。这样一份配置能同时适配两个平台,不用手动切换。
第三步,链接器 → 输入 → 附加依赖项,填入:
jsoncpp_static.lib jsonrpc_static.lib顺序有讲究。json-rpc 依赖 jsoncpp,所以jsonrpc_static.lib应该放在jsoncpp_static.lib前面,或者至少保证链接器能解析到依赖。如果顺序反了,可能报LNK2019: 无法解析的外部符号,提示某个 jsoncpp 的函数找不到。
第四步,C/C++ → 代码生成 → 运行库,必须和静态库编译时使用的运行库一致。静态库通常用/MT(多线程)或/MTd(多线程调试)编译。如果你在 Debug 配置下用了/MDd,链接时会报LNK2038: 检测到“RuntimeLibrary”的不匹配项。解决办法是:Debug 配置设为/MTd,Release 配置设为/MT。
// 验证配置是否正确的测试代码 #include <json/json.h> #include <jsonrpccpp/server.h> #include <iostream> int main() { Json::Value root; root["name"] = "test"; root["version"] = 1; Json::StreamWriterBuilder writer; std::string output = Json::writeString(writer, root); std::cout << output << std::endl; return 0; }这段代码只用了 jsoncpp 的序列化功能,不涉及网络,适合做第一步验证。编译通过并输出{"name":"test","version":1},说明头文件路径和库链接都对了。如果编译报无法打开源文件 json/json.h,检查附加包含目录是否指向了include的上一级;如果链接报无法解析的外部符号 Json::Value::Value(),检查附加依赖项和运行库设置。
2.3 用代码验证 json-rpc 的静态链接是否完整
jsoncpp 验证通过后,再测 json-rpc。json-rpc 的静态库通常依赖网络库,在 Windows 上一般是 Winsock2。所以还需要在链接器 → 输入 → 附加依赖项里加上ws2_32.lib。
#include <jsonrpccpp/server.h> #include <jsonrpccpp/server/connectors/httpserver.h> #include <iostream> // 一个最简单的服务端,只注册一个方法 class TestServer : public jsonrpc::AbstractServer<TestServer> { public: TestServer(jsonrpc::AbstractServerConnector& conn) : jsonrpc::AbstractServer<TestServer>(conn) { this->bindAndAddMethod( jsonrpc::Procedure("sayHello", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1", jsonrpc::JSON_STRING, NULL), &TestServer::sayHello); } void sayHello(const Json::Value& request, Json::Value& response) { response = "Hello, " + request[0].asString(); } }; int main() { jsonrpc::HttpServer httpServer(8383); TestServer server(httpServer); server.StartListening(); std::cout << "Server started on port 8383" << std::endl; // 实际使用时需要阻塞主线程或另起线程 return 0; }这段代码创建了一个监听 8383 端口的 HTTP 服务端,注册了sayHello方法。编译时如果报无法解析的外部符号 __imp_WSAStartup,说明ws2_32.lib没加。如果报无法解析的外部符号 jsonrpc::HttpServer::HttpServer,说明jsonrpc_static.lib没链上或者版本不匹配。
注意:静态库版本的 json-rpc 可能不包含 HTTP 连接器的实现,有些包只提供
AbstractServerConnector的抽象接口,需要自己实现或额外链接。拿到包后先看include/jsonrpccpp/server/connectors/目录下有没有httpserver.h,如果没有,说明这个静态包只含核心库,不含连接器。
3. 从编译到运行:静态库链接期的参数细节与排错路径
3.1 运行库不匹配的三种表现和统一方法
运行库不匹配是静态链接最常见的坑。VS2019 的 C/C++ → 代码生成 → 运行库有四个选项:/MT、/MTd、/MD、/MDd。静态库如果编译时用了/MT,你的工程也必须用/MT,否则链接器会报错。
表现一:LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MT_StaticRelease”不匹配值“MD_DynamicRelease”。这是最明确的提示,直接改运行库即可。
表现二:LNK2005: _malloc 已经在 libcmt.lib 中定义。这是/MT和/MD混用时,两个运行库都提供了malloc的实现,链接器不知道用哪个。解决办法是统一运行库,或者用/NODEFAULTLIB排除其中一个,但后者容易引发其他问题,不推荐。
表现三:程序编译链接都通过,但运行时崩溃在malloc或free内部。这是最隐蔽的,因为跨运行库分配和释放内存会导致堆损坏。比如静态库内部new了一个对象,返回给你的代码delete,如果两边运行库不同,堆句柄不同,直接崩溃。统一运行库能根治这个问题。
统一方法:在项目属性 → C/C++ → 代码生成 → 运行库,Debug 选/MTd,Release 选/MT。同时检查所有依赖的静态库是否也是同样的设置。如果包里的.lib是用/MD编译的,那你的工程也得用/MD,但这样发布时就需要带 VC 运行库,失去了静态编译的部分意义。所以拿到包先确认它的运行库类型,可以用dumpbin /directives jsoncpp_static.lib查看。
3.2 链接顺序与符号冲突:jsoncpp 和 json-rpc 的依赖关系
静态链接器解析符号是从左到右的。如果jsonrpc_static.lib引用了jsoncpp_static.lib里的符号,那么jsonrpc_static.lib必须出现在jsoncpp_static.lib的左边或前面。在 VS2019 的附加依赖项里,顺序就是从上到下。
# 用 dumpbin 查看 lib 的依赖符号(在 VS 开发者命令提示符下运行) dumpbin /symbols jsonrpc_static.lib | findstr "Json"如果输出里大量出现Json::Value相关的未定义符号,说明 json-rpc 确实依赖 jsoncpp。此时附加依赖项应该写成:
jsonrpc_static.lib jsoncpp_static.lib ws2_32.lib如果顺序反了,链接器先处理jsoncpp_static.lib,发现没有引用它的符号,就跳过;再处理jsonrpc_static.lib,发现需要Json::Value,但 jsoncpp 已经处理过了,不会再回头找,于是报LNK2019。
另一种情况是符号冲突。如果工程里同时链接了动态库的导入库和静态库,比如jsoncpp.lib(导入库)和jsoncpp_static.lib,链接器会报LNK2005: 已经在 jsoncpp.lib 中定义。解决办法是只保留静态库,移除所有动态库的导入库。
3.3 用 dumpbin 和 depends 定位链接期与运行期问题
链接期问题用dumpbin最直接。在 VS2019 的“开发者命令提示符”下,进入 lib 目录:
# 查看 lib 是静态库还是导入库 dumpbin /headers jsoncpp_static.lib | findstr "machine" # 输出 x64 表示 64 位,x86 表示 32 位 # 查看 lib 里包含的目标文件 dumpbin /linkermember jsoncpp_static.lib | findstr "\.obj" # 查看 lib 的运行库指令 dumpbin /directives jsoncpp_static.lib | findstr "DEFAULTLIB"/directives的输出里如果看到DEFAULTLIB:LIBCMT,说明是/MT;看到DEFAULTLIB:MSVCRT,说明是/MD。这个信息决定了你的工程该用哪个运行库。
运行期问题用depends.exe(Dependency Walker)或者 VS 自带的dumpbin /dependents。静态链接的程序通常只依赖系统 DLL,如KERNEL32.dll、USER32.dll、WS2_32.dll。如果发现依赖了MSVCP140.dll、VCRUNTIME140.dll,说明运行库没静态链接进去,发布时需要带 VC 运行库,或者改回/MT重新编译。
提示:
dumpbin需要在 VS 开发者命令提示符下运行,普通 cmd 里可能找不到。VS2019 的开发者命令提示符在开始菜单里搜“Developer Command Prompt”就能找到。
4. 避坑:静态库集成时最容易踩的五条血泪经验
4.1 坑一:Debug 配置链接了 Release 版的静态库
现象:Debug 下编译链接通过,但运行时在std::string或Json::Value的析构函数里崩溃,调用栈显示_Container_base12::_Orphan_all之类的内部函数。
原因:Release 版的静态库用了/MT(无调试信息,优化过的堆管理),Debug 工程用了/MTd(带调试堆)。两者对std::string和 STL 容器的内存布局假设不同,跨库传递对象时堆操作错位。
解决:Debug 配置必须链接 Debug 版的静态库。如果包里的lib/x64/下只有一份.lib,用dumpbin /directives确认它是/MT还是/MTd。如果是/MT,那你的 Debug 工程也得改成/MT,但这样会失去调试堆的检查能力。更好的做法是找一份带 Debug 版的包,或者自己用源码编一份 Debug 静态库。
4.2 坑二:头文件包含顺序导致 json-rpc 编译报错
现象:#include <jsonrpccpp/server.h>之后,编译器报Json::Value 未定义或json/json.h 找不到。
原因:json-rpc 的头文件内部依赖 jsoncpp,但有些版本的server.h没有自动包含json/json.h,需要使用者先包含。如果先包含了 json-rpc 的头,再包含 jsoncpp,可能因为头文件保护宏或前置声明冲突导致失败。
解决:在包含 json-rpc 头文件之前,先包含 jsoncpp 的头:
#include <json/json.h> // 先包含 jsoncpp #include <jsonrpccpp/server.h> // 再包含 json-rpc #include <jsonrpccpp/client.h>如果还是报错,检查include/jsonrpccpp/common.h里是否有#include <json/json.h>。如果没有,手动在工程里加一个预编译头或者强制包含。
4.3 坑三:x64 工程误链 x86 静态库
现象:链接器报LNK1112: 模块计算机类型“X86”与目标计算机类型“x64”冲突,或者LNK4272: 库计算机类型“X86”与目标计算机类型“x64”冲突。
原因:附加库目录用了固定路径lib/x86,或者$(Platform)宏在某个配置下没展开成预期值。
解决:附加库目录写成$(ProjectDir)..\jsoncpp-rpc-static\lib\$(Platform),并确认 VS 的配置管理器里,x64 配置对应x64平台,Win32 配置对应Win32平台。如果工程是从 Win32 复制过来的,检查配置管理器里有没有正确新建 x64 配置。
4.4 坑四:静态库缺少连接器实现,运行时抛异常
现象:编译链接都通过,但运行到server.StartListening()时抛出jsonrpc::JsonRpcException,提示Connector not initialized或No connector available。
原因:静态包只包含了 json-rpc 的核心库,没有包含 HTTP 或 TCP 连接器的实现。HttpServer的构造函数可能是空的,或者链接的是抽象基类。
解决:检查include/jsonrpccpp/server/connectors/下是否有httpserver.h,以及lib/下是否有对应的连接器静态库(如jsonrpc_httpserver_static.lib)。如果没有,需要自己实现一个AbstractServerConnector的子类,或者换一个包含连接器的包。
4.5 坑五:多线程下 jsoncpp 的 StreamWriterBuilder 不是线程安全的
现象:单线程测试正常,多线程并发调用Json::writeString时偶尔崩溃或输出乱码。
原因:Json::StreamWriterBuilder和Json::CharReaderBuilder内部有静态或共享状态,多个线程同时使用同一个 builder 实例会竞争。
解决:每个线程用自己的 builder 实例,或者用局部变量:
std::string JsonToString(const Json::Value& root) { Json::StreamWriterBuilder writer; // 局部变量,每次调用新建 return Json::writeString(writer, root); }不要用全局的static Json::StreamWriterBuilder writer;,除非加锁。json-rpc 的服务端如果多线程处理请求,内部序列化响应时也要注意这一点。
5. 进阶:把静态库集成进现有工程的三个实用技巧
5.1 用属性表(.props)固化配置,换项目直接导入
每次新建工程都手动配附加包含目录、库目录、依赖项,容易漏。VS2019 的属性表可以把这些配置存成.props文件,新工程直接导入。
在“属性管理器”里右键项目 → 添加新项目属性表,命名JsonRpcStatic.props。双击打开,在“通用属性” → “用户宏”里定义:
JsonRpcRoot = $(ProjectDir)..\jsoncpp-rpc-static然后在 C/C++ → 常规 → 附加包含目录里填$(JsonRpcRoot)\include,链接器 → 常规 → 附加库目录填$(JsonRpcRoot)\lib\$(Platform),链接器 → 输入 → 附加依赖项填jsonrpc_static.lib;jsoncpp_static.lib;ws2_32.lib。
换项目时,把.props文件复制过去,在属性管理器里“添加现有属性表”即可。如果路径变了,只改JsonRpcRoot宏一处。
5.2 用 dumpbin 脚本一键检查静态库的平台和运行库
手动敲dumpbin命令麻烦,写个批处理脚本:
@echo off set LIB_PATH=%1 if "%LIB_PATH%"=="" ( echo Usage: check_lib.bat ^<path_to_lib^> exit /b 1 ) echo === Machine type === dumpbin /headers "%LIB_PATH%" | findstr "machine" echo === Runtime library === dumpbin /directives "%LIB_PATH%" | findstr "DEFAULTLIB" echo === Dependencies === dumpbin /dependents "%LIB_PATH%" 2>nul | findstr "\.dll"保存为check_lib.bat,在 VS 开发者命令提示符下运行check_lib.bat jsoncpp_static.lib,就能看到平台、运行库和依赖的 DLL。这个脚本在拿到新包时先跑一遍,能避免大部分配置错误。
5.3 静态库与动态库混用时的隔离策略
有些项目主体用动态库,但想在某些模块里静态链接 jsoncpp 和 json-rpc。这时要注意:如果动态库和静态库都提供了同名符号,链接器可能报冲突。
隔离策略是:把静态库的使用限制在一个独立的静态库或目标文件里,对外只暴露纯 C 接口或抽象接口。比如:
// json_wrapper.h - 对外暴露的纯 C 接口 #ifdef __cplusplus extern "C" { #endif char* json_serialize(const char* key, const char* value); void json_free(char* str); #ifdef __cplusplus } #endif// json_wrapper.cpp - 内部使用静态库 #include <json/json.h> #include <cstring> extern "C" char* json_serialize(const char* key, const char* value) { Json::Value root; root[key] = value; Json::StreamWriterBuilder writer; std::string output = Json::writeString(writer, root); char* result = new char[output.size() + 1]; std::strcpy(result, output.c_str()); return result; } extern "C" void json_free(char* str) { delete[] str; }这样,静态库的符号被限制在json_wrapper.cpp所在的编译单元里,不会泄漏到动态库的导出符号表。外部模块只调用json_serialize和json_free,不直接接触 jsoncpp 的类型。
注意:
json_free必须和json_serialize在同一个运行库下编译,否则new[]和delete[]跨运行库会崩溃。这也是为什么包装层要放在同一个静态库或同一个工程里。
5.4 验证静态链接是否彻底:用 dumpbin 检查最终 exe
最后一步,编译出 exe 后,用dumpbin /dependents your_app.exe检查依赖。如果输出里只有系统 DLL,没有MSVCP140.dll、VCRUNTIME140.dll、jsoncpp.dll、jsonrpc.dll,说明静态链接彻底成功。如果出现了MSVCP140.dll,说明运行库没静态链接,需要检查 C/C++ → 代码生成 → 运行库是否设成了/MT。
我自己的习惯是:每次拿到新的静态库包,先跑check_lib.bat看平台和运行库,再建一个最小测试工程验证编译链接,最后用dumpbin /dependents确认 exe 的依赖。这三步走完,基本不会在集成阶段翻车。静态库省去了编译源码的麻烦,但配置的细节一点都不能省,希望帮到你。
本文还有配套的精品资源,点击获取