利用napi编写node的C++扩展进阶之多次调用回调函数的实现方案

一、背景

最近有个需求是将海康摄像头在浏览器上进行实时显示。


然而海康目前的web接口无法满足我们拥有的设备,主要提到我们的设备不支持websocket取流,故不支持无插件开发,需要用到非常旧的chrome版本,方可使用它们的开发包,而且是有插件开发包。


有技术不求人,自己动手自己做。于是本项目立项,自己取流然后进行显示。*


小主出于napi为官方维护,且对node版本可以自动适配,故选之。


anyway, 本博客主要是简单介绍取流实现这一块用到的napi技术要点。

开发流程简介

  • 1、如何初始化环境,制作一个简单的node扩展(本文不做赘述,网上示例一大把), 主要用到node 环境, node-gpy
  • 初始化完成后,进入开发阶段,小主是用windows进行开发,故使用的vs作为开发环境,使用记事本亦可。

接下来你将看到

  • 获取napi错误信息。
  • 获取js传来的参数,包含传入参数及一些回调函数。
  • 多次调用js回调函数的实现(体现在回传视频流时)。

二、步入正题

napi 官网的接口说明: http://nodejs.cn/api/n-api.html

1. 获取napi错误信息接口。

无论何时进行开发,错误的出现是必然的,可能是一时疏忽,亦或是对参数的理解不到位都有可能造成错误的出现,所以获取和分析错误信息的能力非常重要。 根据官方提供的接口 napi_get_last_error_info, 由于每次代码相同,小主对其进一步定义成一个宏定义方便使用。

#define GET_NAPI_ERROR() 	const napi_extended_error_info* errorInfo; \
						status = napi_get_last_error_info(env, &errorInfo); \
						if (status == napi_ok) { \
							printf("last error info is %s\n", errorInfo->error_message); \
						} \
						else { \
							printf("get last error eror\n"); \
						}

如上宏定义,使用时,只需插入 GET_NAPI_ERROR()即可打印出出错信息。
其中的status,一般是函数开头定义好,专门用来接收napi接口状态的变量。
napi_status status;

2. 获取js传来的参数,包含传入参数及一些回调函数。

获取js参数, 我这边以C++实现的实时预览接口为例,简单介绍一下这一个接口的实现, 一共需要用到的有两个参数, 一个参数作为播放通道号进行传入,另一个参数作为视频流的返回方法——回调函数。

1) 主要用到的napi接口

  • 获取js传来的参数列表—— napi_get_cb_info
    利用napi编写node的C++扩展进阶之多次调用回调函数的实现方案_第1张图片

  • 获取参数信息 ——napi_get_value_string_utf8
    利用napi编写node的C++扩展进阶之多次调用回调函数的实现方案_第2张图片

2) 上代码理解

napi_value RealPlay(napi_env env, napi_callback_info info){// napi 标准头
	napi_value napi_ret;
	napi_status status;
	
	// 变量声明
	char strTemp[2] = {}; // 用于接收通道参数
	int channelNum = 0;	// 用于存储视频通道号
	napi_value js_callback; // 用于存储回调函数
	size_t argc = 2;	// 用于napi接口的传入(我要取多少个参数)传出(实际有多少个参数)参数
	napi_value argv[2]; // 得到js传来的所有参数,均为napi_value类型,之后在逐个进行解析
	
	status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
	assert(status == napi_ok);
	if (argc < 2) {
		napi_create_int32(env, 302, &napi_ret); // 传入参数过少,直接返回
		return napi_ret;
	}
	// 至此获取到了传入的所有参数列表 argv.
	
	// 解析传入的int参数 begin
	status = napi_get_value_string_utf8(env, argv[0], strTemp, 2, nullptr); // 这里我直接使用 napi_get_value_int32接口获取不到通道号参数。故转了一下,先获取为char*,再进行char* to  int 的转换。
	channelNum = atoi(strTemp);
	// 解析传入的int参数 end
	
	// 保存获取到的回调函数
	js_callback = argv[1];
	
	// 如果需要直接调用回调函数返回数据,则调用下面的语句
	// napi_value arg_ret;
    // status = napi_create_string_utf8(env, "hello world", NAPI_AUTO_LENGTH, arg_ret);
    // assert(status == napi_ok);
    // napi_value global;
    // status = napi_get_global(env, &global);
    // assert(status == napi_ok);
    // status = napi_call_function(env, global, js_callback, 1, arg_ret, nullptr);
    // assert(status == napi_ok);
    
    // ... 其他一些执行语句

	return napi_ret;
}

3. 多次调用js回调函数的实现(体现在回传视频流时)。

js 如何多次调用回调函数呢,基于上面获取到的js_callback变量进行代码讲述。

1) 本小节用到的napi接口

  • 接口1:实际调用js函数的执行函数, 具体函数参考如下函数指针进行实现。该函数的参数由napi内部自动传入,相当于napi内部的一个回调。利用napi编写node的C++扩展进阶之多次调用回调函数的实现方案_第3张图片

  • 接口2: napi_call_threadsafe_function该函数在任何需要调用回调的地方调用, 本质上相当于Qt发出调用js函数的信号,告诉接口1,我需要调用js函数,并把data数据作为js函数的参数返回.
    其中参数napi_threadsafe_function func由接口3提供。利用napi编写node的C++扩展进阶之多次调用回调函数的实现方案_第4张图片

  • 接口3: 根据已经得到的回调函数js_callback创建napi_threadsafe_function函数, 创建后就可以在接口2调用了。利用napi编写node的C++扩展进阶之多次调用回调函数的实现方案_第5张图片

2) 看代码进一步理解

首先实现接口1的那个函数,以备后用。

napi_call_function

void realPlayThreadSafe_func_callJs(napi_env env, napi_value js_callback, void* context, void* data) {
	napi_status status;
	napi_value argv[2];
	napi_value undefined;
	status = napi_get_undefined(env, &undefined);
	// 如没必要对data进行处理,则可以直接调用js函数,并传入data参数
	status = napi_call_function(env, undefined, js_callback, 2, data, nullptr); 
	if (status != napi_ok) {
		printf("call js call back function failed\n");
		GET_NAPI_ERROR();
	}
}

之后创建全局js_cb函数。

// 由于需要在其他函数中对js_cb进行调用, 故声明为全局定义.
napi_threadsafe_function realPlayCallbackFunc = nullptr;

napi_value RealPlay(napi_env env, napi_callback_info info){
	// ... 一些其他语句
	js_callback = argv[1];
	
	// 开始创建本地 threadsafe 回调函数
	napi_value srcCbName; // napi接口需要,暂未明确其目的??,非可选参数
	if (napi_create_string_utf8(env, "realpCb", NAPI_AUTO_LENGTH, &srcCbName) == napi_ok) {
		printf("create callbak source success\n");
	}
	status = napi_create_threadsafe_function(env,
		js_callback, // napi_value 回调函数
		nullptr,
		srcCbName,
		0,
		1,
		nullptr,
		nullptr,
		nullptr,
		realPlayThreadSafe_func_callJs, // 实际的执行单元, 接口1
		&realPlayCallbackFunc);  // 全局threadsafe函数
	if (status == napi_ok) {
		printf("threadsafe function create success \n");
	}
	else {
		printf("threadsafe function create failed, ret=%d \n", status);
		GET_NAPI_ERROR();
	}
}

至此准备工作完成,找到合适的地方开始调用。

void i_need_call_js( ){
	napi_status status;
	// ...
	int data = 666;
	status = napi_call_threadsafe_function(realPlayCallbackFunc, &data, napi_tsfn_nonblocking); // 调用后,将会自动调用接口1的实现方法进一步调用js回调函数。
	assert(status == napi_ok);
	// ...
}

3) 逻辑图

如果代码看起来不够清晰,希望此图可助你理解。
利用napi编写node的C++扩展进阶之多次调用回调函数的实现方案_第6张图片

三、结束

本次经验分享到此结束,由于网上该部分资料尚未完善,结构基本由博主组织试验而成,如有不足之处还请海涵, 并可在评论区指出以示后人避坑。
有些术语可能不太准确,姑且先这样理解吧。

你可能感兴趣的:(经验详谈,C/C++,c++,后端,nodejs扩展,napi)