使用Node.js为其他程序编写扩展的基本方法


Posted in Javascript onJune 23, 2015

 准备开始

首先我们用下面的目录结构来创建一个节点通知(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");

编写我们的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()方法中:

    声明构造函数,并将其绑定到我们的目标变量。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());

    声明属性: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()
    声明原型方法: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

构造器方法: 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);
}

编译扩展

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

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

打成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 获取文档. 注意 大多数字段都是可选的.

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

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

 

$ npm adduser

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

$ npm publish

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

 

Javascript 相关文章推荐
用js重建星际争霸
Dec 22 Javascript
动态为事件添加js代码示例
Feb 15 Javascript
jQuery.buildFragment使用方法及思路分析
Jan 07 Javascript
JavaScript中的onerror事件概述及使用
Apr 01 Javascript
js随机颜色代码的多种实现方式
Apr 23 Javascript
使用AngularJS来实现HTML页面嵌套的方法
Jun 17 Javascript
node模块机制与异步处理详解
Mar 13 Javascript
微信小程序实现YDUI的ScrollNav组件
Feb 02 Javascript
vue bus全局事件中心简单Demo详解
Feb 26 Javascript
Centos7 安装Node.js10以上版本的方法步骤
Oct 15 Javascript
vue 实现cli3.0中使用proxy进行代理转发
Oct 30 Javascript
vue+elementUI动态生成面包屑导航教程
Nov 04 Javascript
Windows系统下Node.js的简单入门教程
Jun 23 #Javascript
jQuery实现判断滚动条到底部
Jun 23 #Javascript
jQuery实现新消息在网页标题闪烁提示
Jun 23 #Javascript
使用Raygun对Node.js应用进行错误处理的方法
Jun 23 #Javascript
javascript创建函数的20种方式汇总
Jun 23 #Javascript
使用Node.js实现HTTP 206内容分片的教程
Jun 23 #Javascript
jquery.gridrotator实现响应式图片展示画廊效果
Jun 23 #Javascript
You might like
PHP中运用jQuery的Ajax跨域调用实现代码
2012/02/21 PHP
调用WordPress函数统计文章访问量及PHP原生计数器的实现
2016/03/21 PHP
php实现的统计字数函数定义与使用示例
2017/07/26 PHP
jQuery后代选择器用法实例
2014/12/23 Javascript
JavaScript实现单击下拉框选择直接跳转页面的方法
2015/07/02 Javascript
nodejs创建web服务器之hello world程序
2015/08/20 NodeJs
JQuery实现简单的图片滑动切换特效
2015/11/22 Javascript
基于JavaScript实现表单密码的隐藏和显示出来
2016/03/02 Javascript
jQuery简单实现列表隐藏和显示效果示例
2016/09/12 Javascript
javascript中数组(Array)对象和字符串(String)对象的常用方法总结
2016/12/15 Javascript
微信小程序中显示html格式内容的方法
2017/04/25 Javascript
详解Node.js 命令行程序开发教程
2017/06/07 Javascript
JS动态修改网页body的背景色实例代码
2017/10/07 Javascript
微信小程序实现图片放大预览功能
2020/10/22 Javascript
vue从一个页面跳转到另一个页面并携带参数的解决方法
2019/08/12 Javascript
React实现类似淘宝tab居中切换效果的示例代码
2020/06/02 Javascript
JavaScript实现弹出窗口效果
2020/12/09 Javascript
Python如何实现MySQL实例初始化详解
2017/11/06 Python
解决tensorflow1.x版本加载saver.restore目录报错的问题
2018/07/26 Python
Python DataFrame.groupby()聚合函数,分组级运算
2018/09/18 Python
tensorflow入门:TFRecordDataset变长数据的batch读取详解
2020/01/20 Python
Python常用类型转换实现代码实例
2020/07/28 Python
容易被忽略的Python内置类型
2020/09/03 Python
CSS3关于z-index不生效问题的解决
2020/02/19 HTML / CSS
世界上最大的巴士旅游观光公司:Big Bus Tours
2016/10/20 全球购物
科技开发中心办公室主任岗位责任制
2014/02/10 职场文书
就业协议书的作用
2014/04/11 职场文书
法学专业求职信
2014/07/15 职场文书
村级个人对照检查材料
2014/08/22 职场文书
2014最新版群众路线四风整改措施
2014/09/24 职场文书
正风肃纪剖析材料
2014/09/30 职场文书
网球场地租赁协议范本
2014/10/07 职场文书
会计岗位职责
2015/02/03 职场文书
婚礼伴郎致辞
2015/07/28 职场文书
如何使JavaScript休眠或等待
2021/04/27 Javascript
python中的3种定义类方法
2021/11/27 Python