资讯动态

VC6.0下jsoncpp中文乱码解决与UTF-8/GBK转换实战

发布时间:2026/10/20 3:59:13 来源:尧图企业网站定制
简介一套面向 Visual C 6.0 开发者的 JSONCPP 集成与中文解析完整案例包解决老工程中解析含中文 JSON 容易乱码的痛点。资源基于 jsoncpp-src-0.5.0 源码集成了 json_reader、json_writer、json_value 等核心模块并针对 VC6.0 进行了工程配置提供可直接编译运行的 Win32 对话框程序示例方便在老旧 IDE 中快速上手。压缩包共 72 个文件以 h 头文件、cpp 源文件、inl 内联实现为主体附带 doc 使用说明文档、可执行 exe 演示程序以及少量编译中间文件整体仅 3.77MB体积小巧。已有590人学习下载特别适合正在维护或迁移 VC6.0 项目、需要为传统 MFC 或控制台程序接入 JSON 读写能力的开发者。资源内含完整工程源码、防乱码的 UTF-8 处理思路、详细的使用说明文档展示了中文字符从输入解析到界面显示的全过程照着配置即可避免编译报错与中文乱码问题大幅节省自行摸索时间。1. VC6.0里跑通jsoncpp先把中文乱码这个坎迈过去手头还压着VC6.0工程的人多半不是在怀旧而是产线代码动不得。我接到过一批老MFC维护需求界面是VC6搭的HTTP接口返回的却是JSON数据jsoncpp怎么都编不过去好不容易编过了解析中文又是一屏幕问号。这套组合的坑不在JSON本身而在旧编译器和新库之间隔着十几年的C标准以及UTF-8与本地GBK编码的两套字节流。这份jsoncpp调用全案例我按“可编译、可解析中文、不崩溃”三条线重新整理过适合给老工程补JSON能力的人直接照着建工程。它能让你少走我反复折腾版本选择和编码转换的那几周弯路。新工程当然可以用新编译器但如果是动不了的存量代码下面这套做法就是最稳的落地路径。2. 选对jsoncpp版本并编进VC6.0版本兼容是第一个玄学2.1 VC6.0对jsoncpp版本的隐形门槛VC6.0用的是上世纪90年代末的C标准模板、异常处理、标准库实现都不完整。jsoncpp新版本的头文件大量使用C11特性比如nullptr、auto、右值引用、移动构造这些在VC6的编译器眼里全是语法错误。我最早直接拿新版本源码往工程里拖编译报错铺天盖地C2059、C2065、C2143轮着来第一眼根本看不出跟JSON有什么关系。jsoncpp从1.0.0开始明确要求C11及以上真正能在VC6.0下安稳编译的只有0.6.0这个系列及更早的版本。0.6.0的代码风格还停留在老C98时代类设计、模板嵌套都比较克制VC6的编译器能啃下来。判断一个jsoncpp源码包适不适合VC6最简单的办法是打开include/json/config.h看有没有类似#ifdef JSON_HAS_CPP_11这样按编译器版本分支的宏。新版本把这个宏的默认值设成开老编译器没有能力定义它但头文件里那些C11语法不会因为一个宏就消失该报错还是报错。我一般会把0.6.0系列的源码包单独存一份文件名标注“VC6专用”。项目上如果后来有人升级过jsoncpp导致编译失败我直接让他换回这个版本问题立刻消失。别指望靠修改头文件把新版本“打补丁兼容”到VC6你能改到编译通过也改不完标准库层面的不兼容后面运行时崩溃会更难查。2.2 把jsoncpp源码手动加入VC6工程编译选项与链接细节用VC6开发的人应该都用过“添加到工程”这个操作。把下载好的jsoncpp源码里的三个核心.cpp文件加进工程即可json_reader.cpp、json_writer.cpp、json_value.cpp。头文件路径要加到工程设置的“附加包含目录”里指向jsoncpp源码的include目录。以下是我整理的最小文件清单文件作用是否必须json_value.cppValue节点的构造、拷贝、比较、序列化基础必须json_reader.cpp把JSON字符串解析成Value树必须json_writer.cpp把Value树写成JSON字符串必须json.h统一入口头文件必须json/config.h编译分支与配置宏必须json/forwards.hValue等类型的前向声明必须文件加进工程后最先遇到的是预编译头问题。VC6默认给整个工程开“使用预编译头”但jsoncpp的.cpp文件和MFC的stdafx.h体系不搭会在编译每个json源文件时报找不到预编译头。解决办法是针对这三个.cpp文件单独设置右键文件进入“设置- C/C - 预编译头”改成“不使用预编译头”。这个细节我每次新建工程都会先处理不然第一轮编译就会卡住。接着是语言选项。jsoncpp内部有异常处理的代码路径VC6默认的异常模型和现代编译器不完全一样常见的做法是把jsoncpp的异常配置交给它自己的宏去控制在工程预处理定义里加上JSONCPP_DISABLE_EXCEPTIONS让库在解析失败时不抛异常而是通过返回false加错误信息的方式给出反馈。这样对老代码更友好不需要额外写try/catch。还有一个容易忽略的字符集设置。VC6工程的“预处理器定义”里默认可能是空或者只有WIN32,_DEBUG,_MBCS。jsoncpp本身不依赖UNICODE宏但如果你在工程里用了MultiByteToWideChar这类Windows API做编码转换建议保持_MBCS构建避免工程直接切到_UNICODE后所有CString相关的代码跟着变宽字符引发连锁修改。MBCS字符集下本地字符串就是GBK编码后面第3章会把编码转换讲透。2.3 验证环境一个能跑的最小解析Demo加入源码、改完设置后先不要接业务代码用最干净的API验证jsoncpp能不能在VC6里正常工作。下面这段代码是整个环境的“最小冒烟测试”// demo_smoke.cpp —— VC6下jsoncpp最小验证 #include json/json.h #include stdio.h int main() { const char* rawJson {\name\:\test\,\count\:3}; Json::Value root; Json::Reader reader; if (!reader.parse(rawJson, rawJson strlen(rawJson), root)) { // 解析失败时reader.getFormatedErrorMessages()返回错误描述 printf(parse failed: %s\n, reader.getFormatedErrorMessages().c_str()); return -1; } std::string name root[name].asString(); int count root[count].asInt(); printf(name%s count%d\n, name.c_str(), count); return 0; }这段代码里reader.parse的入参用了字符串首尾指针是jsoncpp老版本接口最稳的用法避免传std::string时因为VC6标准库实现差异出现隐式转换问题。root[name]返回的是Value引用asString()把它转成std::string。printf直接输出ASCII内容这一步只验证库本身能编译、能链接、能解析结构化数据不牵扯中文。我一般会在这个Demo跑通后顺手把jsoncpp所有.cpp文件在Release和Debug两种配置下各编一次。如果Debug能编过Release编不过十有八九是工程选项不一致优先检查“优化”和“运行时库”两处设置。这步做完版本兼容和工程集成就稳了后面所有的问题只剩编码一个方向。3. 中文防乱码完整方案把UTF-8与GBK的转换做成强制动作3.1 乱码的根源两套编码在接口处错位JSON规范规定传输编码是UTF-8而VC6的MFC程序里CString、char*、界面控件拿到的本地文本默认是GBK/ANSI。你从HTTP响应里收到的是UTF-8字节流存进std::string时它只是一个字节容器不感知编码。jsoncpp解析出来的字符串值原样保持UTF-8编码而printf、文件写入、CListCtrl插入都按本地代码页GBK去解释字节。UTF-8的中文字符是三个字节一组GBK是两字节一组用GBK的规则去读UTF-8的数据得到的就是“鏄煶”“娴嬭瘯”这类乱码。反方向也一样把界面输入的GBK字符串直接塞进jsoncpp生成JSON发出去对方按UTF-8解析中文一样变乱码。这个问题的本质jsoncpp不是编码转换库它保证“字节进、字节出”。入口给什么编码出口就是什么编码。要让整个链路不乱码必须在两处做显式转换解析后把UTF-8转成GBK给界面显示生成前把GBK转成UTF-8放进Value节点。3.2 统一转换入口UTF8ToGBK与GBKToUTF8两个函数编码转换用Windows API来做不要在VC6里引入第三方转码库也不要用标准库的mbtowc/wcstombs那些函数在VC6的多字节环境下行为不可控。Windows API方案是转换到宽字符中转再转到目标编码。下面是完整的两个函数// enc_convert.h —— UTF-8与GBK互转 #ifndef ENC_CONVERT_H #define ENC_CONVERT_H #include string #include windows.h // UTF-8字节串 - 本地ANSI字符串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(), (int)utf8.size(), NULL, 0); if (wLen 0) return ; // 第二步分配宽字符缓冲区并做实际转换 wchar_t* wBuf new wchar_t[wLen 1]; MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), (int)utf8.size(), wBuf, wLen); wBuf[wLen] L\0; // 第三步宽字符 - GBK同样先拿目标长度 int gbkLen WideCharToMultiByte(CP_ACP, 0, wBuf, wLen, NULL, 0, NULL, NULL); std::string gbk; if (gbkLen 0) { char* gBuf new char[gbkLen 1]; WideCharToMultiByte(CP_ACP, 0, wBuf, wLen, gBuf, gbkLen, NULL, NULL); gBuf[gbkLen] \0; gbk.assign(gBuf, gbkLen); delete[] gBuf; } delete[] wBuf; return gbk; } // 本地ANSI字符串GBK - UTF-8字节串 inline std::string GBKToUTF8(const std::string gbk) { if (gbk.empty()) return ; // 第一步GBK - 宽字符(UTF-16) int wLen MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), (int)gbk.size(), NULL, 0); if (wLen 0) return ; wchar_t* wBuf new wchar_t[wLen 1]; MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), (int)gbk.size(), wBuf, wLen); wBuf[wLen] L\0; // 第二步宽字符 - UTF-8 int utf8Len WideCharToMultiByte(CP_UTF8, 0, wBuf, wLen, NULL, 0, NULL, NULL); std::string utf8; if (utf8Len 0) { char* uBuf new char[utf8Len 1]; WideCharToMultiByte(CP_UTF8, 0, wBuf, wLen, uBuf, utf8Len, NULL, NULL); uBuf[utf8Len] \0; utf8.assign(uBuf, utf8Len); delete[] uBuf; } delete[] wBuf; return utf8; } #endif这里面的核心参数有两个第一个API调用里传入NULL,0作用是只计算转换后需要的缓冲区长度不实际转换计算完长度再调用一次执行真实转换。CP_UTF8代表源编码是UTF-8CP_ACP代表本地代码页VC6默认环境就是GBK/936。每次转换都走“多字节到宽字符、宽字符到多字节”两段这段代码在VC6下没有标准库版本兼容问题因为MultiByteToWideChar是操作系统API。这两个函数我建议放在一个公共头文件里任何需要处理JSON中文的模块直接包含不要在业务代码里重复造轮子。编码转换只能做一层如果某个函数内部又做了第二次转换中文就变成二次乱码这种bug最难查。3.3 在解析与生成两侧分别封堵乱码解析侧的标准流程是HTTP响应体拿到std::string先交给jsoncpp解析取出中文字段的值再对值做UTF8ToGBK转换最后交给界面显示。完整代码如下// 解析侧UTF-8 JSON - 界面可读的GBK字符串 bool ParseApiResponse(const std::string httpBody, std::string outName) { Json::Value root; Json::Reader reader; if (!reader.parse(httpBody.data(), httpBody.data() httpBody.size(), root)) { return false; // 或通过输出参数返回错误信息 } // root[name]取出的值是UTF-8字节流先转GBK再使用 std::string nameUtf8 root[name].asString(); std::string nameGbk UTF8ToGBK(nameUtf8); outName nameGbk; return true; }生成侧的方向相反界面输入的GBK字符串先转成UTF-8塞进Json::Value序列化之后再发送或写入文件。代码如下// 生成侧GBK界面输入 - UTF-8 JSON请求体 std::string BuildLoginRequest(const std::string usernameGbk, const std::string passwordGbk) { Json::Value req; // 关键GBK字符串必须显式转成UTF-8再放入Value req[username] GBKToUTF8(usernameGbk); req[password] GBKToUTF8(passwordGbk); req[op] login; Json::FastWriter writer; std::string body writer.write(req); return body; // body是UTF-8字节流可直接作为HTTP请求体发送 }判断什么时候该转、什么时候不该转我总结成一张表贴在工程代码注释里数据所处位置编码操作HTTP接收缓冲区/请求体UTF-8不做转换直接给jsoncpp解析或序列化jsoncpp解析后的ValueUTF-8取字符串值时需UTF8ToGBKjsoncpp生成后的输出UTF-8直接发送不要额外转换界面控件/CString/本地文件读写GBK放入Value前需GBKToUTF8这条规则如果从工程一开始就定死中文乱码基本不会出现。怕就怕有的人在某个角落直接printf了一个UTF-8字符串看着是乱码用了转换函数反而好了于是以为“反正乱码就套一下转换函数”最后数据流里转来转去污染全链路。4. 全案例实战从登录鉴权到嵌套列表的JSON处理全覆盖4.1 案例一解析接口返回的嵌套JSON取出中文业务字段实际业务里接口数据经常是嵌套结构“data”节点里挂“userInfo”userInfo里才是真正的用户信息。取嵌套字段时jsoncpp支持链式下标访问很直观。下面这段解析登录响应// 解析嵌套JSON{code:0,data:{userId:1001,userInfo:{name:张三,dept:研发部}}} bool ParseLoginResponse(const std::string httpBody) { Json::Value root; Json::Reader reader; if (!reader.parse(httpBody.data(), httpBody.data() httpBody.size(), root)) { printf(JSON解析失败原始数据: %s\n, httpBody.c_str()); return false; } int code root[code].asInt(); if (code ! 0) { printf(业务错误code%d\n, code); return false; } // 注意每一层下标都必须确认节点存在否则asString()返回空 Json::Value data root[data]; Json::Value userInfo data[userInfo]; int userId data[userId].asInt(); std::string nameGbk UTF8ToGBK(userInfo[name].asString()); std::string deptGbk UTF8ToGBK(userInfo[dept].asString()); printf(用户ID%d 姓名%s 部门%s\n, userId, nameGbk.c_str(), deptGbk.c_str()); return true; }这里有一个很关键的用法Json::Value data root[data]拿到的是节点的引用不是拷贝。jsoncpp对不存在的下标会返回一个引用到内部空节点你对它进行isValid()判断是false但这种引用不能保存长期使用因为下次对root的操作可能让这个引用失效。我一般只在当前作用域内使用这种引用不做跨函数传递。4.2 案例二构造带中文的JSON请求体并序列化客户端上报数据是老MFC工程里很常见的需求。界面里有几个编辑框用户填中文程序要把这些内容组装成JSON提交到服务端。下面这段演示完整流程// 组装上报数据界面控件文本(GBK) - UTF-8 JSON std::string BuildReportJson(const char* deviceNameGbk, const char* locationGbk, int status) { Json::Value report; report[deviceName] GBKToUTF8(deviceNameGbk); report[location] GBKToUTF8(locationGbk); report[status] status; report[timestamp] (Json::Int64)time(NULL); // 嵌套数组告警列表 Json::Value alarmList(Json::arrayValue); for (int i 0; i 2; i) { Json::Value alarm; alarm[level] i 1; alarm[desc] GBKToUTF8(i 0 ? 温度过高 : 压力异常); alarmList.append(alarm); } report[alarms] alarmList; // StyledWriter输出带缩进FastWriter输出单行线上环境用FastWriter Json::FastWriter writer; return writer.write(report); }Json::Value(Json::arrayValue)是显式声明这个节点是数组类型之后append往里加元素。如果不显式声明第一次append时jsoncpp会自动把节点变成数组但显式声明能让代码意图更清楚也避免某些边界条件下类型判断出现意外。time(NULL)返回time_t在VC6里赋值给Json::Int64需要强转我上面直接写了(Json::Int64)强转避免64位赋值在32位工程里的截断警告。序列化选择FastWriter还是StyledWriter主要是看用途调试抓包时用StyledWriter带上缩进人眼好读业务请求建议FastWriter体积小。4.3 案例三数组遍历与节点删除把Map逻辑换成jsoncpp写法一个接口返回的设备列表要遍历数组、按下标访问、按条件删除元素。jsoncpp的数组操作跟std::vector的逻辑接近但有几个老手容易犯的错。下面这段遍历并删除// 遍历JSON数组删除disabled字段为true的节点 void RemoveDisabledDevices(Json::Value deviceList) { if (!deviceList.isArray()) return; // 注意从后往前删除避免删除后下标错位 int count (int)deviceList.size(); for (int i count - 1; i 0; i--) { Json::Value item deviceList[i]; if (item[disabled].asBool()) { // removeMember()返回被删除的Value忽略返回值即可 deviceList.removeMember(i); } } }从后往前遍历删除是数组操作的基本功正序遍历时删掉第i个元素后后面的元素会整体前移下一次递增i就会跳过一个元素。jsoncpp的removeMember(int index)在0.6.0版本里是支持的只传一个index参数。注意这里取出的Json::Value item只是当前元素的引用removeMember会释放该元素内部资源所以不要在删除之后再访问item那是典型的悬空引用。遍历时如果只是读数据推荐用for size() 下标访问。jsoncpp自带的迭代器在VC6下类型推导有点绕iterator、const_iterator混用时容易撞上模板实例化问题下标访问最直接、最不容易踩坑。4.4 案例四非标准JSON容错注释、失效节点与异常的边界处理线上接口返回的JSON不一定完全符合规范。有的老服务会返回带注释的JSON形如//注释或/注释/有的返回空字符串有的返回带BOM头的UTF-8文件流。jsoncpp 0.6.0的Reader内置了容错能力默认允许注释存在但对BOM头处理得不好BOM的EF BB BF三个字节会被当成非法字符导致解析失败。下面这段代码把BOM清洗掉再解析// 解析前预处理去掉UTF-8 BOM头裁剪空字符 std::string CleanRawJson(const std::string raw) { const unsigned char* p (const unsigned char*)raw.c_str(); // UTF-8 BOM是 EF BB BF 三个字节 if (raw.size() 3 p[0] 0xEF p[1] 0xBB p[2] 0xBF) return raw.substr(3); return raw; } bool ParseFlexibleJson(const std::string rawJson) { std::string cleaned CleanRawJson(rawJson); if (cleaned.empty()) return false; Json::Value root; Json::Reader reader; // 默认允许注释如果不希望注释被接受改为false bool ok reader.parse(cleaned.data(), cleaned.data() cleaned.size(), root); if (!ok) { printf(解析异常: %s\n, reader.getFormatedErrorMessages().c_str()); return false; } // 判断关键节点是否存在isMember比直接用下标更安全 if (!root.isMember(data)) { printf(缺少data节点\n); return false; } return true; }reader.parse的第三个参数也就是解析选项在旧版本里控制是否允许注释。传true会让jsoncpp忽略//和/* */注释传false则遇到注释直接报错。我建议解析外部接口数据时保持默认true容错性更好。判断节点是否存在尽量用isMember而不是直接取值的then判断因为jsoncpp对不存在节点返回的引用是“无效节点”asString()返回空字符串asInt()返回0不开isMember很容易把“字段缺失”和“字段值为空”混为一谈。5. 避坑与排查我在这套组合上踩过的五个典型翻车现场5.1 坑一编译报C2059 / C2065——新版头文件直接拍碎VC6语法解析现象把jsoncpp新版本源码头文件加入工程后编译json_value.cpp报出一大片C2059语法错误位置全在头文件里有的还指向std::string相关行跟JSON毫无关系。原因jsoncpp 1.x系列使用了C11的移动构造、nullptr、模板别名等语法VC6不认。解决换回0.6.0系列源码包这个版本的语言特性都在VC6能力范围内。我现在的固定做法是在工程目录建一个third_party/jsoncpp_vc6/目录源码包和头文件锁死版本代码注释里写明“禁止升级jsoncpp”谁升级谁负责回归测试。5.2 坑二链接找不到json_reader.obj符号——源文件没进工程或预编译头错位现象编译全过链接时报unresolved external symbol或者LNK2001符号名里有json_reader相关字样。原因源文件没有真正加入工程或者加入工程后预编译头设置不对json源文件被跳过编译。解决确认json_reader.cpp、json_writer.cpp、json_value.cpp三个文件都在工程里右键每个.cpp检查C/C预编译头设置改成“不使用预编译头”。链接错误里的符号名带json_reader字样时优先检查这个不用怀疑库本身有bug。5.3 坑三解析出的中文全是问号或乱码——转换函数用反了现象接口返回“张三”程序的printf输出“寮犱笁”或者直接是“???”。原因“???“大概率是UTF-8字节被当成GBK显示或者转换时源和目标参数写反了。解决第一步在转换前把根字节打出来十六进制看是不是E5 BC A0开头的UTF-8序列第二步确认调的是UTF8ToGBK不是GBKToUTF8。我见过有人把两个函数封装成了一个autoConvert意图是自动判断编码结果在VC6里判断逻辑写得不对中文数据被转了两遍反而乱得更离谱。老老实实按第3章那张表分场景调用别偷懒。5.4 坑四生成的JSON字符串被对方解析失败——特殊字符没转义现象程序生成的JSON自测没问题但发给服务端对方的JSON解析器报格式错误。原因某个字符串字段里带了双引号或反斜杠通过界面输入时被当成普通字符存进了GBK字符串而字符串放进JSON时没有对双引号做转义。jsoncpp的序列化会自动把Value内的引号字符转成但前提是你把原始字符串完整交给了Value。如果中间某一层手动拼接了字符串比如用sprintf拼了半截JSON再接Value输出转义就会断裂。解决不要手工拼JSON所有字段都通过Value节点赋值再统一走writer.write()。如果坚持拼字符串至少对内容做一次replace( , \)但那已经是在重造轮子。5.5 坑五程序运行时崩溃——Value生命周期和拷贝语义惹的祸现象Debug版本一切正常Release版本偶发崩溃或者一个函数里取了root[a][b]的引用存到成员变量下次调用就崩。原因VC6的标准库实现里std::string的拷贝构造和析构在不同模块比如exe和dll之间会因堆不同而崩溃。jsoncpp的Json::Value内部持有std::string如果你在exe里创建Value传到另一个dll里析构跨模块释放内存会直接崩。另外保存Value引用跨作用域使用root被重新赋值或析构后引用悬空。解决Value对象在哪个模块创建就在哪个模块销毁不要做成跨DLL的接口参数需要保存字段值时取出来拷贝成std::string或int不要保存引用。我后来定了一条规矩任何函数的返回值、成员变量都存std::string值类型绝不存Value。6. 把编码细节封装成JsonHelper老工程加JSON能力的最省事收尾前面几章把jsoncpp的编译、中文转换、常用场景都打通了但项目里如果到处散落着UTF8ToGBK和GBKToUTF8的调用代码依然难维护。我习惯在最外层包一个极薄的Helper层把“取字符串自动转GBK”“放字符串自动转UTF-8”这两个动作内聚成一个类的两个方法。// json_helper.h —— 面向业务的最小JSON封装 class JsonHelper { public: // 从Value节点取字符串直接返回GBK编码界面可直接显示 static std::string GetString(const Json::Value node, const std::string key) { if (!node.isMember(key)) return ; return UTF8ToGBK(node[key].asString()); } // 向Value节点放入GBK字符串内部自动转UTF-8 static void PutString(Json::Value node, const std::string key, const std::string gbkValue) { node[key] GBKToUTF8(gbkValue); } // 对已有的Value节点恢复GBK文本 static std::string ToGBK(const Json::Value node) { return UTF8ToGBK(node.asString()); } };这个Helper只做一件事让业务代码里不再出现编码转换字样。取字段用GetString放字段用PutString底层是UTF-8还是GBK业务层不关心。实际接入老工程时我会把第2章的冒烟测试、第3章的转换函数、第4章的四个案例按“解析请求、生成响应、遍历数组、清洗BOM”四个场景拆成四份可编译源码放在README里说明每个案例对应什么业务。后来接手的人如果遇到新字段翻一遍案例基本就能自己写出来。我自己刚折腾这套方案时犯过最蠢的错是工程里有两个模块都封装了编码转换函数一个叫GB2312ToUTF8一个叫AnsiToUTF8底层实现一样接口名称不同结果两个模块互相调用对方的函数转换链绕了一圈又回到原样。从那以后我每次给VC6工程加JSON能力都强制把编码转换收敛到单独一个头文件里全工程只认这一套接口任何新代码不得另起炉灶。VC6的老工程禁不起反复试错一次把版本、编译选项、编码方向都定死后面加接口、调字段才不吵架。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑