NodeJs 专题
专题目录
您的位置:Js应用 > NodeJs专题 > Node.js C/C++ 插件
Node.js C/C++ 插件
作者:--    发布时间:2019-11-20

node.js addons(插件)是动态链接的共享对象。他提供了c/c++类库能力。这些api比较复杂,他包以下几个类库:

  • v8 javascript, c++类库。用来和javascript交互,比如创建对象,调用函数等等。在v8.h头文件中 (目录地址deps/v8/include/v8.h),线上地址online

  • libuv,c事件循环库。等待文件描述符变为可读,等待定时器,等待信号时,会和libuv打交道。或者说,如果你需要和i/o打交道,就会用到libuv。

  • 内部node类库。其中最重要的类node::objectwrap,你会经常派生自它。

  • 其他的参见deps/

node已经将所有的依赖编译成可以执行文件,所以你不必当心这些类库的链接问题。

以下所有例子可以在download下载,也许你可以从中找一个作为你的扩展插件。

hello world

现在我们来写一个c++插件的小例子,它的效果和以下js代码一致:

module.exports.hello = function() { return 'world'; };

通过以下代码来创建hello.cc文件:

// hello.cc
#include <node.h>

using namespace v8;

void method(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);
  args.getreturnvalue().set(string::newfromutf8(isolate, "world"));
}

void init(handle<object> exports) {
  node_set_method(exports, "hello", method);
}

node_module(addon, init)

注意:所有的node插件必须输出一个初始化函数:

void initialize (handle<object> exports);
node_module(module_name, initialize)

node_module之后的代码没有分号,因为它不是一个函数 (参见node.h)。

module_name必须和二进制文件名字一致 (后缀是.node)。

源文件会编译成addon.node二进制插件。为此我们创建了一个很像json的binding.gyp文件,它包含配置信息,这个文件用node-gyp编译。

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

下一步创建一个node-gyp configure工程,在平台上生成这些文件。

创建后,在build/文件夹里拥有一个makefile (unix系统) 文件或者vcxproj文件(windows 系统)。接着调用node-gyp build命令编译,生成.node文件。这些文件位于build/release/目录里。

现在,你能在node工程中使用这些二进制扩展插件,在hello.js中声明require之前编译的hello.node:

// hello.js
var addon = require('./build/release/addon');

console.log(addon.hello()); // 'world'

更多的信息请参考https://github.com/arturadib/node-qt

插件模式

下面是一些addon插件的模式,帮助你开始编码。v8 reference文档里包含v8的各种接口,embedder's guide这个文档包含各种说明,比如handles, scopes, function templates等等。

在使用这些例子前,你需要先用node-gyp编译。创建binding.gyp 文件:

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

将文件名加入到sources数组里就可以使用多个.cc文件,例如 :

"sources": ["addon.cc", "myexample.cc"]

准备好binding.gyp文件后, 你就能配置并编译插件:

$ node-gyp configure build

函数参数

从以下模式中解释了如何从javascript函数中读取参数,并返回结果。仅需要一个addon.cc文件:

// addon.cc
#include <node.h>

using namespace v8;

void add(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);

  if (args.length() < 2) {
    isolate->throwexception(exception::typeerror(
        string::newfromutf8(isolate, "wrong number of arguments")));
    return;
  }

  if (!args[0]->isnumber() || !args[1]->isnumber()) {
    isolate->throwexception(exception::typeerror(
        string::newfromutf8(isolate, "wrong arguments")));
    return;
  }

  double value = args[0]->numbervalue() + args[1]->numbervalue();
  local<number> num = number::new(isolate, value);

  args.getreturnvalue().set(num);
}

void init(handle<object> exports) {
  node_set_method(exports, "add", add);
}

node_module(addon, init)

可以用以下的javascript代码片段测试:

// test.js
var addon = require('./build/release/addon');

console.log( 'this should be eight:', addon.add(3,5) );

回调callbacks

你也能传javascript函数给c++函数,并执行它。在addon.cc中:

// addon.cc
#include <node.h>

using namespace v8;

void runcallback(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);

  local<function> cb = local<function>::cast(args[0]);
  const unsigned argc = 1;
  local<value> argv[argc] = { string::newfromutf8(isolate, "hello world") };
  cb->call(isolate->getcurrentcontext()->global(), argc, argv);
}

void init(handle<object> exports, handle<object> module) {
  node_set_method(module, "exports", runcallback);
}

node_module(addon, init)

注意,这个例子中使用了init()里的2个参数,module对象是第二个参数。它允许addon使用一个函数完全重写exports

可以用以下的代码来测试:

// test.js
var addon = require('./build/release/addon');

addon(function(msg){
  console.log(msg); // 'hello world'
});

对象工厂

addon.cc模式里,你能用c++函数创建并返回一个新的对象,这个对象所包含的msg属性是由createobject()函数传入:

// addon.cc
#include <node.h>

using namespace v8;

void createobject(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);

  local<object> obj = object::new(isolate);
  obj->set(string::newfromutf8(isolate, "msg"), args[0]->tostring());

  args.getreturnvalue().set(obj);
}

void init(handle<object> exports, handle<object> module) {
  node_set_method(module, "exports", createobject);
}

node_module(addon, init)

使用javascript测试:

// test.js
var addon = require('./build/release/addon');

var obj1 = addon('hello');
var obj2 = addon('world');
console.log(obj1.msg+' '+obj2.msg); // 'hello world'

工厂模式

这个模式里展示了如何创建并返回一个javascript函数,它是由c++函数包装的 :

// addon.cc
#include <node.h>

using namespace v8;

void myfunction(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);
  args.getreturnvalue().set(string::newfromutf8(isolate, "hello world"));
}

void createfunction(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);

  local<functiontemplate> tpl = functiontemplate::new(isolate, myfunction);
  local<function> fn = tpl->getfunction();

  // omit this to make it anonymous
  fn->setname(string::newfromutf8(isolate, "thefunction"));

  args.getreturnvalue().set(fn);
}

void init(handle<object> exports, handle<object> module) {
  node_set_method(module, "exports", createfunction);
}

node_module(addon, init)

测试:

// test.js
var addon = require('./build/release/addon');

var fn = addon();
console.log(fn()); // 'hello world'

包装c++对象

以下会创建一个c++对象的包装myobject,这样他就能在javascript中用new实例化。首先在addon.cc中准备主要模块:

// addon.cc
#include <node.h>
#include "myobject.h"

using namespace v8;

void initall(handle<object> exports) {
  myobject::init(exports);
}

node_module(addon, initall)

接着在myobject.h创建包装,它继承自node::objectwrap:

// myobject.h
#ifndef myobject_h
#define myobject_h

#include <node.h>
#include <node_object_wrap.h>

class myobject : public node::objectwrap {
 public:
  static void init(v8::handle<v8::object> exports);

 private:
  explicit myobject(double value = 0);
  ~myobject();

  static void new(const v8::functioncallbackinfo<v8::value>& args);
  static void plusone(const v8::functioncallbackinfo<v8::value>& args);
  static v8::persistent<v8::function> constructor;
  double value_;
};

#endif

myobject.cc中实现各种暴露的方法,通过给构造函数添加prototype属性来暴露plusone方法:

// myobject.cc
#include "myobject.h"

using namespace v8;

persistent<function> myobject::constructor;

myobject::myobject(double value) : value_(value) {
}

myobject::~myobject() {
}

void myobject::init(handle<object> exports) {
  isolate* isolate = isolate::getcurrent();

  // prepare constructor template
  local<functiontemplate> tpl = functiontemplate::new(isolate, new);
  tpl->setclassname(string::newfromutf8(isolate, "myobject"));
  tpl->instancetemplate()->setinternalfieldcount(1);

  // prototype
  node_set_prototype_method(tpl, "plusone", plusone);

  constructor.reset(isolate, tpl->getfunction());
  exports->set(string::newfromutf8(isolate, "myobject"),
               tpl->getfunction());
}

void myobject::new(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);

  if (args.isconstructcall()) {
    // invoked as constructor: `new myobject(...)`
    double value = args[0]->isundefined() ? 0 : args[0]->numbervalue();
    myobject* obj = new myobject(value);
    obj->wrap(args.this());
    args.getreturnvalue().set(args.this());
  } else {
    // invoked as plain function `myobject(...)`, turn into construct call.
    const int argc = 1;
    local<value> argv[argc] = { args[0] };
    local<function> cons = local<function>::new(isolate, constructor);
    args.getreturnvalue().set(cons->newinstance(argc, argv));
  }
}

void myobject::plusone(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);

  myobject* obj = objectwrap::unwrap<myobject>(args.holder());
  obj->value_ += 1;

  args.getreturnvalue().set(number::new(isolate, obj->value_));
}

测试:

// test.js
var addon = require('./build/release/addon');

var obj = new addon.myobject(10);
console.log( obj.plusone() ); // 11
console.log( obj.plusone() ); // 12
console.log( obj.plusone() ); // 13

包装对象工厂

当你想创建本地对象,又不想在javascript中严格的使用new初始化的时候,以下方法非常实用:

var obj = addon.createobject();
// instead of:
// var obj = new addon.object();

addon.cc中注册createobject方法:

// addon.cc
#include <node.h>
#include "myobject.h"

using namespace v8;

void createobject(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);
  myobject::newinstance(args);
}

void initall(handle<object> exports, handle<object> module) {
  myobject::init();

  node_set_method(module, "exports", createobject);
}

node_module(addon, initall)

myobject.h中有静态方法newinstance,他能实例化对象(它就像javascript的new):

// myobject.h
#ifndef myobject_h
#define myobject_h

#include <node.h>
#include <node_object_wrap.h>

class myobject : public node::objectwrap {
 public:
  static void init();
  static void newinstance(const v8::functioncallbackinfo<v8::value>& args);

 private:
  explicit myobject(double value = 0);
  ~myobject();

  static void new(const v8::functioncallbackinfo<v8::value>& args);
  static void plusone(const v8::functioncallbackinfo<v8::value>& args);
  static v8::persistent<v8::function> constructor;
  double value_;
};

#endif

这个实现方法和myobject.cc类似:

// myobject.cc
#include <node.h>
#include "myobject.h"

using namespace v8;

persistent<function> myobject::constructor;

myobject::myobject(double value) : value_(value) {
}

myobject::~myobject() {
}

void myobject::init() {
  isolate* isolate = isolate::getcurrent();
  // prepare constructor template
  local<functiontemplate> tpl = functiontemplate::new(isolate, new);
  tpl->setclassname(string::newfromutf8(isolate, "myobject"));
  tpl->instancetemplate()->setinternalfieldcount(1);

  // prototype
  node_set_prototype_method(tpl, "plusone", plusone);

  constructor.reset(isolate, tpl->getfunction());
}

void myobject::new(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);

  if (args.isconstructcall()) {
    // invoked as constructor: `new myobject(...)`
    double value = args[0]->isundefined() ? 0 : args[0]->numbervalue();
    myobject* obj = new myobject(value);
    obj->wrap(args.this());
    args.getreturnvalue().set(args.this());
  } else {
    // invoked as plain function `myobject(...)`, turn into construct call.
    const int argc = 1;
    local<value> argv[argc] = { args[0] };
    local<function> cons = local<function>::new(isolate, constructor);
    args.getreturnvalue().set(cons->newinstance(argc, argv));
  }
}

void myobject::newinstance(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);

  const unsigned argc = 1;
  handle<value> argv[argc] = { args[0] };
  local<function> cons = local<function>::new(isolate, constructor);
  local<object> instance = cons->newinstance(argc, argv);

  args.getreturnvalue().set(instance);
}

void myobject::plusone(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);

  myobject* obj = objectwrap::unwrap<myobject>(args.holder());
  obj->value_ += 1;

  args.getreturnvalue().set(number::new(isolate, obj->value_));
}

测试:

// test.js
var createobject = require('./build/release/addon');

var obj = createobject(10);
console.log( obj.plusone() ); // 11
console.log( obj.plusone() ); // 12
console.log( obj.plusone() ); // 13

var obj2 = createobject(20);
console.log( obj2.plusone() ); // 21
console.log( obj2.plusone() ); // 22
console.log( obj2.plusone() ); // 23

传递包装对象

除了包装并返回c++对象,你可以使用node的node::objectwrap::unwrap帮助函数来解包。在下面的addon.cc中,我们介绍了一个add()函数,它能获取2个 myobject对象:

// addon.cc
#include <node.h>
#include <node_object_wrap.h>
#include "myobject.h"

using namespace v8;

void createobject(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);
  myobject::newinstance(args);
}

void add(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);

  myobject* obj1 = node::objectwrap::unwrap<myobject>(
      args[0]->toobject());
  myobject* obj2 = node::objectwrap::unwrap<myobject>(
      args[1]->toobject());

  double sum = obj1->value() + obj2->value();
  args.getreturnvalue().set(number::new(isolate, sum));
}

void initall(handle<object> exports) {
  myobject::init();

  node_set_method(exports, "createobject", createobject);
  node_set_method(exports, "add", add);
}

node_module(addon, initall)

介绍myobject.h里的一个公开方法,它能在解包后使用私有变量:

// myobject.h
#ifndef myobject_h
#define myobject_h

#include <node.h>
#include <node_object_wrap.h>

class myobject : public node::objectwrap {
 public:
  static void init();
  static void newinstance(const v8::functioncallbackinfo<v8::value>& args);
  inline double value() const { return value_; }

 private:
  explicit myobject(double value = 0);
  ~myobject();

  static void new(const v8::functioncallbackinfo<v8::value>& args);
  static v8::persistent<v8::function> constructor;
  double value_;
};

#endif

myobject.cc的实现方法和之前的类似:

// myobject.cc
#include <node.h>
#include "myobject.h"

using namespace v8;

persistent<function> myobject::constructor;

myobject::myobject(double value) : value_(value) {
}

myobject::~myobject() {
}

void myobject::init() {
  isolate* isolate = isolate::getcurrent();

  // prepare constructor template
  local<functiontemplate> tpl = functiontemplate::new(isolate, new);
  tpl->setclassname(string::newfromutf8(isolate, "myobject"));
  tpl->instancetemplate()->setinternalfieldcount(1);

  constructor.reset(isolate, tpl->getfunction());
}

void myobject::new(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);

  if (args.isconstructcall()) {
    // invoked as constructor: `new myobject(...)`
    double value = args[0]->isundefined() ? 0 : args[0]->numbervalue();
    myobject* obj = new myobject(value);
    obj->wrap(args.this());
    args.getreturnvalue().set(args.this());
  } else {
    // invoked as plain function `myobject(...)`, turn into construct call.
    const int argc = 1;
    local<value> argv[argc] = { args[0] };
    local<function> cons = local<function>::new(isolate, constructor);
    args.getreturnvalue().set(cons->newinstance(argc, argv));
  }
}

void myobject::newinstance(const functioncallbackinfo<value>& args) {
  isolate* isolate = isolate::getcurrent();
  handlescope scope(isolate);

  const unsigned argc = 1;
  handle<value> argv[argc] = { args[0] };
  local<function> cons = local<function>::new(isolate, constructor);
  local<object> instance = cons->newinstance(argc, argv);

  args.getreturnvalue().set(instance);
}

测试:

// test.js
var addon = require('./build/release/addon');

var obj1 = addon.createobject(10);
var obj2 = addon.createobject(20);
var result = addon.add(obj1, obj2);

console.log(result); // 30

相关教程

如果你想了解更多与c++相关的知识,则可以参考本站的《c++教程》

网站声明:
本站部分内容来自网络,如您发现本站内容
侵害到您的利益,请联系本站管理员处理。
联系站长
373515719@qq.com
关于本站:
编程参考手册