對JavaScript
程序員來講,Node.js
確實是咱們做爲服務端開發的首選語言。Node.js
的性能優點源於其使用Google
的V8引擎
,使用非阻塞式的I / O模型
,依靠事件驅動。但涉及密集型計算的場景時,Node.js
不必定可以有很優秀的表現。還好有C++ Addons的機制,可以使得咱們編寫原生的C++
模塊,而且可以在Node.js
中調用它。javascript
C++
模塊C++
社區龐大,我想在咱們現成的Node.js
應用中使用某個C++
模塊。Fabonacci
斐波那契數列一般解法是以遞歸地方式來完成,在這裏,爲了體現Node.js
中調用C++
模塊的優點,咱們並不在Fabonacci
中使用緩存的機制。html
在Node.js
中,根據Fabonacci
定義,咱們編寫了以下代碼,fabonacci.js
:java
// fabonacci.js
function fabonacciNodeJS(n) {
if (n === 0) {
return 0;
}
if (n === 1) {
return 1;
}
return fabonacciNodeJS(n - 1) + fabonacciNodeJS(n - 2);
}
function TestFabonnacci(func, env, n) {
const start = (new Date()).getTime();
const result = func(n);
const end = (new Date()).getTime();
console.log(`fabonacci(${n}) run in ${env} result is ${result}, cost time is ${end - start} ms.`);
}
TestFabonnacci(fabonacciNodeJS, 'Native Node.js', 40);
複製代碼
能夠在命令行中運行這一段程序,結果以下:node
fabonacci(40) run in Native Node.js result is 102334155, cost time is 1125 ms.
複製代碼
爲了體現密集型計算場景時在Node.js
中使用C++
拓展模塊的優點,我根據C++ Addons編寫了以下代碼,fabonacci.cc
:c++
// fabonacci.cc
#include <node.h>
namespace fabonacci {
using namespace v8;
static inline size_t runFabonacci(size_t n) {
if (n == 0)
{
return 0;
}
if (n == 1)
{
return 1;
}
return runFabonacci(n - 1) + runFabonacci(n - 2);
}
static void Fabonacci(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
// 檢查參數類型
if (!args[0]->IsNumber())
{
isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "argument type must be Number")));
}
size_t n = args[0]->NumberValue();
Local<Number> num = Number::New(isolate, runFabonacci(n));
args.GetReturnValue().Set(num);
}
void init(Local<Object> exports, Local<Object> module) {
NODE_SET_METHOD(module, "exports", Fabonacci);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
}
複製代碼
修改以前的fabonacci.js
,測試以上C++
拓展程序:git
// fabonacci.js
const fabonacciCPP = require('./build/Release/fabonacci');
function fabonacciNodeJS(n) {
if (n === 0) {
return 0;
}
if (n === 1) {
return 1;
}
return fabonacciNodeJS(n - 1) + fabonacciNodeJS(n - 2);
}
function TestFabonnacci(func, env, n) {
const start = (new Date()).getTime();
const result = func(n);
const end = (new Date()).getTime();
console.log(`fabonacci(${n}) run in ${env} result is ${result}, cost time is ${end - start} ms.`);
}
TestFabonnacci(fabonacciNodeJS, 'Native Node.js', 40);
TestFabonnacci(fabonacciCPP, 'C++ Addon', 40);
複製代碼
運行上述程序,結果以下:程序員
fabonacci(40) run in Native Node.js result is 102334155, cost time is 1120 ms.
fabonacci(40) run in C++ Addon result is 102334155, cost time is 587 ms.
複製代碼
能夠看到,在Node.js
中調用C++
拓展模塊,計算n = 40
的斐波那契數,速度快了接近一倍。github
Hello World
開始如今,咱們能夠從書寫一個Hello World
來介紹如何編寫一個C++
拓展,並在Node.js
模塊中調用:npm
如下是一個使用C++ Addons編寫的一個Hello World
模塊,咱們能夠在Node.js
代碼中調用這一個模塊。json
#include <node.h>
namespace helloWorld {
using namespace v8;
static void HelloWorld(const FunctionCallbackInfo<Value>& args) {
// isolate當前的V8執行環境,每一個isolate執行環境相互獨立
Isolate* isolate = args.GetIsolate();
// 設定返回值
args.GetReturnValue().Set(String::NewFromUtf8(isolate, "Hello, World!"));
}
static void init(Local<Object> exports, Local<Object> module) {
// 設定module.exports爲HelloWorld函數
NODE_SET_METHOD(module, "exports", HelloWorld);
}
// 全部的 Node.js 插件必須以如下形式模式的初始化函數
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
}
複製代碼
以上C++
代碼至關於如下JavaScript
代碼:
module.exports.hello = () => 'world';
複製代碼
首先,在工程根目錄下建立一個名爲binding.gyp
的文件,以下:
{
"targets": [
{
"target_name": "fabonacci",
"sources": [ "fabonacci.cc" ]
}
]
}
複製代碼
binding.gyp
使用一個相似JSON
的格式來描述模塊的構建配置。而後使用node-gyp把咱們書寫的C++
模塊源碼編譯爲二進制模塊,咱們可使用sudo npm install -g node-gyp
全局安裝node-gyp
:
在項目根目錄下執行:
node-gyp configure
node-gyp build
複製代碼
編譯構建成功以後,可執行文件fabonacci.node
會在項目根目錄下的/build/Release
目錄下,咱們能夠在Node.js
引入該模塊:
const hello = require('./build/Release/hello');
console.log(hello()); // Hello, World!
複製代碼
根據v8文檔使用v8::Local<v8::Value>
聲明的數據將會被V8
的Garbage Collector
管理。咱們書寫以下的C++
模塊示例,在C++
模塊中聲明以下的V8
類型的變量,並導出給JavaScript
模塊使用:
#include <node.h>
namespace datas {
using namespace v8;
static void MyFunction(const FunctionCallbackInfo<Value> &args) {
Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(String::NewFromUtf8(isolate, "MyFunctionReturn"));
}
static void Datas(const FunctionCallbackInfo<Value> &args) {
Isolate* isolate = args.GetIsolate();
// 聲明一個V8的Object類型的變量
Local<Object> object = Object::New(isolate);
// 聲明一個V8的Number類型的變量
Local<Number> number = Number::New(isolate, 0);
// 聲明一個V8的String類型的變量
Local<String> string = String::NewFromUtf8(isolate, "string");
// 聲明一個V8的Function類型的變量
Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);
Local<Function> func = tpl->GetFunction();
// 聲明一個V8的Array類型的變量
Local<Array> array = Array::New(isolate);
// 給array賦值
for (int i = 0; i < 10; ++i)
{
array->Set(i, Number::New(isolate, i));
}
// 聲明一個V8的Boolean類型的變量
Local<Boolean> boolean = Boolean::New(isolate, true);
// 聲明一個V8的Undefined類型的變量
Local<Value> undefined = Undefined(isolate);
// 聲明一個V8的Null類型的變量
Local<Value> nu = Null(isolate);
// 設定函數的名稱
func->SetName(String::NewFromUtf8(isolate, "MyFunction"));
// 給對象賦值
object->Set(String::NewFromUtf8(isolate, "number"), number);
object->Set(String::NewFromUtf8(isolate, "string"), string);
object->Set(String::NewFromUtf8(isolate, "function"), func);
object->Set(String::NewFromUtf8(isolate, "array"), array);
object->Set(String::NewFromUtf8(isolate, "boolean"), boolean);
object->Set(String::NewFromUtf8(isolate, "undefined"), undefined);
object->Set(String::NewFromUtf8(isolate, "null"), nu);
args.GetReturnValue().Set(object);
}
static void init(Local<Object> exports, Local<Object> module) {
NODE_SET_METHOD(module, "exports", Datas);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
}
複製代碼
使用node-gyp
工具構建上述模塊,在Node.js
模塊中引入:
const datas = require('./build/Release/datas');
console.log(datas());
複製代碼
運行結果:
例如咱們在參數中傳入了一個Number
數據類型的JavaScript
變量,可使用v8::Number::Cast
方法將JavaScript
數據類型轉換爲V8
數據類型,咱們建立了以下模塊factory.cc
,一個工廠模式建立對象的示例:
#include <node.h>
namespace factory {
using namespace v8;
static void Factory(const FunctionCallbackInfo<Value> &args) {
Isolate* isolate = args.GetIsolate();
Local<Object> object = Object::New(isolate);
object->Set(String::NewFromUtf8(isolate, "name"), Local<String>::Cast(args[0])); // Cast方法實現JavaScript轉換爲V8數據類型
object->Set(String::NewFromUtf8(isolate, "age"), Local<Number>::Cast(args[1])); // Cast方法實現JavaScript轉換爲V8數據類型
args.GetReturnValue().Set(object);
}
static void init(Local<Object> exports, Local<Object> module) {
NODE_SET_METHOD(module, "exports", Factory);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
}
複製代碼
調用上述模塊:
const factory = require('./build/Release/factory');
console.log(factory('counter', 21)); // { name: 'counter', age: 21 }
複製代碼
關於其它類型的
Cast
調用,可查閱V8文檔。