Node.js Addon 回调函数事件实现:基于 libuv 异步机制的 C++ 扩展实践
在封装 Node.js Addon 接口时,回调函数的处理经常让开发者绕很多弯路。本文通过一个完整的测试程序,演示如何利用 libuv 异步句柄和 Nan 模块在 C++ 扩展中实现 JS 回调函数的异步触发。## 1. 安装 Nan 模块
首先确保项目中安装了 Nan(Native Abstractions for Node.js):
npm install nan
Nan 提供了跨 Node 版本的 V8 API 封装,让 Addon 编写更稳定。
## 2. 配置 binding.gyp
构建文件
binding.gyp
内容如下:
{
'targets': [
{
"target_name": "bjcast_project_addon",
"sources": ["./bjcast_project_addon.cc"],
"include_dirs": [
"<!(node -e \"require('nan')\")",
],
}
]
}
注意通过
node -e "require('nan')"
动态获取 Nan 头文件路径,避免手动设置绝对目录。
## 3. 编写 C++ 扩展代码
C++ 源文件
bjcast_project_addon.cc
是核心,它借助 libuv 事件循环实现了从 C++ 层向 JS 层回调数据的机制。
### 3.1 包含头文件与数据结构
#include <iostream>
#include <string>
#include <v8.h>
#include <nan.h>
#include <windows.h>
using namespace v8;
定义一个结构体
OnStartSessionData
来保存回调函数、待传递数据以及异步句柄:
struct OnStartSessionData {
Nan::Callback *callback; // JS 侧传入的回调函数
int reason; // 需要回调到 JS 层的数据
uv_async_t handle; // 异步句柄,用于唤醒事件循环
};
全局声明该结构体实例:
OnStartSessionData *on_start_session_data = new OnStartSessionData();
### 3.2 触发异步事件的 C++ 函数
当 C++ 底层发生某个事件(例如会话启动完成)时,需要通知 JS。这里用
OnBJCastStartSession
模拟:
static void OnBJCastStartSession(const int reason) {
on_start_session_data->reason = reason;
on_start_session_data->handle.data = (void *)on_start_session_data;
uv_async_send(&on_start_session_data->handle);// 唤醒事件循环
}
uv_async_send
会触发 libuv 事件循环中注册的异步句柄,从而在 Node 主线程中执行对应的回调。
### 3.3 异步句柄的处理函数
在事件循环中被调用的函数
OnStartSessionEvent
,负责将数据通过 Nan::Callback 传回 JS 层:
void OnStartSessionEvent(uv_async_t *handle) {
Nan::HandleScope scope;
OnStartSessionData *data = (OnStartSessionData *)handle->data;
v8::Local<v8::Value> argv = {Nan::New((int)data->reason)};
data->callback->Call(1, argv);// 调用 JS 回调函数
}
注意
Nan::HandleScope
用于管理 V8 句柄,回调参数通过数组
argv
传递。
### 3.4 NAN_METHOD 导出的函数
使用时调用的函数
bjcast_session
接收参数:IP、端口、PIN 以及回调函数:
NAN_METHOD(bjcast_session) {
Local<String> ip_value = Local<String>::Cast(info);
String::Utf8Value ip(ip_value);
std::string ip_str = std::string(*ip);
Local<Number> port_value = Local<Number>::Cast(info);
double port = port_value->NumberValue();
Local<String> pin_value = Local<String>::Cast(info);
String::Utf8Value pin(pin_value);
std::string pin_str = std::string(*pin);
Nan::Callback *start_session_callback = new Nan::Callback(info.As<Function>());
on_start_session_data->callback = start_session_callback;
// 将异步句柄加入默认事件循环,并关联处理函数
uv_async_init(uv_default_loop(), &on_start_session_data->handle, OnStartSessionEvent);
// 实际项目中这里应调用底层库,如:
// int32_t value = BJCastProjectLib::GetInstance().StartBJCastSession(ip_str.c_str(), (uint16_t)port, pin_str.c_str(), start_session_callback);
// 本例简化,直接模拟一个返回值
int value = 0;
Local<Value> argv = {Nan::New((int)value)};
info.GetReturnValue().Set(Nan::New((int)value)); // 立即返回 0
OnBJCastStartSession(5);// 模拟异步触发回调,传递 reason=5
}
这段代码演示了三个要点:
- 从 JS 参数中提取字符串和数字。
- 创建 Nan::Callback 对象保存回调函数。
- 调用
uv_async_init
将异步句柄与事件循环绑定。
- 立即返回一个值(0),随后通过
OnBJCastStartSession
异步调用回调。
### 3.5 模块初始化与导出
NAN_MODULE_INIT(init) {
Nan::Export(target, "startBJCastSession", bjcast_session);
}
NODE_MODULE(bjcast_project_addon_win, init)
使用
Nan::Export
将
bjcast_session
导出为 JS 函数
startBJCastSession
,模块名为
bjcast_project_addon_win
。
## 4. JS 层调用示例
创建
index.js
:
var BJLib = require('./build/Release/bjcast_project_addon.node');
var serverIp = '192.168.9.155';
var serverPort = 8188;
var pin = '';
var onStartSessionResult = function(result) {
console.log('result = ', result);
};
var ret_value = BJLib.startBJCastSession(
serverIp, serverPort, pin, onStartSessionResult
);
console.log(ret_value);
运行
node index.js
输出:
0
result = 5
第一个 0 是
startBJCastSession
的立即返回值,第二个
result = 5
是从
OnBJCastStartSession(5)
触发的异步回调结果。
## 5. 关键要点总结
- 使用
Nan::Callback
在 C++ 层安全持有并调用 JS 回调。
-
uv_async_t
是 libuv 异步句柄,通过
uv_async_send
唤醒事件循环,确保回调在主线程执行。
- 异步句柄必须初始化(
uv_async_init
)后才能使用。
- 实际项目中,将
OnBJCastStartSession
放在真正的 C++ 库事件回调中,即可实现底层事件到 JS 层的异步传递。
本文提供的代码可以直接作为 Node.js Addon 回调事件开发的基础模板。
Re: Node.js Addon 回调函数事件实现:基于 libuv 异步机制的 C++ 扩展实践
感谢楼主的详细教程,正好最近在封装 Node.js Addon,对 libuv 异步回调这块一直不太清楚。你的代码结构和注解非常清晰,特别是 `uv_async_send` 唤醒事件循环和 `Nan::Callback` 传回数据的流程,直接解决了我的疑惑。 想请教一下,`uv_async_init` 初始化后,在 Addon 生命周期结束时是否需要显式调用 `uv_close` 来关闭异步句柄?防止进程退出时资源泄漏。期待你的后续补充,再次感谢!Re: Node.js Addon 回调函数事件实现:基于 libuv 异步机制的 C++ 扩展实践
感谢楼主的分享,这个例子对理解 libuv 在 Node.js Addon 中的异步回调机制很有帮助。代码结构清晰,从数据结构定义到异步句柄的初始化和触发都讲得很明白。 有一点想请教:在 `OnStartSessionEvent` 里直接调用了 `callback->Call(1, argv)`,如果 JS 回调中执行了比较耗时的操作,会不会阻塞事件循环?有没有考虑过用 `uv_queue_work` 把实际工作放到线程池来处理,只让回调部分在主线程执行?另外,全局使用 `OnStartSessionData` 单例结构,如果多次调用 `bjcast_session` 会不会出现数据覆盖或重复初始化 `uv_async_t` 的问题?是否应该考虑用类封装或者每个会话独立的数据结构? 再次感谢楼主提供的实用示例,期待后续对资源释放和线程安全方面的补充。Re: Node.js Addon 回调函数事件实现:基于 libuv 异步机制的 C++ 扩展实践
楼主分享的这个 Node.js Addon 回调实现非常实用,尤其是利用 libuv 的 `uv_async_send` 来桥接 C++ 事件到 JS 回调,解决了扩展开发中常见的异步通知痛点。结构体 `OnStartSessionData` 的设计清晰,把回调、数据和异步句柄打包在一起,很直观。 有一点想请教:您在 `NAN_METHOD(bjcast_session)` 里直接调用了 `uv_async_init`,如果同一个 Addon 多次调用这个函数,会不会造成重复初始化同一个句柄?还是说每次调用都会覆盖之前的 `on_start_session_data`,这样会不会有内存泄漏或回调被覆盖的问题?期待您的进一步说明。
页:
[1]