本文主要备忘为Node.js编写组件的三种实现:纯js实现、v8 API实现(同步&异步)、借助swig框架实现。

关键字:Node.js、C++、v8、swig、异步、回调。

简介

首先介绍使用v8 API跟使用swig框架的不同:

(1)v8 API方式为官方提供的原生方法,功能强大而完善,缺点是需要熟悉v8 API,编写起来比较麻烦,是js强相关的,不容易支持其它脚本语言。

(2)swig为第三方支持,一个强大的组件开发工具,支持为python、lua、js等多种常见脚本语言生成C++组件包装代码,swig使用者只需要编写C++代码和swig配置文件即可开发各种脚本语言的C++组件,不需要了解各种脚本语言的组件开发框架,缺点是不支持javascript的回调,文档和demo代码不完善,使用者不多。

二、纯JS实现Node.js组件

(1)到helloworld目录下执行npm init 初始化package.json,各种选项先不管,默认即可,更多package.json信息参见:https://docs.npmjs.com/files/package.json。
(2)组件的实现index.js,例如:
module.exports.Hello = function(name) {console.log('Hello ' + name);
}

(3)在外层目录执行:npm install ./helloworld,helloworld于是安装到了node_modules目录中。
(4)编写组件使用代码:
var m = require('helloworld');
m.Hello('zhangsan');
//输出: Hello zhangsan

完整demo。

三、 使用v8 API实现JS组件——同步模式

(1)编写binding.gyp, eg:

{"targets": [{"target_name": "hello","sources": [ "hello.cpp" ]}]
}

关于binding.gyp的更多信息参见:https://github.com/nodejs/node-gyp

(2)编写组件的实现hello.cpp,eg:

#include <node.h>namespace cpphello {using v8::FunctionCallbackInfo;using v8::Isolate;using v8::Local;using v8::Object;using v8::String;using v8::Value;void Foo(const FunctionCallbackInfo<Value>& args) {Isolate* isolate = args.GetIsolate();args.GetReturnValue().Set(String::NewFromUtf8(isolate, "Hello World"));}void Init(Local<Object> exports) {NODE_SET_METHOD(exports, "foo", Foo);}NODE_MODULE(cpphello, Init)
}

(3)编译组件

node-gyp configure
node-gyp build

./build/Release/目录下会生成hello.node模块。

(4)编写测试js代码

const m = require('./build/Release/hello')
console.log(m.foo());  //输出 Hello World

(5)增加package.json 用于安装 eg:

{                                                                                                                                                                                                                 "name": "hello","version": "1.0.0","description": "", "main": "index.js","scripts": {"test": "node test.js"},  "author": "", "license": "ISC"
}

(5)安装组件到node_modules

进入到组件目录的上级目录,执行:npm install ./helloc //注:helloc为组件目录
会在当前目录下的node_modules目录下安装hello模块,测试代码这样子写:
var m = require('hello');
console.log(m.foo());   

完整demo。

四、 使用v8 API实现JS组件——异步模式

上面三的demo描述的是同步组件,foo()是一个同步函数,也就是foo()函数的调用者需要等待foo()函数执行完才能往下走,当foo()函数是一个有IO耗时操作的函数时,异步的foo()函数可以减少阻塞等待,提高整体性能。

异步组件的实现只需要关注libuv的uv_queue_work API,组件实现时,除了主体代码hello.cpp和组件使用者代码,其它部分都与上面三的demo一致。

hello.cpp:

/*
* Node.js cpp Addons demo: async call and call back.
* gcc 4.8.2
* author:cswuyg
* Date:2016.02.22
* */
#include <iostream>
#include <node.h>
#include <uv.h>
#include <sstream>
#include <unistd.h>
#include <pthread.h>namespace cpphello {using v8::FunctionCallbackInfo;using v8::Function;using v8::Isolate;using v8::Local;using v8::Object;using v8::Value;using v8::Exception;using v8::Persistent;using v8::HandleScope;using v8::Integer;using v8::String;// async taskstruct MyTask{uv_work_t work;int a{0};int b{0};int output{0};unsigned long long work_tid{0};unsigned long long main_tid{0};Persistent<Function> callback;};// async functionvoid query_async(uv_work_t* work) {MyTask* task = (MyTask*)work->data;task->output = task->a + task->b;task->work_tid = pthread_self();usleep(1000 * 1000 * 1); // 1 second
    }// async complete callbackvoid query_finish(uv_work_t* work, int status) {Isolate* isolate = Isolate::GetCurrent();HandleScope handle_scope(isolate);MyTask* task = (MyTask*)work->data;const unsigned int argc = 3;std::stringstream stream;stream << task->main_tid;std::string main_tid_s{stream.str()};stream.str("");stream << task->work_tid;std::string work_tid_s{stream.str()};Local<Value> argv[argc] = {Integer::New(isolate, task->output), String::NewFromUtf8(isolate, main_tid_s.c_str()),String::NewFromUtf8(isolate, work_tid_s.c_str())};Local<Function>::New(isolate, task->callback)->Call(isolate->GetCurrentContext()->Global(), argc, argv);task->callback.Reset();delete task;}// async mainvoid async_foo(const FunctionCallbackInfo<Value>& args) {Isolate* isolate = args.GetIsolate();HandleScope handle_scope(isolate);if (args.Length() != 3) {isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "arguments num : 3")));return;} if (!args[0]->IsNumber() || !args[1]->IsNumber() || !args[2]->IsFunction()) {isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "arguments error")));return;}MyTask* my_task = new MyTask;my_task->a = args[0]->ToInteger()->Value();my_task->b = args[1]->ToInteger()->Value();my_task->callback.Reset(isolate, Local<Function>::Cast(args[2]));my_task->work.data = my_task;my_task->main_tid = pthread_self();uv_loop_t *loop = uv_default_loop();uv_queue_work(loop, &my_task->work, query_async, query_finish); }void Init(Local<Object> exports) {NODE_SET_METHOD(exports, "foo", async_foo);}NODE_MODULE(cpphello, Init)
}

异步的思路很简单,实现一个工作函数、一个完成函数、一个承载数据跨线程传输的结构体,调用uv_queue_work即可。难点是对v8 数据结构、API的熟悉。

test.js

// test helloUV module
'use strict';
const m = require('helloUV')m.foo(1, 2, (a, b, c)=>{console.log('finish job:' + a);console.log('main thread:' + b);console.log('work thread:' + c);
});
/*
output:
finish job:3
main thread:139660941432640
work thread:139660876334848
*/

完整demo。

五、swig-javascript 实现Node.js组件

利用swig框架编写Node.js组件

(1)编写好组件的实现:*.h和*.cpp 

eg:

namespace a {class A{public:int add(int a, int y);};int add(int x, int y);
}

(2)编写*.i,用于生成swig的包装cpp文件
eg:
/* File : IExport.i */
%module my_mod 
%include "typemaps.i"
%include "std_string.i"
%include "std_vector.i"
%{
#include "export.h"
%}
%apply int *OUTPUT { int *result, int* xx};
%apply std::string *OUTPUT { std::string* result, std::string* yy };
%apply std::string &OUTPUT { std::string& result };                                                                                                                                                               
%include "export.h"
namespace std {
%template(vectori) vector<int>;
%template(vectorstr) vector<std::string>;
};

上面的%apply表示代码中的 int* result、int* xx、std::string* result、std::string* yy、std::string& result是输出描述,这是typemap,是一种替换。
C++导出函数返回值一般定义为void,函数参数中的指针参数,如果是返回值的(通过*.i文件中的OUTPUT指定),swig都会把他们处理为JS函数的返回值,如果有多个指针,则JS函数的返回值是list。
%template(vectori) vector<int> 则表示为JS定义了一个类型vectori,这一般是C++函数用到vector<int> 作为参数或者返回值,在编写js代码时,需要用到它。
swig支持的更多的stl类型参见:https://github.com/swig/swig/tree/master/Lib/javascript/v8

(3)编写binding.gyp,用于使用node-gyp编译
(4)生成warpper cpp文件 生成时注意v8版本信息,eg:swig -javascript -node -c++ -DV8_VERSION=0x040599 example.i
(5)编译&测试
难点在于stl类型、自定义类型的使用,这方面官方文档太少。
swig - javascript对std::vector、std::string、的封装使用参见:我的练习,主要关注*.i文件的实现

六、其它

在使用v8 API实现Node.js组件时,可以发现跟实现Lua组件的相似之处,Lua有状态机,Node有Isolate。

Node实现对象导出时,需要实现一个构造函数,并为它增加“成员函数”,最后把构造函数导出为类名。Lua实现对象导出时,也需要实现一个创建对象的工厂函数,也需要把“成员函数”们加到table中。最后把工厂函数导出。

Node的js脚本有new关键字,Lua没有,所以Lua对外只提供对象工厂用于创建对象,而Node可以提供对象工厂或者类封装。
附:Lua知识备忘录。

本文所在:http://www.cnblogs.com/cswuyg/p/5215161.html

参考资料:

1、v8 API参考文档:https://v8docs.nodesource.com/node-5.0/index.html

2、swig-javascript文档:http://www.swig.org/Doc3.0/Javascript.html

3、C++开发Node.js组件:https://nodejs.org/dist/latest-v4.x/docs/api/addons.html#addons_addons

4、swig-javascript demo:https://github.com/swig/swig/tree/master/Examples/javascript/simple

5、C++开发Node.js组件 demo:https://github.com/nodejs/node-addon-examples

转载于:https://www.cnblogs.com/cswuyg/p/5215161.html

为Node.js编写组件的几种方式相关推荐

  1. vue3编写组件的几种方式

    一.选项式写法 1.在 vue2.x 项目中使用的写法就是选项API的 写法(说明:类似于与vue2中的data里面写的是定义的数据,methods里面写的是处理数据的方法,每一个选项都只负责自己的部 ...

  2. node.js编写网页_为Node.js编写可扩展架构

    node.js编写网页 by Zafar Saleem 通过Zafar Saleem 为Node.js编写可扩展架构 (Writing Scalable Architecture For Nodejs ...

  3. vue.js 动态加载 html,Vue加载组件、动态加载组件的几种方式

    什么是组件: 组件是Vue.js最强大的功能之一.组件可以扩展HTML元素,封装可重用的代码.在较高层面上,组件是自定义的元素,Vue.js的编译器为它添加特殊功能.在有些情况下,组件也可以是原生HT ...

  4. 推荐一款Node.js编写的HTTP代理服务器Zan Proxy

    Zan Proxy是有赞团队开发的一个用Node.js编写的HTTP代理服务器,可用于修改请求地址和模拟响应数据.它同时也是一个自定义DNS解析和请求监控的工具.该代理服务器有人性化的界面,简单易用. ...

  5. Vue.js 父子组件通信的十种方式;告诉世界前端也能做 AI;你可能不知道的14个JavaScript调试技巧...

    记得点击文章末尾的"阅读原文"查看哟~ 下面先一起看下本周的摘要吧~ 想了解老用户如何参与阿里云双十一1折拼团特惠主机的,可以看第二条推送,文中提供了两种方法~,一起看看本周有哪些 ...

  6. Node.js流,这样的打开方式对不对!

    Node.js流,这样的打开方式对不对! 俗话说的好:"人往高处走,水往低处流":古语有云:"落花有意,流水无情".(吃瓜群众:what?你特么这是要弄啥哩!二 ...

  7. vue 实例化几种方式_vue注册组件的几种方式总结

    vue注册组件的几种方式总结 1.全局注册(这种方式注册组件必须在vue实例化之前声明) Vue.component('tag-name',{}) 2.局部注册 var Child = { templ ...

  8. java jframe添加面板_JFrame添加组件的两种方式

    对JFrame添加组件有两种方式:1) 用getContentPane()方法获得JFrame的内容面板,再对其加入组件:frame.getContentPane().add(childCompont ...

  9. 深入解析React创建组件的三种方式

    eact创建组件的三种方式: 1.函数式无状态组件 2.es5方式React.createClass组件 3.es6方式extends React.Component 三种创建方式的异同 1.函数式无 ...

  10. [译]使用Webpack提高Vue.js应用程序的4种方式

    [译]使用Webpack提高Vue.js应用程序的4种方式 原文地址 ​ Webpack是开发Vue.js单页应用程序的重要工具.通过管理复杂的构建步骤,您可以更轻松地开发工作流程,并优化应用程序的大 ...

最新文章

  1. 打造无所不及的智能:徐直军发布华为AI战略及全栈全场景方案
  2. 【STM32】FreeRTOS 列表和列表项
  3. C++library Sort库排序的实现算法(附完整源码)
  4. 希尔排序+移位法(吊打交换法)
  5. 怎么重置blockinput的锁_OPPOA9锁屏密码忘了怎么办? OPPO忘记锁屏密码的解决办法...
  6. python免费领取视频-quot;免费领取Python资源”
  7. eclipse中的TODO和FIXME
  8. java web基础 --- URL重定向Filter
  9. 不考虑知识点,考代码段更好
  10. @Autowired与@Resource的差别
  11. NGUI常见功能解释
  12. 《Axure RP 8 实战手册》pdf
  13. excel文件损坏修复绝招_修复数据工具大盘点,让你快速掌握电脑数据恢复的秘密武器...
  14. 微型计算机及接口技术笔记,2010年自考微型计算机及其接口技术笔记串讲
  15. Namecheap共享虚拟主机使用体验
  16. php jpeg windows,jpg和jpeg有什么区别
  17. 使用 paddlehub的人物识别 对游戏人物识别 绘制方框
  18. CC2530 zigbee IAR8.10.1环境搭建
  19. bzoj1190梦幻岛宝珠
  20. tabIndex的用途

热门文章

  1. python2和python3中的map()
  2. LayaAir cacheAs 缓存与 visible 隐藏
  3. mongoDB在centos7上的安装
  4. 009-2010网络最热的 嵌入式学习|ARM|Linux|wince|ucos|经典资料与实例分析
  5. [RMQ] [线段树] POJ 3368 Frequent Values
  6. SQLServer 分组查询相邻两条记录的时间差
  7. cocos2d-x-3.0 window+eclipse Android Project 环境与开发新手教程
  8. 汇编实现: C库常见函数,串操作指令作用
  9. 理解numpy dot函数
  10. 第七十三节,css盒模型