Dieser Artikel führt Sie hauptsächlich in die relevanten Informationen zur Verwendung von C/C++ zum Schreiben nativer Module von node.j ein. Es ist für jeden notwendig Freunde, lasst uns dem Herausgeber folgen und einen Blick darauf werfen.
Vorwort
Ich wollte schon immer etwas über die Verwendung von C/C++ zum Schreiben nativer NodeJS-Module lernen Das Internet war nicht in der Lage, die Umgebung zu erstellen, und dann ist eine „Hello World“ fertig. Es gibt nicht einmal mehr Referenzmaterialien. Also habe ich es selbst geklärt und hier geteilt.
Was die Vorbereitung der Umgebung angeht, kann ich viele davon online finden, daher werde ich nicht auf Details eingehen.
Bezieht sich hauptsächlich auf zwei Orte:
offizielle Nodejs-Dokumentation
v8-Dokumentation
Das erste ist das offizielle Dokument von nodejs, das mehrere gute Referenzbeispiele vorstellt.
Das zweite ist die Dokumentation der v8-Engine, die für C++ gedacht ist. Sie sollten dieses Dokument hauptsächlich lesen, wenn Sie ein C++-Modul schreiben.
Okay, fangen wir mit ein paar Beispielen an und verstehen Schritt für Schritt, wie man mit C++ NodeJS-Module schreibt.
Hello World
Es ist unvermeidlich, dass wir die Ersten sind, die Hello World schreiben einander. Das erste Programm ist 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)
Okay, das ist die einfachste HelloWorld. Wir nennen die Datei addon.cc, verwenden Node-Gyp, um sie zu kompilieren, und verwenden sie dann direkt in unserer Anforderung um das Modul in die js-Datei einzuführen, und dann können Sie es aufrufen.
const myAddon = require('./build/Release/addon') ; console.log(myAddon.hello());
Wenn nichts Unerwartetes passiert, wird „Hello World!“ auf dem Terminal angezeigt.
Werfen wir einen kurzen Blick auf den Code. Die erste Zeile #include <node.h>
ist der Code, der die Header-Datei node.h in C++ einführt. Die Header-Datei kann als Schnittstelle verstanden werden, wir definieren sie jedoch nicht. Sie werden dann über andere Dateien implementiert. Der C++-Linker ist für die Verknüpfung der beiden verantwortlich.
definiert dann eine Methode hello()
ohne Rückgabewert. Methodenparameter werden über const v8::FunctionCallbackInfo<v8::Value> &args
übergeben. Beachten Sie, dass wir hier die Präfixanmerkung v8:: hinzugefügt haben. Sie können using v8;
auch direkt am Anfang der Datei verwenden, damit Sie diese Annotation nicht jedes Mal verwenden müssen.
v8::Isolate *isolate = args.GetIsolate();
Hier greifen wir auf den Umfang von Javascript in der Funktion zu.
auto message = v8::String::NewFromUtf8(isolate, "Hello World!");
Wir erstellen eine Variable vom Typ String, weisen Hello World! zu und binden sie an den Bereich.
Den Rückgabewert args.GetReturnValue()
unserer -Funktion erhalten wir durch .
Die Methode Initialize() wird verwendet, um die Modulmethode zu initialisieren und die Methode an den Methodennamen des zu exportierenden Moduls zu binden.
Schließlich exportiert NODE_MODULE dieses Modul.
Das obige Beispiel ist sehr einfach, wenn es sich um JS-Code handelt:
'use strict'; let hello = function hello() { let message = "Hello World!"; return message; }; module.exports = { hello:hello };
Okay, die erste HelloWorld ist vorbei. Es gibt viele Blog-Artikel im Internet, in denen das C++-Modul von nodejs vorgestellt wird, aber sie enden hier. Nachdem ich es gelesen hatte, war ich verwirrt, was ist das? Ich möchte eine andere Methode schreiben, um Parameter zu übergeben und einfache Operationen an den Parametern durchzuführen. Wie soll ich sie schreiben?
Summe(a,b)
Okay. Dann schreiben wir eine weitere sum(a,b)
-Funktion, übergeben zwei numerische Typparameter a und b, ermitteln die Summe der beiden Parameter und geben sie zurück.
Der Code in js ist so einfach wie der folgende:
let sum = function(a,b){ if(typeof a == 'number' && typeof b == 'number'){ return a + b; }else{ throw new Error('参数类型错误'); } }
Also, wie man C++ schreibt:
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()); }
Stellen Sie zunächst fest, ob die beiden Parameter vom Typ Zahl sind. Wenn nicht, lösen Sie direkt eine Ausnahme aus . Wenn ja, wird der Rückgabewert auf die Summe der beiden Parameter gesetzt.
Hier verwenden wir a und b nicht direkt als Parameter in der Parameterliste, sondern direkt das args-Objekt. Dies ähnelt js, der erste Parameter ist args[0]
und der zweite Parameter ist args[1]
.
ruft IsNumber()
auf, um zu bestimmen, ob es sich um einen numerischen Typ handelt. Wenn nicht, lösen Sie eine TypeError-Ausnahme aus.
Wenn der Typ in Ordnung ist, verwenden Sie args[0]->NumberValue()
, um den numerischen Wert des Parameters abzurufen, fügen Sie ihn dann hinzu und weisen Sie ihn dem Rückgabewert zu.
Vielleicht werden Sie fragen: args[0]
Was ist das? Woher kommt die Methode IsNumber()
? Wo finde ich Dokumentation?
Dies ist eigentlich der interne Typ der v8-Engine, der im Wesentlichen eins zu eins den integrierten Objekten von js entspricht. Sie können die Dokumentation zum Typ v8 überprüfen.
Ist das Bild oben sehr vertraut? Es ist dem js-Typsystem sehr ähnlich.
Array, Datum, Funktion, Zeichenfolge usw. von JS erben alle vom Objekt, und innerhalb der v8-Engine erben Objekt und Primitiv alle vom Werttyp.
这里的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('参数错误,必须为Array类型'); } 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 !== 'number' || typeof b !== 'number'){ throw new Error('参数类型错误,只能是Number类型'); } if(typeof fn !== 'function'){ throw new Error('参数fn类型错误,只能是Function类型'); } 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等等。
Das obige ist der detaillierte Inhalt vonAusführliche Erklärung zum Schreiben des nativen Moduls von node.j in C/C++. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!