Maison >interface Web >js tutoriel >La bonne façon d'écrire les plug-ins Node.js_node.js
Node.js est exceptionnel dans l'utilisation de JavaScript pour écrire des backends, et cela vaut la peine d'essayer davantage. Cependant, si vous avez besoin de certaines fonctions qui ne peuvent pas être utilisées directement ou même de modules qui ne peuvent pas être implémentés du tout, pouvez-vous introduire de tels résultats à partir de la bibliothèque C/C ? La réponse est oui, tout ce que vous avez à faire est d'écrire un plug-in et d'utiliser les ressources d'autres bibliothèques de code dans votre propre code JavaScript. Commençons ensemble le voyage d’enquête d’aujourd’hui.
Présentation
Comme indiqué dans la documentation officielle de Node.js, les plug-ins sont des objets partagés qui sont liés dynamiquement et peuvent connecter du code JavaScript aux bibliothèques C/C. Cela signifie que nous pouvons référencer n'importe quoi à partir d'une bibliothèque C/C++ et l'incorporer dans Node.js en créant des plugins.
À titre d'exemple, nous allons créer un wrapper pour l'objet standard std::string.
Préparation
Avant de commencer à écrire, vous devez d'abord vous assurer que vous avez préparé tout le matériel nécessaire à la compilation ultérieure du module. Vous avez besoin de node-gyp et de toutes ses dépendances. Vous pouvez utiliser la commande suivante pour installer node-gyp :
npm install -g node-gyp
En termes de dépendances, nous devons préparer les éléments suivants pour les systèmes Unix : • Python (nécessite la version 2.7, 3.x ne fonctionnera pas correctement)
• faire
• Une chaîne d'outils de compilateur C (comme gpp ou g)
Par exemple, sur Ubuntu, vous pouvez utiliser la commande suivante pour installer tous les projets ci-dessus (Python 2.7 aurait dû être préinstallé) :
sudo apt-get install build-essentials
Dans un environnement système Windows, ce dont vous avez besoin est :
• Python (la version 2.7.3, 3.x ne fonctionne pas correctement)
• Microsoft Visual Studio C 2010 (pour Windows XP/Vista)
• Microsoft Visual Studio C 2012 pour Windows Desktop (pour Windows 7/8)
J'insiste sur le fait que la version Express de Visual Studio fonctionnera également normalement.
fichier binding.gyp
Ce fichier est utilisé par node-gyp pour générer les fichiers de build appropriés pour notre plugin. Vous pouvez cliquer ici pour consulter la documentation du fichier .gyp fournie par Wikipédia, mais l'exemple que nous allons utiliser aujourd'hui est très simple, utilisez donc simplement le code suivant :
{ "targets": [ { "target_name": "stdstring", "sources": [ "addon.cc", "stdstring.cc" ] } ] }
Le nom_cible peut être défini comme vous le souhaitez. Le tableau sources contient tous les fichiers sources que le plug-in doit utiliser. Dans notre exemple, nous incluons également addon.cc, qui est utilisé pour contenir le code nécessaire à la compilation du plug-in et stdstring.cc, ainsi que notre classe wrapper.
Classe STDStringWrapper
La première étape consiste à définir notre propre classe dans le fichier stdstring.h. Si vous êtes familier avec la programmation C, vous ne serez certainement pas étranger aux deux lignes de code suivantes.
#ifndef STDSTRING_H #define STDSTRING_H
Il s'agit d'une protection standard. Ensuite, nous devons inclure les deux en-têtes suivants dans la catégorie d'inclusion :
#inclure
#inclure
Le premier concerne la classe std::string, tandis que le second include concerne tout ce qui concerne Node et V8.
Une fois cette étape terminée, nous pouvons déclarer notre propre classe :
class STDStringWrapper : public node::ObjectWrap {
Pour toutes les classes que nous avons l'intention d'inclure dans le plugin, nous devons étendre la classe node::ObjectWrap.
Nous pouvons maintenant commencer à définir les propriétés privées de cette classe :
private: std::string* s_; explicit STDStringWrapper(std::string s = ""); ~STDStringWrapper();
En plus du constructeur et de la fonction d'analyse, nous devons également définir un pointeur pour std::string. C'est le cœur de la technologie qui peut être utilisée pour interfacer une base de code C/C avec Node - nous définissons un pointeur privé pour la classe C/C et utiliserons ce pointeur pour implémenter des opérations dans toutes les méthodes suivantes.
Maintenant, nous déclarons la propriété statique du constructeur qui fournira des fonctions pour la classe que nous avons créée dans la V8 :
statique v8 ::Constructeur persistant
Les amis intéressés peuvent cliquer ici pour se référer au plan de description du modèle pour plus de détails.
Maintenant, nous avons également besoin d'une méthode New, qui sera assignée au constructeur mentionné précédemment, et la V8 initialisera notre classe :
static v8::Handle New(const v8::Arguments& args);
Chaque fonction qui agit sur V8 doit respecter les exigences suivantes : elle acceptera une référence à un objet v8::Arguments et renverra un v8::Handle>v8::Value> - c'est ce que nous utilisons pour le codage C fortement typé des handles V8. JavaScript faiblement typé de manière cohérente.
Après cela, nous devons également insérer deux autres méthodes dans le prototype de l'objet :
static v8::Handle add(const v8::Arguments& args); static v8::Handle toString(const v8::Arguments& args);
La méthode toString() nous permet d'obtenir la valeur de s_ au lieu de la valeur de [Object object] lors de son utilisation avec des chaînes JavaScript ordinaires.
Enfin, nous présenterons la méthode d'initialisation (cette méthode sera appelée par V8 et assignée à la fonction constructeur) et désactiverons la garde d'inclusion :
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