[文章]Native API在HarmonyOS应用工程中的使用指导

阅读量0
0
0

HarmonyOS 的应用必须用 js 来桥接 native。需要使用ace_napi仓中提供的 napi 接口来处理 js 交互。napi 提供的接口名与三方 Node.js 一致,目前支持部分接口,符号表见 ace_napi 仓中的 libnapi.ndk.json 文件。

开发流程

在 DevEco Studio 的模板工程中包含使用 Native API 的默认工程,使用 File->New->Create Project 创建 Native C++模板工程。创建后在 main 目录下会包含 cpp 目录,可以使用 ace_napi 仓下提供的 napi 接口进行开发。

js 侧通过 import 引入 native 侧包含处理 js 逻辑的 so,如:import hello from 'libhello.so',意为使用 libhello.so 的能力,native 侧通过 napi 接口创建的 js 对象会给到应用 js 侧的 hello 对象。

开发建议

注册建议

● nm_register_func 对应的函数需要加上 static,防止与其他 so 里的符号冲突。

● 模块注册的入口,即使用__attribute__((constructor))修饰的函数的函数名需要确保不与其他模块重复。

so 命名规则

so 命名必须符合以下规则:

● 每个模块对应一个 so。

● 如模块名为 hello,则 so 的名字为 libhello.so,napi_module 中 nm_modname 字段应为 hello,大小写与模块名保持一致,应用使用时写作:import hello from 'libhello.so'。

js 对象线程限制

ark 引擎会对 js 对象线程使用进行保护,使用不当会引起应用 crash,因此需要遵循如下原则:

● napi 接口只能在 js 线程使用。

● env 与线程绑定,不能跨线程使用。native 侧 js 对象只能在创建时的线程使用,即与线程所持有的 env 绑定。

头文件引入限制

在使用 napi 的对象和方法时需要引用"napi/native_api.h"。否则在只引入三方库头文件时,会出现接口无法找到的编译报错。

napi_create_async_work 接口说明

napi_create_async_work 里有两个回调:

● execute:用于异步处理业务逻辑。因为不在 js 线程中,所以不允许调用 napi 的接口。业务逻辑的返回值可以返回到 complete 回调中处理。

● complete:可以调用 napi 的接口,将 execute 中的返回值封装成 js 对象返回。此回调在 js 线程中执行。

napi_status napi_create_async_work(napi_env env,
                                   napi_value async_resource,
                                   napi_value async_resource_name,
                                   napi_async_execute_callback execute,
                                   napi_async_complete_callback complete,
                                   void* data,
                                   napi_async_work* result)

storage 模块——同步异步接口封装

模块简介

本示例通过实现 storage 模块展示了同步和异步方法的封装。storage 模块实现了数据的保存、获取、删除、清除功能。

接口声明

import { AsyncCallback } from './basic';
declare namespace storage {
  function get(key: string, callback: AsyncCallback<string>): void;
  function get(key: string, defaultValue: string, callback: AsyncCallback<string>): void;
  function get(key: string, defaultValue?: string): Promise<string>;
  function set(key: string, value: string, callback: AsyncCallback<string>): void;
  function remove(key: string, callback: AsyncCallback<void>): void;
  function clear(callback: AsyncCallback<void>): void;
  function getSync(key: string, defaultValue?: string): string;
  function setSync(key: string, value: string): void;
  function removeSync(key: string): void;
  function clearClear(): void;
}
export default storage;

具体实现

完整代码参见仓下路径:OpenHarmony/arkui_napi仓库 sample/native_module_storage/

1、模块注册

如下,注册了 4 个同步接口(getSync、setSync、removeSync、clearSync)、4 个异步接口(get、set、remove、clear)。

/***********************************************
 * Module export and register
 ***********************************************/
static napi_value StorageExport(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_FUNCTION("get", JSStorageGet),
        DECLARE_NAPI_FUNCTION("set", JSStorageSet),
        DECLARE_NAPI_FUNCTION("remove", JSStorageDelete),
        DECLARE_NAPI_FUNCTION("clear", JSStorageClear),


        DECLARE_NAPI_FUNCTION("getSync", JSStorageGetSync),
        DECLARE_NAPI_FUNCTION("setSync", JSStorageSetSync),
        DECLARE_NAPI_FUNCTION("deleteSync", JSStorageDeleteSync),
        DECLARE_NAPI_FUNCTION("clearSync", JSStorageClearSync),
    };
    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
    return exports;
}


// storage module
static napi_module storage_module = {.nm_version = 1,
                                     .nm_flags = 0,
                                     .nm_filename = nullptr,
                                     .nm_register_func = StorageExport,
                                     .nm_modname = "storage",
                                     .nm_priv = ((void*)0),
                                     .reserved = {0}};


// storage module register
extern "C" __attribute__((constructor)) void StorageRegister()
{
    napi_module_register(&storage_module);
}

2、getSync 函数实现

如上注册时所写,getSync 对应的函数是 JSStorageGetSync 。从 gKeyValueStorage 中获取数据后,创建一个字符串对象并返回。

static napi_value JSStorageGetSync(napi_env env, napi_callback_info info)
{
    GET_PARAMS(env, info, 2);
    NAPI_ASSERT(env, argc >= 1, "requires 1 parameter");
    char key[32] = {0};
    size_t keyLen = 0;
    char value[128] = {0};
    size_t valueLen = 0;


    // 参数解析
    for (size_t i = 0; i < argc; i++) {
        napi_valuetype valueType;
        napi_typeof(env, argv[i], &valueType);


        if (i == 0 && valueType == napi_string) {
            napi_get_value_string_utf8(env, argv[i], key, 31, &keyLen);
        } else if (i == 1 && valueType == napi_string) {
            napi_get_value_string_utf8(env, argv[i], value, 127, &valueLen);
            break;
        } else {
            NAPI_ASSERT(env, false, "type mismatch");
        }
    }


    // 获取数据的业务逻辑,这里简单地从一个全局变量中获取
    auto itr = gKeyValueStorage.find(key);
    napi_value result = nullptr;
    if (itr != gKeyValueStorage.end()) {
        // 获取到数据后创建一个string类型的JS对象
        napi_create_string_utf8(env, itr->second.c_str(), itr->second.length(), &result);
    } else if (valueLen > 0) {
        // 没有获取到数据使用默认值创建JS对象
        napi_create_string_utf8(env, value, valueLen, &result);
    } else {
        NAPI_ASSERT(env, false, "key does not exist");
    }
    // 返回结果
    return result;
}

3、get 函数实现

如上注册时所写,get 对应的函数式 JSStorageGet。

static napi_value JSStorageGet(napi_env env, napi_callback_info info)
{
    GET_PARAMS(env, info, 3);
    NAPI_ASSERT(env, argc >= 1, "requires 1 parameter");


    // StorageAsyncContext是自己定义的一个类,用于保存执行过程中的数据
    StorageAsyncContext* asyncContext = new StorageAsyncContext();


    asyncContext->env = env;


    // 获取参数
    for (size_t i = 0; i < argc; i++) {
        napi_valuetype valueType;
        napi_typeof(env, argv[i], &valueType);


        if (i == 0 && valueType == napi_string) {
            napi_get_value_string_utf8(env, argv[i], asyncContext->key, 31, &asyncContext->keyLen);
        } else if (i == 1 && valueType == napi_string) {
            napi_get_value_string_utf8(env, argv[i], asyncContext->value, 127, &asyncContext->valueLen);
        } else if (i == 1 && valueType == napi_function) {
            napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef);
            break;
        } else if (i == 2 && valueType == napi_function) {
            napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef);
        } else {
            NAPI_ASSERT(env, false, "type mismatch");
        }
    }


    napi_value result = nullptr;


    // 根据参数判断开发者使用的是promise还是callback
    if (asyncContext->callbackRef == nullptr) {
        // 创建promise
        napi_create_promise(env, &asyncContext->deferred, &result);
    } else {
        napi_get_undefined(env, &result);
    }


    napi_value resource = nullptr;
    napi_create_string_utf8(env, "JSStorageGet", NAPI_AUTO_LENGTH, &resource);


    napi_create_async_work(
        env, nullptr, resource,
        // 回调1:此回调由napi异步执行,里面就是需要异步执行的业务逻辑。由于是异步线程执行,所以不要在此通过napi接口操作JS对象。
        [](napi_env env, void* data) {
            StorageAsyncContext* asyncContext = (StorageAsyncContext*)data;
            auto itr = gKeyValueStorage.find(asyncContext->key);
            if (itr != gKeyValueStorage.end()) {
                strncpy_s(asyncContext->value, 127, itr->second.c_str(), itr->second.length());
                asyncContext->status = 0;
            } else {
                asyncContext->status = 1;
            }
        },
        // 回调2:此回调在上述异步回调执行完后执行,此时回到了JS线程来回调开发者传入的回调
        [](napi_env env, napi_status status, void* data) {
            StorageAsyncContext* asyncContext = (StorageAsyncContext*)data;
            napi_value result[2] = {0};
            if (!asyncContext->status) {
                napi_get_undefined(env, &result[0]);
                napi_create_string_utf8(env, asyncContext->value, strlen(asyncContext->value), &result[1]);
            } else {
                napi_value message = nullptr;
                napi_create_string_utf8(env, "key does not exist", NAPI_AUTO_LENGTH, &message);
                napi_create_error(env, nullptr, message, &result[0]);
                napi_get_undefined(env, &result[1]);
            }
            if (asyncContext->deferred) {
                // 如果走的是promise,那么判断回调1的结果
                if (!asyncContext->status) {
                    // 回调1执行成功(status为1)时触发,也就是触发promise里then里面的回调
                    napi_resolve_deferred(env, asyncContext->deferred, result[1]);
                } else {
                    // 回调1执行失败(status为0)时触发,也就是触发promise里catch里面的回调
                    napi_reject_deferred(env, asyncContext->deferred, result[0]);
                }
            } else {
                // 如果走的是callback,则通过napi_call_function调用callback回调返回结果
                napi_value callback = nullptr;
                napi_value returnVal;
                napi_get_reference_value(env, asyncContext->callbackRef, &callback);
                napi_call_function(env, nullptr, callback, 2, result, &returnVal);
                napi_delete_reference(env, asyncContext->callbackRef);
            }
            napi_delete_async_work(env, asyncContext->work);
            delete asyncContext;
        },
        (void*)asyncContext, &asyncContext->work);
    napi_queue_async_work(env, asyncContext->work);


    return result;
}

4、js 示例代码

import storage from 'libstorage.so';


export default {
  testGetSync() {
      const name = storage.getSync('name');
    console.log('name is ' + name);
  },
  testGet() {
    storage.get('name')
    .then(date => {
        console.log('name is ' + data);
    })
    .catch(error => {
        console.log('error: ' + error);
    });
  }
}

NetServer 模块——native 与 js 对象绑定

模块简介

本示例展示了 on/off/once 订阅方法的实现,同时也包含了 C++ 与 js 对象通过 wrap 接口的绑定。NetServer 模块实现了一个网络服务。

接口声明

export class NetServer {
  function start(port: number): void;
  function stop(): void;
  function on('start' | 'stop', callback: Function): void;
  function once('start' | 'stop', callback: Function): void;
  function off('start' | 'stop', callback: Function): void;
}

具体实现

完整代码参见:OpenHarmony/arkui_napi仓库 sample/native_module_netserver/

1、模块注册

static napi_value NetServer::Export(napi_env env, napi_value exports)
{
    const char className[] = "NetServer";
    napi_property_descriptor properties[] = {
        DECLARE_NAPI_FUNCTION("start", JS_Start),
        DECLARE_NAPI_FUNCTION("stop", JS_Stop),
        DECLARE_NAPI_FUNCTION("on", JS_On),
        DECLARE_NAPI_FUNCTION("once", JS_Once),
        DECLARE_NAPI_FUNCTION("off", JS_Off),
    };
    napi_value netServerClass = nullptr;


    napi_define_class(env, className, sizeof(className), JS_Constructor, nullptr, countof(properties), properties,
                      &netServerClass);


    napi_set_named_property(env, exports, "NetServer", netServerClass);


    return exports;
}

2、在构造函数中绑定 C++ 与 JS 对象

napi_value NetServer::JS_Constructor(napi_env env, napi_callback_info cbinfo)
{
    napi_value thisVar = nullptr;
    void* data = nullptr;
    napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisVar, &data);


    // C++ Native对象,准备与JS对象映射在一起
    NetServer* netServer = new NetServer(env, thisVar);


    // 使用napi_wrap将netServer与thisVar(即当前创建的这个JS对象)做绑定
    napi_wrap(
        env, thisVar, netServer,
        // JS对象由引擎自动回收释放,当JS对象释放时触发该回调,在改回调中释放netServer
        [](napi_env env, void* data, void* hint) {
            printf("NetServer::Destructor\\n");
            NetServer* netServer = (NetServer*)data;
            delete netServer;
        },
        nullptr, nullptr);


    return thisVar;
}

3、从 JS 对象中取出 C++ 对象

napi_value NetServer::JS_Start(napi_env env, napi_callback_info cbinfo)
{
    size_t argc = 1;
    napi_value argv[1] = {0};
    napi_value thisVar = nullptr;
    void* data = nullptr;
    napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, &data);


    NetServer* netServer = nullptr;
    // 通过napi_unwrap从thisVar中取出C++对象
    napi_unwrap(env, thisVar, (void**)&netServer);


    NAPI_ASSERT(env, argc >= 1, "requires 1 parameter");


    napi_valuetype valueType;
    napi_typeof(env, argv[0], &valueType);
    NAPI_ASSERT(env, valueType == napi_number, "type mismatch for parameter 1");


    int32_t port = 0;
    napi_get_value_int32(env, argv[0], &port);


    // 开启服务
    netServer->Start(port);


    napi_value result = nullptr;
    napi_get_undefined(env, &result);
    return result;
}

netServer->Start 后回调通过 on 注册的 start 事件。

int NetServer::Start(int port)
{
    printf("NetServer::Start thread_id: %ld \\n", uv_thread_self());


    struct sockaddr_in addr;
    int r;


    uv_ip4_addr("0.0.0.0", port, &addr);


    r = uv_tcp_init(loop_, &tcpServer_);
    if (r) {
        fprintf(stderr, "Socket creation error\\n");
        return 1;
    }


    r = uv_tcp_bind(&tcpServer_, (const struct sockaddr*)&addr, 0);
    if (r) {
        fprintf(stderr, "Bind error\\n");
        return 1;
    }


    r = uv_listen((uv_stream_t*)&tcpServer_, SOMAXCONN, OnConnection);
    if (r) {
        fprintf(stderr, "Listen error %s\\n", uv_err_name(r));
        return 1;
    }


    // 服务启动后触发“start”事件
    Emit("start", nullptr);


    return 0;
}

4、注册或释放(on/off/once)事件,以 on 为例

napi_value NetServer::JS_On(napi_env env, napi_callback_info cbinfo)
{
    size_t argc = 2;
    napi_value argv[2] = {0};
    napi_value thisVar = 0;
    void* data = nullptr;
    napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, &data);


    NetServer* netServer = nullptr;
    // 通过napi_unwrap取出NetServer指针
    napi_unwrap(env, thisVar, (void**)&netServer);


    NAPI_ASSERT(env, argc >= 2, "requires 2 parameter");


    // 参数类型校验
    napi_valuetype eventValueType;
    napi_typeof(env, argv[0], &eventValueType);
    NAPI_ASSERT(env, eventValueType == napi_string, "type mismatch for parameter 1");


    napi_valuetype eventHandleType;
    napi_typeof(env, argv[1], &eventHandleType);
    NAPI_ASSERT(env, eventHandleType == napi_function, "type mismatch for parameter 2");


    char type[64] = {0};
    size_t typeLen = 0;


    napi_get_value_string_utf8(env, argv[0], type, 63, &typeLen);


    // 注册事件handler
    netServer->On((const char*)type, argv[1]);


    napi_value result = nullptr;
    napi_get_undefined(env, &result);
    return result;
}

5、js 示例代码

import { NetServer } from 'libnetserver.so';


export default {
  testNetServer() {
      var netServer = new NetServer();
      netServer.on('start', (event) => {});
      netServer.start(1000); // 端口号1000, start完成后回调上面注册的 “start” 回调
  }
}

在非 JS 线程中回调 JS 接口

模块简介

本示例介绍如何在非 JS 线程中回调 JS 应用的回调函数。例如 JS 应用中注册了某个 sensor 的监听,这个 sensor 的数据是由一个 SA 服务来上报的,当 SA 通过 IPC 调到客户端时,此时的执行线程是一个 IPC 通信线程,与应用的 JS 线程是两个不同的线程。这时就需要将执行 JS 回调的任务抛到 JS 线程中才能执行,否则会出现崩溃。

具体实现

完整代码参见:OpenHarmony/arkui_napi仓库 sample/native_module_callback/

1、模块注册

如下,注册了 1 个接口 test,会传入一个参数,类型为包含一个参数的函数。

/***********************************************
 * Module export and register
 ***********************************************/
static napi_value CallbackExport(napi_env env, napi_value exports)
{
    static napi_property_descriptor desc[] = {
        DECLARE_NAPI_FUNCTION("test", JSTest)
    };
    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
    return exports;
}


// callback module define
static napi_module callbackModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = CallbackExport,
    .nm_modname = "callback",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};


// callback module register
extern "C" __attribute__((constructor)) void CallbackTestRegister()
{
    napi_module_register(&callbackModule);
}

2、获取 env 中的 loop,抛任务回 JS 线程

#include <thread>


#include "napi/native_api.h"
#include "napi/native_node_api.h"


#include "uv.h"


struct CallbackContext {
    napi_env env = nullptr;
    napi_ref callbackRef = nullptr;
    int retData = 0;
};


void callbackTest(CallbackContext* context)
{
    uv_loop_s* loop = nullptr;
    // 此处的env需要在注册JS回调时保存下来。从env中获取对应的JS线程的loop。
    napi_get_uv_event_loop(context->env, &loop);

    // 创建uv_work_t用于传递私有数据,注意回调完成后需要释放内存,此处省略生成回传数据的逻辑,传回int类型1。
    uv_work_t* work = new uv_work_t;
    context->retData = 1;
    work->data = (void*)context;

    // 调用libuv接口抛JS任务到loop中执行。
    uv_queue_work(
        loop,
        work,
        // 此回调在另一个普通线程中执行,用于处理异步任务,回调执行完后执行下面的回调。本场景下该回调不需要执行任务。
        [](uv_work_t* work) {},
        // 此回调会在env对应的JS线程中执行。
        [](uv_work_t* work, int status) {
            CallbackContext* context = (CallbackContext*)work->data;
            napi_handle_scope scope = nullptr;
            // 打开handle scope用于管理napi_value的生命周期,否则会内存泄露。
            napi_open_handle_scope(context->env, &scope);
            if (scope == nullptr) {
                return;
            }


            // 调用napi。
            napi_value callback = nullptr;
            napi_get_reference_value(context->env, context->callbackRef, &callback);
            napi_value retArg;
            napi_create_int32(context->env, context->retData, &retArg);
            napi_value ret;
            napi_call_function(context->env, nullptr, callback, 1, &retArg, &ret);
            napi_delete_reference(context->env, context->callbackRef);


            // 关闭handle scope释放napi_value。
            napi_close_handle_scope(context->env, scope);


            // 释放work指针。
            if (work != nullptr) {
                delete work;
            }


            delete context;
        }
    );
}


static napi_value JSTest(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value argv[1] = { 0 };
    napi_value thisVar = nullptr;
    void* data = nullptr;
    napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);


    // 获取第一个入参,即需要后续触发的回调函数
    napi_valuetype valueType = napi_undefined;
    napi_typeof(env, argv[0], &valueType);
    if (valueType != napi_function) {
        return nullptr;
    }
    // 存下env与回调函数,用于传递
    auto asyncContext = new CallbackContext();
    asyncContext->env = env;
    napi_create_reference(env, argv[0], 1, &asyncContext->callbackRef);
    // 模拟抛到非js线程执行逻辑
    std::thread testThread(callbackTest, asyncContext);
    testThread.detach();


    return nullptr;
}

3、 js 示例代码

import callback from 'libcallback.so';


export default {
  testcallback() {
      callback.test((data) => {
      console.error('test result = ' + data)
    })
  }
}

回帖

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
链接复制成功,分享给好友