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 相关文章推荐
改变状态栏文字的js代码
Jun 13 Javascript
js判断滚动条是否已到页面最底部或顶部实例
Nov 20 Javascript
javascript原型链继承用法实例分析
Jan 28 Javascript
JavaScript继承模式粗探
Jan 12 Javascript
基于jquery实现轮播特效
Apr 22 Javascript
jQuery纵向导航菜单效果实现方法
Dec 19 Javascript
Vue2.0 UI框架ElementUI使用方法详解
Apr 14 Javascript
JS原生数据双向绑定实现代码
Aug 14 Javascript
angular2 ng2 @input和@output理解及示例
Oct 10 Javascript
原生JavaScript实现的简单放大镜效果示例
Feb 07 Javascript
vue里面使用mui的弹出日期选择插件实例
Sep 16 Javascript
Vant picker 多级联动操作
Nov 02 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
生成卡号php代码
2008/04/09 PHP
php session 预定义数组
2009/03/16 PHP
PHP中session变量的销毁
2014/02/27 PHP
Yii净化器CHtmlPurifier用法示例(过滤不良代码)
2016/07/15 PHP
获取非最后一列td值并将title设为该值的方法
2013/10/30 Javascript
jQuery is()函数用法3例
2014/05/06 Javascript
调整小数的格式保留小数点后两位
2014/05/14 Javascript
Bootstrap组件系列之福利篇几款好用的组件(推荐)
2016/06/23 Javascript
javascript轮播图算法
2016/10/21 Javascript
nodejs构建本地web测试服务器 如何解决访问静态资源问题
2017/07/14 NodeJs
weex slider实现滑动底部导航功能
2017/08/28 Javascript
详解nodejs通过响应回写的方式渲染页面资源
2018/04/07 NodeJs
vue上传图片到oss的方法示例(图片带有删除功能)
2018/09/27 Javascript
VUEX 数据持久化,刷新后重新获取的例子
2019/11/12 Javascript
React中使用UMEditor的方法示例
2019/12/27 Javascript
Python爬虫小技巧之伪造随机的User-Agent
2018/09/13 Python
详解如何用django实现redirect的几种方法总结
2018/11/22 Python
Python获取网段内ping通IP的方法
2019/01/31 Python
python3 自动打印出最新版本执行的mysql2redis实例
2020/04/09 Python
Python中SQLite如何使用
2020/05/27 Python
Python实现自动签到脚本功能
2020/08/20 Python
移动端rem布局的两种实现方法
2018/01/03 HTML / CSS
利用Storage Event实现页面间通信的示例代码
2018/07/26 HTML / CSS
欧洲最大的笔和书写专家:The Pen Shop
2017/03/19 全球购物
Expedia挪威官网:酒店、机票和租车
2018/03/03 全球购物
西班牙多品牌鞋店连锁店:Krack
2018/11/30 全球购物
营销主管自我评价怎么写
2013/09/19 职场文书
学生手册家长评语
2014/02/10 职场文书
一年级语文教学反思
2014/02/13 职场文书
医学生临床实习自我评价
2014/03/07 职场文书
2014全国两会学习心得体会1000字
2014/03/10 职场文书
党的群众路线教育学习材料
2014/05/12 职场文书
小学生常见病防治方案
2014/06/06 职场文书
营销与策划专业求职信
2014/06/20 职场文书
护士节演讲稿开场白
2014/08/25 职场文书
JS新手入门数组处理的实用方法汇总
2021/04/07 Javascript