本文地址 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, 贴图如下代码

#include <node.h>
#include <v8.h>
using namespace v8;void init(Handle<Object> exports) {}
NODE_MODULE(mq, init)

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

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

{"targets": [{"target_name": "mq","sources": [ "adapter.cc" ]}]
}

需要注意的是, target_name必须和NODE_MODULE的第一个参数相同。

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

node-gyp configure
node-gyp rebuild

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

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

var MQ = require('mq.node');
console.log(MQ);

Addon中注册方法供NodeJS调用

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

Handle<Value> XXXXXX(const Arguments& args) {HandleScope scope;return scope.Close(Undefined());
}void init(Handle<Object> exports) {exports->Set(String::NewSymbol("XXXXXX"),FunctionTemplate::New(XXXXXX)->GetFunction());
}

在nodejs中即可调用此方法

var MQ = require('mq.node');
MQ.XXXXXX( 2, false, 'Text');

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

Addon中回调NodeJS方法

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

MQ.setLogCallback(function (level, message) {console.log('[' + level + '] : ' + message)
});

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

static Persistent<Function> s_logCallback;
Handle<Value> setLogCallback(const Arguments& args) {HandleScope scope;if (args.Length() < 1 || !args[0]->IsFunction() ) {return ThrowException(Exception::TypeError(String::New("Invalid parameter.")));}s_logCallback.Dispose();s_logCallback = Persistent<Function>::New(Local<Function>::Cast(args[0]));return scope.Close(Undefined());
}

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

if( !s_logCallback.IsEmpty() ){const unsigned argc = 2;Local<Value> argv[argc] = { Local<Value>::New(Number::New(1)) ,Local<Value>::New(String::New("Test Message")) };s_logCallback->Call(Context::GetCurrent()->Global(), argc, argv);
}

多线程环境下回调

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

首先需要定义 uv_async_t 变量

static uv_async_t s_async = {0};

在主线程中初始化此变量,并注册在主线程中此通知触发时回调的方法。此步骤可以在init中执行。

uv_async_init( uv_default_loop(), &s_async, onCallback);

而onCallback方法则在主线程中,通知发生后执行

void onCallback(uv_async_t* handle, int status){if( !s_logCallback.IsEmpty() ){const unsigned argc = 2;Local<Value> argv[argc] = { Local<Value>::New(Number::New(1)) ,Local<Value>::New(String::New("Callback is happening")) };s_logCallback->Call(Context::GetCurrent()->Global(), argc, argv);}
}

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

uv_async_send(&s_async);

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

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调用如下导出方法

AddOn.lookupIpCountry( ip, function(countryCode){// get the country code// ...
});

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

struct LookupIpCountryBaton {uv_work_t work;Persistent<Function> callback;char ip[IP_LEN];char country_code[COUNTRY_CODE_LEN];
};

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

// lookup country by ip
// 1st argument is ip address
// 2nd argument is the callback function
Handle<Value> lookupIpCountry(const Arguments& args) {HandleScope scope;if (args.Length() < 2 ||!args[1]->IsFunction() ||(!args[0]->IsStringObject() && !args[0]->IsString())  ) {return ThrowException(Exception::TypeError(String::New("Invalid parameter.")));}String::Utf8Value param1(args[0]->ToString());char * ip = *param1;LookupIpCountryBaton * baton = new LookupIpCountryBaton();baton->work.data = baton;memset( baton->country_code, 0, COUNTRY_CODE_LEN);memset( baton->ip, 0, IP_LEN);strncpy( baton->ip, ip, IP_LEN);baton->callback = Persistent<Function>::New(Local<Function>::Cast(args[1]));uv_queue_work( uv_default_loop(), &baton->work, lookupIpCountryAsync, lookupIpCountryCompleted);return Undefined();
}

这里最关键的是 uv_queue_work, 它将请求压入队列交由其它线程执行,同时指定在线程中执行的函数( lookupIpCountryAsyc),亦指定了调用结束后完成的函数( lookupIpCountryCompleted)

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

void lookupIpCountryAsync(uv_work_t * work){LookupIpCountryBaton * baton = (LookupIpCountryBaton*)work->data;// block thread for 3 secondssleep(3);// save the resultstrncpy( baton->country_code, "CN", COUNTRY_CODE_LEN - 1);
}

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

void lookupIpCountryCompleted(uv_work_t * work, int){LookupIpCountryBaton * baton = (LookupIpCountryBaton*)work->data;const unsigned argc = 1;Local<Value> argv[argc] = { Local<Value>::New(String::New(baton->country_code)) ,};baton->callback->Call(Context::GetCurrent()->Global(), argc, argv);baton->callback.Dispose();delete baton;
}

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

Nodejs Native AddOn的编写相关推荐

  1. 使用napi node_使用Napi / node-addon-api和Cmake的独立于Node.js版本的C ++ Native Addon

    使用napi node This is a tutorial for c++ Node-addon-api / Napi addon using cmake.Napi makes it indepen ...

  2. 如何开发 Node.js Native Add-on?

    简介: 来一起为 Node.js 的 add-on 生态做贡献吧~ 作者 | 吴成忠(昭朗) 这篇文章是由 Chengzhong Wu (@legendecas),Gabriel Schulhof ( ...

  3. NodeJs C++ addon(插件nan方式)

    Native Abstractions for Node.js (nan) nan方式的好处:使用nan方式,编写的c++插件接口,会自动兼容不同版本的nodejs 因此,你的代码只需要随着 nan版 ...

  4. nodejs使用addon调用c/c++

    简介 本编文章编写与2019年3月,一直在个人笔记中未发布,以下部分技术可能已过时,可参考思路和步骤.本文详细介绍了nodejs依赖node-gyp使用c/c++的使用步骤. 查看详情 文档 安装和配 ...

  5. nodejs c++ addon插件的应用场景

    高精度计算,如浮点数 多线程并行计算 直接操作硬件 在Nodejs里调用已有的C++的包,而Nodejs自身没有对应的包或者效率不如C++,如视频格式转换. 在多语言开发的项目中,需要用开发可被多种语 ...

  6. nodejs addon实现回调函数事件

    封装nodejs的addon接口,在处理回调函数这块走了很多弯路,在此提供一个简单的测试程序,有兴趣的可以研究下. 安装nan模块 npm install nan binding.gyp文件内容如下: ...

  7. nodejs addon binding osg

    绑定过webpage 到osg 窗口,我需要一个回调机制对osg 显示进行后台显示. 具体的做法是osg作为一个状态机, 前台web界面向后台发送命令, 消息 . 后台接收消息,改变状态.我想架在so ...

  8. 使用React Native编写的一款阅读类app ———《轻松一刻》

    作者:阿钟 博客:http://blog.csdn.net/a_zhon 一款纯React Native原生代码编写的app 源码地址:https://github.com/azhon/Time 效果 ...

  9. Electron学习笔记(五) 通过Addon(n-api)实现可扩展接口

    Electron学习笔记(四) Electron使用的API接口 一方面electron给开发者提供了不少API, 另一方面, 也可以使用node.js的API. 但是, 有时候开发者还是想用自己实现 ...

最新文章

  1. QGIS打印布局cheatsheet
  2. Kosaraju 算法检测有向图的强连通性
  3. MATLAB 无约束一维极值问题
  4. 《剑指offer》把字符串转为整数
  5. CL_CRM_WEB_UTILITY
  6. matlab自带图片下载,数字图像处理中Matlab的应用.pdf
  7. 基于JAVA+SpringMVC+Mybatis+MYSQL的家庭理财管理系统
  8. 几行代码理解Python变量访问的LEGB顺序
  9. mysql udf安全_打造全功能MYSQL入侵UDF
  10. blfs(systemd版本)学习笔记-编译安装openssh软件包
  11. 【Oracle】并行等待之PX Deq Credit: need buffer
  12. 前端开发~uni-app ·[项目-仿糗事百科] 学习笔记 ·004【App.vue引入全局公共样式】
  13. 11G新特性 -- 分区表和增量统计信息
  14. Swift---TextView用法
  15. 外国人在中国工作要交社保吗?
  16. 单元测试用例 php,PHP 单元测试(PHPUnit)(2)
  17. 爪哇国新游记之七----使用ArrayList统计水果出现次数
  18. 程序员翻车时的 30 种常见反应!
  19. 你想跨互联网远程调试Android设备吗,推荐一个远程控制手机的免费软件
  20. 股票——指数移动平均线

热门文章

  1. phpStudy下载(安装)-图文详解(windows)
  2. “上者劳智,中者劳人,下者劳力;小富由勤,大富由命,巨富由恶”
  3. QT 远程升级 实现设备升级
  4. 人以群分 (25 分)
  5. Java参数传递(值传递还是引用传递)
  6. linux服务器开发二(系统编程)--线程相关
  7. 麒麟820啥时候出鸿蒙,鸿蒙OS2.0第二期第三期公测机型陆续公布 麒麟980和麒麟820将登场...
  8. UI设计中你不知道的logo设计理念|优漫动游
  9. 壁纸高清动态主题大全
  10. DDR布线规则与过程