Maison  >  Article  >  interface Web  >  Explication détaillée de l'écriture du module natif node.js en C/C++

Explication détaillée de l'écriture du module natif node.js en C/C++

零下一度
零下一度original
2017-07-09 10:18:551781parcourir

Cet article vous présente principalement les informations pertinentes sur l'utilisation de C/C++ pour écrire des modules natifs node.js. Les étapes de mise en œuvre sont présentées en détail étape par étape. Il a une certaine valeur de référence et d'apprentissage pour tout le monde. Amis, suivons l'éditeur et jetons un œil.

Avant-propos

J'ai toujours voulu apprendre à utiliser C/C++ pour écrire des modules natifs nodejs. La plupart des blogs que j'ai trouvés sur le. Internet était bloqué sur la façon de créer leur environnement, puis un Hello World est créé. Il n'y a même plus de documents de référence. Je l'ai donc réglé moi-même et je l'ai partagé ici.

Quant à la préparation de l’environnement, j’en trouve beaucoup en ligne, je n’entrerai donc pas dans les détails.

Référez-vous principalement à deux endroits :

  • documentation officielle de nodejs

  • Documentation v8

Le premier est le document officiel de nodejs, qui présente plusieurs bons exemples de référence.

La seconde est la documentation du moteur v8, qui est pour le C++. Vous devriez principalement lire ce document lors de l'écriture d'un module C++.

D'accord, commençons par quelques exemples et comprenons étape par étape comment utiliser C++ pour écrire des modules nodejs.

Hello World

C'est inévitable Soyons les premiers à écrire Hello World Après tout, les programmeurs sont les premiers à savoir. les uns les autres. Le premier programme est Hello World.


#include <node.h>
void hello(const v8::FunctionCallbackInfo<v8::Value> &args) {
 v8::Isolate *isolate = args.GetIsolate();
 auto message = v8::String::NewFromUtf8(isolate, "Hello World!");
 args.GetReturnValue().Set(message);
}
void Initialize(v8::Local<v8::Object> exports) {
 NODE_SET_METHOD(exports, "hello", hello);
}
NODE_MODULE(module_name, Initialize)

D'accord, c'est le HelloWorld le plus simple. Nous nommons le fichier addon.cc, nous utilisons node-gyp pour le compiler, puis dans notre Use require directement. pour introduire le module dans le fichier js, puis vous pourrez l'appeler.


const myAddon = require(&#39;./build/Release/addon&#39;) ;
console.log(myAddon.hello());

Si rien d'inattendu ne se produit, Hello World sera imprimé sur le terminal.

Jetons un bref coup d'œil au code. La première ligne #include 7f7d395e5ec8b32ec491af5a6adcd1ae est le code qui introduit le fichier d'en-tête node.h en C++. Le fichier d'en-tête peut être compris comme une interface. Nous y définissons uniquement les méthodes d'interface, mais ne les implémentons pas. Elles sont ensuite implémentées via d'autres fichiers. L'éditeur de liens C++ est chargé de relier les deux.

définit ensuite une méthode hello() sans valeur de retour. Les paramètres de la méthode sont transmis via const v8::FunctionCallbackInfoefcceec664dd4e974e6278d25693ab6b &args. Notez qu'ici nous avons ajouté l'annotation de préfixe v8::. Vous pouvez également utiliser using v8; directement au début du fichier afin de ne pas avoir besoin d'utiliser cette annotation à chaque fois.

v8::Isolate *isolate = args.GetIsolate();Ici, on accède à la portée du javascript dans la fonction.

auto message = v8::String::NewFromUtf8(isolate, "Hello World!");Nous créons une variable de type chaîne, attribuons Hello World et la lions à la portée.

Nous obtenons la valeur de retour args.GetReturnValue() de notre fonction via .

La méthode Initialize() est utilisée pour initialiser la méthode du module et lier la méthode au nom de la méthode du module à exporter.

Enfin, NODE_MODULE exporte ce module.

L'exemple ci-dessus est très simple, s'il s'agit de code js :


&#39;use strict&#39;;
let hello = function hello() {
 let message = "Hello World!";
 return message;
};
module.exports = {
 hello:hello
};

D'accord, le premier HelloWorld est terminé. Il existe de nombreux articles de blog sur Internet présentant le module nodejs C++, mais ils se terminent ici. Après l'avoir lu, j'étais confus, qu'est-ce que c'est ? Je souhaite écrire une autre méthode pour transmettre des paramètres et effectuer des opérations simples sur les paramètres. Comment dois-je l'écrire ?

somme(a,b)

D'accord. Ensuite, nous écrirons une autre fonction sum(a,b), passerons deux paramètres de type numérique a et b, trouverons la somme des deux paramètres et la renverrons.

Le code en js est aussi simple que ce qui suit :


let sum = function(a,b){
 if(typeof a == &#39;number&#39; && typeof b == &#39;number&#39;){
  return a + b;
 }else{
  throw new Error(&#39;参数类型错误&#39;);
 }
}

Alors, comment écrire du C++ :


void sum(const FunctionCallbackInfo<Value> &args) {
 Isolate *isolate = args.GetIsolate();
 if(!args[0]->IsNumber()){
  isolate->ThrowException(v8::Exception::TypeError(
   v8::String::NewFromUtf8(isolate, "args[0] must be a number")));
  return ;
 }
 if(!args[1]->IsNumber()){
  isolate->ThrowException(v8::Exception::TypeError(
   v8::String::NewFromUtf8(isolate, "args[1] must be a number")));
  return ;
 }
 args.GetReturnValue().Set(args[0]->NumberValue() + args[1]->NumberValue());
}

Déterminez d'abord si les deux paramètres sont de type Number. Sinon, directement lancez une exception . Si tel est le cas, la valeur de retour est définie sur la somme des deux arguments.

Ici, nous n'utilisons pas directement a et b comme paramètres dans la liste des paramètres, mais utilisons directement l'objet args. Ceci est similaire à js, ​​le premier paramètre est args[0] et le deuxième paramètre est args[1] .

appelle IsNumber() pour déterminer s'il s'agit d'un type numérique. Sinon, lancez une exception TypeError.
Si le type est OK, utilisez args[0]->NumberValue() pour obtenir la valeur numérique du paramètre, puis ajoutez-la et attribuez-la à la valeur de retour.

Peut-être demanderez-vous : args[0] Qu'est-ce que c'est ? D’où vient sa méthode IsNumber() ? Où puis-je trouver de la documentation ?

Il s'agit en fait du type interne du moteur v8, qui correspond essentiellement aux objets intégrés de js un-à-un. Vous pouvez consulter la documentation du type v8.

L'image ci-dessus est-elle très familière ? Elle est très similaire au système de types js.

Les tableaux, dates, fonctions, chaînes, etc. de JS héritent tous de l'objet, et dans le moteur v8, l'objet et la primitive héritent tous du type valeur.

这里的IsNumber()方法就是Value类型的方法。那么除了这个方法,还有什么方法呢?

上面这张图,我只是截了一小部分,全部的可以直接去查阅文档。看,这里有各种方法,判断是否是数字类型的IsNumber(),判断是否是日期类型的IsDate() ,判断是否是数组的IsArray()方法等等。

v8的接口实现的也很完善了,即使并不精通C++的开发者也可以照猫画虎的实现个简单的模块。

args[0]->NumberValue()返回的是一个double的值,是的,这里是实打实的C++里的double类型,可以直接进行加减运算的。类似的还有BooleanValue()方法等等,都是获取不同类型的值使用的方法。

第二个例子中,我们简单实现了一个sum()方法,传递两个参数,求和。但是这里涉及到的只是整型的值,那如果有其他类型的值怎么办呢?比如数组。

sumOfArray(array)

下面将方法升级一下,传递一个数组,然后求数组中所有值的和。js的话:


let sumOfArray = function(array){
 if(!Array.isArray(array)){
  throw new Error(&#39;参数错误,必须为Array类型&#39;);
 }
 let sum = 0;
 for(let item of array){
  sum += item;
 }
 return sum;
}

逻辑很简单,就是将传过来的数组进行遍历一遍,然后将所有项累加即可。C++也是如此:


void sumOfArray(const FunctionCallbackInfo<Value> &args){
 Isolate *isolate = args.GetIsolate();
 if(!args[0]->IsArray()){
  isolate->ThrowException(v8::Exception::TypeError(
   v8::String::NewFromUtf8(isolate, "args[0] must be an Array")));
  return ;
 }
 Local<Object> received_v8_obj = args[0]->ToObject();
 Local<Array> keys = received_v8_obj->GetOwnPropertyNames();
 int length = keys->Length();
 double sum = 0;
 for(int i=0;i<length;i++){
  sum += received_v8_obj->Get(keys->Get(i))->NumberValue();
 }
 args.GetReturnValue().Set(sum);
}

先判断是否是数组,没什么问题。

然后我们定义了一个Object类型的received_v8_obj属性,将其赋值为args[0]->ToObject() 。这里调用ToObject()方法将其转换为一个对象。

然后调用这个对象的GetOwnPropertyNames()方法获取所有的键,然后根据键获取对象的值,进行累加。

为什么不直接将其转换为数组,然后进行遍历呢?

我们都知道,js中的数组并不是真正的数组,其实质还是对象。其内部都是键值对存储的。因此这里也是一样,Value类型并不提供直接转换为数组的ToArray()方法,而是将其转换为Object对象,通过对象的形式进行操作。

那么对象有哪些操作呢,看文档。

但是你会发现,v8确实有个Array类,继承自Object类。那么Array有什么方法呢?

看文档就知道了,少的可怜:

所以,对数组的操作都将转换为对象操作。

createObj()

说到对象了,那么我们就来写一个创建对象的方法。传递两个参数,一个name,一个age,创建一个对象,表示一个人,名叫啥,多大年纪。


void createObj(const FunctionCallbackInfo<Value> &args){
  Isolate *isolate = args.GetIsolate();
  Local<Object> obj = Object::New(isolate);
  obj->Set(String::NewFromUtf8(isolate,"name"),args[0]->ToString());
  obj->Set(String::NewFromUtf8(isolate,"age"),args[1]->ToNumber());
  args.GetReturnValue().Set(obj);
}

这个方法,参照文档,基本没啥可说的。

通过Object::New(isolate)创建一个对象,然后设置两个属性name,age,将参数依次赋值给这两个属性,然后返回这个对象即可。

如果用js写:


let createObj = function(name,age){
  let obj = {};
  obj.name = name;
  obj.age = age;
  return obj;
};

callback

上面说的,都没提到js中一个重要的东西,回调函数。如果参数中传一个回调函数,那么我们该如何执行呢?

来一个简单的例子。


let cb = function(a,b,fn){
  if(typeof a !== &#39;number&#39; || typeof b !== &#39;number&#39;){
    throw new Error(&#39;参数类型错误,只能是Number类型&#39;);
  }
  if(typeof fn !== &#39;function&#39;){
    throw new Error(&#39;参数fn类型错误,只能是Function类型&#39;);
  }
  fn(a,b);
};

这个例子很简单,我们传两个数字类型参数a,b和一个回调函数fn,然后将a,b作为fn的参数调用fn回调函数。这里我们对a,b的操作转交给回调函数。回调函数里我们可以求和,也可以求积,随你。

这个例子中,暂时还没涉及到的是如何调用回调函数。

先上代码:


void cb(const FunctionCallbackInfo<Value> &args){
  Isolate *isolate = args.GetIsolate();
  if(!args[0]->IsNumber()){
    isolate->ThrowException(v8::Exception::TypeError(
      v8::String::NewFromUtf8(isolate, "args[0] must be a Number")));
  }
  if(!args[1]->IsNumber()){
    isolate->ThrowException(v8::Exception::TypeError(
      v8::String::NewFromUtf8(isolate, "args[1] must be a Number")));
  }
  if(!args[2]->IsFunction()){
    isolate->ThrowException(v8::Exception::TypeError(
      v8::String::NewFromUtf8(isolate, "args[2] must be a Function")));
  }
  Local<Function> jsfn = Local<Function>::Cast(args[2]);
  Local<Value> argv[2] = { Number::New(isolate,args[0]->NumberValue()),Number::New(isolate,args[1]->NumberValue())};
  Local<Value> c = jsfn->Call(Null(isolate),2,argv);
  args.GetReturnValue().Set(c);
}

上面三个判断参数类型,略过。

我们定义一个Function类型属性jsfn,将args[2]强制转换为Function并赋值给jsfn。

然后定义一个具有两个值的参数argv,这两个值就是args[0] , args[1]的数字值。

然后通过jsfn->Call(Null(isolate),2,argv)调用回调函数。

argv是一个数组,其个数我们在定义时指定,2个。

Call()方法为函数类型的值进行调用的方法。

Local< Value > | Call (Handle< Value > recv, int argc, Handle< Value > argv[])

查阅文档,可以看出,Call()方法传3个参数,第一个参数是执行上下文,用于绑定代码执行时的this,第二个参数为参数个数,第三个为参数列表,数组形式。

上面几个例子,只是冰山一角,连一角都算不上。只为了解一下nodejs使用C/C++编写原生模块,如果要编写一个可用的,高性能的C模块,那么,要求程序员一定要精通C/C++,并且对js底层也很精通,包括v8和libuv等等。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn