- 引数+返り値のオーソドックスな関数呼び出し
- 新しいオブジェクト(連想配列)を返す
- 処理の途中でコールバックを呼び出す
- 独自のC++クラスを使う
- 独自のC++クラスにコールバックを設定して任意のタイミングで呼ぶ
- libuvで非同期処理させる
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define BUILDING_NODE_EXTENSION | |
#include <node.h> | |
#include <unistd.h> | |
// 趣味。特にnamespaceを切る必要はない | |
namespace addon { | |
// ---------------------------------------------------------------------------- | |
// 引数を受け取って、普通に結果を返すだけの関数 | |
v8::Handle<v8::Value> Add(const v8::Arguments& args) { | |
v8::HandleScope scope; | |
// 引数はv8::Argumentsにまとめられている | |
// 引数の個数を見る時はargs.Length () | |
if (args.Length() < 2) { | |
// ThrowExceptionだがここで例外を投げて抜けるわけではない。 | |
// 例外のキューに入るイメージみたい | |
v8::ThrowException(v8::Exception::TypeError(v8::String::New("Wrong number of arguments"))); | |
// 値を返さない時はUndefinedを返すのがお作法らしい | |
return scope.Close(v8::Undefined()); | |
} | |
// 数値や文字列などはC++標準ではなく、それぞれv8独自の型を使う | |
// 型はわからないので、IsNumber()などを使って型を確認する | |
if (!args[0]->IsNumber() || !args[1]->IsNumber()) { | |
// 期待する型じゃなかったらException投げる | |
v8::ThrowException(v8::Exception::TypeError(v8::String::New("Wrong arguments"))); | |
return scope.Close(v8::Undefined()); | |
} | |
// 足し算するだけ | |
// 返す値もv8の型にあわせる | |
v8::Local<v8::Number> num = v8::Number::New(args[0]->NumberValue() + | |
args[1]->NumberValue()); | |
return scope.Close(num); | |
} | |
// ---------------------------------------------------------------------------- | |
// 新しいオブジェクト(連想配列)を生成して返す | |
v8::Handle<v8::Value> CreateObject(const v8::Arguments& args) { | |
v8::HandleScope scope; | |
// v8::Object::New ()で作成する。 | |
v8::Local<v8::Object> obj = v8::Object::New(); | |
// Setを使う。連想配列のキーはNewSymbolで生成する | |
obj->Set(v8::String::NewSymbol("msg"), args[0]->ToString()); | |
return scope.Close(obj); | |
} | |
// ---------------------------------------------------------------------------- | |
// 処理が終わったらコールバックを呼び出す関数 | |
v8::Handle<v8::Value> RunCallback(const v8::Arguments& args) { | |
v8::HandleScope scope; | |
// 本当はちゃんとargs[0]が関数かどうかチェックする必要あり | |
// v8::Functionにキャストする | |
v8::Local<v8::Function> cb = v8::Local<v8::Function>::Cast(args[0]); | |
// コールバックに渡す引数を作成する | |
const unsigned argc = 1; | |
v8::Local<v8::Value> argv[argc] = { | |
v8::Local<v8::Value>::New(v8::String::New("hello world")) | |
}; | |
// コールバック呼び出し | |
cb->Call(v8::Context::GetCurrent()->Global(), argc, argv); | |
return scope.Close(v8::Undefined()); | |
} | |
// ---------------------------------------------------------------------------- | |
// 独自クラスを使う | |
// node::ObjectWrapを継承する | |
class Blue : public node::ObjectWrap { | |
public: | |
// クラス名や関数名を登録するため、初期化関数を静的に用意しておく | |
static void init (v8::Handle<v8::Object> target); | |
private: | |
Blue () : count_(0) {} | |
~Blue () {} | |
// v8から呼び出される関数はすべて静的関数にする | |
// 引数const v8::Argumentsに固定 | |
static v8::Handle<v8::Value> New (const v8::Arguments &args); | |
static v8::Handle<v8::Value> Five (const v8::Arguments &args); | |
static v8::Handle<v8::Value> Count (const v8::Arguments &args); | |
int count_; | |
}; | |
// 初期化関数。v8のプロセスで一度しか呼び出されない(はず) | |
void Blue::init (v8::Handle<v8::Object> target) { | |
// FunctionTemplateを作成し、自前のオブジェクト生成関数New (newではない)を設定 | |
v8::Local<v8::FunctionTemplate> tmpl = v8::FunctionTemplate::New(New); | |
// クラス名決める | |
tmpl->SetClassName (v8::String::NewSymbol ("blue")); | |
tmpl->InstanceTemplate ()->SetInternalFieldCount (1); | |
// メンバ関数も名前とFunctionで登録 | |
tmpl->PrototypeTemplate ()->Set (v8::String::NewSymbol ("Five"), | |
v8::FunctionTemplate::New (Five)->GetFunction ()); | |
tmpl->PrototypeTemplate ()->Set (v8::String::NewSymbol ("Count"), | |
v8::FunctionTemplate::New (Count)->GetFunction ()); | |
// 最後にクラス生成の関数を登録 | |
// 多分、ここのシンボル名とSetClassNameの名前は変えられると思うが、 | |
// 混乱すると思うのであまりオススメできない&未検証 | |
target->Set (v8::String::NewSymbol ("blue"), | |
v8::Persistent<v8::Function>::New(tmpl->GetFunction ())); | |
} | |
// クラス生成関数 | |
v8::Handle<v8::Value> Blue::New (const v8::Arguments &args) { | |
v8::HandleScope scope; | |
// ここだけは普通にnew | |
Blue * obj = new Blue (); | |
// これを呼び出す必要がある | |
obj->Wrap (args.This ()); | |
return args.This (); | |
} | |
v8::Handle<v8::Value> Blue::Five (const v8::Arguments &args) { | |
v8::HandleScope scope; | |
return scope.Close (v8::Number::New (5)); | |
} | |
v8::Handle<v8::Value> Blue::Count (const v8::Arguments &args){ | |
v8::HandleScope scope; | |
// node::ObjectWrap::Unwrap <クラス名> (args.This ()) することで、 | |
// インスタンスを取り出すことができる | |
Blue * ao = node::ObjectWrap::Unwrap <Blue> (args.This ()); | |
// 後は好きにごにょごにょいじる | |
ao->count_ += 1; | |
return scope.Close (v8::Number::New (ao->count_)); | |
} | |
// ---------------------------------------------------------------------------- | |
// 任意のタイミング(関数の終了時などではない)でコールバックを呼び出す | |
class Orange : public node::ObjectWrap { | |
public: | |
static void init (v8::Handle<v8::Object> target); | |
private: | |
static v8::Handle<v8::Value> New (const v8::Arguments &args); | |
static v8::Handle<v8::Value> SetCallback (const v8::Arguments & args); | |
static v8::Handle<v8::Value> RunCallback (const v8::Arguments & args); | |
// v8::Persistentでメンバ変数を用意するのが重要 | |
v8::Persistent<v8::Function> callback_; | |
}; | |
void Orange::init (v8::Handle<v8::Object> target) { | |
v8::Local<v8::FunctionTemplate> tmpl = v8::FunctionTemplate::New(New); | |
tmpl->SetClassName (v8::String::NewSymbol ("orange")); | |
tmpl->InstanceTemplate ()->SetInternalFieldCount (1); | |
tmpl->PrototypeTemplate ()->Set (v8::String::NewSymbol ("set_cb"), | |
v8::FunctionTemplate::New (SetCallback)->GetFunction ()); | |
tmpl->PrototypeTemplate ()->Set (v8::String::NewSymbol ("run_cb"), | |
v8::FunctionTemplate::New (RunCallback)->GetFunction ()); | |
target->Set (v8::String::NewSymbol ("orange"), | |
v8::Persistent<v8::Function>::New(tmpl->GetFunction ())); | |
} | |
v8::Handle<v8::Value> Orange::New (const v8::Arguments &args) { | |
v8::HandleScope scope; | |
Orange * obj = new Orange (); | |
obj->Wrap (args.This ()); | |
return args.This (); | |
} | |
v8::Handle<v8::Value> Orange::SetCallback (const v8::Arguments & args) { | |
v8::HandleScope scope; | |
Orange * orange = node::ObjectWrap::Unwrap <Orange> (args.This ()); | |
if (args.Length () != 1 || !args[0]->IsFunction ()) { | |
v8::ThrowException (v8::Exception::TypeError (v8::String::New ("Invalid argument"))); | |
return scope.Close (v8::Undefined ()); | |
} | |
// v8::Persistent<v8::Function>::New でコールバック関数を保持するインスタンスを | |
// 生成しなければならない | |
orange->callback_ = | |
v8::Persistent<v8::Function>::New (v8::Local<v8::Function>::Cast(args[0])); | |
return scope.Close(v8::Undefined()); | |
} | |
v8::Handle<v8::Value> Orange::RunCallback (const v8::Arguments & args) { | |
v8::HandleScope scope; | |
Orange * orange = node::ObjectWrap::Unwrap <Orange> (args.This ()); | |
v8::Local<v8::Value> argv[1]; | |
argv[0] = v8::String::New ("scar"); | |
// コールバック関数を呼び出す時は、もう一度v8::Localに | |
v8::Local<v8::Function> cb = v8::Local<v8::Function>::New(orange->callback_); | |
// コールバック! | |
cb->Call (v8::Context::GetCurrent ()->Global (), 1, argv); | |
return scope.Close(v8::Undefined()); | |
} | |
// ---------------------------------------------------------------------------- | |
// libuvの機能を使った非同期処理 | |
// データをやりとりするためのクラス | |
class Alice { | |
public: | |
v8::Persistent<v8::Function> callback_; | |
long long res_; | |
}; | |
// 処理をする関数 | |
void DoTask (uv_work_t *req) { | |
// 適当になんかやらせる | |
long long a = 1, p, q; | |
p = q = a; | |
for (long long n = 0; n < 10000000000; n++) { | |
a = p + q; | |
q = p; | |
p = a; | |
} | |
// req->dataをキャストすると、そのまま受け渡したインスタンスが見える | |
Alice * k = static_cast<Alice *> (req->data); | |
k->res_ = a; | |
} | |
// 処理終了後の後始末関数 | |
void FinishTask (uv_work_t *req) { | |
v8::HandleScope scope; | |
Alice * a = static_cast<Alice *> (req->data); | |
v8::Handle<v8::Value> argv[1]; | |
// 処理結果を受け取る | |
argv[0] = v8::Integer::New (a->res_); | |
v8::TryCatch try_catch; | |
a->callback_->Call(v8::Context::GetCurrent()->Global(), 1, argv); | |
// お片づけ | |
a->callback_.Dispose(); | |
delete a; | |
delete req; | |
if (try_catch.HasCaught()) | |
node::FatalException(try_catch); | |
} | |
// 処理開始(この関数が呼び出される) | |
v8::Handle<v8::Value> RunTask (const v8::Arguments& args) { | |
v8::HandleScope scope; | |
// uv_work_tを生成 | |
uv_work_t *req = new uv_work_t; | |
// 結果をやりとりするためのクラスを生成 | |
Alice * a = new Alice (); | |
// コールバック登録 | |
a->callback_ = v8::Persistent<v8::Function>::New(v8::Local<v8::Function>::Cast(args[0])); | |
req->data = a; | |
// uv_queue_work()でキューに入れる。 | |
uv_queue_work(uv_default_loop(), req, DoTask, FinishTask); | |
return scope.Close (v8::Undefined()); | |
} | |
// == Initializer ======================================================== | |
void Init(v8::Handle<v8::Object> target) { | |
// targetに関数のシンボル(JavaScript側から呼び出すときの名前)と関数テンプレート化された | |
// 関数を入れ込む | |
target->Set (v8::String::NewSymbol("add"), | |
v8::FunctionTemplate::New(Add)->GetFunction()); | |
target->Set (v8::String::NewSymbol("createObj"), | |
v8::FunctionTemplate::New(CreateObject)->GetFunction ()); | |
target->Set (v8::String::NewSymbol("runCallback"), | |
v8::FunctionTemplate::New(RunCallback)->GetFunction()); | |
target->Set (v8::String::NewSymbol("async_task"), | |
v8::FunctionTemplate::New(RunTask)->GetFunction()); | |
Blue::init (target); | |
Orange::init (target); | |
} | |
// 初期化。"addon"という名前のアドオンになる | |
// 二番目の引数は初期化 | |
NODE_MODULE(addon, Init); | |
} | |