(譯+註解)node.js的C++擴展入門

聲明:本文主要翻譯自node.js addons官方文檔。部分解釋爲做者本身添加。html

編程環境:node

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

本文將介紹node.js中編寫C++擴展的入門知識。編程

1. 基本知識介紹api

在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重複導出來被各類擴展使用。cookie

下面快速地來看一個實例。異步

2. 第一個例子Hellosocket

下面的例子是一個簡單的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使用一些靜態連接庫,好比V八、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');
相關文章
相關標籤/搜索