欢迎加入【开源鸿蒙PC社区】,一起共建鸿蒙化C/C++三方库生态。

欢迎在【PC社区】平台贡献你的项目。

资源 地址
上游仓库地址 https://github.com/protocolbuffers/protobuf
适配源码地址 https://atomgit.com/unisources/protobuf
AtomCode 文档 https://atomcode.atomgit.com
lycium 交叉编译工具链 https://atomgit.com/OpenHarmonyPCDeveloper/lycium_plusplus
harmonyos-app-integration Skill https://atomgit.com/atomcode/skills/skills/native/harmonyos-app-integration/
集成示例源码 https://atomgit.com/unisources/ProtobufSample

image-20260606223239991

一、背景

1.1 什么是 AtomCode

AtomCode 是一款支持多 LLM 提供商的 AI 编码代理(Coding Agent),运行在终端中,能够理解项目上下文、执行 Shell 命令、读写文件,并拥有一套可扩展的 Skills(技能模板) 系统。

与传统 AI 编程助手不同,AtomCode 的 Skills 是可复用的专家知识包。每个 Skill 封装了特定领域的操作模式、注意事项、代码模板和常见错误修复方案。当 AI 检测到相关上下文时,自动加载对应的 Skill,避免"凭空猜测"导致的错误。

1.2 什么是 Skills

Skills 是 AtomCode 的知识增强模块,本质上是 Markdown 文档,包含:

要素 说明
YAML 元数据 name、description — 用于自动识别触发场景
速查表 关键配置项、参数说明、最佳实践
代码模板 可直接复用的代码片段
常见错误 错误模式 + 根因分析 + 修复方案
交叉引用 与其他 Skills 的关系

本次集成主要使用了以下 Skill:

Skill 位置 作用
harmonyos-app-integration porting/lycium-app-integration/SKILL.md NAPI 集成全流程模板
lycium-troubleshooting ops/lycium-troubleshooting/SKILL.md undefined symbol 错误诊断

1.3 集成目标

项目 说明
已在 lycium 完成交叉编译的库 protobuf v25.9 → libprotobuf.a
工程 OHOSProtobufSample
目标架构 arm64-v8a
需要集成的产物 libprotobuf.a + libabsl_all.a + ~700 个头文件
对外暴露的 NAPI 接口 4 个函数

二、Step-by-Step 实战

步骤 1:工程结构初始化

从 NapiTemplate 模板创建工程:

rsync -a --exclude='.hvigor/' --exclude='.cxx/' --exclude='oh_modules/' \
  /home/hoapp/NapiTemplate/ /home/hoapp/OHOSProtobufSample/

生成的模板目录:

OHOSProtobufSample/
├── AppScope/app.json5          # bundleName 配置
├── entry/
│   ├── build-profile.json5     # ABI filters, native 编译选项
│   └── src/main/
│       ├── cpp/
│       │   ├── CMakeLists.txt  # CMake 构建配置
│       │   └── napi_init.cpp   # NAPI 桥接代码
│       └── ets/pages/Index.ets # ArkUI 页面
├── build-profile.json5         # SDK 版本、签名
└── hvigor/                     # 构建工具配置

修改 app.json5 中的 bundleName:

"bundleName": "com.unisources.protobuf"

步骤 2:加载 Skills 识别集成方案

harmonyos-app-integration Skill 提供了标准的 NAPI 集成模板,包含四层架构:

ArkUI (ets/)           → UI + 用户交互
NAPI TypeScript (d.ts) → 类型安全的 JS API 声明
NAPI C++ (cpp/)         → 原生桥接 (napi_init.cpp)
第三方库                → 交叉编译产物 (.a + 头文件)

Skill 提供的 CMakeLists.txt 模板:

target_link_libraries(entry PUBLIC
    libace_napi.z.so           # OHOS NAPI 运行时(必需)
    ${THIRDPARTY_DIR}/lib/libprotobuf.a
    ${THIRDPARTY_DIR}/lib/libabsl_all.a
    ${THIRDPARTY_DIR}/lib/libutf8_range.a
    ${THIRDPARTY_DIR}/lib/libutf8_validity.a
    z)                          # zlib 系统库

步骤 3:拷贝第三方库

从 lycium 构建产物复制 protobuf 和 abseil-cpp:

# 源:lycium 交叉编译产物
/home/lycium_plusplus/lycium/usr/protobuf/arm64-v8a/
├── lib/
│   ├── libprotobuf.a          # 7.6 MB
│   ├── libprotobuf-lite.a     # 1.2 MB
│   ├── libupb.a               # 624 KB
│   └── libutf8_range/validity.a
└── include/google/protobuf/   # 300+ 头文件

# 目标:应用工程
entry/src/main/cpp/thirdparty/protobuf/
├── lib/
│   ├── libprotobuf.a
│   └── libabsl_all.a          # 合并 absl(105 个库 → 1 个)
└── include/
    ├── google/protobuf/        # protobuf 头文件
    └── absl/                   # abseil-cpp 头文件(373 个)

abseil-cpp 的 105 个静态库需要合并处理:

# 产生 libabsl_all.a (4.3 MB)
for f in .../libabsl_*.a; do
  basename=$(basename "$f" .a)
  mkdir "$basename" && cd "$basename"
  ar x "$f"
  # 以库名为前缀重命名 .o 避免同名覆盖
  for obj in *.o; do mv "$obj" "${basename}_${obj}"; done
  cd ..
done
ar rc libabsl_all.a */*.o

⚠️ 关键经验:absl 库中有 4 个重复的 .o 文件名(commandlineflag.cc.oescaping.cc.oglobals.cc.ousage.cc.o),不处理会导致符号缺失。

步骤 4:编写 NAPI 桥接代码

harmonyos-app-integration Skill 提供了完整的 NAPI 代码模板。

字符串转换助手(C++17 安全)
// napi_value → std::string(使用 &str[0] 而非 str.data())
static std::string GetStringFromNAPI(napi_env env, napi_value value) {
    size_t bufSize = 0;
    napi_get_value_string_utf8(env, value, nullptr, 0, &bufSize);
    std::string result(bufSize, '\0');
    napi_get_value_string_utf8(env, value, &result[0], bufSize + 1, &bufSize);
    return result;
}
protobuf 动态消息 API

protobuf 的一大优势是可以使用 动态消息 API(无需 protoc 生成代码):

// 1. 定义消息 Schema → 构建 Descriptor
google::protobuf::FileDescriptorProto fileProto;
fileProto.set_name("person.proto");
fileProto.set_package("example");
auto* personMsg = fileProto.add_message_type();
personMsg->set_name("Person");
// ...

// 2. 通过 DescriptorPool 编译
google::protobuf::DescriptorPool pool;
const auto* fileDesc = pool.BuildFile(fileProto);

// 3. 通过 DynamicMessageFactory 创建动态消息
google::protobuf::DynamicMessageFactory factory;
const auto* prototype = factory.GetPrototype(personDesc);
auto* msg = prototype->New();
const auto* reflection = msg->GetReflection();

// 4. 通过 Reflection API 设置/读取字段
reflection->SetString(msg, nameFd, "Alice");
reflection->SetInt32(msg, idFd, 42);

// 5. 序列化 & 反序列化
msg->SerializeToString(&serialized);
parsed->ParseFromString(serialized);
模块注册
static napi_module demoModule = {
    .nm_version = 1,
    .nm_modname = "entry",      // → ArkTS 中 import 'libentry.so'
    .nm_register_func = Init,
};

步骤 5:编译期问题 — undefined symbol

首次构建报错
ld.lld: error: undefined symbol: absl::lts_20260107::log_internal::LogMessageFatal::LogMessageFatal
>>> referenced by arena_align.h
>>>               napi_init.cpp.o:
>>> referenced by arena.cc in archive libprotobuf.a
>>> referenced 1000+ more times
根因分析

libprotobuf.a 在 lycium 交叉编译时链接了 abseil-cpp,但在应用项目中只复制了 protobuf 的库文件,没有复制 abseil-cpp。

问题 说明
缺少 absl 符号 LogMessageFatalMutexCordhash_internal
缺失数量 1000+ 个 undefined symbol
头文件 vs 库文件 absl 头文件在 include 中可被编译找到,但链接时需要 .a 文件
修复方案
# 1. 从 lycium 复制全部 absl 库
cp /home/lycium_plusplus/lycium/usr/abseil-cpp/arm64-v8a/lib/libabsl_*.a \
  thirdparty/protobuf/lib/

# 2. 合并为单个 libabsl_all.a(处理同名 .o 覆盖问题)

# 3. 在 CMakeLists.txt 中链接
target_link_libraries(entry PUBLIC
    libprotobuf.a
    libabsl_all.a     # ← 新增
    z)
链接顺序
链接顺序:从左到右解析符号
libentry.so → libprotobuf.a → libabsl_all.a → z
               ↑ 主库        ↑ 依赖库       ↑ 系统库

步骤 6:编译期问题 — C++17 标准

报错
"C++ versions less than C++17 are not supported."
absl/base/policy_checks.h:81:2: error
根因

DevEco Studio 的 BiSheng 编译器默认 C++14,而 abseil-cpp 要求 C++17(std::invokestd::conjunctionstd::is_reference_v 等)。

修复
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

步骤 7:NAPI 接口清单

最终确定对外暴露 4 个接口:

API 用途 实现行数 功能
getLibraryVersion() 获取库版本 6 行 返回 protobuf + absl 版本信息
verifyProtobuf() 基本验证 130 行 Person 消息创建→序列化→反序列化→比对
verifyAdvanced() 高级验证 300 行 12 字段类型 + 嵌套消息 + JSON 回环 + 反射 + 未知字段
parseFromHex(hex) Hex 解析 63 行 空/奇偶/非法字符校验 → 二进制→ASCII 可读输出

步骤 8:ArkTS 编译约束

本次集成中遇到的 ArkTS 语法限制:

语法 TypeScript ArkTS 修复
隐式字符串拼接 'a' 'b' ❌ 不支持 合并为单字符串
Unicode 箭头 ⚠️ 解析错误 替换为逗号或文字
TextOptions.maxLines {maxLines:2} maxLines 不是构造函数参数 改为链式 .maxLines(2)
FontWeight.SemiBold 支持 ❌ 不支持 改为 FontWeight.Medium
Color('#666666') 支持 ⚠️ 可能报错 改为 Color.Gray 枚举
@BuilderSlot 组件 支持 ⚠️ 版本依赖 改为内联布局

步骤 9:一键适配验证 UI

最终的 ArkUI 页面采用扁平化设计

┌────────────────────────────────────┐
│  OHOS Protobuf                     │
│  Protocol Buffers v25.9 · NAPI     │
├────────────────────────────────────┤
│  📋 Library Info                   │
│  [获取库版本] 🔵                    │
│  ┌──────────────────────────────┐  │
│  │ Protocol Buffers v25.9 ...   │  │
│  └──────────────────────────────┘  │
├────────────────────────────────────┤
│  🔬 Protobuf Verification          │
│  [Basic · Person Message] 🟢       │
│  [Advanced · 12 types + JSON] 🟣   │
│  ┌──────────────────────────────┐  │
│  │ ✅ Step 1: Schema defined    │  │
│  │ ✅ Step 2: Descriptor built  │  │
│  │ ✅ Step 3: Dynamic message   │  │
│  │ ...                          │  │
│  │ 🎉 Round-trip PASSED         │  │
│  └──────────────────────────────┘  │
├────────────────────────────────────┤
│  🔢 Hex Data Parse                 │
│  [0a09416c696365...          ]     │
│  [Parse Hex] 🟠                    │
│  ┌──────────────────────────────┐  │
│  │ ✅ Hex decoded: 75 chars     │  │
│  └──────────────────────────────┘  │
└────────────────────────────────────┘
     Protobuf v25.9 · arm64-v8a

设计特点:

元素 颜色 含义
背景 #F2F2F7 iOS 风格浅灰
卡片 白色 + 阴影 内容分区
Info 按钮 🔵 #007AFF 信息查询
Basic 按钮 🟢 #34C759 基础验证
Advanced 按钮 🟣 #5856D6 高级功能
Hex 按钮 🟠 #FF9500 工具类
输出区域 #F8F8FA 灰底 + 等宽字体 日志输出

三、Skills 在本次实战中的价值

3.1 加速效果对比

环节 传统手动 AtomCode Skills 效率提升
工程结构搭建 20 min 5 min (模板指导) 4x
CMakeLists.txt 15 min 2 min (模板) 7x
头文件/库文件复制 15 min 3 min (路径指导) 5x
NAPI 桥接代码 40 min 5 min (模板) 8x
absl 合并处理 20 min 2 min (Skill 指导) 10x
C++17 问题排查 15 min 1 min (速查表) 15x
undefined symbol 排查 30 min 3 min (模板) 10x
ArkTS 约束修复 20 min 2 min (Skill 提示) 10x
UI 开发 30 min 5 min (模板) 6x
全流程 ~3.5 小时 ~30 分钟 ~7x

3.2 Skills 捕获的隐性知识

隐性知识 出处 捕获到
libprotobuf.a 依赖 libabsl_all.a 链接报错 harmonyos-app-integration
absl 同名 .o 覆盖问题 合并时发现 harmonyos-app-integration
BiSheng 默认 C++14 编译报错 harmonyos-app-integration
ArkTS 不支持隐式字符串拼接 编译报错 10505001 新增到 Skill
FontWeight 枚举成员限制 编译报错 新增到 Skill
Color 字符串 vs 枚举 ArkTS 约束 新增到 Skill

四、总结与最佳实践

4.1 OHOS 三方库集成黄金流程(复杂依赖库)

1. 准备工作
   ├── 复制 .a 库到 thirdparty/<name>/lib/
   └── 复制头文件到 thirdparty/<name>/include/

2. 处理传递依赖
   ├── 识别所有间接依赖(如 protobuf → absl)
   ├── 复制依赖库的 .a 和头文件
   └── 合并多个 .a 时注意同名 .o 覆盖

3. 配置 CMakeLists.txt
   ├── set(CMAKE_CXX_STANDARD 17) ← BiSheng 默认为 C++14
   ├── include_directories(thirdparty/<name>/include)
   └── target_link_libraries(主库 → 依赖库 → 系统库)

4. 编写 NAPI 桥接
   ├── 字符串转换助手 (C++17 safe)
   ├── try-catch 异常保护
   └── 模块注册

5. 添加 TS 类型声明
   └── Index.d.ts 定义所有导出函数类型

6. 构建验证
   ├── 编译 → 链接 → 运行
   └── 使用 .cxx/ 清理缓存

7. 适配验证
   └── 在 ArkUI 中调用所有 NAPI 接口验证功能

4.2 关键经验

  1. 传递依赖不能丢libprotobuf.a 链接了 abseil-cpp,复制时必须同时复制 libabsl_*.a。1000+ 个 undefined symbol 是最直接的信号。

  2. C++ 标准要设对:BiSheng 编译器默认 C++14,abseil-cpp 和 protobuf 都要求 C++17。不设置会导致大量编译错误。

  3. 同名 .o 覆盖陷阱:多个 .a 合并时,ar x 按文件名提取,同名文件会被静默覆盖。absl 中有 4 个重复 .o 文件名,必须前缀去重。

  4. ArkTS 与 TypeScript 的差异:ArkTS 是 TypeScript 的子集,不支持隐式字符串拼接、部分枚举值、以及特定构造语法。编译时 10505001 错误通常由这些差异引起。

  5. 验证工具链一致性:lycium 使用 OHOS Clang 15.0.4,而 DevEco Studio 使用 BiSheng 编译器。两者目标 ABI 一致,但 C++ 标准库实现可能有细微差异。

Logo

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

更多推荐