简介:这份资源面向仍在使用 Visual C++ 6.0 的开发者,聚焦于在 VC6.0 工程中集成 JSONCPP 库并完成 JSON 解析与生成,重点解决中文字符读写时的乱码问题,适合需要维护老旧 C++ 项目或做接口数据交换的初中级开发者参考。压缩包共 72 个文件,约 3.77MB,以 h 头文件、cpp 源文件、inl 内联实现为主,另含 obj、sbr 等编译中间文件、工程配置与资源文件,以及可执行程序和使用说明文档,源码与工程结构一并保留,便于直接打开对照。已有 590 人学习下载。资源内含完整可编译的 VC6.0 工程案例,覆盖 JSON 读取、序列化与中文编码处理,并附测试通过的源码类使用说明文档,能帮助读者少走集成与编码配置的弯路,快速验证解析结果。
1. 老 VC6 工程里塞进 jsoncpp:一个中文乱码卡了三天的真实场景
接手一个维护了十几年的老工控上位机项目,编译器还是 VC6.0,界面用 MFC 画,数据要跟外部服务对接,对方发过来的是 JSON。这时候最直接的想法就是找个 JSON 库塞进去,jsoncpp 是绕不开的选择——轻量、纯头文件加少量 cpp、API 直白。但真把 jsoncpp 拖进 VC6 工程,编译报错、链接报错、解析出来的中文全是问号,一个比一个磨人。这份资源就是围绕「VC6.0 调用 jsoncpp」这个具体场景整理的全流程案例,重点解决两件事:老编译器怎么把 jsoncpp 编进去,以及解析中文时怎么防乱码。适合还在维护 VC6 遗留工程、又不得不处理 JSON 数据的开发者,新手照着能跑通,熟手能直接看关键改动点。
2. 环境与工程结构:VC6 里怎么把 jsoncpp 摆进去
2.1 为什么 VC6 调 jsoncpp 会翻车
VC6 的编译器是 1998 年的产物,对 C++ 标准的支持停留在很早期的阶段。jsoncpp 新版本大量使用std::string的现代用法、模板偏特化、long long、以及 C++11 的语法糖,直接扔进 VC6 基本是编译不过的。血泪经验是:不要拿最新版 jsoncpp 硬怼 VC6,要选对老编译器友好的版本分支,或者用社区已经做过兼容裁剪的源码。这份案例的价值就在于它把「哪个版本能编、要改哪几处」这件事固定下来了,而不是让你自己去试错。
另一个坑是字符集。VC6 默认工程是 MBCS(多字节字符集),而 jsoncpp 内部按 UTF-8 处理字符串。外部服务发来的 JSON 通常是 UTF-8 编码,解析出来直接std::string拿到的是 UTF-8 字节流,往 MFC 的CString或SetWindowText一塞,中文就变成乱码。这不是 jsoncpp 的 bug,是编码边界没处理好。
2.2 目录结构长什么样
一份能直接跑的 VC6 工程,目录一般这样组织:
JsonCppDemo/ ├── jsoncpp/ # jsoncpp 源码目录 │ ├── include/ │ │ └── json/ # 对外头文件 │ │ ├── json.h │ │ ├── reader.h │ │ ├── writer.h │ │ └── value.h │ └── src/ │ ├── json_reader.cpp │ ├── json_value.cpp │ └── json_writer.cpp ├── Demo.dsp / Demo.dsw # VC6 工程文件 ├── Demo.cpp # 主程序入口 ├── DemoDlg.cpp # MFC 对话框逻辑 └── StdAfx.h # 预编译头关键点:jsoncpp 的src下那几个 cpp 要直接加进 VC6 工程参与编译,而不是编成静态库再链接。VC6 的静态库链接在 Debug/Release 混用时容易出 LNK2001,直接源码编译最省心。头文件路径在工程设置里加jsoncpp/include,这样代码里写#include "json/json.h"就能找到。
2.3 工程配置的三个必改项
打开 VC6 的 Project → Settings,有三处必须动:
第一,C/C++ 选项卡里的 Preprocessor definitions,确认没有误加_UNICODE。如果工程是 MBCS,加了_UNICODE会导致CString和std::string转换时行为不一致。
第二,Code Generation 里的 Struct member alignment 保持默认 8 字节,不要为了省内存改成 1,jsoncpp 内部有依赖对齐的结构。
第三,Link 选项卡里如果之前链接过别的 JSON 库,把对应的 lib 去掉,避免符号冲突。
// StdAfx.h 里加这两行,保证 jsoncpp 头文件能正确包含 #include <string> #include <vector> #include <map>逻辑说明:jsoncpp 的value.h依赖 STL 容器,VC6 的预编译头如果没提前引入这些标准头,会在 jsoncpp 源码里报一堆「未定义模板」的错。参数说明:<map>是 jsoncpp 内部对象存储用的,<vector>是数组用的,缺一个都编不过。
3. 核心代码拆解:解析、取值、中文处理一条链路
3.1 最小可跑的解析入口
先看一段能直接放进DemoDlg.cpp的解析代码,这是整个案例的主干:
#include "json/json.h" #include <string> // 解析一段 UTF-8 编码的 JSON 字符串 bool ParseJson(const std::string& utf8Json, std::string& outName, int& outAge) { Json::Reader reader; Json::Value root; // parse 第二个参数默认 false,表示不收集注释 if (!reader.parse(utf8Json, root)) { // 解析失败时拿错误信息,方便定位 std::string err = reader.getFormattedErrorMessages(); return false; } // 取值前先判断类型,避免类型不符抛异常 if (!root["name"].isString() || !root["age"].isInt()) return false; outName = root["name"].asString(); outAge = root["age"].asInt(); return true; }逻辑说明:Json::Reader是解析器,parse把字符串转成Json::Value树。root["name"]这种下标访问在 key 不存在时会返回 null 值,所以取值前用isString()、isInt()做类型校验,这是老工程里最稳的写法。参数说明:utf8Json必须是 UTF-8 编码,如果源数据是 GBK,要先转码再传进来;outName拿到的也是 UTF-8 字节流,后面显示时要转成 MBCS。
3.2 中文乱码的根因与转码函数
乱码的本质是「UTF-8 字节流被当成 GBK 显示」。VC6 的 MFC 控件在 MBCS 工程下按当前代码页(简体中文是 936,即 GBK)解释字节,而 jsoncpp 给的是 UTF-8。解决办法是在边界处做一次转换:
#include <windows.h> // UTF-8 转 GBK(MBCS),用于把 jsoncpp 的结果显示到 MFC 控件 std::string Utf8ToGbk(const std::string& utf8Str) { if (utf8Str.empty()) return ""; // 第一步:UTF-8 -> 宽字符 int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), (int)utf8Str.size(), NULL, 0); if (wlen <= 0) return ""; std::wstring wstr(wlen, L'\0'); MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), (int)utf8Str.size(), &wstr[0], wlen); // 第二步:宽字符 -> GBK int glen = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), (int)wstr.size(), NULL, 0, NULL, NULL); if (glen <= 0) return ""; std::string gbkStr(glen, '\0'); WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), (int)wstr.size(), &gbkStr[0], glen, NULL, NULL); return gbkStr; }逻辑说明:走「UTF-8 → UTF-16 宽字符 → GBK」两段转换,中间用宽字符做桥梁,这是 Windows 上最可靠的转码路径。参数说明:CP_UTF8指定源编码,CP_ACP指定目标为系统默认代码页(简体中文下就是 GBK);两次调用MultiByteToWideChar,第一次传 NULL 拿长度,第二次才真正转换,这是标准的两段式用法。注意wstr用std::wstring而不是wchar_t数组,避免长度算错。
3.3 组装 JSON 并输出中文
解析只是半条链路,回写 JSON 时同样要处理编码。jsoncpp 的Json::FastWriter输出的是 UTF-8,如果直接发给外部服务没问题,但要存到本地 GBK 文件就得先转:
std::string BuildJson(const std::string& gbkName, int age) { Json::Value root; // 入参是 GBK,先转 UTF-8 再塞进 jsoncpp root["name"] = GbkToUtf8(gbkName); root["age"] = age; Json::FastWriter writer; std::string utf8Out = writer.write(root); // 如果对端要 GBK,这里再转回去 return Utf8ToGbk(utf8Out); }逻辑说明:GbkToUtf8是Utf8ToGbk的反向实现,把CP_UTF8和CP_ACP对调即可。FastWriter输出紧凑格式,没有换行和缩进;如果要给人看,换成StyledWriter。参数说明:writer.write返回的字符串末尾带换行符,拼接到协议里时注意去掉。
3.4 数组和嵌套对象的遍历
实际数据很少是扁平的,遍历数组是高频操作:
// 遍历 JSON 数组,把每个元素的某个字段收集起来 bool CollectIds(const Json::Value& root, std::vector<int>& ids) { if (!root.isArray()) return false; for (Json::ArrayIndex i = 0; i < root.size(); ++i) { const Json::Value& item = root[i]; if (item.isObject() && item["id"].isInt()) ids.push_back(item["id"].asInt()); } return true; }逻辑说明:Json::ArrayIndex是 jsoncpp 定义的数组下标类型,别用int硬写,老版本里它是unsigned int,混用会有符号比较警告。参数说明:root.size()返回元素个数,root[i]越界会返回 null 值而不是崩溃,但还是要先判isArray()。
4. 避坑与排查:VC6 + jsoncpp 的五个高频翻车点
4.1 编译报错「无法识别 long long」
现象:jsoncpp 源码里出现long long相关报错,VC6 直接不认这个类型。
原因:VC6 编译器不支持long long,这是 C99 才标准化的类型。
解决:在 jsoncpp 的兼容头里把long long替换成__int64,或者用宏定义#define long_long __int64做一层映射。这份案例的源码已经处理过,自己换版本时要重点检查json_value.cpp里的整数处理部分。
4.2 链接报错 LNK2001 找不到符号
现象:编译通过,链接时报一堆unresolved external symbol,指向 jsoncpp 的函数。
原因:jsoncpp 的 cpp 没加进工程,或者加进去了但没参与编译(VC6 里文件图标是灰色表示不编译)。
解决:在 FileView 里右键 jsoncpp 的 cpp 文件,确认「Compile」勾选;或者干脆把三个 cpp 拖进 Source Files 节点下。别用预编译库的方式,VC6 的库版本管理太容易出错。
4.3 解析成功但中文全是问号
现象:reader.parse返回 true,asString()拿到的内容在界面上显示为???。
原因:源 JSON 是 UTF-8,直接塞给 MFC 的CString按 GBK 解释。
解决:所有从 jsoncpp 出来的字符串,显示前统一走Utf8ToGbk;所有进 jsoncpp 的字符串,先走GbkToUtf8。把这两个函数封装成工具类,在数据边界处强制调用,别指望在中间某处「顺手转一下」。
4.4 Debug 能跑 Release 崩
现象:Debug 模式下解析正常,切到 Release 后程序在parse附近崩溃。
原因:VC6 的 Release 优化对 jsoncpp 里某些依赖求值顺序的代码不友好,加上std::string的引用计数实现在多线程下有隐患。
解决:Project Settings → C/C++ → Optimizations 里把优化级别降到 Default 或 Disable,先确认是不是优化引起的;如果是多线程访问同一个Json::Value,加临界区保护,jsoncpp 的 Value 不是线程安全的。
4.5 大 JSON 解析卡顿甚至栈溢出
现象:解析几 MB 的 JSON 时界面卡死,或者直接崩在递归里。
原因:jsoncpp 的Reader默认递归解析嵌套结构,VC6 默认栈空间只有 1MB,深层嵌套会爆栈。
解决:在链接选项里把栈大小调大(/STACK:4194304),或者把大 JSON 拆成多次小解析。另外Reader对象用完就释放,别在循环里反复 new。
5. 二次开发改造:把案例代码变成工程里能用的模块
5.1 封装成独立的 JSON 工具类
案例里的函数是散的,真正用到工程里要收拢成一个类,把编码转换和解析都包进去:
class CJsonHelper { public: // 从 GBK 字符串解析,内部自动转 UTF-8 static bool ParseFromGbk(const CString& gbkJson, Json::Value& root) { std::string utf8 = GbkToUtf8(std::string(gbkJson)); Json::Reader reader; return reader.parse(utf8, root); } // 取字符串字段并转回 GBK,直接给界面用 static CString GetGbkString(const Json::Value& node, const char* key) { if (!node[key].isString()) return _T(""); return CString(Utf8ToGbk(node[key].asString()).c_str()); } };逻辑说明:把「转码 + 解析」和「取值 + 转码」两个方向都封死,调用方不用关心编码细节。参数说明:ParseFromGbk接收CString是为了贴合 MFC 工程的调用习惯,内部转std::string再转 UTF-8;GetGbkString返回CString可以直接赋给控件。
5.2 改造时的验证清单
改完代码别急着提交,按这个顺序验一遍:
| 验证项 | 方法 | 预期结果 |
|---|---|---|
| 纯英文 JSON | 解析{"name":"abc"} | 正常取值 |
| 中文 JSON | 解析含中文的 UTF-8 串 | 界面显示正常中文 |
| 缺字段 | 解析{"age":1}取 name | 返回空,不崩溃 |
| 类型不符 | 对字符串字段调asInt | 先判类型,不抛异常 |
| 大数组 | 解析 1000 条记录 | 不卡顿、不爆栈 |
这张表是我每次改完编码相关代码必走的流程,尤其是「缺字段」和「类型不符」两项,线上事故多半出在这。
5.3 一个容易忽略的细节:BOM 头
外部服务发来的 UTF-8 文件有时带 BOM(EF BB BF),jsoncpp 的Reader遇到 BOM 会解析失败,报「语法错误」。处理办法是在解析前剥掉前三个字节:
std::string StripBom(const std::string& s) { if (s.size() >= 3 && (unsigned char)s[0] == 0xEF && (unsigned char)s[1] == 0xBB && (unsigned char)s[2] == 0xBF) return s.substr(3); return s; }这个坑我踩过一次,排查了半天以为是编码问题,结果是 BOM。从那以后我每次接外部 JSON 数据,都强制先走一遍StripBom再进Reader,宁可多一行代码也不赌对端不带 BOM。希望这份案例能帮你少走几天弯路,把老工程里的 JSON 链路一次跑通。
本文还有配套的精品资源,点击获取