[FFH]openharmony南向研究-系统移植和驱动开发(4)南北向接口Napi实现
![[FFH]openharmony南向研究-南北向接口Napi实现(4)-鸿蒙开发者社区 [FFH]openharmony南向研究-南北向接口Napi实现(4)-鸿蒙开发者社区](https://dl-harmonyos.51cto.com/images/202205/e2e83e180571e3f12b24501a080dee833ffc4e.jpg?x-oss-process=image/resize,w_175,h_100)
综述
在openharmony的项目开发中大量使用到北向应用调取与真实物理环境交互数据的场景,比如农田检测,污染检测,甚至是一些机器人的控制和数据回收,这些功能的实现都必须要求北向使用ark编译器编译js的同时,在其中调用的js api必须由南向提供正常功能的实现。这些部分的功能实现在南向除了用户态内核态程序的维护外还需要实现与北向应用通信的接口,接口这部分将在本篇文章中仔细探讨。
参考文献
napi_generator: NAPI框架生成工具 (gitee.com)
ace_napi: Development framework for extending the JS Native Module | 原生模块扩展开发框架 (gitee.com)
OpenHarmony 源码解析之JavaScript API框架(NAPI)-51CTO.COM
OpenHarmony 源码解析之 JavaScriptAPI NAPI-C 接口-51CTO.COM
openharmony标准系统L2 JS、eTS 的napi socket 网络接口开发 TCP-开源基础软件社区-51CTO.COM
接口的分类和定义
造北向程序中导入一个jsapi接口即可实现数据的上传和下发。
在本文中采用简化描写 c->napi napi->c 来表示南向发送数据到北向和北向发送数据到南向。
其实还看到了很多种可能的南北向通讯机制,但是都没有更加详细的参数手册和操作方案,等到过段时间官方实现的更加完善后再做一些更深入的研究,包括通过c++实现Ability再通过北向调用ability进行数据收取,使用泛sensor子系统进行传感器和执行器的配置。或者是通过软总线,把l0/l1的物联网openharmony设备的信息转发到北向已经实现的支持网络协议和分布式软总线的设备上。
OpenHarmony上JS API实现方式有三种,分别是:JSI机制、Channel机制、NAPI机制。
-
JSI机制:L0~L1设备支持。
-
Channel机制:L3设备支持。
-
NAPI机制:目前仅L2-L3设备支持,后续须推广到L3~L5设备。
这部分中的工作目前JSI机制在小熊派的板子上有过尝试,而且教程和案例都还比较全面,channel机制目前在官方文档中没有看到,只闻其声不见其形,NAPI则是可以采用作为标准open harmony系统可以使用的比较好用的接口方式
![[FFH]openharmony南向研究-南北向接口Napi实现(4)-鸿蒙开发者社区 [FFH]openharmony南向研究-南北向接口Napi实现(4)-鸿蒙开发者社区](https://dl-harmonyos.51cto.com/images/202205/928c996478b2a2105017879bd284c4aeab0d4d.jpg?x-oss-process=image/resize,w_820,h_317)
在完成JS API 北向的操作方案是仅需要调取js api完成一个接口函数的调用即可,在此处约定完成,北向调用的库是napi_demo
在调用过程中可以使用多线程模式,去开启其他连续任务。
在开发中尝试了使用北向app发送信息点灯,但是在子系统的线程中访问linux驱动核心遇到了一些问题,目前仍在解决中,本篇文章先总结了各个大佬的思路去完整展现一遍NAPI构造南北向桥梁的过程.
接口的实现(hello world)
分别采用两种方案实现,第二种方案不是非常普遍适用,因为他只是调用了napi的接口且生成的是gn中定义的shared lib,如果其他的真实进程写在一起将产生一个奇怪的内存隔离现象,napi有一部分数据拿取不到
方案1
代码和实现如下
首先构建目录结构
–参考系列文章3
代码如下
同组 BUILD.gn实现
在编译后会生成一个动态链接库下载进入后即可从北向与南向完成交互(这是官方的解决方案)
仔细看代码部分会发现有两个注册部分,一个是参数表一个是接口声明,这与说明文档一致
方案二
建立目录,参考本系列第二篇文章中的代码和架构
BUILD.gn
ohos.build
其他部分参考本系列第二篇文章进行配置,会产生一个新的myapp子系统,该子系统可以完成和北向的单向交互调用,
仔细看这部分c++代码发现并没有参数表和注册,只是调用了一个函数
但是经过测试这些代码确实能够完成指定任务,这也是让人感觉些许迷惑的地方。
代码分析
但是两部分除了模块注册之外,函数注册以及函数实现方式都是一致的,并没有明显区别
具体的实现流程和操作
首先要了解,NAPI的机制中实现都是异步机制,而异步模型包括两种常用的是callback模型,Promise模型还没有进行过测试。似乎也有一些同步机制的方法,但是没看到有很多讲解。
Promise、Callback 异步模型都是 OHOS 标准异步模型之一。
(以下两个小结摘录自大佬的文章,在官方文档中并没有查到相关的技术手册。)
Promise 异步模型
Promise对象:
- ES6原生提供了Promise对象,Promise是异步编程的一种解决方案,可以替代传统的解决方案
回调函数和事件
;
- promise对象是一个异步操作的结果,提供了一些API使得异步执行可以按照同步的流表示出来,避免了层层嵌套的回调函数,保证了回调是以异步的方式进行调用的;
- 用户在调用这些接口的时候,接口实现将异步执行任务,同时返回一个 Promise 对象,其代表异步操作的结果;
- 在返回的结果的个数超过一个时,其以对象属性的形式返回。
Promise特点: 作为对象,Promise有两个特点:
- 对象的状态不受外界影响;
- 一旦状态改变了就不会再变,也就是说任何时候Promise都只有一种状态。
Callback 异步模型
- 用户在调用这些接口的时候,接口实现将异步执行任务;
- 任务执行结果以参数的形式提供给用户注册的回调函数;
- 这些参数的第一个是 Error 或 undefined 类型,分别表示执行出错与正常。
函数相关分析
首先napi的实现必须包括两个部分函数,第一个是模块初始化的部分,在其中要进行函数的定义
使用
参数一是北向使用的js名称,参数二是南向需要实现的函数名称
其他的部分一般不需要做改变
另一个部分是具体的函数实现
以上代码就简单实现了一个赋值helloworld到变量池的操作,在北向调用就可以简介访问到这个变量。
关于异步实现我搬运大佬的代码过来大家一起看看然后保留一下以后学习用,目前还不是很理解,更不要说使用了,菜鸡保命,在之后的研究中心突破了这块会来更新文章
·```C
static napi_value Get(napi_env env, napi_callback_info info)
{
size_t requireArgc = 1;
size_t argc = 3; //参数个数
napi_value argv[3] = { 0 }; //参数定义
napi_value thisVar = nullptr; //JS对象的this参数
void* data = nullptr; //回调数据指针
napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
NAPI_ASSERT(env, argc >= requireArgc, "requires 1 parameter");
auto asyncContext = new StorageAsyncContext();
asyncContext->env = env;
for (size_t i = 0; i < argc; i++) {
napi_valuetype valueType = napi_undefined;
napi_typeof(env, argv[i], &valueType);
if ((i == 0) && (valueType == napi_string)) {
napi_get_value_string_utf8(env, argv[i], asyncContext->key, KEY_BUFFER_SIZE, &asyncContext->keyLen);
} else if (valueType == napi_string) {
napi_get_value_string_utf8(env, argv[i], asyncContext->value, VALUE_BUFFER_SIZE, &asyncContext->valueLen);
} else if (valueType == napi_function) {
napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef);
break;
} else {
NAPI_ASSERT(env, false, "type mismatch");
}
}
napi_value result = nullptr;
if (asyncContext->callbackRef == nullptr) {
napi_create_promise(env, &asyncContext->deferred, &result);
} else {
napi_get_undefined(env, &result);
}
napi_unwrap(env, thisVar, (void**)&asyncContext->objectInfo);
napi_value resource = nullptr;
napi_create_string_utf8(env, "JSStorageGet", NAPI_AUTO_LENGTH, &resource);
napi_create_async_work(
env, nullptr, resource,
[](napi_env env, void* data) {
StorageAsyncContext* asyncContext = (StorageAsyncContext*)data;
auto itr = g_keyValueStorage.find(asyncContext->key);
if (itr != g_keyValueStorage.end()) {
if (strncpy_s(asyncContext->value, VALUE_BUFFER_SIZE, itr->second.c_str(), itr->second.length()) ==
-1) {
asyncContext->status = 1;
} else {
asyncContext->status = 0;
}
} else {
asyncContext->status = 1;
}
},
[](napi_env env, napi_status status, void* data) {
StorageAsyncContext* asyncContext = (StorageAsyncContext*)data;
napi_value result[2] = { 0 };
if (!asyncContext->status) {
napi_get_undefined(env, &result[0]);
napi_create_string_utf8(env, asyncContext->value, strlen(asyncContext->value), &result[1]);
} else {
napi_value message = nullptr;
napi_create_string_utf8(env, "key does not exist", NAPI_AUTO_LENGTH, &message);
napi_create_error(env, nullptr, message, &result[0]);
napi_get_undefined(env, &result[1]);
asyncContext->objectInfo->Emit(nullptr, "error");
}
if (asyncContext->deferred) {
if (!asyncContext->status) {
napi_resolve_deferred(env, asyncContext->deferred, result[1]);
} else {
napi_reject_deferred(env, asyncContext->deferred, result[0]);
}
} else {
napi_value callback = nullptr;
napi_get_reference_value(env, asyncContext->callbackRef, &callback);
napi_call_function(env, nullptr, callback, sizeof(result) / sizeof(result[0]), result, nullptr);
napi_delete_reference(env, asyncContext->callbackRef);
}
napi_delete_async_work(env, asyncContext->work);
delete asyncContext;
},
(void*)asyncContext,
&asyncContext->work);
napi_queue_async_work(env, asyncContext->work);
return result;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
在c->js的过程中,重要的代码如下,主要以回调函数出现
js -> c的过程中,js拉起在南向实现的函数接口,并且南向需要做一次js参数到c参数的转换,重要的代码如下
具体案例的实现
参考文献:openharmony标准系统L2 JS、eTS 的napi socket 网络接口开发 TCP-开源基础软件社区-51CTO.COM
主要参考了上面这篇中大佬讲解的一些工作,然后在此基础上做了一些线程增加,在有相关数据回调之后再南向的硬件端除了tcpip协议拉起wifi组网完成信息发送外,也完成比如一些硬件驱动的使用。
原始代码来源于上文中大佬的文章
#include "napi/native_api.h"
#include "napi/native_node_api.h"
#include "utils/log.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
static char buf[1024];
static size_t buflen;
static int wflag;
static int st;
static int client_st;
static napi_ref CallbackReff;
static napi_env envs;
void* socketserverthrd(void *ptr)
{
napi_value jsObj, prop1,prop2,prop3, callback = nullptr,undefine = nullptr;
napi_get_reference_value(envs, CallbackReff, &callback);
int port = 18000;
st = socket(AF_INET, SOCK_STREAM, 0);
int opt = SO_REUSEADDR;
setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)
{
HILOG_INFO("test0002 bind failed %s\n", strerror(errno));
return NULL;
}
if (listen(st, 20) == -1)
{
HILOG_INFO("test0002 listen failed %s\n", strerror(errno));
return NULL;
}
HILOG_INFO("test0002 listen success\n");
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(client_addr));
socklen_t len = sizeof(client_addr);
HILOG_INFO("test0002 waiting for client.......\n");
wflag = 1;
char str[1024];
while(wflag)
{
client_st = accept(st, (struct sockaddr*) &client_addr, &len);
if (client_st == -1)
{
HILOG_INFO("test0002 accept failed %s\n", strerror(errno));
return NULL;
}
HILOG_INFO("test0002 accept by %s\n", inet_ntoa(client_addr.sin_addr));
while (wflag)
{
memset(str, 0, sizeof(str));
int numbytes = recv(client_st, str, sizeof(str), 0);
if (numbytes <= 0)
break;
strcpy(buf,str);
if((int)str[0] == 170)
{
int cPara1 = (int)str[1];
int cPara2 = (int)str[2];
int cPara3 = (int)str[3];
napi_create_object(envs, &jsObj);
napi_create_int32(envs, cPara1, &prop1);
napi_create_int32(envs, cPara2, &prop2);
napi_create_int32(envs, cPara3, &prop3);
napi_set_named_property(envs, jsObj, "prop1", prop1);
napi_set_named_property(envs, jsObj, "prop2", prop2);
napi_set_named_property(envs, jsObj, "prop3", prop3);
napi_call_function(envs, nullptr, callback, 1, &jsObj, &undefine);
}
buflen = strlen(str);
}
}
return NULL;
}
static napi_value ServerStart(napi_env env, napi_callback_info info)
{
size_t argc = 1;
napi_value argv[argc];
napi_value thisVar = nullptr;
void *data = nullptr;
envs = env;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, &data));
NAPI_ASSERT(env, argc >= 1, "JSCallback Wrong number of arguments");
napi_valuetype callbackType = napi_undefined;
napi_typeof(env, argv[0], &callbackType);
NAPI_ASSERT(env, callbackType == napi_function, "parameter 1 type mismatch");
napi_create_reference(env, argv[0], 1, &CallbackReff);
pthread_t thrd;
HILOG_INFO("test0002 thrs start!");
pthread_create(&thrd, NULL, socketserverthrd,NULL);
HILOG_INFO("test0002 end!");
napi_value result = nullptr;
napi_get_undefined(env, &result);
return result;
}
static napi_value ServerStop(napi_env env, napi_callback_info info)
{
close(st);
wflag = 0;
napi_value result = nullptr;
napi_get_undefined(env, &result);
return result;
}
static napi_value ServerWrite(napi_env env, napi_callback_info info)
{
size_t requireArgc = 3;
size_t argc = 3;
napi_value args[3] = { nullptr };
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
NAPI_ASSERT(env, argc >= requireArgc, "Wrong number of arguments");
napi_valuetype valuetype0;
NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
napi_valuetype valuetype1;
NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
napi_valuetype valuetype2;
NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2));
NAPI_ASSERT(env, valuetype0 == napi_number && valuetype1 == napi_number && valuetype2 == napi_number, "Wrong argument type. Numbers expected.");
char str[4];
uint32_t a,b,c;
NAPI_CALL(env, napi_get_value_uint32(env, args[0], &a));
NAPI_CALL(env, napi_get_value_uint32(env, args[1], &b));
NAPI_CALL(env, napi_get_value_uint32(env, args[2], &c));
str[0] = (char)0xAA;
str[1] = (char)a;
str[2] = (char)b;
str[3] = (char)c;
if (-1 == write(client_st, str,4)){
HILOG_INFO("test0002 okok servertest error");
}
napi_value result = nullptr;
napi_get_undefined(env, &result);
return result;
}
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
napi_property_descriptor desc[] = {
DECLARE_NAPI_FUNCTION("ServerStart", ServerStart),
DECLARE_NAPI_FUNCTION("ServerStop", ServerStop),
DECLARE_NAPI_FUNCTION("ServerWrite", ServerWrite)
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
}
EXTERN_C_END
static napi_module demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "socketserver",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void RegisterModule(void)
{
napi_module_register(&demoModule);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
使用的是上文中方案二提到的napi注册方案完成了有关的注册,并且构造了三个北向可以调用的接口函数,完成tcp ip的读写操作。
由于篇幅限制,另一版本完成的线程增加就不放代码了,有需要可留言联系。
分析一下代码中的线程拉起
使用该函数创建了一个新的线程完成tcp servo的工作,但是在这个工作之前已经写好了有关的头文件和一些变量,
在我们所设定的项目中,tcp也是基于这部分的servo和client完成的数据交换
在这部分代码中需要更改和注意的地方是port以及地址参数需要修正,设定值和代码中的值应当不同。
如果按照文中描述的方法把模块写在了ace的demo模组中编译时的参数一定不能写错,要使用这个参数才能成功编译。
TCPIP的协议帧组成和协议解析需要专门的库和函数处理,且使用的是16进制码,需要注意的是需要提前对所传输的数据进行处理,传出后也需要做相关的解析。
总结
南北向接口的互相实现是openharmony程序中极为重要的一部分,在这部分当中既要启动底层驱动又要处理好留给北向的接口协议,对于整体的系统结构和代码解耦有比较大的要求,目前有些第三方库在调取extern_depend时无效,且没有给出说明,在napi子系统中启动hilog一直无法成功,有待解决后会补充一篇文章。
看这篇文章前没想过打通南北居然有这么多的问题
目前openharmony南北向确实有很多东西没有详细的文档和参考 只能大家都是在黑暗里打着灯笼走路只能是大家一起努力!争取能把社区建设的更好让openharmony更加完善
感谢分享,正愁怎么入手,学习中