如何编写 Node.js 扩展 已翻译 100%

oschina 投递于 2013/05/12 21:58 (共 8 段, 翻译完成于 05-13)
阅读 6386
收藏 87
7
加载中

引言

这里是继续跟进 (如何推出你自己V8版本Javascript API) How to roll out your own Javascript API with V8. 如果你没有读过它的话你应该继续跟进.

我们现在将(为V8写的代码) code we have written for V8 转向 Node.js 并且使用npm打包.

node-notify screenshot

完整的代码可以从这里获取 from github:

git clone git://github.com/olalonde/jsnotify.git

你也可以通过npm来安装:

npm install notify

代码测试环境: Ubuntu 10.10 64位  Node.js v0.5.0-pre.

skyline520
翻译于 2013/05/12 22:38
3

准备开始

首先我们用下面的目录结构来创建一个节点通知(node-notify)文件夹.

.
|-- build/                   # This is where our extension is built. 
|-- demo/
|   `-- demo.js              # This is a demo Node.js script to test our extension.
|-- src/
|   `-- node_gtknotify.cpp   # This is the where we do the mapping from C++ to Javascript.
`-- wscript                  # This is our build configuration used by node-waf

这个看起来很漂亮的tree 用通用的 tree 生成.

现在让我来创建测试脚本demo.js 和决定我们扩展的API前期看起来应该像:

// 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");
skyline520
翻译于 2013/05/12 22:56
1

编写我们的Node.js扩展

Init方法

为了创建一个Node.js扩展,我们需要编写一个继承node::ObjectWrap的C++类。 ObjectWrap 实现了让我们更容易与Javascript交互的公共方法

我们先来编写类的基本框架:

#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);
}

现在,我们必须把下面的代码编写到我们的Init()方法中:

  1. 声明构造函数,并将其绑定到我们的目标变量。var n = require("notification");将绑定notification() 到 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());
  1. 声明属性:n.title 和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()
  1. 声明原型方法:n.send()
    // 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);

现在我们的Init()方法看起来应该是这样的:

// 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());
}

剩下要做的就是编写我们在Init方法中用的C++方法:New,GetTitle,SetTitle,GetIcon,SetIcon,Send

DYOS
翻译于 2013/05/13 16:28
2

构造器方法: New()

New() 方法创建了我们自定义类的新实例(一个 Gtknotify 对象),并设置一些初始值,然后返回该对象的 JavaScript 处理。这是 JavaScript 使用 new 操作符调用构造函数的期望行为。

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()

下面主要是一些样板代码,可以归结为 C++ 和 JavaScript (v8) 之间的值转换。

// 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;
}

原型方法: Send()

首先我们抽取 C++ 对象的 this 引用,然后使用对象的属性来构建通知并显示。

// 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);
}
小编辑
翻译于 2013/05/12 23:05
3

编译扩展

node-waf 是一个构建工具,用来编译 Node 的扩展,这是 waf 的基本封装。构建过程可通过名为 wscript 的文件进行配置。

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']

现在我们已经准备好要开始构建了,在顶级目录下运行如下命令:

node-waf configure && node-waf build

如果一切正常,我们将得到编译过的扩展,位于:./build/default/gtknotify.node ,来试试:

$ node
> var notif = require('./build/default/gtknotify.node');
> n = new notif.notification();
{ icon: 'terminal', title: 'Node.js' }
> n.send("Hello World!");
true

上述的代码将在你的屏幕右上方显示一个通知信息。

小编辑
翻译于 2013/05/12 23:08
3

打成npm包

这是非常酷的, 但是怎样与Node社区分享你的努力的成果呢? 这才是npm主要的用途: 使它更加容易扩展和分发.

打npm的扩展包是非常简单的. 你所要做的就是在你的顶级目录中创建一个包含你的扩展信息的文件package.json :

{
  // 扩展的名称 (不要在名称中包含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"
    }
}

关于package.json 格式的更多细节, 可以通过 npm help json 获取文档. 注意 大多数字段都是可选的.

skyline520
翻译于 2013/05/13 18:11
1

你现在可以在你的顶级目录中通过运行npm install 来安装你的新的npm包了. 如果一切顺利的话, 应该可以简单的加载你的扩展 var notify = require('你的包名');. 另外一个比较有用的命令式 npm link 通过这个命令你可以创建一个到你开发目录的链接,当你的代码发生变化时不必每次都去安装/卸载.

假设你写了一个很酷的扩展, 你可能想要在中央npm库发布到网上. 首先你要先创建一个账户:

$ npm adduser

下一步, 回到你的根目录编码并且运行:

$ npm publish

就是这样, 你的包现在已经可以被任何人通过npm install 你的包名命令来安装了.

skyline520
翻译于 2013/05/13 18:45
1

结论

编写一个原创的Node扩展可能有点麻烦和繁琐,但这是非常值得你去获得得瑟的资本!

感谢您的阅读。如果您遇到任何问题可以通过评论告诉我,我很乐意提供帮助。

如果你喜欢这篇文章,或许你也喜欢我在Twitter上的微薄!甚至可能要聘请我?

参考文献

DYOS
翻译于 2013/05/12 22:44
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(8)

crossmix
crossmix

OK

程序员老雷
程序员老雷
代码测试环境: Ubuntu 10.10 64位 Node.js v0.5.0-pre. 。。。。。。。。。。得翻译一些较新的文章啊,尤其是对于这种正在“高速发展”的东西
打杂程序猿
打杂程序猿
v0.5.0 ??两年前的文章来了吧。。。
每天打起精神即可
每天打起精神即可

引用来自“se77en”的评论

我一直以为小编辑只是个打杂的呢!原来深藏功与名啊!!!

我也以为只是官方微博 = =!!!
se77en
se77en
我一直以为小编辑只是个打杂的呢!原来深藏功与名啊!!!
Neeke
Neeke
基本上是扫盲贴
z
zhs077
现在都用node-gyp
luwenhua
luwenhua
oschina 功德无量!
返回顶部
顶部