node.jsのterminfo utility初版
npmの流儀でモジュール化してgithubに登録してみた。
https://github.com/ousttrue/termutil
予定している機能が揃ったらnpmにも登録してみよう。
(追記)
登録してみた
http://npm.mape.me/
参考
node.jsのモジュール作り方メモ
とりあえず雛形
http://nodejs.jp/nodejs.org_ja/api/addons.html
に書いてあるとおり。
wscriptを作る
srcdir = '.' blddir = 'build' VERSION = '0.0.1' 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.target = 'cstr' # とりあえずcstrという名のモジュールを作ることにした。 obj.source = 'cstr.cpp' # ソース。cppは習慣
wafについては
http://d.hatena.ne.jp/tanakh/20100212
が詳しい。
moduleの登録
#include <v8.h> extern "C" void init (v8::Handle<v8::Object> target) { // targetがjavascriptの{}に相当する }
buildと実行
$ node-waf Project not configured (run 'waf configure' first) # 最初にビルドディレクトリで一回だけconfigureする $ node-waf configure Checking for program g++ or c++ : /usr/bin/g++ Checking for program cpp : /usr/bin/cpp Checking for program ar : /usr/bin/ar Checking for program ranlib : /usr/bin/ranlib Checking for g++ : ok Checking for node path : not found Checking for node prefix : ok /usr/local 'configure' finished successfully (0.631s) # configure済んだので改めてnode-waf $ node-waf Waf: Entering directory '/home/focke/work/node.js/cstr/build' [1/2] cxx: cstr.cpp -> build/default/cstr_1.o [2/2] cxx_link: build/default/cstr_1.o -> build/default/cstr.node Waf: Leaving directory '/home/focke/work/node.js/cstr/build' 'build' finished successfully (1.368s) $ node # ビルドしたモジュールを相対パスで読み込む > require('./build/default/cstr'); {} // 空のv8::Handle<v8::Object> target > process.exit(); $
スタート地点。
C++クラスをwrapする
wrapされるクラス。cstr.h
#include <vector> #include <algorithm> class CStr { std::vector<unsigned char> str_; public: CStr() { } CStr(const char *begin, const char *end) { std::copy(begin, end, std::back_inserter(str_)); } size_t length(){ str_.size(); } // 追記 char* begin(){ str_.begin(); } };
wrapするべく追記したcstr.cpp
#include <v8.h> #include <node.h> #include "cstr.h" #include <string.h> // CStrをnode.js向けにwrapする class v8_CStr : node::ObjectWrap { CStr str_; public: v8_CStr() { } v8_CStr(const char *begin, const char *end) : str_(begin, end) { } // 以降は、javascriptへの登録用のstatic method // Javascriptでのコンストラクタ static v8::Handle<v8::Value> New(const v8::Arguments& args) { if(args.Length()==0){ // instanceを作って, Thisをwrapする (new v8_CStr())->Wrap(args.This()); } else{ // 第1引数からchar*を取り出す char buf[1024]; // v8::ValueからV8::Stringを取り出す v8::Local<v8::String> str= v8::Local<v8::String>::Cast(args[0]); //int length=str->WriteAscii(buf, 0, 1024); int length=str->WriteUtf8(buf, 1024); const char *end=buf+length; while(*(end-1)=='\0'){ --end; } // instanceを作って, Thisをwrapする (new v8_CStr(buf, end))->Wrap(args.This()); } return args.This(); } // prototypeに登録する関数 static v8::Handle<v8::Value> ByteLength(const v8::Arguments& args) { // instanceからc++ポインタを取り出す v8_CStr *str=static_cast<v8_CStr*>( args.This()->GetPointerFromInternalField(0)); return v8::Integer::New(str->str_.length()); } }; extern "C" void init (v8::Handle<v8::Object> target) { // NODE_SET_PROTOTYPE_METHODで必要 using namespace v8; // JavascriptのObjectとしてのコンストラクタ v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(v8_CStr::New); // t関数をnewして作るインスタンスは中にpointerを1つ持ってますよ? t->InstanceTemplate()->SetInternalFieldCount(1); // CStr.prototype.byte_length=CStr::byte_length NODE_SET_PROTOTYPE_METHOD(t, "byte_length", v8_CStr::ByteLength); // target["CStr"]=t; target->Set(v8::String::NewSymbol("CStr"), t->GetFunction()); }
なるべくusing namespace無しで書いてみた。
実戦では、適当にファイルやスコープを分けて多少はusing namespaceすることになりそうだ。
Javascript objectの中にC++クラスへのポインタを埋める際に以下の3つがセットになるようだ。
// 定義 t->InstanceTemplate()->SetInternalFieldCount(1); // インスタンスに埋める(コンストラクタ) (new v8_CStr())->Wrap(args.This()); // インスタンスから掘り出す(メソッド実行時など) v8_CStr *str=static_cast<v8_CStr*>( args.This()->GetPointerFromInternalField(0));
あとは、Javascriptの仕様がわかっていればだいたい類推できる。たぶん。
test.js
var CSTR=require('./build/default/cstr'); var s=new CSTR.CStr('あいうえお'); process.stdout.write(s.byte_length()+'\n'); // -> 15 var s=new CSTR.CStr('abc'); process.stdout.write(s.byte_length()+'\n'); // -> 3
EventEmitterの継承
最後、EventEmitterの継承について。
cstr.cppで変えるところ
#include <node_events.h> // 新しいEvent static v8::Persistent<v8::String> delete_symbol; // 親クラスを変える class v8_CStr : node::EventEmitter { // デストラクタでイベント発動 ~v8_CStr() { // イベントの引数 v8::Local<v8::Value> values[] = { v8::String::New(str_.begin(), str_.length()) }; // node::EventEmitter::Emit // on(addEventListener)の第1引数: delete_symbol // Listerの引数の数: 1 // Listerの引数: values Emit(delete_symbol, 1, values); } }; extern "C" void init (v8::Handle<v8::Object> target) { // 省略 // event用のsymbol delete_symbol = NODE_PSYMBOL("delete"); // EventEmitterの継承 t->Inherit(node::EventEmitter::constructor_template); // 継承した後で、Setする // target["CStr"]=t; target->Set(v8::String::NewSymbol("CStr"), t->GetFunction()); }
test.js
var CSTR=require('./build/default/cstr'); var s1=new CSTR.CStr('あいうえお'); process.stdout.write(s1.byte_length()+'\n'); var s2=new CSTR.CStr('abc'); process.stdout.write(s2.byte_length()+'\n'); s1.on('delete', function(){ process.stdout.write("s1 deleted\n"); }); s2.on('delete', function(){ process.stdout.write("s2 deleted\n"); }); // GCが発生するまで待つ setTimeout(function(){}, 10000);
node.js向けに作る場合は、最初からEventEmitterを継承して作っておけばよいのではないか。
EventEmitterまで作ってみたのだが、node.jsの構成はGlibに似ている。
GObjectがEventEmitterに、MainLoopがlibevに相当するわけですな。