Electron-利用DLL实现不可能_weixin_33872566的博客-程序员资料

技术标签: 网络  操作系统  c/c++  

为什么

如果我们的应用想要实现这样一个需求:监听电脑的usb接口,当有新的设备(移动硬盘或者U盘)接入电脑时,能够获取里面的移动设备的情况并更新到应用程序的界面上。

按照 Electron 或者 Node.js 现成的接口,我们无法直接实现。

这时候,我们就可以根据我们自己的情况,对系统的底层接口封装成独立的动态链接库(.dll),然后把动态链接库暴露给Electron 或者 Node.js 进行调用,从而实现需求。

简介

Node.js 可以通过对接 .dll,调用系统底层接口从而实现原有接口不提供的功能。同理,Electron 是基于Node.jss 进行封装的,Electron 也就拥有了对接动态链接库,能够把不可能变为可能。

方案选择

截至本文完成时间(2018-12-17)

现有的主流的方案一共有两项

  1. 自定义C++ Addons

  2. node-ffi 对接方案

(暂不讨论 .net core 等对接方式)

原理分析

首先,明确一个观点,Electron 是基于 Node.js 封装的框架,Electron 对接 动态链接库 的底层方案,是基Node.js c++ Addons机制

从 Node.js 官网上可以了解到,Addons机制涉及多个组件和 API 的知识:

  • V8: Node.js 目前用于提供 JavaScript 实现的 C++ 库。 V8 提供了用于创建对象、调用函数等的机制。 V8 的 API 文档主要在v8.h头文件中(Node.js 源代码中的deps/v8/include/v8.h),也可以在查看V8 在线文档

  • libuv: 实现了 Node.js 的事件循环、工作线程、以及平台所有的的异步操作的 C 库。 它也是一个跨平台的抽象库,使所有主流操作系统中可以像 POSIX 一样访问常用的系统任务,比如与文件系统、socket、定时器、以及系统事件的交互。 libuv 还提供了一个类似 POSIX 多线程的线程抽象,可被用于强化更复杂的需要超越标准事件循环的异步插件。 建议插件开发者多思考如何通过在 libuv 的非阻塞系统操作、工作线程、或自定义的 libuv 线程中降低工作负载来避免在 I/O 或其他时间密集型任务中阻塞事件循环。

  • 内置的 Node.js 库:Node.js 自身开放了一些插件可以使用的 C++ API。 其中最重要的是node::ObjectWrap类。

  • Node.js 包含一些其他的静态链接库:如 OpenSSL,这些库位于 Node.js 源代码中的deps/目录。 只有 V8 和 OpenSSL 符号是被 Node.js 开放的,并且通过插件被用于不同的场景。 更多信息可查看链接到 Node.js 自身的依赖

总结来说,Node.js 可以理解为 js 和 c++ dll 相互工作的桥梁,而 Node.js 自身也提供了扩展 c++ dll 调用的插件机制。

同理,Electron 也可以从这个机制中获益。

目前,主流的 .dll 调用方案的关系图如下所示:

解释如下:

  • node-ffi 本质上是 Addons 机制下,进行过抽象封装的方案

  • addons插件需要针对对应版本的Node.js编译后,才能被对应版本的Node.js进行调用;换言之,如果addons插件在编译时的目标版本是 Node.js v8.3.1,那么它编译后的代码就不能被 Node.js v6.0.0的版本进行调用

  • node-gyp 可以帮助开发者脱离当前全局安装的Node.js版本,指定任意 Node.js 版本进行模块的编译,在编译前,需要下载对应版本的原生模块头文件,头文件的默认下载地址为 nodejs.org

  • Electron想要调用 .dll 文件,也需要进行对 addons 插件的编译,编译用的头文件也需要额外下载,和Node.js不同,Electron对应的头文件的默认下载地址为 atom.io/downloaded

  • 头文件的版本必须与 调用者 ( Node.js 或者 Electron )版本一致,这样 addons插件(包括 自定义 addon 和 node-ffi)才能正确运行

执行情况:

Node.js的原生模块编译,通过 node-gyp 可以比较方便地进行编译

Electron的原生模块编译,由于头文件与 Node.js 的头文件并不一致,直接用 node-gyp 进行编译的话,还需要进行一些额外的配置(头文件下载地址、版本映射等),相对没这么方便。幸好,开源社区已经准备好了一个封装好的工具 Electron-rebuild ,它底层原理也是使用 node-gyp 进行编译,不过就不需要开发者进行额外的配置了

根据 Electron 版本的不同(主要是 v4 和 以前的版本不同),需要在应用中执行额外的代码

编译环境要提前准备,三大操作系统(Windows、MacOS、Linux)各不相同,看官需要根据 node-gyp 的文档,提前调整好自己的编译环境。参考文档(截至 2018-12-17):node-gyp.readme

案例分享

万事俱备,我们把源码准备好,按照 node-gyp的教程准备好编译环境,开始操作:

本次的案例方案为 Node-FFI,想要自定义addon的看管,可以先了解Electron addon的编写后,再进行编译 和 使用

第一步:

在项目路径下,

安装好所有依赖

npm install
复制代码

安装 node-ffi、ref、ref-array:

npm install node-ffi --save
npm install ref --save
npm install ref-array --save
复制代码

全局安装好 Electron-rebuild

npm install -g Electron-rebuild
复制代码

第二步:

假设我们的Electron版本为 v3.0.11,32位应用

在项目路径下,执行 Electron-rebuild 命令,重新编译 node-ffi、ref、ref-array 原生模块:

Electron-rebuild -v 3.0.11 -a ia32
复制代码

第三步:

如无意外,编译成功后,我们就可以通过 Electron 应用调用 ffi 和 ref 模块了

var ffi = require('ffi');
var ref = require('ref');
复制代码

第四步:

使用 ref 定义好数据类型,因为 c++ 的数据类型的内存模型不可能和 js 的是一致的,使用时,需要利用 ref 库进行转换

var intPointer         = ref.refType('int');
var doublePointer     = ref.refType('double');
var charPointer      = ref.refType('char');
var stringPointer   = ref.refType(ref.types.CString);
var boolPointer        = ref.refType('bool');
复制代码

pointer,可以理解为对应 c++ 里面的指针,pointer.ref() 则是获取指针对应的数据

使用 ffi 连接 .dll 文件

var usbLib = ffi.Library(libpath, {
    'InitSDK': ['int', ['pointer']],
    'GetData': ['int', ['char', stringPointer]],
    'ClearData': ['int', [charPointer, charPointer]],
    'GetRemovableDrives': ['int', [stringPointer]]
});
复制代码

libpath 为 .dll所在的路径,相对路径与绝对路径均可,考虑到后续的安装包打包,建议为相对路径

第五步:

把连接好的dll使用起来

var data = new Buffer(1000);

var result = usbLib.GetData(driveName, data);    
var resultStr = '';

if(result === 0){
    resultStr = wideCharBufferToString(data);
}

return resultStr;
复制代码

如上述代码,js 调用 .dll中定义好的 GetData 方法

.dll中的C++源码如下:

int GetData(string driveName, char *data)
复制代码

函数调用结果,通过 data 参数返回,调用状态 通过 int 的数据格式返回到 js 的 result 变量中

js中的data,是一个 ref 生成的 StringPointer(实际上是通过Buffer扩展出的数据结构)

当函数调用结束,函数的结果也以指针的形式赋值给了data

接下来,把data这个指针指向的数据解析出来,即可获取函数的返回数据

问题记录

截至 2018-12-17

1、NodeJs v10.x 与 Electron v3.x 对应的原生模块头文件,都无法和 Node-FFI latest 版本完成编译(互相不兼容)

解决方法:

开源社区上已经有开发者提交了 Node-FFI 的 PR 并通过了测试

开发人员可以先安装 PR 版本的 Node-FFI ,实测可以正常编译与正常使用

npm install node-ffi/node-ffi#169773d
复制代码

转载于:https://juejin.im/post/5c5ee440f265da2deb6a82bb

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_33872566/article/details/91444609

智能推荐

再见,谷歌_longware_新浪博客_龙威的博客-程序员资料

谷歌不堪内地法律,已经转至香港发展,g.cn已经跳转到http://www.google.com.hk/webhp?source=g_cn内地没有谷歌,大家网照上,搜索照样用,没什么大不了。别太把自己当回事,再见,谷歌。 ...

pytorch 获取tensor维度信息_siqi_fighting的博客-程序员资料

参考https://stackoverflow.com/questions/46826218/pytorch-how-to-get-the-shape-of-a-tensor-as-a-list-of-int>>> import torch>>> from torch.autograd import Variable>>> from ...

查找二叉树的任意节点的所有父节点_二叉树任一节点的所有父子节点_梦在未名湖畔的博客-程序员资料

该问题可以利用非递归的后序遍历加以修改一点即可完成:void GetParent(BiTree Tree,char data,char Path[]){ BiTree p = (BiTree)malloc(sizeof(tree)); BiTree IsPri = (BiTree)malloc(sizeof(tree)); int PathCount = 0; IsP

java.lang.NoClassDefFoundError: javax/servlet/ServletContext_橙子cch的博客-程序员资料

@java.lang.NoClassDefFoundError: javax/servlet/ServletContextTOCjava.lang.NoClassDefFoundError: javax/servlet/ServletContext使用idea 开发项目,启动时报如下错误"D:\Program Files\Java\jdk1.8.0_211\bin\java.exe" "-javaagent:D:\Program Files\IntelliJ IDEA Community Editi

史上最全的程序员求职渠道总结_yuwang668的博客-程序员资料

我前前后后写过多篇与程序员找工作相关的文章,比如程序员跳槽神级攻略,找工作的辟邪剑谱,任性,春节前辞职,程序员该不该考虑初创公司,这些文章都收录在我的漫谈程序员专栏里,它们从跳槽时机、跳槽原因、简历优化等不同侧面讨论了程序员找工作的那些事儿,受到很多人的关注。今天呢,我准备专门分析一下程序员求职渠道,有料是必须的,就算你搜遍互联网深挖全宇宙,也会发现这篇文章将是史上最全、最强、最有针对性的程序员求

java解析过程_【JVM系列】一步步解析java执行过程_令和时代的柯南的博客-程序员资料

对于任何一门语言,要想达到精通的水平,研究它的执行原理(或者叫底层机制)不失为一种良好的方式。在本篇文章中,将重点研究java源代码的执行原理,即从程序员编写JAVA源代码,到最终形成产品,在整个过程中,都经历了什么?每一步又是怎么执行的?执行原理又是什么?.....当然,本篇文章的粒度可能稍微侧重于宏观方面,更细粒度的技术分析,需要在接下来的该系列文章中与大家分享....一 编写java源程序j...

随便推点

2.5.1 元组关系演算语言ALPHA_iteye_3748的博客-程序员资料

2.5 关系演算 关系演算是以数理逻辑中的谓词演算为基础的。按谓词变元的不同,关系演算可分为元组关系演算和域关系演算。本节先介绍元组关系演算,然后简单简绍域关系演算。...

Android-APP隐私合规检测Camille(安卓root模拟器)_camille运行没作用_Alex_z0897的博客-程序员资料

根据隐私合规的场景,辅助检查是否符合隐私合规标准.android studio模拟器(root权限)python3环境下载camille下载pc端frida(pip)手工下载安卓端frida

初识前端框架Vue.js_初识vue.js在哪写_Chasel IBM的博客-程序员资料

Vue.js作为目前最热门最具前景的前端框架之一,其提供了一种帮助我们快速构建并开发前端项目的新的思维模式。本文旨在帮助大家认识Vue.js,了解Vue.js的开发流程,并进一步理解如何通过Vue.js来构建一个中大型的前端项目,同时做好相应的部署与优化工作。文章将以PPT图片附加文字介绍的形式展开,不会涉及知识点的具体代码,点到为止。有兴趣的同学可以查看相应的文档进行了解。Vue.js简介从上图...

极速SpringCloud开发-Dalston版本_spring cloud dalston_白黑黑白的博客-程序员资料

服务的注册与发现(Eureka )在这里,我们需要用的的组件上Spring Cloud Netflix的Eureka ,eureka是一个服务注册和发现模块。Eureka介绍官方的介绍在这里Eureka wiki。Eureka是Netflix开源的一个RESTful服务,主要用于服务的注册发现。Eureka由两个组件组成:Eureka服务器和Eureka客户端。Eureka服务器用作服务注...

深度学习Deep Learning 101_with the explosion of big data——a new product_GarfieldEr007的博客-程序员资料

Deep learning has become something of a buzzword in recent years with the explosion of 'big data', 'data science', and their derivatives mentioned in the media. Justifiably, deep learning approaches h

推荐文章

热门文章

相关标签