鸿蒙原生应用元服务开发-仓颉ArkTS相互操作(二)

鸿蒙时代
发布于 2024-8-1 10:43
浏览
0收藏

九、在 ArkTS 里操作仓颉对象
这里用例展示的是把仓颉对象分享到 ArkTS 运行时,使用 ArkTS 运行时的内存管理机制来控制仓颉对象的生命周期,并通过相关的互操作接口来访问该对象。

// 定义共享类
class Data <: SharedObject {
    Data(
        // 定义2个属性
        let id: Int64,
        let name: String
    ) {}

    static init() {
        // 注册导出到ark的函数
        JSModule.registerFunc("createData", createData)
        JSModule.registerFunc("setDataId", setDataId)
        JSModule.registerFunc("getDataId", getDataId)
    }

    // 创建共享对象
    static func createData(context: JSContext, _: JSCallInfo): JSValue {
        // 创建仓颉对象
        let data = Data(1, "abc")
        // 创建js对仓颉对象的引用
        let jsExternal = context.external(data)
        // 返回js对仓颉对象的引用
        return jsExternal.toJSValue()
    }

    // 设置对象的id
    static func setDataId(context: JSContext, callInfo: JSCallInfo): JSValue {
        // 读取参数
        let arg0 = callInfo[0]
        let arg1 = callInfo[1]

        // 把参数0转换为js对仓颉对象的引用
        let jsExternal = arg0.asExternal(context)
        // 获取仓颉对象
        let data: Data = jsExternal.cast<Data>().getOrThrow()
        // 把参数1转换为Float64
        let value = arg1.toNumber()

        // 仓颉对象修改属性
        data.id = Int64(value)

        // 返回undefined
        let result = context.undefined().toJSValue()
        return result
    }

    // 获取对象的id
    static func getDataId(context: JSContext, callInfo: JSCallInfo): JSValue {
        let arg0 = callInfo[0]

        let jsExternal = arg0.asExternal(context)

        let data: Data = jsExternal.cast<Data>().getOrThrow()

        let result = context.number(Float64(data.id)).toJSValue()
        return result
    }
}
import {requireCJLib} from "libark_interop_loader.so"
// 定义导出符号
interface CustomLib {
    createData(): undefined
    setDataId(data: undefined, value: number): void
    getDataId(data: undefined): number
}

// 加载自定义库
const cjLib = requireCJLib("libentry.so") as CustomLib

// 创建共享对象
let data = cjLib.createData()
// 操作对象属性
cjLib.setDataId(data, 3)
let id = cjLib.getDataId(data)

console.log("id is " + id)

JSExternal 对象在 ArkTS 里的类型会被识别为 undefined,直接使用 undefined 来作为参数很容易被传递错误的参数会在运行时出错,如下示例:

...
// 创建共享对象
let data = cjLib.createData()
// 操作对象属性
cjLib.setDataId(undefined, 3) // 错误的参数,应该传递的是仓颉引用,但是编译器能通过编译
let id = cjLib.getDataId(data)
...

十、把仓颉对象的引用挂在 JSObject 上传递到 ArkTS
在实际开发接口时,可以把 JSExternal 对象绑定到一个 JSObject 对象上,把 JSExternal 的数据隐藏起来,以此来提高接口的安全性。
下面通过一个例子来展示:

// 定义共享类
class Data <: SharedObject {
    Data(
        // 定义2个属性
        var id: Int64,
        let name: String
    ) {}

    static init() {
        // 注册导出到ark的函数
        JSModule.registerFunc("createData", createData)
    }

    // 创建共享对象
    static func createData(context: JSContext, _: JSCallInfo): JSValue {
        let data = Data(1, "abc")
        let jsExternal = context.external(data)

        // 创建空JSObject
        let object = context.object()
        // 把js对仓颉对象的引用挂在JSObject的隐藏属性上
        object.attachCJObject(jsExternal)

        // 为js对象增加2个方法
        object["setId"] = context.function(setDataId).toJSValue()
        object["getId"] = context.function(getDataId).toJSValue()

        return object.toJSValue()
    }

    // 设置对象的id
    static func setDataId(context: JSContext, callInfo: JSCallInfo): JSValue {
        // 获取this指针
        let thisArg = callInfo.thisArg
        let arg0 = callInfo[0]

        // 把this指针转换为JSObject
        let thisObject = thisArg.asObject(context)
        // 从JSObject上获取隐藏属性
        let jsExternal = thisObject.getAttachInfo().getOrThrow()
        // 从js对仓颉对象的引用上获取仓颉对象
        let data = jsExternal.cast<Data>().getOrThrow()
        // 把参数0转换为Float64
        let value = arg0.toNumber()

        // 修改仓颉对象的属性
        data.id = Int64(value)

        let result = context.undefined()
        return result.toJSValue()
    }

    // 获取对象的id
    static func getDataId(context: JSContext, callInfo: JSCallInfo): JSValue {
        let thisArg = callInfo.thisArg
        let thisObject = thisArg.asObject(context)
        let jsExternal = thisObject.getAttachInfo().getOrThrow()
        let data = jsExternal.cast<Data>().getOrThrow()

        let result = context.number(Float64(data.id)).toJSValue()
        return result
    }
}
import {requireCJLib} from "libark_interop_loader.so"
// 定义导出符号
interface Data {
    setId(value: number): void
    getId(): number
}

interface CustomLib {
    createData(): Data
}

// 加载自定义库
const cjLib = requireCJLib("libentry.so") as CustomLib

// 创建共享对象
let data = cjLib.createData()
// 操作对象属性
data.setId(3)
let id = data.getId()

console.log("id is " + id)

十一、为仓颉共享对象创建 JSClass
把所有的对象操作方法直接挂在对象上,一方面占用内存比较大,另一方面创建对象的开销比较大。对于追求性能的场景,可以定义一个 JSClass 来加速对象创建和减小内存占用。

// 定义共享类
class Data <: SharedObject {
    Data(
        // 定义2个属性
        var id: Int64,
        let name: String
    ) {}

    static init() {
        // 注册导出到ark的类
        JSModule.registerClass("Data") { context =>
            // 创建JSClass
            let clazz = context.clazz(jsConstructor)
            // 增加方法
            clazz.addMethod(context.string("setId"), context.function(setDataId))
            clazz.addMethod(context.string("getId"), context.function(getDataId))

            return clazz
        }
    }

    // js构造函数
    static func jsConstructor(context: JSContext, callInfo: JSCallInfo): JSValue {
        // 获取this指针
        let thisArg = callInfo.thisArg
        // 转换为JSObject
        let thisObject = thisArg.asObject(context)
        // 创建创建对象
        let data = Data(1, "abc")
        // 创建js对仓颉对象的引用
        let jsExternal = context.external(data)
        // 设置JSObject属性
        thisObject.attachCJObject(jsExternal)
        return thisObject.toJSValue()
    }

    // 设置对象的id
    static func setDataId(context: JSContext, callInfo: JSCallInfo): JSValue {
        // 获取this指针
        let thisArg = callInfo.thisArg
        // 把this指针转换为JSObject
        let thisObject = thisArg.asObject(context)
        // 从JSObject上获取隐藏属性
        let jsExternal = thisObject.getAttachInfo().getOrThrow()
        // 从js对仓颉对象的引用上获取仓颉对象
        let data = jsExternal.cast<Data>().getOrThrow()

        let arg0 = callInfo[0]
        // 把参数0转换为Float64
        let value = arg0.toNumber()

        // 修改仓颉对象的属性
        data.id = Int64(value)

        let result = context.undefined()
        return result.toJSValue()
    }

    // 获取对象的id
    static func getDataId(context: JSContext, callInfo: JSCallInfo): JSValue {
        let thisArg = callInfo.thisArg
        let thisObject = thisArg.asObject(context)
        let jsExternal = thisObject.getAttachInfo().getOrThrow()
        let data = jsExternal.cast<Data>().getOrThrow()

        let result = context.number(Float64(data.id)).toJSValue()
        return result
    }
}
import {requireCJLib} from "libark_interop_loader.so"
// 定义Data的接口
interface Data {
    setId(value: number): void
    getId(): number
}

interface CustomLib {
    // 定义Data的构造函数(JSClass)
    Data: {new (): Data}
}

// 加载自定义库
const cjLib  = requireCJLib("libentry.so") as CustomLib

// 创建共享对象
let data = new cjLib.Data()
// 操作对象属性
data.setId(3)
let id = data.getId()

console.log("id is " + id)

十二、ArkTS 互操作和仓颉多线程
ArkTS 是单线程执行的虚拟机,在运行时上没有对并发做任何的容错;而仓颉在语法上支持内存共享的多线程。
如果在互操作的场景不加限制的使用多线程,可能会导致无法预期的错误,因此需要一些规范和指引来保证程序正常执行:
ArkTS 代码以及大部分互操作接口只能在 ArkTS 线程上执行,否则会抛出仓颉异常。
在进入其他线程前,需要把所有依赖的 ArkTS 数据转换为仓颉数据。
在其他线程如果想要使用 ArkTS 接口,需要通过 context.postJSTask 切换到 ArkTS 线程来执行。
下面通过一个用例来展示具体做法,该用例是互操作函数,该函数的功能是对两个数字相加,并调用回调来返回相加数。

import {requireCJLib} from "libark_interop_loader.so"
// 定义导出的接口
interface CustomLib {
    addNumberAsync(a: number, b: number, callback: (result: number)=>void): void
}
// 导入仓颉库
const cjLib = requireCJLib("libentry.so") as CangjieLib;
// 调用仓颉函数
cjLib.addNumberAsync(1, 2, (result)=> {
    console.log("1 + 2 = " + result)
})
// 类名没有影响
class Main {
    // 定义静态构造函数
    static init() {
        // 注册键值对
        JSModule.registerFunc("addNumberAsync", addNumberAsync)
    }
}

func addNumberAsync(context: JSContext, callInfo: JSCallInfo): JSValue {
    // 从JSCallInfo获取参数列表
    let arg0: JSValue = callInfo[0]
    let arg1: JSValue = callInfo[1]
    let arg2: JSValue = callInfo[2]
    // 把JSValue转换为仓颉类型
    let a: Float64 = arg0.toNumber()
    let b: Float64 = arg1.toNumber()
    let callback = arg2.asFunction(context)
    // 新建仓颉线程
    spawn {
        // 实际仓颉函数行为
        let value = a + b
        // 发起异步回调
        context.postJSTask {
            // 创建result
            let result = context.number(value)
            // 调用js回调
            callback.call(result)
        }
    }

    // 返回 void
    return context.undefined()
}

在 ArkTS 存在着 Promise,这是对回调机制的一种封装,配合 async 、 await 的语法让回调机制变成同步调用的形式。对于上一个用例,使用 Promise 的形式来定义接口和访问:

// 接口定义
func addNumberAsync(context: JSContext, callInfo: JSCallInfo): JSValue {
    // 参数转换为仓颉类型
    let a = callInfo[0].toNumber()
    let b = callInfo[1].toNumber()
    // 创建PromiseCapability对象
    let promise = context.promiseCapability()
    // 创建新线程
    spawn {
        // 在新线程执行仓颉逻辑
        let result = a + b
        // 切换到ArkTS线程
        context.postJSTask {
            // 在ArkTS线程执行resolve
            promise.resolve(context.number(result).toJSValue())
        }
    }
    // 返回Promise
    promise.toJSValue()
}
// ArkTS 调用
import {requireCJLib} from "libark_interop_loader.so"
// 定义导出的接口
interface CustomLib {
    addNumberAsync(a: number, b: number): Promise<number>
}

async function main() {
    // 导入仓颉库
    const cjLib = requireCJLib("libentry.so") as CangjieLib;
    // 调用仓颉函数
    let result = await cjLib.addNumberAsync(1, 2)
    console.log("1 + 2 = " + result)
}

main()

资料来源:HarmonyOS Developer 官方网站

分类
标签
收藏
回复
举报
回复
    相关推荐