开源libunistring命令行工具鸿蒙化探索过程
本文详细记录了在开源项目Termony中使用命令OHOS_ARCH=aarch64 OHOS_ABI=arm64-v8a sh ./create-hnp.sh构建Libunistring 1.3的完整过程。内容涵盖构建环境配置、构建链路解析、关键日志记录、常见问题解决方案以及产物验证方法。重点介绍了Libunistring的核心功能,包括Unicode字符串操作、字符编码转换、文本规范化等模块。
·
本文记录在开源项目Termony中使用命令 OHOS_ARCH=aarch64 OHOS_ABI=arm64-v8a sh ./create-hnp.sh 构建 Libunistring 1.3 的完整过程,包括环境、构建链路、关键日志、常见问题与解决方案、产物验证与重建方法,便于复现与运维。
📖 Libunistring 简介
Libunistring 是一个用于操作 Unicode 字符串的 C 库,提供了丰富的 Unicode 字符串处理功能。它是 GNU 项目的一部分,为需要处理国际化字符串的应用程序提供了强大的支持。
🎯 Libunistring 的作用与重要性
Libunistring 是 Unicode 字符串处理的核心库,提供了:
- Unicode 字符串操作:完整的 Unicode 字符串处理功能
- 字符编码转换:支持多种字符编码之间的转换
- 大小写转换:Unicode 感知的大小写转换
- 字符分类:Unicode 字符类型和属性查询
- 文本规范化:Unicode 文本规范化(NFC、NFD、NFKC、NFKD)
- 文本宽度计算:计算文本显示宽度(用于终端显示)
🔧 Libunistring 核心特性
1. 字符串操作模块
- unistr.h:基本 Unicode 字符串操作
- 字符串创建、复制、连接、比较
- 字符串搜索、替换、分割
- 字符串长度计算、子串提取
- uniconv.h:字符编码转换
- 支持多种字符编码(UTF-8、UTF-16、UTF-32、ISO-8859-* 等)
- 编码检测和转换
- 错误处理
2. 文本处理模块
- unicase.h:大小写转换
- Unicode 感知的大小写转换
- 大小写折叠
- 大小写比较
- unictype.h:字符分类
- Unicode 字符类型查询
- 字符属性查询
- 字符范围检查
- uninorm.h:文本规范化
- NFC(规范化形式 C)
- NFD(规范化形式 D)
- NFKC(兼容规范化形式 C)
- NFKD(兼容规范化形式 D)
3. 文本布局模块
- uniwidth.h:文本宽度计算
- 计算文本显示宽度
- 支持全角和半角字符
- 用于终端和 GUI 显示
- unigbrk.h:字素边界
- 字素簇检测
- 字素边界定位
- uniwbrk.h:词边界
- 词边界检测
- 分词功能
- unilbrk.h:行边界
- 行断行检测
- 文本换行处理
4. 应用场景
- 国际化应用:处理多语言文本
- 文本编辑器:Unicode 文本编辑功能
- 网络应用:处理国际化域名和 URL
- 数据库:Unicode 字符串存储和查询
- 文本处理工具:文本分析和处理
🚀 构建入口与环境
- 📝 执行命令:
OHOS_ARCH=aarch64 OHOS_ABI=arm64-v8a sh ./create-hnp.sh - 🔧 入口脚本:
create-hnp.sh- 检查必需的环境变量
OHOS_ARCH和OHOS_ABI - 导出
LC_CTYPE、TOOL_HOME、OHOS_SDK_HOME - 执行
make -C build-hnp
- 检查必需的环境变量
- 📦 顶层构建:
build-hnp/MakefilePKGS变量定义需要构建的包列表(包含libunistring)- 通过
check-pkgs机制自动检测PKGS变化并触发重新构建 - 自动合并
external-hnp目录下的外部 HNP 包 base.hnp依赖所有包的.stamp和外部 HNP 包- 总目标
all: copy,打包base.hnp并拷贝到entry/hnp/$(OHOS_ABI)
⚙️ Libunistring 包的构建配置
- 📁 包目录:
build-hnp/libunistring/Makefile- 继承通用规则:
include ../utils/Makefrag - 源地址:
$(GNU_MIRROR)/gnu/libunistring/libunistring-1.3.tar.xz - 版本:
1.3
- 继承通用规则:
- ⚙️ Autotools 配置参数:
--prefix=$(PREFIX)- 安装前缀(/data/app/base.org/base_1.0)--host $(OHOS_ARCH)-unknown-linux-musl- 目标平台--disable-static- 禁用静态库--enable-shared- 构建共享库
- 🔨 构建流程:
- 下载源码包(支持多镜像回退)
- 解包并进入
temp/libunistring-1.3目录 - 运行
./configure配置构建系统 - 使用
make -j $(nproc)并行编译 - 使用
make install安装 - 执行 ELF strip 减小体积
- 复制到
../sysroot
- 🔧 通用工具链与路径:
build-hnp/utils/MakefragCC/CXX/LD/AR/RANLIB/...均指向 OHOS SDK 的 LLVM 工具链- 下载支持多镜像回退:
wget→curl,主镜像失败时自动尝试备用镜像
📋 关键日志与过程节点
- 📥 下载与解包:
- 从 GNU 镜像下载
libunistring-1.3.tar.xz - 完成解包并进入
temp/libunistring-1.3目录 - 下载规则支持多镜像回退:
wget→curl兜底
- 从 GNU 镜像下载
- ⚙️ 配置阶段:
- 运行
./configure --prefix=... --disable-static --enable-shared --host=aarch64-unknown-linux-musl ... - 配置警告:
using cross tools not prefixed with host triplet(不影响构建) - 标准头与接口探测全面通过(
stdio.h、wchar.h、pthread.h、arpa/inet.h等) - 链接器
ld.lld与工具链llvm-ar/llvm-ranlib检测成功 - 配置成功,启用共享库构建
- 生成
Makefile和构建配置
- 运行
- 🔨 编译与安装:
- 使用
make -j $(nproc)并行编译 - 成功编译生成
libunistring.so.5.2.0和全套头文件 - 使用
make install安装到临时前缀 - 执行
llvm-strip剥离共享库符号 - 复制到
../sysroot
- 使用
- 📦 打包:
- 完成
base.hnp重打包,拷贝产物到entry/hnp/arm64-v8a/ - Libunistring 库已成功打包到
base.hnp中
- 完成
✅ 产物验证
📦 检查打包文件
ls build-hnp/base.hnp # 应存在
ls entry/hnp/arm64-v8a/*.hnp # 应包含 base.hnp 与 base-public.hnp
🔍 检查库文件和头文件
# 检查库文件
ls -lh build-hnp/sysroot/lib/libunistring.so*
file build-hnp/sysroot/lib/libunistring.so.5.2.0
# 检查主要头文件
ls -lh build-hnp/sysroot/include/unistr.h
ls -lh build-hnp/sysroot/include/uniconv.h
ls -lh build-hnp/sysroot/include/unicase.h
ls -lh build-hnp/sysroot/include/unictype.h
# 检查辅助头文件目录
ls -lh build-hnp/sysroot/include/unistring/*.h
✅ 构建验证结果:
- ✅ Libunistring 库已成功安装:
libunistring.so.5.2.0(2.0M) - 主库文件libunistring.so.5- 版本符号链接libunistring.so- 通用符号链接
- ✅ 文件类型:ELF 64-bit LSB shared object, ARM aarch64
- ✅ 动态链接:interpreter
/lib/ld-musl-aarch64.so.1 - ✅ 已剥离符号:
stripped - ✅ 头文件已安装:
unistr.h(24K) - Unicode 字符串操作uniconv.h(7.4K) - 字符编码转换unicase.h、unictype.h、uninorm.h、uniwidth.h等unistring/子目录中的辅助头文件
- ✅ 已打包到
base.hnp中(2,033,704 字节)
💻 终端中执行的示例命令
📝 Libunistring 编程示例
1. 基本字符串操作(unistr.h)
// 示例:基本 Unicode 字符串操作
#include <unistr.h>
#include <stdio.h>
int main() {
// 创建 Unicode 字符串
uint8_t *str1 = u8_strdup((uint8_t *)"Hello, 世界");
uint8_t *str2 = u8_strdup((uint8_t *)"Hello, World");
// 获取字符串长度(字符数,不是字节数)
size_t len1 = u8_strlen(str1);
size_t len2 = u8_strlen(str2);
printf("Length of str1: %zu\n", len1);
printf("Length of str2: %zu\n", len2);
// 字符串比较
int cmp = u8_strcmp(str1, str2);
if (cmp == 0) {
printf("Strings are equal\n");
} else if (cmp < 0) {
printf("str1 < str2\n");
} else {
printf("str1 > str2\n");
}
// 字符串连接
uint8_t *result = u8_strconcat(str1, (uint8_t *)" - ", str2, NULL);
printf("Concatenated: %s\n", result);
// 清理内存
free(str1);
free(str2);
free(result);
return 0;
}
// 编译命令
// gcc -o test_unistr test_unistr.c -lunistring
2. 字符编码转换(uniconv.h)
// 示例:字符编码转换
#include <uniconv.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
// UTF-8 字符串
const char *utf8_str = "Hello, 世界";
// 转换为 UTF-16
size_t utf16_len;
uint16_t *utf16_str = u8_to_u16((uint8_t *)utf8_str, strlen(utf8_str), NULL, &utf16_len);
if (utf16_str != NULL) {
printf("UTF-16 length: %zu\n", utf16_len);
// 转换回 UTF-8
size_t utf8_len;
uint8_t *utf8_result = u16_to_u8(utf16_str, utf16_len, NULL, &utf8_len);
if (utf8_result != NULL) {
printf("UTF-8 result: %s\n", utf8_result);
free(utf8_result);
}
free(utf16_str);
}
return 0;
}
// 编译命令
// gcc -o test_uniconv test_uniconv.c -lunistring
3. 大小写转换(unicase.h)
// 示例:大小写转换
#include <unicase.h>
#include <unistr.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
// 原始字符串
uint8_t *str = u8_strdup((uint8_t *)"Hello, 世界");
// 转换为小写
size_t lower_len;
uint8_t *lower = u8_tolower(str, u8_strlen(str), NULL, NULL, &lower_len);
if (lower != NULL) {
printf("Lowercase: %s\n", lower);
free(lower);
}
// 转换为大写
size_t upper_len;
uint8_t *upper = u8_toupper(str, u8_strlen(str), NULL, NULL, &upper_len);
if (upper != NULL) {
printf("Uppercase: %s\n", upper);
free(upper);
}
free(str);
return 0;
}
// 编译命令
// gcc -o test_unicase test_unicase.c -lunistring
4. 字符分类(unictype.h)
// 示例:字符分类
#include <unictype.h>
#include <unistr.h>
#include <stdio.h>
int main() {
uint8_t *str = u8_strdup((uint8_t *)"Hello123世界");
size_t len = u8_strlen(str);
for (size_t i = 0; i < len; i++) {
ucs4_t ch = u8_strchr(str, len, str[i]) ? u8_next(str[i], str + i, len - i) : 0;
if (ch != 0) {
printf("Character: %c (U+%04X)\n", (char)ch, ch);
printf(" Is letter: %d\n", uc_is_alpha(ch));
printf(" Is digit: %d\n", uc_is_digit(ch));
printf(" Is space: %d\n", uc_is_space(ch));
printf(" Is upper: %d\n", uc_is_upper(ch));
printf(" Is lower: %d\n", uc_is_lower(ch));
}
}
free(str);
return 0;
}
// 编译命令
// gcc -o test_unictype test_unictype.c -lunistring
5. 文本规范化(uninorm.h)
// 示例:文本规范化
#include <uninorm.h>
#include <unistr.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
uint8_t *str = u8_strdup((uint8_t *)"Café");
size_t len = u8_strlen(str);
// NFC 规范化
size_t nfc_len;
uint8_t *nfc = u8_normalize(UNINORM_NFC, str, len, NULL, &nfc_len);
if (nfc != NULL) {
printf("NFC normalized: %s\n", nfc);
free(nfc);
}
// NFD 规范化
size_t nfd_len;
uint8_t *nfd = u8_normalize(UNINORM_NFD, str, len, NULL, &nfd_len);
if (nfd != NULL) {
printf("NFD normalized: %s\n", nfd);
free(nfd);
}
free(str);
return 0;
}
// 编译命令
// gcc -o test_uninorm test_uninorm.c -lunistring
6. 文本宽度计算(uniwidth.h)
// 示例:文本宽度计算
#include <uniwidth.h>
#include <unistr.h>
#include <stdio.h>
int main() {
uint8_t *str1 = u8_strdup((uint8_t *)"Hello");
uint8_t *str2 = u8_strdup((uint8_t *)"世界");
uint8_t *str3 = u8_strdup((uint8_t *)"Hello世界");
// 计算显示宽度
int width1 = u8_width(str1, u8_strlen(str1), "UTF-8");
int width2 = u8_width(str2, u8_strlen(str2), "UTF-8");
int width3 = u8_width(str3, u8_strlen(str3), "UTF-8");
printf("Width of '%s': %d\n", str1, width1);
printf("Width of '%s': %d\n", str2, width2);
printf("Width of '%s': %d\n", str3, width3);
free(str1);
free(str2);
free(str3);
return 0;
}
// 编译命令
// gcc -o test_uniwidth test_uniwidth.c -lunistring
7. 字符串搜索和替换
// 示例:字符串搜索和替换
#include <unistr.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
uint8_t *str = u8_strdup((uint8_t *)"Hello, 世界! Hello, World!");
uint8_t *search = u8_strdup((uint8_t *)"Hello");
uint8_t *replace = u8_strdup((uint8_t *)"Hi");
// 查找子串
uint8_t *found = u8_strstr(str, search);
if (found != NULL) {
printf("Found '%s' at position %ld\n", search, found - str);
}
// 替换(简化示例)
// 注意:实际替换需要手动实现或使用其他函数
free(str);
free(search);
free(replace);
return 0;
}
// 编译命令
// gcc -o test_search test_search.c -lunistring
8. 字符串分割
// 示例:字符串分割
#include <unistr.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
uint8_t *str = u8_strdup((uint8_t *)"apple,banana,orange");
uint8_t *delim = u8_strdup((uint8_t *)",");
// 分割字符串
uint8_t *token = u8_strtok(str, delim, NULL);
int count = 0;
while (token != NULL) {
printf("Token %d: %s\n", count++, token);
token = u8_strtok(NULL, delim, NULL);
}
free(str);
free(delim);
return 0;
}
// 编译命令
// gcc -o test_split test_split.c -lunistring
9. 实际应用示例
// 示例:国际化字符串处理工具
#include <unistr.h>
#include <unicase.h>
#include <uninorm.h>
#include <stdio.h>
#include <stdlib.h>
// 函数:规范化并转换为小写
uint8_t *normalize_and_lowercase(const char *input) {
uint8_t *str = u8_strdup((uint8_t *)input);
size_t len = u8_strlen(str);
// NFC 规范化
size_t nfc_len;
uint8_t *nfc = u8_normalize(UNINORM_NFC, str, len, NULL, &nfc_len);
free(str);
if (nfc == NULL) return NULL;
// 转换为小写
size_t lower_len;
uint8_t *lower = u8_tolower(nfc, nfc_len, NULL, NULL, &lower_len);
free(nfc);
return lower;
}
int main() {
const char *test_strings[] = {
"Café",
"Hello, 世界",
"Привет",
"こんにちは"
};
for (int i = 0; i < 4; i++) {
uint8_t *result = normalize_and_lowercase(test_strings[i]);
if (result != NULL) {
printf("Input: %s\n", test_strings[i]);
printf("Normalized & Lowercase: %s\n", result);
printf("\n");
free(result);
}
}
return 0;
}
// 编译命令
// gcc -o test_i18n test_i18n.c -lunistring
10. 使用 pkg-config
# 使用 pkg-config 获取编译和链接标志
pkg-config --cflags libunistring
pkg-config --libs libunistring
# 编译示例
gcc -o program program.c $(pkg-config --cflags --libs libunistring)
# 检查库版本
pkg-config --modversion libunistring
# 检查库是否存在
pkg-config --exists libunistring && echo "Library found"
🧪 功能验证脚本
#!/bin/bash
# Libunistring 库验证脚本
LIBUNISTRING_LIB="build-hnp/sysroot/lib"
LIBUNISTRING_INCLUDE="build-hnp/sysroot/include"
echo "=== Libunistring 库验证 ==="
# 检查库文件
if [ -f "$LIBUNISTRING_LIB/libunistring.so.5.2.0" ]; then
echo "✓ libunistring.so.5.2.0: 存在"
file "$LIBUNISTRING_LIB/libunistring.so.5.2.0"
echo "文件大小: $(ls -lh "$LIBUNISTRING_LIB/libunistring.so.5.2.0" | awk '{print $5}')"
echo "架构信息: $(file "$LIBUNISTRING_LIB/libunistring.so.5.2.0" | grep -o "ARM aarch64")"
else
echo "✗ libunistring.so.5.2.0: 缺失"
fi
# 检查符号链接
for link in libunistring.so libunistring.so.5; do
if [ -L "$LIBUNISTRING_LIB/$link" ]; then
echo "✓ $link: 符号链接 -> $(readlink "$LIBUNISTRING_LIB/$link")"
else
echo "✗ $link: 缺失"
fi
done
# 检查主要头文件
echo ""
echo "=== 头文件验证 ==="
for header in unistr.h uniconv.h unicase.h unictype.h uninorm.h uniwidth.h; do
if [ -f "$LIBUNISTRING_INCLUDE/$header" ]; then
echo "✓ $header: 存在"
echo " 文件大小: $(ls -lh "$LIBUNISTRING_INCLUDE/$header" | awk '{print $5}')"
else
echo "✗ $header: 缺失"
fi
done
# 检查辅助头文件目录
echo ""
echo "=== 辅助头文件验证 ==="
if [ -d "$LIBUNISTRING_INCLUDE/unistring" ]; then
echo "✓ unistring/ 目录: 存在"
echo " 包含文件:"
ls -1 "$LIBUNISTRING_INCLUDE/unistring"/*.h 2>/dev/null | wc -l | xargs echo " 数量:"
else
echo "✗ unistring/ 目录: 缺失"
fi
# 测试 pkg-config(如果可用)
echo ""
echo "=== pkg-config 验证 ==="
if command -v pkg-config >/dev/null 2>&1; then
export PKG_CONFIG_PATH="build-hnp/sysroot/lib/pkgconfig:$PKG_CONFIG_PATH"
if pkg-config --exists libunistring 2>/dev/null; then
echo "✓ pkg-config 支持: 是"
echo " 版本: $(pkg-config --modversion libunistring)"
echo " CFLAGS: $(pkg-config --cflags libunistring)"
echo " LIBS: $(pkg-config --libs libunistring)"
else
echo "✗ pkg-config 支持: 否"
fi
else
echo "⚠ pkg-config: 未安装"
fi
🐛 常见问题与处理
❌ 问题 1:交叉工具链警告
- 🔍 症状:配置警告
using cross tools not prefixed with host triplet - 🔎 原因:交叉编译工具链未使用标准的三元组前缀
- ✅ 解决方法:
- 这是配置警告,不影响构建
- 工具链已正确检测和使用
- 位置:配置阶段日志
❌ 问题 2:GNU 镜像不可达
- 🔍 症状:下载失败,无法获取源码包
- 🔎 原因:网络问题或 GNU 镜像访问受限
- ✅ 解决方法:
- 手动下载源码包放置到
build-hnp/libunistring/download/libunistring-1.3.tar.xz - 通用下载逻辑支持备用镜像与重试(
wget→curl) - 位置:
build-hnp/utils/Makefrag:61-69
- 手动下载源码包放置到
❌ 问题 3:依赖关系问题
- 🔍 症状:其他包(如
libidn2)构建失败,提示找不到libunistring - 🔎 原因:
libunistring未先构建,或构建顺序不正确 - ✅ 解决方法:
- 确保
libunistring在依赖它的包之前构建 - 建议构建顺序:
libunistring → libidn2 → c-ares → openssl → curl - 位置:
build-hnp/Makefile:PKGS顺序
- 确保
❌ 问题 4:静态/共享库选择
- 🔍 症状:需要静态库但构建时使用了
--disable-static - 🔎 原因:当前配置禁用了静态库以减小体积
- ✅ 解决方法:
- 如果需要静态库,修改
CONFIG_ARGS为--enable-static --disable-shared - 或同时启用:
--enable-static --enable-shared - 位置:
build-hnp/libunistring/Makefile:6
- 如果需要静态库,修改
❌ 问题 5:链接失败
- 🔍 症状:链接错误,无法找到
libunistring符号 - 🔎 原因:链接时未指定
-lunistring或库路径不正确 - ✅ 解决方法:
- 确保链接时添加
-lunistring - 使用
pkg-config --libs libunistring获取正确的链接标志 - 检查
LD_LIBRARY_PATH是否包含库路径 - 位置:编译和链接阶段
- 确保链接时添加
❌ 问题 6:头文件未找到
- 🔍 症状:编译错误
unistr.h: No such file or directory - 🔎 原因:头文件路径未正确设置
- ✅ 解决方法:
- 确保编译时添加
-I$(prefix)/include - 使用
pkg-config --cflags libunistring获取正确的编译标志 - 检查头文件是否已安装到
sysroot/include - 位置:编译阶段
- 确保编译时添加
❌ 问题 7:架构不支持
- 🔍 症状:
Unsupported OHOS_ARCH= - 🔎 原因:传入的架构不在支持列表中
- ✅ 解决方法:
- 确保传入支持架构(
aarch64或x86_64) - 位置:
build-hnp/Makefile:1-45
- 确保传入支持架构(
🔄 重建与扩展
-
🔧 重建单包:
make -C build-hnp rebuild-libunistring # 触发子包重新编译并刷新 .stamp -
🧹 清理:
make -C build-hnp clean # 清理 sysroot、所有 .stamp 和 PKGS_MARKER -
📦 扩展:Libunistring 是 Unicode 字符串处理的基础库,被许多网络和文本处理库依赖
-
🔄 自动重建机制:
- 修改
PKGS后,check-pkgs会自动检测变化并触发重新构建 - 新增外部 HNP 包到
external-hnp目录后,会自动合并到base.hnp
- 修改
💡 实践建议
- 🔧 构建配置:使用共享库构建以减少体积,适合大多数场景
- 🚀 依赖管理:确保
libunistring在依赖它的包之前构建 - 📦 编程接口:使用
pkg-config获取正确的编译和链接标志 - 🔗 模块使用:根据需求选择合适的模块(unistr、uniconv、unicase 等)
- 🌐 国际化支持:Libunistring 为国际化应用提供了强大的 Unicode 支持
📝 结论与建议
- ✅ 本次已在 aarch64 环境下完成 Libunistring 1.3 的交叉编译与打包,libunistring 库已安装到
sysroot并纳入 HNP 包。 - 💡 为保证构建稳定:
- 使用共享库构建以减少体积
- 确保在依赖它的包之前构建
libunistring - 配置警告不影响构建,可以忽略
- 确保通过
create-hnp.sh触发构建以获得完整环境变量 - 利用
check-pkgs机制自动检测包列表变化,无需手动清理 - Libunistring 为 Unicode 字符串处理提供了强大的库支持
- 常见陷阱包括依赖关系问题、静态/共享库选择、链接失败;当前已通过构建顺序和配置参数处理
- 按网络栈阶段顺序(
libunistring → libidn2 → c-ares → openssl → curl)进行构建,确保上层网络库的功能完整与探测稳定 - 已完成 aarch64 目标下 Libunistring 的交叉编译与打包,库与头文件进入
sysroot并纳入 HNP 包
📚 以上为 Libunistring 构建的深度解读与实践记录。
更多推荐



所有评论(0)