來源:https://www.cloudkick.com/blog/2010/aug/23/writing-nodejs-native-extensions/ , http://lupomontero.e-noise.com/blog/writing-node-js-modules-in-cpp
程式碼:https://github.com/pquerna/node-extension-examples/tree/master/helloworld
由於 Node.js 是採用 v8 作為引擎,所以要擴充他的物件與函式,也算是寫 v8 的模組。
因為我也是 c++ noobie…,所以用初學者的觀點介紹一下這個最簡單的模組好了。以下有範例與說明
模組 – 函式
#include v8.h #include node.h using namespace node; using namespace v8; static Handle foo(const Arguments& args) { return String::New("Hello World"); } extern "C" { static void init(Handle<Object> target) { NODE_SET_METHOD(target, "foo", foo); } NODE_MODULE(foo, init); }
最上面要 include v8 跟 node 的 header 跟 namespace …(廢話)。
然後這邊就是實作一個可以在 node.js 被呼叫的函式叫做 foo,引數也是從 js 被丟過來的,
結果回傳一個字串 “Hello World”,猜測這邊的 Handel 應該是一個多型,丟什麼都可以被轉回 js 的 variable。
這樣就可以動囉~只是要在設定 js 跟 c++ function 的銜接點,在這邊就是 NODE_MODULE 跟他註冊這個模組叫什麼名字,這模組內有什麼函式(NODE_SET_METHOD)。
接著在寫 wscript 編譯設定~還不太懂是在幹麼的…類似 automake,ant 之類的編譯工具設定檔吧,注意 source 跟 target 應該就 ok 了
def set_options(opt): opt.tool_options("compiler_cxx") def configure(conf): conf.check_tool("compiler_cxx") conf.check_tool("node_addon") def build(bld): obj = bld.new_task_gen("cxx", "shlib", "node_addon") obj.cxxflags = ["-g", "-D_FILE_OFFSET_BITS=64", "-D_LARGEFILE_SOURCE", "-Wall"] obj.target = "foo" obj.source = "cpphello.cc" [/code] ./node-waf configuration ; ./code-waf build
應該就會產生模組在 build/default/foo.node。
接著可以寫一個 js 來測試他。這樣就是實作一個包含函式的模組囉。
var Foo = require('./foo'); console.log(Foo.foo()); // output : Hello World
<strong>模組 - 物件</strong>
class HelloWorld: ObjectWrap { private: int m_count; public: static Persistent s_ct; static void Init(Handle<Object> target) { HandleScope scope; Local<FunctionTemplate> t = FunctionTemplate::New(New); s_ct = Persistent<FunctionTemplate>::New(t); s_ct->InstanceTemplate()->SetInternalFieldCount(1); s_ct->SetClassName(String::NewSymbol("HelloWorld")); NODE_SET_PROTOTYPE_METHOD(s_ct, "hello", Hello); target->Set(String::NewSymbol("HelloWorld"), s_ct->GetFunction()); } HelloWorld() : m_count(0) { } ~HelloWorld() { } static Handle<Value> New(const Arguments& args) { HandleScope scope; HelloWorld* hw = new HelloWorld(); hw->Wrap(args.This()); return args.This(); } static Handle<Value> Hello(const Arguments& args) { HandleScope scope; HelloWorld* hw = ObjectWrap::Unwrap<HelloWorld>(args.This()); hw->m_count++; Local<String> result = String::New("Hello World"); return scope.Close(result); } }; extern "C" { static void init (Handle<Object> target) { HelloWorld::Init(target); } NODE_MODULE(helloworld, init); }
這是一個模組包含一個 JS 物件與物件函式,物件是 helloworld 物件裡面有一個 hello 的函式會回傳 HelloWorld,且每次 hello() 被呼叫都會遞增 m_count。
裡面有幾種型態可以注意,
Persistent 是除非你去移除他,不然他會一直存在記憶體內。
Local 應該就是會自動清除的區域變數囉!
以此推論,在這邊配置一個 JS 物件時,要先用 Local 的變數產生,再將他轉型成 Persistent 來保存他。
然後 target 指的應該是模組本身這個已經建立的物件,這要跟 js 的觀念做個整合。在 node.js 裡面你寫一個 module 他會將該 module 內所有 exports 的變數都綁在一個物件上,就像如下範例。
// a.js exports.hello = function() { console.log('Hello'); } // test.js var a = require('./a.js') ; a.hello();
如果不寫成模組你就好像只是做了下面這件事而已,因此在這邊有一個 a = {} 配置一個物件空間出來給模組用,類似 namespace 的功能。
a = {}; a.hello = function() { console.log('Hello'); } a.hello();
對應到原本的 C++ Module , a 就是從頭到尾一脈相傳的 Handle<Object> target 物件囉。
所以要把 s_ct->GetFunction() 這個物件綁在這上面就用 set 指定了名稱~
debug
因為我 C++ 開發經驗不足,模組實際運行起來常遇到[記憶體區段]錯誤,應該是變數生命週期的問題。
所以順便學一下用 gdb 看一下錯誤,在編譯的時候(wscript)內記得要加 -g 來紀錄除錯訊息,
$ gdb node $ set args test.js (gdb) run Program received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x40043940 (LWP 29589)] 0x0000000000554b00 in v8::Value::IsArray() const () (gdb) bt #0 0x0000000000554b00 in v8::Value::IsArray() const () #1 0x0000000000506d56 in node::FatalException (try_catch=...) at ../src/node.cc:1728 #2 0x00002aaacf122ee2 in SSH::EIO_READ (req=0xcbc620) at ../ssh.cc:376 #3 0x00000000005348ab in eio_execute (thr_arg=) at ../deps/libeio/eio.c:1826 #4 etp_proc (thr_arg=) at ../deps/libeio/eio.c:1635 #5 0x0000003253c0673d in start_thread () from /lib64/libpthread.so.0 #6 0x00000032530d44bd in clone () from /lib64/libc.so.6
或許就能推論出是什麼原因出錯了=_=
相關資源:
- v8 module http://code.google.com/apis/v8/embed.html
- v8 module 基礎概念與物件(翻譯) http://www.cppblog.com/corelito/category/8535.html
- node-crashing-async-buf https://github.com/pkrumins/node-crashing-async-buf
- gdb http://www.cis.nctu.edu.tw/~is93007/acd.htm
- node.js 作者寫的範例 eio_custom https://github.com/isaacs/node-async-simple
- node-ssh https://github.com/substack/node-ssh lib-ssh binding
- Library/V8/Tutorial http://www.athile.net/library/wiki/index.php?title=Library/V8/Tutorial