本文主要介绍Rust在鸿蒙Native中的开发实践,并详细介绍如何把Rust编译生成的so库和ts文件打包成Har包。
1. Native模块开发
详细代码参考 《Rust移动端跨平台开发实践》中的ohos模块, ohos目录下使用ohos-rs对rust代码写一些binding code。以下仅对binding code代码进行解释。
1.1 对象
#[napi(object)]
pub struct MobileConfig {
pub id: String,
pub username: String,
pub pwd: String,
pub user_properties: Option<HashMap<String, String>>,
}
#[napi(object)]可以根据普通的rust struct生成ts 中的interface,具体如下
export interface MobileConfig {
id: string
username: string
pwd: string
userProperties?: Record<string, string>
}
1.2 函数
#[napi]
pub fn hello_rust(config: MobileConfig) -> Result<()> {
mobile::hello_rust(mq_config_2_inner(config))
.map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;
Ok(())
}
#[napi]可以根据rust的普通函数生成ts中的function,具体如下
export declare export function helloRust(config: MobileConfig): void
1.3 Callback 回调
涉及到带callback的接口要麻烦一点,因为在Node.js的单线程执行环境中,数据和函数调用操作与环境(env)紧密相关,而env无法安全地在线程间传递,这限制了子线程操作的便利性。异步接口设置的Callback可能在子线程处理,导致跨线程回调调用问题。为了解决这一问题,N-API引入了thread-safe-function(TSFN)
下面这段代码讲解如何在ohos-rs中使用callback。
#[napi(ts_args_type = "callback: (err: null | Error, msg: string) => void")]
pub fn set_logger(callback: JsFunction) -> Result<()> {
let call_fn: ThreadsafeFunction<String, ErrorStrategy::CalleeHandled> = callback
.create_threadsafe_function(0, |ctx: ThreadSafeCallContext<String>| {
ctx.env.create_string(ctx.value.as_str()).map(|v| vec![v])
})?;
mobile::set_logger(Box::new(FfiLog { logger: call_fn }))
.map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;
Ok(())
}
其中ErrorStrategy定义如下
pub enum ErrorStrategy {
/// Input errors (including conversion errors) are left for the callee to
/// handle:
///
/// The callee receives an extra `error` parameter (the first one), which is
/// `null` if no error occurred, and the error payload otherwise.
CalleeHandled,
/// Input errors (including conversion errors) are deemed fatal:
///
/// they can thus cause a `panic!` or abort the process.
///
/// The callee thus is not expected to have to deal with [that extra `error`
/// parameter][CalleeHandled], which is thus not added.
Fatal,
}
ErrorStrategy::CalleeHandled即调用者处理错误,此时回调接口会接收一个额外的error参数(第一个参数),如果没有错误发生,该参数为null,否则为错误数据。所以上述接口生成的Ts接口对应如下
export declare export function setLogger(callback: (err: null | Error, msg: string) => void): void
2. 把so库和ts文件打包成Har包
2.1 编译产物介绍
ohos路径下的编译产物如下
.
├── arm64-v8a
│ └── libmobile_oh.so
├── armeabi-v7a
│ └── libmobile_oh.so
├── index.d.ts
└── x86_64
└── libmobile_oh.so
so是不同架构下的动态库。index.d.ts是接口文件,完整内容如下
/* auto-generated by OHOS-RS */
/* eslint-disable */
export interface MobileConfig {
id: string
username: string
pwd: string
userProperties?: Record<string, string>
}
export declare export function helloRust(config: MobileConfig): void
export declare export function setLogger(callback: (err: null | Error, msg: string) => void): void
2.2 打包Har文件
新建一个鸿蒙工程,工程内建一个Static Library的module,名称为mobilerust
├── AppScope
│ └── resources
├── entry
│ ├── lib
│ └── src
├── mobilerust
│ ├── libs
│ │ └── arm64-v8a
│ │ └── libmobile_oh.so
│ └── src
│ ├── main
│ │ ├── ets
│ │ │ └── utils
│ │ │ └── Mobile.ets
│ │ └── rust
│ │ └── types
│ │ └── libmobileoh
│ │ ├── index.d.ts
│ │ └── oh-package.json5
├── ── oh-package.json5
把arm64-v8a的so拷贝到目录./mobilerust/libs/arm64-v8a/下,把index.d.ts拷贝到./mobilerust/src/main/rust/types/libmobileoh/目录下。
目录下的oh-package.json5配置如下
{
"name": "libmobile_oh.so",
"types": "./index.d.ts",
"version": "1.0.0",
"description": "Please describe the basic information."
}
./mobilerust/ 下的oh-package.json5配置如下
{
"name": "mobilerust",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "Index.ets",
"author": "",
"license": "Apache-2.0",
"dependencies": {
"libmobile_oh.so": "file:./src/main/rust/types/libmobileoh",
}
}
./mobilerust/src/main/ets/utils/路径下的Mobile.ets对生成的ts接口做一个简单封装(该示例仅是一个测试,实际请按具体接口封装)
import { helloRust, MobileConfig, setLogger } from "libmobile_oh.so";
export class Mobile {
public static helloRust() {
try {
setLogger((err: null | Error, msg: string) => {
console.log(`sdk log err ${err} msg ${msg}`)
})
} catch (e) {
console.log(`set logger ${e}`)
}
let config : MobileConfig = {
id: '111',
username: '2222',
pwd: '3333'
};
helloRust(config)
}
}
对外导出的是上述Mobile.ets封装的接口,最后可以把module mobilerust打包成mobilerust.har,提供给外部使用。