Node.js使用ffi-napi,ref-napi,ref-array-napi,ref-struct-napi调用动态库

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Node.js使用ffi-napi,ref-napi,ref-array-napi,ref-struct-napi调用动态库相关的知识,希望对你有一定的参考价值。

参考技术A

使用electron开进行桌面程序的开发,似乎成了WEB前端开发人员转桌面程序开发的首选。近期有一些使用在electron中使用加密锁的需求,学习了一下在Node.js中通过ffi-napi模块调用动态链接库,把几款加密锁产品的动态库使用javascript封装了一下,实现了electron中使用加密锁功能。

开发过程中遇到了一些问题,踩了一些坑,这里总结记录一下。这里使用接口函数参数类型比较复杂的ROCKEY-ARM的动态链接库来进行开发。

NOTE: javascript封装的ROCKEY-ARM接口模块源码,我已经分享出来,如果只是需要electron或者Node.js工程中使用ROCKEY-ARM的网友,可以直接使用。

首先需要在node.js项目中安装调用动态链接库时需要依赖的模块 ffi-napi,ref-napi,ref-array-napi,ref-struct-napi

下面大概介绍一下这几个模块的用途:

向 飞天诚信 购买ROCKEY-ARM加密锁产品,可以获得ROCKEY-ARM的SDK,可以获得Windows和Linux的动态链接库,文件名一般为Dongle_d.和libRockeyARM.so.0.3。

ffi-napi支持Windows,Linux系统,所以.dll和.so都可以支持,在不同的操作系统下去加载不同的动态库文件就可以了。加载动态库的方法如下:

Library()第一个参数是.dll的路径,Linux系统是.so的路径。第二个参数rockeyInterface是动态库导出函数的声明,ROCKEY-ARM的导出函数比较多,我单独拿出来定义。具体下面会讲到。

首先从ROCKEY-ARM中找几个参数简单的函数来声明一下。

首先看一下上面几个接口用到的数据类型有:DONGLE_HANDLE,DWORD,DONGLE_HANDLE ,int,BYTE 这几种。
再看下ffi-napi支持的ref-napi支持的数据类型有以下类型:

参数这里应该用长度一致的数据类型,可以有以下匹配。

声明的写法如下:

一个json,key是动态库导出函数名,比如\'Dongle_Open\',value是个列表,第一个元素是返回值,第二个元素是参数。其中参数还是个列表。这个ref-napi中有适合类型的,直接写称具体类型即可,比如返回值DWORD和传入的长度int,我这里都用\'int\'。其他的参数我额外定义了句柄ryHandle、句柄的指针ptrHandle、字节的指针ptrByte。其中ryHandle,ptrryHandle,ptrByte的定义如下:

DONGLE_HANDLE本质是void *类型, void* 类型最开始的时候妄图定义一个void的数组,然后用void数组来表示void ,然后发现报断言错误,数组不支持void类型。所以就直接用无符号数来表示void指针,在64位系统是8字节,32位系统是4字节,使用uint类型就可以了。DONGLE_HANDLE 。

在ROCKEY-ARM的函数中也有很多带参数的接口,比如:

拿以上两个函数接口举例,Dongle_Enum中的第一个参数是一个指向DONGLE_INFO结构体的指针,运行后返回设备信息的列表,使用ROCKEY-ARM的时候需要通过枚举函数获得设备信息列表,然后比较产品ID或者硬件ID决定打开哪一个设备。为了方便从枚举函数返回的设备信息中方便的解析出产品ID或者硬件ID等信息,需要把DONGLE_INFO* pDongleInfo这个参数声明成一个结构体数组。Dongle_RsaGenPubPriKey()函数中有RSA_PUBLIC_KEY ,RSA_PRIVATE_KEIY 两个结构体指针参数,因为在这里一般用户并不需要解析RSA密钥中的n,d,e等分量,可以直接做作为一个字节数组,直接声明成上面的ptrByte类型即可。所以在声明如下:

调用ffi-napi声明的函数,主要是给自己定义的数据类型赋初值以及获得自定义参数的返回值。下面分别说明。

这里的int*,是让函数返回设备的数量,或者传入输入数据的长度或者传出输出数据的长度,所以只要定义一个长度为1的int数组即可,如下:

给传入的数据赋值,只要给下标为0的元素赋值即可。

这个参数是枚举函数传出枚举到设备信息的列表,枚举到多少设备,就传出多少个DONGLE_INFO,所以需要传入足够数量的的DONGLE_INFO,如下:

这个参数一般是作为传入传出数据的缓冲区的,所以创建数组的时候,需要创建足够长的空间,如下:

开发的过程中,踩到一些坑耽误了不少时间,这里总结一下。

ROCKEY-ARM的结构体是按字节对齐的,ref-struct-napi没有找到设置字节对齐的方法。当时声明的结构体如下:

测试的时候会发现定义的结构体和ROCKEY-ARM定义的结构体对齐方式不一样,于是把m_Birthday和m_HID两个成员从ref.types.uint64,拆分成左右两个uint32,这样就可以让结构体对齐方式和ROCKEY-ARM的一致。使用m_Birthday和m_HID的时候,需要讲左右两个uint32拼接一些,稍微麻烦一点,但是在没找到配置StructType对齐方的情况,保证结果正确,还是可以接受的。

Node.js模块封装及使用

 Node.js中也有一些功能的封装,类似C#的类库,封装成模块这样方便使用,安装之后用require()就能引入调用.

一、Node.js模块封装

 1.创建一个名为censorify的文件夹

 2.在censorify下创建3个文件censortext.js、package.json、README.md文件

    1)、在censortext.js下输入一个过滤特定单词并用星号代替的函数。

var censoredWorlds=["sad","bad","mad"];
var custormCensoredWords=[];
function censor(inStr)
{
	for(idx in censoredWorlds)
	{
		inStr=inStr.replace(censoredWorlds[idx],"****");
	}
	for(idx in custormCensoredWords)
	{
		inStr=inStr.replace(custormCensoredWords[idx],"****");
	}
	return inStr;
}

function addCensoreWorld(world)
{
	custormCensoredWords.push(world);
}

function getCensoreWorlds()
{
	return censoredWorlds.concat(custormCensoredWords);
}
exports.censor=censor;
exports.addCensoreWorld=addCensoreWorld;
exports.getCensoreWorlds=getCensoreWorlds;

  2)、在package中配置清单信息 例如版本 名称和main指令等。

{
 "author":"cuiyanwei",
 "name":"censority",
 "version":"0.1.1",
 "description":"Censors words out of text",
 "main":"censortext",
 "dependencies":{
    "express":"latest"
 },
 "enginee":{
    "node":"*"
 }
}

  3)、创建的README.md文件主要是描述说明

   3.使用命令行创建封装模块

使用命令行导航到censorify文件夹下,然后使用命令 npm pack 封装生成tgz文件,这样就封装了一个模块。

技术分享

二、封装模块的使用

封装模块的使用有两种方法 :发布到NPM注册表、本地使用,这里只记录下本地使用的方法.

1.创建名readwords文件夹

2.命令行导航到readwords文件夹下,然后安装已经封装好的模块,如果是已经发布到NPM注册表的直接 npm install 名字,如果是在本地 npm install tgz文件路径。

技术分享

3.安装完成后会在readwords文件夹下生成包含censority子文件夹的node_modules文件夹

技术分享

4.新建readwords.js文件测试(注意代码console、封装模块的函数别写错了)

var censor=require("censority");
console.log(censor.getCensoreWorlds());
console.log(censor.censor("Some very sad,bad and mad text"));
censor.addCensoreWorld("gloomy");
console.log(censor.getCensoreWorlds());
console.log(censor.censor("A very goolmy day."));

5.使用

用命令行node readwords.js来调用readwords.js查看结果

技术分享

以上是关于Node.js使用ffi-napi,ref-napi,ref-array-napi,ref-struct-napi调用动态库的主要内容,如果未能解决你的问题,请参考以下文章

electron调用dll文件

electron调用dll文件

仅使用 Node.js 与将 Node.js 与 Apache/Nginx 一起使用

Node.js详解整理:node.js的优势特点优缺点及适用场景,安装及基本使用

node.js学习使用node.js定时发送邮件任务

“使用 Node.js 直接响应” - 使用不同的 Node.js 进程(不同于主进程)发送 HTTP 响应