使用 zig 开发鸿蒙原生模块(二)

在很早之前我们简单介绍了下如何使用 zig 作为原生模块的编译工具以及使用 zig 直接开发一个原生的应用模块。在此前的章节中,我们已经体会到了 zig 这门语言带来的神奇之处。

那么在今天的文章中,我将介绍最近一段时间我所基于 zig 生态做的一些工作以及对未来的一些展望,当然这仅是我自己对于 zig 的一些认知和使用建议,按需阅读和参考即可。

本文的示例代码在:zig-napi-example

背景

在开始之前,我们思考一个这样的问题:为什么我们要使用 zig 来开发原生应用模块?

我们现在已经有 C/C++/Rust 甚至是 Golang 这样的语言,为什么我们还需要尝试用 zig 来开发原生模块?

在我两年的鸿蒙开发经验中,我发现了几个非常小众的场景:

  1. 当我们使用 C++ 开发的时候,由于 NDK 支持的标准为:C++15 完整支持,C++17/20 部分语法支持,这会导致一些社区较新的库无法在鸿蒙上编译使用。比如:ada-url
  2. 我经常使用 Rust 开发一些小的工具包,如:网络工具库node-rs同款工具库等。他们会面临的一个很大的问题就是:功能非常的单一、原子化,但是最终的产物包体积不小。这在 Node 生态位中其实影响并不是特别大,但是在移动客户端生态中这变得比较严重了。我只是想要实现一个简单的 Ping 居然要额外引入接近 900KB 大小的包体积,长期来看这对于包体积来说不是非常的友好。

而借助于 zig 本身的构建系统,我们可以较为完美的解决这些问题。

开发工具

在使用 C++ 开发原生模块时,我们可以使用node-addon-api-ohos来简化我们的 N-API 重复开发工作。

在使用 Rust 开发原生模块时,我们可以使用ohos-rs来简化我们的 N-API 重复开发工作。

而使用 zig 来开发原生模块,我们依旧需要一些辅助工具来简化我们的开发工作。

因此我参考以上这些工具库,基于 zig 实现了一套 zig 上面的工具:zig-napi,它能够像 ohos-rs 一样极大的简化我们的工作,使我们专注于业务代码本身。

示例

现在我们先直接看一个示例代码来进行体验:

zig 复制代码
const napi = @import("napi");

pub fn add(left: f32, right: f32) f32 {
    return left + right;
}

comptime {
    napi.NODE_API_MODULE("hello", @This());
}

依旧是一个最简的两数之和实现,我们在初探 zig-lang 开发鸿蒙应用中基于原子能力实现的代码大约为 70 行,但现在经过工具库的简化,我们的代码仅有 9 行。

我们基于 zig 的编译期能力实现了对 N-API 重复代码的封装,这允许我们在实际的开发中直接聚焦于业务代码本身而非 N-API 部分。

接入

现在我们从 0 开始在一个新项目中接入并且完成一个上述两数之和的开发。

安装 Zig

正常情况下,我们直接通过官方的方式安装 zig 即可。但是由于新版本我们需要一些特殊的实现以确保我们的所有能力在鸿蒙上可用,需要使用 patch 的版本来构建。现在我们提供了一个对应的版本:zig-patch,目前支持:

  • Arm macOS
  • x86_64 Windows
  • x86_64 Linux gnu
  • x86_64 Linux musl

等平台,如有其他平台需要可以提交 ISSUE 或者 PR 以支持。

创建项目

直接使用 zig 的命令即可完成项目的创建。

bash 复制代码
mkdir zig-napi-example && cd zig-napi-example && zig init

默认生成的 zon 文件带了很多注释,按需查阅即可,当然这里我们直接简化下整体的内容。

zon 复制代码
.{
    .name = .zig_napi_example,
    .version = "0.0.0",
    // 生成的hash值
    .fingerprint = 0x1dfe36438848601b, // Changing this has security and trust implications.
    
    // 如果使用 patch 的zig版本生成的话,版本会如下所示。建议修改为 0.15.1,可以避免使用 ZLS 其自动下载版本失败。
    .minimum_zig_version = "0.16.0-dev.312+164c598cd",

    .dependencies = .{ },

    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
    },
}

引入依赖

zig 项目中使用 zon 作为包依赖的管理工具,我们需要手动编辑 zon 来引入我们的包。

zon 复制代码
.{
    .name = .zig_napi_example,
    .version = "0.0.0",
    .fingerprint = 0x1dfe36438848601b, // Changing this has security and trust implications.

    // Tracks the earliest Zig version that the package considers to be a
    // supported use case.
    .minimum_zig_version = "0.15.1",

    .dependencies = .{ .@"zig-napi" = .{ .url = "https://github.com/openharmony-zig/zig-napi/archive/refs/tags/0.0.1-beta.2.tar.gz" } },

    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
    },
}

dependencies中通过 git 的标签引入我们的库,注意这时候我们只写了引入的 url,这时候编辑器可能会提示我们缺少 hash 字段,该字段默认情况下我们不知道应该填什么。但是在编译阶段,zig 会自己告诉你应该是什么值,然后填入即可。

接下来,我们修改build.zig来完成构建工具的初始化。其代码如下所示:

zig 复制代码
const std = @import("std");
// 引入我们的模块构建工具
const napi_build = @import("zig-napi").napi_build;

pub fn build(b: *std.Build) !void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    
    // 初始化定义的模块
    const zig_napi = b.dependency("zig-napi", .{});

    const napi = zig_napi.module("napi");

    // 初始化构建参数
    const result = try napi_build.nativeAddonBuild(b, .{
        // 定义构建产物的名字
        .name = "zig_napi_example",
        .root_module_options = .{
            // 定义入口文件
            .root_source_file = b.path("./src/hello.zig"),
            .target = target,
            .optimize = optimize,
        },
    });

    // 构建产物使其链接 napi 模块
    // arm64 产物
    if (result.arm64) |arm64| {
        arm64.root_module.addImport("napi", napi);
    }
    // armv7a 产物
    if (result.arm) |arm| {
        arm.root_module.addImport("napi", napi);
    }
    // x86_64 产物
    if (result.x64) |x64| {
        x64.root_module.addImport("napi", napi);
    }
}

注意所有的模块都需要将初始化完整之后的构建产物进行链接,否则会导致找不到。如上述的 napi 模块一致,可以为三个平台添加链接。如果只需要一个平台,则仅处理一个平台即可。

开发

接下来我们将默认生成的main.zigroot.zig删除,新增hello.zig文件,其内容如下所示:

zig 复制代码
const napi = @import("napi");

pub fn add(left: f32, right: f32) f32 {
    return left + right;
}

comptime {
    // 注意 这里定义的名称必须和build.zig中定义的名称一致,在鸿蒙的平台上面会进行一致性校验,校验失败会导致模块加载失败。
    napi.NODE_API_MODULE("zig_napi_example", @This());
}

构建

接下来我们直接使用 zig 的命令进行构建即可。

bash 复制代码
zig build

不出意外的话,会出现以下的错误

这就是我们刚才提到的编辑器错误,现在我们只需要将这里出现的 hash 内容复制到 zon 文件中即可,现在我们完整的 zon 文件如下所示:

zon 复制代码
.{
    .name = .zig_napi_example,
    .version = "0.0.0",
    .fingerprint = 0x1dfe36438848601b, // Changing this has security and trust implications.

    // Tracks the earliest Zig version that the package considers to be a
    // supported use case.
    .minimum_zig_version = "0.15.1",

    .dependencies = .{ .@"zig-napi" = .{ .url = "https://github.com/openharmony-zig/zig-napi/archive/refs/tags/0.0.1-beta.2.tar.gz", .hash = "zig_napi-0.0.1-beta.2-H6OwawaxAgDMitwMH0m5ZJZoIpdc5LU2C34P_msTfEQo" } },

    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
    },
}

重新执行构建,我们将会在项目中获得最终的构建产物,其目录示意如下所示:

这与我们使用 C/C++/Rust 等语言构建的产物基本等同。

如果只需要构建一个平台的产物可以指定目标平台:zig build -Dtarget=aarch64-linux-ohos

这样我们就完成了一个最简项目的初始化、接入和构建。

工具链

除此之外,我们还提供了一个简单的 Github Action 用于简化 CI 构建环境的搭建,基于该能力我们可以使用 zig 进行原生模块的开发以及使用 zigbuild 跨平台构建能力。

我们在这里提供了一个简单示例来展示如何使用该能力:ada-ohos add ci test

展望

在未来的时间中,我们希望能够继续完成如下几个目标以丰富和完善整体生态:

  1. 完善 zig-napi 的能力,基本上对齐 ohos-rs 所支持的能力。
  2. 持续测试 zig patch 的部分,并尽可能推进 zig 合入对 ohos 的支持。

以上就是笔者近期在鸿蒙使用 zig 上面的一些工作内容以及产出,如有错误还请斧正。希望能够对你有所帮助~

相关推荐
爱笑的眼睛1112 小时前
ArkUI V2中Repeat组件使用注意事项总结
华为·harmonyos
Devil枫15 小时前
HarmonyOS 地图服务进阶:POI 搜索与路径规划全流程实现
华为·harmonyos
懒惰蜗牛15 小时前
鸿蒙开发3--UI布局(玩转鸿蒙的Row、Column与Stack容器)
ui·华为·harmonyos·鸿蒙·鸿蒙系统
_waylau21 小时前
如何将鸿蒙5应用升级到鸿蒙6
华为·harmonyos·鸿蒙·鸿蒙系统
爱笑的眼睛111 天前
HarmonyOS应用开发深度解析:ArkTS语法与组件化开发实践
华为·harmonyos
安卓开发者1 天前
鸿蒙NEXT Wi-Fi扫描开发指南:从基础到实战
华为·harmonyos
安卓开发者1 天前
在鸿蒙NEXT中发起HTTP网络请求:从入门到精通
网络·http·harmonyos
米羊1211 天前
【鸿蒙心迹】工匠雕琢,攻克难题(下)
华为·harmonyos
chenbin___2 天前
鸿蒙键盘遮挡react native内容尝试
react native·harmonyos