使用Electron构建桌面应用(一)——使用FFI模块调用DLL文件

Published: 2018-03-03

Tags: Electron

本文总阅读量

工作中接触到Electron桌面开发,摸索了几天,结合网上的教程,整理一下笔记,方便自己今后查阅也方便刚接触Electron的朋友参考

Electron是一个基于Node.js与Chromium的框架,可以使用HTML/JS/CSS,jQuery,Vue来编写页面,优势在于编写的程序可以跨平台(Windows,Linux,Mac),使用前端的脚手架的同时还能享受Node生态带来的便利

经过一周的尝试,走通了开发一个EXE安装程序的流程,加载运行DLL的小工具及生成安装包

先从环境搭建开始,国内配置环境着实耽误了我不少时间,如果手里有全局加速上网工具最好不过

所需的软件环境

  • vs2013
  • Python 2.7
  • Node.js

VS系列软件用于编译C++代码,2015,2017版本也都是可以的,Python需要是2.7.X版本(必须),Node.js 32或者64位都可以,不过这里有个蛮坑的地方,32位版本只能加载32位的DLL,64位的版本只能加载64位的DLL,这个还是挺需要注意的

小Tips: 在安装Python过程中设置添加环境变量(此处默认为不设置环境变量)

安装Electron

使用淘宝提供的cnpm

npm install -g cnpm --registry=https://registry.npm.taobao.org

然后使用cnpm安装

cnpm install -g electron

可能是我这里的网络问题,下载Electron的中途会断,报错如下

add python.exe to system path

没有报错最好,如果遇到同样的报错,可以看下面的补充或手动下载Electron放置在缓存目录

如果本机存在HTTP代理,可以使用

npm config set proxy http://server:port
npm config set registry http://registry.npmjs.org/

代理方式可能会在下载安装包的过程中自动断掉导致下载失败

设置源:
在不使用cnpm的前提下,也可以配置npm让其使用taobao源

npm config set registry https://registry.npm.taobao.org # 注册模块镜像
npm config set disturl https://npm.taobao.org/dist # node-gyp 编译依赖的 node 源码镜像
npm config set electron_mirror https://npm.taobao.org/mirrors/electron/ # electron 二进制包镜像

另外,Npm源配置会保存在C:\Users\Administrator\ .npmrc文件中,可直接编辑配置文件修改或删除设置

参考:https://segmentfault.com/a/1190000008410558

手动下载Electron并安装

针对cnpm与设置代理下载Electron都可能报错的问题,手动下载Electron并将其放在指定位置,npm安装的时候会在缓存目录查找,这样就可以把Electron安装成node模块

https://npm.taobao.org/mirrors/electron 下载相应版本及SHASUMS256.txt校验文件

将下载到的electron及校验文件放置在当前用户目录下的“.electron”文件夹中,之后使用npm或cnpm安装即可

electron tmp dir

ia32是指32位版本,x64为64位版本,这个需要根据你安装的Node.js位数决定

运行官方示例

网站:https://electronjs.org/

# 克隆示例项目的仓库
$ git clone https://github.com/electron/electron-quick-start

# 进入这个仓库
$ cd electron-quick-start

# 安装依赖并运行
$ cnpm install && cnpm start

electron-quick-start-master

接下来对demo进行一些改造,使其能够调用dll文件中函数

使用Electron调用DLL文件

Electron同样也支持Node模块,但由于和官方的Node相比使用了不同的V8引擎,如果重新编译原生模块,则需要下载Electron的headers头文件。

Node-gyp编译需要使用Node的源文件头以及Electron的源文件头,这个从npm仓库下载也会很慢,不过我们可以预先把头文件下载下来

预先下载Node.js与Electron的头文件

编译需要用到当前版本Node.js的头文件,可以通过淘宝源预先下载

node-gyp install --dist-url https://npm.taobao.org/mirrors/node

如果提示找不到node-gyp,运行cnpm install node-gyp -g进行安装

node-gyp会把它放在C:\Users\Administrator\.node-gyp

Electron的头文件也需要下载,也是存放在.node-gyp目录,你大可跳过这部分,如果之后重编译模块的时候下载Electron超时,再回过来看也不迟

node-gyp rebuild --target=1.8.2 --arch=ia32 --dist-url=https://atom.io/download/electron

如果你在rebuild的时候卡住了,那可以手动下载electron的头文件放置在用户目录下的.node-gyp 目录下,下载地址如下,你可能需要根据Electron软件的版本去修改下载路径

https://atom.io/download/electron/v1.8.2/iojs-v1.8.2.tar.gz https://atom.io/download/electron/v1.8.2/SHASUMS256.txt https://atom.io/download/electron/v1.8.2/win-x64/iojs.lib https://atom.io/download/electron/v1.8.2/win-x86/iojs.lib

node-gyp tmp dir

8.9.4文件夹对应的是Node.js的头文件 Iojs-1.8.2文件夹对应的是Electron编译所需的头文件(很奇怪为什么electron的头文件保存在iojs文件下,难道是因为Electron创建的时候是基于的分裂后的Node,也就是iojs吗?)

把下载的iojs.lib根据x86和x64保存在相应的文件夹下(如下图,文件夹手动创建),然后再次使用node-gyp重新编译,会发现自动使用缓存目录中的文件,不会再从网络下载了

node-gyp electron headers dir list

我倒是觉得,更好的方式是在开发时锁定Electron版本,打包.node-gyp文件夹及.electron文件夹分发即可,免除网络方面的困扰,并且桌面应用可能没有那么高的框架更新需求,定期进行升级依赖版本即可

使用ffi模块加载DLL

安装ffi模块

cnpm install ffi --save

在根目录下创建一个test_load_dll.js文件,我们先使用node调用ffi模块,并看一下输出

PcInfo.dll 是我随手找到的一个DLL文件,用来获取公司产品标识码,就不去生成个做加减法的DLL了,PcInfo.dll下载: PcInfo.dll

新建一个DLL目录,把它放在里面

var ffi = require("ffi")  

var DLL = ffi.Library('dll/PcInfo.dll', {  
    'GetHylinkDeviceId' : ['string', []]  
});  

var result = DLL.GetHylinkDeviceId();
console.log(result)

这个代码不用多解释,声明了DLL有一个GetHylinkDeviceId函数,不传递参数,返回string类型

在cmd下运行node test_load_dll.js,输出如下:

test load dll

别慌,输出ERROR代表调用DLL成功了,因为在非指定设备上,比如个人PC电脑,输出就是ERROR

接下来在这个DEMO中引入DLL,先简单看一下目录结构

electron-quick-start-master

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    <!-- All of the Node.js APIs are available in this renderer process. -->
    We are using Node.js <script>document.write(process.versions.node)</script>,
    Chromium <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.

    <script>
      // You can also require other files to run in this process
      require('./renderer.js')
    </script>
  </body>
</html>

默认的index.html文件,之前运行demo的页面内容就是在这里定义的,下边是修改后引入DLL的index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
    <script>
      var ffi = require("ffi")  

      var DLL = ffi.Library('dll/PcInfo.dll', {  
          'GetHylinkDeviceId' : ['string', []]  
      });  

      var result = DLL.GetHylinkDeviceId();  
    </script>
  </head>
  <body>
    <h1>Hello World!</h1>
    <!-- All of the Node.js APIs are available in this renderer process. -->
    We are using Node.js <script>document.write(process.versions.node)</script>,
    Chromium <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
    <br />
    <br />
    Load PcInfo.dll <script>document.write("GetHylinkDeviceId: "+ result)</script>

    <script>
      // You can also require other files to run in this process
      require('./renderer.js')
    </script>
  </body>
</html>

如果我们直接运行npm start,看看会发生什么(使用Ctrl + Shift + I 打开调试页面)

err when electron use node ffi module

报错信息:

E:\code\electron-project\electron-quick-start-master\node_modules\_bindings@1.3.0@bindings\bindings.js:96 Uncaught Error: Could not locate the bindings file. Tried:
 → E:\code\electron-project\electron-quick-start-master\node_modules\_ref@1.3.5@ref\build\binding.node
 → E:\code\electron-project\electron-quick-start-master\node_modules\_ref@1.3.5@ref\build\Debug\binding.node
 → E:\code\electron-project\electron-quick-start-master\node_modules\_ref@1.3.5@ref\build\Release\binding.node
 → E:\code\electron-project\electron-quick-start-master\node_modules\_ref@1.3.5@ref\out\Debug\binding.node
 → E:\code\electron-project\electron-quick-start-master\node_modules\_ref@1.3.5@ref\Debug\binding.node
 → E:\code\electron-project\electron-quick-start-master\node_modules\_ref@1.3.5@ref\out\Release\binding.node
 → E:\code\electron-project\electron-quick-start-master\node_modules\_ref@1.3.5@ref\Release\binding.node
 → E:\code\electron-project\electron-quick-start-master\node_modules\_ref@1.3.5@ref\build\default\binding.node
 → E:\code\electron-project\electron-quick-start-master\node_modules\_ref@1.3.5@ref\compiled\8.2.1\win32\ia32\binding.node
    at bindings (E:\code\electron-project\electron-quick-start-master\node_modules\_bindings@1.3.0@bindings\bindings.js:93:9)
    at Object.<anonymous> (E:\code\electron-project\electron-quick-start-master\node_modules\_ref@1.3.5@ref\lib\ref.js:5:47)
    at Object.<anonymous> (E:\code\electron-project\electron-quick-start-master\node_modules\_ref@1.3.5@ref\lib\ref.js:1465:3)
    at Module._compile (module.js:569:30)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:503:32)
    at tryModuleLoad (module.js:466:12)
    at Function.Module._load (module.js:458:3)
    at Module.require (module.js:513:17)
    at require (internal/module.js:11:18)

基本上Uncaught Error: Could not locate the bindings file. 就说明是模块未编译的原因了,接下来重新编译ffi模块,以便electron使用

重编译原生Node模块

此处需要重新编译的有ffi和ref两个模块,命令如下(ref模块不需要手动安装,在安装ffi模块的时候就会自动安装ref)

cd node_modules\ffi
node-gyp rebuild --target=1.8.2 --arch=ia32 --dist-url=https://atom.io/download/electron
cd node_modules\ref
node-gyp rebuild --target=1.8.2 --arch=ia32 --dist-url=https://atom.io/download/electron

简单解释: target指明electron版本,arch表明想要重新编译成多少位的,ia32位x86平台,x64位64位平台,DLL位数,模块位数,Node.js位数应保持一致,否则会报错,dist-url指定了去哪里下载electron的头文件,可能这里你下载的很缓慢,去前边看看手动下载electron头文件的方法

electron ffi load dll

显示ERROR,证明DLL已经调用成功

Uncaught Error: Dynamic Linking Error: Win32 error 126 报错为DLL未找到,遇到这个提示看看dll放的位置和html文件中引用的位置是不是不一致

接下来,或许你有闲暇时间可以试试运行这个demo:https://github.com/wzdxy/electron-ffi-demo

有了前边的基础,可以试着运行这个demo,我运行这个demo的时候遇到了一些小问题,可以尝试解决

1,提供的DLL是64位的,可以按照说明给出的代码编译一个32位的使用 2,package.json中指定的版本好都是1.7.11,如果安装依赖会安装1.8.2版本的electron,npm run rebuild-ffi 运行的命令中指定的1.7.11不匹配,所以应该修改package.json凡出现1.7.11的地方设置为当前electron版本(如:1.8.2)

electron-ffi-demo

有一个比较坑的地方是electron网上查询了解到只能使用C标准DLL而不能使用C++ DLL,我没进行测试,大家可以试试,另外Node.js 32位的版本只能引用x86的DLL,64位置能引用x64位的DLL,所以如果需要引入很多的三方DLL,也不是那么很方便,先这样,也许以后会有改善

配置electron与加载DLL的内容差不多就是这样,涉及到DLL的,以后会跟进并补充,下一篇记录一下打包electron应用,使其可以成为一个单独运行的exe,或者是一个可安装程序,先这样