简介这份资源面向仍在使用 Visual C 6.0 的开发者尤其是需要在老项目中处理 JSON 数据、又常被中文乱码困扰的 C 程序员。它以 jsoncpp-src-0.5.0 源码为基础演示了在 VC6.0 中集成 JSONCPP 并正确解析、生成含中文字符 JSON 的完整案例属于可直接复用的实战型参考。压缩包共 72 个文件约 3.77MB包含 24 个 h 头文件、9 个 cpp 源文件、6 个 inl 内联实现文件以及 dsp、dsw 工程文件、doc 使用说明、txt 必看提示和已编译的 exe 可执行文件覆盖从源码到可运行工程的完整链路。已有 590 人学习下载。读者可据此掌握源码集成方式、UTF-8 编码配置与中文防乱码处理思路并借助说明文档和测试案例快速验证解析与序列化结果减少在老版本编译环境中踩坑的时间。1. 老编译器遇上新数据格式VC6.0 调 JSONCPP 到底卡在哪如果你手里维护着一套 VC6.0 的工控上位机、老 MFC 工具或者产线测试软件最近突然被要求“把配置改成 JSON 下发”“把日志输出成 JSON 给新平台”那你大概率会经历一次血泪经验VC6.0 的 C 标准库残缺得让人怀疑人生而 JSONCPP 这种现代 C 库默认写法根本喂不进去。标题里说的“VC6.0 调用 JSONCPP 全案例”核心要解决的就是三件事——让 JSONCPP 在 VC6.0 下编译通过、能正确解析带中文的 JSON 字符串、并且不出现乱码。这不是一个“随便找个库 include 一下”的活VC6.0 对模板、命名空间、std::string的实现都有坑JSONCPP 的版本选择、字符集设置、编码转换每一步都能让你翻车。适合谁看还在用 VC6.0 维护存量项目、又必须对接 JSON 接口的嵌入式/工控/桌面端开发者。下面我按“先跑通最小解析、再处理中文、最后避坑”的顺序把可复现的路径讲清楚。2. 选对 JSONCPP 版本并让 VC6.0 编译通过2.1 为什么不能直接拿最新版 JSONCPP 塞进 VC6.0JSONCPP 从 0.x 到 1.x 的演进过程中大量使用了 VC6.0 不支持的 C 特性std::auto_ptr的替代、nullptr、变参模板、std::unique_ptr、以及更严格的std::string接口。VC6.0 的 STL 是 1998 年之前的实现std::string没有c_str()的 const 重载问题、没有std::istringstream的完整支持模板偏特化也残缺。常见做法是选 JSONCPP 0.6.0-rc2 或 0.5.0 这类老版本它们对 C98 的依赖更保守源码里没有nullptr和变参模板。我一般会直接找 0.6.0-rc2 的源码包因为它的json_value.cpp、json_reader.cpp、json_writer.cpp三个文件在 VC6.0 下改动量最小。不要用 1.x哪怕你手动改代码Json::Value内部的std::map迭代器在 VC6.0 下也会因为std::map的erase返回值问题编译失败。2.2 把 JSONCPP 源码加入 VC6.0 工程的步骤先建一个 Win32 Console Application 空工程把 JSONCPP 的include/json目录和src/lib_json下的.cpp文件加进来。VC6.0 的工程文件是.dsp直接 Add Files to Project 即可。关键一步在Project Settings - C/C - Preprocessor里加上JSONCPP_NO_STDINT_H和_MBCS。JSONCPP_NO_STDINT_H是因为 VC6.0 没有stdint.hJSONCPP 会尝试用__int64替代_MBCS是让 JSONCPP 内部按多字节字符集处理避免它默认走 Unicode 宽字符路径。然后需要手动改几处源码json_value.cpp里所有std::auto_ptr改成裸指针加手动 deletejson_reader.cpp里std::istringstream换成std::stringstream并补#include sstream。下面是一个最小可编译的工程配置片段放在stdafx.h之后// VC6.0 下 JSONCPP 最小配置头文件 #define JSONCPP_NO_STDINT_H // 禁用 stdint.h 依赖 #define _MBCS // 多字节字符集中文解析基础 #include string #include map #include vector #include sstream // VC6.0 需手动补否则 json_reader 报错 #include json/json.h // 路径按实际 include 目录调整 // 注意VC6.0 的 std::string 没有 c_str() 的 const 重载问题 // 但 JSONCPP 内部调用 c_str() 时可能返回非 const需在 json_value.cpp 里 // 把 const char* 强转去掉 const否则编译报 C2440。逻辑说明JSONCPP_NO_STDINT_H让 JSONCPP 用__int64定义Json::Int64VC6.0 能识别_MBCS保证Json::Value::asString()返回的是多字节字符串而不是宽字符。参数说明如果你的工程已经用了_UNICODE必须改成_MBCS否则 JSONCPP 内部std::string和std::wstring混用会直接编译失败。改完源码后编译顺序建议先单独编译json_value.cpp再编译json_reader.cpp最后编译json_writer.cpp这样报错定位更准。2.3 最小解析案例从字符串到 Json::Value编译通过后先跑一个不涉及中文的解析确认库本身能用。新建一个main.cpp写入以下代码#include stdafx.h #include json/json.h #include iostream int main(int argc, char* argv[]) { // 构造一个简单 JSON 字符串注意转义 const char* szJson {\name\:\test\,\value\:123,\flag\:true}; Json::Value root; Json::Reader reader; // parse 返回 bool失败时可通过 reader.getFormatedErrorMessages() 看原因 if (!reader.parse(szJson, root)) { std::cout parse error: reader.getFormatedErrorMessages() std::endl; return -1; } // 读取字段asString 默认返回 std::string std::string name root[name].asString(); int value root[value].asInt(); bool flag root[flag].asBool(); std::cout name name , value value , flag flag std::endl; return 0; }逻辑说明Json::Reader::parse接受const char*和Json::Value解析成功返回 true。root[name]返回Json::Value引用asString()内部调用std::string构造。参数说明szJson里的双引号必须转义VC6.0 对字符串字面量长度有限制超过 16KB 的 JSON 建议用文件读取。如果parse失败getFormatedErrorMessages()会返回带行号的错误信息但 VC6.0 下这个函数返回的std::string可能因为std::ostringstream问题显示不全可以改用reader.getFormatedErrorMessages().c_str()打印。这一步跑通后说明 JSONCPP 在 VC6.0 下已经能用了接下来处理中文。3. 中文解析与防乱码从字节流到正确显示3.1 中文乱码的根因VC6.0 的字符集与 JSONCPP 的编码假设VC6.0 默认使用多字节字符集MBCS中文 Windows 下代码页是 GBK936。JSONCPP 内部把字符串当作字节流处理Json::Value::asString()返回的std::string里存的是原始字节。如果你从网络或文件读到的 JSON 是 UTF-8 编码直接asString()得到的是 UTF-8 字节而 VC6.0 的std::cout或 MFC 控件按 GBK 解释就会显示乱码。反过来如果你把 GBK 字符串直接塞进Json::Value再toStyledString()输出JSONCPP 不会做任何转码输出到 UTF-8 环境也会乱。所以“防乱码”的本质是在 JSONCPP 边界做一次编码转换要么统一转成 GBK 再显示要么统一转成 UTF-8 再传输。我一般会在解析后立刻把std::string从 UTF-8 转成 GBK写入时再转回 UTF-8。3.2 手写 UTF-8 与 GBK 互转函数VC6.0 可用VC6.0 没有std::codecvt但可以用 Windows APIMultiByteToWideChar和WideCharToMultiByte做中转。下面两个函数放在stdafx.h之后直接调用#include windows.h #include string // UTF-8 转 GBK std::string Utf8ToGbk(const std::string strUtf8) { if (strUtf8.empty()) return ; // 先 UTF-8 转宽字符 int nWideLen MultiByteToWideChar(CP_UTF8, 0, strUtf8.c_str(), -1, NULL, 0); if (nWideLen 0) return ; wchar_t* pWide new wchar_t[nWideLen 1]; MultiByteToWideChar(CP_UTF8, 0, strUtf8.c_str(), -1, pWide, nWideLen); pWide[nWideLen] L\0; // 宽字符转 GBK int nGbkLen WideCharToMultiByte(CP_ACP, 0, pWide, -1, NULL, 0, NULL, NULL); if (nGbkLen 0) { delete[] pWide; return ; } char* pGbk new char[nGbkLen 1]; WideCharToMultiByte(CP_ACP, 0, pWide, -1, pGbk, nGbkLen, NULL, NULL); pGbk[nGbkLen] \0; std::string strGbk(pGbk); delete[] pWide; delete[] pGbk; return strGbk; } // GBK 转 UTF-8 std::string GbkToUtf8(const std::string strGbk) { if (strGbk.empty()) return ; int nWideLen MultiByteToWideChar(CP_ACP, 0, strGbk.c_str(), -1, NULL, 0); if (nWideLen 0) return ; wchar_t* pWide new wchar_t[nWideLen 1]; MultiByteToWideChar(CP_ACP, 0, strGbk.c_str(), -1, pWide, nWideLen); pWide[nWideLen] L\0; int nUtf8Len WideCharToMultiByte(CP_UTF8, 0, pWide, -1, NULL, 0, NULL, NULL); if (nUtf8Len 0) { delete[] pWide; return ; } char* pUtf8 new char[nUtf8Len 1]; WideCharToMultiByte(CP_UTF8, 0, pWide, -1, pUtf8, nUtf8Len, NULL, NULL); pUtf8[nUtf8Len] \0; std::string strUtf8(pUtf8); delete[] pWide; delete[] pUtf8; return strUtf8; }逻辑说明MultiByteToWideChar第一个参数CP_UTF8表示源编码是 UTF-8CP_ACP表示当前系统 ANSI 代码页中文 Windows 下是 GBK。-1表示字符串以 null 结尾函数会自动计算长度。参数说明nWideLen和nGbkLen是转换后的字符数分配时加 1 给结尾符。注意 VC6.0 的new wchar_t[n]在 n 很大时可能失败建议对超过 1MB 的字符串分块处理。这两个函数没有依赖任何 C11 特性VC6.0 直接编译通过。3.3 解析中文 JSON 并正确输出的完整案例假设你从文件读到一个 UTF-8 编码的 JSON内容包含中文姓名和地址。用以下代码解析并转成 GBK 输出到控制台#include stdafx.h #include json/json.h #include fstream #include iostream #include string // 声明前面写的转换函数 std::string Utf8ToGbk(const std::string strUtf8); std::string GbkToUtf8(const std::string strGbk); int main(int argc, char* argv[]) { // 读取 UTF-8 JSON 文件 std::ifstream ifs(data.json, std::ios::binary); if (!ifs.is_open()) { std::cout open file failed std::endl; return -1; } std::string strJson((std::istreambuf_iteratorchar(ifs)), std::istreambuf_iteratorchar()); ifs.close(); Json::Value root; Json::Reader reader; if (!reader.parse(strJson, root)) { std::cout parse error: reader.getFormatedErrorMessages() std::endl; return -1; } // 读取中文字段此时是 UTF-8 字节 std::string strNameUtf8 root[name].asString(); std::string strAddrUtf8 root[address].asString(); // 转成 GBK 再输出避免控制台乱码 std::string strNameGbk Utf8ToGbk(strNameUtf8); std::string strAddrGbk Utf8ToGbk(strAddrUtf8); std::cout name: strNameGbk std::endl; std::cout address: strAddrGbk std::endl; // 如果要写回 JSON先把 GBK 转回 UTF-8 Json::Value outRoot; outRoot[name] GbkToUtf8(strNameGbk); outRoot[address] GbkToUtf8(strAddrGbk); Json::StyledWriter writer; std::string strOut writer.write(outRoot); std::ofstream ofs(out.json, std::ios::binary); ofs.write(strOut.c_str(), strOut.size()); ofs.close(); return 0; }逻辑说明std::ifstream以二进制模式读取避免 VC6.0 文本模式把\r\n转换导致 JSON 解析失败。std::istreambuf_iterator在 VC6.0 下可用但需要#include iterator。参数说明data.json必须是 UTF-8 无 BOM 格式如果带 BOMJSONCPP 会在第一个字符处报错需要手动跳过前三个字节。StyledWriter输出的字符串是 UTF-8写文件时用二进制模式否则 VC6.0 会插入\r。这一步跑通后中文解析和输出就不会乱码了。4. 避坑与排查VC6.0 JSONCPP 的 5 个典型翻车现场4.1 编译报错 C2440无法从 const char* 转换为 char*现象编译json_value.cpp时大量报 C2440指向c_str()返回值。原因VC6.0 的std::string::c_str()返回const char*但 JSONCPP 老版本内部有些函数参数是char*没有 const 修饰。解决在json_value.cpp里找到报错行把c_str()强转成(char*)或者把函数参数改成const char*。注意不要全局替换只改报错的那几处。4.2 解析中文返回空字符串或问号现象root[name].asString()返回空或???。原因JSON 文件本身是 GBK 编码但 JSONCPP 按 UTF-8 解析遇到 GBK 双字节会当成非法 UTF-8 序列解析失败或截断。解决统一把 JSON 源文件转成 UTF-8 无 BOM或者用GbkToUtf8先把整个 JSON 字符串转成 UTF-8 再parse。我一般会在读取文件后立刻判断编码如果是 GBK 就先转。4.3 输出到 MFC 编辑框仍然乱码现象控制台正常但SetWindowText到 MFC 编辑框显示乱码。原因MFC 编辑框在_MBCS下期望 GBK但你传入的是 UTF-8 的std::string。解决在SetWindowText之前调用Utf8ToGbk并且确保SetWindowText的参数是CString时用CString(strGbk.c_str())构造。如果编辑框是 Unicode 的则不要转 GBK直接CString(strUtf8.c_str())但需要工程支持_UNICODE而 JSONCPP 在_UNICODE下编译又是另一个坑建议统一用_MBCS。4.4 大 JSON 解析时程序崩溃或内存泄漏现象解析超过 1MB 的 JSON 时 VC6.0 程序无响应或退出。原因VC6.0 的std::string在频繁拼接时性能极差JSONCPP 内部大量使用std::string拼接导致内存碎片和栈溢出。解决把 JSON 拆成多个小对象分次解析或者改用Json::Reader::parse的流式接口如果版本支持。另外检查json_value.cpp里std::auto_ptr改成裸指针后是否在异常路径漏了 delete建议用std::auto_ptr的替代类封装。4.5 写入 JSON 时中文变成 \uXXXX 转义现象StyledWriter输出的中文变成\u4e2d\u6587。原因JSONCPP 默认对非 ASCII 字符做 Unicode 转义这是符合 JSON 标准的但有些下游系统不认。解决在json_writer.cpp里找到writeString函数把转义逻辑改成直接输出原始字节。具体是注释掉if (c 0x20 || c 0x7f)分支里的sprintf转义直接*s c。注意这样输出的 JSON 仍然是 UTF-8 字节下游按 UTF-8 解析即可。5. 进阶把 JSONCPP 封装成 VC6.0 可复用的配置读写类5.1 封装目标与接口设计在 VC6.0 项目里你不可能每次解析都写一遍Reader和编码转换。我一般会封装一个CJsonConfig类提供LoadFromFile、GetString、SetString、SaveToFile四个方法内部自动处理 UTF-8/GBK 转换和 JSONCPP 的Json::Value生命周期。这样业务代码只需要config.GetString(name)不用关心底层编码。下面是一个精简版实现// JsonConfig.h #ifndef __JSON_CONFIG_H__ #define __JSON_CONFIG_H__ #include string #include json/json.h class CJsonConfig { public: CJsonConfig(); ~CJsonConfig(); bool LoadFromFile(const std::string strFilePath); bool SaveToFile(const std::string strFilePath); std::string GetString(const std::string strKey, const std::string strDefault ); void SetString(const std::string strKey, const std::string strValue); int GetInt(const std::string strKey, int nDefault 0); void SetInt(const std::string strKey, int nValue); private: Json::Value m_root; bool m_bDirty; }; #endif // __JSON_CONFIG_H__逻辑说明m_root是Json::Value对象默认构造为 nullLoadFromFile成功后变成 object。m_bDirty标记是否有修改SaveToFile时决定是否写盘。参数说明strKey支持a.b.c形式的嵌套路径需要在GetString里按.分割逐层查找。VC6.0 的std::string::find和substr可用但注意npos的判断。5.2 实现 LoadFromFile 与编码自动识别// JsonConfig.cpp #include stdafx.h #include JsonConfig.h #include fstream #include iterator // 声明编码转换函数 std::string Utf8ToGbk(const std::string strUtf8); std::string GbkToUtf8(const std::string strGbk); CJsonConfig::CJsonConfig() : m_bDirty(false) { } CJsonConfig::~CJsonConfig() { } bool CJsonConfig::LoadFromFile(const std::string strFilePath) { std::ifstream ifs(strFilePath.c_str(), std::ios::binary); if (!ifs.is_open()) return false; std::string strContent((std::istreambuf_iteratorchar(ifs)), std::istreambuf_iteratorchar()); ifs.close(); // 简单判断如果包含非 ASCII 字节且不是合法 UTF-8则按 GBK 转 // 这里简化处理先尝试直接解析失败再转 Json::Reader reader; if (!reader.parse(strContent, m_root)) { // 尝试 GBK 转 UTF-8 后再解析 std::string strUtf8 GbkToUtf8(strContent); if (!reader.parse(strUtf8, m_root)) { return false; } } m_bDirty false; return true; }逻辑说明先按原始字节解析如果失败再尝试 GBK 转 UTF-8。这样兼容两种编码的 JSON 文件。参数说明std::istreambuf_iterator在 VC6.0 下需要#include iterator且读取大文件时可能较慢建议加一个 1MB 的大小限制。reader.parse会清空m_root再填充所以不需要手动 reset。5.3 GetString 的嵌套路径查找与默认值处理std::string CJsonConfig::GetString(const std::string strKey, const std::string strDefault) { // 按 . 分割路径 std::vectorstd::string vecKeys; std::string strTemp strKey; std::string::size_type pos 0; while ((pos strTemp.find(.)) ! std::string::npos) { vecKeys.push_back(strTemp.substr(0, pos)); strTemp strTemp.substr(pos 1); } vecKeys.push_back(strTemp); // 逐层查找 Json::Value* pNode m_root; for (size_t i 0; i vecKeys.size(); i) { if (!pNode-isObject() || !pNode-isMember(vecKeys[i])) { return strDefault; } pNode ((*pNode)[vecKeys[i]]); } if (!pNode-isString()) return strDefault; // 返回 GBK 编码方便 MFC 显示 return Utf8ToGbk(pNode-asString()); }逻辑说明vecKeys存储分割后的路径段pNode从m_root开始逐层下钻。isMember检查键是否存在isObject确保当前节点是对象。参数说明strDefault在路径不存在或类型不匹配时返回。返回前调用Utf8ToGbk因为 JSON 内部是 UTF-8而 VC6.0 业务层通常用 GBK。如果业务层需要 UTF-8可以再加一个GetStringUtf8方法。5.4 验证封装类一个完整的读写测试写一个测试函数加载 JSON、读取中文、修改后再保存验证整个链路void TestJsonConfig() { CJsonConfig config; if (!config.LoadFromFile(config.json)) { std::cout load failed std::endl; return; } // 读取中文GetString 内部已转 GBK std::string strName config.GetString(user.name, unknown); std::cout name: strName std::endl; // 修改并保存 config.SetString(user.name, 测试用户); config.SetInt(user.age, 30); if (config.SaveToFile(config_out.json)) { std::cout save ok std::endl; } }逻辑说明SetString内部需要把 GBK 转回 UTF-8 再存入Json::ValueSaveToFile用StyledWriter输出。参数说明config.json和config_out.json都用二进制模式读写。测试时先准备一个包含{user:{name:张三,age:25}}的 UTF-8 文件运行后检查config_out.json是否中文正常。如果输出正常说明封装类可用。5.5 一个我常犯的错误忘记处理 BOM最后说一个我踩过的坑UTF-8 文件带 BOM 时JSONCPP 解析会在第一个字符报错错误信息是* Line 1, Column 1 Syntax error: value, object or array expected。我一开始以为是 JSON 格式问题查了半天才发现是 BOM。解决办法是在LoadFromFile里读取内容后判断前三个字节是否为0xEF 0xBB 0xBF如果是就strContent strContent.substr(3)。这个判断要放在解析之前且对 GBK 文件不生效。现在我的习惯是所有 JSON 文件保存时都选“UTF-8 无 BOM”并且在代码里加一行 BOM 检测作为后悔药。希望帮到你。本文还有配套的精品资源点击获取