使用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 相关文章推荐
让jQuery Mobile不显示讨厌loading界面的方法
Feb 19 Javascript
Node.js和PHP根据ip获取地理位置的方法
Mar 14 Javascript
用队列模拟jquery的动画算法实例
Jan 20 Javascript
Javascript无参数和有参数类继承问题解决方法
Mar 02 Javascript
JQuery DIV 动态隐藏和显示的方法
Jun 23 Javascript
js跨域资源共享 基础篇
Jul 02 Javascript
第一次动手实现bootstrap table分页效果
Sep 22 Javascript
Node.js包管理器Yarn的入门介绍与安装
Oct 17 Javascript
Javascript 实现计算器时间功能详解及实例(二)
Jan 08 Javascript
微信小程序 基础知识css样式media标签
Feb 15 Javascript
关于layui 实现点击按钮添加一行(方法渲染创建的table)
Sep 29 Javascript
jQuery实现移动端图片上传预览组件的方法分析
May 01 jQuery
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使用Jpgraph绘制饼状图的方法
2015/06/10 PHP
PHP表单数据写入MySQL数据库的代码
2016/05/31 PHP
JavaScript获取/更改文本框的值的实例代码
2013/08/02 Javascript
Javascript selection的兼容性写法介绍
2013/12/20 Javascript
jquery+css3实现网页背景花瓣随机飘落特效
2015/08/17 Javascript
自己动手写的javascript前端等待控件
2015/10/30 Javascript
基于BootStrap Metronic开发框架经验小结【七】数据的导入、导出及附件的查看处理
2016/05/12 Javascript
解析微信JS-SDK配置授权,实现分享接口
2016/12/09 Javascript
基于JS实现弹出一个隐藏的div窗口body页面变成灰色并且不可被编辑
2016/12/14 Javascript
微信小程序实现图片预览功能
2018/01/31 Javascript
微信小程序支付前端源码
2018/08/29 Javascript
nodejs实现范围请求的实现代码
2018/10/12 NodeJs
vue强制刷新组件的方法示例
2019/02/28 Javascript
JQuery animate动画应用示例
2019/05/14 jQuery
回顾Javascript React基础
2019/06/15 Javascript
Node.js实现批量下载图片简单操作示例
2020/01/18 Javascript
maptalks+three.js+vue webpack实现二维地图上贴三维模型操作
2020/08/10 Javascript
[44:40]Serenity vs Pain 2018国际邀请赛小组赛BO2 第一场 8.19
2018/08/21 DOTA
利用Python爬取可用的代理IP
2016/08/18 Python
python数据结构之链表详解
2017/09/12 Python
Python实现将HTML转换成doc格式文件的方法示例
2017/11/20 Python
python的unittest测试类代码实例
2017/12/07 Python
QML使用Python的函数过程解析
2019/09/26 Python
JetBrains PyCharm(Community版本)的下载、安装和初步使用图文教程详解
2020/03/19 Python
keras分类之二分类实例(Cat and dog)
2020/07/09 Python
Html5之自定义属性(data-,dataset)
2019/11/19 HTML / CSS
绿色美容,有机护肤品和化妆品:Safe & Chic
2018/10/29 全球购物
采购主管的岗位职责
2013/12/17 职场文书
24岁生日感言
2014/01/13 职场文书
网上卖盒饭创业计划书
2014/01/26 职场文书
2015年安全保卫工作总结
2015/05/14 职场文书
公司欠款证明
2015/06/24 职场文书
《假如》教学反思
2016/02/17 职场文书
财务年终工作总结大全
2019/06/20 职场文书
大学生饮品店创业计划书范文
2019/07/10 职场文书
解决Golang time.Parse和time.Format的时区问题
2021/04/29 Golang