简介:ICU4C 54.1版本的完整源码压缩包,面向Windows平台下需要借助Visual C++编译并集成国际化能力的C/C++开发者。ICU作为全球广泛采用的Unicode与全球化库,能解决字符集转换、文本布局、日期与数字格式化、排序规则和本地化资源管理等跨语言场景的核心问题。资源共4519个文件,压缩后25.7MB,主要包括C/C++源文件(.h/.cpp/.c)、构建配置脚本(.in/.ac/.sh/.pl)、Visual Studio工程文件(.sln/.vcxproj/.filters)以及测试与性能工具(.bat/.ucm/.bin),目录组织清晰;已有539人学习下载。资源自带完整构建系统,可大幅节省环境配置与库编译时间。开发者可借助配置脚本与VS工程文件快速产出libicudata、libicui18n、libicuuc等核心库,并通过内置的perf测试工具验证字符集转换、格式化与文本边界等功能的运行效果,为多语言应用提供可靠底层支撑。
1. 翻出这份 icu4c-54_1-src.zip 的人,多半是被老项目卡住了
前阵子帮某开发者把一套老 C++ 服务从旧环境搬到新容器,编译到一半冒出一堆跟auto_ptr、库顺序有关的报错,追了半天才发现代码里到处是ucnv_open、ucol_open——这套东西从多年前就一直锁在 icu4c-54_1-src.zip 这个源码包上。当时我第一反应是换成系统自带的新版 ICU,结果转码行为变了、排序结果也对不上,最后还是老老实实把这份老源码包重新构建了一遍。这份 zip 是 ICU 的 C/C++ 实现源码,解决的是编码转换、排序规则、日期格式化这些国际化底座问题。如果你手里有老项目要复现、要迁移,或者想在可控版本上做嵌入式多语言处理,这份源码包就是最稳的基准。新版库功能多,但 54.1 的行为已经被大量老代码当成了默认预期,绕过它会踩出更多意料之外的坑。
2. 源码包解压与构建:configure 参数、数据打包与安装路径
解压这一步看起来简单,但这里有一个很常见的操作失误:ICU 的源码包解压后,不能在解压出来的根目录直接敲 configure。它把所有构建相关文件放在source子目录里,configure、Makefile 的入口都在那里,很多人第一步就敲错目录,然后对着 "configure: No such file or directory" 发愣。
2.1 解压后的目录结构:source 目录才是真正的构建入口
mkdir -p ~/icu && cd ~/icu unzip icu4c-54_1-src.zip ls icu/ # 常见输出:LICENSE readme.html source cd icu/source ls -F能看到一个configure脚本和若干子目录。重点记住几个:common/对应 libicuuc,i18n/对应 libicui18n,io/对应 libicuio,tools/下面是 pkgdata、genrb、gencnval 这些构建期工具,test/是 intltest 和 cintltest 两套测试。如果你打算裁剪数据或者调整内部宏,最先接触到的就是common/unicode下的头文件。这个目录布局从 54.1 一直延续到后来的版本,所以熟悉它之后再看新版源码也不会迷路。
2.2 最常用的构建参数:静态库、数据打包与安装前缀
./configure --prefix=$HOME/icu54 \ --enable-static \ --disable-shared \ --with-data-packaging=archive--enable-static与--disable-shared的组合在 GNU/Linux 上几乎是万能解,产物是三个.a文件,部署时把静态库拷走就行;如果做插件系统或者想多个模块共享同一份 ICU,才考虑反过来用 shared。--prefix建议装到用户目录而不是/usr/local,这样一台机器上可以同时维护 54.1 和后来版本两套,运行时只改环境变量就能切换。--with-data-packaging的取值直接影响程序发布形态,后面单独说明。
提示:configure 必须在 icu/source 目录下运行,否则找不到脚本。
make -j$(nproc) make check make installmake check会跑完整测试集,首次构建建议留出时间跑一遍。如果只想快速验证核心,可以只跑test/intltest。个别精简系统因为缺少 locale 数据,测试会有零星失败,但主流程通过就能继续往下走。构建失败的排错路径也有规律:先看config.log末尾,绝大多数缺依赖问题都写在最后几十行里,最常见的两种情况是机器上没有 g++ 导致 configure 直接退出,或者缺 libtool 相关组件。ICU 54 的 configure 对 C++ 编译器版本敏感,老 GCC 和新 GCC 会报不同的错,这一点在第 5 章展开。
2.3 安装产物清单与 pkg-config 文件生成
make install 之后,安装目录会有四块内容:include/unicode/下的头文件、lib/下的静态库和 pkgconfig 文件、bin/下的工具(uconv、genrb、pkgdata 等)、share/icu/54.1/下的数据文件。数据文件是否出现,取决于打包方式:
--with-data-packaging取值 | 数据去向 | 运行时行为 | 适用场景 |
|---|---|---|---|
| archive(默认) | 单独的icudt54l.dat | 按 ICU_DATA 环境变量找文件 | 常规部署 |
| static | 编译进 libicudata.a | 无需外部数据文件 | 单文件、嵌入式 |
| plain | 散文件放在 share/icu 下 | 按目录加载 | 需要手动裁剪调试 |
archive 是默认也最常见的;static 适合做成单文件程序;plain 适合调试内部数据。我一般给嵌入式设备编译时选 static,给桌面或服务端程序保持 archive。注意--enable-static是编译静态库,--with-data-packaging=static是把数据打进静态库,两者是不同维度,别混在一起。
安装完成后检查 pkg-config 文件是否生成。正常情况下$prefix/lib/pkgconfig下会有icu-uc.pc、icu-i18n.pc、icu-io.pc三个文件。如果 configure 时用了非标准 prefix,pkg-config 找不到这些.pc文件是常事,设置PKG_CONFIG_PATH指向该目录即可,后面集成项目会用到。
3. 把 54.1 接进你的 C/C++ 项目:CMake、Makefile 与链接顺序
库构建好了,接下来就是把它接进项目。这一章的命令按静态库场景写,如果用的是动态库,链接顺序和参数基本一致,只是-ldl的依赖可能不再需要。ICU 和普通库有个显著区别:它的导出符号带着版本后缀,而且静态库链接顺序非常敏感,这两点处理不好会浪费大量时间。
3.1 手写编译命令:cflags、libs 与静态库链接顺序
export ICU_PREFIX=$HOME/icu54 g++ demo.cpp -o demo \ -I$ICU_PREFIX/include \ -L$ICU_PREFIX/lib \ -licui18n -licuuc -licudata \ -lpthread -ldl-I让编译器找到 unicode/utypes.h;-L指定库查找路径;-licui18n -licuuc -licudata是链接顺序,i18n 依赖 uc,uc 依赖 data,顺序不能反过来。后面两个-lpthread -ldl在 54.1 时代是必须的,因为数据加载和线程初始化依赖 POSIX 线程与动态链接器。如果只做转码,只链-licuuc -licudata就够了;用了 Collator 或日期格式化,就必须带-licui18n。
更省心的做法是交给 pkg-config:
export PKG_CONFIG_PATH=$HOME/icu54/lib/pkgconfig g++ demo.cpp -o demo $(pkg-config --cflags --libs icu-uc icu-i18n)pkg-config 会把库顺序、附加的-pthread、-ldl全部处理好。注意这里传了icu-uc和icu-i18n两个 pc 文件,如果你只用转码,icu-uc就够了;用排序就必须追加icu-i18n。
3.2 CMake 集成两套方案:find_package 与 pkg-config 回退
方案 A,依赖系统有 pkg-config 工具,CMake 3.6 以上支持IMPORTED_TARGET:
find_package(PkgConfig REQUIRED) pkg_check_modules(ICU REQUIRED IMPORTED_TARGET icu-uc icu-i18n) add_executable(demo demo.cpp) target_link_libraries(demo PRIVATE PkgConfig::ICU)方案 B,不依赖 pkg-config,显式指定路径,交叉编译时更直接:
set(ICU_ROOT "$ENV{HOME}/icu54") find_path(ICU_INCLUDE_DIR NAMES unicode/utypes.h HINTS ${ICU_ROOT}/include) find_library(ICU_I18N_LIBRARY NAMES icui18n HINTS ${ICU_ROOT}/lib) find_library(ICU_UC_LIBRARY NAMES icuuc HINTS ${ICU_ROOT}/lib) find_library(ICU_DATA_LIBRARY NAMES icudata HINTS ${ICU_ROOT}/lib) add_executable(demo demo.cpp) target_include_directories(demo PRIVATE ${ICU_INCLUDE_DIR}) target_link_libraries(demo PRIVATE ${ICU_I18N_LIBRARY} ${ICU_UC_LIBRARY} ${ICU_DATA_LIBRARY})方案 A 在容器环境里如果没装 pkg-config 会直接失败;方案 B 只调 find_path 和 find_library,我把 ICU_ROOT 显式写出来,避免 CMake 去系统目录碰运气。实际项目里我优先用 B,因为构建机环境不可控,A 少装一个工具都跑不起来。
3.3 链接期符号规则:为什么所有函数都带着 _54
ICU 的导出符号默认带版本后缀,这是它区别于普通库的重要机制:
nm $HOME/icu54/lib/libicuuc.a | grep ' T ucnv_open' | head # 输出形如:0000000000123ab0 T ucnv_open_54库里导出的是ucnv_open_54,头文件通过urename.h里的一组宏把ucnv_open映射到ucnv_open_54。这个机制允许同一进程内加载多个 ICU 版本而不冲突。如果客户端编译时定义了U_DISABLE_RENAMING,宏映射失效,头文件声明的是不带后缀的名字,库里却只有带后缀的符号,链接阶段就会报 undefined reference。第 5 章会专门讲这个坑,在这里只要记住:客户端不要额外定义U_DISABLE_RENAMING,头文件与库必须来自同一套 54.1 安装目录。
4. 高频功能实战:转码与排序规则的正确用法
ICU 54.1 用得最多的两块是字符编码转换和排序规则。转码处理的是“字节序列 ↔ UTF-16 代码单元”,排序解决的是“字符串在某种语言习惯下谁先谁后”。这两个功能代码量不大,但参数细节多,一旦出错不是编译期能发现的,必须运行期验证。
4.1 ucnv 转码回路:从 UTF-8 到 UTF-16 再到 UTF-8
#include <stdio.h> #include <unicode/utypes.h> #include <unicode/uerror.h> #include <unicode/ucnv.h> int main(void) { UErrorCode status = U_ZERO_ERROR; UConverter *conv = ucnv_open("UTF-8", &status); if (U_FAILURE(status)) { fprintf(stderr, "ucnv_open: %s\n", u_errorName(status)); return 1; } const char *utf8_in = "ICU 54 转码验证"; UChar u16[64]; int32_t len = ucnv_toUChars(conv, u16, 64, utf8_in, -1, &status); if (U_FAILURE(status)) { fprintf(stderr, "toUChars: %s\n", u_errorName(status)); ucnv_close(conv); return 1; } printf("code unit 数: %d\n", len); for (int32_t i = 0; i < len; i++) { printf("U+%04X ", u16[i]); } printf("\n"); ucnv_close(conv); return 0; }编译时如果只做转码,不需要链接 i18n:
gcc ucnv_demo.c -o ucnv_demo -I$HOME/icu54/include \ -L$HOME/icu54/lib -licuuc -licudata注意ucnv_toUChars的第五个参数传-1,表示输入以 NUL 结尾、让函数自己数长度;如果数据不一定以 NUL 结尾,传实际字节数更安全。destCapacity是 64,如果输出超过上限,函数会先返回U_BUFFER_OVERFLOW_ERROR,同时把需要的长度写进len。常见做法是先传 NULL 调一次拿长度,再分配缓冲,最后再真正转码。
4.2 Collator 排序规则:强度、地区与比较结果
#include <iostream> #include <unicode/coll.h> #include <unicode/utypes.h> int main() { UErrorCode status = U_ZERO_ERROR; icu::Collator* coll = icu::Collator::createInstance("zh_CN", status); if (U_FAILURE(status)) { std::cerr << "createInstance: " << u_errorName(status) << std::endl; return 1; } coll->setStrength(icu::Collator::TERTIARY); icu::UnicodeString s1("app"), s2("apple"); UCollationResult r = coll->compare(s1, s2); if (r == UCOL_LESS) std::cout << "app 在 apple 前面" << std::endl; else if (r == UCOL_EQUAL) std::cout << "相等" << std::endl; else std::cout << "app 在 apple 后面" << std::endl; delete coll; return 0; }编译时要用 g++ 并链接 i18n:
g++ collator_demo.cpp -o collator_demo \ $(pkg-config --cflags --libs icu-i18n)比较强度从小到大是 PRIMARY、SECONDARY、TERTIARY、QUATERNARY、IDENTICAL。PRIMARY 只看基本字符,a和A视为相等;TERTIARY 会区分大小写。字符串排序场景里,我一般用 TERTIARY 兼顾可读性和准确性;如果要字典序且不区分大小写,把强度设为 SECONDARY 即可。
createInstance的第一个参数是 locale 字符串。zh_CN会按中文默认排序(多数环境是拼音),想用笔画排序可以写成zh@collation=stroke这种关键字写法。locale 名写错不会直接崩溃,ICU 会 fallback 到 root,但排序行为就会变成英文习惯,这是很多项目排序结果“看起来不对”的隐藏原因。
4.3 资源释放与线程隔离:每个线程维护自己的转换器
UConverter 不是线程安全的,同一实例不能同时在两个线程里调用ucnv_toUChars,常见做法是按线程创建,或用线程局部存储缓存。Collator 的只读compare在共享场景下问题不大,但setStrength之后再共享就要小心,我一般每个线程 new 一个。另外,ICU 54 用的是裸指针,Collator::createInstance返回的指针必须delete,UConverter必须ucnv_close,没有智能指针包装,忘了就泄露。不要在信号处理函数里调用 ICU——它的数据加载可能触发文件 I/O,在信号上下文里会出无法预估的问题。
5. 避坑记录:ICU 54.1 在不同工具链上的五个翻车点
54.1 是多年前的版本,没赶上后来 GCC 和 MSVC 的改动。下面五个坑我基本都亲手踩过,按现象、原因、解决写清楚,每条都能省下至少半天排查时间。
5.1 C++17 编译错误:auto_ptr 被移除
现象:用较新的 GCC 编译时,报一堆std::auto_ptr相关错误,提示这个类型已不存在。
原因:ICU 54.1 的部分头文件和 i18n 源码里还用了 C++98 时代的std::auto_ptr,C++17 标准已经把它从标准库移除,新 GCC 默认按 C++17 编译,于是直接找不到这个类型。
解决:configure 时强制指定 C++11,其他参数不变:
CXXFLAGS="-std=c++11" ./configure --prefix=$HOME/icu54 \ --enable-static --disable-shared如果编译时还有别的兼容告警,可以在 CXXFLAGS 里追加-Wno-deprecated和-Wno-deprecated-declarations,但不建议无脑关告警,先看清楚是哪一类问题。
5.2 链接失败:版本符号 _54 与 U_DISABLE_RENAMING 的博弈
现象:编译客户端代码没问题,链接时报一堆undefined reference to 'ucnv_open_54',或者反过来报'ucnv_open' is undefined。
原因:ICU 的导出符号默认带_54后缀,这是版本隔离机制。头文件通过urename.h把ucnv_open宏映射到ucnv_open_54。如果客户端定义了U_DISABLE_RENAMING,宏映射失效,头文件声明的是不带后缀的名字,库里却没有这个符号。
解决:客户端代码不要定义U_DISABLE_RENAMING;如果是从老项目里拷来的 CPPFlags,检查有没有这个宏。拿不准时用nm libicuuc.a | grep ucnv_open看实际导出符号,确认是否带_54后缀。
5.3 运行时报错:找不到 icudt54l.dat
现象:程序编译链接都通过,运行时提示file not found: icudt54l.dat,或者干脆显示内存不足、直接退出。
原因:archive 打包模式下数据文件与库分离。程序运行时 ICU 会按编译期路径找数据文件,换机器或换目录后路径失效。
解决:部署时带上share/icu/54.1/icudt54l.dat,并设置环境变量ICU_DATA指向它。想彻底消除这个外部依赖,就在 configure 时把--with-data-packaging改为static,重新编译库,数据会直接编进 libicudata.a。
5.4 交叉编译:宿主机不能执行目标二进制
现象:交叉编译到 ARM 平台,make 进行到一半执行某个工具时,报cannot execute binary file,构建中断。
原因:ICU 构建过程中需要运行 genrb、pkgdata 等工具。交叉编译出的这些工具是 ARM 二进制,无法在 x86 宿主机上执行。
解决:交叉编译的 configure 命令要加--with-cross-build=宿主机上已经编译好的同一版本 ICU 的 source 目录。这个参数让构建系统继续使用宿主机上能执行的工具版本,交叉编译链只负责生成目标库。注意指向的是源码目录,不是安装目录。
5.5 Windows 老工程:VS 升级引发的构建连锁反应
现象:Windows 上打开源码里 allinone 的 VS 工程,较新 VS 升级工程后编译报大量 C4996,或者链接阶段找不到符号。
原因:54.1 在 Windows 上只有 MSVC 工程路径。老工程文件被新版 IDE 升级时,默认开启了更严格的安全检查和警告级别,把fopen这类 CRT 调用直接当成错误暴露出来。
解决:在工程属性里关闭 SDL 检查、把警告设置为不视作错误,再选择 Release 配置重新编译。如果不想折腾 IDE 工程,也可以改用 MinGW 环境跑 configure 流程,Linux 上的脚本路径在 Windows 下同样能用,我实际走这条路的成功率更高。
6. 收尾验证:写一个最小程序确认库与数据都完好
无论前面构建多顺利,最后都要跑一个最小验证程序,确认库、头文件、数据文件三者版本一致。下面这段代码只依赖 icu-uc,不涉及排序,适合作为冒烟测试:
#include <stdio.h> #include <unicode/utypes.h> #include <unicode/uerror.h> #include <unicode/ucnv.h> int main(void) { UErrorCode status = U_ZERO_ERROR; UConverter *conv = ucnv_open("UTF-8", &status); if (U_FAILURE(status)) { fprintf(stderr, "open failed: %s\n", u_errorName(status)); return 1; } const char *msg = "icu54 check"; UChar buf[64]; int32_t n = ucnv_toUChars(conv, buf, 64, msg, -1, &status); if (U_SUCCESS(status)) { printf("ICU %s, units=%d\n", U_ICU_VERSION, n); } ucnv_close(conv); return 0; }编译运行:
export PKG_CONFIG_PATH=$HOME/icu54/lib/pkgconfig gcc verify.c -o verify $(pkg-config --cflags --libs icu-uc) ./verify预期输出ICU 54.1, units=11,其中 units 表示转换后的 UTF-16 code unit 数量。如果输出版本不对或 units 对不上,优先检查头文件路径是不是指向了别的 ICU 版本。再顺手验证一下转换工具和数据文件是否正常:
echo hello | $HOME/icu54/bin/uconv -f UTF-8 -t UTF-8正常会原样输出 hello,这一步能确认 icudt54l.dat 被正确加载。如果想连排序一起验证,就把第 4 章的 Collator 例子编出来跑一遍,两个输出都对,这套 54.1 基本就可以放心投入使用了。
从那以后我每次接到老 C++ 项目,第一件事就是问清楚项目锁的是哪一版 ICU,再决定构建参数。这份 54.1 源码包也因此一直留在工具箱里当基准参照。希望这份笔记帮到你。
本文还有配套的精品资源,点击获取