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则建议用增量解析方式。

知识体系总览

下面这张图,帮你理清本章的核心脉络:

序列化与反序列化知识体系 JSON 序列化 (cJSON) XML 序列化 (libxml2) 编译:直接编译cJSON.c,无外部依赖 API:cJSON_CreateObject / cJSON_Parse 内存:手动管理,cJSON_Delete释放 适用:嵌入式、移动端、轻量场景 编译:依赖iconv/zlib,推荐CMake/vcpkg API:xmlNewDoc / xmlReadFile / XPath 内存:xmlFreeDoc / xmlFree 释放 适用:桌面、服务器、复杂配置场景 核心原则:根据场景选库,注意跨平台兼容性

总结

序列化与反序列化,说白了就是让数据在不同平台间「说同一种语言」。cJSON轻巧灵活,适合资源受限的环境;libxml2功能全面,适合需要严格校验的场景。跨平台编译时,注意依赖管理和内存释放,这两点最容易出问题。

我个人建议,新手先从cJSON入手,它简单直观,能快速上手。等遇到复杂XML需求时,再研究libxml2也不迟。记住,工具是死的,思路是活的。选对工具,事半功倍。

公众号:蓝海资料掘金营,微信deep3321