# Addons插件
Addons插件就是動態連接庫。它類似膠水,將c、c++和Node粘貼起來。它的API(目前來說)相當復雜,涉及到了幾個類庫的知識。
- V8 JavaScript引擎,一個 C++ 類庫. 用于和JavaScript進行交互的接口。 創建對象, 調用函數等. 文檔大部分在這里: `v8.h` 頭文件 (`deps/v8/include/v8.h`在Node源代碼目錄里), 也有可用的線上文檔 [線上](http://izs.me/v8-docs/main.html). (譯者:想要學習c++的addons插件編寫,必須先了解v8的接口)
- [libuv](https://github.com/joyent/libuv), C語言編寫的事件循環類庫。任何時候需要等待一個文件描述符變為可讀狀態,等待一個定時器,或者等待一個接受信號都需要使用libuv類庫的接口。也就是說,如果你執行任何I/O操作,libuv類庫將會被用到。
- 內部 Node 類庫.最重要的接口就是 `node::ObjectWrap` 類,這個類你應該是最可能想要派生的。
- 其他.請參閱 `deps/` 獲得更多可用類庫。
Node 靜態編譯了所有依賴到它的可執行文件中去了。當編譯你的模塊時,你不必擔心無法連接上述那些類庫。 (譯者:換而言之,你在編譯自己的addons插件時,只管在頭部 #include <uv.h>,不必在binding.gyp中聲明)
下面所有的例子都可以下載到: [下載](https://github.com/rvagg/node-addon-examples) 這或許能成為你學習和創作自己addon插件的起點。
### Hello world(世界你好)
作為開始,讓我們用編寫一個小的addon插件,這個addon插件的c++代碼相當于下面的JavaScript代碼。
~~~
module.exports.hello = function() { return 'world'; };
~~~
首先我們創建一個 `hello.cc`文件:
~~~
NODE_MODULE(hello, init)//譯者:將addon插件名hello和上述init函數關聯輸出
~~~
注意所有Node的addons插件都必須輸出一個初始化函數:
~~~
void Initialize (Handle<Object> exports);
NODE_MODULE(module_name, Initialize)
~~~
在`NODE_MODULE`之后沒有分號,因為它不是一個函數(請參閱`node.h`)
這個`module_name`(模塊名)需要和最后編譯生成的2進制文件名(減去.node后綴名)相同。
源代碼需要生成在`hello.node`,這個2進制addon插件中。 需要做到這些,我們要創建一個名為`binding.gyp`的文件,它描述了創建這個模塊的配置,并且它的格式是類似JSON的。 文件將被命令:[node-gyp](https://github.com/TooTallNate/node-gyp) 編譯。
~~~
{
"targets": [
{
"target_name": "hello", //譯者:addon插件名,注意這里的名字必需和上面NODE_MODULE中的一致
"sources": [ "hello.cc" ] //譯者:這是需要編譯的源文件
}
]
}
~~~
下一步是根據當前的操作系統平臺,利用`node-gyp configure`命令,生成合適的項目文件。
現在你會有一個`Makefile` (在Unix平臺) 或者一個 `vcxproj` file (在Windows上),它們都在`build/` 文件夾中. 然后執行命令 `node-gyp build`進行編譯。 (譯者:當然你可以執行 `node-gyp rebuild`一步搞定)
現在你已經有了編譯好的 `.node` 文件了,這個編譯好的綁定文件會在目錄 `build/Release/`下
現在你可以使用這個2進制addon插件在Node項目`hello.js` 中了,通過指明`require`這個剛剛創建的`hello.node`模塊使用它。
~~~
console.log(addon.hello()); // 'world'
~~~
請閱讀下面的內容獲得更多詳情或者訪問[https://github.com/arturadib/node-qt](https://github.com/arturadib/node-qt)獲取一個生產環境的例子。
### Addon patterns(插件方式)
下面是一些幫助你開始編寫addon插件的方式。參考這個在線的[v8 手冊](http://izs.me/v8-docs/main.html)用來幫助你調用各種v8接口, 然后是v8的 [嵌入式開發向導](http://code.google.com/apis/v8/embed.html) ,解釋幾個概念,如 handles, scopes,function templates等。
為了能跑起來這些例子,你必須用 `node-gyp` 來編譯他們。 創建一個`binding.gyp` 文件:
~~~
{
"targets": [
{
"target_name": "addon",
"sources": [ "addon.cc" ]
}
]
}
~~~
事實上可以有多個 `.cc` 文件, 就簡單的在 `sources` 數組里加上即可,例子:
~~~
"sources": ["addon.cc", "myexample.cc"]
~~~
現在你有了你的`binding.gyp`文件了,你可要開始執行configure 和 build 命令構建你的addon插件了
~~~
$ node-gyp configure build
~~~
### Function arguments(函數參數)
下面的部分說明了如何從JavaScript的函數調用獲得參數然后返回一個值。這是主要的內容并且僅需要源代碼`addon.cc`。
~~~
NODE_MODULE(addon, Init)
~~~
你可以使用下面的JavaScript代碼片段來測試它
~~~
console.log( 'This should be eight:', addon.add(3,5) );
~~~
### Callbacks(回調)
你可以傳遞JavaScript functions 到一個C++ function 并且執行他們,這里是 `addon.cc`文件:
~~~
NODE_MODULE(addon, Init)
~~~
注意這個例子對`Init()`使用了兩個參數,將完整的 `module` 對象作為第二個參數傳入。這允許addon插件完全的重寫 `exports`,這樣就可以用一個函數代替多個函數作為`exports`的屬性了。
你可以使用下面的JavaScript代碼片段來測試它
~~~
addon(function(msg){
console.log(msg); // 'hello world'
});
~~~
### Object factory(對象工廠)
在這個`addon.cc`文件里用一個c++函數,你可以創建并且返回一個新的對象,這個新的對象擁有一個msg的屬性,它的值是通過createObject()方法傳入的
~~~
NODE_MODULE(addon, Init)
~~~
在js中測試如下:
~~~
var obj1 = addon('hello');
var obj2 = addon('world');
console.log(obj1.msg+' '+obj2.msg); // 'hello world'
~~~
### Function factory(函數工廠)
這次將展示如何創建并返回一個JavaScript function函數,這個函數其實是通過c++包裝的。
~~~
NODE_MODULE(addon, Init)
~~~
測試它:
~~~
var fn = addon();
console.log(fn()); // 'hello world'
~~~
### Wrapping C++ objects(包裝c++對象)
這里將創建一個被c++包裹的對象或類`MyObject`,它是可以在JavaScript中通過`new`操作符實例化的。 首先我們要準備主要的模塊文件`addon.cc`:
~~~
NODE_MODULE(addon, InitAll)
~~~
然后在`myobject.h`文件中創建你的包裝類,它繼承自 `node::ObjectWrap`:
~~~
#endif
~~~
在文件 `myobject.cc` 可以實施各種你想要暴露給js的方法。 這里我們暴露方法名為 `plusOne`給就是,它表示將構造函數的屬性加1.
~~~
return scope.Close(Number::New(obj->counter_));
}
~~~
測試它:
~~~
var obj = new addon.MyObject(10);
console.log( obj.plusOne() ); // 11
console.log( obj.plusOne() ); // 12
console.log( obj.plusOne() ); // 13
~~~
### Factory of wrapped objects(工廠包裝對象)
這是非常有用的,當你想創建原生的JavaScript對象時,又不想明確的使用JavaScript的`new`操作符。
~~~
var obj = addon.createObject();
// 用上面的方式代替下面的:
// var obj = new addon.Object();
~~~
讓我們注冊在 `addon.cc` 文件中注冊`createObject`方法:
~~~
NODE_MODULE(addon, InitAll)
~~~
在`myobject.h`文件中,我們現在介紹靜態方法NewInstance`,它能夠實例化對象(舉個例子,它的工作就像是 在JavaScript中的`new` 操作符。)
~~~
#endif
~~~
這里的處理方式和上面的 `myobject.cc`很像:
~~~
return scope.Close(Number::New(obj->counter_));
}
~~~
測試它:
~~~
var obj2 = createObject(20);
console.log( obj2.plusOne() ); // 21
console.log( obj2.plusOne() ); // 22
console.log( obj2.plusOne() ); // 23
~~~
### Passing wrapped objects around(傳遞包裝的對象)
除了包裝和返回c++對象以外,你可以傳遞他們并且通過Node的`node::ObjectWrap::Unwrap`幫助函數解包裝他們。 在下面的`addon.cc` 文件中,我們介紹了一個函數`add()`,它能夠獲取2個`MyObject`對象。
~~~
NODE_MODULE(addon, InitAll)
~~~
為了使事情變得有趣,我們在 `myobject.h` 采用一個公共的方法,所以我們能夠在unwrapping解包裝對象之后使用私有成員的值。
~~~
#endif
~~~
`myobject.cc`文件的處理方式和前面類似
~~~
return scope.Close(instance);
}
~~~
測試它:
~~~
var obj1 = addon.createObject(10);
var obj2 = addon.createObject(20);
var result = addon.add(obj1, obj2);
~~~
~~~
console.log(result); // 30
~~~