本文我们将简单尝试使用 Golang 开发鸿蒙上面的模块。
2024.09.07 更新:
在与群友的交流中,我们发现可以通过一些绕过的实现来实现 Go 在鸿蒙上的正常使用。这里有两个方案来实现调用:
-
直接将 GOOS 修改为
android
,构建的时候将安卓的android/log.h
文件和对应的动态链接库添加到构建过程中以完成构建,最终使用dlopen
进行调用。- 笔者自己没试过这种方法,群里有同学已经跑通 具体请参考:b23.tv/jEI3oiM
- 群友反馈只能用 dlopen 调用,具体未验证。
-
简单修改 go 的源码来实现构建。本质上与第一种方式没有区别,在构建的时候仍然需要将 GOOS 修改为 android,不过我们将对安卓日志的依赖直接移除了。
本地构建出魔改的版本替代官方版本进行构建即可,此方式构建的动态/静态链接库可以与其他的链接库一样正常使用,支持正常 link 逻辑。
这里先给出一个直接的结论:Golang 构建的模块无法在鸿蒙上被正常使用。
本文后续所有内容是认为 Golang 可以正常构建和执行的流程进行分析和实践,不过最后一步执行会出现问题。
本文相关代码在 go-example 可以找到。
背景
最近在社区和交流群中看到许多开发者在询问:如何将使用 Golang 开发的动态/静态链接库在鸿蒙中进行适配?
恰巧最近将 zig 的构建系统尝试实现了对鸿蒙的构建支持,因此我们可以使用 zig 构建系统来提供给 go 实现交叉编译到鸿蒙生态中。因此笔者做了一些简单的尝试,本文将简单讲解尝试过程以及最终的一些结果。
初识 Golang
简介
我们在之前的一些文章中讲过,鸿蒙原生模块开发本质上对于语言没有太大的限制,只要是能够构建出符合标准的动态/静态链接库即可。
比如与鸿蒙非常类似的 Node 生态中,有使用 Rust 进行开发的 swc/oxc 等工具,也有使用 Golang 开发的 esbuild 等工具。那么类似的我们依旧可以在鸿蒙上也使用 go 开发一些能力来提供给上层的 ArkTS 使用。
关于 go 的环境配置、项目初始化等能力这里不做过多的讲解,网上有大把参考资料可供学习。
FFI
这里我们简单讲一些 go 的 ffi 能力,与很多语言相同。go 提供了开箱即用的 ffi 能力,不过仍然需要做一些额外的数据处理以及适配工作。
对于 go 很熟悉的读者可以直接跳过这一部分。
Go 中提供了C
包,提供了与 C ABI 桥接的各种能力,包括数据定义等。一个标准的使用 go 暴露 C ABI 接口的代码如下所示:
package main
import "C"
//export Add
func Add(a, b int) int {
return a + b
}
func main() {}
这里有几点需要特别注意:
- 对于需要导出的函数和方法必须使用注释声明,否则将无法导出。
main
函数是必须的,即使没有任何任务也必须声明。- export 注释必须遵循规范,在编写代码的时候会有一定的提示,如下所示:
这样我们就完成了一个简单的 C ABI 声明。
现在我们尝试将其构建为对应平台上动态链接库:
- Mac: xx.dylib
- Linux: xx.so
- Windows: xx.dll
一个标准的使用 CGO 构建的命令如下所示:
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -o ../go-shared/libadd.dylib -buildmode=c-shared ../go-shared/add.go
简单讲解下这里配置参数:
- GGO_ENABLED
允许使用 CGO 进行编译 - GOOS
构建的目标平台系统 - GOARCH
构建的目标平台系统的架构 - -o
最终的输出产物名 - -buildmode
构建产物类型:c-shared c-archive 动态/静态链接库 - xx
构建的文件
构建完成之后我们会得到两个文件:libadd.dylib
和libadd.h
分别为产物以及其头文件。然后我们可以编写一个简单的 C 来验证我们的动态链接库是否可以正常使用。
#include "libadd.h"
#include "stdio.h"
int main() {
int ret = Add(1,2);
printf("%d",ret);
}
然后构建并且尝试运行即可:
# 构建时需要指定链接的路径以及动态链接库
gcc ../go-shared/main.c -o main -L../go-shared -ladd
cp ../go-shared/libadd.dylib ./
./main
然后我们可以看到已经能够正常的使用并且输出了对应的结果
这就是使用 CGO 的一个基本流程,其他的内容这里就不再赘述。
Zig with go
接下来回到正题,我们讲过 zig 提供了一个非常健全和便捷的构建系统。
这个构建系统不仅能够在 zig 本身使用,还能够让我们在其他语言中体验到相当便利的构建能力,比如我们在之前的文章中使用 zig 来构建 rust 的代码使其运行在鸿蒙系统上面。同样的,我们也能够使用 zig 在 golang 上面替代其默认的构建系统从而体验到 zig 带来的便利构建能力。
初试
我们直接使用刚才的例子来尝试使用 zig 作为 CGO 的构建系统能力。只需要为构建命令新增CC
和CXX
环境变量即可,那么我们的命令就应该如下所示:
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 CC="zig cc -target aarch64-macos" CXX="zig c++ -target aarch64-macos" go build -o ../go-shared/libadd.dylib -buildmode=c-shared ../go-shared/add.go
不过这里没有办法给大家演示其正确性,因为 zig 在 0.12 以及 0.13 的版本中引入了一些 bug 会导致无法构建。
具体问题可以参考 issue
构建 go 到鸿蒙
与使用 zig 构建其他平台的能力类似,我们只需要将 zig 的 target 修改成为鸿蒙的即可。同时我们的构建产物应该输出为.so
,因此我们的构建命令如下所示:
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC="zig cc -target aarch64-linux-ohos" CXX="zig c++ -target aarch64-linux-ohos" go build -o ../go-shared/libadd.so -buildmode=c-shared ../go-shared/add.go
注意:这里的 zig 不能使用官方提供的版本,官方提供的版本无法对鸿蒙使用 zig cc 能力,具体可以参考之前的文章 当zig-lang构建系统遇上鸿蒙。
尝试构建运行,我们发现可以正常得到鸿蒙的产物。
验证
接下来我们使用 ohos-rs 来简单编写一个包来验证我们的能力是否完整。
我们在lib.rs
中声明我们将要通过 ffi 调用的函数能力。
use std::ffi::c_int;
use napi_derive_ohos::napi;
extern "C" {
pub fn Add(left: c_int, right: c_int) -> c_int;
}
#[napi]
pub fn add(left: i32, right: i32) -> i32 {
unsafe { Add(left, right) }
}
然后在build.rs
中声明我们需要链接的动态库即可。
use std::env;
fn main() {
napi_build_ohos::setup();
let dir = env::current_dir().unwrap();
let binding = dir.parent().unwrap().join("go-shared");
println!("cargo:rustc-link-search={}",binding.to_str().unwrap());
println!("cargo:rustc-link-lib=dylib=add");
}
注意:我们现在应该只构建 arm64 架构的产物,因为我们只提供了 arm64 架构下的libadd.so
文件。
因此,我们构建命令如下所示:
ohrs build --arch aarch
现在我们只需要将构建产物在鸿蒙中正常调用即可。
import { add } from 'libhello.so';
@Entry
@Component
struct Index {
build() {
Row() {
Column() {
Button('add').onClick(() => {
const ret = add(1,2);
console.log(`ret is:${ret}`);
})
}
.width('100%')
}
.height('100%')
}
}
当然不出意外的话,应该是出意外了~ 😭 当我们尝试运行的时候不出意外的应该是报错了,应用直接 crash了,报错信息如下所示:
看起来应该是 native 模块有问题导致的,我们在 hilog 中查询相关日志会发现这样一个错误:
到这里,已成艺术(划掉。这里就回到了文章的最开头讲的内容:Go 目前无法在鸿蒙上正常运行。
这是因为 go 对于 musl 基础库的兼容性不是特别好,而鸿蒙本身的 C 基础库就是基于 musl 实现的,因此运行时会出现一些问题,具体的问题可以参考 issue。
尾
到这里我们就讲完了 go 目前在鸿蒙上的原生模块的一些尝试,有相关小伙伴想要尝试使用 go 开发鸿蒙能力的基本上可以放弃了,当然这是我个人目前得出的结论,如果有什么不对的或者说遗漏导致结论错误,希望你能给予指正。
希望本文对你有所帮助~