关于在electron中调用C++动态库的经验总结
前言
electron以nodejs作为底层运行环境,所以自然而然就想到了他能否调用C++编写的动态库,恰好我最近在做一个关于使用electron调用dll的项目,也就花了一点时间去了解和实践,这期间走了不少弯路,这里分享出来,希望能帮助到有遇到相关问题的小伙伴
很多刚入门不久的小伙伴第一个问题可能就是electron能不能调用DLL动态库?这里给一个明确的答复是可以的。为什么?因为electron本身就集成了nodejs运行环境,而nodejs又是用C++实现的,相当于C++是他的原配。
既然能调用,那么第二个问题来了,怎么调用?nodejs在官网也给出了方案addon有兴趣的可以看看,但是对于我们前端来说这个方案太过于复杂了,学习成本太高,如果没有c++开发背景还是另辟蹊径吧,electron是可以做到和C++混合开发的,如果团队中有C++的开发人员也可以尝试,结合C++本身的优势,开发出来的产品效果可能会更好,我们常用的IDE工具VSCode其实有部分功能也是使用C++来时实现的,这其中规避了一些electron的缺点。另一种调用DLL动态库的方案就是这里要重点介绍的模块node-ffi。
其实这个模块时用起来非常的简单,先贴一段官方使用案例,简单加了几句注释:
var ffi = require('ffi');//引入ffi模块/* *使用ffi模块将dll和js打通,可以把它看做是RPC(远程调用协议) *@libm 动态库的绝对地址例如"C://plugin/test.dll" *@ceil 动态库中方法的名称 double返回值的数据类型 ['double'] 这是函数输入参数的数据类型 *这里提一下,应为C++是是属于强类型语言这个js不同,所以这里一定要指定返回参数和输入参数的类型*/var libm = ffi.Library('libm', { 'ceil': [ 'double', [ 'double' ] ]});//通过上面的注册的libm对象来调用dll中的ceil方法libm.ceil(1.5); // 2
这一切看起来就是这么简单,但是在这几句简单的代码后面,会让刚入门的小伙伴非常的蛋疼,因为在使用的过程中你会遇到各种问题,而且这些问题会让你对本身的目的产生动摇,目前公开的环境能够支持nodejs调用的dll的npm包好像就只有ffi,而在使用中出现的问题,貌似你尝试了所有解决问题的方案,最终还是解决不了。我自己在搞这个的时候也弄了大概一周时间,各种环境捣腾,各种测试。最后弄出来了,这里总结一下这个过程中遇见的问题,打开分为这么3点:
- 安装时报错
- 安装成功但是在运行electron时调用ffi时报错
- 打包electron时报错
安装过程中报错
//全局安装原生模块编译模块,编译ffi模块时需要用到的,这里一定要安装npm install node-gyp -g//安装ffi模块npm install ffi --save
 这时安装ffi会提示Python没有安装,需要安装python,我安装的是Python2.7.13。完成之后当我兴高采烈的再去安装ffi模块,结果失望的发现下一个错误来了:
错误非常明显,就是C++的构建环境没有,后面也告诉了我们要怎么来解决:
1). 就是安装.NET Framework 2.0 。一般我们使用的windows系统本本身会自动带上.NET Framework的,只是版本可能会有所区别而已,我们可以去看看C:\Windows\Microsoft.NET\Framework目录下看看到底有没安装,安装了什么版本 ,一般来说这里不需要重新再去安装的,所以这里的第一条建议就可以忽略了。
2). 第二条建议就是安装Microsoft Visual Stu,熟悉C++开发的人可能对它很熟悉,对前端来所就是比较陌生了,前端可以把他理解C++的编译环境,这里面需要下载很多C++的组件库,在node-gyp编译的时候会使用到,就好像nodejs一样,如果没有装nodejs环境,在使用npm进行安装的时候,及会提示命令找不到等等。咋一看,路子好像挺对的,因为node-gyp会重新编译node-ffi模块,所以使用到C++的环境很正常,也是因为这样我就吭呲吭吃去安装了,因为Visual Stu很大,有好几个G,像我这种家里只装了10M带宽人来说,你懂的。晚上睡觉的时候开始下载,第二天一早起来开始安装,安装好了,又开始安装ffi模块,心里那种期待的感觉,只有程序员能理解。结果没什么意外还是失败。
经过各种尝试发现,其实我门除了安装VS外其实还可以通过npm 安装c++在windows环境下的构建工具windows-build-tools,好了不多说了直接开始干
npm install --global --production windows-build-tools
这里可能需要等待的时间比较久一点,有点耐心,好东西都是值得等待的。
安装完成后再来安装ffi模块,结果还是让人失望,不信你看,是不是和你遇见的一样:
通过日志可以看出是在node-gyp rebuild的时候出错了,这里也耽搁了大量的时间,因为对C++的编译环境不熟,我一直认为是我C++的本地编译环境不对,最后各种论坛翻遍后得出的结论是这个ffi模块有问题,其实在报错信息中也说的比较明白了
“ForceSet”: 不是“v8::Object”的成员
因为不明白什么意思,就一直在哪里坑次坑次的瞎忙活,其实有大牛早就发现这个问题,并在ffi的master上开了一个分支来解决这个问题,我们安装这个分支就好了
npm install ffi@gavignus/node-ffi#torycl/forceset-fix --save
peng一碰冷水泼来
再一次安装失败,不过这个问题简单,就是git没装啦,我们装一个git就行了
终于成功了,天啊,终于安装成功了,怎么安装一个npm模块费了这么大的劲。好了不管了,能安装成功就谢天谢地了。写段测试代码测试一下,看看使用node-ffi到底能不能调用DLL
const ffi = require("ffi");const User32 = ffi.Library('user32', { 'GetWindowLongPtrW': ['int', ['int', 'int']], 'SetWindowLongPtrW': ['int', ['int', 'int', 'long']], 'GetSystemMenu': ['int', ['int', 'bool']], 'DestroyWindow': ['bool', ['int']] })console.log(User32.GetWindowLongPtrW);
测试结果
E:\code workplace\test>node app.js{ [Function: proxy] async: [Function] }
确实调用到了。走到这里心里的大石头好像可以放下来了,毕竟解决了能不调用的问题。但是这仅仅是完成了一半。因为这只是在nodejs成功调用的dll,还没有在electron中调用,事实证明两者还是有一定区别的,下面就来讲讲在electron调用时会出现什么问题。
在electron中使用ffi模块时报错
首先说一下ffi.Library加载的dll路径问题,上面使用到的user32,部分人可能开始犯迷糊了,前面不是说这里应该是dll文件的地址,这个user32哪里冒出来的,其实这是系统的dll文件,也就是说如果我们不写成路径的形式,ffi模块就会自动去系统文件夹中寻找这个文件,有明确的路径时才会加载该路径下的dll,可以在C:\Windows\System32和C:\Windows\SysWOW64两个路径下发现这两个文件,这两个文件夹是分别放的是32位和64位dll。那到底是使用的那个文件夹下dll那?这里其实就出现了另外一个我们在开发是需要注意的问题了,因为我们的应用最终在那个环境上跑这个是不确定的,所以ffi模块加载的dll在编译的时候需要编译32位和64位两个版本的,我们需要在程序中判断系统的类型:
//x64代表64位,x86代表32位const type = process.version.arch;
将不同的编译环境编译的dll放在不同的目录下,然后根据系统类型去加载不同文件夹下的dll。如果加载错误的dll也是会报错的,最开始我在这上面是吃过亏的。
正确加载dll后我们将ffi模块用在electron项目中
//dll.jsconst remote = require("electron").remote;const ffi = remote.require("ffi");const options = { User32:(()=>{ return ffi.Library('user32', { 'GetWindowLongPtrW': ['int', ['int', 'int']], 'SetWindowLongPtrW': ['int', ['int', 'int', 'long']], 'GetSystemMenu': ['int', ['int', 'bool']], 'DestroyWindow': ['bool', ['int']] }) })()};export default options;
import Dll from "@/static/js/dll.js";console.log("Dll:",Dll.User32.GetSystemMenu)
执行
npm run dev
我们会发现应用打开后一片空白并不是我们希望的那样,打开F12调试窗口我们会发现一堆红色报错
大致的意思就是ffi加载失败了。纳尼,前面已经成功安装了ffi模块,并且测试了一下调用dll没毛病啊,咋又作妖了呢?事实上electron在使用c++模时还需要根据electron的版本等信息重新编译一下,这样在electron中才能执行,我们需要进入ffi模块执行重新编译的命令,并注入参数
node-gyp rebuild -target=1.8.7 -arch=x64 -dist-url=https://npm.taobao.org/mirrors/atom/
target:electron的版本号
arch : 计算机的架构
dist-url :文件的下载地址,编译的时候回去这个地址上下载一些额外的文件,具体作用我不是很清楚。这里使用的是国内镜像,不是官方给出的地址,至于为什么,太慢了。然后再来执行启动命令
npm run dev
然后,没有然后了,应用跑起来了:
这样就完了吗?事实证明并没有,要正在把应用打包出来,才能算结束了。下面说一下在打包的过程中他又可能出现的问题,
打包后运行electron,调用dll可能出现的问题
执行
//构建npm run build
完成打包后,安装应用,然后启动,发现还是出现了前面那种页面白屏,ffi模块调用失败的问题,原因是我们在执行npm run build的时候,是通过electron-rebuild 来build的,这时会再次执行node-gyp rebuild,这里没有添加任何参数,所以打包出来的应用可能还是会失败,我们可以参照electron官方的方案来解决使用node原生模块,最直接的方式就是在项目根目录下添加一个npm安装配置文件.npmrc,里面包含了一下npm的运行需要注入的参数。
.npmrc:
# Electron 的版本。set npm config --target=1.2.3# Electron 的系统架构, 值为 ia32 或者 x64。set npm config --arch=x64# 下载 Electron 的 headers。eset npm config --disturl=https://npm.taobao.org/mirrors/atom/# 告诉 node-pre-gyp 我们是在为 Electron 生成模块。set npm config --runtime=electron
通过上面的一系列填坑,差不多可以成功的再electron项目中使用DLL动态库了,并且打包出来的应用调用也没有问题了。
上面可能讲的有点杂,下面稍微捋一下整个的解决流程流程:
- 切换npm官方镜像到国内镜像
npm config set registry=https://registry.npm.taobao.org
- 安装Python,配置Python的系统环境变量
- 安装C++构建环境,这里有两种方案
1. 安装Visual Studio
2. 通过npm的方式安装windows环境的的C++构建工具包
npm install --global --production windows-build-tools
我推荐使用第二种方式,第一种方式,到目前为止,我也没有完全跑起来,总是再各个环节会出现不同的问题。 - 安装forceset问题已修复的ffi模块分支。
npm install ffi@gavignus/node-ffi#torycl/forceset-fix --save
顺便提一下,安装的的使用一定要加–save,不能是–save-dev或不加,因为在build的时候,会将package.json里dependencies下的所有文件都打包到asar文件中去,否则在应用中调用ffi模块的时候也会出现模块找不到的问题。
- 进入下载好的ffi模块中去重新编译一下ffi模块,到此你就可以运行开发环境了
node-gyp rebuild -target=1.8.7 -arch=x64 -dist-url=https://npm.taobao.org/mirrors/atom/
- 再项目根目录下创建npm运行环境配置参数文件.npmrc
# Electron 的版本。set npm config --target=1.2.3# Electron 的系统架构, 值为 ia32 或者 x64。set npm config --arch=x64# 下载 Electron 的 headers。eset npm config --disturl=https://npm.taobao.org/mirrors/atom/# 告诉 node-pre-gyp 我们是在为 Electron 生成模块。set npm config --runtime=electron
通过上面这一系列的操作。基本上也就可以成功调用DLL动态库了。据说这种方式比起官方给出的方式性能上有些损失,具体损失到什么程度,我这边还没有去测过,也没有明显感觉出来。这种方式的优势就是使用简单,开发成本低,而且一般的需求也是能够满足的,如果团队有足够的资源,也可以去尝试nodejs官方给出的方式,到时候教教我。
总结:
上面写了很多,其实干货感觉好像就是上面最后这几条,写前面的过程主要是记录一下我在解决这个问题是遇到的各个问题,然后怎么一步一步去寻找解决方案的,很多小伙伴在使用的过程中可能也会遇见相同的问题,关键在于我们遇见问题后不要轻易去下这个问题能不能解决的结论,多尝试几种方式,论坛,度娘,谷歌上面多查查,即使查出来的这条信息最终可能并没什么用,但是多尝试几次,自己大概也就知道这个问题出现的原因,可能也会形成相关解决的思路,也许还会有意外的收获。
最后总结一句:多尝试,多动手,多思考。
忽略"通假字"…