Node.js编写组件的三种实现方式


Posted in Javascript onFebruary 25, 2016

首先介绍使用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,各种选项先不管,默认即可。

(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

二、 使用v8 API实现JS组件——同步模式
 (1)编写binding.gyp, eg:

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

(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());

三、 使用v8 API实现JS组件——异步模式
上面描述的是同步组件,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 task
  struct 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 function
  void 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 callback
  void 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 main
  void 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
*/

四、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++函数参数中的指针参数,如果是返回值的(通过*.i文件中的OUTPUT指定),swig都会把他们处理为JS函数的返回值,如果有多个指针,则JS函数的返回值是list。
%template(vectori) vector<int> 则表示为JS定义了一个类型vectori,这一般是C++函数用到vector<int> 作为参数或者返回值,在编写js代码时,需要用到它。
(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可以提供对象工厂或者类封装。

以上就是本文的全部内容,希望对大家的学习有所帮助。

Javascript 相关文章推荐
Javascript 布尔型分析
Dec 22 Javascript
潜说js对象和数组
May 25 Javascript
javascript验证身份证完全方法具体实现
Nov 18 Javascript
JavaScript中的变量作用域介绍
Dec 31 Javascript
JavaScript学习笔记之ES6数组方法
Mar 25 Javascript
AngularJS 指令详细介绍
Jul 27 Javascript
JS数组搜索之折半搜索实现方法分析
Mar 27 Javascript
javascript实现日期三级联动下拉框选择菜单
Dec 03 Javascript
使用jQuery 操作table 完成单元格合并的实例
Dec 27 jQuery
让Vue也可以使用Redux的方法
May 23 Javascript
Vue多环境代理配置方法思路详解
Jun 21 Javascript
在layui中使用form表单监听ajax异步验证注册的实例
Sep 03 Javascript
JS根据浏览器窗口大小实时动态改变网页文字大小的方法
Feb 25 #Javascript
你所未知的3种Node.js代码优化方式
Feb 25 #Javascript
jQuery使用contains过滤器实现精确匹配方法详解
Feb 25 #Javascript
原生javascript实现addClass,removeClass,hasClass函数
Feb 25 #Javascript
javascript随机抽取0-100之间不重复的10个数
Feb 25 #Javascript
JavaScript实现多种排序算法
Feb 24 #Javascript
JavaScript中的时间处理小结
Feb 24 #Javascript
You might like
跟我学小偷程序之成功偷取首页(第三天)
2006/10/09 PHP
腾讯QQ微博API接口获取微博内容
2013/10/30 PHP
PHP Streams(流)详细介绍及使用
2015/05/12 PHP
PHP实现中国公民身份证号码有效性验证示例代码
2017/05/03 PHP
PHP简单留言本功能实现代码
2017/06/09 PHP
php在windows环境下获得cpu内存实时使用率(推荐)
2018/02/08 PHP
Laravel5.7框架安装与使用学习笔记图文详解
2019/04/02 PHP
PHP钩子实现方法解析
2019/05/21 PHP
javascript 异步页面查询实现代码(asp.net)
2010/05/26 Javascript
实现前后端数据交互方法汇总
2015/04/07 Javascript
node.js调用C++开发的模块实例
2015/07/03 Javascript
Jquery+Ajax+PHP+MySQL实现分类列表管理(下)
2015/10/28 Javascript
js实现的彩色方块飞舞奇幻效果
2016/01/27 Javascript
JS获取元素多层嵌套思路详解
2016/05/16 Javascript
深入理解setTimeout函数和setInterval函数
2016/05/20 Javascript
关于BootStrap modal 在IOS9中不能弹出的解决方法(IOS 9 bootstrap modal ios 9 noticework)
2016/12/14 Javascript
vue.js实现简单购物车功能
2020/05/30 Javascript
微信小程序多列表渲染数据开关互不影响的实现
2020/06/05 Javascript
详解React 条件渲染
2020/07/08 Javascript
JavaScript实现网页动态生成表格
2020/11/25 Javascript
python对html代码进行escape编码的方法
2015/05/04 Python
python 计算两个日期相差多少个月实例代码
2017/05/24 Python
Python实现购物评论文本情感分析操作【基于中文文本挖掘库snownlp】
2018/08/07 Python
Python tkinter的grid布局及Text动态显示方法
2018/10/11 Python
使用python读取.text文件特定行的数据方法
2019/01/28 Python
如何利用Pyecharts可视化微信好友
2019/07/04 Python
Python模拟键盘输入自动登录TGP
2020/11/27 Python
Mavi牛仔裤美国官网:土耳其著名牛仔品牌
2016/09/24 全球购物
美国五金商店:Ace Hardware
2018/03/27 全球购物
CSMA/CD介质访问控制协议
2015/11/17 面试题
自荐信模版
2013/10/24 职场文书
小学教师寄语大全
2014/04/03 职场文书
单位租房协议书样本
2014/10/30 职场文书
反邪教观后感
2015/06/11 职场文书
2016年主题党日活动总结
2016/04/05 职场文书
Mysql 8.x 创建用户以及授予权限的操作记录
2022/04/18 MySQL