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 相关文章推荐
线路分流自动跳转代码;希望对大家有用!
Dec 02 Javascript
用Javascript实现锚点(Anchor)间平滑跳转
Sep 08 Javascript
javascript中的document.open()方法使用介绍
Oct 09 Javascript
Javascript简写条件语句(推荐)
Jun 12 Javascript
为jQuery-easyui的tab组件添加右键菜单功能的简单实例
Oct 10 Javascript
jquery实现百叶窗效果
Jan 12 Javascript
JS按条件 serialize() 对应标签的使用方法
Jul 24 Javascript
微信小程序实现顶部下拉菜单栏
Nov 04 Javascript
如何写好一个vue组件,老夫的一年经验全在这了(推荐)
May 18 Javascript
Node.js 多进程处理CPU密集任务的实现
May 26 Javascript
JavaScript数组去重实现方法小结
Jan 17 Javascript
js里面的变量范围分享
Jul 18 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
1982年日本摄影师镜头下的中国孩子 那无忧无虑的童年
2020/03/12 杂记
php 引用(&amp;)详解
2009/11/20 PHP
在PHP中使用curl_init函数的说明
2010/11/02 PHP
php中print(),print_r(),echo()的区别详解
2014/12/01 PHP
PHP基于phpqrcode生成带LOGO图像的二维码实例
2015/07/10 PHP
Laravel框架处理用户的请求操作详解
2019/12/20 PHP
PHP使用PDO实现mysql防注入功能详解
2019/12/20 PHP
给Javascript数组插入一条记录的代码
2007/08/30 Javascript
在网页中使用document.write时遭遇的奇怪问题
2010/08/24 Javascript
Jquery之美中不足小结
2011/02/16 Javascript
javascript学习笔记(五)正则表达式
2011/04/08 Javascript
jQuery操作基本控件方法实例分析
2015/12/31 Javascript
JS手机端touch事件计算滑动距离的方法示例
2017/10/26 Javascript
详解Angular操作cookies方法
2018/06/01 Javascript
JS获取子节点、父节点和兄弟节点的方法实例总结
2018/07/06 Javascript
layui中使用jquery控制radio选中事件的示例代码
2018/08/15 jQuery
Angular使用cli生成自定义文件、组件的方法
2018/09/04 Javascript
详解如何模拟实现node中的Events模块(通俗易懂版)
2019/04/15 Javascript
解决vue单页面修改样式无法覆盖问题
2019/08/05 Javascript
JavaScript检测是否开启了控制台(F12调试工具)
2020/10/02 Javascript
Pyhton中单行和多行注释的使用方法及规范
2016/10/11 Python
python实现kMeans算法
2017/12/21 Python
python画图系列之个性化显示x轴区段文字的实例
2018/12/13 Python
python实现QQ批量登录功能
2019/06/19 Python
连接pandas以及数组转pandas的方法
2019/06/28 Python
Python如何计算语句执行时间
2019/11/22 Python
详解PyQt5信号与槽的几种高级玩法
2020/03/24 Python
Python存储读取HDF5文件代码解析
2020/11/25 Python
Python使用pyenv实现多环境管理
2021/02/05 Python
Omio荷兰:预订火车、巴士和机票
2018/11/04 全球购物
什么是跨站脚本攻击
2014/12/11 面试题
小松树教学反思
2014/02/11 职场文书
生产部厂长职位说明书
2014/03/03 职场文书
利用Python+OpenCV三步去除水印
2021/05/28 Python
React Native项目框架搭建的一些心得体会
2021/05/28 Javascript
英镑符号 £
2022/02/17 杂记