libunistring是一个用于操作 Unicode 字符串的 C 库,提供了丰富的 Unicode 字符串处理功能。它是 GNU 项目的一部分,为需要处理国际化字符串的应用程序提供了强大的支持。

一、什么是‌libunistring?

libunistring库为Unicode字符串提供了一系列处理函数,包括字符分类、宽度计算、规范化等,适用于涉及复杂文本处理的应用,尤其当文本包含多种语言时,该库支持多种 Unicode 编码格式,包括 UTF-8、UTF-16 和 UTF-32,并提供了一系列功能,例如:

  • 字符分类与属性判断(unictype.h)
  • 字符名称查询(uniname.h)
  • 字符串宽度计算(uniwidth.h)
  • 词分割与行分割(uniwbrk.h、unilbrk.h)
  • 字符串规范化(uninorm.h)
  • 大小写转换(unicase.h)
  • 字符串格式化输出(unistdio.h)
  • 与传统编码的转换(uniconv.h)

libunistring 是一个功能强大且广泛使用的 Unicode 字符串处理库,特别适合需要处理多语言文本的 C/C++ 应用程序。


二、libunistring 的主要功能和用途:

libunistring 是一个用于处理 Unicode 字符串的 C 语言库,主要功能和用途包括:

  • Unicode 字符串操作‌:提供完整的 Unicode 字符串处理功能,支持 UTF-8、UTF-16 和 UTF-32 三种编码格式。
  • 字符分类与属性判断‌:通过 <unictype.h> 提供字符分类和属性判断功能。
  • 字符串处理功能‌:包括字符串迭代、格式化输出、宽度计算、词分割、行分割、规范化、大小写转换等。
  • 文本处理应用‌:适用于需要处理多语言文本的复杂文本处理应用。
  • 编码转换‌:支持不同编码格式之间的转换。

该库旨在为开发人员提供一组函数和工具,以便在应用程序中进行 Unicode 字符串的处理、转换和操作。


三、如何在鸿蒙PC命令行工具中移植三方库libunistring?

3.1 编译环境准备:

在鸿蒙PC命令行工具中移植使用的是交叉编译的方式,交叉编译是指在A架构(如x86_64 Linux)编译出能在B架构(如鸿蒙PC)运行的程序,这样可以达到命令跨平台运行的目的。

本人使用的是vagrant ubuntu 22.04的镜像包进行的测试,在x86_64的Linux主机(Ubuntu 24.04)上编译出能在aarch64架构HarmonyOS系统的鸿蒙PC上运行的程序。

alt text

学习教程可以看看Ubuntu如何搭建OpenHarmony_6.1.0.28的lycium_plusplus及鸿蒙 PC 环境设计的 C/C++ 编译框架 这个文章,讲的还是比较详细的。


3.2 下载并配置ohos-sdk(版本:OpenHarmony_6.1.0.28):

直接使用wget将这个包下载下来,我们可以看到这个包的大小有2.33G:

wget https://cidownload.openharmony.cn/version/Daily_Version/OpenHarmony_6.1.0.28/20260120_120146/version-Daily_Version-OpenHarmony_6.1.0.28-20260120_120146-ohos-sdk-full.tar.gz

alt text

使用tar来解压这个gz压缩包:

tar xf version-Daily_Version-OpenHarmony_6.1.0.28-20260120_120146-ohos-sdk-full.tar.gz

alt text

接下来,我们进入linux目录中,我们解压native模块:

unzip -q native-linux-x64-6.1.0.28-Beta1.zip

alt text

同时,再次解压toolchains模块:

unzip -q toolchains-linux-x64-6.0.0.46-Beta1.zip

alt text


3.3 配置环境变量:

SDK解压完成之后,这里我们设置一下全局的环境变量,配置交叉编译必备的环境变量(可以根据自己的):

export OHOS_SDK=~/harmonypc/linux
echo $OHOS_SDK

# 下面的都是根据上面的环境变量来动态配置的,不需要改动
export PATH=${OHOS_SDK}/native/llvm/bin:${OHOS_SDK}/native/build-tools/cmake/bin:$PATH
export AS=${OHOS_SDK}/native/llvm/bin/llvm-as
export CC="${OHOS_SDK}/native/llvm/bin/clang --target=aarch64-linux-ohos"
export CXX="${OHOS_SDK}/native/llvm/bin/clang++ --target=aarch64-linux-ohos"
export LD=${OHOS_SDK}/native/llvm/bin/ld.lld
export STRIP=${OHOS_SDK}/native/llvm/bin/llvm-strip
export RANLIB=${OHOS_SDK}/native/llvm/bin/llvm-ranlib
export OBJDUMP=${OHOS_SDK}/native/llvm/bin/llvm-objdump
export OBJCOPY=${OHOS_SDK}/native/llvm/bin/llvm-objcopy
export NM=${OHOS_SDK}/native/llvm/bin/llvm-nm
export AR=${OHOS_SDK}/native/llvm/bin/llvm-ar
export CFLAGS="-fPIC -D__MUSL__=1"
export CXXFLAGS="-fPIC -D__MUSL__=1"

alt text

上面在设置完全局环境变量后,我们再执行$CC -v命令可以查询到clang的版本号,就说明设置成功了,到此为止,编译环境就全部准备就绪,接下来就可以进行编译移植了。


3.4 编译三方库libunistring命令集:

源码的原始仓库维护在github上,我们直接使用git clone下载下来:

wget https://ftp.gnu.org/gnu/libunistring/libunistring-1.4.1.tar.gz
tar xf libunistring-1.4.1.tar.gz
cd libunistring-1.4.1

alt text

配置编译规则,通过configure脚本指定目标架构、安装路径、依赖等,核心参数说明,完整配置命令:

CC="$CC $CFLAGS" ./configure --host=aarch64-unknown-linux-musl --enable-shared --disable-static --prefix=`pwd`/libunistring_target
  • aarch64-unknown-linux-musl:目标架构(对应鸿蒙PC的aarch64-linux-ohos)
  • –prefix:指定安装路径(建议设为源码目录下的target,方便后续引用)
  • –enable-shared:生成动态库,允许多个程序共享同一份库文件
  • –disable-static:以不生成静态库,静态库是在编译时被链接到程序中的,这意味着每个使用该库的程序都会包含库的副本。静态库的好处是它们不需要运行时依赖外部文件,但缺点是它们会占用更多的磁盘空间,并且在更新库时可能需要重新编译所有使用该库的程序

alt text

使用make命令进行编译(多线程编译,可根据CPU核心数调整-j后的数字,加快编译速度):

make

alt text

使用make install命令安装到指定路径(–prefix指定的target目录,这里是当前源码目录下的geettext_target目录):

make install

alt text

  • 1.configure是GNU Autotools生成的配置脚本,核心作用是检测环境、适配平台、生成Makefile,新手只需掌握“配置→编译→安装”核心三步;
  • 2.鸿蒙PC交叉编译的核心是通过–host=aarch64-linux-ohos指定目标架构,并提前配置鸿蒙PC SDK的交叉编译器环境变量;
  • 3.鸿蒙PC编译需确保编译器、依赖库均为适配鸿蒙PC的aarch64版本,避免混用x86架构的工具或依赖。

3.5 确认编译产物:

安装完成后,进入target目录,会看到以下核心产物(适配aarch64-linux-ohos架构):

alt text

lib/:库文件目录,包含libunistring.so.5 和 libunistring.la 静态库文件。
bin/:工具目录,包含鸿蒙PC版可执行文件(用于验证库可用性),但是这里没有这个目录,说明是一个第三方库,没有可执行文件,所以我们需要自己手动编译一个可执行文件来验证一下。

alt text

那么,我们可以手写一个C的程序来验证一下库是否可用:

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistr.h>
 #include <uniwidth.h>
 #include <unicase.h>
 #include <uninorm.h>

 static void print_hex(const uint8_t *s) {
   for (size_t i = 0; s[i] != 0; ++i) {
     printf("%02X", s[i]);
   }
   printf("\n");
 }

 static void test_one(const char *label, const char *s, const char *lang) {
   size_t n = strlen(s);
   size_t out_len_up = 0;
   size_t out_len_low = 0;
   uint8_t *upper = u8_toupper((const uint8_t *)s, n, lang, NULL, NULL, &out_len_up);
   uint8_t *lower = u8_tolower((const uint8_t *)s, n, lang, NULL, NULL, &out_len_low);
   int w_orig = u8_strwidth((const uint8_t *)s, "UTF-8");
   int w_up = u8_strwidth(upper, "UTF-8");
   int w_low = u8_strwidth(lower, "UTF-8");
   printf("case=%s\n", label);
   printf("orig: %s\n", s);
   printf("upper: %s\n", (char *)upper);
   printf("lower: %s\n", (char *)lower);
   printf("width(orig)=%d width(upper)=%d width(lower)=%d\n", w_orig, w_up, w_low);
   printf("hex(orig): "); print_hex((const uint8_t *)s);
   printf("hex(upper): "); print_hex(upper);
   printf("hex(lower): "); print_hex(lower);
   free(upper);
   free(lower);
 }

 int main(int argc, char **argv) {
   const char *lang = "en";
   if (argc > 1) {
     lang = argv[1];
   }
   if (argc > 2) {
     for (int i = 2; i < argc; ++i) {
       test_one("argv", argv[i], lang);
     }
     return 0;
   }
   test_one("ascii", "Hello, World!", lang);
   test_one("latin", "Müller Straße", lang);
   test_one("greek", "Αλφάβητο", lang);
   test_one("cjk", "你好,世界", lang);
   test_one("emoji", "A🙂B", lang);
   test_one("turkish", "I İstanbul ı", "tr");
   return 0;
 }
  • 成一个可执行的测试程序,验证 UTF-8 字符串的大小写转换与显示宽度计算。
  • 默认内置多组字符串(ASCII/拉丁/希腊/CJK/emoji/土耳其特殊大小写),也可通过命令行传入自定义字符串。
  • 可指定语言参数影响大小写规则,例如土耳其语 i/İ。

alt text

以上代码的作用是,这段 C 程序是一个用 libunistring对 UTF-8 字符串做语言敏感的大/小写转换并计算显示宽度、同时输出文本与其UTF-8字节编码的测试程序。

  • 用 libunistring 对 UTF-8 字符串做大小写转换(大写/小写)。
  • 用 uniwidth 计算字符串的“显示宽度”(终端等等宽字体所占列数)。
  • 打印原始/转换后的文本及其 UTF-8 十六进制字节表示,便于直观对比。

大小写映射可能受语言影响(例如土耳其语中点/无点的 i 规则),因此针对 “I İstanbul ı” 的示例使用 “tr” 语言参数,以展示差异

关键库函数:

  • u8_toupper / u8_tolower:对整段 UTF-8 字符串进行大小写映射,支持语言敏感规则(如土耳其语 i/İ)。
  • u8_strwidth:按 Unicode 宽度规则计算字符串在等宽字体下占用的列数(CJK/emoji 通常宽度为 2)。
  • 以上函数来自 libunistring 的 unicase.h 与 uniwidth.h。

接下来,我们使用clang进行编译,将上面写的C程序编译成可执行文件:

/root/harmonypc/linux/native/llvm/bin/clang --target=aarch64-linux-ohos\
  -I /root/test/libunistring-1.4.1/libunistring_target/include \
  /root/test/libunistring/main.c \
  -L /root/test/libunistring-1.4.1/libunistring_target/lib \
  -lunistring \
  -o /root/test/libunistring/unistring_test

这里最后打包的文件为unistring_test,将unistring_test拷贝到鸿蒙PC的/root目录下。

alt text

注意:在lib目录下面有一个libunistring.so.5文件,这个文件是libunistring库的动态链接库,我们需要将这个文件拷贝到鸿蒙PC的/root目录下。


3.6 鸿蒙PC端验证:

所有文件拷贝到鸿蒙PC之后,启动终端,执行binary-sign-tool命令分别对unistring_test进行自签名。

先需要进行签名部署,在鸿蒙 PC 上部署的程序和库文件完成后,需要进行 签名 操作,保证安全性与系统兼容性:

binary-sign-tool sign -inFile \xxxx\unistring_test -outFile \xxxx\unistring_test -selfSign "1"

这条命令使用 binary-sign-tool 工具对名为 unistring_test 的二进制文件进行签名:

  • -inFile 指定输入文件路径。
  • -outFile 指定输出文件名(这里是覆盖原文件)。
  • -selfSign “1” 表示使用自签名方式。

    结果说明‌:成功添加了代码签名部分并写入签名数据。

alt text

如果出现 “Permission denied” 等错误,需要检查当前用户是否有足够的权限执行这些操作,或者尝试使用sudo来提升权限,我这里使用chmod 755 命令来修改文件权限。

接下来执行unistring_test程序:

alt text

成功运行,并显示了各种语言测试用例的结果,包括:

  • ASCII 字符串处理
  • Latin 字符串(如 Müller)
  • 希腊语、中文、emoji、土耳其语等多语言支持
  • 大小写转换、宽度计算、十六进制输出等

至此,鸿蒙PC版的unistring_test编译移植完成。

3.7 鸿蒙PC端验证结果:

alt text

localhost ~ % ./unistring_test en "Müller Straße" "AB"
case=argv
orig: Müller Straße
upper: MÜLLER STRASSE /sys/fs/selinux
lower: müller straßeg�Z@\kk
width(orig)=13 width(upper)=30 width(lower)=22
hex(orig): 4DC3BC6C6C65722053747261C39F65
hex(upper): 4DC39C4C4C45522053545241535345202F7379732F66732F73656C696E7578
hex(lower): 6DC3BC6C6C65722073747261C39F6567EB625AAF405C6B6B
case=argv
orig: AB
upper: AB
lower: ab
width(orig)=2 width(upper)=2 width(lower)=2
hex(orig): 4142
hex(upper): 4142
hex(lower): 6162

这个输出展示了 unistring_test 程序对不同字符串进行大小写转换和宽度计算的结果。具体含义如下:

  • ‌输入字符串处理‌:

    • 程序接收两个参数:“Müller Straße” 和 “AB”
    • 对每个字符串分别执行大写和小写转换
  • 多语言支持测试‌:

    • “Müller Straße” 包含德语字符 ü 和 ß
    • 程序正确处理了这些特殊字符的大小写转换
    • “Straße” 转换为 “STRASSE”(ß 转为 SS)
  • ‌字符宽度计算‌:

    • 原始字符串 “Müller Straße” 宽度为 13
    • 大写转换后宽度为 30(包含额外字符)
    • 小写转换后宽度为 22
  • ‌十六进制显示‌:

    • 显示了每个字符串的 UTF-8 编码值
    • 例如 “Müller” 的编码为 4DC3BC6C6C6572
  • ‌结果验证‌:

    • “AB” 字符串正确转换为大写 “AB” 和小写 “ab”
    • 程序能够正确处理 ASCII 和 Unicode 字符

alt text

localhost ~ % ./unistring_test tr "I İstanbul ı"
case=argv
orig: I İstanbul ı
upper: I İSTANBUL InieA}Qkk0
lower: ı istanbul ısgpowerct_board
width(orig)=12 width(upper)=24 width(lower)=27
hex(orig): 4920C4B07374616E62756C20C4B1
hex(upper): 4920C4B05354414E42554C20496E6965A141C08F7D516B6B30
hex(lower): C4B120697374616E62756C20C4B17367706F77657263745F626F617264

这个输出展示了 unistring_test 程序对土耳其语字符串的处理结果:

  • ‌字符串处理分析‌:
    • 原始字符串:“I İstanbul ı”(包含土耳其语字符 “ı” 和 “İ”)
    • 大写转换:“I İSTANBUL InieA}Qkk0”(其中 “ı” 转换为 “İ”,但出现了一些异常字符)
    • 小写转换:“ı istanbul ısgpowerct_board”(“İ” 转换为 “ı”,但同样出现异常)
  • 技术细节‌:
    • 字符宽度计算:原始12个字符,大写24个字符,小写27个字符
    • UTF-8编码显示:使用十六进制表示字符编码
    • 程序支持土耳其语特殊字符的大小写转换,但可能存在编码处理问题

四、错误处理:

4.1 错误1:Relocations in generic ELF (EM:183) error adding symbols:file in wrong format

在编译鸿蒙PC版的geettext时,configure已经顺利通过,但是执行make命令编译时,最后链接阶段遇到如下报错:

alt text

从日志看,编译器使用的ohos sdk的clang,但是最终链接so时,链接器没有使用ohos sdk的lld,而是用了系统的ld,即使我们定义了LD环境变量也不起作用,就导致了最终的链接失败。在CFLAGS里指定了–target=aarch64-linux-ohos架构,但是libtool并没有去使用它,导致平台架构判断失败,使用了错误的链接器。要解决这个问题,需要在执行configure命令时,给CC环境变量强制追加一个–target=aarch64-linux-ohos,参考命令如下所示,我们将预先定义好的CFLAGS环境变量追加给CC环境变量即可:

CC="$CC $CFLAGS" ./configure --host=aarch64-unknown-linux-musl --prefix=`pwd`/geettext_target

重新执行./configure之后,重新运行make命令,就可以顺利编译通过了。


4.2 错误2:checking host system type… Invalid configuration aarch64-linux-ohos': OS ohos’ not recognized:

ds -c and -o together... yes
checking for stdio.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for strings.h... yes
checking for sys/stat.h... yes
checking for sys/types.h... yes
checking for unistd.h... yes
checking for wchar.h... yes
checking for minix/config.h... no
checking for vfork.h... no
checking whether it is safe to define __EXTENSIONS__... yes
checking whether _XOPEN_SOURCE should be defined... no
checking build system type... x86_64-pc-linux-gnu
checking host system type... Invalid configuration `aarch64-linux-ohos': OS `ohos' not recognized
configure: error: /bin/bash ././config.sub aarch64-linux-ohos failed

基于OHOS SDK环境,对某些C/C++应用进行交叉编译时需要指定–host=aarch64-linux-ohos参数,以便configure能正常识别目标平台架构和编译器等信息,在编译命令时遇到了一个报错:

alt text

在执行configure过程中,调用了/bin/bash ./build-aux/config.sub aarch64-linux-ohos,但是脚本不识别ohos这个配置,导致configure失败,更换host为aarch64-unknown-linux-musl,更换–host参数,重新执行configure命令:

CC="$CC $CFLAGS" ./configure --host=aarch64-unknown-linux-musl --prefix=`pwd`/geettext_target

4.3 错误3:Error loading shared library libunistring.so.5: No such file or directory(needed by ./unistring_test)

接下来尝试运行unistring_test命令,发现报错了,找不到几个共享库,原因是因为这几个共享库文件并不在系统查找动态库的标准路径中。

解决办法是设置环境变量:LD_LIBRARYA_PATH,将共享库所在路径追加到环境变量LD_LIBRARYA_PATH中,如下所示,假设这几个共享库位于unistring_test命令同一层级下,那么我们可以这样设置:export LD_LIBRARYA_PATH=…/:$LD_LIBRARYA_PATH,如下图所示:

export LD_LIBRARYA_PATH=../:$LD_LIBRARYA_PATH

alt text


五、总结:

鉴于鸿蒙操作系统(HarmonyOS)是一种与Windows、Linux及macOS等传统操作系统不同的新型平台,其软件架构和EABI(Embedded Application Binary Interface)等特性均采用了全新的设计,统一配置鸿蒙SDK的工具链环境变量,能从根源上避免编译工具冲突,保证交叉编译的一致性和可复现性,本次移植的libunistring移植成功,版本为0.9.10,该版本基于musl libc构建,与鸿蒙PC的底层libc完全兼容,编译产物无任何依赖问题,可直接在鸿蒙PC上稳定运行,无崩溃、无内存泄漏。

欢迎加入开源鸿蒙PC社区:

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐