用 nan 寫 node native extension
c9s 前輩在 5 月 12 號開始寫一個叫做 r3 ,飛快的 url router 。隨後掀起了幫 r3 寫不同語言 binding 的熱潮。到今天( 6 月 23 日)已經有 Perl, Python, Haskell, Vala, node.js 版,甚至還有個叫做 pathing ,受 r3 啟發,純 JavaScript 的實作。
5 月 22 日,自己推坑自己,寫 node.js binding :
19:43:51 \
意圖使人學寫 node extension...
隨即被 au 大長輩推坑 nan ,
19:54:33 \
趁 nan 推出新版,現在學正是時候 XD
幾天後又推這個 issue node-webworker-threads#16 ,沒想到還真的解了。
18:42:02 \
搞清楚了教我 XD https://github.com/audreyt/node-webworker-threads/issues/16 懸而未決久矣
(沒能力教啊,誠惶誠恐,只好發願寫文章分享)
途中感謝 au, c9s, C, godfat 等人協助, happy hacking XD
這篇心得將分成兩部份,前面講講怎麼用 nan 寫 node extension ,後面講講為啥在 node 0.11.x 寫 extension 那麼麻煩。
node-libr3
以前就聽說可以用 C/C++ 寫 node extension ,但除了過去在學校的日子外,很久很久沒寫 C/C++ ,加上自己算不上是個好 C programmer ,一直將這塊當成黑魔法。
r3 的 APIs 可以說是靠 C 寫 OO ,例如 r3_tree_create
和 r3_tree_free
負責處理 struct 的生成跟消滅, r3_tree_*
則用來操作 struct 。所以一開始想把 C functions 和 JavaScript functions 一一對應,那時還不知道 node-ffi 的存在,只好硬幹。
Handle\ , Local\ and Persistent\
HandleScope 是用來管理 Handle ,是 JS 值的容器,分兩種, Local\
如果只單純將 C 和 JavaScript 對應起來,那最大的問題是:「我該什麼時候呼叫 r3_tree_free
呢?」,在 JS 這邊並沒有 destructor ,而 GC 又不被控制。沒辦法只好放棄一一對應,在 C++ 那邊處理這些事情。
JS 與 C++ ,兩種不同的 OOP paradigm
JS 並沒有 class ,每個 instance 以一個 function 作為 constructor 生成,新的 instance 經過 constructor 把值設定好。在 C/C++ 世界為了配合這點,至少得產生 ObjectTemplate
再生出 Object
想知道更多資訊,請見 這篇文章 ,還提到了 FunctionTemplate
。
// nan 把 function 的 arguments 包起來了。
NAN_METHOD(treeConstructor) {
if (!args.IsConstructCall()) {
NanThrowError("Cannot call Tree constructor as function");
}
// nan 把 isolate 還有 handle_scope 也包起來了。
NanScope();
// 生出非 V8 的 instance 。
int capacity = args[0]->Uint32Value();
r3::node *n = r3::r3_tree_create(capacity);
//std::cout << "r3_tree_create(" << capacity << ");" << std::endl;
// 設計好可以放一個東西的 Object 。
Handle<ObjectTemplate> tree_template = ObjectTemplate::New();
tree_template->SetInternalFieldCount(1);
// 生出該 Object ,把非 V8 的 node pointer 放進去。
Local<Object> instance = tree_template->NewInstance();
instance->SetInternalField(0, NanNew<External>(n));
// 把 methods 接上去,可以說是:
// this.insert = [C function];
// 這樣吧。
instance->Set(NanNew<String>("insert"),
NanNew<FunctionTemplate>(treeInsertPath)->GetFunction());
instance->Set(NanNew<String>("insertRoute"),
NanNew<FunctionTemplate>(treeInsertRoute)->GetFunction());
instance->Set(NanNew<String>("compile"),
NanNew<FunctionTemplate>(treeCompile)->GetFunction());
instance->Set(NanNew<String>("match"),
NanNew<FunctionTemplate>(treeMatch)->GetFunction());
instance->Set(NanNew<String>("matchRoute"),
NanNew<FunctionTemplate>(treeMatchRoute)->GetFunction());
// 這個是被 nan 包裝起來,產生 weak persistent 的方法,下一段再好好說明。
NanMakeWeakPersistent(instance, n, &treeCleanUp);
// nan 把 handle_scope.Close() 也包起來了。
NanReturnValue(instance);
}
什麼時候該 free/delete
Persistent\
// object 是將被清掉的值, parameter 則是在將一個 persistent 容器設為 weak 時提供的值,
// 要放啥都可以(void *),這邊用來傳遞該被刪除的 r3 node pointer 。
void cleanUp(Persistent<Value> object, void *parameter) {
std::cout << "r3_tree_free() " << std::endl;
r3::node *n = static_cast<r3::node *>(parameter);
r3::r3_tree_free(n);
object.Dispose();
object.Clear();
}
NAN_METHOD(constructor) {
if (!args.IsConstructCall()) {
return ThrowException(String::New("Cannot call constructor as function"));
}
NanScope();
int capacity = args[0]->Uint32Value();
r3::node *n = r3::r3_tree_create(capacity);
std::cout << "r3_tree_create(" << capacity << ");" << std::endl;
Handle<ObjectTemplate> r3_template = ObjectTemplate::New();
r3_template->SetInternalFieldCount(1);
Persistent<External> external_n = Persistent<External>::New(External::New(n));
// 第一個參數會變成 cleanUp 的 parameter 。
external_n.MakeWeak(n, cleanUp);
// 把包著 r3 node pointer 的容器存起來給別的 method 用。
Local<Object> instance = r3_template->NewInstance();
instance->SetInternalField(0, external_n);
NanReturnValue(instance);
}
Persistent::MakeWeak
那部份可以看出不經過 nan 要寫 extension 也不複雜,但 node 0.11.13 剛好遇到 v8 升級,許多 APIs 都變了,靠 nan 幫忙做掉這些問題會比自己手動升級輕鬆。