查看: 246|回复: 1

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

[复制链接]
发表于 昨天 12:00 | 显示全部楼层 |阅读模式
在封装 Node.js Addon 接口时,回调函数的处理经常让开发者绕很多弯路。本文通过一个完整的测试程序,演示如何利用 libuv 异步句柄和 Nan 模块在 C++ 扩展中实现 JS 回调函数的异步触发。

## 1. 安装 Nan 模块
首先确保项目中安装了 Nan(Native Abstractions for Node.js):
  1. npm install nan
复制代码
Nan 提供了跨 Node 版本的 V8 API 封装,让 Addon 编写更稳定。

## 2. 配置 binding.gyp
构建文件
  1. binding.gyp
复制代码
内容如下:
  1. {
  2.   'targets': [
  3.     {
  4.       "target_name": "bjcast_project_addon",
  5.       "sources": ["./bjcast_project_addon.cc"],
  6.       "include_dirs": [
  7.         "<!(node -e "require('nan')")",
  8.       ],
  9.     }
  10.   ]
  11. }
复制代码
注意通过
  1. node -e "require('nan')"
复制代码
动态获取 Nan 头文件路径,避免手动设置绝对目录。

## 3. 编写 C++ 扩展代码
C++ 源文件
  1. bjcast_project_addon.cc
复制代码
是核心,它借助 libuv 事件循环实现了从 C++ 层向 JS 层回调数据的机制。

### 3.1 包含头文件与数据结构
  1. #include <iostream>
  2. #include <string>
  3. #include <v8.h>
  4. #include <nan.h>
  5. #include <windows.h>
  6. using namespace v8;
复制代码
定义一个结构体
  1. OnStartSessionData
复制代码
来保存回调函数、待传递数据以及异步句柄:
  1. struct OnStartSessionData {
  2.     Nan::Callback *callback;   // JS 侧传入的回调函数
  3.     int reason;                // 需要回调到 JS 层的数据
  4.     uv_async_t handle;         // 异步句柄,用于唤醒事件循环
  5. };
复制代码
全局声明该结构体实例:
  1. OnStartSessionData *on_start_session_data = new OnStartSessionData();
复制代码

### 3.2 触发异步事件的 C++ 函数
当 C++ 底层发生某个事件(例如会话启动完成)时,需要通知 JS。这里用
  1. OnBJCastStartSession
复制代码
模拟:
  1. static void OnBJCastStartSession(const int reason) {
  2.     on_start_session_data->reason = reason;
  3.     on_start_session_data->handle.data = (void *)on_start_session_data;
  4.     uv_async_send(&on_start_session_data->handle);  // 唤醒事件循环
  5. }
复制代码
  1. uv_async_send
复制代码
会触发 libuv 事件循环中注册的异步句柄,从而在 Node 主线程中执行对应的回调。

### 3.3 异步句柄的处理函数
在事件循环中被调用的函数
  1. OnStartSessionEvent
复制代码
,负责将数据通过 Nan::Callback 传回 JS 层:
  1. void OnStartSessionEvent(uv_async_t *handle) {
  2.     Nan::HandleScope scope;
  3.     OnStartSessionData *data = (OnStartSessionData *)handle->data;
  4.     v8::Local<v8::Value> argv[1] = {Nan::New((int)data->reason)};
  5.     data->callback->Call(1, argv);  // 调用 JS 回调函数
  6. }
复制代码
注意
  1. Nan::HandleScope
复制代码
用于管理 V8 句柄,回调参数通过数组
  1. argv
复制代码
传递。

### 3.4 NAN_METHOD 导出的函数
[JS 层调用] 使用时调用的函数
  1. bjcast_session
复制代码
接收参数:IP、端口、PIN 以及回调函数:
  1. NAN_METHOD(bjcast_session) {
  2.     Local<String> ip_value = Local<String>::Cast(info[0]);
  3.     String::Utf8Value ip(ip_value);
  4.     std::string ip_str = std::string(*ip);
  5.    
  6.     Local<Number> port_value = Local<Number>::Cast(info[1]);
  7.     double port = port_value->NumberValue();
  8.    
  9.     Local<String> pin_value = Local<String>::Cast(info[2]);
  10.     String::Utf8Value pin(pin_value);
  11.     std::string pin_str = std::string(*pin);
  12.    
  13.     Nan::Callback *start_session_callback = new Nan::Callback(info[3].As<Function>());
  14.     on_start_session_data->callback = start_session_callback;
  15.    
  16.     // 将异步句柄加入默认事件循环,并关联处理函数
  17.     uv_async_init(uv_default_loop(), &on_start_session_data->handle, OnStartSessionEvent);
  18.    
  19.     // 实际项目中这里应调用底层库,如:
  20.     // int32_t value = BJCastProjectLib::GetInstance().StartBJCastSession(ip_str.c_str(), (uint16_t)port, pin_str.c_str(), start_session_callback);
  21.     // 本例简化,直接模拟一个返回值
  22.     int value = 0;
  23.     Local<Value> argv[1] = {Nan::New((int)value)};
  24.     info.GetReturnValue().Set(Nan::New((int)value));   // 立即返回 0
  25.    
  26.     OnBJCastStartSession(5);  // 模拟异步触发回调,传递 reason=5
  27. }
复制代码
这段代码演示了三个要点:
- 从 JS 参数中提取字符串和数字。
- 创建 Nan::Callback 对象保存回调函数。
- 调用
  1. uv_async_init
复制代码
将异步句柄与事件循环绑定。
- 立即返回一个值(0),随后通过
  1. OnBJCastStartSession
复制代码
异步调用回调。

### 3.5 模块初始化与导出
  1. NAN_MODULE_INIT(init) {
  2.     Nan::Export(target, "startBJCastSession", bjcast_session);
  3. }
  4. NODE_MODULE(bjcast_project_addon_win, init)
复制代码
使用
  1. Nan::Export
复制代码
  1. bjcast_session
复制代码
导出为 JS 函数
  1. startBJCastSession
复制代码
,模块名为
  1. bjcast_project_addon_win
复制代码


## 4. JS 层调用示例
创建
  1. index.js
复制代码
  1. var BJLib = require('./build/Release/bjcast_project_addon.node');
  2. var serverIp = '192.168.9.155';
  3. var serverPort = 8188;
  4. var pin = '';
  5. var onStartSessionResult = function(result) {
  6.     console.log('result = ', result);
  7. };
  8. var ret_value = BJLib.startBJCastSession(
  9.     serverIp, serverPort, pin, onStartSessionResult
  10. );
  11. console.log(ret_value);
复制代码
运行
  1. node index.js
复制代码
输出:
  1. 0
  2. result = 5
复制代码
第一个 0 是
  1. startBJCastSession
复制代码
的立即返回值,第二个
  1. result = 5
复制代码
是从
  1. OnBJCastStartSession(5)
复制代码
触发的异步回调结果。

## 5. 关键要点总结
- 使用
  1. Nan::Callback
复制代码
在 C++ 层安全持有并调用 JS 回调。
-
  1. uv_async_t
复制代码
是 libuv 异步句柄,通过
  1. uv_async_send
复制代码
唤醒事件循环,确保回调在主线程执行。
- 异步句柄必须初始化(
  1. uv_async_init
复制代码
)后才能使用。
- 实际项目中,将
  1. OnBJCastStartSession
复制代码
放在真正的 C++ 库事件回调中,即可实现底层事件到 JS 层的异步传递。

本文提供的代码可以直接作为 Node.js Addon 回调事件开发的基础模板。
回复

使用道具 举报

发表于 昨天 12:10 | 显示全部楼层

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

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

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-14 03:07 , Processed in 0.034836 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部