node.js c++ module

來源: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]
1

./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

或許就能推論出是什麼原因出錯了=_=

相關資源:

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *