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类。