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 基础问答三
Dec 03 Javascript
JQuery优缺点分析说明
Jun 09 Javascript
基于jquery的内容循环滚动小模块(仿新浪微博未登录首页滚动微博显示)
Mar 28 Javascript
javascript中关于执行环境的杂谈
Aug 14 Javascript
js FLASH幻灯片字符串中有连接符&的处理方法
Mar 01 Javascript
JQuery 中几个类选择器的简单使用介绍
Mar 14 Javascript
JavaScript利用HTML DOM进行文档操作的方法
Mar 28 Javascript
javascript的列表切换【实现代码】
May 03 Javascript
js通过classname来获取元素的方法
Nov 24 Javascript
jQuery的ztree仿windows文件新建和拖拽功能的实现代码
Dec 05 jQuery
JS实现简单的抽奖转盘效果示例
Feb 16 Javascript
jquery html添加元素/删除元素操作实例详解
May 20 jQuery
基于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 和 XML: 使用expat函数(三)
2006/10/09 PHP
在PHP中使用curl_init函数的说明
2010/11/02 PHP
PHP第一季视频教程(李炎恢+php100 不断更新)
2011/05/29 PHP
php递归方法实现无限分类实例代码
2014/02/28 PHP
PHP会话处理的10个函数
2015/08/11 PHP
(推荐一个超好的JS函数库)S.Sams Lifexperience ScriptClassLib
2007/04/29 Javascript
document.onreadystatechange事件的用法分析
2009/10/17 Javascript
广泛收集的jQuery拖放插件集合
2012/04/09 Javascript
jquery ui对话框实例代码
2013/05/10 Javascript
js操作table示例(个人心得)
2013/11/29 Javascript
JQUERY dialog的用法详细解析
2013/12/19 Javascript
Shell脚本实现Linux系统和进程资源监控
2015/03/05 Javascript
jquery实现鼠标悬浮停止轮播特效
2020/08/20 Javascript
简单实现JavaScript图片切换效果
2016/11/28 Javascript
html5 canvas 详细使用教程
2017/01/20 Javascript
JS基于ES6新特性async await进行异步处理操作示例
2019/02/02 Javascript
Vue中使用canvas方法总结
2019/02/12 Javascript
echarts实现折线图的拖拽效果
2019/12/19 Javascript
[48:23]DOTA2上海特级锦标赛主赛事日 - 4 败者组第四轮#1COL VS EG第一局
2016/03/05 DOTA
Python写的贪吃蛇游戏例子
2014/06/16 Python
Python进程间通信之共享内存详解
2017/10/30 Python
django开发post接口简单案例,获取参数值的方法
2018/12/11 Python
python pandas cumsum求累计次数的用法
2019/07/29 Python
pytorch 加载(.pth)格式的模型实例
2019/08/20 Python
python字符串反转的四种方法详解
2019/12/02 Python
快速查找Python安装路径方法
2020/02/06 Python
解决Python paramiko 模块远程执行ssh 命令 nohup 不生效的问题
2020/07/14 Python
如何用python爬取微博热搜数据并保存
2021/02/20 Python
Hello Molly美国:女性时尚在线
2019/08/26 全球购物
世界上最大的字体市场:MyFonts
2020/01/10 全球购物
PHP面试题附答案
2015/11/28 面试题
制定岗位职责的原则
2013/11/08 职场文书
高中的自我鉴定
2013/12/16 职场文书
给领导的检讨书
2014/02/16 职场文书
《珍珠泉》教学反思
2014/02/20 职场文书
人民教师的自我评价分享
2014/02/21 职场文书