21、序列化与反序列化:JSON/XML库(cJSON, libxml2)的跨平台编译与使用
说到跨平台开发,序列化与反序列化这块儿,几乎是绕不开的坎儿。你想想看,你的程序要在不同系统间传数据,总得有个统一的格式吧?JSON轻量、XML严谨,各有各的战场。今天我就聊聊cJSON和libxml2这两个库,在跨平台编译时那些坑,以及怎么用好它们。
为什么选cJSON和libxml2?
我个人习惯,做嵌入式或移动端项目时首选cJSON。它纯C实现,就一个.c和一个.h文件,移植起来太方便了。libxml2呢,功能更全,支持XPath、Schema验证,适合做配置文件和协议解析。
我在项目中遇到过这样的情况:用cJSON解析服务器返回的配置数据,结构简单,解析速度快,内存占用也小。而libxml2则用在需要严格校验的XML配置文件中,比如一些工业设备的参数配置。
核心区别:
- cJSON:轻量、无依赖、适合嵌入式/移动端
- libxml2:功能全面、支持XPath/Schema、适合复杂场景
跨平台编译的实战经验
cJSON的跨平台编译
cJSON的编译其实很简单。它没有外部依赖,直接编译cJSON.c和你的代码一起链接就行。但要注意几个细节:
// 编译示例(Linux/macOS)
gcc -c cJSON.c -o cJSON.o
gcc your_code.c cJSON.o -o your_program -lm
// Windows下用MinGW
gcc -c cJSON.c -o cJSON.o
gcc your_code.c cJSON.o -o your_program.exe
嗯,这里要注意:cJSON默认使用标准C库的malloc/free。如果你在RTOS或裸机环境下,可以自定义内存分配函数:
// 自定义内存分配
cJSON_Hooks hooks;
hooks.malloc_fn = my_malloc;
hooks.free_fn = my_free;
cJSON_InitHooks(&hooks);
我曾经在一个STM32项目里就这么干过。系统只有16KB RAM,用默认的malloc容易碎片化,换成自己管理的内存池后,稳定多了。
libxml2的跨平台编译
libxml2就麻烦多了。它依赖libiconv、zlib等库,编译时得小心处理。我建议用CMake来管理:
# CMakeLists.txt 示例
cmake_minimum_required(VERSION 3.10)
project(XmlDemo)
# 查找libxml2
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBXML2 REQUIRED libxml-2.0)
add_executable(xml_demo main.c)
target_link_libraries(xml_demo ${LIBXML2_LIBRARIES})
target_include_directories(xml_demo PRIVATE ${LIBXML2_INCLUDE_DIRS})
在Windows上,我建议用vcpkg来安装:
vcpkg install libxml2:x64-windows
# 然后CMake里设置 -DCMAKE_TOOLCHAIN_FILE=[vcpkg-root]/scripts/buildsystems/vcpkg.cmake
注意:libxml2在Windows上编译时,记得定义LIBXML_STATIC或LIBXML_STATIC_FOR_DLL宏,否则链接会报错。我当初折腾了整整一个下午才找到原因。
序列化与反序列化的核心用法
cJSON实战:JSON解析与生成
先看一个完整的例子:
#include "cJSON.h"
#include <stdio.h>
#include <string.h>
// 生成JSON
char* create_json() {
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "name", "传感器A");
cJSON_AddNumberToObject(root, "value", 25.6);
cJSON_AddBoolToObject(root, "status", 1);
cJSON *tags = cJSON_CreateArray();
cJSON_AddItemToArray(tags, cJSON_CreateString("温度"));
cJSON_AddItemToArray(tags, cJSON_CreateString("湿度"));
cJSON_AddItemToObject(root, "tags", tags);
char *json_str = cJSON_Print(root);
cJSON_Delete(root);
return json_str;
}
// 解析JSON
void parse_json(const char *json_str) {
cJSON *root = cJSON_Parse(json_str);
if (root == NULL) {
printf("解析失败\n");
return;
}
cJSON *name = cJSON_GetObjectItem(root, "name");
cJSON *value = cJSON_GetObjectItem(root, "value");
cJSON *status = cJSON_GetObjectItem(root, "status");
if (cJSON_IsString(name) && name->valuestring) {
printf("名称: %s\n", name->valuestring);
}
if (cJSON_IsNumber(value)) {
printf("数值: %f\n", value->valuedouble);
}
if (cJSON_IsBool(status)) {
printf("状态: %s\n", status->valueint ? "正常" : "异常");
}
cJSON_Delete(root);
}
int main() {
char *json = create_json();
printf("生成的JSON: %s\n", json);
parse_json(json);
free(json);
return 0;
}
说白了,cJSON的API设计得很直观。创建对象用cJSON_CreateObject,添加字段用cJSON_AddXxxToObject,解析时用cJSON_GetObjectItem。记住一点:每次用完都要cJSON_Delete释放内存。
libxml2实战:XML解析与生成
libxml2的API稍微复杂些,但功能强大:
#include <libxml/parser.h>
#include <libxml/tree.h>
// 生成XML
xmlDocPtr create_xml() {
xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
xmlNodePtr root = xmlNewNode(NULL, BAD_CAST "device");
xmlDocSetRootElement(doc, root);
xmlNewChild(root, NULL, BAD_CAST "name", BAD_CAST "传感器A");
xmlNewChild(root, NULL, BAD_CAST "value", BAD_CAST "25.6");
xmlNewProp(root, BAD_CAST "status", BAD_CAST "active");
return doc;
}
// 解析XML
void parse_xml(const char *filename) {
xmlDocPtr doc = xmlReadFile(filename, NULL, 0);
if (doc == NULL) {
printf("解析失败\n");
return;
}
xmlNodePtr root = xmlDocGetRootElement(doc);
xmlNodePtr cur = root->children;
while (cur != NULL) {
if (cur->type == XML_ELEMENT_NODE) {
if (xmlStrcmp(cur->name, BAD_CAST "name") == 0) {
xmlChar *content = xmlNodeGetContent(cur);
printf("名称: %s\n", content);
xmlFree(content);
}
}
cur = cur->next;
}
xmlFreeDoc(doc);
xmlCleanupParser();
}
int main() {
xmlDocPtr doc = create_xml();
xmlSaveFormatFileEnc("device.xml", doc, "UTF-8", 1);
xmlFreeDoc(doc);
parse_xml("device.xml");
return 0;
}
小技巧:libxml2的字符串都用BAD_CAST宏转换。另外,记得在程序结束时调用xmlCleanupParser()清理全局状态,否则内存泄漏会让你头疼。
跨平台兼容性要点
我整理了一张表,方便你对照:
| 对比项 | cJSON | libxml2 |
|---|---|---|
| 依赖数量 | 0(纯C标准库) | 3+(iconv, zlib等) |
| 内存占用 | 约10KB | 约500KB |
| 编译难度 | 简单(直接编译) | 中等(需处理依赖) |
| 功能范围 | JSON解析/生成 | XML解析/生成/XPath/Schema |
| 适用场景 | 嵌入式、移动端 | 桌面、服务器、工业 |
避坑指南
我曾经在跨平台项目里踩过几个坑,分享给你:
- 字符编码问题:cJSON默认用UTF-8,libxml2也支持多种编码。但Windows下文件可能是GBK,记得做编码转换。我一般用iconv库来处理。
- 内存管理:cJSON的cJSON_Print返回的字符串需要free,libxml2的xmlNodeGetContent返回的内容需要xmlFree。别搞混了。
- 线程安全:cJSON本身不是线程安全的,多线程环境下要加锁。libxml2的解析函数是线程安全的,但全局初始化函数不是。
- 大文件处理:libxml2支持流式解析(xmlReader),适合处理大XML文件。cJSON则建议用增量解析方式。
知识体系总览
下面这张图,帮你理清本章的核心脉络:
总结
序列化与反序列化,说白了就是让数据在不同平台间「说同一种语言」。cJSON轻巧灵活,适合资源受限的环境;libxml2功能全面,适合需要严格校验的场景。跨平台编译时,注意依赖管理和内存释放,这两点最容易出问题。
我个人建议,新手先从cJSON入手,它简单直观,能快速上手。等遇到复杂XML需求时,再研究libxml2也不迟。记住,工具是死的,思路是活的。选对工具,事半功倍。