一直想要開一個博客,總結記錄一下自己學到的東西,今天終於動筆寫了第一篇,希望能夠堅持下去。
我的博客主要會分享一些自己最近學習的東西,主要是給自己看的,如果能幫到別人的話當然最好了。
----------------------我是華麗的分割線-------------------------------
實驗室最近正在做一個基於Node.js的項目,之前對Front End的知識了解很少,所以從JavaScript一點點學起慢慢熟悉。 我的主要任務是把一個已經寫好的C語言程序轉化為Node.js的Library可以隨時調用運行。
按照Node官方給的文檔,我從最基礎的Hello World開始,學習如何添加Addon,以下是結合官方的documentation我自己的總結。
Node.js Addons 是C/C++寫成的Objects,可以通過require()調用,用起來就像Node自帶的module一樣,主要用於提供介於Node.js和C/C++庫之間的接口。
實現Addons所需要的背景知識主要包括:V8 JavaScript Engine, Libuv, Node.js內部library, Node.js內部庫自帶一些C/C++ API可以供Addons使用。
從最簡單的開始,添加一個C++ Addon實現的功能等同於: module.exports.hello = function(){return 'world';}; 調用這個函數,可以返回字符串“world”;
Step1
首先需要通過運行命令’npm init‘創建一個 package.json文件的框架,用來記錄所需的配置文件
Step2
安裝 “NAN”。 NAN是一個介於 C++源程序,Node,和 V8 API之間的抽象層。NAN的存在是為了保證當V8的API更新后,之前的程序仍可以運行。 當V8的API更新后,只需更新NAN便可以保證已經寫好的程序可以繼續運行,避免了修改源代碼的問題。
通過運行‘npm install nan@latest --save'來安裝NAN,並將NAN作為 dependency存儲在package.json里。
Step3
所有的Addons 都會使用[node-gyp]編譯,在package.json中加入‘gypfile:true’來使能nodel-gyp編譯。 當 node-gyp被調用時,會尋找一個與‘package.json’在同一目錄下的‘binding.gyp’ file。 這個binding文件用YAML寫成,用於描述build的細節,比如源文件以及所需的dependencies。node-gyp會參考我們自己寫成的binding.gyp文件和一個common.gypi來進行編譯,common.gypi描述了node.js會用到的設置和可能的dependencies。 因為我們的代碼將會在node下編譯,所以必須要有common.gypi file用於描述node的環境設置。另外,我們還需要下載和我們正在運行的版本對應的tarball。(原文是it must also download the complete tarball of the particular release you are running,我也沒有很理解這句話,看懂的朋友希望指正)。
文檔中給出了簡單的binding文件:
{
"targets": [
{
"target_name": "hello",
"sources": [ "hello.cc" ],
"include_dirs": [
"<!(node -e \"require('nan')\")"
]
}
]
}
我們只要新建一個binding.gyp file並復制這段代碼就可以了。
這段代碼做了如下幾件事:1 命名目標Addon為“hello”,最終輸出的文件名將會是hello.node. 2 需要編譯的源文件是"hello.cc" 3 規定了編譯時[NAN]的路徑
Step4
編寫hello.cc的源代碼。
#include <nan.h>
using namespace v8;
NAN_METHOD(Method) {
NanScope();
NanReturnValue(String::New("world"));
}
void Init(Handle<Object> exports) {
exports->Set(NanSymbol("hello"), FunctionTemplate::New(Method)->GetFunction());
}
NODE_MODULE(hello, Init)
現在從下到上來分析這段代碼:
NODE_MODULE(hello,Init)定義了addon的entry-point, 參數“hello”必須與binding file中的target相同,第二個參數“Init“指向了要調用的函數。
void Init(Handle<Object> exports) {
exports->Set(NanSymbol("hello"), FunctionTemplate::New(Method)->GetFunction());
}
按照NODE_MODULE的定義,這一函數是addon實際的entry-point。 這一函數有兩個參數: ‘exports’ 與js文件中的‘module.exports'相同,第二個參數(本例中已經被忽略)是‘module’,類似js文件中的‘module’。一般情況下,我們會給exports attach一些屬性,但是我們也可以使用‘module’(第二個參數)來替換‘module’的exports屬性,這樣就只能 export a single thing, e.g. module.exports = function(){}.
本例中, 我們只需要attach一個“hello” property 在module.exports中。所以我們給V8 的一個‘Function’對象[SET]一個 V8’String‘ 屬性。 首先,我們使用 NanSymbol() 函數新建一個’symbol‘ 字符串(之后可以重復使用). 我們使用 V8 'FunctionTemplate' 把一個普通的 C++函數轉換為一個 V8-callable function. 在我們的例子中,這個‘C++函數’是Method function。
NAN_METHOD(Method) {
NanScope();
NanReturnValue(String::New("world"));
}
這里體現出了NAN的用處。 V8 API的改變使得 將C++ Addon 添加到不同版本的node變得很困難。 所以NAN提供了一個簡單的mapping,我們可以定義一個 ‘FunctionTemplate’可以接受的 V8 compatible function。 在最近的V8中,NAN_METHOD(Method) 可以擴展成 'void Method(const v8::FunctionCallbackInfo<v8::Value>& args)',這是一個 V8 callable function的標准signature。'args' 包括了 call information, 比如 JavaScript function的參數,設置返回值等。
NanScope() 定義了新建的‘handles’的生命周期。當我們在函數開始時使用NanScope時表示所有的 我們使用的V8 object存活的時間與該function相等。 如果沒有這句代碼,V8 Object將不會在function結束時被回收。
NanReturnValue設置了函數的返回值。在本例子中, 我們新建了一個簡單的“world” 字符串,這個字符串將會被暴露為標准的JavaScript String。
Step 5
編譯我們的Addon。 通過‘sudo npm install node-gyp -g' 安裝'node-gyp'。
運行‘node-gyp configure' 來設置build fixtures. 會生成一個 'Makefile' 文件
運行‘node-gyp build'啟動編譯過程。 或者可以使用'node-gyp rebuild' 將 ‘configure‘ & ’build‘和成一步。成功以后我們就有了一個可以使用的addon binary文件。Node可以載入並運行這個文件像載入普通.js module file 一樣。
Step 6
編寫JavaScript
var addon = require('./build/Release/hello.node');
console.log(addon.hello());
調用addon的hello()function,輸出“world”