Node.js插件的正确编写方式


Posted in Javascript onAugust 03, 2014

Node.js在利用JavaScript编写后端方面效果拔群,值得我们多加尝试。不过如果大家需要一些无法直接使用的功能甚至是根本无从实现的模块使用,那么能否从C/C++库当中引入此类成果呢?答案是肯定的,大家要做的就是编写一款插件,并借此在自己的JavaScript代码中使用其它代码库的资源。下面我们就一同开始今天的探询之旅。

介绍

正如Node.js在官方说明文档中所言,插件是以动态方式进行链接的共享式对象,能够将JavaScript代码与C/C++库接驳起来。这意味着我们可以引用任何来自C/C++库中的内容,并通过创建插件的方式将其纳入到Node.js当中。

作为实例,我们将为标准std::string对象创建一套封装。

准备工作

在我们开始编写工作之前,大家首先需要确保自己已经准备好所有后续模块编译所需要的素材。大家需要node-gyp及其全部依赖关系。大家可以利用以下命令安装node-gyp:

npm install -g node-gyp

 在依赖性方面,我们需要为Unix系统准备以下项目:• Python (要求2.7版本, 3.x无法正常起效)

• make

• 一款C++编译器工具链(例如gpp或者g++)

举例来说,在Ubuntu上大家可以利用以下命令安装所有上述项目(其中Python 2.7应该已经预先安装完毕了):

sudo apt-get install build-essentials

在Windows系统环境下,大家需要的是:

• Python (2.7.3版本, 3.x无法正常起效)

• 微软Visual Studio C++ 2010 (适用于Windows XP/Vista)

• 微软Visual Studio C++ 2012 for Windows Desktop (适用于Windows 7/8)

强调一点,Visual Studio的Express版本也能正常起效。

binding.gyp文件

该文件由node-gyp使用,旨在为我们的插件生成适当的build文件。大家可以点击此处查看维基百科提供的.gyp文件说明文档,但今天我们要使用的实例非常简单、因此只需使用以下代码即可:

{ 
  "targets": [ 
    { 
      "target_name": "stdstring", 
      "sources": [ "addon.cc", "stdstring.cc" ] 
    } 
  ] 
}

其中target_name可以设置为大家喜欢的任何内容。而sources数组当中包含该插件需要用到的所有源文件。在我们的实例中还包括addon.cc,它的作用在于容纳编译插件及stdstring.cc所必需的代码,外加我们的封装类。

STDStringWrapper类

第一步,我们要做的是在stdstring.h文件当中定义自己的类。如果大家对于C++编程比较熟悉,那么也一定不会对以下两行代码感到陌生。

#ifndef STDSTRING_H 
#define STDSTRING_H

这属于标准的include guard。接下来,我们需要将以下两个header纳入include范畴:

#include 
#include
第一个面向的是std::string类,而第二个include则作用于全部与Node以及V8相关的内容。

这一步完成之后,我们可以对自己的类进行声明:

class STDStringWrapper : public node::ObjectWrap {
对于所有我们打算包含在插件当中的类来说,我们必须扩展node::ObjectWrap类。

现在我们可以开始定义该类的private属性了:

private: 
  std::string* s_; 
  
  explicit STDStringWrapper(std::string s = ""); 
  ~STDStringWrapper();

除了构造函数与解析函数,我们还需要为std::string定义一个指针。这是该技术的核心所在,能够被用于将C/C++代码库与Node相对接——我们为该C/C++类定义一个私有指针,并将在随后的所有方法中利用该指针实现操作。

现在我们声明的constructor静态属性,它将为我们在V8中创建的类提供函数:

static v8::Persistent constructor;
感兴趣的朋友可以点击此处参阅模板说明方案以获取更多细节信息。

现在我们还需要一个New方法,它将被分配给前面提到的constructor,同时V8会对我们的类进行初始化:

static v8::Handle New(const v8::Arguments& args);
 作用于V8的每一个函数都应该遵循以下要求:它将接受指向v8::Arguments对象的引用,并返回一个v8::Handle>v8::Value>——这正是我们在使用强类型C++编码时,V8处理弱类型JavaScript的一贯方式。

在此之后,我们还需要将另外两个方法插入到对象的原型当中:

static v8::Handle add(const v8::Arguments& args); 
static v8::Handle toString(const v8::Arguments& args);

其中toString()方法允许我们在将其与普通JavaScript字符串共同使用时获得s_的值而非[Object object]的值。

最后,我们将引入初始化方法(此方法将由V8调用并指派给constructor函数)并关闭include guard:

public: 
    static void Init(v8::Handle exports); 
}; 
  
#endif

其中exports对象在JavaScript模块中的作用等同于module.exports。

stdstring.cc文件、构造函数与解析函数

现在来创建stdstring.cc文件。我们首先需要include我们的header:

#include "stdstring.h"

下面为constructor定义属性(因为它属于静态函数):

v8::Persistent STDStringWrapper::constructor;

这个为类服务的构造函数将分配s_属性:

STDStringWrapper::STDStringWrapper(std::string s) { 
  s_ = new std::string(s); 
}

而解析函数将对其进行delete,从而避免内存溢出:

STDStringWrapper::~STDStringWrapper() { 
  delete s_; 
}

再有,大家必须delete掉所有与new一同分配的内容,因为每一次此类情况都有可能造成异常,因此请牢牢记住上述操作或者使用共享指针。

Init方法

该方法将由V8加以调用,旨在对我们的类进行初始化(分配constructor,将我们所有打算在JavaScript当中使用的内容安置在exports对象当中):

void STDStringWrapper::Init(v8::Handle exports) {
首先,我们需要为自己的New方法创建一个函数模板:

v8::Local tpl = v8::FunctionTemplate::New(New);
这有点类似于JavaScipt当中的new Function——它允许我们准备好自己的JavaScript类。

现在我们可以根据实际需要为该函数设定名称了(如果大家漏掉了这一步,那么构造函数将处于匿名状态,即名称为function someName() {}或者function () {}):

tpl->SetClassName(v8::String::NewSymbol("STDString"));
我们利用v8::String::NewSymbol()来创建一个用于属性名称的特殊类型字符串——这能为引擎的运作节约一点点时间。

在此之后,我们需要设定我们的类实例当中包含多少个字段:

tpl->InstanceTemplate()->SetInternalFieldCount(2);
我们拥有两个方法——add()与toString(),因此我们将数量设置为2。现在我们可以将自己的方法添加到函数原型当中了:

tpl->PrototypeTemplate()->Set(v8::String::NewSymbol("add"), v8::FunctionTemplate::New(add)->GetFunction()); 
tpl->PrototypeTemplate()->Set(v8::String::NewSymbol("toString"), v8::FunctionTemplate::New(toString)->GetFunction());
这部分代码量看起来比较大,但只要认真观察大家就会发现其中的规律:我们利用tpl->PrototypeTemplate()->Set()来添加每一个方法。我们还利用v8::String::NewSymbol()为它们提供名称与FunctionTemplate。

最后,我们可以将该构造函数安置于我们的constructor类属性内的exports对象中:

constructor = v8::Persistent::New(tpl->GetFunction()); 
  exports->Set(v8::String::NewSymbol("STDString"), constructor); 
}

New方法

现在我们要做的是定义一个与JavaScript Object.prototype.constructor运作效果相同的方法:

v8::Handle STDStringWrapper::New(const v8::Arguments& args) {

 我们首先需要为其创建一个范围:

v8::HandleScope scope;

在此之后,我们可以利用args对象的.IsConstructCall()方法来检查该构造函数是否能够利用new关键词加以调用:

if (args.IsConstructCall()) {

如果可以,我们首先如下所示将参数传递至std::string处:

v8::String::Utf8Value str(args[0]->ToString()); 
std::string s(*str);

……这样我们就能将它传递到我们封装类的构造函数当中了:

STDStringWrapper* obj = new STDStringWrapper(s);

在此之后,我们可以利用之前创建的该对象的.Wrap()方法(继承自node::ObjectWrap)来将它分配给this变量:

obj->Wrap(args.This());

最后,我们可以返回这个新创建的对象:

return args.This();

如果该函数无法利用new进行调用,我们也可以直接调用构造函数。接下来,我们要做的是为参数计数设置一个常数:

} else { 
  const int argc = 1;

现在我们需要利用自己的参数创建一个数组:

v8::Local argv[argc] = { args[0] };

然后将constructor->NewInstance方法的结果传递至scope.Close,这样该对象就能在随后发挥作用(scope.Close基本上允许大家通过将对象处理句柄移动至更高范围的方式对其加以维持——这也是函数的起效方式):

return scope.Close(constructor->NewInstance(argc, argv)); 
  } 
}

add方法

现在让我们创建add方法,它的作用是允许大家向对象的内部std::string添加内容:

v8::Handle STDStringWrapper::add(const v8::Arguments& args) {

首先,我们需要为我们的函数创建一个范围,并像之前那样把该参数转换到std::string当中:

v8::HandleScope scope; 
  
v8::String::Utf8Value str(args[0]->ToString()); 
std::string s(*str);

现在我们需要对该对象进行拆包。我们之前也进行过这种反向封装操作——这一次我们是要从this变量当中获取指向对象的指针。

STDStringWrapper* obj = ObjectWrap::Unwrap(args.This());

接着我们可以访问s_属性并使用其.append()方法:

obj->s_->append(s);

最后,我们返回s_属性的当前值(需要再次使用scope.Close):

return scope.Close(v8::String::New(obj->s_->c_str()));

由于v8::String::New()方法只能将char pointer作为值来接受,因此我们需要使用obj->s_->c_str()来加以获取。

这时大家的插件文件夹中还应该创建出一个build目录。

测试

现在我们可以对自己的插件进行测试了。在我们的插件目录中创建一个test.js文件以及必要的编译库(大家可以直接略过.node扩展):

var addon = require('./build/Release/addon');

下一步,为我们的对象创建一个新实例:

var test = new addon.STDString('test');

下面再对其进行操作,例如添加或者将其转化为字符串:

test.add('!'); 
console.log('test\'s contents: %s', test);

在运行之后,大家应该在控制台中看到以下执行结果:

结论

我希望大家能在阅读了本教程之后打消顾虑,将创建与测试以C/C++库为基础的定制化Node.js插件视为一项无甚难度的任务。大家可以利用这种技术轻松将几乎任何C/C++库引入Node.js当中。如果大家愿意,还可以根据实际需求为插件添加更多功能。std::string当中提供大量方法,我们可以将它们作为练习素材。

实用链接

感兴趣的朋友可以查看以下链接以获取更多与Node.js插件开发、V8以及C事件循环库相关的资源与详细信息。

• Node.js插件说明文档

• V8说明文档

• libuv (C事件循环库),来自GitHub

英文:http://code.tutsplus.com/tutorials/writing-nodejs-addons--cms-21771

Javascript 相关文章推荐
javascript 中对象的继承〔转贴〕
Jan 22 Javascript
js处理自己不能定义二维数组的方法详解
Mar 03 Javascript
jQuery实现的倒计时效果实例小结
Apr 16 Javascript
深入理解Javascript中的自执行匿名函数
Jun 03 Javascript
js 基础篇必看(点击事件轮播图的简单实现)
Aug 20 Javascript
JSON键值对序列化和反序列化解析
Jan 24 Javascript
jQuery插件HighCharts实现的2D面积图效果示例【附demo源码下载】
Mar 15 Javascript
layui分页效果实现代码
May 19 Javascript
JavaScript如何对图片进行黑白化
Apr 10 Javascript
通过npm或yarn自动生成vue组件的方法示例
Feb 12 Javascript
js数组中去除重复值的几种方法
Aug 03 Javascript
vue数据字典取键值项目的字典问题
Apr 12 Vue.js
基于jquery实现的可编辑下拉框实现代码
Aug 02 #Javascript
基于编写jQuery的无缝滚动插件
Aug 02 #Javascript
js使用removeChild方法动态删除div元素
Aug 01 #Javascript
js使用html()或text()方法获取设置p标签的显示的值
Aug 01 #Javascript
js中的getAttribute方法使用示例
Aug 01 #Javascript
jquery append()方法与html()方法的区别及使用介绍
Aug 01 #Javascript
JS中产生20位随机数以0-9为例也可以是a-z A-Z
Aug 01 #Javascript
You might like
基于PHP Web开发MVC框架的Smarty使用说明
2013/04/19 PHP
PHP CLI模式下的多进程应用分析
2013/06/03 PHP
php批量添加数据与批量更新数据的实现方法
2014/12/16 PHP
PHP生成唯一订单号的方法汇总
2015/04/16 PHP
基于PHP实现假装商品限时抢购繁忙的效果
2015/10/16 PHP
php结合web uploader插件实现分片上传文件
2016/05/10 PHP
PHP基于mssql扩展远程连接MSSQL的简单实现方法
2016/10/08 PHP
Laravel框架使用Redis的方法详解
2018/05/30 PHP
css图片自适应大小
2007/11/28 Javascript
jquery photoFrame 图片边框美化显示插件
2010/06/28 Javascript
JavaScript.The.Good.Parts阅读笔记(二)作用域&闭包&减缓全局空间污染
2010/11/16 Javascript
js 通用javascript函数库整理
2011/08/14 Javascript
js 获取坐标 通过JS得到当前焦点(鼠标)的坐标属性
2013/01/04 Javascript
js 中的switch表达式使用示例
2020/06/03 Javascript
编写自己的jQuery提示框(Tip)插件
2015/02/05 Javascript
Vue.js动态添加、删除选题的实例代码
2016/09/30 Javascript
基于javascript实现最简单选项卡切换
2017/02/01 Javascript
JavaScript基础进阶之数组方法总结(推荐)
2017/09/04 Javascript
vue 解决addRoutes动态添加路由后刷新失效问题
2018/07/02 Javascript
layui表格 列自动适应大小失效的解决方法
2019/09/06 Javascript
JavaScript字符串处理常见操作方法小结
2019/11/15 Javascript
用js编写留言板
2020/03/17 Javascript
python实现的简单FTP上传下载文件实例
2015/06/30 Python
python实现文件快照加密保护的方法
2015/06/30 Python
Python实现读取文件最后n行的方法
2017/02/23 Python
python使用原始套接字发送二层包(链路层帧)的方法
2019/07/22 Python
PyTorch中Tensor的维度变换实现
2019/08/18 Python
python+pygame实现坦克大战小游戏的示例代码(可以自定义子弹速度)
2020/08/11 Python
Petmate品牌官方网站:宠物用品
2018/11/25 全球购物
Airbnb爱彼迎官网:成为爱彼迎房东,赚取收入
2019/03/14 全球购物
精彩的广告词
2014/03/19 职场文书
文秘班元旦晚会活动策划方案
2014/08/28 职场文书
2014年向国旗敬礼活动方案
2014/09/27 职场文书
2015年党员创先争优承诺书
2015/01/22 职场文书
围城读书笔记
2015/06/26 职场文书
MySQL数据库完全卸载的方法
2022/03/03 MySQL