(译+注解)node.js的C++扩展入门

声明:本文主要翻译自node.js addons官方文档。部分解释为作者自己添加。

编程环境:

1. 操作系统 Mac OS X 10.9.5
1. node.js v4.4.2
2. npm v3.9.2

本文将介绍node.js中编写C++扩展的入门知识。

1. 基本知识介绍

在node.js中,除了用js写代码以外,还可以使用C++编写扩展,这有点类似DLL,动态链接进js代码中。使用上也相当方便,只需用require包含,这和一般的js模块并没有什么区别。C++扩展为js和C++代码的通信提供了一个接口。

要编写node.js的C++扩展,需要了解一些基本知识:

1. V8: Google出品的大名鼎鼎的V8引擎,它实际上是一个C++类库,用来和 JavaScript 交互,比如创建对象,调用函数等等。V8的API大部分都声明在v8.h头文件中。
2. libuv:一个C实现的事件循环库,node.js使用libuv来实现自己的事件循环、工作线程和所有的异步行为。它是一个跨平台的,高度抽象的lib,提供了简单易用的、POSIX-like的方式来让操作系统和系统任务进行交互。比如和文件系统、sockets、定时器和系统事件。libuv还提供了POSIX threads线程级别的抽象来增强标准事件循环中不具备的复杂异步能力。我们鼓励C++扩展的作者思考如何通过转换I/O或其他耗时操作到非阻塞系统操作来避免阻塞事件循环。
3. node.js内部lib,node.js本身提供了很多C/C++ API来给扩展使用,比如最重要的一个:node::ObjectWrap类。
4. node.js包含了很多静态链接库,比如OpenSSL。这些库都放在node.js代码树的deps/目录下。只有V8和OpenSSL标识符被有意地被node.js重复导出来被各种扩展使用。

下面快速地来看一个实例。

2. 第一个例子Hello

下面的例子是一个简单的C++扩展,其功能相当于js的如下代码:

module.exports.hello = () => ‘world‘;

首先创建一个hello.cc:

 1 // hello.cc
 2 #include <node.h>
 3
 4 namespace demo {
 5     using v8::FunctionCallbackInfo;
 6     using v8::Isolate;
 7     using v8::Local;
 8     using v8::Object;
 9     using v8::String;
10     using v8::Value;
11
12     void Method(const FunctionCallbackInfo<Value>& args) {
13         Isolate* isolate = args.GetIsolate();
14         args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world"));
15     }
16
17     void init(Local<Object> exports) {
18         NODE_SET_METHOD(exports, "hello", Method);
19     }
20
21     NODE_MODULE(addon, init)
22 } // namespace demo

这个最简单的例子,已经出现了一些我们完全没有接触过的东西。大致解释一下:

1. 函数Method的参数类型是FunctionCallbackInfo<Value>&,FunctionCallbackInfo
2. Isolate,英文意思是“隔离”,在这里Isolate指的是一个独立的V8 runtime,可以理解为一个独立的V8执行环境,它包括了自己的堆管理器、GC等组件。后续的很多操作都要依赖于这个Isolate,后面我们会看到在很多操作中,都会使用Isolate的实例作为一个上下文传入。
(注:一个给定的Isolate在同一时间只能被一个线程访问,但如果有多个不同的Isolate,就可以给多个线程同时访问。不过,一个Isolate还不足以运行脚本,你还需要一个全局对象,一个执行上下文通过指定一个全局对象来定义一个完整的脚本执行环境。因此,可以有多个执行上下文存在于一个Isolate中,而且它们还可以简单安全地共享它们的全局对象。这是因为这个全局对象实际上属于Isolate,而却这个全局对象被Isolate的互斥锁保护着。)
3. 返回值需要用args.GetReturnValue().Set()来设置。
4. 向外导出方法需要在扩展的初始化函数中使用NODE_SET_METHOD(exports, Method_Name, Method);。如果有多个方法需要导出,就写多个NODE_SET_METHOD。

注意到node.js的C++扩展都必须按以下形式导出一个初始化函数(该函数名字可以随便设置一个):

void Initialize(Local<Object> exports);
NODE_MODULE(module_name, Initialize)

NODE_MODULE这行后面并没有分号(;),因为它并不是一个函数,你可以认为这是一个声明。module_name必须匹配最后生成的二进制文件的文件名(不包括.node后缀)。在hello.cc这个例子中,初始化函数是init,扩展模块名是addon。

构建(Building)

写好源代码后我们就要把它编译成二进制的addon.node文件了。binding.gyp文件用来描述我们模块的构建配置,这个文件的内容是JSON形式的:

1 {
2     "targets": [
3         {
4             "target_name": "addon",
5             "sources": [ "hello.cc" ]
6         }
7     ]
8 }

实施构建操作需要用到node-gyp,如果尚未安装的话,需要运行(可能要用到sudo):

npm install -g node-gyp

来全局安装node-gyp。

编写完binding.gyp文件,我们使用:

node-gyp configure

来生成对应项目在当前平台的build目录。这将会在build目录下生成一个Makefile(Unix-like系统)或者一个vcxproj文件(Windows系统)还有一部分其他文件。

接着,运行:

node-gyp build

来生成一个编译过的addon.node文件,这个文件会被放在build/Release/目录下。

build成功后,这个二进制的C++扩展就可以在node.js中使用require包含进来:

1 // hello.js
2 const addon = require(‘./build/Release/addon‘);
3 console.log(addon.hello()); // ‘world‘

由于扩展的二进制文件的存放位置会根据编译方式不同而变化(有可能放在build/Debug/目录),所以可以用这种方式来引入扩展:

1 try {
2     return require(‘./build/Release/addon.node‘);
3 } catch (err) {
4     return require(‘./build/Debug/addon.node‘);
5 }

但是个人觉得这种引入方式很奇怪,在能保证正确性的情况下,如果是开发模式,用Debug目录下的,生产模式用Release下的。

链接node.js依赖

node.js使用一些静态链接库,比如V8、libuv和OpenSSL。所有扩展都必须链接V8,还有可能需要链接一些其他的库。典型情况下,使用#include <...>来include这些库(比如链接V8就是#include <v8.h>),node-gyp会自动找到这些库。然而,有几个注意事项需要说明:

1. node-gyp运行时,它会检测node.js的版本并且下载全部源码文件或者只是下载头文件。如果下载了全部源码文件,扩展就可以使用node.js的所有依赖,如果仅仅下载了头文件,则只有node.js导出的那些东西可以被使用。
2. node-gyp可以使用--nodedir选项来指定本地node.js映像,使用这个选项时,扩展可以使用全部的node.js依赖。

使用require加载C++扩展

经过编译的node.js C++扩展的后缀名是.node(类似.so和.dll),require()函数会查找这些.node文件并像初始化动态链接库那样初始化它们。

当使用reqiure()时,.node后缀可以被省略。需要注意的是,node.js在使用reqiure()加载模块时,会优先加载js后缀的文件。比如说一个目录下有一个addon.js和一个addon.node,当使用require(‘addon‘)时,node.js会优先加载addon.js。

3.对node.js的原生抽象(这个暂略)

4.第二个例子

以下的几个例子的binding.gyp都使用:

1 {
2     "targets": [
3         {
4             "target_name": "addon",
5             "sources": [ "addon.cc" ]
6         }
7     ]
8 }

如果有多于一个的C++文件,可以把所有文件放在sources数组中:  

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

写好binding.gyp后,可以使用以下命令来一次性地配置和构建C++扩展:

node-gyp configure build

函数参数

C++扩展可以暴露函数和对象出来让node.js访问。当从js中调用C++扩展中的函数时,入参和返回值必须映射到C/C++事先声明好的代码中。

以下代码展示了C++扩展代码如何读取从js传递过来的函数入参和如何返回值:

 1 // addon.cc
 2 #include < node.h >
 3
 4 namespace demo {
 5     using v8: :Exception;
 6     using v8: :FunctionCallbackInfo;
 7     using v8: :Isolate;
 8     using v8: :Local;
 9     using v8: :Number;
10     using v8: :Object;
11     using v8: :String;
12     using v8: :Value;
13
14     // This is the implementation of the "add" method
15     // Input arguments are passed using the
16     // const FunctionCallbackInfo<Value>& args struct
17     void Add(const FunctionCallbackInfo < Value > &args) {
18         Isolate * isolate = args.GetIsolate();
19
20         // Check the number of arguments passed.
21         if (args.Length() < 2) {
22             // Throw an Error that is passed back to JavaScript
23             isolate - >ThrowException(Exception: :TypeError(String: :NewFromUtf8(isolate, "Wrong number of arguments")));
24             return;
25         }
26
27         // Check the argument types
28         if (!args[0] - >IsNumber() || !args[1] - >IsNumber()) {
29             isolate - >ThrowException(Exception: :TypeError(String: :NewFromUtf8(isolate, "Wrong arguments")));
30             return;
31         }
32
33         // Perform the operation
34         double value = args[0] - >NumberValue() + args[1] - >NumberValue();
35         Local < Number > num = Number: :New(isolate, value);
36
37         // Set the return value (using the passed in
38         // FunctionCallbackInfo<Value>&)
39         args.GetReturnValue().Set(num);
40     }
41
42     void Init(Local < Object > exports) {
43         NODE_SET_METHOD(exports, "add", Add);
44     }
45
46     NODE_MODULE(addon, Init)
47 } // namespace demo

编译成功后,这个扩展可以被node.js使用require()包含并使用:

1 // test.js
2 const addon = require(‘./build/Release/addon‘);
3 console.log(‘This should be eight:‘, addon.add(3, 5));

回调函数

一种很常见的做法是从js传递回调函数给C++调用,下面这个示例展示了如何做:

 1 // addon.cc
 2 #include < node.h >
 3
 4 namespace demo {
 5
 6     using v8: :Function;
 7     using v8: :FunctionCallbackInfo;
 8     using v8: :Isolate;
 9     using v8: :Local;
10     using v8: :Null;
11     using v8: :Object;
12     using v8: :String;
13     using v8: :Value;
14
15     void RunCallback(const FunctionCallbackInfo < Value > &args) {
16         Isolate * isolate = args.GetIsolate();
17         Local < Function > cb = Local < Function > ::Cast(args[0]);
18         const unsigned argc = 1;
19         Local < Value > argv[argc] = {
20             String: :NewFromUtf8(isolate, "hello world")
21         };
22         cb - >Call(Null(isolate), argc, argv);
23     }
24
25     void Init(Local < Object > exports, Local < Object > module) {
26         NODE_SET_METHOD(module, "exports", RunCallback);
27     }
28
29     NODE_MODULE(addon, Init)
30
31 } // namespace demo

解释:

1. 传递回调函数,其实和传递普通参数没什么大的区别,使用

Local<Function> cb = Local<Function>::Cast(args[0]);

可以获得这个回调函数。然后需要显式声明这个回调函数的参数个数和参数数组:

const unsigned argc = 1;
Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "hello world") };

调用这个回调函数需要传入isolate、参数个数argc、参数数组argv:

cb->Call(Null(isolate), argc, argv);

2. Init函数和之前有点不同,上面这个扩展的Init()使用了两个参数的形式(之前都是单参数),其中第二个参数接受一个module对象:

void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", RunCallback); // 相当于直接导出整个模块作为方法
}

这将允许扩展使用单个函数的形式代替之前往exports中添加函数作为属性的方式来完全地重写exports。因此可以直接用扩展的名字作为函数名来调用,这适用于此扩展只对外暴露一个方法的情况:

1 // test.js
2 const addon = require(‘./build/Release/addon‘);
3 addon((msg) => {
4     console.log(msg); // ‘hello world‘
5 });

作为演示,在这个示例中只是同步地调用回调函数。

对象工厂

在下面的示例中,扩展可以使用C++创建并返回新对象。下面的例子中,createObject()函数接受一个string类型的参数,然后创建一个一模一样的string,并在一个对象的msg属性中返回这个string:

 1 // addon.cc
 2 #include < node.h >
 3
 4 namespace demo {
 5
 6     using v8: :FunctionCallbackInfo;
 7     using v8: :Isolate;
 8     using v8: :Local;
 9     using v8: :Object;
10     using v8: :String;
11     using v8: :Value;
12
13     void CreateObject(const FunctionCallbackInfo < Value > &args) {
14         Isolate * isolate = args.GetIsolate();
15
16         Local < Object > obj = Object: :New(isolate);
17         obj - >Set(String: :NewFromUtf8(isolate, "msg"), args[0] - >ToString());
18
19         args.GetReturnValue().Set(obj);
20     }
21
22     void Init(Local < Object > exports, Local < Object > module) {
23         NODE_SET_METHOD(module, "exports", CreateObject);
24     }
25
26     NODE_MODULE(addon, Init)
27
28 } // namespace demo

解释:

1. 创建一个新对象,也需要把isolate作为参数传入并设置对象属性msg为第一个入参:

Local<Object> obj = Object::New(isolate);
obj->Set(String::NewFromUtf8(isolate, "msg"), args[0]->ToString());

2. Init函数中导出CreateObject作为模块函数。

测试上面扩展的js代码:

1 // test.js
2 const addon = require(‘./build/Release/addon‘);
3
4 var obj1 = addon(‘hello‘);
5 var obj2 = addon(‘world‘);
6 console.log(obj1.msg + ‘ ‘ + obj2.msg); // ‘hello world‘

函数工厂

还有一种常见的行为是创建包装了C++函数的js函数,并返回给js:

 1 // addon.cc
 2 #include < node.h >
 3
 4 namespace demo {
 5
 6     using v8: :Function;
 7     using v8: :FunctionCallbackInfo;
 8     using v8: :FunctionTemplate;
 9     using v8: :Isolate;
10     using v8: :Local;
11     using v8: :Object;
12     using v8: :String;
13     using v8: :Value;
14
15     void MyFunction(const FunctionCallbackInfo < Value > &args) {
16         Isolate * isolate = args.GetIsolate();
17         args.GetReturnValue().Set(String: :NewFromUtf8(isolate, "hello world"));
18     }
19
20     void CreateFunction(const FunctionCallbackInfo < Value > &args) {
21         Isolate * isolate = args.GetIsolate();
22
23         Local < FunctionTemplate > tpl = FunctionTemplate: :New(isolate, MyFunction);
24         Local < Function > fn = tpl - >GetFunction();
25
26         // omit this to make it anonymous
27         fn - >SetName(String: :NewFromUtf8(isolate, "theFunction"));
28
29         args.GetReturnValue().Set(fn);
30     }
31
32     void Init(Local < Object > exports, Local < Object > module) {
33         NODE_SET_METHOD(module, "exports", CreateFunction);
34     }
35
36     NODE_MODULE(addon, Init)
37
38 } // namespace demo

解释:

1. CreateFunction中使用v8::FunctionTemplate创建函数模板(传入参数MyFunction),并创建一个函数,其中函数命名是可选的:

1 Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);
2 Local<Function> fn = tpl->GetFunction();
3
4 // omit this to make it anonymous
5 fn->SetName(String::NewFromUtf8(isolate, "theFunction"));

测试一下:

1 // test.js
2 const addon = require(‘./build/Release/addon‘);
3
4 var fn = addon();
5 console.log(fn()); // ‘hello world‘

包装C++对象

还可以使用js的new操作符创建由C++包装的对象或类:

 1 // addon.cc
 2 #include < node.h > #include "myobject.h"
 3
 4 namespace demo {
 5
 6     using v8: :Local;
 7     using v8: :Object;
 8
 9     void InitAll(Local < Object > exports) {
10         MyObject: :Init(exports);
11     }
12
13     NODE_MODULE(addon, InitAll)
14
15 } // namespace demo

在上面的myobject.h中,包装类继承自node::ObjectWrap:

 1 // myobject.h
 2 #ifndef MYOBJECT_H#define MYOBJECT_H
 3
 4 #include < node.h > #include < node_object_wrap.h >
 5
 6 namespace demo {
 7
 8     class MyObject: public node: :ObjectWrap {
 9         public: static void Init(v8: :Local < v8: :Object > exports);
10
11         private: explicit MyObject(double value = 0);~MyObject();
12
13         static void New(const v8: :FunctionCallbackInfo < v8: :Value > &args);
14         static void PlusOne(const v8: :FunctionCallbackInfo < v8: :Value > &args);
15         static v8: :Persistent < v8: :Function > constructor;
16         double value_;
17     };
18
19 } // namespace demo
20 #endif

在myobject.cc中,实现了那些被暴露出去的方法。下面的代码通过把plusOne()添加到构造函数的prototype来暴露它:

 1 // myobject.cc
 2 #include "myobject.h"
 3
 4 namespace demo {
 5
 6     using v8: :Context;
 7     using v8: :Function;
 8     using v8: :FunctionCallbackInfo;
 9     using v8: :FunctionTemplate;
10     using v8: :Isolate;
11     using v8: :Local;
12     using v8: :Number;
13     using v8: :Object;
14     using v8: :Persistent;
15     using v8: :String;
16     using v8: :Value;
17
18     Persistent < Function > MyObject: :constructor;
19
20     MyObject: :MyObject(double value) : value_(value) {}
21
22     MyObject: :~MyObject() {}
23
24     void MyObject: :Init(Local < Object > exports) {
25         Isolate * isolate = exports - >GetIsolate();
26
27         // Prepare constructor template
28         Local < FunctionTemplate > tpl = FunctionTemplate: :New(isolate, New);
29         tpl - >SetClassName(String: :NewFromUtf8(isolate, "MyObject"));
30         tpl - >InstanceTemplate() - >SetInternalFieldCount(1);
31
32         // Prototype
33         NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);
34
35         constructor.Reset(isolate, tpl - >GetFunction());
36         exports - >Set(String: :NewFromUtf8(isolate, "MyObject"), tpl - >GetFunction());
37     }
38
39     void MyObject: :New(const FunctionCallbackInfo < Value > &args) {
40         Isolate * isolate = args.GetIsolate();
41
42         if (args.IsConstructCall()) {
43             // Invoked as constructor: `new MyObject(...)`
44             double value = args[0] - >IsUndefined() ? 0 : args[0] - >NumberValue();
45             MyObject * obj = new MyObject(value);
46             obj - >Wrap(args.This());
47             args.GetReturnValue().Set(args.This());
48         } else {
49             // Invoked as plain function `MyObject(...)`, turn into construct call.
50             const int argc = 1;
51             Local < Value > argv[argc] = {
52                 args[0]
53             };
54             Local < Context > context = isolate - >GetCurrentContext();
55             Local < Function > cons = Local < Function > ::New(isolate, constructor);
56             Local < Object > result = cons - >NewInstance(context, argc, argv).ToLocalChecked();
57             args.GetReturnValue().Set(result);
58         }
59     }
60
61     void MyObject: :PlusOne(const FunctionCallbackInfo < Value > &args) {
62         Isolate * isolate = args.GetIsolate();
63
64         MyObject * obj = ObjectWrap: :Unwrap < MyObject > (args.Holder());
65         obj - >value_ += 1;
66
67         args.GetReturnValue().Set(Number: :New(isolate, obj - >value_));
68     }
69
70 } // namespace demo

解释:

1. 在MyObject::Init中,使用v8::FunctionTemplate创建一个函数模板(传入参数New),并给这个模板设置一个类名MyObject,SetInternalFieldCount用来设定类的内部储存多少个内部变量,这里是1:

1 Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
2 tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
3 tpl->InstanceTemplate()->SetInternalFieldCount(1);

然后使用:

1 NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);

来设置prototype中的plusOne方法。

代码:

1 constructor.Reset(isolate, tpl->GetFunction());
2 exports->Set(String::NewFromUtf8(isolate, "MyObject"),
3 tpl->GetFunction());

第一行相当于js中的

1 XXX.prototype.constructor = XXX;

然后导出这个MyObject类。

2. 在MyObject::New中,情况略微复杂一些。首先判断是否是构造调用(使用js中的new操作符),如果是构造调用,运行以下代码:

MyObject* obj = new MyObject(value);

来new一个MyObject实例,value是构造入参,然后返回这个实例。

js中的函数如果不是构造调用就是普通的函数调用。

3. 在MyObject::PlusOne中,通过以下代码获取MyObject实例:

1 MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
2 obj->value_ += 1;

然后返回加1后的数值结果。

为了构建这个例子,需要把myobject.cc加入binding.gyp:

1 {
2     "targets": [{
3         "target_name": "addon",
4         "sources": ["addon.cc", "myobject.cc"]
5     }]
6 }

测试:

1 // test.js
2 const addon = require(‘./build/Release/addon‘);
3
4 var obj = new addon.MyObject(10);
5 console.log(obj.plusOne()); // 11
6 console.log(obj.plusOne()); // 12
7 console.log(obj.plusOne()); // 13

包装对象工厂

另外,还可以使用工厂模式来避免显式使用new操作符创建对象实例:

1 var obj = addon.createObject();
2 // instead of:
3 // var obj = new addon.Object();

首先,需要在addon.cc中实现createObject()方法:

 1 // addon.cc
 2 #include < node.h > #include "myobject.h"
 3
 4 namespace demo {
 5
 6     using v8: :FunctionCallbackInfo;
 7     using v8: :Isolate;
 8     using v8: :Local;
 9     using v8: :Object;
10     using v8: :String;
11     using v8: :Value;
12
13     void CreateObject(const FunctionCallbackInfo < Value > &args) {
14         MyObject: :NewInstance(args);
15     }
16
17     void InitAll(Local < Object > exports, Local < Object > module) {
18         MyObject: :Init(exports - >GetIsolate());
19
20         NODE_SET_METHOD(module, "exports", CreateObject);
21     }
22
23     NODE_MODULE(addon, InitAll)
24
25 } // namespace demo

在myobject.h中,加入静态方法NewInstance()来处理实例化对象的操作,我们将用NewInstance()替代js的new操作符:

 1 // myobject.h
 2 #ifndef MYOBJECT_H#define MYOBJECT_H
 3
 4 #include < node.h > #include < node_object_wrap.h >
 5
 6 namespace demo {
 7
 8     class MyObject: public node: :ObjectWrap {
 9         public: static void Init(v8: :Isolate * isolate);
10         static void NewInstance(const v8: :FunctionCallbackInfo < v8: :Value > &args);
11
12         private: explicit MyObject(double value = 0);~MyObject();
13
14         static void New(const v8: :FunctionCallbackInfo < v8: :Value > &args);
15         static void PlusOne(const v8: :FunctionCallbackInfo < v8: :Value > &args);
16         static v8: :Persistent < v8: :Function > constructor;
17         double value_;
18     };
19
20 } // namespace demo
21 #endif

myobject.cc中的实现和前面差不多:

 1 // myobject.cc
 2 #include < node.h > #include "myobject.h"
 3
 4 namespace demo {
 5
 6     using v8: :Context;
 7     using v8: :Function;
 8     using v8: :FunctionCallbackInfo;
 9     using v8: :FunctionTemplate;
10     using v8: :Isolate;
11     using v8: :Local;
12     using v8: :Number;
13     using v8: :Object;
14     using v8: :Persistent;
15     using v8: :String;
16     using v8: :Value;
17
18     Persistent < Function > MyObject: :constructor;
19
20     MyObject: :MyObject(double value) : value_(value) {}
21
22     MyObject: :~MyObject() {}
23
24     void MyObject: :Init(Isolate * isolate) {
25         // Prepare constructor template
26         Local < FunctionTemplate > tpl = FunctionTemplate: :New(isolate, New);
27         tpl - >SetClassName(String: :NewFromUtf8(isolate, "MyObject"));
28         tpl - >InstanceTemplate() - >SetInternalFieldCount(1);
29
30         // Prototype
31         NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);
32
33         constructor.Reset(isolate, tpl - >GetFunction());
34     }
35
36     void MyObject: :New(const FunctionCallbackInfo < Value > &args) {
37         Isolate * isolate = args.GetIsolate();
38
39         if (args.IsConstructCall()) {
40             // Invoked as constructor: `new MyObject(...)`
41             double value = args[0] - >IsUndefined() ? 0 : args[0] - >NumberValue();
42             MyObject * obj = new MyObject(value);
43             obj - >Wrap(args.This());
44             args.GetReturnValue().Set(args.This());
45         } else {
46             // Invoked as plain function `MyObject(...)`, turn into construct call.
47             const int argc = 1;
48             Local < Value > argv[argc] = {
49                 args[0]
50             };
51             Local < Function > cons = Local < Function > ::New(isolate, constructor);
52             Local < Context > context = isolate - >GetCurrentContext();
53             Local < Object > instance = cons - >NewInstance(context, argc, argv).ToLocalChecked();
54             args.GetReturnValue().Set(instance);
55         }
56     }
57
58     void MyObject: :NewInstance(const FunctionCallbackInfo < Value > &args) {
59         Isolate * isolate = args.GetIsolate();
60
61         const unsigned argc = 1;
62         Local < Value > argv[argc] = {
63             args[0]
64         };
65         Local < Function > cons = Local < Function > ::New(isolate, constructor);
66         Local < Context > context = isolate - >GetCurrentContext();
67         Local < Object > instance = cons - >NewInstance(context, argc, argv).ToLocalChecked();
68
69         args.GetReturnValue().Set(instance);
70     }
71
72     void MyObject: :PlusOne(const FunctionCallbackInfo < Value > &args) {
73         Isolate * isolate = args.GetIsolate();
74
75         MyObject * obj = ObjectWrap: :Unwrap < MyObject > (args.Holder());
76         obj - >value_ += 1;
77
78         args.GetReturnValue().Set(Number: :New(isolate, obj - >value_));
79     }
80
81 } // namespace demo

解释:

1. 这个例子和之前那个差不太多,只不过在扩展中提供了CreateObject()工厂方法来创建MyObject实例,CreateObject()在内部又使用MyObject::NewInstance()来创建对象。

再强调一次,为了构建这个例子,需要把myobject.cc加入binding.gyp:

1 {
2     "targets": [{
3         "target_name": "addon",
4         "sources": ["addon.cc", "myobject.cc"]
5     }]
6 }

测试:

 1 // test.js
 2 const createObject = require(‘./build/Release/addon‘);
 3
 4 var obj = createObject(10);
 5 console.log(obj.plusOne()); // 11
 6 console.log(obj.plusOne()); // 12
 7 console.log(obj.plusOne()); // 13
 8
 9 var obj2 = createObject(20);
10 console.log(obj2.plusOne()); // 21
11 console.log(obj2.plusOne()); // 22
12 console.log(obj2.plusOne()); // 23

传递包装对象

为了进一步包装和返回C++对象,可以利用node.js的helper函数node::ObjectWrap::Unwrap来展开包装对象。下面的例子展示了一个接受两个MyObject对象作为参数的函数add():

 1 // addon.cc
 2 #include < node.h > #include < node_object_wrap.h > #include "myobject.h"
 3
 4 namespace demo {
 5
 6     using v8: :FunctionCallbackInfo;
 7     using v8: :Isolate;
 8     using v8: :Local;
 9     using v8: :Number;
10     using v8: :Object;
11     using v8: :String;
12     using v8: :Value;
13
14     void CreateObject(const FunctionCallbackInfo < Value > &args) {
15         MyObject: :NewInstance(args);
16     }
17
18     void Add(const FunctionCallbackInfo < Value > &args) {
19         Isolate * isolate = args.GetIsolate();
20
21         MyObject * obj1 = node: :ObjectWrap: :Unwrap < MyObject > (args[0] - >ToObject());
22         MyObject * obj2 = node: :ObjectWrap: :Unwrap < MyObject > (args[1] - >ToObject());
23
24         double sum = obj1 - >value() + obj2 - >value();
25         args.GetReturnValue().Set(Number: :New(isolate, sum));
26     }
27
28     void InitAll(Local < Object > exports) {
29         MyObject: :Init(exports - >GetIsolate());
30
31         NODE_SET_METHOD(exports, "createObject", CreateObject);
32         NODE_SET_METHOD(exports, "add", Add);
33     }
34
35     NODE_MODULE(addon, InitAll)
36
37 } // namespace demo

在myobject.h中,加入一个新的public方法value()来获取private变量:

 1 // myobject.h
 2 #ifndef MYOBJECT_H#define MYOBJECT_H
 3
 4 #include < node.h > #include < node_object_wrap.h >
 5
 6 namespace demo {
 7
 8     class MyObject: public node: :ObjectWrap {
 9         public: static void Init(v8: :Isolate * isolate);
10         static void NewInstance(const v8: :FunctionCallbackInfo < v8: :Value > &args);
11         inline double value() const {
12             return value_;
13         }
14
15         private: explicit MyObject(double value = 0);~MyObject();
16
17         static void New(const v8: :FunctionCallbackInfo < v8: :Value > &args);
18         static v8: :Persistent < v8: :Function > constructor;
19         double value_;
20     };
21
22 } // namespace demo
23 #endif

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

 1 // myobject.cc
 2 #include < node.h > #include "myobject.h"
 3
 4 namespace demo {
 5
 6     using v8: :Context;
 7     using v8: :Function;
 8     using v8: :FunctionCallbackInfo;
 9     using v8: :FunctionTemplate;
10     using v8: :Isolate;
11     using v8: :Local;
12     using v8: :Object;
13     using v8: :Persistent;
14     using v8: :String;
15     using v8: :Value;
16
17     Persistent < Function > MyObject: :constructor;
18
19     MyObject: :MyObject(double value) : value_(value) {}
20
21     MyObject: :~MyObject() {}
22
23     void MyObject: :Init(Isolate * isolate) {
24         // Prepare constructor template
25         Local < FunctionTemplate > tpl = FunctionTemplate: :New(isolate, New);
26         tpl - >SetClassName(String: :NewFromUtf8(isolate, "MyObject"));
27         tpl - >InstanceTemplate() - >SetInternalFieldCount(1);
28
29         constructor.Reset(isolate, tpl - >GetFunction());
30     }
31
32     void MyObject: :New(const FunctionCallbackInfo < Value > &args) {
33         Isolate * isolate = args.GetIsolate();
34
35         if (args.IsConstructCall()) {
36             // Invoked as constructor: `new MyObject(...)`
37             double value = args[0] - >IsUndefined() ? 0 : args[0] - >NumberValue();
38             MyObject * obj = new MyObject(value);
39             obj - >Wrap(args.This());
40             args.GetReturnValue().Set(args.This());
41         } else {
42             // Invoked as plain function `MyObject(...)`, turn into construct call.
43             const int argc = 1;
44             Local < Value > argv[argc] = {
45                 args[0]
46             };
47             Local < Context > context = isolate - >GetCurrentContext();
48             Local < Function > cons = Local < Function > ::New(isolate, constructor);
49             Local < Object > instance = cons - >NewInstance(context, argc, argv).ToLocalChecked();
50             args.GetReturnValue().Set(instance);
51         }
52     }
53
54     void MyObject: :NewInstance(const FunctionCallbackInfo < Value > &args) {
55         Isolate * isolate = args.GetIsolate();
56
57         const unsigned argc = 1;
58         Local < Value > argv[argc] = {
59             args[0]
60         };
61         Local < Function > cons = Local < Function > ::New(isolate, constructor);
62         Local < Context > context = isolate - >GetCurrentContext();
63         Local < Object > instance = cons - >NewInstance(context, argc, argv).ToLocalChecked();
64
65         args.GetReturnValue().Set(instance);
66     }
67
68 } // namespace demo

解释:

1. addon.cc中使用户如下代码来获取包装对象:

1 MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>(args[0]->ToObject());

测试:

1 // test.js
2 const addon = require(‘./build/Release/addon‘);
3
4 var obj1 = addon.createObject(10);
5 var obj2 = addon.createObject(20);
6 var result = addon.add(obj1, obj2);
7
8 console.log(result); // 30

AtExit钩子

一个AtExit钩子是这样一种函数:它会在node.js事件循环结束后、js虚拟机被终止前或node.js停机前被调用。AtExit钩子需要被使用node::AtExit来注册。

函数声明如下:

1 void AtExit(callback, args)

callback: void (*)(void*),一个在exit时被调用的函数的函数指针。
args: void*,一个传递给callback的指针。

AtExit钩子运行在事件循环之后和js虚拟机被kill掉之前。

AtExit钩子接受两个参数:一个回调函数的函数指针和一个传递给回调函数的隐式上下文数据的指针。

回调函数的调用方式是后进先出(LIFO),和栈一样。

以下的addon.cc实现了AtExit钩子:

 1 // addon.cc
 2 #undef NDEBUG#include < assert.h > #include < stdlib.h > #include < node.h >
 3
 4 namespace demo {
 5
 6     using node: :AtExit;
 7     using v8: :HandleScope;
 8     using v8: :Isolate;
 9     using v8: :Local;
10     using v8: :Object;
11
12     static char cookie[] = "yum yum";
13     static int at_exit_cb1_called = 0;
14     static int at_exit_cb2_called = 0;
15
16     static void at_exit_cb1(void * arg) {
17         Isolate * isolate = static_cast < Isolate * >(arg);
18         HandleScope scope(isolate);
19         Local < Object > obj = Object: :New(isolate);
20         assert(!obj.IsEmpty()); // assert VM is still alive
21         assert(obj - >IsObject());
22         at_exit_cb1_called++;
23     }
24
25     static void at_exit_cb2(void * arg) {
26         assert(arg == static_cast < void * >(cookie));
27         at_exit_cb2_called++;
28     }
29
30     static void sanity_check(void * ) {
31         assert(at_exit_cb1_called == 1);
32         assert(at_exit_cb2_called == 2);
33     }
34
35     void init(Local < Object > exports) {
36         AtExit(sanity_check);
37         AtExit(at_exit_cb2, cookie);
38         AtExit(at_exit_cb2, cookie);
39         AtExit(at_exit_cb1, exports - >GetIsolate());
40     }
41
42     NODE_MODULE(addon, init);
43
44 } // namespace demo

解释:

1. 上面例子定义了4个AtExit函数:

1 void init(Local < Object > exports) {
2     AtExit(sanity_check);
3     AtExit(at_exit_cb2, cookie);
4     AtExit(at_exit_cb2, cookie);
5     AtExit(at_exit_cb1, exports - >GetIsolate());
6 }

根据LIFO特性,在时间循环之后,VM停机之前,会依次执行:

1 AtExit(at_exit_cb1, exports->GetIsolate());
2 AtExit(at_exit_cb2, cookie);
3 AtExit(at_exit_cb2, cookie);
4 AtExit(sanity_check);

sanity_check会检查at_exit_cb1和at_exit_cb2的调用次数:

1 assert(at_exit_cb1_called == 1);
2 assert(at_exit_cb2_called == 2);

测试:

1 // test.js
2 const addon = require(‘./build/Release/addon‘);
时间: 2024-10-12 22:48:47

(译+注解)node.js的C++扩展入门的相关文章

Node.js开发札记之一&#183;入门篇

前言: Node.js这个名字并不陌生.刚开始时,以为又是某个团体搞的JS类库.作为jQuery忠实追随者,当时还是比较关注这个异端的出现.后来发现,其实是服务器端的JS.用了J2EE那么多年了,没有心思再去搞这一套.还不如深入写下J2EE的架构什么的.而技术的革新的风暴还是席卷了整个IT界.鄙人再次了开启学习天赋. 环境搭建: 软件下载: 1. Node.js安装包 下载地址(详见官方博客:http://blog.nodejs.org 更新): http://nodejs.org/dist/v

【译】 Node.js v0.12的新特性 -- 性能优化

Performance Optimizations性能优化 原文: https://strongloop.com/strongblog/performance-node-js-v-0-12-whats-new/ January 21, 2014/in Community, Node.js v0.12, StrongNode /by Ben Noordhuis Node.js v0.12版本如此长的研发周期(9个月并且还在继续,目前为止最长的)使得核心团队和贡献者有足够的机会来介绍一些性能优化.本

【转】【译】Node.js 是什么?

著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处.作者:贾厂长链接:https://www.zhihu.com/question/33578075/answer/56951771来源:知乎 如果你去年注意过技术方面的新闻,我敢说你至少看到node.js不下一两次.那么问题来了“node.js是什么?”.有些人没准会告诉你“这是一种通过JavaScript语言开发web服务端的东西”.如果这种晦涩解释还没把你搞晕,你没准会接着问:“为什么我们要用node.js?”,别人一般会告诉

node.js(express)连接mongoDB入门指导

一.写在前面 人人都想成为全栈码农,作为一个web前端开发人员,通往全栈的简洁之路,貌似就是node.js了.前段时间学习了node.js,来谈谈新手如何快速的搭建自己的web服务,开启全栈之路. 二.安装node.js 接触过后端开发的人都知道,首先要安装服务.作为新手,肯定是选择最简单的可视化安装了(傻瓜式下一步,其它的方式等熟悉相关操作后自然就会了的),通过官网http://nodejs.org/dist/v0.6.1/node-v0.6.1.msi 下载电脑适配的安装包(这个是windo

【译】 Node.js v0.12的新特性 -- Cluster模式采用Round-Robin负载均衡

原文:https://strongloop.com/strongblog/whats-new-in-node-js-v0-12-cluster-round-robin-load-balancing Node.js v0.12的新特性 -- Cluster采用轮询调度算法来进行负载均衡 November 19, 2013 by Ben Noordhuis 欢迎来到由Node的核心提交者 Ben Noordhuis 和 Bert Belder撰写的系列博文的第一篇.本系列可能由7-8篇构成,主要涵盖

学习node.js的C++扩展

本想买本书,可是太贵,了一下作者可惜没有回应,不然也会去支持一下.于是自己baidu罗.先是从这个入手 安装好环境 https://github.com/nodejs/node-gyp#installation 主要是这一段,我用win10开发 弄完环境后,准备几个文件,参考了这个例子 https://www.cnblogs.com/andrewwang/p/9409876.html 这个例子,没有提到需要“node-gyp configure”命令,所以只适合取其代码部分,过程还需要看第一个地

vs.code调试node.js的C++扩展

其实也很简单 点击“Add Configration..”后,会在launch.json增加一个节点,稍调整两个位置 以上完了后,就能在cpp源码里加上自己的断点,执行debug调试我们的C++源代码了.记住这个“(Windows) Launch",下面一步会选择它.当然你也可以自己改另一个名字 点击“三角”程序就会跑起来,没有问题的话,程序会停在执行过程中遇到的第一个“断点”的位置. 有一点需要注意:在我们修改代码后,必须要执行一次build node-gyp build 如果我们已有多年使用

Ajax+Node.js前后端交互最佳入门实践(01)

1.Node.js简介 1.0.前后台数据交互流程 在web开发中,我们经常听说前端和后台,他们分别是做什么具体工作的呢?他们怎样交互的呢?我们得把这些基础的问题都搞明白了,才有一个大致的学习方向,首先,我们来看一张生活中几乎每个人都经历过的一个场景,如下图: 当你去餐馆吃饭的时候,坐下后服务员会带着一个菜单过来,问你需要点什么菜,这个时候你浏览了菜单上的菜,把想吃的菜告诉服务员,服务员把你点的菜拿到后台,后台根据你点的菜名,逐一完成,菜做完后叫服务员给你上菜,就这么一个场景其实和我们web开发

(转)heX——基于 HTML5 和 Node.JS 开发桌面应用

本文转载自:http://techblog.youdao.com/?p=685 简介:heX,一个允许你采用前端技术(HTML,CSS,JavaScript)开发桌面应用软件的跨平台解决方案.是你开发桌面应用的一种新的选择,意在解决传统桌面应用开发中繁琐的UI和交互开发工作,使其变的简单而高效.特别适合重UI,重交互的桌面应用软件. 欢迎关注http://hex.youdao.com  或微博 @youdao_hex. 一.项目背景 二.用heX开发桌面应用的优势 三.如何用heX开始一个桌面应