在Electron中使用ffi-napi引入C++的dll 转

转自:https://www.cnblogs.com/silenzio/p/11606389.html

ffi

 

实现这个功能, 主要使用的插件是ffi.

node-ffi是一个用于使用纯JavaScript加载和调用动态库的Node.js插件。它可以用来在不编写任何C++代码的情况下创建与本地DLL库的绑定。同时它负责处理跨JavaScript和C的类型转换。

node-ffi连接了C代码和JS代码, 通过内存共享来完成调用, 而内部又通过ref,ref-arrayref-struct来实现类型转换.

 

安装 ffi-napi

 

ffi-napi是作者(node-ffi-napi)根据node-ffi修改而发布到npm仓库的, 可以直接通过npm安装, 支持node.js 12和electron高版本.

ffi-napi详情见: ffi-napi的github页面

node-ffi是ffi的官方版本, 但是不能用在我们的项目中, 如果你对它失败的原因感兴趣, 我写在了本文的最后一节.

 

1. 部署node.js+electron环境

按步骤完成electron教程(一): electron的安装和项目的创建所介绍的内容.

 

2. 安装ffi-napi

执行指令:

yarn add ffi-napi

 

使用ffi-napi

 

main.js中添加如下代码:

const ffi = require('ffi-napi');
/**
 * 先定义一个函数, 用来在窗口中显示字符
 * @param {String} text
 * @return {*} none
 */
function showText(text) {
  return new Buffer(text, 'ucs2').toString('binary');
};
// 通过ffi加载user32.dll
const myUser32 = new ffi.Library('user32', {
  'MessageBoxW': // 声明这个dll中的一个函数
    [
      'int32', ['int32', 'string', 'string', 'int32'], // 用json的格式罗列其返回类型和参数类型
    ],
});

// 调用user32.dll中的MessageBoxW()函数, 弹出一个对话框
const isOk = myUser32.MessageBoxW(
    0, showText('I am Node.JS!'), showText('Hello, World!'), 1
);
console.log(isOk);

这段代码中, 主要调用了windows的user32.dll, 具体的步骤都写在了代码的注释中.

启动程序:

npm start

弹窗出现, Hello World!

自己生成一个dll

 

0. 首先要明确一点的就是:

ffi只接受纯C函数, 确切的说, 是按照C标准编译的函数

下面来说说具体的原因:

在通过ffi引入dll的时候, 我们是这么声明的:

const myUser32 = new ffi.Library('user32', {
  'MessageBoxW': // 声明这个dll中的一个函数
    [
      'int32', ['int32', 'string', 'string', 'int32'], // 用json的格式罗列其返回类型和参数类型
    ],
});

user32.dll中, 寻找一个名字叫MessageBoxW的函数.

但是, C和C++的函数命名是不同的, 我指的是编译后的函数名字

对于C, 函数int func(int n)会被编译为类似_func这样的名字.

对于C++, 函数int func(int n)会被编译为类似?func@@YAHH@Z这样的名字.

同样是C++, 函数int func(int double)会被编译为类似?func@@YAHN@Z这样的名字(和上一个不同).

名字中包含了较多信息, 比如:

参数的入栈方式
返回值的类型
参数的类型和数量

这是因为C++有函数重载特性, 虽然函数命名是func, 但int func(int n)int func(int double)完全是两个不同的函数, 编译器通过给它们赋予不同的名字来区分它们.

如果你感兴趣, 这里有更详细的解释

回到ffi, 它在dll中查找函数名字的时候, 是用C风格来查找的.

所以如果你的函数使用C++编译的, ffl在这个dll中就找不到这个函数, 错误LINK 126!

1. 创建工程

使用VS创建一个C++空项目即可. 项目名成以myAddDll为例.

当然, 你也可以直接创建动态链接库DLL.

 

2.修改配置类型为动态库.dll


如图:

在项目配置中, 选择生成动态库.dll

确保你配置了Debug和Release, 同时确保你在x64环境下生成.

 

3. 函数声明

创建一个myAdd.h头文件

声明一个函数:

extern "C"
{
    __declspec(dllexport) int funAdd(int a, int b);
}

extern "C"意味着:

被 extern "C" 修饰的变量和函数是按照 C 语言方式编译和链接的

__declspec(dllexport)意味着:

__declspec(dllexport)用于Windows中的动态库中,声明导出函数、类、对象等供外面调用,省略给出.def文件。即将函数、类等声明为导出函数,供其它程序调用,作为动态库的对外接口函数、类等。

 

4. 函数定义

创建一个myAdd.cpp文件

定义funAdd函数:

#include "myAdd.h"
int funAdd(int a, int b)
{
  return (a + b);
}

函数的内容很简单, 接受两个int类型参数, 返回它们的和.

 

5. 生成dll

右键项目选择生成即可, 生成的myAddDll.dll位于项目目录下的x64/Debug中.

(根据你项目的配置去找, x64或x86, Debug或Release)

 

6. 测试dll

myAddDll.dll拷贝至你的electron项目的根目录下的dll文件夹内

在main.js中添加如下代码:

const ffi = require('ffi-napi'); // 如果前面已经定义过ffi, 就注释掉这一行
// 通过ffi加载myAddDll.dll
const myAddDll = new ffi.Library('../dll/myAddDll', {
  'funAdd': // 声明这个dll中的一个函数
    [
      'int', ['int', 'int'], // 用json的格式罗列其返回类型和参数类型
    ],
});

// 调用函数, 参数1和2, 将返回值直接打印出来, 预计为3
const result = myAddDll.funAdd(1, 2);
console.log(`the result of 1 + 2 is: `+ result);

这段代码中, 主要调用了myAddDll.dll中的int funAdd(int, int), 具体的步骤都写在了代码的注释中.

启动程序:

npm start

查看cmd中的日志:

the result of 1 + 2 is: 3

 

6.1 可能的错误

LINK 126

这个错误, 意味者electron无法使用你的dll.

在这行代码中

const myAddDll = new ffi.Library('../dll/myAddDll', {

ffi.Library的第一个参数, 不光指定了dll的名字, 还指定了dll的路径.

出现LINK 126有两个常见原因:

1. 没有这个目录, 或这个目录下没有myAddDll.dll

2. myAddDll.dll还依赖了其他的一些dll, 但是electron无法找到这个dll.

LINK 127

出现LINK 127的可能原因:

1. electron找到了你的dll, 但是在dll中找不到你声名的函数(funAdd).这通常是由于函数名字错误, 或者是返回值类型/参数的个数及类型不一致导致的.

node-ffi为什么会安装失败

 

如果我们按照node-ffi的github链接中介绍的方法来安装ffi, 即

npm install ffi

然后尝试在main.js中加上一句代码来导入这个模块,

const ffi = require('ffi');

运行一下

npm start

你会得到, 一个错误!

仔细看看提示:

The module xxxxxx was compiled against a different Node.js version using
NODE_MODULE_VERSION 69. This version of Node.js requires
NODE_MODULE_VERSION 73

简单地说:

这个模块是用一个NODE_MODULE_VERSION 69的node.js版本进行编译的,
而当前的版本的Node.js需要NODE_MODULE_VERSION 73(来进行编译).

那么NODE_MODULE_VERSION是什么意思? 后面的数字又是什么意思?

我们在这里可以查询到, 各个NODE_MUODULE_VERSION对应的node.js版本或electron版本, 也叫做node_abi.

再翻译一下错误提示:

这个模块是用一个electron 4.0.4 版本进行编译的,
而当前的版本需要electron 6(来进行编译).

目前为止, 我们的问题解决方案:

重新编译.

重新编译, 指定electron版本, 执行指令:

npm rebuild --runtime=electron --target=6.0.10 --disturl=https://atom.io/download/atom-shell --abi=73

注: 从https://atom.io/download/atom-shell下载会比较慢, 请自备梯子, 或者使用淘宝库来下载.

日志中打印出了多条error, 原因是:

node-ffi里面会调用v8或其他依赖模块的接口, 而这些接口已经更新了, 有的接口改了名字, 有的接口改了参数数量. 但是node-ffi的调用接口语句并没有更新, 所以编译不过.

进一步的问题解决方案:

修改node-ffi的代码, 以适应新版本的v8, 和其他依赖模块.

一般而言, 我建议翻阅github中该项目的的issues, 在关于编译和electron的话题中, 你会找到其他人已经修改好的代码, 通常是一个该项目的fork版本. 你需要下载这个fork版本的源码, 将它拷贝至你的项目node_modules文件夹中, 使用上面的编译指令编译安装.

而前文介绍的ffi-napi是一个特殊情况, 作者不光修改了node-ffi. 还把它修改后的代码上传到了npm仓库, 所以我们可以通过npm install ffi-napi来进行安装, 不必按照下载-拷贝-编译的流程来安装它.

点赞
  1. BluffMaster说道:
    Google Chrome Windows 10
    https://t.me/s/RejtingTopKazino
  2. DealerShadow说道:
    Google Chrome Windows 10
    В мире игр, где любой площадка стремится привлечь гарантиями быстрых выигрышей, рейтинг рублевых казино становится как раз той картой, что проводит сквозь ловушки обмана. Для профи плюс новичков, что пресытился от фальшивых обещаний, такой помощник, чтобы почувствовать настоящую выплату, как вес выигрышной ставки у пальцах. Минус пустой ерунды, только реальные площадки, где выигрыш не только число, но реальная везение.Собрано из яндексовых поисков, будто ловушка, что вылавливает самые свежие веяния в сети. Здесь отсутствует места для шаблонных приёмов, любой пункт словно карта на игре, где блеф проявляется мгновенно. Хайроллеры знают: в рунете тон речи на сарказмом, в котором юмор скрывается словно совет, помогает обойти рисков.В www.don8play.ru этот топ ждёт словно готовая колода, приготовленный на старту. Зайди, коли нужно почувствовать биение настоящей азарта, без обмана плюс провалов. Для тех любит тактильность приза, это как иметь ставку в руках, вместо глядеть в экран.

发表回复

电子邮件地址不会被公开。必填项已用 * 标注