Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 364 lines (252 sloc) 16.224 kb
f489367 Olivier Lalonde 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 Olivier Lalonde Wrote tutorial
authored
2
3 ##Introduction##
4
f489367 Olivier Lalonde 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 Olivier Lalonde 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 Olivier Lalonde modif tutorial
authored
33 *This fine looking tree was generated with the `tree` utility.*
d64d74c Olivier Lalonde Wrote tutorial
authored
34
32ee2bc Olivier Lalonde 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 Olivier Lalonde 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 Olivier Lalonde modif tutorial
authored
49
d64d74c Olivier Lalonde Wrote tutorial
authored
50 ###The Init method###
51
32ee2bc Olivier Lalonde 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 Olivier Lalonde Wrote tutorial
authored
55
f489367 Olivier Lalonde updated blog post URL in README.md and TUTORIAL.md
authored
56 #!cpp
d64d74c Olivier Lalonde 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 Olivier Lalonde modif tutorial
authored
96 Now, we'll have to we have to write the following code in our Init() method:
d64d74c Olivier Lalonde Wrote tutorial
authored
97
32ee2bc Olivier Lalonde 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 Olivier Lalonde Wrote tutorial
authored
99
f489367 Olivier Lalonde updated blog post URL in README.md and TUTORIAL.md
authored
100 #!cpp
d64d74c Olivier Lalonde 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 Olivier Lalonde modif tutorial
authored
117 2. Declare our attributes: `n.title` and `n.icon`.
d64d74c Olivier Lalonde Wrote tutorial
authored
118
f489367 Olivier Lalonde updated blog post URL in README.md and TUTORIAL.md
authored
119 #!cpp
d64d74c Olivier Lalonde 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 Olivier Lalonde modif tutorial
authored
127 3. Declare our prototype method: `n.send()`
d64d74c Olivier Lalonde Wrote tutorial
authored
128
f489367 Olivier Lalonde updated blog post URL in README.md and TUTORIAL.md
authored
129 #!cpp
d64d74c Olivier Lalonde 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 Olivier Lalonde modif tutorial
authored
135 Our Init() method should now look like this:
d64d74c Olivier Lalonde Wrote tutorial
authored
136
f489367 Olivier Lalonde updated blog post URL in README.md and TUTORIAL.md
authored
137 #!cpp
d64d74c Olivier Lalonde 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 Olivier Lalonde 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 Olivier Lalonde Wrote tutorial
authored
164
165 ###Our constructor method: New()###
166
32ee2bc Olivier Lalonde 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 Olivier Lalonde Wrote tutorial
authored
168
f489367 Olivier Lalonde updated blog post URL in README.md and TUTORIAL.md
authored
169 #!cpp
d64d74c Olivier Lalonde 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 Olivier Lalonde 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 Olivier Lalonde Wrote tutorial
authored
191
f489367 Olivier Lalonde updated blog post URL in README.md and TUTORIAL.md
authored
192 #!cpp
d64d74c Olivier Lalonde 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 Olivier Lalonde 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 Olivier Lalonde Wrote tutorial
authored
223
f489367 Olivier Lalonde updated blog post URL in README.md and TUTORIAL.md
authored
224 #!cpp
d64d74c Olivier Lalonde 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 Olivier Lalonde 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 Olivier Lalonde 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 Olivier Lalonde modif tutorial
authored
269 We're now ready to build! In the top directory, run the following command:
d64d74c Olivier Lalonde Wrote tutorial
authored
270
271 node-waf configure && node-waf build
272
32ee2bc Olivier Lalonde 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 Olivier Lalonde Wrote tutorial
authored
274
275 #!bash
32ee2bc Olivier Lalonde modif tutorial
authored
276
d64d74c Olivier Lalonde 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 Olivier Lalonde modif tutorial
authored
284 The previous code should display a notification in the top right corner of your screen!
d64d74c Olivier Lalonde Wrote tutorial
authored
285
286 ##Packaging for npm##
287
32ee2bc Olivier Lalonde 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 Olivier Lalonde Wrote tutorial
authored
289
32ee2bc Olivier Lalonde 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 Olivier Lalonde 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 Olivier Lalonde 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 Olivier Lalonde Wrote tutorial
authored
331
32ee2bc Olivier Lalonde 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 Olivier Lalonde Wrote tutorial
authored
333
32ee2bc Olivier Lalonde 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 Olivier Lalonde 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 Olivier Lalonde 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 Olivier Lalonde Wrote tutorial
authored
343
344 ##Conclusion##
345
32ee2bc Olivier Lalonde 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 Olivier Lalonde Wrote tutorial
authored
347
32ee2bc Olivier Lalonde 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 Olivier Lalonde 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
354 [How to roll out your own Javascript API with V8](http://syskall.com/how-to-roll-out-your-own-javascript-api-with)
355
356 [Writing Node.js Native Extensions](https://www.cloudkick.com/blog/2010/aug/23/writing-nodejs-native-extensions/)
357
358 [V8 JavaScript Engine Embedder's Guid](http://code.google.com/apis/v8/embed.html)
73e1e66 Olivier Lalonde initial commit, minimal functionnality
authored
359 Part 1 (V8 extension) available here: [http://syskall.com/how-to-roll-out-your-own-javascript-api-with](http://syskall.com/how-to-roll-out-your-own-javascript-api-with)
d64d74c Olivier Lalonde Wrote tutorial
authored
360
361 [Introduction to npm](http://howtonode.org/introduction-to-npm)
362
363 [Node.js](http://nodejs.org/)
Something went wrong with that request. Please try again.