百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Node.js与C++:napi调用JavaScript回调函数

myzbx 2025-06-24 15:27 5 浏览

前情提要 : 前段时间想要为一个基于 Electron 框架开发的应用编写一个原生模块,以便它能够调用 Win32 API。在这个过程中,需要处理涉及到 JavaScript 函数回调的问题,由于接触 Node.js 的时间比较少,还无法理解其中的精髓,因此被这个问题困扰了很久。

介绍

NAPI(Node-API)是用于构建 Node.js 原生插件的一种API,旨在提供一个稳定的、跨 Node.js 版本的应用程序二进制接口(ABI),使得开发人员可以编写一次插件代码,并且能够在不同版本的 Node.js 上运行而无需重新编译。它的目标是提供一致的接口,使得编写 Node.js 扩展更加容易。

node-addon-api 是一个仅包含头文件的 C++ 包装类,它简化了基于 C 的 Node-API 的使用。

(如何开发node模块网上已经有很多教程啦,这里就不多赘述啦orz)

尝试1:Function 类

    
    // 检查参数是否正确
    if(!info[0].IsFunction()){
        // ……
    }

    // 获取传入的 JavaScript 回调函数
    Napi::Function callback = info[0].As<Napi::Function>();

    // ……

    // 在某些条件下调用 JavaScript 回调函数
    callback[i].Call({});
    

开始的时候直接尝试使用Function类,Napi::Function 类提供了一些方法,可用于调用在 JavaScript 中创建并传递给插件的函数。

但是在我的代码中,获取传入回调函数的位置和调用回调函数的位置不在同一个函数中,并且这样写在调用的时候,程序闪退,错误信息为

C++

复制代码

Fatal error in V8: v8::HandleScope::CreateHandle() Cannot create a handle without a HandleScope

看了一些文档也没有搞明白该怎么解决,尝试1大失败(流泪猫猫头.jpg)

尝试2:线程安全函数

由于回调的问题一直解决不了,之后想做的很多东西就都被搁置了。在一天摸鱼的时候突然看到:异步线程安全函数调用 和 调用线程安全的函数。

1. napi_create_threadsafe_function

    NAPI_EXTERN napi_status
    napi_create_threadsafe_function(napi_env env,
                                    napi_value func,
                                    napi_value async_resource,
                                    napi_value async_resource_name,
                                    size_t max_queue_size,
                                    size_t initial_thread_count,
                                    void* thread_finalize_data,
                                    napi_finalize thread_finalize_cb,
                                    void* context,
                                    napi_threadsafe_function_call_js call_js_cb,
                                    napi_threadsafe_function* result);
  • [in] env:调用 API 的环境。
  • [in] func:调用的 JavaScript 函数。
  • [in] async_resource:与将传递给可能的 async_hooks init 钩子 的异步工作关联的可选对象。
  • [in] async_resource_name:一个 JavaScript 字符串,用于为 async_hooks API 公开的诊断信息提供的资源类型提供标识符。
  • [in] max_queue_size:队列的最大大小。0 为无限制。
  • [in] initial_thread_count:初始获取数,即初始线程数,包括将使用此函数的主线程。
  • [in] thread_finalize_data:要传递给 thread_finalize_cb 的可选数据。
  • [in] thread_finalize_cb:napi_threadsafe_function 被销毁时调用的可选函数。
  • [in] context:附加到生成的 napi_threadsafe_function 的可选数据。
  • [in] call_js_cb:可选回调调用 JavaScript 函数以响应不同线程上的调用。详情查看napi_threadsafe_function_call_js
  • [out] result:异步线程安全的 JavaScript 函数。

(内心OS:顾名思义,创建一个线程安全的函数,虽然目前对这些参数还不是很清楚,但是现在可以先顺着
napi_threadsafe_function_call_js 这条线索走下去~)

2. napi_threadsafe_function_call_js

与异步线程安全函数调用一起使用的函数指针。通过调用 napi_call_function,然后调用 JS 函数。

具体函数必须满足:

    typedef void (*napi_threadsafe_function_call_js)(napi_env env,
                                                    // 用于 API 调用的环境,或者 NULL。
                                                    napi_value js_callback,
                                                    // 要调用的 JavaScript 函数,或者 NULL。
                                                    void* context,
                                                    // 用于创建线程安全函数的可选数据。
                                                    void* data);
                                                    // 由辅助线程创建的数据。回调负责将此原生数据转换为 JavaScript 值(使用 Node-API 函数),这些值可以在调用 js_callback 时作为参数传递。

(内心OS:感觉这个函数像是具体JS回调函数的执行者,那怎么调用回调函数捏)

3.napi_call_threadsafe_function

可以在任何调用JS回调函数的地方调用这个函数。

    NAPI_EXTERN napi_status
    napi_call_threadsafe_function(napi_threadsafe_function func,
                                // 要调用的异步线程安全 JavaScript 函数。
                                void* data,
                                // 通过创建线程安全 JavaScript 函数期间提供的回调 call_js_cb 发送到 JavaScript 的数据。
                                napi_threadsafe_function_call_mode is_blocking);
                                // 标志,其值可以是 napi_tsfn_blocking 或 napi_tsfn_nonblocking。

4. 照猫画虎

大概看了一下文档,就开始照猫画虎写自己的代码啦!!

  • 创建JS回调函数。(在我的代码中,我的CallbackInfo是一组Object。)
    // ……
    // 判断参数类型
    if(obj.Get("click").IsFunction()){
                    //创建回调JS函数
                    //callback_map_[i] = obj.Get("click").As<Function>();(这里是原本使用 Function 类的写法)
                    napi_value js_callback;
                    napi_status status;
                    napi_value srcCbName;

                    js_callback = obj.Get("click");
                    status = napi_create_threadsafe_function(env,
                        js_callback, // napi_value 回调函数
                        nullptr,
                        srcCbName,
                        0,
                        1,
                        nullptr,
                        nullptr,
                        nullptr,
                        ThreadSafe_Func_CallJs, 
                        // 实际的执行单元, 在内部调用napi_call_function
                        &callback_map_[i]);  
                        // 全局threadsafe函数,存在 using CallbackMap = std::map<int, napi_threadsafe_function> 中

                    if (status == napi_ok) {
                        // 成功
                    }
                }
  • 创建napi_threadsafe_function_call_js
void ThreadSafe_Func_CallJs(napi_env env, napi_value js_callback, void* context, void* data) {
	napi_status status;
	napi_value undefined;
	status = napi_get_undefined(env, &undefined);
	status = napi_call_function(env, undefined, js_callback, 0, nullptr, nullptr); 
	if (status != napi_ok) {
		// ……
	}
}
  • 调用JS回调函数
    napi_call_threadsafe_function(callback_map_[wmId], nullptr, napi_tsfn_nonblocking);
  • 在JS中调用原生模块
    TaskbarThumbnailManager.setThumbarButtons([
      {
        icon: "Cursor_Laser_Pointer.ico",
        tooltip: "上一首",
        click() {
          console.log('shangyishou\n');
        }
      }
      // ……
    ]);


总结

回调成功的时候简直忍不住在房间里旋转跳跃不停歇hhh

但是这次成功是有点运气在的,因为还是有很多没有解决的问题orz

仅作个人纪录,感谢所有写过相关文章的其他作者!!

相关推荐

陈冠希飞机争执事件:维权还是失态?

陈冠希最近又上热搜了!这次不是因为潮牌,而是在飞机上和机组人员“杠”上了。事情是这样的:他在东京飞纽约的航班上,发现机组人员让一名日籍VIP乘客优先下机,当场就炸了,直接质问:“我跟他哪里不一样?钻石...

风向变了,小S被吴宗宪猛爆黑料,至亲好友背刺,s家乱成一锅粥

前言当吴宗宪5月26日直播中甩出"黄子佼犯罪小S知情"的录音时,谁还记得这对师徒曾在《我猜》里默契十足的黄金年代?昔日提携晚辈的综艺天王,如今用三小时连爆12条黑料,把综艺女王钉在道德...

吴宗宪开撕小S,离婚内幕疑曝光,S家起内讧,汪小菲果然没说错

文|东方不败难怪葛思琪说小S大概率是不能复出了。原来一切都是有迹可循的!被吴宗宪猛曝黑料、被至亲好友背刺。失去大S的s家彻底乱成一锅粥。小S还能如以往那般幸运地“化险为夷”吗?01不得不说,作为台湾主...

美国俄亥俄大学性侵案细节曝光,新纪录片揭开体育界被忽视的丑闻

美国俄亥俄州立大学一直是美国校际体育运动的标杆,以至于很少有人将该大学与美国历史上最令人震惊的性虐待丑闻联系起来。近日,由澳大利亚纪录片导演伊娃·奥纳(EvaOrner)执导的《俄亥俄州立大学的幸存...

陈冠希飞机上怒怼空姐,称要让其丢掉工作?原因曝光后大家纷纷支持

【点新闻报道】44岁的陈冠希(Edison)被爆料在一架由东京羽田飞往纽约的航班上,疑不满头等舱的下机安排,与空姐发生口角,甚至放话:“把客诉信拿来,我会让你丢工作!”,引发网上热议。有内地网民在小红...

陈冠希机上风波再起!一场由“优先权”引发的对峙

一句“我会让你丢工作”的激烈争执录音,将陈冠希再次推向风口浪尖。飞机引擎的轰鸣尚未完全停歇,纽约机场的廊桥尚未对接,头等舱内的空气却已骤然凝固。44岁的陈冠希,这位早已褪去偶像光环却始终身处舆论漩涡...

传祺M8 vs 别克GL8,谁才是MPV终极选择?

广汽传祺M8与别克GL8一直都是很多人在选择MPV时纠结的对象,尤其是对于选择“困难症”的朋友来说,更是如此。今天我们将广汽传祺M8大师超混版和别克GL8ES陆尊进行对比,看看究竟怎么选!不是合资买...

开源鸿蒙OpenHarmony 6.0 Beta1发布

IT之家6月19日消息,开源鸿蒙OpenHarmony6.0Beta1(APILevel20)现已发布并上线Gitee。据介绍,OpenHarmony6.0Beta1版本进一...

巴雷特(Barrett)食管(巴雷特食管?)

近年来随着HP根除的增加等因素存在,食管胃结合部腺癌发病率逐年增加,食管胃结合部腺癌主要包括Barrett腺癌、胃贲门腺癌,而Barrett食管(Barrett’sesophagus,BE)为Bar...

儿子对象三天不出门 吵架动手后关系僵持

这几天家里事儿多。儿子交的女朋友搬来同住三天,人跟消失似的。每天中午才起床吃我家做的饭,吃完就喊着出去,问晚上回不回来,答不回来。昨天中午我找她谈儿子动手的事,她也不说话,现在微信电话全拉黑,连饭都不...

偷鸡不成蚀把米!命理师称小S将有大劫,老公许雅钧被爆换继承人

近期有命理师称小S将有大劫,其老公许雅钧也被爆换继承人,具体情况如下:命理师称小S有大劫有台湾省命理师称小S面相不好,将会有一场“大劫”,会影响到她生活的重大事件。还有细心网友翻出2022年某命理师在...

如何设计Agent的记忆系统(agent记忆方法)

最近看了一张画Agent记忆分类的图我觉得分类分的还可以,但是太浅了,于是就着它的逻辑,仔细得写了一下在不同的记忆层,该如何设计和选型先从流程,作用,实力和持续时间的这4个维度来解释一下这几种记忆:1...

深入理解跨域及常见误区揭秘(深入理解跨域及常见误区揭秘论文)

跨域问题是前端与后端协作中不可避免的话题,处理不当将直接影响系统可用性与安全性。本文将系统梳理跨域的概念、原理、常见解决方案,并结合实际开发中易错点进行总结,帮助你全面掌握跨域知识。一、什么是跨域?*...

aardio + Java + JavaScript 混合开发快速入门

aardio最近在AI功能上做了很多细节的改进,建议大家更新。aardio的AI接口里的Gemini2.5pro更新到了刚发布的最新版本(Gemini2.5pro0605),...

一种改进的锂离子电池剩余寿命预测算法

摘要:锂离子电池故障往往会使系统性能下降甚至瘫痪,故障部件剩余寿命的精确估计对整个系统的寿命预测和健康管理至关重要。粒子滤波是一种有效的序列信号处理方法,然而应用于锂离子电池剩余寿命预测准确性并不高...