Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 359 lines (251 sloc) 16.062 kb
f489367 @olalonde updated blog post URL in README.md and TUTORIAL.md
authored
1 This tutorial is also available online here: [How to write your own native Node.js extension](http://syskall.com/how-to-write-your-own-native-nodejs-extension)
d64d74c @olalonde Wrote tutorial
authored
2
3 ##Introduction##
4
f489367 @olalonde updated blog post URL in README.md and TUTORIAL.md
authored
5 *This is a follow up to [How to roll out your own Javascript API with V8](http://syskall.com/how-to-roll-out-your-own-javascript-api-with). You should still be able to follow if you haven't read it.*
d64d74c @olalonde Wrote tutorial
authored
6
7 We will now port the [code we have written for V8](https://github.com/olalonde/node-notify) to [Node.js](http://nodejs.org/) and package it for [npm](http://npmjs.org/).
8
9 ![node-notify screenshot](http://i.imgur.com/n7ZIw.png)
10
11 The full source code of this tutorial is available [from github](https://github.com/olalonde/node-notify):
12
13 git clone git://github.com/olalonde/jsnotify.git
14
15 You can also install it through `npm`:
16
17 npm install notify
18
19 The code was tested on Ubuntu 10.10 64-bit and Node.js v0.5.0-pre.
20
21 ##Getting started##
22
23 First let’s create a node-notify folder and with the following directory structure.
24
25 .
26 |-- build/ # This is where our extension is built.
27 |-- demo/
28 | `-- demo.js # This is a demo Node.js script to test our extension.
29 |-- src/
30 | `-- node_gtknotify.cpp # This is the where we do the mapping from C++ to Javascript.
31 `-- wscript # This is our build configuration used by node-waf
32
32ee2bc @olalonde modif tutorial
authored
33 *This fine looking tree was generated with the `tree` utility.*
d64d74c @olalonde Wrote tutorial
authored
34
32ee2bc @olalonde modif tutorial
authored
35 Now let's create our test script `demo.js` and decide upfront what our extension's API should look like:
d64d74c @olalonde Wrote tutorial
authored
36
37 #!js
38 // This loads our extension on the notify variable.
39 // It will only load a constructor function, notify.notification().
40 var notify = require("../build/default/gtknotify.node"); // path to our extension
41
42 var notification = new notify.notification();
43 notification.title = "Notification title";
44 notification.icon = "emblem-default"; // see /usr/share/icons/gnome/16x16
45 notification.send("Notification message");
46
47 ##Writing our Node.js extension##
48
32ee2bc @olalonde modif tutorial
authored
49
d64d74c @olalonde Wrote tutorial
authored
50 ###The Init method###
51
32ee2bc @olalonde modif tutorial
authored
52 In order to create a Node.js extension, we need to write a C++ class that extends [node::ObjectWrap](https://github.com/joyent/node/blob/master/src/node_object_wrap.h). ObjectWrap implements some utility methods that lets us easily interface with Javascript.
53
54 Let's write the skeletton for our class:
d64d74c @olalonde Wrote tutorial
authored
55
f489367 @olalonde updated blog post URL in README.md and TUTORIAL.md
authored
56 #!cpp
d64d74c @olalonde Wrote tutorial
authored
57
58 #include <v8.h> // v8 is the Javascript engine used by Node
59 #include <node.h>
60 // We will need the following libraries for our GTK+ notification
61 #include <string>
62 #include <gtkmm.h>
63 #include <libnotifymm.h>
64
65 using namespace v8;
66
67 class Gtknotify : node::ObjectWrap {
68 private:
69 public:
70 Gtknotify() {}
71 ~Gtknotify() {}
72 static void Init(Handle<Object> target) {
73 // This is what Node will call when we load the extension through require(), see boilerplate code below.
74 }
75 };
76
77 /*
78 * WARNING: Boilerplate code ahead.
79 *
80 * See https://www.cloudkick.com/blog/2010/aug/23/writing-nodejs-native-extensions/ & http://www.freebsd.org/cgi/man.cgi?query=dlsym
81 *
82 * Thats it for actual interfacing with v8, finally we need to let Node.js know how to dynamically load our code.
83 * Because a Node.js extension can be loaded at runtime from a shared object, we need a symbol that the dlsym function can find,
84 * so we do the following:
85 */
86
87 v8::Persistent<FunctionTemplate> Gtknotify::persistent_function_template;
88 extern "C" { // Cause of name mangling in C++, we use extern C here
89 static void init(Handle<Object> target) {
90 Gtknotify::Init(target);
91 }
92 // @see http://github.com/ry/node/blob/v0.2.0/src/node.h#L101
93 NODE_MODULE(gtknotify, init);
94 }
95
32ee2bc @olalonde modif tutorial
authored
96 Now, we'll have to we have to write the following code in our Init() method:
d64d74c @olalonde Wrote tutorial
authored
97
32ee2bc @olalonde modif tutorial
authored
98 1. Declare our constructor function and bind it to our target variable. `var n = require("notification");` will bind notification() to n: `n.notification()`.
d64d74c @olalonde Wrote tutorial
authored
99
f489367 @olalonde updated blog post URL in README.md and TUTORIAL.md
authored
100 #!cpp
d64d74c @olalonde Wrote tutorial
authored
101
102 // Wrap our C++ New() method so that it's accessible from Javascript
103 // This will be called by the new operator in Javascript, for example: new notification();
104 v8::Local<FunctionTemplate> local_function_template = v8::FunctionTemplate::New(New);
105
106 // Make it persistent and assign it to persistent_function_template which is a static attribute of our class.
107 Gtknotify::persistent_function_template = v8::Persistent<FunctionTemplate>::New(local_function_template);
108
109 // Each JavaScript object keeps a reference to the C++ object for which it is a wrapper with an internal field.
110 Gtknotify::persistent_function_template->InstanceTemplate()->SetInternalFieldCount(1); // 1 since a constructor function only references 1 object
111 // Set a "class" name for objects created with our constructor
112 Gtknotify::persistent_function_template->SetClassName(v8::String::NewSymbol("Notification"));
113
114 // Set the "notification" property of our target variable and assign it to our constructor function
115 target->Set(String::NewSymbol("notification"), Gtknotify::persistent_function_template->GetFunction());
116
32ee2bc @olalonde modif tutorial
authored
117 2. Declare our attributes: `n.title` and `n.icon`.
d64d74c @olalonde Wrote tutorial
authored
118
f489367 @olalonde updated blog post URL in README.md and TUTORIAL.md
authored
119 #!cpp
d64d74c @olalonde Wrote tutorial
authored
120
121 // Set property accessors
122 // SetAccessor arguments: Javascript property name, C++ method that will act as the getter, C++ method that will act as the setter
123 Gtknotify::persistent_function_template->InstanceTemplate()->SetAccessor(String::New("title"), GetTitle, SetTitle);
124 Gtknotify::persistent_function_template->InstanceTemplate()->SetAccessor(String::New("icon"), GetIcon, SetIcon);
125 // For instance, n.title = "foo" will now call SetTitle("foo"), n.title will now call GetTitle()
126
32ee2bc @olalonde modif tutorial
authored
127 3. Declare our prototype method: `n.send()`
d64d74c @olalonde Wrote tutorial
authored
128
f489367 @olalonde updated blog post URL in README.md and TUTORIAL.md
authored
129 #!cpp
d64d74c @olalonde Wrote tutorial
authored
130
131 // This is a Node macro to help bind C++ methods to Javascript methods (see https://github.com/joyent/node/blob/v0.2.0/src/node.h#L34)
132 // Arguments: our constructor function, Javascript method name, C++ method name
133 NODE_SET_PROTOTYPE_METHOD(Gtknotify::persistent_function_template, "send", Send);
134
32ee2bc @olalonde modif tutorial
authored
135 Our Init() method should now look like this:
d64d74c @olalonde Wrote tutorial
authored
136
f489367 @olalonde updated blog post URL in README.md and TUTORIAL.md
authored
137 #!cpp
d64d74c @olalonde Wrote tutorial
authored
138
139 // Our constructor
140 static v8::Persistent<FunctionTemplate> persistent_function_template;
141
142 static void Init(Handle<Object> target) {
143 v8::HandleScope scope; // used by v8 for garbage collection
144
145 // Our constructor
146 v8::Local<FunctionTemplate> local_function_template = v8::FunctionTemplate::New(New);
147 Gtknotify::persistent_function_template = v8::Persistent<FunctionTemplate>::New(local_function_template);
148 Gtknotify::persistent_function_template->InstanceTemplate()->SetInternalFieldCount(1); // 1 since this is a constructor function
149 Gtknotify::persistent_function_template->SetClassName(v8::String::NewSymbol("Notification"));
150
151 // Our getters and setters
152 Gtknotify::persistent_function_template->InstanceTemplate()->SetAccessor(String::New("title"), GetTitle, SetTitle);
153 Gtknotify::persistent_function_template->InstanceTemplate()->SetAccessor(String::New("icon"), GetIcon, SetIcon);
154
155 // Our methods
156 NODE_SET_PROTOTYPE_METHOD(Gtknotify::persistent_function_template, "send", Send);
157
158 // Binding our constructor function to the target variable
159 target->Set(String::NewSymbol("notification"), Gtknotify::persistent_function_template->GetFunction());
160 }
161
162
32ee2bc @olalonde modif tutorial
authored
163 All that is left to do is to write the C++ methods that we used in our Init method: `New`, `GetTitle`, `SetTitle`, `GetIcon`, `SetIcon`, `Send`
d64d74c @olalonde Wrote tutorial
authored
164
165 ###Our constructor method: New()###
166
32ee2bc @olalonde modif tutorial
authored
167 The New() method creates an instance of our class (a Gtknotify object), sets some default values to our properties and returns a Javascript handle to this object. This is the expected behavior when calling a constructor function with the new operator in Javascript.
d64d74c @olalonde Wrote tutorial
authored
168
f489367 @olalonde updated blog post URL in README.md and TUTORIAL.md
authored
169 #!cpp
d64d74c @olalonde Wrote tutorial
authored
170
171 std::string title;
172 std::string icon;
173
174 // new notification()
175 static Handle<Value> New(const Arguments& args) {
176 HandleScope scope;
177 Gtknotify* gtknotify_instance = new Gtknotify();
178 // Set some default values
179 gtknotify_instance->title = "Node.js";
180 gtknotify_instance->icon = "terminal";
181
182 // Wrap our C++ object as a Javascript object
183 gtknotify_instance->Wrap(args.This());
184
185 return args.This();
186 }
187
188 ###Our getters and setters: GetTitle(), SetTitle(), GetIcon(), SetIcon()####
189
32ee2bc @olalonde modif tutorial
authored
190 The following is pretty much boilerplate code. It boils down to back and forth conversion between C++ values to Javascript (V8) values.
d64d74c @olalonde Wrote tutorial
authored
191
f489367 @olalonde updated blog post URL in README.md and TUTORIAL.md
authored
192 #!cpp
d64d74c @olalonde Wrote tutorial
authored
193
194 // this.title
195 static v8::Handle<Value> GetTitle(v8::Local<v8::String> property, const v8::AccessorInfo& info) {
196 // Extract the C++ request object from the JavaScript wrapper.
197 Gtknotify* gtknotify_instance = node::ObjectWrap::Unwrap<Gtknotify>(info.Holder());
198 return v8::String::New(gtknotify_instance->title.c_str());
199 }
200 // this.title=
201 static void SetTitle(Local<String> property, Local<Value> value, const AccessorInfo& info) {
202 Gtknotify* gtknotify_instance = node::ObjectWrap::Unwrap<Gtknotify>(info.Holder());
203 v8::String::Utf8Value v8str(value);
204 gtknotify_instance->title = *v8str;
205 }
206 // this.icon
207 static v8::Handle<Value> GetIcon(v8::Local<v8::String> property, const v8::AccessorInfo& info) {
208 // Extract the C++ request object from the JavaScript wrapper.
209 Gtknotify* gtknotify_instance = node::ObjectWrap::Unwrap<Gtknotify>(info.Holder());
210 return v8::String::New(gtknotify_instance->icon.c_str());
211 }
212 // this.icon=
213 static void SetIcon(Local<String> property, Local<Value> value, const AccessorInfo& info) {
214 Gtknotify* gtknotify_instance = node::ObjectWrap::Unwrap<Gtknotify>(info.Holder());
215 v8::String::Utf8Value v8str(value);
216 gtknotify_instance->icon = *v8str;
217 }
218
219
220 ###Our prototype method: Send()####
221
32ee2bc @olalonde modif tutorial
authored
222 First we have to extract the C++ object `this` references. We then build our notification using the object's properties (title, icon) and finally display it.
d64d74c @olalonde Wrote tutorial
authored
223
f489367 @olalonde updated blog post URL in README.md and TUTORIAL.md
authored
224 #!cpp
d64d74c @olalonde Wrote tutorial
authored
225
226 // this.send()
227 static v8::Handle<Value> Send(const Arguments& args) {
228 v8::HandleScope scope;
229 // Extract C++ object reference from "this"
230 Gtknotify* gtknotify_instance = node::ObjectWrap::Unwrap<Gtknotify>(args.This());
231
232 // Convert first argument to V8 String
233 v8::String::Utf8Value v8str(args[0]);
234
235 // For more info on the Notify library: http://library.gnome.org/devel/libnotify/0.7/NotifyNotification.html
236 Notify::init("Basic");
237 // Arguments: title, content, icon
238 Notify::Notification n(gtknotify_instance->title.c_str(), *v8str, gtknotify_instance->icon.c_str()); // *v8str points to the C string it wraps
239 // Display the notification
240 n.show();
241 // Return value
242 return v8::Boolean::New(true);
243 }
244
245 ##Compiling our extension##
246
32ee2bc @olalonde modif tutorial
authored
247 `node-waf` is the build tool used to compile Node extensions which is basically a wrapper for [waf](http://code.google.com/p/waf/). The build process can be configured with a file called `wscript` in our top directory:
d64d74c @olalonde Wrote tutorial
authored
248
249 #!python
250
251 def set_options(opt):
252 opt.tool_options("compiler_cxx")
253
254 def configure(conf):
255 conf.check_tool("compiler_cxx")
256 conf.check_tool("node_addon")
257 # This will tell the compiler to link our extension with the gtkmm and libnotifymm libraries.
258 conf.check_cfg(package='gtkmm-2.4', args='--cflags --libs', uselib_store='LIBGTKMM')
259 conf.check_cfg(package='libnotifymm-1.0', args='--cflags --libs', uselib_store='LIBNOTIFYMM')
260
261 def build(bld):
262 obj = bld.new_task_gen("cxx", "shlib", "node_addon")
263 obj.cxxflags = ["-g", "-D_FILE_OFFSET_BITS=64", "-D_LARGEFILE_SOURCE", "-Wall"]
264 # This is the name of our extension.
265 obj.target = "gtknotify"
266 obj.source = "src/node_gtknotify.cpp"
267 obj.uselib = ['LIBGTKMM', 'LIBNOTIFYMM']
268
32ee2bc @olalonde modif tutorial
authored
269 We're now ready to build! In the top directory, run the following command:
d64d74c @olalonde Wrote tutorial
authored
270
271 node-waf configure && node-waf build
272
32ee2bc @olalonde modif tutorial
authored
273 If everything goes right, we should now have our compiled extension in `./build/default/gtknotify.node`. Let's try it!
d64d74c @olalonde Wrote tutorial
authored
274
275 #!bash
32ee2bc @olalonde modif tutorial
authored
276
d64d74c @olalonde Wrote tutorial
authored
277 $ node
278 > var notif = require('./build/default/gtknotify.node');
279 > n = new notif.notification();
280 { icon: 'terminal', title: 'Node.js' }
281 > n.send("Hello World!");
282 true
283
32ee2bc @olalonde modif tutorial
authored
284 The previous code should display a notification in the top right corner of your screen!
d64d74c @olalonde Wrote tutorial
authored
285
286 ##Packaging for npm##
287
32ee2bc @olalonde modif tutorial
authored
288 That's pretty cool, but how about sharing your hard work with the Node community? That's primarily what the Node Package Manager is used for: making it easy to import extensions/modules and distribute them.
d64d74c @olalonde Wrote tutorial
authored
289
32ee2bc @olalonde modif tutorial
authored
290 Packaging an extension for npm is very straightforward. All you have to do is create a `package.json` file in your top directory which contains some info about your extension:
d64d74c @olalonde Wrote tutorial
authored
291
292 #!javascript
293
294 {
295 // Name of your extension (do not include node or js in the name, this is implicit).
296 // This is the name that will be used to import the extension through require().
297
298 "name" : "notify",
299
300 // Version should be http://semver.org/ compliant
301
302 "version" : "v0.1.0"
303
304 // These scripts will be run when calling npm install and npm uninstall.
305
306 , "scripts" : {
307 "preinstall" : "node-waf configure && node-waf build"
308 , "preuninstall" : "rm -rf build/*"
309 }
310
311 // This is the relative path to our built extension.
312
313 , "main" : "build/default/gtknotify.node"
314
315 // The following fields are optional:
316
317 , "description" : "Description of the extension...."
318 , "homepage" : "https://github.com/olalonde/node-notify"
319 , "author" : {
320 "name" : "Olivier Lalonde"
321 , "email" : "olalonde@gmail.com"
322 , "url" : "http://www.syskall.com/"
323 }
324 , "repository" : {
325 "type" : "git"
326 , "url" : "https://github.com/olalonde/node-notify.git"
327 }
328 }
329
32ee2bc @olalonde modif tutorial
authored
330 *For more details on the package.json format, documentation is available through `npm help json`. Note that most fields are optional.*
d64d74c @olalonde Wrote tutorial
authored
331
32ee2bc @olalonde modif tutorial
authored
332 You can now install your new npm package by running `npm install` in your top directory. If everything goes right, you should be able to load your extension with a simple `var notify = require('your-package-name');`. Another useful command is `npm link` which creates a symlink to your development directory so that any change to your code is reflected instantly - no need to install/uninstall perpetually.
d64d74c @olalonde Wrote tutorial
authored
333
32ee2bc @olalonde modif tutorial
authored
334 Assuming you wrote a cool extension, you might want to publish it online in the central npm repository. In order to do that, you first need to create an account:
d64d74c @olalonde Wrote tutorial
authored
335
336 $ npm adduser
337
338 Next, go back to the root of your package code and run:
339
340 $ npm publish
341
32ee2bc @olalonde modif tutorial
authored
342 That's it, your package is now available for anyone to install through the `npm install your-package-name` command.
d64d74c @olalonde Wrote tutorial
authored
343
344 ##Conclusion##
345
32ee2bc @olalonde modif tutorial
authored
346 Writing a native Node extension can be cumbersome and verbose at times but it is well worth the hard earned bragging rights!
d64d74c @olalonde Wrote tutorial
authored
347
32ee2bc @olalonde modif tutorial
authored
348 Thanks for reading. Let me know in the comments if you run into any problem, I’ll be glad to help.
d64d74c @olalonde Wrote tutorial
authored
349
350 *If you liked this, maybe you'd also like what I [tweet on Twitter](http://twitter.com/olivierll)! Might even want to [hire me](mailto:olalonde@gmail.com)?*
351
352 ##References##
353
513ef11 @olalonde typo in tutorial.md
authored
354 - [How to roll out your own Javascript API with V8](http://syskall.com/how-to-roll-out-your-own-javascript-api-with)
355 - [Writing Node.js Native Extensions](https://www.cloudkick.com/blog/2010/aug/23/writing-nodejs-native-extensions/)
e47b842 @olalonde Edited TUTORIAL.md via GitHub
authored
356 - [V8 JavaScript Engine Embedder's Guide](http://code.google.com/apis/v8/embed.html)
513ef11 @olalonde typo in tutorial.md
authored
357 - [Introduction to npm](http://howtonode.org/introduction-to-npm)
358 - [Node.js](http://nodejs.org/)
Something went wrong with that request. Please try again.