在封装 Node.js Addon 接口时,回调函数的处理经常让开发者绕很多弯路。本文通过一个完整的测试程序,演示如何利用 libuv 异步句柄和 Nan 模块在 C++ 扩展中实现 JS 回调函数的异步触发。
## 1. 安装 Nan 模块
首先确保项目中安装了 Nan(Native Abstractions for Node.js):Nan 提供了跨 Node 版本的 V8 API 封装,让 Addon 编写更稳定。
## 2. 配置 binding.gyp
构建文件内容如下:- {
- 'targets': [
- {
- "target_name": "bjcast_project_addon",
- "sources": ["./bjcast_project_addon.cc"],
- "include_dirs": [
- "<!(node -e "require('nan')")",
- ],
- }
- ]
- }
复制代码 注意通过动态获取 Nan 头文件路径,避免手动设置绝对目录。
## 3. 编写 C++ 扩展代码
C++ 源文件是核心,它借助 libuv 事件循环实现了从 C++ 层向 JS 层回调数据的机制。
### 3.1 包含头文件与数据结构- #include <iostream>
- #include <string>
- #include <v8.h>
- #include <nan.h>
- #include <windows.h>
- using namespace v8;
复制代码 定义一个结构体来保存回调函数、待传递数据以及异步句柄:- 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。这里用模拟:- 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); // 唤醒事件循环
- }
复制代码 会触发 libuv 事件循环中注册的异步句柄,从而在 Node 主线程中执行对应的回调。
### 3.3 异步句柄的处理函数
在事件循环中被调用的函数,负责将数据通过 Nan::Callback 传回 JS 层:- void OnStartSessionEvent(uv_async_t *handle) {
- Nan::HandleScope scope;
- OnStartSessionData *data = (OnStartSessionData *)handle->data;
- v8::Local<v8::Value> argv[1] = {Nan::New((int)data->reason)};
- data->callback->Call(1, argv); // 调用 JS 回调函数
- }
复制代码 注意用于管理 V8 句柄,回调参数通过数组传递。
### 3.4 NAN_METHOD 导出的函数
[JS 层调用] 使用时调用的函数接收参数:IP、端口、PIN 以及回调函数:- NAN_METHOD(bjcast_session) {
- Local<String> ip_value = Local<String>::Cast(info[0]);
- String::Utf8Value ip(ip_value);
- std::string ip_str = std::string(*ip);
-
- Local<Number> port_value = Local<Number>::Cast(info[1]);
- double port = port_value->NumberValue();
-
- Local<String> pin_value = Local<String>::Cast(info[2]);
- String::Utf8Value pin(pin_value);
- std::string pin_str = std::string(*pin);
-
- Nan::Callback *start_session_callback = new Nan::Callback(info[3].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[1] = {Nan::New((int)value)};
- info.GetReturnValue().Set(Nan::New((int)value)); // 立即返回 0
-
- OnBJCastStartSession(5); // 模拟异步触发回调,传递 reason=5
- }
复制代码 这段代码演示了三个要点:
- 从 JS 参数中提取字符串和数字。
- 创建 Nan::Callback 对象保存回调函数。
- 调用将异步句柄与事件循环绑定。
- 立即返回一个值(0),随后通过异步调用回调。
### 3.5 模块初始化与导出- NAN_MODULE_INIT(init) {
- Nan::Export(target, "startBJCastSession", bjcast_session);
- }
- NODE_MODULE(bjcast_project_addon_win, init)
复制代码 使用将导出为 JS 函数,模块名为。
## 4. 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);
复制代码 运行输出:第一个 0 是的立即返回值,第二个是从触发的异步回调结果。
## 5. 关键要点总结
- 使用在 C++ 层安全持有并调用 JS 回调。
-是 libuv 异步句柄,通过唤醒事件循环,确保回调在主线程执行。
- 异步句柄必须初始化()后才能使用。
- 实际项目中,将放在真正的 C++ 库事件回调中,即可实现底层事件到 JS 层的异步传递。
本文提供的代码可以直接作为 Node.js Addon 回调事件开发的基础模板。 |