Nodejs Native AddOn的编写

2024-08-21 18:08
文章标签 编写 nodejs native addon

本文主要是介绍Nodejs Native AddOn的编写,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文地址 http://blog.csdn.net/wangjia184/article/details/18940165


如果要在nodejs中调用动态链接库中的导出方法,或者从动态链接库中回调nodejs中的某个方法,可以采用 node-ffi(https://github.com/rbranson/node-ffi )。不过我试了很久都没有成功,貌似ffi对于回调的支持有问题,无法正确区分 _stdcall 与 _cdecl。而另一种实现方式就非常简单直接了,通过编写nodejs addon的方式直接实现。


nodejs中的addon使用C编写,其编译链接的工具链不是常见的makefile autoconf之类,而是从Chromium移植来的node-gyp。所以,如果直接将复杂的C/C++代码在addon中实现,容易产生编译或者链接冲突。比较简单的方式是,addon只作为adapter使用,在addon中通过dlopen/LoadLibrary去操纵动态链接库或者回调js.


http://nodejs.org/api/addons.html 有实现Addon的基本讲解。


AddOn的基本结构

首先新建adapter.cc, 贴图如下代码

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. #include <node.h>  
  2. #include <v8.h>  
  3. using namespace v8;  
  4.   
  5.   
  6. void init(Handle<Object> exports) {  
  7.    
  8. }  
  9. NODE_MODULE(mq, init)  

这是一个“空”的addon,啥事都没干。 NODE_MODULE 宏的第一个参数是该模块的名称;第二个参数是初始化函数init,此函数在addon加载后调用。

然后在adapter.cc同目录中新建文件building.gyp,它的内容是JSON格式

[javascript]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. {  
  2.   "targets": [  
  3.     {  
  4.       "target_name""mq",  
  5.       "sources": [ "adapter.cc" ]  
  6.     }  
  7.   ]  
  8. }  
需要注意的是, target_name 必须和NODE_MODULE的第一个参数相同。

然后就可以在此目录下,使用gyp编译了

[plain]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. node-gyp configure  
  2. node-gyp rebuild  

编译的结果在build/release目录下,文件的扩展名是 *.node, 而文件名就是之前指定的模块名。

将此*.node文件拷贝到nodejs工程中的node_modules文件夹下,就可以进行加载了。

[javascript]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. var MQ = require('mq.node');  
  2. console.log(MQ);  


Addon中注册方法供NodeJS调用

在Addon中被NodeJS调用的函数原型必须是 Handle<Value> method(const Arguments& args), 在模块初始化的时候注册此方法。如:

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. Handle<Value> XXXXXX(const Arguments& args) {  
  2.     HandleScope scope;  
  3.   
  4.     return scope.Close(Undefined());  
  5. }  
  6.   
  7.   
  8. void init(Handle<Object> exports) {  
  9.   exports->Set(String::NewSymbol("XXXXXX"),  
  10.       FunctionTemplate::New(XXXXXX<span style="font-family: Arial, Helvetica, sans-serif;">)->GetFunction());</span>  
  11. }  
在nodejs中即可调用此方法
[javascript]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. var MQ = require('mq.node');  
  2. MQ.XXXXXX( 2, false'Text');  

从javascript这样的弱类型语言向C强类型语言传递参数,在输入时需要做好类型检查与类型转换。


Addon中回调NodeJS方法

首先在NodeJS中将需要被回调的函数地址通过参数传入。

[javascript]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. MQ.setLogCallback(function (level, message) {  
  2.     console.log('[' + level + '] : ' + message)  
  3. });  

在Addon中,将传递进来的回调函数进行保存

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. static Persistent<Function> s_logCallback;  
  2. Handle<Value> setLogCallback(const Arguments& args) {  
  3.     HandleScope scope;  
  4.     if (args.Length() < 1 || !args[0]->IsFunction() ) {  
  5.         return ThrowException(Exception::TypeError(String::New("Invalid parameter.")));  
  6.     }  
  7.   
  8.     s_logCallback.Dispose();  
  9.     s_logCallback = Persistent<Function>::New(Local<Function>::Cast(args[0]));  
  10.   
  11.     return scope.Close(Undefined());  
  12. }  

在Addon中,当需要回调此函数的时候,直接调用即可。如

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. if( !s_logCallback.IsEmpty() ){  
  2.     const unsigned argc = 2;  
  3.     Local<Value> argv[argc] = {   
  4.         Local<Value>::New(Number::New(1)) ,  
  5.         Local<Value>::New(String::New("Test Message"))   
  6.     };  
  7.     s_logCallback->Call(Context::GetCurrent()->Global(), argc, argv);  
  8. }  


多线程环境下回调 

NodeJS中的V8引擎是以单线程执行的,回调JS方法也必须在V8的主线程中进行,否则会发生未知的后果甚至crash掉整个进程。NodeJS底层的libuv提供了相应的通知机制来实现主线程中的调用。

首先需要定义 uv_async_t 变量

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. static uv_async_t s_async = {0};  
在主线程中初始化此变量,并注册在主线程中此通知触发时回调的方法。此步骤可以在init中执行。

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. uv_async_init( uv_default_loop(), &s_async, onCallback);  

而onCallback方法则在主线程中,通知发生后执行
[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. void onCallback(uv_async_t* handle, int status){  
  2.     if( !s_logCallback.IsEmpty() ){  
  3.         const unsigned argc = 2;  
  4.         Local<Value> argv[argc] = {   
  5.             Local<Value>::New(Number::New(1)) ,  
  6.             Local<Value>::New(String::New("Callback is happening"))   
  7.         };  
  8.         s_logCallback->Call(Context::GetCurrent()->Global(), argc, argv);  
  9.     }  
  10. }  

在任何线程中,都可以通过 uv_async_send 来触发此回调的执行。

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. uv_async_send(&s_async);  

当不再需要回调的时候,可以调用 uv_close 来取消注册此回调方法。

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. uv_close( &s_async, NULL);  

这里特别需要注意的是uv_async_send触发回调的次数并不是一一对应的。它只能保证最少一次的触发。可能会出现这样一种情况,连续调用了3次uv_async_send方法,但回调只被触发了一次(调用第1、2、3次的时候,NodeJS的主循环可能忙于其它处理而直到检测到此通知时,3次调用都已经发生了,而此时只会进行一次回调)。针对这种情况,应该设计相应的队列结构来传递数据到回调中依次处理。

 

异步调用

NodeJS的主线程只负责event loop和V8的执行,如果addon中某个导出方法在调用时会发生阻塞,会严重地影响到NodeJS的整体性能。因此,libuv设计了异步调用的方式--将阻塞类操作放入其它线程中处理,在处理完成后回调。

例如,JS调用如下导出方法

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. AddOn.lookupIpCountry( ip, function(countryCode){  
  2.     // get the country code  
  3.     // ...  
  4. });  

在AddOn中,定义一个结构体在异步调用中传递数据。

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. struct LookupIpCountryBaton {  
  2.     uv_work_t work;  
  3.     Persistent<Function> callback;  
  4.     char ip[IP_LEN];  
  5.     char country_code[COUNTRY_CODE_LEN];  
  6. };  

导出方法首先保存回调函数,并验证和解析传入参数

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. // lookup country by ip  
  2. // 1st argument is ip address  
  3. // 2nd argument is the callback function  
  4. Handle<Value> lookupIpCountry(const Arguments& args) {  
  5.     HandleScope scope;  
  6.     if (args.Length() < 2 ||  
  7.         !args[1]->IsFunction() ||  
  8.         (!args[0]->IsStringObject() && !args[0]->IsString())  ) {  
  9.         return ThrowException(Exception::TypeError(String::New("Invalid parameter.")));  
  10.     }  
  11.   
  12.     String::Utf8Value param1(args[0]->ToString());  
  13.     char * ip = *param1;  
  14.   
  15.   
  16.     LookupIpCountryBaton * baton = new LookupIpCountryBaton();  
  17.     baton->work.data = baton;  
  18.     memset( baton->country_code, 0, COUNTRY_CODE_LEN);  
  19.     memset( baton->ip, 0, IP_LEN);  
  20.     strncpy( baton->ip, ip, IP_LEN);  
  21.     baton->callback = Persistent<Function>::New(Local<Function>::Cast(args[1]));  
  22.       
  23.     uv_queue_work( uv_default_loop(), &baton->work, lookupIpCountryAsync, lookupIpCountryCompleted);  
  24.   
  25.     return Undefined();  
  26. }  
这里最关键的是 uv_queue_work , 它将请求压入队列交由其它线程执行,同时指定在线程中执行的函数( lookupIpCountryAsyc ),亦指定了调用结束后完成的函数( lookupIpCountryCompleted )


lookupIpCountryAsyc函数中,进行阻塞调用。这里要注意,此函数不是在主线程中运行,所以不能访问或者调用任何V8有关的函数或数据。

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. void lookupIpCountryAsync(uv_work_t * work){  
  2.     LookupIpCountryBaton * baton = (LookupIpCountryBaton*)work->data;  
  3.   
  4.     // block thread for 3 seconds  
  5.     sleep(3);  
  6.     // save the result  
  7.     strncpy( baton->country_code, "CN", COUNTRY_CODE_LEN - 1);  
  8. }  

当此函数执行完后, lookupIpCountryCompleted 函数会在主线程中被执行,完成回调和清理工作。

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. void lookupIpCountryCompleted(uv_work_t * work, int){  
  2.     LookupIpCountryBaton * baton = (LookupIpCountryBaton*)work->data;  
  3.   
  4.     const unsigned argc = 1;  
  5.     Local<Value> argv[argc] = {   
  6.         Local<Value>::New(String::New(baton->country_code)) ,  
  7.     };  
  8.     baton->callback->Call(Context::GetCurrent()->Global(), argc, argv);  
  9.   
  10.     baton->callback.Dispose();  
  11.     delete baton;  
  12. }  


本文地址 http://blog.csdn.net/wangjia184/article/details/18940165

这篇关于Nodejs Native AddOn的编写的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1093899

相关文章

利用Python编写一个简单的聊天机器人

《利用Python编写一个简单的聊天机器人》这篇文章主要为大家详细介绍了如何利用Python编写一个简单的聊天机器人,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 使用 python 编写一个简单的聊天机器人可以从最基础的逻辑开始,然后逐步加入更复杂的功能。这里我们将先实现一个简单的

使用PyQt5编写一个简单的取色器

《使用PyQt5编写一个简单的取色器》:本文主要介绍PyQt5搭建的一个取色器,一共写了两款应用,一款使用快捷键捕获鼠标附近图像的RGB和16进制颜色编码,一款跟随鼠标刷新图像的RGB和16... 目录取色器1取色器2PyQt5搭建的一个取色器,一共写了两款应用,一款使用快捷键捕获鼠标附近图像的RGB和16

使用Java编写一个文件批量重命名工具

《使用Java编写一个文件批量重命名工具》这篇文章主要为大家详细介绍了如何使用Java编写一个文件批量重命名工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录背景处理1. 文件夹检查与遍历2. 批量重命名3. 输出配置代码片段完整代码背景在开发移动应用时,UI设计通常会提供不

安装nodejs环境

本文介绍了如何通过nvm(NodeVersionManager)安装和管理Node.js及npm的不同版本,包括下载安装脚本、检查版本并安装特定版本的方法。 1、安装nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash 2、查看nvm版本 nvm --version 3、安装

native和static native区别

本文基于Hello JNI  如有疑惑,请看之前几篇文章。 native 与 static native java中 public native String helloJni();public native static String helloJniStatic();1212 JNI中 JNIEXPORT jstring JNICALL Java_com_test_g

如何编写Linux PCIe设备驱动器 之二

如何编写Linux PCIe设备驱动器 之二 功能(capability)集功能(capability)APIs通过pci_bus_read_config完成功能存取功能APIs参数pos常量值PCI功能结构 PCI功能IDMSI功能电源功率管理功能 功能(capability)集 功能(capability)APIs int pcie_capability_read_wo

Wondows dos下怎么编写bat批处理文件

最近搞php,在运行时,以Nginx+php-cgi.exe方式运行Wordpress项目 打开dos,先cd到php-cgi.exe文件当前目录下执行启动命令:php-cgi.exe -b 127.0.0.1:9001再打开一个dos,再cd到nginx.exe文件当前目录下执行启动命令:start nginx 大概过程要经过这些步骤,觉得很麻烦,就学下怎么编写一个bat文件,以双击运行代替

nvm及nodejs安装相关

安装 1.清空文件夹,卸载nvm及nodejs 2.下载安装包 https://github.com/coreybutler/nvm-windows/releases (也下载有) 3.安装nvm 地址写D:/nvm和D:/nodejs 4.安装nodejs nvm ls available //查询版本nvm install 16.20.2 //安装对应版本号nvm use 1

用Python编写倒计时程序:详细教程

目录 引言 环境准备 基本概念 代码实现 步骤一:导入必要的库 步骤二:获取用户输入 步骤三:实现倒计时逻辑 步骤四:整合代码 运行程序 高级功能 扩展功能示例:支持分钟和小时输入 扩展功能示例:图形用户界面 (GUI) 总结 引言 倒计时程序是一个非常常见的小工具,广泛用于各种应用场景中,例如考试时间提醒、烹饪计时器、会议倒计时等。Python 作为一种

Nodejs的Express框架使用总结

初始安装express,你也可以通过编辑器的shell安装依赖,比如vs # 创建并切换到 myapp 目录mkdir myappcd myapp# 初始化 package.json 文件npm init -y# 安装 express 到项目中npm i express 新建一个基础的接口 // 0. 加载 Expressconst express = require('expre