ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

VC6.0调用JSONCPP中文乱码解决方案:UTF-8与GBK转换实战

VC6.0调用JSONCPP中文乱码解决方案:UTF-8与GBK转换实战 简介面向VC6.0开发者的JSONCPP集成与中文防乱码完整案例包基于jsoncpp-src-0.5.0源码演示在Visual C 6.0的Win32控制台与对话框工程中如何配置、编译和调用JSONCPP重点解决中文解析与序列化时常见的乱码问题适合需要维护旧版C项目或正在学习JSON数据格式处理的初中级开发者。资源内含可直接导入的VC6.0工程文件.dsp/.dsw、全部核心源码.h/.cpp/.inl、编译中间文件以及已生成的exe示例并附有《【重要】VC6.0 测试通过的JSONCPP源码类使用说明.doc》、“必看.txt”和ReadMe等文档从编码设置、源码集成到运行验证给出完整说明。压缩包共72个文件涵盖头文件、C实现、内联模板、对话框资源、调试信息及多种说明文档整体仅3.77MB轻量易用。目前已有590人学习浏览适合在老旧VC6.0环境中快速接入JSONCPP并实现中文兼容的C开发者和遗留项目维护者。通过对照案例工程与文档可以直接获得可运行的解析中文JSON测试程序、常见错误排查思路和编码处理技巧减少反复试错成本。1. VC6.0 调用 JSONCPP老编译器里为什么也在折腾 JSON 中文编码VC6.0 调用 JSONCPP 做 JSON 解析最折磨人的不是库本身而是中文防乱码。系统还是老的机器跑着 XPVC6.0 的界面黄得像上个世纪——但项目还得继续维护对面新系统的接口也还是 JSON。问题就出在字符集上VC6.0 默认的源代码和字符串都按 GBK 处理而 JSONCPP 解析出来的字符串一律是 UTF-8 字节两边不对齐中文就变成“鈥濄涓”这类鬼字样。这不是 JSONCPP 的锅是 VC6.0 的字符集和 JSON 标准之间的冲突。这篇文章把 VC6.0 环境下调用 JSONCPP 的完整方案讲透怎么选库、怎么加进工程、怎么解析中文、怎么生成中文 JSON以及 5 个最常见的报错和乱码教训。适合维护老项目、要把历史 C 程序接入新 JSON 接口、又不想升级编译环境的开发者。照着复现能少走一个月的弯路。2. 编译准备把 JSONCPP 源文件塞进 VC6.0 工作区的两种接法2.1 选哪个版本老版本源码包优先VC6.0 是 1998 年的产品对 C 标准的支持停在 C98 甚至更早。最新版 JSONCPP 用了大量 C11 特性VC6.0 编译直接就是一堆“fatal error C1001”或者“语法错误”。所以下载 jsoncpp 库的时候目标不是 master 分支而是 0.x 系列的老版本比如 0.6.0 这类。常见做法是去 SourceForge 的历史版本页或者 GitHub 仓库的 Tags 列表里找旧标签不要用所谓的“最新稳定版”。老版本源码包目录很清楚include/json/下放头文件src/lib_json/下是三个.cpp文件没有 CMake 依赖也省去了生成库的麻烦。另一个重要理由老版本 JSONCPP 不依赖 Boost只用了标准库和 Windows API对 VC6.0 的模板支持比较友好。新版源码里出现了std::unique_ptr、std::move、std::is_constructible这些东西VC6.0 根本不认识。也有同行用“jsoncpp-master 改一版硬编”的做法但结果一般是改了几十个编译错误最后连自己加的改动在哪都找不到。我一般不建议这么干VC6.0 不是用来编译现代 C 的硬上不划算。2.2 把源码加入 VC6.0 工程的最小步骤拿到源码包后我这里给出最踏实的一套操作不生成库直接把源码编进你的主工程。第一步把 jsoncpp 目录拷贝到你的工程目录下比如丢到third_party/jsoncpp。第二步在 VC6.0 的 FileView 里右键“Source Files”选“Add Files to Folder”把src/lib_json下的json_reader.cpp、json_writer.cpp、json_value.cpp加进去。第三步打开 Project - Settings - C/C - Preprocessor在“Additional include directories”里填上..\third_party\jsoncpp\include注意路径要配到包含json/json.h的那个目录。第四步确认 C/C - General 里 Debug 和 Release 的“Warning level”不要设成“Level 4”VC6 对模板实例化的警告很多设成 Level 3 就够省得刷屏。文件作用json_reader.cpp实现 Json::Reader负责把字符串或流解析成 Valuejson_writer.cpp实现 Json::FastWriter 和 Json::StyledWriter负责序列化json_value.cpp实现 Json::Value 的存储、索引、类型转换加完之后写一个最小测试验证编译和链接能通。#include stdio.h #include json/json.h int main() { const char* testJson {\code\:0,\msg\:\ok\}; Json::Reader reader; Json::Value root; if (reader.parse(testJson, root)) { // asInt 读取整数asString 读取字符串 int code root[code].asInt(); std::string msg root[msg].asString(); printf(code%d, msg%s\n, code, msg.c_str()); } else { printf(parse failed\n); } return 0; }注意这里没有中文就为了先跑通环境。reader.parse接受const char*内部会把字符串内容复制进Value所以不用担心testJson是常量导致生命周期问题。root[code]返回的是Json::Value的引用asInt()做类型转换如果键不存在返回默认值 0 而不是报错这是 JSONCPP 老版本的行为新手容易误判后面避坑章再细说。asString()返回std::string在 VC6.0 里务必.c_str()再传给printf千万别把std::string直接给%s那是未定义行为。2.3 用静态库方式接法适合多个工程共用如果公司里有好几个 VC6.0 工程都要调用 JSONCPP每个工程都塞三个.cpp会重复编译比较蠢。另一个接法是单独建一个 Win32 Static Library 工程把三个.cpp编译成一个jsoncpp_vc6.lib其他工程只链接这个 lib。具体操作在 VC6.0 里新建工程选“Win32 Static Library”把三个.cpp加进去编译生成 lib。然后调用方在 Project - Settings - Link - Object/library modules 里输入jsoncpp_vc6.lib再在 Link - Input 的“Additional library path”里填 lib 所在目录。头文件路径还是要配方法跟 2.2 一样。这里有一个必须盯死的参数C/C - Code Generation - Use run-time library。VC6.0 里 Debug 工程默认是“Debug Multithreaded”对应/MTdRelease 默认是“Multithreaded”对应/MT。你用静态库方式时lib 工程和调用工程的这个选项必须完全一致。如果 lib 用/MTd调用工程用/MDd链接阶段几乎必然报 LNK2005后面避坑里细讲。源码直接加入主工程的方式就无所谓因为源文件跟着主工程的参数编译天然一致。2.4 VC6.0 编译 JSONCPP 的报错速查编译老版本 JSONCPP 时最常见的是下面几类错误现象原因处理fatal error C1083: Cannot open include file: json/json.h头文件路径没配检查 Additional include directorieserror C2065: string : undeclared identifier没包含string或编译器不自动带在代码里#include stringerror C2664: cannot convert parameter 2 from ...VC6 对隐式类型转换极严格显式调用.c_str()或强转类型warning C4251: ... needs to have dll-interface模板类跨 DLL 导出的警告不必理会这是 VC6 时代的老提示如果遇到编译错误发生在 jsoncpp 的源码内部比如说“C2910”或“Cannot open include file: config.h”那说明你下错版本了。老版本源码包有一个json/config.h在include/json目录下如果缺多半是从新版仓库里拷的头文件。直接回炉重找完整源码包别在残缺文件上浪费时间。这一步是“无错版”的地基地基歪了后面全白搭。3. 解析中文防乱码UTF-8 与 GBK 互转的两种落地写法3.1 乱码根源JSON 的 UTF-8 和 VC6.0 的 GBK 默认字符串JSON 标准 RFC 8259 明确规定JSON 文本必须用 UTF-8 编码。JSONCPP 严格遵守所以从 JSON 里解析出来的std::string就是 UTF-8 字节。而 VC6.0 在中文 Windows 下char默认按多字节字符集处理运行时的printf、Win32 的MessageBoxA都按系统代码页 936GBK解释字符串。两边一对接就出问题。举一个具体的例子汉字“张”的 UTF-8 编码是E5 BC A0三个字节这三个字节被 GBK 解码时E5 BC正好是一个生僻汉字“宷”的编码A0又是一个控制符最后显示成“宷”或空白后面几个字也连锁错位。反过来如果直接把 GBK 字符串塞给 JSONCPP生成的文件里写的是 GBK 字节不是合法 UTF-8服务器或其他下游程序读了会直接报非法字节序列。所以不要试图绕开编码问题。JSONCPP 内部只认 UTF-8业务显示层只认 GBK二者必须通过显式转码搭桥。谁在中间偷懒谁就等着在测试现场被领导围观乱码。3.2 UTF-8 转 GBK 的 Win32 转码函数最稳妥的做法是借助 Windows APIMultiByteToWideChar和WideCharToMultiByte。思路是先把 UTF-8 解码成 UTF-16 宽字符再把宽字符编码成 GBK。VC6.0 里wchar_t在 Windows 平台下就是 16 位正好对应 UTF-16 的代码单元。// utf8_gbk.h #ifndef UTF8_GBK_H #define UTF8_GBK_H #include string #include windows.h // UTF-8 转 GBK失败时返回原字符串避免返回空串造成二次问题 inline std::string Utf8ToGbk(const std::string utf8) { if (utf8.empty()) return ; // 第一步UTF-8 - UTF-16 int wLen MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, NULL, 0); if (wLen 0) return utf8; wchar_t* wBuf new wchar_t[wLen]; MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, wBuf, wLen); // 第二步UTF-16 - GBK int gbkLen WideCharToMultiByte(CP_ACP, 0, wBuf, -1, NULL, 0, NULL, NULL); if (gbkLen 0) { delete[] wBuf; return utf8; } char* gbkBuf new char[gbkLen]; WideCharToMultiByte(CP_ACP, 0, wBuf, -1, gbkBuf, gbkLen, NULL, NULL); std::string result(gbkBuf); delete[] gbkBuf; delete[] wBuf; return result; } #endif参数说明CP_UTF8是代码页 65001也就是 UTF-8CP_ACP是系统默认 ANSI 代码页中文系统就是 936。函数里用-1表示输入字符串以\0结尾让 API 自动处理结尾的终止符new wchar_t[wLen]得到的缓冲区容量足够放下 UTF-16 字符串及其\0结束符。这里手动new/delete是为了兼容 VC6.0 对std::wstring薄弱的模板实现虽然丑但稳定。调用方式就简单了Json::Reader reader; Json::Value root; const char* jsonText {\name\:\\\u5f20\\u4e09\}; // 等价于 UTF-8 的“张三” if (reader.parse(jsonText, root)) { std::string utf8Name root[name].asString(); // UTF-8 字节 std::string gbkName Utf8ToGbk(utf8Name); // 转成 GBK 用于显示 MessageBoxA(NULL, gbkName.c_str(), 结果, MB_OK); }注意root[name].asString()返回的std::string里的字节。转换后的gbkName传给 ANSI 版本的 Win32 API 才能显示中文别用MessageBoxW除非你的工程配置了 Unicode 字符集且把所有交互相都改成宽字符。大多数老工程还是多字节集用 A 系 API 最省事。3.3 GBK 转 UTF-8 的函数构造 JSON 前必须过一遍写 JSON 文件时方向相反。你的源代码保存成 GBK 编码字符串字面量“张三”在编译后就是 GBK 字节。直接赋给Json::ValueJSONCPP 会原封不动地把这些字节写进 JSON 文件文件就不是合法 UTF-8。解决办法是在赋值前把 GBK 转成 UTF-8。// 继续写在 utf8_gbk.h 里 inline std::string GbkToUtf8(const char* gbk) { if (gbk NULL || gbk[0] \0) return ; // 第一步GBK - UTF-16 int wLen MultiByteToWideChar(CP_ACP, 0, gbk, -1, NULL, 0); if (wLen 0) return gbk; wchar_t* wBuf new wchar_t[wLen]; MultiByteToWideChar(CP_ACP, 0, gbk, -1, wBuf, wLen); // 第二步UTF-16 - UTF-8 int utf8Len WideCharToMultiByte(CP_UTF8, 0, wBuf, -1, NULL, 0, NULL, NULL); if (utf8Len 0) { delete[] wBuf; return gbk; } char* utf8Buf new char[utf8Len]; WideCharToMultiByte(CP_UTF8, 0, wBuf, -1, utf8Buf, utf8Len, NULL, NULL); std::string result(utf8Buf); delete[] utf8Buf; delete[] wBuf; return result; }这里两个 API 的第一个参数和前面正好对调从 GBK 到 UTF-8第一步用CP_ACP第二步用CP_UTF8。很多人写反结果越转越乱。返回值里我写成return gbk;而不是return ;是因为如果某个字无法转换至少把原始内容传递出去调试时问题容易暴露不至于静默变成空字符串。赋值时这样写Json::Value root; root[name] GbkToUtf8(张三); // 源文件里是 GBK 字面量转成 UTF-8 再给 JSONCPP root[city] GbkToUtf8(北京);这样写出的 JSON 文件会是标准 UTF-8不依赖 JSONCPP 的内部编码也不依赖 Windows 的代码页开关。3.4 什么时候转两条边界法则核心原则可以压缩成两句话进入 JSONCPP 之前必须是 UTF-8从 JSONCPP 出来转成 GBK 再交给 Win32 控件或控制台。这叫“内部保持 UTF-8外部按 GBK 落地”。具体到代码里我习惯把转码调用放在两个地方一是从Json::Value取字符串后、准备printf或MessageBoxA之前调用Utf8ToGbk二是准备要给Json::Value赋值前把 GBK 字符串先过GbkToUtf8。千万不要尝试改 JSONCPP 源码让它内部用 GBK 存储字符串。JSONCPP 的序列化、\uXXXX解析、键名排序全部建立在 UTF-8 字节流上你改了存储writer 和 reader 全会错位那是无底洞。验证这个回路是否正确用一个完整的读写测试// 构造并写出 Json::Value v; v[user] GbkToUtf8(王工); std::string jsonText Json::FastWriter().write(v); FILE* f fopen(out.json, wb); // 二进制写避免换行被替换 fwrite(jsonText.c_str(), 1, jsonText.length(), f); fclose(f); // 读回来再显示 FILE* f2 fopen(out.json, rb); char buf[1024] {0}; fread(buf, 1, sizeof(buf) - 1, f2); fclose(f2); Json::Value r; Json::Reader().parse(buf, r); // 从 Value 拿的是 UTF-8显示前转 GBK printf(读出来%s\n, Utf8ToGbk(r[user].asString()).c_str());如果输出“王工”正常说明编码链路是通的。如果你看到“鐜嬪”说明Utf8ToGbk没有生效或读文件时已经解码出错。4. 全案例演练解析、生成、改键值、嵌套 JSON 的完整代码4.1 案例一读取 UTF-8 JSON 文件并打印中文第一个案例解决“从一个 JSON 文件里读数据”这个最普遍的需求。读取文件时我不用std::ifstream的迭代器构造字符串那个写法在 VC6.0 的旧标准库里有坑——窄字符流的istreambuf_iterator在实例化时容易崩溃或读到空内容。无错版的稳定做法是 C 风格fopenfread一字节一字节用量来读绝不多读。#include stdio.h #include string.h #include json/json.h #include utf8_gbk.h // 以二进制方式读入 UTF-8 文件完整传给 JSONCPP bool ReadJsonFile(const char* path, Json::Value root) { FILE* f fopen(path, rb); if (NULL f) return false; // 定位到文件末尾拿长度 fseek(f, 0, SEEK_END); long len ftell(f); fseek(f, 0, SEEK_SET); // 多给 1 字节保证字符串终止符合法 char* buf new char[len 1]; if (len 0) { size_t rd fread(buf, 1, len, f); buf[rd] \0; } else { buf[0] \0; } fclose(f); bool ok Json::Reader().parse(buf, root); delete[] buf; return ok; } int main() { Json::Value root; if (!ReadJsonFile(user.json, root)) { printf(读取或解析失败\n); return 1; } // asString 返回 UTF-8 字节 std::string utf8Name root[name].asString(); // 转成 GBK 再交给 MessageBoxA std::string gbkName Utf8ToGbk(utf8Name); MessageBoxA(NULL, gbkName.c_str(), 姓名, MB_OK); return 0; }这里fread的返回值rd可能小于len尤其是网络盘或特殊设备所以终止符一定要用buf[rd]而不是固定buf[len]。Json::Reader().parse(buf, root)是临时对象调用成员函数VC6.0 支持这种写法返回bool表示解析是否成功。解析成功后buf可以立刻释放因为Json::Value已经复制了内容。如果user.json是 Windows 记事本保存的 UTF-8 带 BOM 文件这个案例会失败原因和解决办法在避坑章 5.3 给出。4.2 案例二构造含中文的 JSON 并写回文件第二个案例是把程序里的配置或结果输出成 JSON 文件。先看关键代码重点在写文件前的中文转码。#include stdio.h #include json/json.h #include utf8_gbk.h bool SaveJsonFile(const Json::Value root, const char* path) { // FastWriter 输出紧凑格式StyledWriter 输出带缩进的格式 Json::StyledWriter writer; std::string output writer.write(root); // 已经是 UTF-8 字节流 FILE* f fopen(path, wb); // 二进制写避免 \n 被替换成 \r\n if (NULL f) return false; fwrite(output.c_str(), 1, output.length(), f); fclose(f); return true; } int main() { Json::Value root; root[name] GbkToUtf8(张三); // GBK 字面量转 UTF-8 root[age] 18; root[address][city] GbkToUtf8(北京); // 嵌套对象直接用 [] 创建 // 修改键值直接再赋一次 root[age] 19; if (SaveJsonFile(root, out.json)) { printf(保存成功文件编码为 UTF-8 无 BOM\n); } return 0; }Json::StyledWriter生成带缩进的文本方便人检查Json::FastWriter生成无空格的紧凑文本适合接口传输。老版本里FastWriter会把非 ASCII 字符转义成\uXXXX所以如果你用紧凑格式打开out.json看到的可能是name:\u5f20\u4e09这个不是乱码后面第 5 章会单独说怎么让它输出原始中文。root[address][city]这种方式会自动创建address这个嵌套对象不需要提前声明这是 JSONCPP 很方便的设计。修改键值就是重新赋值简单粗暴。写文件用wb而不是w很重要。文本模式下 VC6.0 的fwrite会把每一个\n替换成\r\nJSON 字符串里如果包含\n换行会导致字节数变化极端情况下破坏 UTF-8 序列。二进制模式完全不做转换原样写入。4.3 案例三解析嵌套数组并遍历键值嵌套数组也是高频需求。比如接口返回一个用户列表里面每个用户又有嵌套对象。代码如下int main() { const char* jsonText {\list\:[ {\name\:\\\u5f20\\u4e09\,\score\:88}, {\name\:\\\u674e\\u56db\,\score\:76} ],\total\:2}; Json::Reader reader; Json::Value root; if (!reader.parse(jsonText, root)) { printf(parse failed\n); return 1; } Json::Value list root[list]; // 拿到数组 int count (int)list.size(); // VC6 里 size() 返回无符号数先转 int for (int i 0; i count; i) { Json::Value item list[i]; // 取第 i 个元素 std::string name item[name].asString(); int score item[score].asInt(); printf(第 %d 个%s分数 %d\n, i 1, Utf8ToGbk(name).c_str(), score); } printf(总数%d\n, root[total].asInt()); return 0; }Json::Value list root[list];这里是一次拷贝。JSONCPP 老版本的Value拷贝是深拷贝数据量不大时没问题如果列表有几十万条建议用const Json::Value list root[list];避免拷贝开销。VC6.0 对size()的默认返回unsigned int循环变量用int会有 warning C4018我直接强转省得编译日志里全是黄条。printf里我用了Utf8ToGbk(name).c_str()。注意这里Utf8ToGbk返回一个临时对象.c_str()指针的有效期持续到 printf 调用结束所以这个写法是安全的。如果先保存再打印更稳妥但临时对象方式在单条语句里也成立。4.4 案例四跨文件调用的封装模块实际项目里 JSONCPP 的调用不会只在一个文件里发生。多个窗口、多个业务模块都要解析 JSON如果每个人都记得转码迟早有人忘记。所以做一个封装层把“从 Value 取字符串并转 GBK”变成唯一入口这是跨文件调用的正确姿势。// json_utils.h #ifndef JSON_UTILS_H #define JSON_UTILS_H #include json/json.h #include string bool LoadJsonUtf8(const char* filePath, Json::Value root); bool SaveJsonUtf8(const char* filePath, const Json::Value root); // 统一从 Value 里取字符串并转成 GBK 返回 std::string GetJsonStringAsGbk(const Json::Value root, const char* key); #endif// json_utils.cpp #include json_utils.h #include utf8_gbk.h #include stdio.h bool LoadJsonUtf8(const char* filePath, Json::Value root) { FILE* f fopen(filePath, rb); if (NULL f) return false; fseek(f, 0, SEEK_END); long len ftell(f); fseek(f, 0, SEEK_SET); char* buf new char[len 1]; size_t rd (len 0) ? fread(buf, 1, len, f) : 0; buf[rd] \0; fclose(f); // 去掉 UTF-8 BOM 头避免 jsoncpp 解析失败 if (rd 3 (unsigned char)buf[0] 0xEF (unsigned char)buf[1] 0xBB (unsigned char)buf[2] 0xBF) { memmove(buf, buf 3, rd - 3); buf[rd - 3] \0; } bool ok Json::Reader().parse(buf, root); delete[] buf; return ok; } bool SaveJsonUtf8(const char* filePath, const Json::Value root) { Json::StyledWriter writer; std::string output writer.write(root); FILE* f fopen(filePath, wb); if (NULL f) return false; fwrite(output.c_str(), 1, output.length(), f); fclose(f); return true; } std::string GetJsonStringAsGbk(const Json::Value root, const char* key) { if (!root.isMember(key)) return ; return Utf8ToGbk(root[key].asString()); }业务代码只关心键名不再关心底层编码#include json_utils.h int main() { Json::Value root; if (!LoadJsonUtf8(config.json, root)) { printf(加载失败\n); return 1; } std::string name GetJsonStringAsGbk(root, name); printf(姓名%s\n, name.c_str()); return 0; }这个封装有几个隐藏收益首先全项目只有GetJsonStringAsGbk这一个地方调用了Utf8ToGbk以后想换成别的转码实现只改一个文件。其次LoadJsonUtf8把 BOM 处理收进公共代码谁读文件都不会再踩首键解析失败的坑。最后isMember先判断键是否存在避免取到无效键后返回空字符串而不是崩溃。跨文件调用时务必让所有模块都 includejson_utils.h不要直接 includejson/json.h再各自转码不然封装就白做了。5. VC6.0 常见的 5 个 JSONCPP 调用避坑记录5.1 链接报错 LNK2005运行时库不一致现象编译全部通过链接时报一堆重复符号最常见的是_free、_malloc、__imp__...错误格式是LNK2005: _free already defined in LIBCMT.lib。Debug 和 Release 表现还不一样。原因JSONCPP 源文件或 lib 使用的运行时库和主工程不一样。VC6.0 的运行时库选项有单线程、多线程、多线程调试、多线程 DLL 四种不同选项对应不同的 CRT 链接方式。两个模块各用自己的堆链接器在解析 CRT 符号时就打架了。解决先搞清楚主工程是/MD还是/MT。在 Project - Settings - C/C - Code Generation - Use run-time library 里Debug 选“Debug Multithreaded DLL”Release 选“Multithreaded DLL”这是最常见组合。如果你用的是静态库方式把 jsoncpp 的 lib 工程也改成完全相同的选项。如果源码直接加入主工程这个问题自然消失。5.2 解析出来的中文变成“鈥濄涓”乱码现象用printf(%s, root[name].asString().c_str())直接输出或者MessageBoxA显示出现一排生僻字和空格比如“鈥濄涓”或“鏂囩”。原因asString()返回的是 UTF-8 字节而输出函数按 GBK 解释。UTF-8 三字节一字符GBK 两字节一字符错位后整个字符串面目全非。解决输出前必须经过Utf8ToGbk。这是最核心的一条记住“JSONCPP 不输出 GBK除非你转了”。另外不要用cout root[name]直接输出VC6.0 标准库的operator只认char*而且不会帮你转码结果一样乱。5.3 带 BOM 的文件首键解析失败现象Lodading JSON string with BOM.reader.parse返回false。但用命令行查看文件内容看起来没问题用其他语言解析也正常。原因Windows 记事本保存 UTF-8 文件时会在最前面写入EF BB BF三个字节这就是 BOM。JSON 标准不允许文件开头有 BOMJSONCPP 的解析器把EF BB BF当作非法字符处理第一行就报错整个解析直接失败。解决读文件后判断前三个字节如果是EF BB BF用memmove把后面的数据前移三个字节并更新终止符。代码已经写在 4.4 的LoadJsonUtf8里。自己写文件时不要加 BOM用二进制模式fwrite写的 JSON 天然无 BOM。如果客户拿来的是带 BOM 文件你的程序要有兼容能力这是老项目里常见的“隐性需求”。5.4 Debug 正常 Release 崩溃或者反过来现象Debug 编译运行一切正常Release 编译通过但运行到delete或函数返回时崩溃也有反过来的Release 好端端Debug 一跑就挂。原因两个模块使用的 CRT 不同比如 Debug 用了/MDdRelease 用了/MT。std::string在 VC6.0 里不是 POD内部有指针指向堆内存。如果 A 模块申请了内存B 模块释放而 A、B 分别链接不同版本的 CRT堆管理器和运行时错误处理逻辑不一致轻则崩溃重则内存损坏。解决最彻底的办法是把 JSONCPP 源码直接加入主工程让它和业务代码用同一套运行时库。如果非要用独立 liblib 工程和调用工程的 Use run-time library 必须一模一样。还要避免在 DLL 边界传递std::string如果 DLL 接口必须返回字符串用const char*加自定义释放函数。5.5 生成后的 JSON 里中文全是\u5f20\u4e09现象FastWriter写出out.json打开一看name:\u5f20\u4e09虽然不是乱码但甲方说“这不是我要的中文”要求文件里直接显示“张三”。原因JSONCPP 老版本出于安全考虑对非 ASCII 字符统一转义成\uXXXX。这是 JSON 标准允许的表示法任何标准 JSON 解析器都能正确还原。只是文件可读性差调试时看一眼都是转义序列烦。解决如果你坚持要原始中文可以修改json_writer.cpp里的appendQuotedString函数。找到类似“else if (i 0x80)输出原字符else转义成\\uXXXX”的代码把else分支改成直接输出当前字节else { // 原代码转成 \uXXXX 转义序列 out \\u; // ... 若干代码拼出四位十六进制 // 改成 out static_castchar(i); // 前提是输入必须是合法 UTF-8 字节流 }注意这么做的前提是进入Json::Value的字符串已经是合法 UTF-8否则文件中会出现非法字节。修改后FastWriter和StyledWriter输出时都会保留原始 UTF-8 中文字节。如果你不想改源码也可以保留转义终端程序解析后记忆但这方案在交付审核时很容易被打回。个人习惯是改一次源码写个注释以后一劳永逸。6. 把乱码问题一次性焊死自写 UTF8ToGBK 小工具与验证方法最后放一个我实战里必用的验证工具。当你拿不准一个字符串到底是 UTF-8 还是 GBK 时不要靠肉眼猜把十六进制字节打出来一眼就能分辨。#include stdio.h #include string.h // 打印字符串每个字节的十六进制用于判断编码 void PrintHex(const char* s) { if (s NULL) return; const unsigned char* p (const unsigned char*)s; while (*p) { printf(%02X , *p); p; } printf(\n); }判断规则很简单常用汉字的 UTF-8 编码第一个字节在E4到E9之间比如“测”是E6 B5 8BGBK 编码的汉字第一个字节在D0到D9之间比如“测”是B2 E2。如果你在输出里看到一串E6 B5 8B E8 AF 95那几乎可以肯定是 UTF-8看到B2 E2说明还是 GBK 原样。配合一个完整测试int main() { // 模拟“测试”两个字从 GBK 到 UTF-8 再回 GBK 的完整回路 std::string utf8 GbkToUtf8(测试); PrintHex(utf8.c_str()); // 期望 E6 B5 8B E8 AF 95 std::string gbk Utf8ToGbk(utf8); printf(显示%s\n, gbk.c_str()); // 期望“测试” return 0; }验证点有三个。第一PrintHex(utf8.c_str())的输出必须是E6 B5 8B E8 AF 95只要以E开头说明 UTF-8 转换对了。第二printf显示的是“测试”而不是乱码说明Utf8ToGbk转换正确控制台代码页也是正常的 GBK。第三把这个字符串用fwrite写进文件再用十六进制编辑器打开文件头不能有EF BB BF内容必须是E6 B5 8B E8 AF 95这就证明整个链路从内存到磁盘都干净了。另外有个控制台技巧在main开头调用SetConsoleOutputCP(CP_UTF8);然后直接printfUTF-8 字符串Windows XP 以上配合 TrueType 字体也能显示中文。但我不建议把它当常规方案因为你的代码越过了 GBK 层一旦输出到日志文件或对话框又绕回乱码那条路。验证阶段用一下可以生产代码里还是老老实实转 GBK。我处理这类老项目时固定动作是先把utf8_gbk.h放到公共头文件目录再写一个json_utils封装层业务代码绝对不允许直接碰Json::Value里的字符串。每次新同事抱怨乱码我就让他把十六进制打出来看三分钟定位问题。这套流程用在 VC6.0 上至少能减少 80% 的编码扯皮希望帮到你。本文还有配套的精品资源点击获取
返回列表