如何 Electron 中调用 Dll
客户端有些硬件的接口需要调试,是在电脑上连了一些硬件的设备,比如打印机、扫描仪或者进行串口通信等等。单靠 JS 是完成不了了,我们决定通过把 C ++ 或者 C# 把这些功能打包成 Dll,然后在 Electron 客户端中通过 Node 调用 Dll 来实现所需要的功能。
Dll 类型
先简单说一下什么是 Dll,Dll 是动态链接库文件,也是一种代码库的形式,与静态链接库相比,它是在每次程序运行的时候去调用,而静态链接库指令都会被打包到最后的 exe 文件里,所以如果函数有什么变化那就需要重新生成 exe,那动态链接库就不需要这么做了。生成 Dll 可以通过 VS 来完成,可以选择使用 C#或者 C ++ 开发,C#开发界面的比较方便,如果你的功能需要弹出一些界面,那就要用 C#编写相应的 Dll。不过这里要注意了,用 C#语言编写生成的 Dll 和用 C ++ 语言编写生成的 Dll 是不一样的,通过 C# 生成的 Dll 需要.net 的开发环境,而 C ++ 生成的 Dll 就没有限制。
Node 如何调用 Dll
Electron 里调用 Dll 其实就是 node 调用 Dll,刚才说了,生成的 Dll 不一样,那么调用方式也不一样。我是用到了这两个模块,ffi 和 edge,使用 ffi 调用 C ++ 生成的 Dll,使用 edge 调用 C# 生成的 Dll。
ffi 调用 Dll
比如我这里有个 ffiTest.dll 的文件,里面有个导出的函数叫做 joinStr,就是暴露的方法,给定两个字符串,然后会返回这两个参数的拼接结果。注意 C ++ 生成的 Dll 要使用 C 风格 extern“C”
否则可能找不到对应的方法名。
var ffi = require('ffi');
var path = require('path');
var dllPath = path.resolve('ffiTest.dll');
var lib = ffi.Library(dllPath, {'joinStr': ['string', ['string', 'string']],
})
var result = lib.joinStr('hello', 'world');
console.log(result); // 打印 helloworld
更详细的示例可以参考它的教程。ffi.Library
里第二个参数是一个 Json 结构,key 表示是方法名,value 示一个数组,数组的第一个参数是返回值类型,第二个参数是方法的列表,如果返回值是空的话,那数组第一个参数应该是 void。如果返回值或者参数类型不知道是什么类型就写void*
。要使用 ffi 中的类型表示 C /C++ 语言中的类型,对照表如下
基本类型
int8 Signed 8-bit Integer
uint8 Unsigned 8-bit Integer
int16 Signed 16-bit Integer
uint16 Unsigned 16-bit Integer
int32 Signed 32-bit Integer
uint32 Unsigned 32-bit Integer
int64 Signed 64-bit Integer
uint64 Unsigned 64-bit Integer
float Single Precision Floating Point Number (float)
double Double Precision Floating Point Number (double)
pointer Pointer Type
string Null-Terminated String (char *)
常见的 C 语言类型
byte unsigned char
char char
uchar unsigned char
short short
ushort unsigned short
int int
uint unsigned int
long long
ulong unsigned long
longlong long
ulonglong unsigned long long
size_t platform-dependent, usually pointer size
如果是指针类型,可以利用 ref
模块来表示
var ref = require('ref');
var refArray = require('ref-array');
var intPtr = ref.refType('int'); //int* 类型
var charPtr = 'hello'; //char* 可以用 string 表示
// 如果是个字符数组
var refArray = require('ref-array');
var charPtrPtr = refArray(ref.types.char, 50); //50 个大小的数组
假如参数或者返回值是一个结构体,那就需要借助 ref-struct
模块来表示
var ref = require('ref');
var FFI = require('ffi');
var Struct = require('ref-struct');
var TimeVal = Struct({
'tv_sec': 'long',
'tv_usec': 'long'
});
var TimeValPtr = ref.refType(TimeVal);
var lib = new FFI.Library(null, {'gettimeofday': ['int', [TimeValPtr, 'pointer']]
});
var tv = new TimeVal();
lib.gettimeofday(tv.ref(), null);
console.log("Seconds since epoch:" + tv.tv_sec);
edge 调用 Dll
edge
这个模块非常强大,不仅可以在 node 中编写 C#的代码也可以在 C#中调用 node 的代码,它要求有一个.net4.5 或者更高版本的环境。C# 编写的 Dll 要通过 async 修饰后才能被 node 调用,大致像是这样
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestDll
{
public class StartUp
{public async Task<object> Invoke(object param)
{return "Hello World!";}
}
}
这样会生成一个 TestDll.dll 的文件,在 node 中
var edge = require('edge');
var path = require('path');
var driver = edge.func({assemblyFile: path.resolve('TestDll.dll'),
typeName: 'TestDll.StartUp',
methodName: 'Invoke'
})
// 还可以这么写,var driver = edge.func(path.resolve('TestDll.dll'))
// 这么写默认方法名就是 Invoke,C# 中 class 的名字就是 StartUp。如果不一致的话调用就会报错
driver(null, function(err,result) {if (err) {throw err;} else {console.log(result);
}
});
利用 edge 其实可以在 js 直接编写 C#的代码,那完全不用多个步骤还要去生成 Dll 了,但是这个项目里还依赖了别的 Dll,这个语法还是有点懵,搞清楚后再试试直接写 C# 代码试试。
遇到的问题
过程总是那么地不顺利,即便知道了语法怎么写也会出现一些问题,总结了下大概是以下几种
- win32 error 126 Dll 文件的路径写错了,或者 Dll 有相关的依赖,依赖没有放在与入口 Dll 在同一级目录下
- win32 error 127 ffi 定义的函数名、返回值类型或者参数类型与 Dll 定义的不一致
- win32 error 193 Dll 与当前的操作系统不匹配,当前系统是 64 位的 Dll 是 32 位的
-
在 Electron 的项目使用 edge 无法编译 edge 是一个原生的模块需要用你当前安装 node 的版本重新编译,重新编译需要使用
node-gyp
,按下面几步执行即可- npm install -g node-gyp
- 安装 Python2.7 和 Visual Studio Build Tools 或者 VS2017
- npm config set msvs_version 2017
- node-gyp –python 你当前 Python 安装的路径
- cd node_modules/edge
- node-gyp configure
- node-gyp build
- 如果觉得麻烦可以直接使用 electron-edge-js 就不用自己重新编译了,如果还是不行,就再装一下 electron-rebuild 然后执行
.\node_modules\.bin\electron-rebuild.cmd