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

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

(0)

相关推荐

  • node.js插件nodeclipse安装图文教程

    在eclipse插件中,node.js插件中比较知名的是nodeclipse,本文为大家介绍了nodeclipse安装教程,供大家参考,具体内容如下 安装nodeclipse 1. 依次点击工具→插件安装→浏览eclipse插件市场→搜索node.js→在搜索结果里找nodeclipse如下图 2.点击上图中的install开始加载,加载完毕点下一步接受许可协议静待安装完成即可 3. 安装完毕后重启HBuilder 4. 新建node项目:依次点击菜单文件→新建→其他如下图 5.选择要新建的no

  • Node.js:Windows7下搭建的Node.js服务(来玩玩服务器端的javascript吧,这可不是前端js插件)

    这里只是纯粹的搭建,连环境都没有,还玩什么服务器端js,一切都成了浮云,让我们先搭建一个环境,输入一个"hello world"的页面. 对的,windows7下的搭建,你只需一步一步跟着我做,就ok了,不用去了解过多的细节,那不是我们现在要关心的,我们现在首要目的是把环境搭建好,要不然就没有下一步了. Step 1. 下载node.js在windows下是要安装在Cygwin下的,去Cygwin网站下载Cygwin安装程序. Cygwin网站:http://cygwin.com/ 直

  • node.js使用npm 安装插件时提示install Error: ENOENT报错的解决方法

    在使用npm install安装扩展插件时,系统提示"npm install Error: ENOENT, stat 'C:Users<用户名>AppDataRoamingnpm'". 以前都是很顺利的安装过程,没出现这种情况.我这里的解决办法是直接创建上面提示的目录就好了,应该是node.js权限不够,在此备注. 有时候就是这样,很可能一个很久都无法解决的问题,实际解决方法很简单,就是这么任性!!

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

    Node.js在利用JavaScript编写后端方面效果拔群,值得我们多加尝试.不过如果大家需要一些无法直接使用的功能甚至是根本无从实现的模块使用,那么能否从C/C++库当中引入此类成果呢?答案是肯定的,大家要做的就是编写一款插件,并借此在自己的JavaScript代码中使用其它代码库的资源.下面我们就一同开始今天的探询之旅. 介绍 正如Node.js在官方说明文档中所言,插件是以动态方式进行链接的共享式对象,能够将JavaScript代码与C/C++库接驳起来.这意味着我们可以引用任何来自C/

  • Node.js插件安装图文教程

    Node.js简介 Node.js是一个Javascript运行环境(runtime).实际上它是对Google V8引擎进行了封装.V8引 擎执行Javascript的速度非常快,性能非常好.Node.js对一些特殊用例进行了优化,提供了替代的API,使得V8在非浏览器环境下运行得更好. Node.js是一个基于Chrome JavaScript运行时建立的平台, 用于方便地搭建响应速度快.易于扩展的网络应用.Node.js 使用事件驱动, 非阻塞I/O 模型而得以轻量和高效,非常适合在分布式

  • Node.js基础入门之使用方式及模块化详解

    目录 什么是Node.js ? Node.js下载 Node.js和JavaScript的区别 Node.js安装与验证 Node.js使用方式 1. REPL模式 2. 文件模式 Node.js模块化 1. 什么是模块? 2. 模块分类 3. 创建自定义模块 4. 调用自定义模块 5. 模块测试 6. 主模块 7. 模块组成 在这个竞争日益激烈的今天,已经不是一门语言,一项技术走天下的时代了.正所谓艺多不压身,今天开始学习Node.js,学而时习之,不亦乐乎,希望可以借鉴经验,学以致用,如有不

  • 用NODE.JS中的流编写工具是要注意的事项

    Node.js中的流十分强大,它对处理潜在的大文件提供了支持,也抽象了一些场景下的数据处理和传递.正因为它如此好用,所以在实战中我们常常基于它来编写一些工具 函数/库 ,但往往又由于自己对流的某些特性的疏忽,导致写出的 函数/库 在一些情况会达不到想要的效果,或者埋下一些隐藏的地雷.本文将会提供两个在编写基于流的工具时,私以为有些用的两个tips. 一,警惕EVENTEMITTER内存泄露 在一个可能被多次调用的函数中,如果需要给流添加事件监听器来执行某些操作.那么则需要警惕添加监听器而导致的内

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

     准备开始 首先我们用下面的目录结构来创建一个节点通知(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_gtkno

  • @vue/cli4升级@vue/cli5 node.js polyfills错误的解决方式

    目录 一.错误描述 二.错误日志 1.日志内容: 2.错误原因 3.解决方法[可以使用的方法] 4.解决办法[存在问题,需要研究还] 总结 一.错误描述 因前端项目做的少,今天用 vue脚手架创建项目选择了 @vue/cli 5.0 版本,在编译项目时出现如下错误: 二.错误日志 1.日志内容: 错误1: error  in ./node_modules/jwa/index.js Module not found: Error: Can't resolve 'crypto' in 'H:\iWo

  • Node.js事件的正确使用方法

    前言 事件驱动的编程变得流行之前,在程序内部进行通信的标准方法非常简单:如果一个组件想要向另外一个发送消息,只是显式地调用了那个组件上的方法.但是在 react 中用的却是事件驱动而不是调用. 事件的好处 这种方法能够使组件更加分离.在我们继续写程序时,会识别整个过程中的事件,在正确的时间触发它们,并为每个事件附加一个或多个事件监听器,这使得功能扩展变得更加容易.我们可以为特定事件添加更多的 listener,而不必修改现有的侦听器或触发事件的应用程序部分.我们所谈论的是观察者模式. 设计一个事

  • Java开发者结合Node.js编程入门教程

    首先, 我必须得承认,作为一个有着十多年开发经验的java开发者,我已经形成了解决绝大部分问题的固有套路,尽管它们很多时候显得笨重和繁琐. 比如说如果要读取一个文件,那应该就是初始化一个BufferedReader 实例并传入一个FileReader,这几乎是顺理成章的,我在很多自认为算得上"企业级"的项目中编写这样的代码并且很享受这个过程,可以说我就是一个对其他语言不屑一顾的java脑残粉. 如果你正在阅读这篇博文,你可能已经陷入了我多年前早就陷入的一个误区,作为一名合格的开发人员应

  • Node.js 制作实时多人游戏框架

    在 Node.js 如火如荼发展的今天,我们已经可以用它来做各种各样的事情.前段时间UP主参加了极客松活动,在这次活动中我们意在做出一款让"低头族"能够更多交流的游戏,核心功能便是 Lan Party 概念的实时多人互动.极客松比赛只有短得可怜的36个小时,要求一切都敏捷迅速.在这样的前提下初期的准备显得有些"水到渠成".跨平台应用的 solution 我们选择了node-webkit,它足够简单且符合我们的要求. 按照需求,我们的开发可以按照模块分开进行.本文具体

随机推荐