1. 程式人生 > >Node.js 和 C++ 之間的型別轉換

Node.js 和 C++ 之間的型別轉換

我非常喜歡使用 Node.js,但是當涉及到計算密集型的場景時 Node.js 就不能夠很好地勝任了。而在這樣的情況下 C++ 是一個很好的選擇,非常幸運 Node.js 官方提供了 C/C++ Addons 的機制讓我們能夠使用 V8 API 把 Node.js 和 C++ 結合起來。

雖然在 Node.js 官方網站有很多的關於怎麼使用這些 API 的文件,但是在 JavaScript 和 C++ 之間傳遞資料是一件非常麻煩的事情,C++ 是強型別語言(”1024” 是字串型別而不是整數型別),而 JavaScript 卻總是預設的幫我們做一些型別轉換。

JavaScript 的基本型別包括 String,Number,Boolean,null,undefined,V8 使用類繼承的方式來定義這型別,這些型別都繼承了 Primitive

類,而 Primitive 繼承了 Value,v8 也支援整型(包括 Int32Uint32),而所有的型別定義都可以從 V8 型別文件中看到,除了基本的型別,還有 Object,Array,Map 等型別的定義。

基本型別的繼承關係如下圖:
primitive

在 V8 中所有 JavaScript 值都是被放在 Local 物件中,通過這個物件指定了 JavaScript 執行時的記憶體單元。

下面這段代定義了一個 Number 型別的值,其中 Test 函式中宣告的 isolate 變數代表著 V8 虛擬機器中的堆記憶體,當建立新變數的時候就需要用到它,接下來的一行程式碼就通過 isolate 聲明瞭一個 Number

型別的變數。

12345678910111213141516 #include <node.h>#include <v8.h>using namespacev8;voidTest(constv8::FunctionCallbackInfo<v8::Value>&args){Isolate*isolate=args.GetIsolate();// 宣告變數Local<Number>retval=v8::Number::New(isolate,1000);}voidinit(Local<Object>exports,Local<Object>module){NODE_SET_METHOD(exports,"getTestValue",Test);}NODE_MODULE(returnValue,init)

看了 V8 型別 API 文件 你會發現對於基本的 JavaScript 型別,只有變數的宣告而沒有變數的賦值。最初想可能覺得這個非常的奇怪,可是仔細想一想後發現這個是合理的。主要由以下幾點原因:

  • JavaScript 的基本型別是不可變型別,變數都是指向一個不可變的記憶體單元,var a = 10,則 a 指向的記憶體單元中包含的值為 5,重新賦值 a = 100,沒有改變這個記憶體單元的值,而是使得 a 指向了另外一個記憶體單元,其中的值為 100。如果宣告兩個變數 x,y 的值都為 10,則他們指向的是同一個記憶體單元。
  • 函式的傳參都是傳值,而不是傳引用,當在 JavaScript 中呼叫 C++ 的函式時,如果引數是基本型別則每次都是把這個值拷貝過去,改變引數的值不會影響原來的值。
  • 使用 Local 宣告基本型別的變數都是對記憶體單元的引用,因為第一條原因不可能改變引用的值使其指向另外一個記憶體單元,因此不存在變數的重新賦值。

資料流向 C++ -> JavaScript

下面 demo 定義了一些常用的 JavaScript 型別,包括基本型別的以及 Object, Array, Fuction。

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162 #include <node.h>#include <v8.h>using namespacev8;voidMyFunction(constv8::FunctionCallbackInfo<Value>&args){Isolate*isolate=args.GetIsolate();args.GetReturnValue().Set(String::NewFromUtf8(isolate,"Hello World!"));}voidTest(constv8::FunctionCallbackInfo<v8::Value>&args){Isolate*isolate=args.GetIsolate();// Number 型別的宣告Local<Number>retval=v8::Number::New(isolate,1000);// String 型別的宣告Local<String>str=v8::String::NewFromUtf8(isolate,"Hello World!");// Object 型別的宣告Local<Object>obj=v8::Object::New(isolate);// 物件的賦值obj->Set(v8::String::NewFromUtf8(isolate,"arg1"),str);obj->Set(v8::String::NewFromUtf8(isolate,"arg2"),retval);// Function 型別的宣告並賦值Local<FunctionTemplate>tpl=v8::FunctionTemplate::New(isolate,MyFunction);Local<Function>fn=tpl->GetFunction();// 函式名字fn->SetName(String::NewFromUtf8(isolate,"theFunction"));obj->Set(v8::String::NewFromUtf8(isolate,"arg3"),fn);// Boolean 型別的宣告Local<Boolean>flag=Boolean::New(isolate,true);obj->Set(String::NewFromUtf8(isolate,"arg4"),flag);// Array 型別的宣告Local<Array>arr=Array::New(isolate);// Array 賦值arr->Set(0,Number::New(isolate,1));arr->Set(1,Number::New(isolate,10));arr->Set(2,Number::New(isolate,100));arr->Set(3,Number::New(isolate,1000));obj->Set(String::NewFromUtf8(isolate,"arg5"),arr);// Undefined 型別的宣告Local<Value>und=Undefined(isolate);obj->Set(String::NewFromUtf8(isolate,"arg6"),und);// null 型別的宣告Local<Value>null=Null(isolate);obj->Set(String::NewFromUtf8(isolate,"arg7"),null);// 返回給 JavaScript 呼叫時的返回值args.GetReturnValue().Set(obj);}voidinit(Local<Object>exports,Local<Object>module){NODE_SET_METHOD(exports,"getTestValue",Test);}NODE_MODULE(returnValue,init)

所有的 addon 都需要一個初始化的函式,如下面的程式碼:

12 voidInitialize(Local<Object>exports);NODE_MODULE(module_name,Initialize)

Initialize 是初始化的函式,module_name 是編譯後產生的二進位制檔名,上述程式碼的模組名為 returnValue

上述程式碼通過 node-gyp 編譯後(編譯過程官方文件 C/C++ Addons 有詳細的介紹),可以通過如下的方式呼叫。

123 // returnValue.node 這個檔案就是編譯後產生的檔案,通過 NODE_MODULE(returnValue, init) 決定的檔名constreturnValue=require('./build/Release/returnValue.node');console.log(returnValue.getTestValue());

執行結果如下:

returnValue

資料流向 javaScript -> C++

上面的 demo 展示了怎樣在在 C++ 定義 JavaScript 型別,資料的是從 C++ 流向 JavaScript,反過來資料也需要從 javaScript 流向 C++,也就是呼叫 C++ 函式的時候需要傳入一些引數。

下面的程式碼展示了引數個數判斷,引數型別判斷,以及引數型別裝換成 V8 型別的過程,包括基本型別以及 Object, Array, Fuction。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114 #include <node.h>#include <v8.h>#include <iostream>using namespacev8;using namespacestd;voidGetArgument(constFunctionCallbackInfo<Value>&args){Isolate*isolate=args.GetIsolate();// 引數長度判斷if(args.Length()<2){isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate,"Wrong number of arguments")));return;}// 引數型別判斷if(!args[0]->IsNumber()||!args[1]->IsNumber()){//丟擲錯誤isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate,"argumnets must be number")));}if(!args[0]->IsObject()){printf("I am not Object\n");