NodeJS 和 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 也支援整型(包括 Int32
和 Uint32
),而所有的型別定義都可以從 V8 型別文件中看到,除了基本的型別,還有 Object,Array,Map 等型別的定義。
基本型別的繼承關係如下圖:
在 V8 中所有 JavaScript 值都是被放在 Local
物件中,通過這個物件指定了 JavaScript 執行時的記憶體單元。
下面這段代定義了一個 Number
型別的值,其中 Test 函式中宣告的 isolate 變數代表著 V8 虛擬機器中的堆記憶體,當建立新變數的時候就需要用到它,接下來的一行程式碼就通過 isolate 宣告瞭一個 Number
型別的變數。
#include <node.h> #include <v8.h> using namespace v8; void Test(const v8::FunctionCallbackInfo<v8::Value>& args) { Isolate* isolate = args.GetIsolate(); // 宣告變數 Local<Number> retval = v8::Number::New(isolate, 1000); } void init(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<Value>
宣告基本型別的變數都是對記憶體單元的引用,因為第一條原因不可能改變引用的值使其指向另外一個記憶體單元,因此不存在變數的重新賦值。
資料流向 C++ -> JavaScript
下面 demo 定義了一些常用的 JavaScript 型別,包括基本型別的以及 Object, Array, Fuction。
#include <node.h> #include <v8.h> using namespace v8; void MyFunction(const v8::FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); args.GetReturnValue().Set(String::NewFromUtf8(isolate, "Hello World!")); } void Test(const v8::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); } void init(Local <Object> exports, Local<Object> module) { NODE_SET_METHOD(exports, "getTestValue", Test); } NODE_MODULE(returnValue, init)
所有的 addon 都需要一個初始化的函式,如下面的程式碼:
void Initialize(Local<Object> exports); NODE_MODULE(module_name, Initialize)
Initialize
是初始化的函式,module_name
是編譯後產生的二進位制檔名,上述程式碼的模組名為returnValue
。
上述程式碼通過 node-gyp 編譯後(編譯過程官方文件 C/C++ Addons 有詳細的介紹),可以通過如下的方式呼叫。
// returnValue.node 這個檔案就是編譯後產生的檔案,通過 NODE_MODULE(returnValue, init) 決定的檔名 const returnValue = require('./build/Release/returnValue.node'); console.log(returnValue.getTestValue());
執行結果如下:
資料流向 javaScript -> C++
上面的 demo 展示了怎樣在在 C++ 定義 JavaScript 型別,資料的是從 C++ 流向 JavaScript,反過來資料也需要從 javaScript 流向 C++,也就是呼叫 C++ 函式的時候需要傳入一些引數。
下面的程式碼展示了引數個數判斷,引數型別判斷,以及引數型別裝換成 V8 型別的過程,包括基本型別以及 Object, Array, Fuction。
#include <node.h> #include <v8.h> #include <iostream> using namespace v8; using namespace std; void GetArgument(const FunctionCallbackInfo<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"); } if (!args[0]->IsBoolean()) { printf("I am not Boolean\n"); } if (!args[0]->IsArray()) { printf("I am not Array\n"); } if (!args[0]->IsString()) { printf("I am not String\n"); } if (!args[0]->IsFunction()) { printf("I am not Function\n"); } if (!args[0]->IsNull()) { printf("I am not Null\n"); } if (!args[0]->IsUndefined()) { printf("I am not Undefined\n"); } // js Number 型別轉換成 v8 Number 型別 Local<Number> value1 = Local<Number>::Cast(args[0]); Local<Number> value2 = Local<Number>::Cast(args[1]); double value = value1->NumberValue() + value2->NumberValue(); // js String 型別轉換成 v8 String 型別 Local<String> str = Local<String>::Cast(args[2]); String::Utf8Value utfValue(str); cout<<string(*utfValue)<<endl; // js Array 型別轉換成 v8 Array 型別 Local<Array> input_array = Local<Array>::Cast(args[3]); printf("%d, %f %f\n", input_array->Length(), input_array->Get(0)->NumberValue(), input_array->Get(1)->NumberValue()); // js Object 型別轉換成 v8 Object 型別 Local<Object> obj = Local<Object>::Cast(args[4]); // 根據 key 獲取物件中的值 Local<Value> a = obj->Get(String::NewFromUtf8(isolate, "a")); Local<Value> b = obj->Get(String::NewFromUtf8(isolate, "b")); // js Array 型別轉換成 v8 Array 型別 Local<Array> c = Local<Array>::Cast(obj->Get(String::NewFromUtf8(isolate, "c"))); cout<<a->NumberValue()<<" "<<b->NumberValue()<<endl; printf("%d, %f %f\n", c->Length(), c->Get(0)->NumberValue(), c->Get(1)->NumberValue()); // js String 型別轉換成 v8 String 型別 Local<String> cString = Local<String>::Cast(c->Get(2)); String::Utf8Value utfValueD(cString); cout<<string(*utfValueD)<<endl; // 根據 key 獲取物件中的值 Local<Object> d = Local<Object>::Cast(obj->Get(String::NewFromUtf8(isolate, "d"))); Local<String> dString1 = Local<String>::Cast(d->Get(String::NewFromUtf8(isolate, "m"))); String::Utf8Value utfValued1(dString1); cout<<string(*utfValued1)<<endl; // 根據 key 獲取物件中的值 Local<String> dString2 = Local<String>::Cast(d->Get(String::NewFromUtf8(isolate, "n"))); String::Utf8Value utfValued2(dString2); cout<<string(*utfValued2)<<endl; // js Booelan 型別轉換成 v8 Boolean 型別 Local<Boolean> FlagTrue = Local<Boolean>::Cast(args[5]); cout<<"Flag: "<<FlagTrue->BooleanValue()<<endl; // js Function 型別轉換成 v8 Function 型別 Local<Function> cb = Local<Function>::Cast(args[8]); const unsigned argc = 2; Local<Value> argv[2]; argv[0] = a; argv[1] = b; cb->Call(Null(isolate), argc, argv); args.GetReturnValue().Set(value); } void Init(Local <Object> exports, Local <Object> module) { NODE_SET_METHOD(module, "exports", GetArgument); } NODE_MODULE(argumentss, Init)
通過 node-gyp 編譯後,可以通過如下的方式呼叫。
const getArguments = require('./build/Release/arguments'); console.log(getArguments(2, 3, 'Hello Arguments', [1, 2, 3], { a: 10, b: 100, c: [23, 22, "我是33"], d: { m: '我是22', n: '我是23' } }, true, null, undefined, function myFunction(...args) { console.log('I am Function!'); console.log(...args); console.log('I am Function!'); }));
執行結果如下:
關於其他的型別,我這裡就就不一一介紹,V8 文件裡面都有對應的 API。
NAN
由於 V8 的 API 還沒有徹底穩定下來,所以對於不同版本的 Node.js 型別相關的 API 會發生變化,而 NAN 幫我們做了封裝,在編碼的時候不需要關心版本問題,只需要引入相應的標頭檔案即可。
引入標頭檔案後,可以如下使用方式:
v8::Local<v8::Primitive> Nan::Undefined() v8::Local<v8::Primitive> Nan::Null()
參考資料
- Type conversions from JavaScript to C++ in V8
- node addon
- v8 types documentation
- node-gyp
- gyp user documentation
- nan
相關文章
- 字串和Date型別之間的轉換字串型別
- 【C++】C++之型別轉換C++型別
- C++中的向上型別轉換和向下型別轉換C++型別
- Map和String型別之間的轉換型別
- date和timestamp型別之間的轉換型別
- java基本型別和物件之間的轉換Java型別物件
- Java 資料型別之間的轉換Java資料型別
- C++型別轉換C++型別
- c++ 型別轉換C++型別
- Java學習--Java 中基本型別和字串之間的轉換Java型別字串
- C++ 常型別轉換C++型別
- Java--包裝類(基本型別和字串之間的轉換)、進位制轉換Java型別字串
- 淺談 Go 型別轉換之間的那些事Go型別
- 淺談Go型別轉換之間的那些事Go型別
- 5.JavaScript資料型別之間的轉換JavaScript資料型別
- C++ 的強制型別轉換C++型別
- C++隱式型別的轉換C++型別
- SQL資料型別和C#資料型別間的轉換SQL資料型別C#
- 字元型別轉換成時間型別字元型別
- nodejs字元與位元組之間的轉換NodeJS字元
- C++強制型別轉換C++型別
- 再談c++型別轉換C++型別
- C++ 動態型別轉換C++型別
- c++中幾種常見的型別轉換。int與string的轉換,float與string的轉換以及string和long型別之間的相互轉換。to_string函式的實現和應用。C++型別函式
- C++基本資料型別及型別轉換C++資料型別
- C++ 表示式中的型別轉換C++型別
- RGB和HSL之間的轉換 C++實現C++
- 7.GoLang中基本資料型別之間的轉換Golang資料型別
- C++ 型別轉換(conv.)C++型別
- C++隱式類型別轉換C++型別
- C++ 隱式類型別轉換C++型別
- C C++ 強制型別轉換C++型別
- C++ 重解釋型別轉換C++型別
- C++整理18_型別轉換_C++型別
- 時間型別及格式轉換型別
- c++隱式型別轉換存在的陷阱C++型別
- Generic:型別和值之間的對映 (轉)型別
- 強制型別轉換之(==)型別