赞
踩
将c++接口转换到js接口有两种方式:N-API和V8引擎。
NAPI_MODULE_INIT() {
napi_value fn = nullptr;
NODE_API_CALL(env, napi_create_function(env, nullptr, 0, Init, nullptr, &fn));
NODE_API_CALL(env, napi_set_named_property(env, exports, "Init", fn));
return exports;
}
//NODE_API_CALL宏定义参见node.js Github上的示例
napi_value Init(napi_env env, napi_callback_info info) { size_t argc = 2; napi_value argv[2];//需要预估数组大小,不能小于实际参数个数,否则调用napi_get_cb_info会出现崩溃 napi_valuetype valueType = napi_undefined; //1.get param information NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr)); //2.throw exception if (argc < 2) { NODE_API_CALL(env,napi_throw_type_error(env, nullptr, "error message"); } //3.type check NODE_API_CALL(env, napi_typeof(env, argv[0], &valueType)); if (valueType != napi_number) { NODE_API_CALL(env,napi_throw_type_error(env, nullptr,"error message"); } NODE_API_CALL(env, napi_typeof(env, argv[1], &valueType)); if (valueType != napi_string) { NODE_API_CALL(env,napi_throw_type_error(env, nullptr,"error message"); } return nullptr; }
//1.创建Number类型 int iValue = 88; napi_value value = nullptr; NODE_API_CALL(env, napi_create_int32(env, iValue, &value));//napi_create_double、napi_create_uint32和napi_create_int64的用法类似 //2.创建String类型 std::string strCxx = "hello world"; napi_value strValue= nullptr; NODE_API_CALL(env,napi_create_string_utf8(env, strCxx.c_str(), strCxx.size(), &strValue)); //3.创建Array //+ 创建数组 int iArraySize = 10; napi_value napiArray = nullptr; NODE_API_CALL_NO_RETURN(env, napi_create_array_with_length(env, (size_t)iArraySize, &napiArray)); //+ 为数组赋值 for (int i = 0; i < iArraySize ; i++) { NODE_API_CALL_NO_RETURN(env, napi_set_element(env, napiArray, i, i)); } //4.创建对象 //+ 创建对象 napi_value obj = nullptr; NODE_API_CALL_NO_RETURN(env, napi_create_object(env, &obj)); //+ 为对象设置属性及创建ExternArrayVuffer napi_value key = nullptr, value = nullptr; int iValue = 8; std::string strKey = "age"; //++ 设置int型属性 NODE_API_CALL_NO_RETURN(env, napi_create_int32(env, iValue, &value)); NODE_API_CALL_NO_RETURN(env, napi_create_string_utf8(env, strKey.c_str(), NAPI_AUTO_LENGTH, &key)); NODE_API_CALL_NO_RETURN(env, napi_set_property(env, obj, key, value)); //++ 设置extern arrayBuffer(ArrayBuffer在JS中需要通过视图(TypeArray/DataView)来操作,Buffer在JS中实质上是Uint8Array,详情参见“关于js中的ArrayBuffer文章”) uint8* pData = new uint8[100]; napi_value externArrayBuf = nullptr; strKey = "pY"; memset(pData,0,100); NODE_API_CALL_NO_RETURN(env, napi_create_external_arraybuffer(env, (void*)pData , 100, nullptr, nullptr, &externArrayBuf)); NODE_API_CALL_NO_RETURN(env, napi_create_string_utf8(env, strKey.c_str(), NAPI_AUTO_LENGTH, &key)); NODE_API_CALL_NO_RETURN(env, napi_set_property(env, obj, key, externArrayBuf ));
//1.获取int型 //value为napi_value类型封装的Number int iUserId = 0; NODE_API_CALL(env, napi_get_value_int32(env, value , &iUserId)); //2.获取string型 //value为napi_value类型封装的string char chUsername[MAX_BUFFER_SIZE] = {0,}; size_t sRealSize = 0; NODE_API_CALL(env,napi_get_value_string_utf8(env, value , chUsername, MAX_BUFFER_SIZE, &sRealSize)); //3.获取bool类型 //value为napi_value类型封装的String bool bMute = false; NODE_API_CALL(env, napi_get_value_bool(env, value, &bMute)); //4.获取longlong类型 //value为napi_value类型封装的String long long llTimestamp = 0; NODE_API_CALL(g_env,napi_get_value_int64(g_env, value, &llTimestamp)); //5.获取对象的属性 //obj为napi_object napi_value property = nullptr,key = nullptr; NODE_API_CALL(g_env, napi_create_string_utf8(g_env, "peerId", NAPI_AUTO_LENGTH, &key)); NODE_API_CALL(g_env, napi_get_property(g_env, obj, key, &property));
4.c++模块中为多线程时如何传输数据给js层
当基于c++开发的模块中除主线程外还有其他线程时,如何在非主线程中传输数据到js层?
js层中的主线程一般是指渲染线程,大部分的N-API都是需要在主线程中才能使用。当需要在c++模块插件中的其他线程中传输数据给js层就需要N-API的线程安全函数了,请见下面示例
//1.在主线程中创建线程安全函数 napi_threadsafe_function tsFn; napi_value async_name; NODE_API_CALL(env, napi_create_string_utf8(env,"LocalStreamCallback", NAPI_AUTO_LENGTH, &async_name)); //jsLocalStreamCallback为js层的回掉函数 //CallbackJSFn为c++层的回调函数 //tsFn线程安全函数 NODE_API_CALL(env, napi_create_threadsafe_function(env, jsLocalStreamCallback,nullptr,async_name, 0,2,nullptr,nullptr,nullptr, CallbackJSFn,&tsFn)); //在非主线程中调用线程安全函数 //+ tsfn为主线程中创建的线程安全函数 napi_acquire_threadsafe_function(tsFn); //+ 调用线程安全函数成功后,则执行创建线程安全函数时设置的CallbackJSFn回调函数 napi_call_threadsafe_function(tsFn, cbData, napi_tsfn_nonblocking); //在CallbackJSFn回调函数中调用js层的函数(CallbackJSFn是由主线程调用的) void CallbackJSFn(napi_env env, napi_value cb, void* hint, void* data) { NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined)); //cb为创建线程安全函数时设置的js层回调函数jsLocalStreamCallback NODE_API_CALL_NO_RETURN(env, napi_call_function(env, undefined, cb, 1, &napiArray, nullptr)); //执行完线程安全函数后若后续不再使用,则需要释放掉线程安全函数 NODE_API_CALL_NO_RETURN(env, napi_release_threadsafe_function(tsfn, napi_tsfn_release)); }
N-API的线程安全函数使用分为创建、调及销毁三个过程:
【创建】 在主线程中调用,要设置js层回调函数和供主线程调用的c++层函数
【调用】 在非主线程中调用,其实是将线程安全函数压入队列,等待主线程调用
【销毁】 在不需要线程安全函数的时候进行销毁
总结:
在非主线程中传输数据到js层,会涉及到c++到js的数据类型转换,数据类型转换所需要的函数必须在主线程中才能执行,因此需要实现主线程与非主线成之间的数据传输,线程安全函数是通过共享队列的方式在线程之间传输数据,非主线程将线程安全函数及参数放入队列中,主线程从队列提取消息,在消息中提取出线程安全函数对应的c++回调函数及参数并执行,至此,CallbackJSFn在主线成中的到执行,非主线程的数据也通过函数参数的方式传送到主线程,将数据类型转换后调用js层函数,完成数据到js层的传输。
【HandleScope】 一个函数中,可以有很多Handle,而HandleScope则相当于用来装Handle(Local)的容器,当HandleScope生命周期结束的时候,Handle也将会被释放,会引起Heap中对象引用的更新。HandleScope是分配在栈上,不能通过New的方式进行创建。对于同一个作用域内可以有多个HandleScope,新的HandleScope将会覆盖上一个HandleScope,并对Local Handle进行管理。
1.模块接口导出
NODE_MODULE_INIT(/*Local<Object> exports, Local<Value> module, Local<Context> context*/) {
Isolate* isolate = context->GetIsolate();
Local<External> external = External::New(isolate, data);
exports->Set(context,
String::NewFromUtf8(isolate, "Init").ToLocalChecked(),
FunctionTemplate::New(isolate, Init, external)
->GetFunction(context).ToLocalChecked()).FromJust();
exports->Set(context,
String::NewFromUtf8(isolate, "SetEnv").ToLocalChecked(),
FunctionTemplate::New(isolate, SetEnv, external)
->GetFunction(context).ToLocalChecked()).FromJust();
}
2.导出接口定义及参数解析
void EngineInit(const FunctionCallbackInfo<Value>& args) {
int iUserId = JSCXXTypeConversion::JSToCXXInt(args[0]);
std::string strUserName = JSCXXTypeConversion::JSToCXXString(args.GetIsolate(),args[1]);
}
3.类型转换
JSCXXTypeConversion.h
#pragma once #include <v8.h> using namespace v8; class JSCXXTypeConversion { public: //JS To CXX static std::string JSToCXXString(Isolate* isolate, Local<Value> value); static int JSToCXXInt(Local<Value> value); static long JSToCXXLong(Local<Value> value); static long long JSToCXXLongLong(Local<Value> value); static void JSToCXXObject(Local<Value> value); static bool JSToCXXBool(Local<Value> value); //CXX To JS static Local<String> CXXToJSString(Isolate* isolate, std::string strValue); static Local<Value> CXXToJSInt(int iValue); };
JSCXXTypeConversion.cpp
#include "JSCXXTypeConversion.h" //JS To CXX std::string JSCXXTypeConversion::JSToCXXString(Isolate* isolate, Local<Value> value){ v8::String::Utf8Value sValue(isolate, value); std::string strValue(*sValue, sValue.length()); return strValue; } int JSCXXTypeConversion::JSToCXXInt(Local<Value> value){ double dValue = static_cast<double>(value.As<Number>()->Value()); return static_cast<int>(dValue); } long JSCXXTypeConversion::JSToCXXLong(Local<Value> value){ double dValue = static_cast<double>(value.As<Number>()->Value()); return static_cast<long>(dValue); } long long JSCXXTypeConversion::JSToCXXLongLong(Local<Value> value){ double dValue = static_cast<double>(value.As<Number>()->Value()); return static_cast<long long>(dValue); } void JSCXXTypeConversion::JSToCXXObject(Local<Value> value){ } bool JSCXXTypeConversion::JSToCXXBool(Local<Value> value){ bool bValue = static_cast<bool>(value.As<Boolean>()->Value()); return bValue; } //CXX To JS Local<String> JSCXXTypeConversion::CXXToJSString(Isolate* isolate, std::string strValue){ Local<String> lstrValue = String::NewFromUtf8(isolate, strValue.c_str()).ToLocalChecked(); return lstrValue; } Local<Value> JSCXXTypeConversion::CXXToJSInt(int iValue){ }
4.解析对象与函数转换
void EnterRoom(const FunctionCallbackInfo<Value>& args){ //假定args[0]为对象,args[0]的streamCB属性是函数 Isolate* isolate = args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext(); //1.解析对象 Local<Object> obj = args[0]->ToObject(context).ToLocalChecked(); //2.解析对象属性roomID Local<String> roomIdProp = JSCXXTypeConversion::CXXToJSString(isolate, "roomId"); Local<Value> valRoomId= obj->Get(context, roomIdProp).ToLocalChecked(); //3.解析对象的streamCB属性 Local<String> streamCBProp= JSCXXTypeConversion::CXXToJSString(isolate, "streamCB"); Local<Value> valCallback = obj->Get(context, streamCBProp).ToLocalChecked(); Local<Function> cb = Local<Function>::Cast(valCallback); //4.调用Local<Function> const unsigned argc = 2; HandleScope hs(isolate); Local<Context> context = Context::New(isolate); Context::Scope contextScope(context); Local<Value> argv[argc] = { Number::New(isolate,111), String::NewFromUtf8(isolate,"stream").ToLocalChecked() }; cb->Call(context, Null(isolate), argc, argv).ToLocalChecked(); //5.Local<Function>转换为Persistent<Function> Persistent<Function> fn(isolate, cb); //+ 调用Persistent<Function> fn.Get(isolate)->Call(context, Null(isolate), argc, argv).ToLocalChecked(); }
5.创建数组、创建对象
void CaptureInfoCallBack(const FunctionCallbackInfo<Value>& args) {
int iSize = 10;
int iArray[10] = {0};
Isolate* isolate = args.GetIsolate();
//创建数组
Local<Value> arrayArgv = Array::New(isolate , iSize);
for (int i = 0; i < iSize; i++) {
//创建对象
Local<Object> obj = Object::New(isolate);
obj->Set(context, String::NewFromUtf8(isolate,"id").ToLocalChecked(),
Number::New(pIsolate, iArray[i]));
arrayArgv.As<Array>()->Set(context, i, obj);
}
}
6.关于v8的多线程编程
基于node.js的v8多线程编程,没有找到合适方法创建多个隔离,目前是采用N-API方式代替。
参考链接:
node.js中文网:http://nodejs.cn/api/addons.html
node.js Github:https://github.com/nodejs/node (test目录下由api示例)
通过v8的js与c++的类型转换:https://github.com/freezer333/nodecpp-demo/blob/master/conversions/strict/strict_type_demo.cp
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。