写在前面

本篇文章的作者是仓颉高级三方库开发者刘老师所作,本人发布该篇文章已经过刘老师允许,想让更多的粉丝朋友可以了解到更优秀的学习资源、更优秀的行业引导者
刘老师本人主页链接:https://blog.csdn.net/weixin_47308626?type=blog

导语

作为鸿蒙生态的核心新兴编程语言,仓颉凭借强大的跨语言互操作能力、高效的开发体验,正在快速成为应用开发的新选择。随着语言生态的逐步完善,三方库的开发与复用已成为提升开发效率、丰富应用功能的关键,也是开发者进阶路上的核心议题。

仓颉三方库的核心价值

  • 生态复用:通过标准化包封装与编译,实现代码跨项目、跨团队高效复用,减少重复开发。
  • 模块化开发:支持导出仓颉组件、类和方法,通过public修饰符精准控制访问权限,助力模块化架构设计。
  • 原生性能:CJNative 后端将库代码编译为原生二进制,直接运行于操作系统层面,兼顾高效执行与资源优化。
  • 安全保障:静态强类型检查杜绝类型错误,自动内存管理避免内存泄漏,双重保障运行时稳定性。

仓颉包管理核心机制

仓颉采用 “包 - 模块” 的层级设计组织代码,官方包管理工具 CJPM(Cangjie Package Manager)提供从初始化、构建到依赖管理的全流程支持,是三方库开发与使用的基础。

一、核心概念厘清

  • 包(Package):编译的最小单元,拥有独立命名空间,同一包内不允许同名顶层定义(函数重载除外)。
  • 模块(Module):若干相关包的集合,是第三方开发者发布、分享三方库的最小单元。
  • 源码包识别:直接包含至少一个.cj仓颉代码文件即为有效源码包;其父包(递归向上)需均为有效源码包,根包仅需满足首条规则。

二、CJPM 工具核心用法

CJPM 提供简洁的命令行接口,覆盖开发全流程,核心功能如下:

1. 常用命令
cjpm init        # 初始化新仓颉模块(生成项目结构与配置文件)
cjpm build       # 构建当前模块(输出库文件或可执行文件)
cjpm run         # 编译并运行可执行模块
cjpm test        # 执行本地包/模块的单元测试
cjpm install     # 安装指定版本的仓颉二进制包(本地或远程)
2. 核心配置文件
  • cjpm.toml:模块核心配置文件,定义包名、版本、输出类型、作者、依赖关系等元数据。
  • cjpm.lock:自动生成的依赖锁定文件,固定依赖版本,避免多环境版本不一致问题。
3. 依赖管理特性
  • 自动分析多版本依赖,智能合并并解决冲突。
  • 支持本地依赖(通过路径引用)和远程依赖(通过仓库地址引用)。

三、标准项目结构

遵循以下目录结构可确保兼容性,便于他人复用:

project/
├── src/                  # 源码目录
│   ├── main.cj           # 主入口文件(根包代码)
│   └── pkg1/             # 子包目录(与包名对应)
│       └── mod1.cj       # 子包模块代码
└── cjpm.toml             # 包配置文件(必需)
包声明规范

代码文件开头需明确声明所属包,包名需与目录层级一致:

package demo.pkg1  // 对应 src/pkg1 目录,父包为 demo

四、编译与构建流程

1. 编译器基础用法
cjc [option] file...  # 直接编译仓颉源码文件
2. 完整构建流程
  1. 前端编译:将仓颉源码转换为 LLVM IR 中间表示。
  2. 后端编译:基于 LLVM IR 生成目标平台的机器码。
  3. 链接:将机器码与依赖库整合,生成最终产物。
3. 关键构建选项
  • --output-type:指定输出类型(executable可执行文件 /static静态库 /dynamic动态库)。
  • --output-dir:指定产物输出目录(默认生成target文件夹)。
  • -p:指定包路径,用于多包项目的精准编译。

三方库开发实战

仓颉三方库的编译产物分为静态库(static)和动态库(dynamic)两类,需根据使用场景选择合适类型,核心实现步骤如下:

一、静态库与动态库核心差异

特性 静态库(static) 动态库(dynamic)
链接时机 编译阶段直接嵌入可执行文件 程序运行时动态加载
产物大小 可执行文件较大(包含库代码) 可执行文件较小(仅含引用入口)
内存占用 多个进程独立加载,占用内存较多 多进程共享实例,内存占用更优
更新方式 需重新编译主程序才能更新 直接替换动态库文件即可生效
部署依赖 无外部依赖,可独立部署 需随主程序分发对应的动态库文件

二、适用场景选择

静态库适用场景
  • 嵌入式设备、独立工具等需要 “零依赖部署” 的场景。
  • 对程序启动速度要求高,需避免运行时加载开销的场景。
  • 需规避动态库版本冲突的复杂项目。
动态库适用场景
  • 多进程共享功能模块(如系统级工具库),需优化内存占用。
  • 大型应用的插件系统,支持功能热更新(无需重启主程序)。
  • 频繁迭代的功能模块,需快速更新且不影响主程序。

三、基础配置示例

cjpm.toml中指定输出类型,即可快速配置库类型:

[package]
name = "math_utils"       # 库名称(需与包声明一致)
version = "1.0.0"         # 版本号(遵循语义化版本规范)
output-type = "dynamic"   # 输出动态库(改为"static"即为静态库)
authors = ["Your Name <your.email@example.com>"]
description = "轻量高效的仓颉数学工具库"

[dependencies]
# 无依赖时留空,有依赖需声明版本与来源

四、简易数学工具库实现

以 “加减乘除幂运算” 工具库为例,完整演示开发流程:

1. 项目结构
math-utils/
├── src/
│   └── index.cj  # 库核心实现文件
└── cjpm.toml     # 配置文件
2. 核心代码实现

cangjie

// src/index.cj
package math_utils  # 包名需与 cjpm.toml 中的 name 一致

// 加法(public修饰符表示可被外部导入使用)
public func add(a: Int64, b: Int64): Int64 {
    return a + b
}

// 减法
public func subtract(a: Int64, b: Int64): Int64 {
    return a - b
}

// 乘法
public func multiply(a: Int64, b: Int64): Int64 {
    return a * b
}

// 除法(带除零检查,返回可选类型)
public func divide(a: Int64, b: Int64): ?Int64 {
    if (b == 0) {
        return None  // 除零返回空
    }
    return Some(a / b)
}

// 幂运算(base为底数,exponent为非负指数)
public func power(base: Int64, exponent: UInt64): Int64 {
    if (exponent == 0) {
        return 1  // 任何数的0次幂为1
    }
    var result = base
    for (_ in 1..exponent) {
        result *= base
    }
    return result
}
3. 构建库文件

进入math-utils目录,执行以下命令构建:

cjpm build

构建成功后,会在target目录下生成对应库文件(动态库为.so/.dll,静态库为.a/.lib)。

三方库使用教程

使用已开发的三方库仅需两步:配置依赖 + 导入使用,支持本地库和远程库两种引用方式。

一、本地库引用(开发调试场景)

假设已有两个项目,math-utils为开发好的库,math-example为使用库的应用,目录结构如下:

project-root/
├── math-utils/       # 已构建好的数学工具库
└── math-example/     # 待开发的应用项目
1. 配置应用依赖

编辑math-example/cjpm.toml,添加本地库依赖:

[package]
name = "math_example"
version = "1.0.0"
output-type = "executable"  # 应用为可执行类型

[dependencies]
# 引用本地库:path指定库目录,version需与库的版本一致
math_utils = { path = "../math-utils", version = "1.0.0" }
2. 导入并使用库

编辑math-example/src/index.cj,通过import导入库功能:

package math_example  # 应用包名

// 方式1:全量导入库(推荐,便捷高效)
import math_utils.*

// 方式2:按需导入指定函数(减少冗余,按需使用)
// import math_utils.add
// import math_utils.multiply

main() {
    // 调用库函数
    let sum = add(5, 3)          // 结果:8
    let difference = subtract(10, 4)  // 结果:6
    let product = multiply(4, 6)   // 结果:24
    let quotient = divide(15, 3)   // 结果:Some(5)
    let powerResult = power(2, 3)  // 结果:8

    // 打印结果(需确保仓颉环境支持控制台输出)
    println("5+3 = {sum}")
    println("15÷3 = {quotient.unwrapOr(0)}")  // 处理可选类型
}
3. 运行应用

进入math-example目录,执行以下命令运行:

cjpm run

开发与使用注意事项

  • 包名一致性:库的package声明需与cjpm.toml中的name完全一致,否则会导致导入失败。
  • 访问权限:需对外暴露的函数、类必须添加public修饰符,默认无修饰符的内容仅包内可见。
  • 版本兼容性:遵循语义化版本规范(主版本号变更可能不兼容),避免依赖版本冲突。
  • 错误处理:调用库函数时需注意返回值类型(如示例中的可选类型?Int64),避免运行时异常。
Logo

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

更多推荐