N-API编程之HelloWord

1     技术研究背景
公司产品考虑部署到kubernetes环境中,为了使产品具备云原生应用的一些特性和优点,整个系统需要重构。公司产品原有界面全部基于C/S架构设计,基于云原生的特点,界面逐步实行B/S重构,B/S重构必须选定一种web化技术路线。

1.1   产品特点因素
原有产品大部分使用C/C++语言开发,产品的大部分数据都在共享内存中。因此,总的思路使通过C++写扩展的JavaScript接口,实现对共享内存的读取,实现对数据的读取。并可以对已有的C++模块封装成JavaScript接口,实现模块的复用。

图1


如图1所示,在一个POD中,多个业务容器的数据都在共享内存中,部署了nodejs服务的容器通过读取共享内存中的数据对外提数据,从而实现界面的web化。

1.2   技术路线比较
产品的web化有常用的技术路线有使用java生态体系。图1中nodejs容器理论上可以选用java生态web服务器替换,c++的Java接口也可以使用JNI技术实现。之所以选择图1的技术路线,基于两个方面考虑。

1.2.1     web服务器比较
java体系常用的tomcat与nodejs常用的express都没有深入实践。java体系根据产品特点,需要每个POD部署一个tomcat服务或者其他未知的方案,如果是每个POD部署各个tomcat服务相对与nodejs太重。

相关资料介绍nodesjs是轻量级的,比较适合每个pod启动一个web服务。学习难易程度nodejs比java体系感觉要容易上手些,JavaScript前后端统一更节省了学习成本。

产品的特点界面都是一些实时数据的展示,简单的html页面即可,nodejs使用JavaScript作为开发语言,前后端数据传输会减少数据转换操作。另外,产品主要功能界面特点类似网络游戏页面,这些游戏后台服务nodejs居多,故web服务器倾向与使用nodejs。

1.2.2     JNI与N-API比较
JNI与N-API技术类似,都是进行语言级别的转换和调用。以前用JNI开发过一些模块,没有深入研究,主要是JAVA和C++类似,功能也差不多,JNI的需求场景实在不多,没有太深刻的理解,也不想深入研究。

N-API是随着nodejs而生发展的新技术,市面资料也不多。根据nodejs的实现原理,N-API看上去像是对nodejs原生的扩展,感觉做N-API开发比做JNI开发更有意义。

1.3   N-API知识体系
选用N-API作为产品开发技术之一,开始了解N—API开发需要的知识或者工具。

1.3.1     vscode
N-API开发需要同时使用C/C++和JavaScript,vscode是大多数人的选择,windows,linux平台都支持,比较方便。一些常用插件安装觉得不方便时Google下,我的环境安装了哪些,自己都记不清楚了。

1.3.2     nodejs
nodejs安装参看官网https://nodejs.org/zh-cn/download/,我安装的是V12.16.2,安装完成后使用node –v命令行查看安装是否成功。

在windows和linux平台下安装,windows下安装单纯是开发调试方便,最终服务器运行在linux平台下。下载对应的源代码,这里面有官方示例,学习起来方便些。

1.3.3     node-gyp
node-gyp官网是https://github.com/nodejs/node-gyp,分别在windows和linux平台下安装。安装过程大概两个步骤,安装C/C++编译工具,安装node-gyp。

windows平台下,先在控制台输入:npm install --global --production windows-build-tools,安装window下C/C++编译工具。然后npm install -g node-gyp安装gyp本身。

linux平台下,C/C++通常使用gcc编译,一般都已经安装,所以只需要npm install -g node-gyp本身就可以了。

安装完成后运行node-gyp configure不报错安装完成,图2是window安装完成后截图,图3是linux安装完成后截图。编译环境安装完毕,可以干活了。出现错误提示,google吧,这个不难解决。


图2


图3

1.3.4     VS2019
安装VS2019大部分原因是开发C/C++需要,安装完后可能要重新配置下node-gyp。

配置命令好像是npm config set msvs_version 2019。主要是告诉系统node-gyp中windows下C/C++编译使用VS2019了,以前运行npm install --global --production windows-build-tools时默认安装的好像不是VS2019编译工具。

还有关于python版本的问题,现在pytohn大多使用pytohn3了,google吧,研发水平总是在Google过程中成长。

2     demo实现目标
主要测试JavaScript对共享内存的读写。编写JavaScript两个模块共同对一块共享内存进行操作,一个模块读,一个模块写。

2.1   JavaScript代码
shareRead.js读取共享内存内容,在控制台打印出来。shareWrite.js写共享内存内容,写完后的内容通过shareRead.js打印出来。

2.2   C代码
JavaScript语言标准库提供对共享内存操作的接口,nodejs也没有提供。shmdemo.cxx封装对某一块特定的共享内存的读写操作。shmdemo.cxx就是nodejs的N-API插件的实现,导出两个对共享内存读写的JavaScript方法。

2.3   扩展思考
产品中其他C/C++编写的模块可以对同一块共享内存进行操作,从而实现C/C++模块和JavaScript模块数据的交互,并实现C/C++模块的数据到HTML页面的展示。

3     JavaScript代码
3.1   shareRead.js
//从N-API插件中导入模块信息

const blg_shmObject = require('./build/Release/shmdemo.node');
// 初始化数据
var txt = "123唐qw@#$¥¥#!";
var buferWrite = new Buffer.from(txt);
var bufLen = buferWrite.length;
//调用N-API模块写入信息到共享内存
blg_shmObject.write(buferWrite,bufLen);
setInterval(() => {
    //定时读取共享内存信息并在控制台输出,共享内存信息发生变化时,实时变化
    var bufferStr=blg_shmObject.read(bufLen).toString('utf8');
    console.log('Read Message: '+ bufferStr);
}, 1000);

3.2   shareWrite.js
//从N-API插件中导入模块信息
const blg_shmObject = require('./build/Release/shmdemo.node');
var txt = "456唐qw@#$¥¥#!";
var buferWrite = new Buffer.from(txt);
var bufLen = buferWrite.length;
//调用N-API模块写入信息到共享内存
var writeStrLen = blg_shmObject.write(buferWrite,bufLen);
console.log('Write Message:strLen:' + writeStrLen + '('+bufLen+')' );

4     C/C++代码
demo中common.h、entry_point.c文件是直接从nodejs测试用例中复制过来的,功能的主要实现在shmdemo.cxx中。

4.1   shmdemo.cxx
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

//不同的平台共享内存的实现方式不一样
#ifdef WIN32
#include <windows.h>
#else
#include <sys/shm.h>
#endif

#include <node_api.h>
#include "./common.h"

#define SHM_KEY_VALUE 0x40001 //共享内存key

//获取共享内存地址信息
void *get_share_adress(size_t min_size, int is_init = 0)
{
  void *mmap_ptr = NULL;
#ifdef WIN32
  HANDLE shmid;
  char shmfilename[50];
  sprintf(shmfilename, "UTLSHM%d", SHM_KEY_VALUE);
  shmid = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, shmfilename);
  if (shmid == NULL)
  {
    shmid = CreateFileMapping((HANDLE)INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, min_size, shmfilename);
  }
  mmap_ptr = (void *)MapViewOfFile(shmid, FILE_MAP_ALL_ACCESS, 0, 0, 0);

#else
  int shmid;
  shmid = shmget((key_t)SHM_KEY_VALUE, min_size, 0666 | IPC_CREAT);
  if (shmid < 0)
    return 0;
  mmap_ptr = (void *)shmat(shmid, (void *)0, 0666);

#endif
  if (mmap_ptr == ((void *)-1))
  {
    printf("get_share_adress err!\n");
    return 0;
  }
  return mmap_ptr;
}

/*实现JavaScript方法read;

shmdemo.read(bufLen)
[in] bufLen: <number>     //要读入的共享内存长度
Returns: <Buffer>          //共享内存的内容
*/

napi_value readMethod(napi_env env, napi_callback_info info)
{
  size_t argc = 1;
  napi_value args[1];
  //获取JavaScript调用参数信息
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
  NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments");
  uint32_t strLen;
  
  //转换JavaScript入口参数number格式的bufLen为c语言长度uint32_t格式的strLen
  NAPI_CALL(env, napi_get_value_uint32(env, args[0], &strLen));

  //获取共享内存地址
  void *mmap_ptr = get_share_adress(strLen);
  napi_value output;
  //将共享内存中的内容创建成一个Buffer并返回
  NAPI_CALL(env, napi_create_buffer_copy(env, strLen, mmap_ptr, NULL, &output));

  //返回这个Buffer
  return output;
}

/*实现JavaScript方法write;
shmdemo.write(buffer,bufLen)
[in] buffer:<Buffer>    //要写入的共享内存内容
[in] bufLen:<number>    //要写入的共享内存长度
Returns: <number>        //成功写入的共享内存长度
*/

napi_value writeMethod(napi_env env, napi_callback_info info)
{
  size_t argc = 2;
  napi_value args[2];
  //获取JavaScript调用参数信息
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
  NAPI_ASSERT(env, argc >= 2, "Wrong number of arguments");

  char *bufferData;
  size_t outStrLen;
  //转换JavaScript入口参数Buffer格式的buffer为c语言char *格式的bufferData
  NAPI_CALL(env, napi_get_buffer_info(env, args[0], (void **)(&bufferData), &outStrLen));
  uint32_t strLen;
  //转换JavaScript入口参数number格式的bufLen为c语言长度uint32_t格式的strLen
  NAPI_CALL(env, napi_get_value_uint32(env, args[1], &strLen));
  //将Buffer中的信息写入共享内存
  char *mmap_ptr = (char *)get_share_adress(strLen);
  memcpy(mmap_ptr, bufferData, outStrLen);

  //将写入共享内存的长度创建成一个<number>
  napi_value output;
  NAPI_CALL(env, napi_create_uint32(env, outStrLen, &output));

  //返回这个<number>
  return output;
}


4.2   common.h
#include <js_native_api.h>
// Empty value so that macros here are able to return NULL or void
#define NAPI_RETVAL_NOTHING  // Intentionally blank #define
见源代码

5     binding.gyp
官网地址https://github.com/nodejs/node-gyp,最好的文档就是官网。

5.1   binding.gyp内容
见源代码

6     编译测试
代码可以运行在windows平台和linux平台,直接上两个图。
6.1   windows下编译

图4
6.2   linux下编译

源代码下载地址:https://download.csdn.net/download/eruter/12556141

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值