IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 基于C++的Node.js插件开发 -> 正文阅读

[C++知识库]基于C++的Node.js插件开发

1.从c++接口到js接口

将c++接口转换到js接口有两种方式:N-API和V8引擎。

  • N-API
    需要<node_api.h>头文件。
    N-API的目的是将插件与底层JavaScript引擎的变化隔离开来,并允许为一个主要版本编译的模块在Node.js的后续主要版本上运行,而无需重新编译。
    1.模块接口导出
    napi_create_function将c++的函数转换为JS的函数。napi_set_named_property将转换后的JS函数设置为exports的一个属性,最后返回exports。
    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上的示例
    
    2.导出接口定义及参数解析
    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;
    }
    
    3.类型转换
    c++类型和js类型是通过N-API的napi_value类型来转换的,napi_value是一个不透明的指针。
    • 在c++中创建js类型(c++类型转换位js类型)
          //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 ));
      
      
    • js类型转换为c++类型
          //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层的传输。

  • v8
    需要<v8.h>头文件。
    简单的说v8是一个c++开发库,其功能之一是用来解析js语法。v8中有两个概念需要明确一下:Isolate和Context。
    【Isolate】
    Isolate represents an isolated instance of the V8 engine. V8 isolates have completely separate states. Objects from one isolate must not be used in other isolates. The embedder can create multiple isolates and use them in parallel in multiple threads. An isolate can be entered by at most one thread at any given time. The Locker/Unlocker API must be used to synchronize.
    Isolate表示V8引擎的一个独立实例。V8隔离具有完全独立的状态。来自一个隔离物的对象不能用于其他隔离物。嵌入式程序可以创建多个隔离并在多个线程中并行使用它们。在任何给定的时间内,最多只能有一个线程进入隔离。必须使用Locker/Unlocker API进行同步。 —— 官方解释
    【Context】 JavaScript的执行环境。Context中包了JavaScript内建函数、对象等。所以,通过Context::New出来的Context都是一个全新的干净的JavaScript执行环境。
    A sandboxed execution context with its own set of built-in objects and functions.
    带有自己的一组内置对象和函数的沙箱执行上下文。—— 官方解释
    【Handle】 在V8中,内存分配都是在V8的Heap中进行分配的,JavaScript的值和对象也都存放在V8的Heap中。这个Heap由V8独立的去维护,失去引用的对象将会被V8的GC掉并可以重新分配给其他对象。而Handle即是对Heap中对象的引用。V8为了对内存分配进行管理,GC需要对V8中的所有对象进行跟踪,而对象都是用Handle方式引用的,所以GC需要对Handle进行管理,这样GC就能知道Heap中一个对象的引用情况,当一个对象的Handle引用为发生改变的时候,GC即可对该对象进行回收(gc)或者移动。因此,V8编程中必须使用Handle去引用一个对象,而不是直接通过C++的方式去获取对象的引用,直接通过C++的方式去直接去引用一个对象,会使得该对象无法被V8管理。
    Handle分为Local和Persistent两种。从字面上就能知道,Local是局部的,它同时被HandleScope进行管理。persistent,类似与全局的,不受HandleScope的管理,其作用域可以延伸到不同的函数,而Local是局部的,作用域比较小。Persistent Handle对象需要Persistent::New, Persistent::Dispose配对使用,类似于C++中new和delete.Persistent::MakeWeak可以用来弱化一个Persistent Handle,如果一个对象的唯一引用Handle是一个Persistent,则可以使用MakeWeak方法来如果该引用,该方法可以出发GC对被引用对象的回收。

【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

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-07 21:43:07  更:2021-08-07 21:43:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/8 19:43:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码