diff options
author | Artur Adib <arturadib@gmail.com> | 2012-01-19 19:18:15 +0400 |
---|---|---|
committer | Ben Noordhuis <info@bnoordhuis.nl> | 2012-01-19 19:31:36 +0400 |
commit | e282c0a0813dd700cc6d9862e27eba448f43b1c9 (patch) | |
tree | 2e877b26265893f611afcbbb6896976409d33855 /doc | |
parent | f7b612550a8716b0266a83a86bad060e2688eb1b (diff) |
docs: document common add-on design patterns
Diffstat (limited to 'doc')
-rw-r--r-- | doc/api/addons.markdown | 547 |
1 files changed, 542 insertions, 5 deletions
diff --git a/doc/api/addons.markdown b/doc/api/addons.markdown index 6f99f8be48e..4527b24063b 100644 --- a/doc/api/addons.markdown +++ b/doc/api/addons.markdown @@ -6,7 +6,8 @@ knowledge of several libraries: - V8 JavaScript, a C++ library. Used for interfacing with JavaScript: creating objects, calling functions, etc. Documented mostly in the - `v8.h` header file (`deps/v8/include/v8.h` in the Node source tree). + `v8.h` header file (`deps/v8/include/v8.h` in the Node source tree), + which is also available [online](http://izs.me/v8-docs/main.html). - [libuv](https://github.com/joyent/libuv), C event loop library. Anytime one needs to wait for a file descriptor to become readable, wait for a timer, or @@ -22,12 +23,15 @@ Node statically compiles all its dependencies into the executable. When compiling your module, you don't need to worry about linking to any of these libraries. + +### Hello world + To get started let's make a small Addon which is the C++ equivalent of the following Javascript code: exports.hello = function() { return 'world'; }; -To get started we create a file `hello.cc`: +First we create a file `hello.cc`: #include <node.h> #include <v8.h> @@ -40,7 +44,8 @@ To get started we create a file `hello.cc`: } void init(Handle<Object> target) { - NODE_SET_METHOD(target, "hello", Method); + target->Set(String::NewSymbol("hello"), + FunctionTemplate::New(Method)->GetFunction()); } NODE_MODULE(hello, init) @@ -87,5 +92,537 @@ the recently built module: console.log(addon.hello()); // 'world' -For the moment, that is all the documentation on addons. Please see -<https://github.com/pietern/hiredis-node> for a real example. +Please see patterns below for further information or +<https://github.com/pietern/hiredis-node> for an example in production. + + +## Addon patterns + +Below are some addon patterns to help you get started. Consult the online +[v8 reference](http://izs.me/v8-docs/main.html) for help with the various v8 +calls, and v8's [Embedder's Guide](http://code.google.com/apis/v8/embed.html) +for an explanation of several concepts used such as handles, scopes, +function templates, etc. + +To compile these examples, create the `wscript` file below and run +`node-waf configure build`: + + 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 = 'addon' + obj.source = ['addon.cc'] + +In cases where there is more than one `.cc` file, simply add the file name to the +`obj.source` array, e.g.: + + obj.source = ['addon.cc', 'myexample.cc'] + + +#### Function arguments + +The following pattern illustrates how to read arguments from JavaScript +function calls and return a result. This is the main and only needed source +`addon.cc`: + + #define BUILDING_NODE_EXTENSION + #include <node.h> + + using namespace v8; + + Handle<Value> Add(const Arguments& args) { + HandleScope scope; + + if (args.Length() < 2) { + ThrowException(Exception::TypeError(String::New("Wrong number of arguments"))); + return scope.Close(Undefined()); + } + + if (!args[0]->IsNumber() || !args[1]->IsNumber()) { + ThrowException(Exception::TypeError(String::New("Wrong arguments"))); + return scope.Close(Undefined()); + } + + Local<Number> num = Number::New(args[0]->NumberValue() + + args[1]->NumberValue()); + return scope.Close(num); + } + + void Init(Handle<Object> target) { + target->Set(String::NewSymbol("add"), + FunctionTemplate::New(Add)->GetFunction()); + } + + NODE_MODULE(addon, Init) + +You can test it with the following JavaScript snippet: + + var addon = require('./build/Release/addon'); + + console.log( 'This should be eight:', addon.add(3,5) ); + + +#### Callbacks + +You can pass JavaScript functions to a C++ function and execute them from +there. Here's `addon.cc`: + + #define BUILDING_NODE_EXTENSION + #include <node.h> + + using namespace v8; + + Handle<Value> RunCallback(const Arguments& args) { + HandleScope scope; + + Local<Function> cb = Local<Function>::Cast(args[0]); + const unsigned argc = 1; + Local<Value> argv[argc] = { Local<Value>::New(String::New("hello world")) }; + cb->Call(Context::GetCurrent()->Global(), argc, argv); + + return scope.Close(Undefined()); + } + + void Init(Handle<Object> target) { + target->Set(String::NewSymbol("runCallback"), + FunctionTemplate::New(RunCallback)->GetFunction()); + } + + NODE_MODULE(addon, Init) + +To test it run the following JavaScript snippet: + + var addon = require('./build/Release/addon'); + + addon.runCallback(function(msg){ + console.log(msg); // 'hello world' + }); + + +#### Object factory + +You can create and return new objects from within a C++ function with this +`addon.cc` pattern, which returns an object with property `msg` that echoes +the string passed to `createObject()`: + + #define BUILDING_NODE_EXTENSION + #include <node.h> + + using namespace v8; + + Handle<Value> CreateObject(const Arguments& args) { + HandleScope scope; + + Local<Object> obj = Object::New(); + obj->Set(String::NewSymbol("msg"), args[0]->ToString()); + + return scope.Close(obj); + } + + void Init(Handle<Object> target) { + target->Set(String::NewSymbol("createObject"), + FunctionTemplate::New(CreateObject)->GetFunction()); + } + + NODE_MODULE(addon, Init) + +To test it in JavaScript: + + var addon = require('./build/Release/addon'); + + var obj1 = addon.createObject('hello'); + var obj2 = addon.createObject('world'); + console.log(obj1.msg+' '+obj2.msg); // 'hello world' + + +#### Function factory + +This pattern illustrates how to create and return a JavaScript function that +wraps a C++ function: + + #define BUILDING_NODE_EXTENSION + #include <node.h> + + using namespace v8; + + Handle<Value> MyFunction(const Arguments& args) { + HandleScope scope; + return scope.Close(String::New("hello world")); + } + + Handle<Value> CreateFunction(const Arguments& args) { + HandleScope scope; + + Local<FunctionTemplate> tpl = FunctionTemplate::New(MyFunction); + Local<Function> fn = tpl->GetFunction(); + fn->SetName(String::NewSymbol("theFunction")); // omit this to make it anonymous + + return scope.Close(fn); + } + + void Init(Handle<Object> target) { + target->Set(String::NewSymbol("createFunction"), + FunctionTemplate::New(CreateFunction)->GetFunction()); + } + + NODE_MODULE(addon, Init) + + +To test: + + var addon = require('./build/Release/addon'); + + var fn = addon.createFunction(); + console.log(fn()); // 'hello world' + + +#### Wrapping C++ objects + +Here we will create a wrapper for a C++ object/class `MyObject` that can be +instantiated in JavaScript through the `new` operator. First prepare the main +module `addon.cc`: + + #define BUILDING_NODE_EXTENSION + #include <node.h> + #include "myobject.h" + + using namespace v8; + + void InitAll(Handle<Object> target) { + MyObject::Init(target); + } + + NODE_MODULE(addon, InitAll) + +Then in `myobject.h` make your wrapper inherit from `node::ObjectWrap`: + + #ifndef MYOBJECT_H + #define MYOBJECT_H + + #include <node.h> + + class MyObject : public node::ObjectWrap { + public: + static void Init(v8::Handle<v8::Object> target); + + private: + MyObject(); + ~MyObject(); + + static v8::Handle<v8::Value> New(const v8::Arguments& args); + static v8::Handle<v8::Value> PlusOne(const v8::Arguments& args); + double counter_; + }; + + #endif + +And in `myobject.cc` implement the various methods that you want to expose. +Here we expose the method `plusOne` by adding it to the constructor's +prototype: + + #define BUILDING_NODE_EXTENSION + #include <node.h> + #include "myobject.h" + + using namespace v8; + + MyObject::MyObject() {}; + MyObject::~MyObject() {}; + + void MyObject::Init(Handle<Object> target) { + // Prepare constructor template + Local<FunctionTemplate> tpl = FunctionTemplate::New(New); + tpl->SetClassName(String::NewSymbol("MyObject")); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + // Prototype + tpl->PrototypeTemplate()->Set(String::NewSymbol("plusOne"), + FunctionTemplate::New(PlusOne)->GetFunction()); + + Persistent<Function> constructor = Persistent<Function>::New(tpl->GetFunction()); + target->Set(String::NewSymbol("MyObject"), constructor); + } + + Handle<Value> MyObject::New(const Arguments& args) { + HandleScope scope; + + MyObject* obj = new MyObject(); + obj->counter_ = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); + obj->Wrap(args.This()); + + return args.This(); + } + + Handle<Value> MyObject::PlusOne(const Arguments& args) { + HandleScope scope; + + MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.This()); + obj->counter_ += 1; + + return scope.Close(Number::New(obj->counter_)); + } + +Test it with: + + var addon = require('./build/Release/addon'); + + var obj = new addon.MyObject(10); + console.log( obj.plusOne() ); // 11 + console.log( obj.plusOne() ); // 12 + console.log( obj.plusOne() ); // 13 + + +#### Factory of wrapped objects + +This is useful when you want to be able to create native objects without +explicitly instantiating them with the `new` operator in JavaScript, e.g. + + var obj = addon.createObject(); + // instead of: + // var obj = new addon.Object(); + +Let's register our `createObject` method in `addon.cc`: + + #define BUILDING_NODE_EXTENSION + #include <node.h> + #include "myobject.h" + + using namespace v8; + + Handle<Value> CreateObject(const Arguments& args) { + HandleScope scope; + return scope.Close(MyObject::NewInstance(args)); + } + + void InitAll(Handle<Object> target) { + MyObject::Init(); + + target->Set(String::NewSymbol("createObject"), + FunctionTemplate::New(CreateObject)->GetFunction()); + } + + NODE_MODULE(addon, InitAll) + +In `myobject.h` we now introduce the static method `NewInstance` that takes +care of instantiating the object (i.e. it does the job of `new` in JavaScript): + + #define BUILDING_NODE_EXTENSION + #ifndef MYOBJECT_H + #define MYOBJECT_H + + #include <node.h> + + class MyObject : public node::ObjectWrap { + public: + static void Init(); + static v8::Handle<v8::Value> NewInstance(const v8::Arguments& args); + + private: + MyObject(); + ~MyObject(); + + static v8::Persistent<v8::Function> constructor; + static v8::Handle<v8::Value> New(const v8::Arguments& args); + static v8::Handle<v8::Value> PlusOne(const v8::Arguments& args); + double counter_; + }; + + #endif + +The implementation is similar to the above in `myobject.cc`: + + #define BUILDING_NODE_EXTENSION + #include <node.h> + #include "myobject.h" + + using namespace v8; + + MyObject::MyObject() {}; + MyObject::~MyObject() {}; + + Persistent<Function> MyObject::constructor; + + void MyObject::Init() { + // Prepare constructor template + Local<FunctionTemplate> tpl = FunctionTemplate::New(New); + tpl->SetClassName(String::NewSymbol("MyObject")); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + // Prototype + tpl->PrototypeTemplate()->Set(String::NewSymbol("plusOne"), + FunctionTemplate::New(PlusOne)->GetFunction()); + + constructor = Persistent<Function>::New(tpl->GetFunction()); + } + + Handle<Value> MyObject::New(const Arguments& args) { + HandleScope scope; + + MyObject* obj = new MyObject(); + obj->counter_ = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); + obj->Wrap(args.This()); + + return args.This(); + } + + Handle<Value> MyObject::NewInstance(const Arguments& args) { + HandleScope scope; + + const unsigned argc = 1; + Handle<Value> argv[argc] = { args[0] }; + Local<Object> instance = constructor->NewInstance(argc, argv); + + return scope.Close(instance); + } + + Handle<Value> MyObject::PlusOne(const Arguments& args) { + HandleScope scope; + + MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.This()); + obj->counter_ += 1; + + return scope.Close(Number::New(obj->counter_)); + } + +Test it with: + + var addon = require('./build/Release/addon'); + + var obj = addon.createObject(10); + console.log( obj.plusOne() ); // 11 + console.log( obj.plusOne() ); // 12 + console.log( obj.plusOne() ); // 13 + + var obj2 = addon.createObject(20); + console.log( obj2.plusOne() ); // 21 + console.log( obj2.plusOne() ); // 22 + console.log( obj2.plusOne() ); // 23 + + +#### Passing wrapped objects around + +In addition to wrapping and returning C++ objects, you can pass them around +by unwrapping them with Node's `node::ObjectWrap::Unwrap` helper function. +In the following `addon.cc` we introduce a function `add()` that can take on two +`MyObject` objects: + + #define BUILDING_NODE_EXTENSION + #include <node.h> + #include "myobject.h" + + using namespace v8; + + Handle<Value> CreateObject(const Arguments& args) { + HandleScope scope; + return scope.Close(MyObject::NewInstance(args)); + } + + Handle<Value> Add(const Arguments& args) { + HandleScope scope; + + MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>( + args[0]->ToObject()); + MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>( + args[1]->ToObject()); + + double sum = obj1->Val() + obj2->Val(); + return scope.Close(Number::New(sum)); + } + + void InitAll(Handle<Object> target) { + MyObject::Init(); + + target->Set(String::NewSymbol("createObject"), + FunctionTemplate::New(CreateObject)->GetFunction()); + + target->Set(String::NewSymbol("add"), + FunctionTemplate::New(Add)->GetFunction()); + } + + NODE_MODULE(addon, InitAll) + +To make things interesting we introduce a public method in `myobject.h` so we +can probe private values after unwrapping the object: + + #define BUILDING_NODE_EXTENSION + #ifndef MYOBJECT_H + #define MYOBJECT_H + + #include <node.h> + + class MyObject : public node::ObjectWrap { + public: + static void Init(); + static v8::Handle<v8::Value> NewInstance(const v8::Arguments& args); + double Val() const { return val_; } + + private: + MyObject(); + ~MyObject(); + + static v8::Persistent<v8::Function> constructor; + static v8::Handle<v8::Value> New(const v8::Arguments& args); + double val_; + }; + + #endif + +The implementation of `myobject.cc` is similar as before: + + #define BUILDING_NODE_EXTENSION + #include <node.h> + #include "myobject.h" + + using namespace v8; + + MyObject::MyObject() {}; + MyObject::~MyObject() {}; + + Persistent<Function> MyObject::constructor; + + void MyObject::Init() { + // Prepare constructor template + Local<FunctionTemplate> tpl = FunctionTemplate::New(New); + tpl->SetClassName(String::NewSymbol("MyObject")); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + + constructor = Persistent<Function>::New(tpl->GetFunction()); + } + + Handle<Value> MyObject::New(const Arguments& args) { + HandleScope scope; + + MyObject* obj = new MyObject(); + obj->val_ = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); + obj->Wrap(args.This()); + + return args.This(); + } + + Handle<Value> MyObject::NewInstance(const Arguments& args) { + HandleScope scope; + + const unsigned argc = 1; + Handle<Value> argv[argc] = { args[0] }; + Local<Object> instance = constructor->NewInstance(argc, argv); + + return scope.Close(instance); + } + +Test it with: + + var addon = require('./build/Release/addon'); + + var obj1 = addon.createObject(10); + var obj2 = addon.createObject(20); + var result = addon.add(obj1, obj2); + + console.log(result); // 30 |