Electron调用dll的新姿势
之前旧的系统在浏览器中调用dll都是使用IE的activex控件实现。进而通过dll脚本和硬件发生交互。现在IE浏览器已经不在默认预制在系统中,且对windows的操作系统有限制,win10之前的版本才能正常访问。
在不断的业务迭代过程中,已经成了制约系统扩展的最大阻碍。调研后选择了electron-egg框架来进行业务功能尝试,主要是dll的嵌入调用和设备交互。
ElectronEgg

作为一个对前端不是那么擅长的后端来说,electron-egg已经包装了大部分操作,且拥有非常详尽的中文开发文档。可以无缝切换,低成本代码的开发。
框架设计

具体的业务逻辑还是放在后台服务中,electron只负责透传交互指令和硬件设备进行交互。这里调用dll用的js库是koffi。
Koffi
Koffi 是一个快速且易于使用的 Node.js C FFI 模块。实现了在Node中调用dll的功能。
koffi版本2.8.0
DLL配置
按照官方文档dll文件放置在extraSources文件中。
DLL加载
const lib = koffi.load(path.join(Ps.getExtraResourcesDir(), "dll", "dcrf32.dll"));
DLL调用
dll调用有两种方式。分别为经典语法和类c原型方式。
- 经典语法
 
定义函数名,返回参数类型,入参类型constprintf=lib.func('printf','int', ['str','...']);
- 类C语法
 
在类中定义方法类似,lib.func('int printf(const char *fmt, ...)');
推荐使用类C语法更加方便,不受参数的限制,更易于修改。
DLL调用类型
- 同步调用
 
本机函数,您就可以像调用任何其他 JS 函数一样简单地调用它。
const atoi = lib.func('int atoi(const char *str)');
let value = atoi('1257');
- 异步调用
 
有一些耗时的操作,可以使用异步调用回调的方式处理。
const atoi = lib.func('int atoi(const char *str)');
atoi.async('1257', (err, res) => {
console.log('Result:', res);
})
JS类型值传递
JS基础类型时不支持值传递的,遇到需要传递指针变量时,直接调用是无法获取到变更后的值。相应的koffi也提供了非常方便的值包装。
- 数组包装
 
项目中采用比较方便的数组包装来进行值传递。包装基础对象到数组中,变更后取出第一位就能获取到变更后的值。

需要定义返回的值的获取长度,防止出现只获取到部分的返回结果。
- 引用类型包装
 
把基础类型包装成引用对象。传递到函数中。
let cardsenr = koffi.alloc('int', 64);
let cardRet = dcCard(icdev, 0, cardsenr);
这种就更方便,调用后也不需要转换。在调用完后需要通过free方法进行内存释放。
- Buffer对象
 
如果遇到接收中文数据时,koffi可以结合Node中的Buffer进行对象传递。
let text = Buffer.alloc(1024);
let ret = read(text);
部分dll默认读出来的编码是gbk格式,需要将buffer对象转换成utf8格式的字符串进行展示。 就需要通过iconv组件进行gbk解码。
iconv.decode(text, 'gbk')
如果需要把utf8转成gbk,使用相反的方式就可以
iconv.encode(build/photos/${id_number}.bmp, "gbk")
结构体调用
JS中只有引用对象,如果遇到结构体参数需要进行JS包装。
// C
typedef struct A {
int a;
char b;
const char *c;
struct {
double d1;
double d2;
    } d;
} A;
// JS
const A = koffi.struct('A', {
    a: 'int',
    b: 'char',
    c: 'const char *', // Koffi does not care about const, it is ignored
    d: koffi.struct({
        d1: 'double',
        d2: 'double'
    })
});
如果调用出现对齐不对的情况,可以使用pack方法进行手动对齐类型。
// This struct is 3 bytes long
const PackedStruct = koffi.pack('PackedStruct', {
    a: 'int8_t',
    b: 'int16_t'
});
常规dll的调用都可以轻易的在JS中实现。
Node后端
底层调用已经通过koffi来实现。后面就需要借助electron-egg框架能力进行业务代码指令透传。
service层
'use strict';
const { Service } = require('ee-core');
/**
 * 示例服务(service层为单例)
 * @class
 */
class ExampleService extends Service {
constructor(ctx) {
super(ctx);
  }
/**
   * test
   */
async test(args) {
let obj = {
      status:'ok',
      params: args
    }
return obj;
  }
}
ExampleService.toString = () => '[class ExampleService]';
module.exports = ExampleService;
定义我们需要交互的方法
controller层
'use strict';
const { Controller } = require('ee-core');
const Log = require('ee-core/log');
const Services = require('ee-core/services');
/**
 * example
 * @class
 */
class ExampleController extends Controller {
constructor(ctx) {
super(ctx);
  }
/**
   * 所有方法接收两个参数
   * @param args 前端传的参数
   * @param event - ipc通信时才有值。详情见:控制器文档
   */
/**
   * test
   */
async test () {
const result = await Services.get('example').test('electron');
Log.info('service result:', result);
return 'hello electron-egg';
  }
}
ExampleController.toString = () => '[class ExampleController]';
module.exports = ExampleController;
JS前端
通过ipc的方式,乡调用api一样调用node后端接口。
定义路由
import { ipc } from '@/utils/ipcRenderer';
const ipcApiRoute = {
test: 'controller.d8.test',
  init: 'controller.d8.init',
  reset: 'controller.d8.reset',
exit: 'controller.d8.exit',
command:'controller.d8.command'
}
调用
ipc.invoke(this.ipcApiRoute.init).then(r => {
// r为返回的数据
if(r >= 0) {
this.setInitRet(r);
this.scrollToBottom("连接成功,返回码:" + r);
this.connectStr = "连接成功";
this.setDeviceStatus('success');
  } else {
this.scrollToBottom("连接失败,返回码:" + r);
  }
})
通过ipc的invode方法调用方法即可。后续就可以愉快的编写我们的业务代码了。
参照
来源:juejin.cn/post/7352075771534868490