【鸿蒙(HarmonyOS)开发】native层调用ets层函数

admin 2024年1月25日21:21:06评论17 views字数 5426阅读18分5秒阅读模式

【鸿蒙(HarmonyOS)开发】native层调用ets层函数

【鸿蒙(HarmonyOS)开发】native层调用ets层函数

前言

最近遇到一个问题,就是鸿蒙如何在native层去调用js的函数,做过Android开发的都知道,在jni当中,提供了获取java类和调用java方法的相关函数,但是呢,在napi当中,我们并没有获取js文件的相关函数,于是乎,在napi当中,如何调用js层的函数,便成为了一个问题。

探索过程

首先,那肯定是网络上搜一波解决方案,毕竟这个也是一个比较常见的功能,然后,通过一顿网络上的搜索,最终找到一个方案,也就是可以传入到native层当中,然后缓存下来,之后再通过缓存的函数指针来调用。

虽然找到了这个解决方案,但是呢,感觉这个方案又不是很优雅,于是乎,问了下其他的鸿蒙开发者,得到的答复依然是,需要缓存后调用,这,这就没有办法了,只能用这一种方案了,这里感谢解答的开发者们。

开发环境

我这里只有9的API,因此这里基于API 9进行测试,对于大于API 9的版本,因为我拿不到对应的sdk,因此无法测试,对于高版本文档,我这也看不了,因此也没办法查阅。

【鸿蒙(HarmonyOS)开发】native层调用ets层函数

具体环境如上图所示。

实现方案

有了这个方案,实现起来就比较简单,注意参考资料1当中的代码是有点问题的,也有可能是我版本的原因,通过查看文档,最终确定了可以使用的代码。

简单测试

首先,写一个简单的js函数

function addOne(val: number): number {
  return val + 1;
}

之后呢,需要写一个注册的函数,将js层的函数传递给native层。

// src/main/cpp/types/libentry/index.d.ts
export const register: (func: Function) => void;

export const callNative: () => void;

然后,在native层缓存函数,并且缓存下来函数指针。

#include "napi/native_api.h"
#include <hilog/log.h>

#define TAG "LQ->"

#define LOGI(...) ((void)OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, TAG, __VA_ARGS__))
#define LOGW(...) ((void)OH_LOG_Print(LOG_APP, LOG_WARN, LOG_DOMAIN, TAG, __VA_ARGS__))
#define LOGD(...) ((void)OH_LOG_Print(LOG_APP, LOG_DEBUG, LOG_DOMAIN, TAG, __VA_ARGS__))
#define LOGE(...) ((void)OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, TAG, __VA_ARGS__))

#define GET_NAPI_ERROR()                                                   
    const napi_extended_error_info *errorInfo;                             
    napi_status status = napi_get_last_error_info(env, &errorInfo);        
    if (status == napi_ok) {                                               
        LOGE("last error info is %{public}sn", errorInfo->error_message); 
    } else {                                                               
        LOGE("get last error erorn");                                     
    }


napi_ref js_fun_ref;

static napi_value Register(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value argv[1] = {NULL};
    napi_get_cb_info(env, info, &argc, argv, NULLNULL);

    napi_create_reference(env, argv[0], 1, &js_fun_ref);

    napi_value ret;
    napi_create_int32(env, 0, &ret);
    return ret;
}

static napi_value CallNative(napi_env env, napi_callback_info info) {
    napi_value js_fun = NULL;
    napi_get_reference_value(g_env, js_fun_ref, &js_fun);

    size_t argc = 1;
    napi_value argv[1];
    napi_create_int32(env, 666, &argv[0]);

    napi_value callbackResult = nullptr;
    napi_status callRet = napi_call_function(env, nullptr, js_fun, 1, argv, &callbackResult);
    if (callRet != napi_ok) {
        GET_NAPI_ERROR();
    }
    
    if (callbackResult != nullptr) {
        int resultValue;
        napi_get_value_int32(env, callbackResult, &resultValue);
        LOGE("result: %{public}d", resultValue);
    }

    napi_value ret;
    napi_create_int32(env, 0, &ret);
    return ret;
}

然后,给注册一下native的函数,这样就完成了

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) 
{
    napi_property_descriptor desc[] = {
        {"register"nullptr, Register, nullptrnullptrnullptr, napi_default, nullptr},
        {"callNative"nullptr, CallNative, nullptrnullptrnullptr, napi_default, nullptr},
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    g_env = env;
    return exports;
}
EXTERN_C_END

然后,看一下,实验的效果,发现还是十分不错的。

【鸿蒙(HarmonyOS)开发】native层调用ets层函数

进一步思考

这么做的话,如果每一次,我都需要传入一个函数指针进来,那么将会非常的麻烦,因此呢,要思考一种方式,能够动态的支持多个函数的注册,于是乎,我们需要改造一下我们的函数。

static napi_value Register(napi_env env, napi_callback_info info) {
    napi_value ret;
    napi_create_int32(env, 0, &ret);
    size_t argc = 2;
    napi_value argv[2] = {NULL};
    // 获取到传入的函数名字
    void *data;

    // 获取JavaScript传递的参数
    napi_get_cb_info(env, info, &argc, argv, nullptr, &data);

    // 获取字符串的长度
    size_t str_length;
    napi_get_value_string_utf8(env, argv[0], nullptr0, &str_length);

    // 因为这个长度不包括, 因此分配缓冲区,加1是为了容纳终止字符 ''
    char *buffer = new char[str_length + 1];

    napi_get_value_string_utf8(env, argv[0], buffer, str_length + 1, &str_length);

    LOGD("func name: %{public}s", buffer);

    // 缓存函数
    if (g_method_cache.find(buffer) == g_method_cache.end()) {
        return ret;
    }

    napi_get_cb_info(env, info, &argc, argv, NULLNULL);
    napi_ref ref;
    napi_create_reference(env, argv[1], 1, &ref);
    g_method_cache[buffer] = ref;

    return ret;
}

static napi_value CallNative(napi_env env, napi_callback_info info) {
    napi_value ret;
    napi_create_int32(env, 0, &ret);
    const char *buffer = "addOne";
    
    napi_ref js_ref = g_method_cache[buffer];
    napi_value js_fun = NULL;
    napi_get_reference_value(g_env, js_ref, &js_fun);

    size_t argc = 1;
    napi_value argv[1];
    napi_create_int32(env, 666, &argv[0]);

    napi_value callbackResult = nullptr;
    napi_status callRet = napi_call_function(env, nullptr, js_fun, 1, argv, &callbackResult);
    if (callRet != napi_ok) {
        GET_NAPI_ERROR();
    }

    if (callbackResult != nullptr) {
        int resultValue;
        napi_get_value_int32(env, callbackResult, &resultValue);
        LOGE("result: %{public}d", resultValue);
    }

    return ret;
}

然后对应的js文件如下

export const callNative: () => void;

export const register: (name: string, func: Function) => void;

这样,就可以动态传入,并且缓存下来了,效果也是可以的。

总结

到这里,在native层调用js的函数也就讲完了,总的来说,写起来还是比较繁琐的,而且需要自己注册和缓存起来,不过这里还有些问题,所有方法都是同步调用的,我并没有处理异步和多线程的情况,有机会再聊,溜了,溜了。

参考资料

  • https://juejin.cn/post/7302029216142376995
  • https://nodejs.cn/api/n-api.html
  • https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/2_1_u6a21_u5757-0000001446810528-V3


原文始发于微信公众号(Coder小Q):【鸿蒙(HarmonyOS)开发】native层调用ets层函数

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月25日21:21:06
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【鸿蒙(HarmonyOS)开发】native层调用ets层函数http://cn-sec.com/archives/2432059.html

发表评论

匿名网友 填写信息