当前位置: 首页 > 科技观察

NodeJS和C++的类型转换

时间:2023-03-13 12:05:14 科技观察

我很喜欢用Node.js,但是涉及到计算密集型的场景,Node.js就不太擅长了。在这种情况下,C++是一个不错的选择。幸运的是,Node.js官方提供了C/C++Addons的机制,这样我们就可以使用V8API来结合Node.js和C++。虽然Node.js官网有很多关于如何使用这些API的文档,但是在JavaScript和C++之间传递数据是一件很麻烦的事情,而且C++是强类型语言(“1024”是字符串类型而不是Integer类型),而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#includeusingnamespacev8;voidTest(constv8::FunctionCallbackInfo&args){Isolate*isolate=args.GetIsolate();//声明变量Localretval=v8::Number::New(isolate,1000);}voidinit(Localexports,Localmodule){NODE_SET_METHOD(exports,"getTestValue",Test);}NODE_MODULE(returnValue,init)V8类型API文档你会发现对于基本的JavaScript类型,只有变量声明而没有变量赋值。一开始我可能觉得这很奇怪,但仔细想想,我发现这是有道理的。主要有以下几个原因:JavaScript的基本类型是不可变类型,所有的变量都指向一个不可变的内存单元,vara=10,那么a指向的内存单元中包含的值为5,重新赋值a=100,并没有改变这个内存单元的值,而是指向另一个内存单元,这个内存单元的值为100。如果两个变量x和y被声明为10,那么它们指向同一个内存单元。函数的参数是按值传递的,而不是按引用传递的。在JavaScript中调用C++函数时,如果参数是基本类型,则每次都复制值。更改参数值不会影响原始值。.用Local声明的变量都是对内存单元的引用。因为第一个原因,不可能改变引用的值指向另一个内存单元,所以没有变量的重新赋值。数据流向C++->JavaScript下面的demo定义了一些常用的JavaScript类型,包括基本类型和Object、Array、Function。#include#includeusingnamespacev8;voidMyFunction(constv8::FunctionCallbackInfo&args){Isolate*isolate=args.GetIsolate();args.GetReturnValue().Set(String::NewFromUtf8(isolate,"HelloWorld!"));}voidTest(constv8::FunctionCallbackInfo&args){Isolate*isolate=args.GetIsolate();//数字类型声明Localretval=v8::Number::New(isolate,1000);//字符串类型声明Localstr=v8::String::NewFromUtf8(isolate,"HelloWorld!");//对象类型声明Localobj=v8::Object::New(isolate);//对象赋值obj->Set(v8::String::NewFromUtf8(isolate,"arg1"),str);obj->Set(v8::String::NewFromUtf8(isolate,"arg2"),retval);//函数类型声明和赋值Localtpl=v8::FunctionTemplate::New(isolate,MyFunction);Localfn=tpl->GetFunction();//函数名fn->SetName(String::NewFromUtf8(isolate,"theFunction"));obj->Set(v8::String::NewFromUtf8(isolate,"arg3"),fn);//布尔声明Localflag=Boolean::New(isolate,true);obj->Set(String::NewFromUtf8(isolate,"arg4"),flag);//数组类型声明Localarr=Array::New(isolate);//数组赋值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);//未定义类型声明Localund=Undefined(isolate);obj->Set(String::NewFromUtf8(isolate,"arg6"),und);//空类型声明Localnull=Null(isolate);obj->Set(String::NewFromUtf8(isolate,"arg7"),null);//返回JavaScript调用args.GetReturnValue().Set(obj)时的返回值;}voidinit(Localexports,Localmodule){NODE_SET_METHOD(exports,"getTestValue",Test);}NODE_MODULE(returnValue,init)all所有插件都需要一个初始化函数,比如下面的代码:voidInitialize(Localexports);NODE_MODULE(module_name,Initialize)Initialize为初始化函数,module_name为编译后生成的二进制文件名,以上代码通过node-gyp编译后的模块名为returnValue(编译过程官方文档C/C++Addons有详细介绍),可以通过以下方式调用。//returnValue.node这个文件是编译后生成的文件,文件名由NODE_MODULE(returnValue,init)决定constreturnValue=require('./build/Release/returnValue.node');console.log(returnValue.getTestValue());运行结果如下:数据流向javaScript->C++上面的demo展示了如何在C++中定义JavaScript类型,数据从C++流向JavaScript,反过来数据也需要从javaScript流向C++,即就是,调用C++函数需要传入一些参数,下面的代码展示了判断参数个数,判断参数类型,将参数类型转换成V8类型的过程,包括基本类型,Object,Array,和功能。#include#include#includeusingnamespacev8;usingnamespacestd;voidGetArgument(constFunctionCallbackInfo&args){Isolate*isolate=args.GetIsolate();//参数长度判断if(args.Length()<2){isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate,"Wrongnumberofarguments")));return;}//参数类型判断if(!args[0]->IsNumber()||!args[1]->IsNumber()){//抛出错误isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate,"argumnetsmustbenumber")));}if(!args[0]->IsObject()){printf("IamnotObject\n");}if(!args[0]->IsBoolean()){printf("IamnotBoolean\n");}if(!args[0]->IsArray()){printf("IamnotArray\n");}if(!args[0]->IsString()){printf("IamnotString\n");}if(!args[0]->IsFunction()){printf("IamnotFunction\n");}if(!args[0]->IsNull()){printf("IamnotNull\n");}if(!args[0]->IsUndefined()){printf("IamnotUndefined\n");}//jsNumber类型转换为v8Number类型Localvalue1=Local::Cast(args[0]);Localvalue2=Local::Cast(args[1]);doublevalue=value1->NumberValue()+value2->NumberValue();//jsString类型转v8String类型Localstr=Local::Cast(args[2]);String::Utf8ValueutfValue(str);cout<input_array=Local::Cast(args[3]);printf("%d,%f%f\n",input_array->Length(),input_array->Get(0)->NumberValue(),input_array->Get(1)->NumberValue());//jsObject类型转换为v8Object类型Localobj=Local::Cast(args[4]);//根据key获取对象中的值Locala=obj->Get(String::NewFromUtf8(isolate,"a"));Localb=obj->Get(String::NewFromUtf8(isolate,"b"));//jsArray类型转换为v8Array类型Localc=Local::Cast(obj->Get(String::NewFromUtf8(isolate,"c")));cout<NumberValue()<<""<NumberValue()<Length(),c->Get(0)->NumberValue(),c->Get(1)->NumberValue());//jsString类型转为v8String类型LocalcString=Local::Cast(c->Get(2));String::Utf8ValueutfValueD(cString);cout<d=Local::Cast(obj->Get(String::NewFromUtf8(isolate,"d")));LocaldString1=Local::Cast(d->Get(String::NewFromUtf8(isolate,"m")));String::Utf8ValueutfValued1(dString1);cout<dString2=Local::Cast(d->Get(String::NewFromUtf8(isolate,"n")));String::Utf8ValueutfValued2(dString2);cout<;FlagTrue=Local::Cast(args[5]);cout<<"Flag:"<BooleanValue()<cb=Local::Cast(args[8]);constunsignedargc=2;Localargv[2];argv[0]=a;argv[1]=b;cb->Call(Null(隔离),argc,argv);args.GetReturnValue().Set(value);}voidInit(Localexports,Localmodule){NODE_SET_METHOD(module,"exports",GetArgument);}NODE_MODULE(argumentss,Init)通过node-gyp编译后,可以调用constgetArguments=require('./build/Release/arguments');console.log(getArguments(2,3,'HelloArguments',[1,2,3],{a:10,b:100,c:[23,22,"我33"],d:{m:'我22',n:'我23'}},true,null,undefined,functionmyFunction(...args){console.log('IamFunction!');console.log(...args);console.log('IamFunction!');}));运行结果如下:关于其他Type,这里就不一一介绍了,在V8的文档中有对应的API。由于NAN的V8API还没有完全稳定下来,不同版本Node相关的API。头文件没问题。导入头文件后,可以如下使用:到V8中的C++节点插件v8类型文档node-gypgyp用户文档nan