鸿蒙应用集成coost:AtomCode 4步搞定NAPI桥接

欢迎加入【开源鸿蒙PC社区】,一起共建鸿蒙化C/C++三方库生态。
欢迎在【PC社区】平台贡献你的项目。
仓库: https://github.com/idealvin/coost v2025_05_23 — 跨平台 C++ 基础库
集成平台: 鸿蒙PC| 测试SDK: API 20

示例代码仓库:https://atomgit.com/unisources/OHOSCoostSample

image-20260628145748502

前置说明

项目 说明
集成库 coost v2025_05_23 (MIT 许可证,零外部依赖)
目标平台 鸿蒙PC (OpenHarmony arm64-v8a)
SDK 版本 API 20
开发工具 DevEco Studio 6.0
交叉编译工具链 lycium_plusplus + HPKBUILD
三方库静态库 libco.a 6.3 MB for arm64-v8a

传统方式的效率瓶颈

失败回退

失败

修复

工程搭建

库文件部署

CMake 配置

NAPI 桥接

类型声明

UI 验证

编译测试

手动排错

AtomCode + Skills 集成全流程

Step 1:交叉编译 coost(2 分钟)

先在 lycium_plusplus 中完成 coost 的交叉编译:

/new-package coost v2025_05_23 https://github.com/idealvin/coost
cd /home/lycium_plusplus/lycium
./build.sh coost

输出产物:

arm64-v8a/
├── include/co/
│   ├── fastring.h       # 高性能字符串
│   ├── json.h           # JSON 解析
│   ├── os.h             # OS 工具
│   ├── base64.h         # Base64 编解码
│   ├── fs.h             # 文件系统
│   └── ... 共 41 个头文件
└── lib/
    └── libco.a          # 6.3 MB, ELF64 AArch64

Step 2:生成 NAPI 示例工程(1 分钟)

/new-sample coost "coost base library"

AtomCode 自动执行 7 项配置:

动作 修改目标
① 复制模板 创建 /home/hoapp/OHOSCoostSample
② 改 bundleName com.unisources.spdlogcom.unisources.coost
③ 改 abiFilters ["arm64-v8a"]
④ 改 deviceTypes ["phone", "2in1"](鸿蒙PC)
⑤ 部署产物 libco.a + 41 个头文件 → thirdparty/coost/
⑥ 重写 CMakeLists.txt 链接 libco.a + pthread
⑦ 重写 napi_init.cpp 8 项回归测试 + JSON 输出

Step 3:CMakeLists.txt 配置

cmake_minimum_required(VERSION 3.5.0)
project(OHOSCoostSample)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include
                    ${NATIVERENDER_ROOT_PATH}/thirdparty/coost/include)

link_directories(${NATIVERENDER_ROOT_PATH}/thirdparty/coost/lib)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 关键:解除 OHOS 编译器 unix 宏冲突
add_compile_options(-Uunix)

add_library(entry SHARED napi_init.cpp)

target_link_libraries(entry PUBLIC libace_napi.z.so)
target_link_libraries(entry PUBLIC ${...}/thirdparty/coost/lib/libco.a)
target_link_libraries(entry PUBLIC pthread)

3 个必须遵守的规则

规则 原因
link_directories()add_library() 之前 CMake 在 create target 时才解析库路径
系统库在前,三方库在后 链接器从左到右解析符号
额外 pthread coost 使用线程局部存储

Step 4:NAPI 桥接 — JSON 输出模式

// ── JSON 条目构建器(无第三方依赖)──
static void AppendJson(std::ostringstream &oss,
                       const char *testName, bool passed,
                       const std::string &detail = "",
                       const std::string &desc = "")
{
    if (oss.tellp() > 0) oss << ",";
    auto esc = [&](const std::string &s) {
        for (char c : s) { if (c == '"' || c == '\\') oss << '\\'; oss << c; }
    };
    oss << "{\"n\":\"";  esc(testName);
    oss << "\",\"p\":" << (passed ? "true" : "false");
    oss << ",\"d\":\"";  esc(detail);
    oss << "\",\"c\":\""; esc(desc);
    oss << "\"}";
}

NAPI 模块注册:

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "coostFullTest", nullptr, CoostFullTest, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_modname = "entry",
    .nm_register_func = Init,
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}

TypeScript 声明:

// entry/src/main/cpp/types/libentry/Index.d.ts
export const add: (a: number, b: number) => number;
export const coostFullTest: () => string;

ArkUI 3 列 Grid 卡片页:

Grid() {
  ForEach(this.testResults, (item: TestResult) => {
    GridItem() {
      this.TestCard(item)
    }
  })
}
.columnsTemplate('1fr 1fr 1fr')

每张卡片显示:✓/✗ 状态 + 测试名中文说明结果详情


踩坑专区

坑 1:OHOS 编译器 unix 宏冲突

现象

.../include/co/time.h:41:17: error: expected unqualified-id
extern xx::Unix unix;
                ^
<built-in>:423:14: note: expanded from here
#define unix 1

根因:coost 的 time.h 中使用 unix 作为变量名,而 OHOS SDK 的 Clang/BiSheng 编译器内建了 #define unix 1(Linux 传统预定义宏)。宏展开后 extern xx::Unix unix; 变成 extern xx::Unix 1;,导致语法错误。

排查过程

# 验证 OHOS 编译器内建宏
echo | /home/ohpkg/linux/native/llvm/bin/clang -dM -E - | grep unix
# 输出: #define unix 1

修复:CMakeLists.txt 中添加 -Uunix Undefine 该宏:

+ # ── Undefine `unix` macro (OHOS compiler built-in) ──
+ add_compile_options(-Uunix)

经验总结-Uunix(以及类似 -Ulinux-Ui386)是交叉编译时最常见的宏冲突修复方案。它不可能影响运行时行为,只会解除编译器预定义宏的干扰。

坑 2:coost API 命名空间结构误判

现象

error: no member named 'os' in namespace 'co'; did you mean simply 'os'?
        int pid = co::os::pid();

根因:coost 的 API 命名空间结构很特殊——不同模块分布在不同的命名空间层级中:

模块 实际命名空间 错误写法 修正后
OS 工具 os::pid() 全局 co::os::pid() os::pid()
文件系统 fs::exists() 全局 co::fs::exists() fs::exists()
命令行标志 flag::set_version() 全局 co::flag::set_version() flag::set_version()
字符串 fastring 全局 struct co::fastring fastring
Base64 co::base64_encode() 正确 不变
数字转串 co::i32toa() 正确 不变

排查过程:逐一检查每个头文件的 namespace 声明:

grep "^namespace" include/co/os.h      # → namespace os {   (全局)
grep "^namespace" include/co/base64.h  # → namespace co {   (co::)
grep "^struct" include/co/fastring.h   # → struct fastring  (全局)

修复:删除所有错误的 co:: 前缀:

- int pid = co::os::pid();
+ int pid = os::pid();

- co::fastring s("Hello");
+ fastring s("Hello");

- co::fs::exists(".");
+ fs::exists(".");

- co::flag::set_version(...);
+ flag::set_version(...);

经验总结:coost 的命名空间设计是命名空间的实际含义优先于统一前缀——OS 相关、文件系统相关放在全局 os/fs 命名空间更"自然";通用工具类(base64、strconv)放在 co:: 下。这种设计在 C++ 库中并不罕见(如 std 包含 std::filesystem,但也有全局 ::socket),但在 NAPI 桥接时容易踩坑。建议在编写桥接代码前,用 grep "^namespace\|^struct\|^class" include/co/*.h 过一次所有头文件

坑 3:fastring 无 str() 方法

现象

error: no member named 'str' in 'fastring'
            "homedir=" + home.str()

根因fastring 继承自 co::stream,提供了 c_str()data() 方法(返回 const char*),但没有 str() 方法。这个命名习惯与 std::string 不同(std::string 也没有 str(),但 Rust 的 String 有)。

排查过程:查看 co::stream 基类的公开方法:

grep "c_str\|data" include/co/stream.h
# char* data() noexcept { return _p; }
# const char* c_str() const { ... }
# 没有 str() 方法

修复:统一使用 std::string(fastring.c_str()) 转换为标准字符串:

- "homedir=" + home.str()
+ "homedir=" + std::string(home.c_str())

- "encode=" + enc.str()
+ "encode=" + std::string(enc.c_str())

经验总结:coost 的 fastring 是一个轻量替代 std::string 的实现,API 不尽相同。在 NAPI 桥接中,建议将 coost 类型统一转换为标准 C++ 类型后与其他代码交互,避免跨库 API 混用的心智负担。

通用集成模板(拿来即用)

CMakeLists.txt 通用模板

cmake_minimum_required(VERSION 3.5.0)
project(OHOSLibIntegration)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

# ── 修改这里的库名 ──
set(LIB_NAME "coost")
set(LIB_PATH "${NATIVERENDER_ROOT_PATH}/thirdparty/${LIB_NAME}")

include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include
                    ${LIB_PATH}/include)
link_directories(${LIB_PATH}/lib)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# ── OHOS 宏冲突修复(按需调整) ──
add_compile_options(-Uunix)

add_library(entry SHARED napi_init.cpp)

target_link_libraries(entry PUBLIC libace_napi.z.so)
target_link_libraries(entry PUBLIC ${LIB_PATH}/lib/lib${LIB_NAME}.a)
target_link_libraries(entry PUBLIC pthread)

NAPI JSON 输出模式模板(适用于多测试项回归)

// ── JSON 条目构建器 ──
static void AppendJson(std::ostringstream &oss,
                       const char *name, bool passed,
                       const std::string &detail = "",
                       const std::string &desc = "")
{
    if (oss.tellp() > 0) oss << ",";
    auto esc = [&](const std::string &s) {
        for (char c : s) { if (c == '"' || c == '\\') oss << '\\'; oss << c; }
    };
    oss << "{\"n\":\"";  esc(name);
    oss << "\",\"p\":" << (passed ? "true" : "false");
    oss << ",\"d\":\"";  esc(detail);
    oss << "\",\"c\":\""; esc(desc);
    oss << "\"}";
}

// ── NAPI 返回 JSON 数组 ──
static napi_value FullTest(napi_env env, napi_callback_info info)
{
    std::ostringstream oss;
    // ... 若干 AppendJson 调用 ...
    std::string json = "[" + oss.str() + "]";
    napi_value result;
    napi_create_string_utf8(env, json.c_str(), json.length(), &result);
    return result;
}

开源库命名空间快速排查命令

# 查看所有模块所属命名空间
grep "^namespace\|^struct\|^class" thirdparty/<lib>/include/*.h

# 查看头文件中所有公开函数声明的命名空间路径
grep -rn "^namespace" thirdparty/<lib>/include/

# 验证 .a 中某个符号是否已定义
nm lib/lib<lib>.a | grep <symbol_name>

总结

coost 的鸿蒙 NAPI 集成之旅暴露了三个典型问题:编译器内建宏冲突(unix)、开源库命名空间设计多样性(全局 vs co::)、以及 API 名称差异(str() vs c_str())。每一个坑都反映了同一个本质问题——库作者的设计假设与集成者的预期之间的认知鸿沟

交叉编译解决了"能不能编译"的问题,NAPI 桥接解决了"能不能调用"的问题,而踩坑实录解决了"能不能一次通过"的问题。AtomCode Skills 的价值不在于消灭所有坑,而在于将 2 小时的排查压缩到 15 分钟——通过模板固化最佳实践、通过踩坑记录积累标准答案。

金句:NAPI 集成中最贵的时间不是写代码的时间,而是发现"原来是这个原因"之前的困惑时间。


你在 NAPI 集成中遇到过什么奇怪的命名空间或宏冲突问题?欢迎在评论区分享你的经验。

如果本文对你有帮助,请 点赞、收藏、转发 支持一下~

更多推荐