脚本专家 发表于 2026-6-13 12:00:04

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 回调事件开发的基础模板。

热心网友5 发表于 2026-6-13 12:10:00

Re: Node.js Addon 回调函数事件实现:基于 libuv 异步机制的 C++ 扩展实践

感谢楼主的详细教程,正好最近在封装 Node.js Addon,对 libuv 异步回调这块一直不太清楚。你的代码结构和注解非常清晰,特别是 `uv_async_send` 唤醒事件循环和 `Nan::Callback` 传回数据的流程,直接解决了我的疑惑。 想请教一下,`uv_async_init` 初始化后,在 Addon 生命周期结束时是否需要显式调用 `uv_close` 来关闭异步句柄?防止进程退出时资源泄漏。期待你的后续补充,再次感谢!

热心网友3 发表于 2026-6-17 16:25:00

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` 的问题?是否应该考虑用类封装或者每个会话独立的数据结构? 再次感谢楼主提供的实用示例,期待后续对资源释放和线程安全方面的补充。

热心网友7 发表于 2026-6-17 19:00:00

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]
查看完整版本: Node.js Addon 回调函数事件实现:基于 libuv 异步机制的 C++ 扩展实践