Bereit zum Start
Zuerst verwenden wir die folgende Verzeichnisstruktur, um einen Node-Notify-Ordner zu erstellen.
Dieser wunderschön aussehende Baum wird mithilfe eines generischen Baums generiert.
Lassen Sie mich nun das Testskript demo.js erstellen und im Voraus entscheiden, wie die API unserer Erweiterung aussehen soll:
// This loads our extension on the notify variable. // It will only load a constructor function, notify.notification(). var notify = require("../build/default/gtknotify.node"); // path to our extension var notification = new notify.notification(); notification.title = "Notification title"; notification.icon = "emblem-default"; // see /usr/share/icons/gnome/16x16 notification.send("Notification message");
Schreiben unserer Node.js-Erweiterung
Init-Methode
Um eine Node.js-Erweiterung zu erstellen, müssen wir eine C-Klasse schreiben, die node::ObjectWrap erbt. ObjectWrap implementiert öffentliche Methoden, die uns die Interaktion mit Javascript erleichtern
Lassen Sie uns zunächst das Grundgerüst der Klasse schreiben:
#include <v8.h> // v8 is the Javascript engine used by QNode #include <node.h> // We will need the following libraries for our GTK+ notification #include <string> #include <gtkmm.h> #include <libnotifymm.h> using namespace v8; class Gtknotify : node::ObjectWrap { private: public: Gtknotify() {} ~Gtknotify() {} static void Init(Handle<Object> target) { // This is what Node will call when we load the extension through require(), see boilerplate code below. } }; /* * WARNING: Boilerplate code ahead. * * See https://www.cloudkick.com/blog/2010/aug/23/writing-nodejs-native-extensions/ & http://www.freebsd.org/cgi/man.cgi?query=dlsym * * Thats it for actual interfacing with v8, finally we need to let Node.js know how to dynamically load our code. * Because a Node.js extension can be loaded at runtime from a shared object, we need a symbol that the dlsym function can find, * so we do the following: */ v8::Persistent<FunctionTemplate> Gtknotify::persistent_function_template; extern "C" { // Cause of name mangling in C++, we use extern C here static void init(Handle<Object> target) { Gtknotify::Init(target); } // @see http://github.com/ry/node/blob/v0.2.0/src/node.h#L101 NODE_MODULE(gtknotify, init); }
Jetzt müssen wir den folgenden Code in unsere Init()-Methode schreiben:
Deklarieren Sie den Konstruktor und binden Sie ihn an unsere Zielvariable. var n = require("notification"); bindet notification() an n:n.notification().
// Wrap our C++ New() method so that it's accessible from Javascript // This will be called by the new operator in Javascript, for example: new notification(); v8::Local<FunctionTemplate> local_function_template = v8::FunctionTemplate::New(New); // Make it persistent and assign it to persistent_function_template which is a static attribute of our class. Gtknotify::persistent_function_template = v8::Persistent<FunctionTemplate>::New(local_function_template); // Each JavaScript object keeps a reference to the C++ object for which it is a wrapper with an internal field. Gtknotify::persistent_function_template->InstanceTemplate()->SetInternalFieldCount(1); // 1 since a constructor function only references 1 object // Set a "class" name for objects created with our constructor Gtknotify::persistent_function_template->SetClassName(v8::String::NewSymbol("Notification")); // Set the "notification" property of our target variable and assign it to our constructor function target->Set(String::NewSymbol("notification"), Gtknotify::persistent_function_template->GetFunction());
Deklarationsattribute: n.title und n.icon.
// Set property accessors // SetAccessor arguments: Javascript property name, C++ method that will act as the getter, C++ method that will act as the setter Gtknotify::persistent_function_template->InstanceTemplate()->SetAccessor(String::New("title"), GetTitle, SetTitle); Gtknotify::persistent_function_template->InstanceTemplate()->SetAccessor(String::New("icon"), GetIcon, SetIcon); // For instance, n.title = "foo" will now call SetTitle("foo"), n.title will now call GetTitle()
// 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) // Arguments: our constructor function, Javascript method name, C++ method name NODE_SET_PROTOTYPE_METHOD(Gtknotify::persistent_function_template, "send", Send);
// Our constructor static v8::Persistent<FunctionTemplate> persistent_function_template; static void Init(Handle<Object> target) { v8::HandleScope scope; // used by v8 for garbage collection // Our constructor v8::Local<FunctionTemplate> local_function_template = v8::FunctionTemplate::New(New); Gtknotify::persistent_function_template = v8::Persistent<FunctionTemplate>::New(local_function_template); Gtknotify::persistent_function_template->InstanceTemplate()->SetInternalFieldCount(1); // 1 since this is a constructor function Gtknotify::persistent_function_template->SetClassName(v8::String::NewSymbol("Notification")); // Our getters and setters Gtknotify::persistent_function_template->InstanceTemplate()->SetAccessor(String::New("title"), GetTitle, SetTitle); Gtknotify::persistent_function_template->InstanceTemplate()->SetAccessor(String::New("icon"), GetIcon, SetIcon); // Our methods NODE_SET_PROTOTYPE_METHOD(Gtknotify::persistent_function_template, "send", Send); // Binding our constructor function to the target variable target->Set(String::NewSymbol("notification"), Gtknotify::persistent_function_template->GetFunction()); }
Konstruktormethode: New()
DieNew()-Methode erstellt eine neue Instanz unserer benutzerdefinierten Klasse (ein Gtknotify-Objekt), legt einige Anfangswerte fest und gibt dann den JavaScript-Handler für dieses Objekt zurück. Dies ist das erwartete Verhalten von JavaScript, das einen Konstruktor mit dem neuen Operator aufruft.
std::string title; std::string icon; // new notification() static Handle<Value> New(const Arguments& args) { HandleScope scope; Gtknotify* gtknotify_instance = new Gtknotify(); // Set some default values gtknotify_instance->title = "Node.js"; gtknotify_instance->icon = "terminal"; // Wrap our C++ object as a Javascript object gtknotify_instance->Wrap(args.This()); return args.This(); } getters 和 setters: GetTitle(), SetTitle(), GetIcon(), SetIcon()
Was folgt, ist größtenteils Boilerplate-Code, der auf die Konvertierung von Werten zwischen C und JavaScript (v8) hinausläuft.
// this.title static v8::Handle<Value> GetTitle(v8::Local<v8::String> property, const v8::AccessorInfo& info) { // Extract the C++ request object from the JavaScript wrapper. Gtknotify* gtknotify_instance = node::ObjectWrap::Unwrap<Gtknotify>(info.Holder()); return v8::String::New(gtknotify_instance->title.c_str()); } // this.title= static void SetTitle(Local<String> property, Local<Value> value, const AccessorInfo& info) { Gtknotify* gtknotify_instance = node::ObjectWrap::Unwrap<Gtknotify>(info.Holder()); v8::String::Utf8Value v8str(value); gtknotify_instance->title = *v8str; } // this.icon static v8::Handle<Value> GetIcon(v8::Local<v8::String> property, const v8::AccessorInfo& info) { // Extract the C++ request object from the JavaScript wrapper. Gtknotify* gtknotify_instance = node::ObjectWrap::Unwrap<Gtknotify>(info.Holder()); return v8::String::New(gtknotify_instance->icon.c_str()); } // this.icon= static void SetIcon(Local<String> property, Local<Value> value, const AccessorInfo& info) { Gtknotify* gtknotify_instance = node::ObjectWrap::Unwrap<Gtknotify>(info.Holder()); v8::String::Utf8Value v8str(value); gtknotify_instance->icon = *v8str; }
Prototypmethode: Send()
Zuerst extrahieren wir die Referenz des C-Objekts und verwenden dann die Eigenschaften des Objekts, um die Benachrichtigung zu erstellen und anzuzeigen.
// this.send() static v8::Handle<Value> Send(const Arguments& args) { v8::HandleScope scope; // Extract C++ object reference from "this" Gtknotify* gtknotify_instance = node::ObjectWrap::Unwrap<Gtknotify>(args.This()); // Convert first argument to V8 String v8::String::Utf8Value v8str(args[0]); // For more info on the Notify library: http://library.gnome.org/devel/libnotify/0.7/NotifyNotification.html Notify::init("Basic"); // Arguments: title, content, icon Notify::Notification n(gtknotify_instance->title.c_str(), *v8str, gtknotify_instance->icon.c_str()); // *v8str points to the C string it wraps // Display the notification n.show(); // Return value return v8::Boolean::New(true); }
Erweiterung kompilieren
node-waf ist ein Build-Tool zum Kompilieren von Node-Erweiterungen, dem Basispaket von waf. Der Build-Prozess kann über eine Datei namens wscript konfiguriert werden.
def set_options(opt): opt.tool_options("compiler_cxx") def configure(conf): conf.check_tool("compiler_cxx") conf.check_tool("node_addon") # This will tell the compiler to link our extension with the gtkmm and libnotifymm libraries. conf.check_cfg(package='gtkmm-2.4', args='--cflags --libs', uselib_store='LIBGTKMM') conf.check_cfg(package='libnotifymm-1.0', args='--cflags --libs', uselib_store='LIBNOTIFYMM') def build(bld): obj = bld.new_task_gen("cxx", "shlib", "node_addon") obj.cxxflags = ["-g", "-D_FILE_OFFSET_BITS=64", "-D_LARGEFILE_SOURCE", "-Wall"] # This is the name of our extension. obj.target = "gtknotify" obj.source = "src/node_gtknotify.cpp" obj.uselib = ['LIBGTKMM', 'LIBNOTIFYMM']
Da wir nun mit dem Erstellen beginnen können, führen Sie den folgenden Befehl im Verzeichnis der obersten Ebene aus:
node-waf configure && node-waf build
Wenn alles gut geht, erhalten wir die kompilierte Erweiterung unter: ./build/default/gtknotify.node, versuchen wir es:
$ node > var notif = require('./build/default/gtknotify.node'); > n = new notif.notification(); { icon: 'terminal', title: 'Node.js' } > n.send("Hello World!"); true
Der obige Code zeigt eine Benachrichtigung in der oberen rechten Ecke Ihres Bildschirms an.
Verpackt im npm-Paket
Das ist sehr cool, aber wie teilen Sie die Ergebnisse Ihrer Bemühungen mit der Node-Community? Dies ist der Hauptzweck von npm: die Erweiterung und Verteilung zu vereinfachen.
Das Packen einer Erweiterung mit npm ist sehr einfach. Sie müssen lediglich eine Datei package.json in Ihrem obersten Verzeichnis erstellen, die Ihre Erweiterungsinformationen enthält:
{ // 扩展的名称 (不要在名称中包含node 或者 js, 这是隐式关键字). // 这是通过require() 导入扩展的名称. "name" : "notify", // Version should be http://semver.org/ compliant "version" : "v0.1.0" // 这些脚本将在调用npm安装和npm卸载的时候运行. , "scripts" : { "preinstall" : "node-waf configure && node-waf build" , "preuninstall" : "rm -rf build/*" } // 这是构建我们扩展的相对路径. , "main" : "build/default/gtknotify.node" // 以下是可选字段: , "description" : "Description of the extension...." , "homepage" : "https://github.com/olalonde/node-notify" , "author" : { "name" : "Olivier Lalonde" , "email" : "olalonde@gmail.com" , "url" : "http://www.syskall.com/" } , "repository" : { "type" : "git" , "url" : "https://github.com/olalonde/node-notify.git" } }
Weitere Informationen zum package.json-Format finden Sie in der Dokumentation über npm help json. Beachten Sie, dass die meisten Felder optional sind.
Sie können Ihr neues npm-Paket jetzt installieren, indem Sie npm install in Ihrem Verzeichnis der obersten Ebene ausführen. Wenn alles gut geht, sollten Sie einfach Ihre Erweiterung var notify = require('your package name'); laden können npm link. Mit diesem Befehl können Sie einen Link zu Ihrem Entwicklungsverzeichnis erstellen, sodass Sie nicht jedes Mal installieren/deinstallieren müssen, wenn sich Ihr Code ändert
$ npm adduser
下一步, 回到你的根目录编码并且运行:
$ npm publish
就是这样, 你的包现在已经可以被任何人通过npm install 你的包名命令来安装了.