• Lv0
    粉丝1

积分41 / 贡献20

提问0答案被采纳0文章1

作者动态

    [经验分享] 【开源三方库】Aki:一行代码极简体验JS&C++跨语言交互 原创 精华

    pandafactory 显示全部楼层 发表于 2023-11-30 23:39:35

    OpenHarmony知识体系组

    本篇主创:郭振雄

    一、简介

    OpenAtom OpenHarmony(以下简称“OpenHarmony”)的前端开发语言是ArkTS,在TypeScript(简称TS)生态基础上做了进一步扩展,继承了TS的所有特性,是JavaScript(简称JS)的超集。而Node-API(简称NAPI)是方舟引擎用于封装JS能力为Native插件的API,是前端JS与Native C/C++的FFI(Foreign Function Interface 跨语言交互接口)。

    Aki——针对OpenHarmony上提供JS与C/C++跨语言访问场景的解决方案,支持复杂度仅为O(1)级别的极简语法糖使用方式,一行代码无障碍完成JS与C/C++的跨语言交互,所键即所得。同时开发者无需关心NAPI的线程安全问题、Native对象GC问题,为开发者屏蔽NAPI内部复杂逻辑。

    OpenHarmony中NAPI的用法不在本文阐述,不然就有点像孔乙己“茴香豆有几种写法”的感觉了。感兴趣的可以参考OpenHarmony关于Native API使用指导。而开发者使用NAPI过程中还会发现:为了做跨线程任务,需要做线程管理,需要关心环境上下文;为了使用结构体对象,需要关注napi_value生命周期GC如何管理;巴拉巴拉等等与自己业务无关的逻辑。搞了半天,发现业务代码一行没写,还在写NAPI的跨语言调用实现。拥有洁癖的开发者还会发现,很难做到隔离NAPI代码与业务代码,我们讨厌毫无边界性的编程。

    二、 所键即所得: NAPI的尽头就是Aki

    归根结底NAPI要做的就是FFI即跨语言调用,而开发者重视的是自己的业务逻辑而不是如何做跨语言调用:我就想把大象放进冰箱,你非要告诉我:先打开冰箱,然后抬起大象放进去,再关上冰箱。开发者想要的就是直截了当指明这个函数是个跨语言调用函数。Aki提供了JSBind语法糖,就做了这么一件事,开发者集成后,完全做到一行代码:所键即所得,并把业务代码与FFI代码完美隔离,提供了友好的边界性编程体验。

    示例一:同步异步接口封装

    示例一期望将C++业务逻辑(GetName)注册为JS同步接口(getSync)、异步接口(getAsync),Aki提供了极简的JSBind函数绑定语法糖,一行代码绑定跨语言调用接口:

                 ● C/C++ Code
    #include <aki/jsbind.h>
    
    // C++业务逻辑
    std::string GetName(std::string key)
    {
        std::string result = ...... // 获取数据的业务逻辑
    
        return result; // 返回字符串类型
    }
    
    // Aki JSBind语法糖
    JSBIND_ADDON(task_runner);
    JSBIND_GLOBAL() {
        JSBIND_FUNCTION(GetName, "getNameSync"); // 绑定同步方法
        JSBIND_PFUNCTION(GetName, "getNameAsync"); // 绑定异步方法
    }
                 ● JavaScript Code
    import libtask_runner from 'libtask_runner.so';
    
    const name = libstorage.getNameSync('name');// 调用同步方法
    console.log('name is ' + name);
    
    // 调用异步方法
    libstorage.getNameAsync('name').then(date => {
        console.log('name is ' + data);
    }).catch(error => {
        console.log('error: ' + error);
    });

    示例二:Native与JS对象绑定

    示例二期望将C++结构体/类对象(Person)逻辑注册为JS类对象(Person),包含类构造函数+类成员函数+类静态函数+类属性访问等特性,通知支持类对象作为参数及返回值。Aki提供了极简的JSBind对象绑定语法糖,开发者无需关注Native对象内存与JS引擎GC垃圾回收关系,直接绑定Native对象:

                 ● C/C++ Code
    #include <aki/jsbind.h>
    
    // C++逻辑
    struct Person {
        // 构造函数,用于JS侧 new 对象
        Person(std::string name) : name(name) {}
    
        // 静态函数
        static Person GetAllPerson(); // 支持类对象作为参数
    
        // 成员函数
        int SayHello();
    
        std::string name;
    };
    
    // Aki JSBind语法糖
    JSBIND_ADDON(person);
    JSBIND_CLASS(Person) {
        JSBIND_CONSTRUCTOR<std::string>(); // 绑定构造函数
        JSBIND_METHOD(GetAllPerson); // 绑定类静态函数
        JSBIND_METHOD(SayHello); // 绑定类成员函数
        JSBIND_PROPERTY(name); // 绑定类成员属性
    }
                 ● JavaScript Code
    import libperson from 'libperson.so';
    
    let person = new libperson.Person("aki"); // 调用构造函数
    console.log('person name: ' + person.name); // 访问类属性
    let greeting = person.SayHello(); // 调用类成员函数
    let persons = libperson.Person.GetAllPerson(); // 调用类静态函数
    

    示例三:在非JS线程中回调JS接口

    示例三期望在非JS线程中回调JS接口,Aki提供了线程安全的JSBind语法糖,开发者无需关注JS线程安全问题——OpenHarmony方舟引擎规定JS回调的任务必须抛到JS线程中才能执行,否则会出现崩溃(即Native侧只能在JS线程使用NAPI)。

                 ● C/C++ Code
    #include <aki/jsbind.h>
    
    // C++逻辑
    void SafetyCallback(std::function<void (std::string)> callback) {
        std::thread t([callback = std::move(callback)] () {
            callback("aki"); // 线程安全,直接调用
        });
        t.detach();
    }
    
    // Aki JSBind语法糖
    JSBIND_ADDON(sub_thread);
    JSBIND_GLOBAL() {
        JSBIND_FUNCTION(SafetyCallback);
    }
                 ● JavaScript Code
    import libsub_thread from 'libsub_thread.so';
    // 入参为JS方法回调
    libsub_thread.SafetyCallback((data) => {
      console.error('test result = ' + data); // test result = aki
    })

    示例四:Native调用绑定JS函数

    示例四期望在C/C++侧调用JS接口(非回调)创建rdb关系型数据库表。Aki提供了JS侧的内建JSBind语法糖,开发者可直接绑定JS侧函数供Native侧调用。

                 ● JavaScript Code
    import libAddon from 'libaddon.so'
    
    function createTable(table: string) {
        rdbStore.executeSql()... // OHOS 关系型数据库逻辑
    }
    
    libAddon.JSBind.bindFunction('createTable', createTable); // 绑定JS函数
                 ● C/C++ Code
    #include <aki/jsbind.h>
    
    // C++逻辑
    bool DoSomethingFromNative() {
        if (auto createTable = aki::JSBind::GetJSFunction("createTable")) {
            createTable->Invoke<void>("MYSTORE"); // 入参类型 string
        }

    示例五:类型转换

    Aki 支持丰富的类型转换,几乎所有JS的数据类型都可以通过Aki映射为同等的C/C++数据类型,开发者无需处理类型转换,如上述示例用法,框架支持自动匹配类型转换,下表为当前支持的完整类型转换关系:

    JavaScript C++
    Boolean bool
    Number short, int32, uint32, int64, double, enum
    String const char*, std::string
    Array std::vector<T>, std::array<T, N>
    FunctionPromise std::function<R (P...)>aki::Callbackn<R (P...)>aki::SafetyCallbackn<R (P...)>
    Class Object class
    JsonObject std::map<std::string, T>
    ArrayBufferTypedArray aki::ArrayBuffer

    三、集成依赖Aki

    1. 创建平台工程
    DevEco Studio 创建包含Native C++的工程
    
    
        File > New > Create Project | Module
    2. 配置依赖并安装
                 ● ohpm三方组件依赖:@ohos/aki

    指定模块路径下(如:项目根路径/entry),输入如下命令安装ohpm har包依赖:

    cd entry
    ohpm install @ohos/aki

    CMakeLists.txt添加依赖:

    ...
    set(AKI_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules/@ohos/aki) # 设置AKI根路径
    set(CMAKE_MODULE_PATH ${AKI_ROOT_PATH})
    find_package(Aki REQUIRED)
    ...
    target_link_libraries(${YOUR_TARGET} PUBLIC Aki::libjsbind) # 链接二进制依赖
    ...
                 ● 源码依赖
    
    用户自定义路径下(如:项目根路径/entry/src/main/cpp),输入如下命令下载源码:
    cd entry/src/main/cpp
    git clone https://gitee.com/openharmony-sig/aki.git

    CMakeLists.txt添加依赖::

    ...
    add_subdirectory(aki)
    target_link_libraries(entry PUBLIC aki_jsbind) // entry 为编译目标
    ...
    3. 编译工程 && 运行

    完成!!!

    《Aki使用指导》:https://gitee.com/openharmony-sig/aki

    《Aki example》:https://gitee.com/openharmony-sig/aki/tree/master/example/ohos

    OpenHarmony三方库中心仓:https://ohpm.openharmony.cn/#/cn/home

    DevEco Studio :https://developer.harmonyos.com/cn/develop/deveco-studio/

    Native API使用指导:https://docs.openharmony.cn/pages/v4.0/zh-cn/application-dev/napi/napi-guidelines.md/

    ©著作权归作者所有,转载或内容合作请联系作者

    您尚未登录,无法参与评论,登录后可以:
    参与开源共建问题交流
    认同或收藏高质量问答
    获取积分成为开源共建先驱

    精彩评论10

    chmchen

    沙发 发表于 2023-12-8 09:36:07
    这个版本在API10 IDE 4.0.3.601以上版本编译不过,应为在ArkTS中  let result = aki.SayHello("hello world");
    报“Use explicit types instead of "any", "unknown" (arkts-no-any-unknown) <ArkTSCheck>”

    pandafactory

    发表于 2023-12-12 14:03  IP属地: 贵州省贵阳市

    回复 chmchen: 你好,具体是跑哪个用例的时候报错的呢?当前ohos工程请使用examples/ohos下的,如果是hmos的使用examples/hmos下的

    chmchen

    发表于 2023-12-21 10:52  IP属地: - 中国广东省佛山市 中国电信IDC

    回复 pandafactory: 按照文档介绍的使用 aki
    import aki from 'libhello.so' // 工程编译出来的*.so
    语法在  4.0.3.601 的ide上报错
    let result = aki.SayHello("hello world");

    pandafactory

    发表于 2024-1-2 10:48  IP属地: 贵州省贵阳市

    回复 chmchen: 报错信息有么? 需要看下

    【3 条回复】

    jjdw

    板凳 发表于 2023-12-29 11:43:02
    直接在c++代码里调用dlopen开so可以吗?

    pandafactory

    发表于 2024-1-2 10:48  IP属地: 贵州省贵阳市

    回复 jjdw: 可以,但这个跟AKI特性无关?

    jjdw

    发表于 2024-1-4 08:44  IP属地: - 中国江苏省 中国移动IDC

    回复 pandafactory: 有使用sample吗?我自己用了下dlopen出错,Symbol not found: NAPI_default/entry_GetABCCode

    【2 条回复】

    深开鸿_张守忠

    地板 发表于 2023-12-29 16:38:43
    在c++里调用已编译好的三方库so的接口,可以在cmakelist文件中添加对该so的依赖吗?如果可以的话,so文件放在哪个目录下?

    xq_qyh

    发表于 2024-2-2 15:37  IP属地: - 中国广东省广州市 中国电信IDC

    回复 深开鸿_张守忠: 同问


















    【1 条回复】

    silentdoer

    5# 发表于 2024-1-15 14:36:56
    对OpenHarmony4.0/4.1支持的怎么样呢?3.2说实在话一堆动态特性性能和可维护性都很差
    共10 条回复,点击查看

    Copyright   ©2023  OpenHarmony开发者论坛  京ICP备2020036654号-3 |技术支持 Discuz!

    返回顶部