使用Rust高效开发鸿蒙Native模块

1,341 阅读3分钟

本文主要介绍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

其他用法参考 ohos-rs napi-rs

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,提供给外部使用。