精华内容
下载资源
问答
  • Electron调用dll

    千次阅读 2019-04-22 18:00:35
    Electron通过ffi调用dll(我这里以调用32位dll为例子,如果调用64为dll,更改编译环境即可) 编译 npm install ffi cd .\node_modules\ffi node-gyp rebuild --target=4.0.0 --arch=ia32 --target_arch=ia32 --msvs_...

    Electron通过ffi调用dll(我这里以调用32位dll为例子,如果调用64为dll,更改编译环境即可)

    1. 编译
    npm install ffi
    cd .\node_modules\ffi
    node-gyp rebuild --target=4.0.0 --arch=ia32 --target_arch=ia32 --msvs_version=2015
    

    以同样的方式编译ref

    target=4.0.0 --4.0.0是Electron的版本

    1. 使用

    初始化

    	const ffi = require('ffi')
    	const ref = require('ref')
    	const stringPointer = ref.refType(ref.types.CString)
    

    声明dll方法

          let dllObj = ffi.Library(__dirname + '/dllProject.dll', {
            'add': ['int', ['int', 'int']],
            'stringtest': ['int', [stringPointer, 'int *']],
            'callJsFun': ['void', []],
            'setcallback': ['void', ['pointer']]
          })
          // 设置回调函数 可以直接在dll中调用js方法
          let callback = ffi.Callback('void', ['string'], function (str) {
            alert(str)
          })
          dllObj.setcallback(callback)
    

    调用dll方法

            let results = dllObj.add(1, 2)
            alert(results)
    

    传递指针

            let stringData = ref.alloc('string')
            let stringLen = ref.alloc('int')
            let results = dllObj.stringtest(stringData, stringLen)
            stringData = ref.readPointer(stringData, 0, ref.deref(stringLen))
            alert(results + ': ' + stringData.toString())
    

    GitHub地址

    展开全文
  • 注意: 32位DLL仅能在32位Node、32位Electron上运行。 64位DLL仅能在64位Node、64位Electron上运行。 若要切换Node版本,需要删除组件重新下载。 下载 Visual Studio 2019 Community: ...打开软件,新建项目,搜索...
    1. 注意:

      • 32位DLL仅能在32位Node、32位Electron上运行。
      • 64位DLL仅能在64位Node、64位Electron上运行。
      • 若要切换Node版本,需要删除组件重新下载,删除.electron-gyp文件夹
    2. 下载 Visual Studio 2019 Community:
      https://visualstudio.microsoft.com/zh-hans/vs/

    3. 安装使用C++的桌面开发
      在这里插入图片描述

    4. 打开软件,新建项目,搜索 dll,选择具有导出项的(DLL)动态链接库
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    5. 添加一个函数:

      int sum(int a, int b) {
          return a + b;
      }
      

      在这里插入图片描述

    6. 导出函数:

      extern "C" _declspec(dllexport) int sum(int a, int b);
      

      在这里插入图片描述

    7. 解决方案配置:
      在这里插入图片描述

    8. 生成解决方案:
      在这里插入图片描述
      在这里插入图片描述

    9. 安装python及构建环境( 以管理员身份运行):

      npm i -g node-gyp rebuild
      npm i -g --production windows-build-tools
      
    10. 项目安装ffi-napi

      npm i ffi-napi
      
    11. 新增vue.config.js文件:

      module.exports = {
        pluginOptions: {
          electronBuilder: {
            nodeIntegration: true,
            externals: ['ffi-napi'],
            builderOptions: {
              extraResources: [
                {
                  from: "./dll/arithmeticoperations.dll",
                  to: "../dll/arithmeticoperations.dll"
                }
              ]
            }
          }
        }
      }
      
    12. 修改App.vue

      <template>
        <img alt="Vue logo" src="./assets/logo.png">
      
        <br>
        <input id="numA" type="text"/>
        <input id="numB" type="text"/>
        <button @click="sum">求和</button>
        <input id="numC" type="text" disabled/>
        <br>
      
        <HelloWorld msg="Welcome to Your Vue.js App"/>
      </template>
      
      <script>
      import HelloWorld from './components/HelloWorld.vue'
      const ffi = require('ffi-napi')
      const path = require('path')
      const fs = require("fs")
      
      // 获取 DLL 路径
      // eslint-disable-next-line no-undef
      let dll_path = path.join(__static, 'dll/arithmeticoperations.dll')
      
      // 修正开发环境的路径
      dll_path = dll_path.replace('\\public\\', '\\')
      
      // 修正生产环境的路径
      dll_path = dll_path.replace('\\resources\\app.asar\\', '\\')
      
      export default {
        name: 'App',
        components: {
          HelloWorld
        },
        methods: {
          sum() {
      
            fs.access(dll_path, function (err) {
              if (err) {
                alert('DLL 文件不存在:'+ dll_path)
              } else {
                console.log('DLL 文件已存在:', dll_path)
              }
            })
      
            const dll = new ffi.Library(dll_path, {
              'sum':
                  [
                    'int', ['int', 'int']
                  ]
            })
      
            const result = dll.sum(
                parseInt(document.getElementById("numA").value),
                parseInt(document.getElementById("numB").value)
            )
      
            document.getElementById('numC').value = result.toString()
      
          }
        }
      }
      </script>
      
      <style>
      @import "../src/style/app.scss";
      
      </style>
      
    13. 文件目录:
      在这里插入图片描述

    14. 计算:
      在这里插入图片描述

    15. 注意:
      由于截图与代码编写先后顺序的问题,请以源码为准(源码包含根据32位、64位系统自动选择对应的DLL)。

    16. DLL 源码:
      GitHub
      Gitee
      CodeChina
      DLL解决方案:
      GitHub
      Gitee
      CodeChina

    17. 源码:
      GitHub
      Gitee
      CodeChina

    展开全文
  • 相关网址版本对比表Electron版本NODE_MODULE_VERSIONNode版本v3.1.1364v10.2.0v4.2.1169v10.11.0v5.0.1170v12.0.0v6.0.1273v12.4.0Electron中的Node的NODE_MODULE_VERSION版本和官方给出的对应关系不太一样Electron ...

    相关网址

    版本对比表Electron版本

    NODE_MODULE_VERSION

    Node版本

    v3.1.13

    64

    v10.2.0

    v4.2.11

    69

    v10.11.0

    v5.0.11

    70

    v12.0.0

    v6.0.12

    73

    v12.4.0

    Electron中的Node的NODE_MODULE_VERSION版本和官方给出的对应关系不太一样

    Electron 4和5的语法变动较大

    Electron 2已停止维护

    也就是说node-ffi官方版本支持的Electron版本已停止维护

    为什么使用DLL需要使用系统 API 操作或扩展应用程序;

    需要调用第三方的接口API,特别是与硬件设备进行通信,而这些接口 API 基本上都是通过 C++ 动态链接库(DLL)实现的;

    需要调用C++实现的一些复杂算法等。

    Edge.js

    开源项目 edge 可以帮助我们实现 Node 和 .NET 之间的相互调用

    我们最常见就是使用它来调用C#的代码方法或者C#生成的DLL文件的方法

    C/C++生成的DLL就要用node-ffi 因为我是要调用系统的DLL所以主要使用node-ffi

    node-ffi简介

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

    与Node.js Addons相比,此方法有如下优点:

    不需要源代码。

    不需要每次重编译node,Node.js Addons引用的.node会有文件锁,会对`electron应用热更新造成麻烦。

    不要求开发者编写C代码,但是仍要求开发者具有一定C的知识。

    缺点是:

    性能有折损

    类似其他语言的FFI调试,此方法近似黑盒调用, 查错比较困难。

    node-ffi安装不要使用Electron v6.0.x

    不要使用Electron v6.0.x

    不要使用Electron v6.0.x

    重要事情说三遍不要用Electron v6.0.x 不能成功编译node-ffi 第三方修改版也不支持

    系统的Node版本最好用v10.16.3 不要用最新的Node版本 各种问题坑死人

    node-ffi通过Buffer类,在C代码和JS代码之间实现了内存共享,

    类型转换则是通过ref、ref-array、ref-struct实现。

    设置镜像地址

    npm镜像

    更新npm的包镜像源

    1

    2npm config set registry https://registry.npm.taobao.org

    npm config list

    还原默认

    1npm config set registry https://registry.npmjs.org

    electron镜像

    查看配置文件的位置

    1npm config list

    可以查看到本机的userconfig在哪,即.npmrc文件在哪

    比如我的

    userconfig C:\Users\Jian.npmrc

    打开该文件 添加

    1

    2registry=https://registry.npm.taobao.org

    electron_mirror="https://npm.taobao.org/mirrors/electron/"

    rebuild镜像

    提前写在这里 具体看下文

    1

    2

    3"scripts": {

    "rebuild": "electron-rebuild -d=http://npm.taobao.org/mirrors/atom-shell -v 5.0.11 -m=./"

    }

    配置编译环境

    由于node-ffi/ref包含C原生代码,所以安装需要配置Node原生插件编译环境。

    配置Node原生插件编译环境

    1

    2

    3# 管理员运行bash/cmd/powershell,否则会提示权限不足

    npm install --global --production windows-build-tools

    npm install -g node-gyp

    上面的操作会自动把Python和C++开发工具包都集成进去

    手动下载C++编译环境

    Visual Studio Build Tools (using “Visual C++ build tools” workload)

    或者

    Visual Studio 2017 Community (using the “Desktop development with C++” workload)

    如果没有Python则下载Python2.x版本,不支持Python3。传送门

    设置python路径(根据自己的实际情况设置)

    1npm config set python C:\Users\Jian\.windows-build-tools\python27\python.exe

    查看npm全局安装目录

    1npm root -g

    安装依赖/重新编译

    根据需要安装对应的库

    1

    2

    3

    4npm install ffi --save

    npm install ref --save

    npm install ref-array --save

    npm install ref-struct --save

    如果是electron项目,则项目可以安装electron-rebuild插件,能够方便遍历node-modules中所有需要rebuild的库进行重编译。

    1npm install electron-rebuild --save

    在package.json中配置快捷运行方式

    推荐 下面的方法能使用淘宝的镜像,防止构建时下载依赖失败

    1

    2

    3"scripts": {

    "rebuild": "electron-rebuild -d=http://npm.taobao.org/mirrors/atom-shell -v 5.0.11 -m=./"

    }

    -v 为Electron的版本号

    之后执行

    1npm run rebuild

    操作即可完成electron的重编译。

    需要electron-rebuild重新build的模块必须在dependencies中,不能在devDependencies中。

    因为electron-rebuild只会rebuild dependencies中依赖。

    Node>=10编译失败

    Electron内置的Node版本10或10以上编译ffi会失败

    两种解决方法:

    使用新的ffi-napi(api是一样的,同时支持node.js新的napi)

    1npm install ffi-napi --save

    引用

    1const ffi = require('ffi-napi');

    使用第三方修改过的ffi

    注意这种方法只支持到Electron v5.0.11

    Electron v6.x也不成功

    推荐用这种方法 因为上面那种用的人还不够多 遇见坑的话不好解决

    在package.json中

    1

    2

    3

    4"ffi": "^2.3.0",

    "ref": "^1.3.5",

    "ref-array": "^1.2.0",

    "ref-struct": "^1.1.0"

    改为

    1

    2

    3

    4"ffi": "github:lxe/node-ffi#node-12",

    "ref": "github:lxe/ref#node-12",

    "ref-array": "github:lxe/ref-array#node-12",

    "ref-struct": "github:lxe/ref-struct#node-12"

    安装依赖

    1

    2npm install

    npm run rebuild

    Rebuild编译失败

    问题一 rebuild不生效

    解决方式就是不要使用cnpm 使用npm安装依赖

    cnpm安装的依赖的文件夹是软连接 在重新构建时是不会生效的

    问题二App threw an error during load

    Error: Could not locate the bindings file.

    运行

    1npm rebuild

    问题三node.lib : fatal error LNK1106

    尝试删除下面文件夹(根据实际情况)下对应Electron版本的文件夹 重新rebuild

    1C:\Users\Jian\.electron-gyp

    问题四gyp ERR! configure error

    gyp ERR! stack Error: 403 status code downloading arm64 node.lib

    删除上面的文件夹下对应Electron版本的文件夹

    删除项目下的node_modules 用npm安装依赖

    删除npm缓存

    1npm cache clean -f

    使用阿里源

    1

    2

    3"scripts": {

    "rebuild": "electron-rebuild -d=http://npm.taobao.org/mirrors/atom-shell -v 5.0.11 -m=./"

    }

    重新安装

    1

    2npm install

    npm run rebuild

    node-ffi语法详解

    变量类型

    C语言中有4种基础数据类型—-整型 浮点型 指针 聚合类型

    基础

    整型、字符型都有分有符号和无符号两种。

    类型

    最小范围

    char

    0 ~ 127

    signed char

    -127 ~ 127

    unsigned char

    0 ~ 256

    在不声明unsigned时 默认为signed型

    ref中unsigned会缩写成u, 如 uchar 对应 usigned char。

    浮点型中有 float double long double。

    ref库中已经帮我们准备好了基础类型的对应关系。

    C++类型

    ref对应类型

    void

    ref.types.void

    int8

    ref.types.int8

    uint8

    ref.types.uint8

    int16

    ref.types.int16

    uint16

    ref.types.uint16

    float

    ref.types.float

    double

    ref.types.double

    bool

    ref.types.bool

    char

    ref.types.char

    uchar

    ref.types.uchar

    short

    ref.types.short

    ushort

    ref.types.ushort

    int

    ref.types.int

    uint

    ref.types.uint

    long

    ref.types.long

    ulong

    ref.types.ulong

    DWORD

    ref.types.ulong

    DWORD为winapi类型,下文会详细说明

    更多拓展可以去ref doc

    ffi.Library中,既可以通过ref.types.xxx的方式申明类型,也可以通过文本(如uint16)进行申明。

    字符型

    字符型由char构成,在GBK编码中一个汉字占2个字节,在UTF-8中占用3~4个字节。一个ref.types.char默认一字节。根据所需字符长度创建足够长的内存空间。这时候需要使用ref-array库。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13const ref = require('ref')

    const refArray = require('ref-array')

    const CharArray100 = refArray(ref.types.char, 100) // 申明char[100]类型CharArray100

    const bufferValue = Buffer.from('Hello World') // Hello World转换Buffer

    // 通过Buffer循环复制, 比较啰嗦

    const value1 = new CharArray100()

    for (let i = 0, l = bufferValue.length; i < l; i++) {

    value1[i] = bufferValue[i]

    }

    // 使用ref.alloc初始化类型

    const strArray = [...bufferValue] //需要将`Buffer`转换成`Array`

    const value2 = ref.alloc(CharArray100, strArray)

    在传递中文字符型时,必须预先得知DLL库的编码方式。node默认使用UTF-8编码。若DLL不为UTF-8编码则需要转码,推荐使用iconv-lite

    1npm install iconv-lite --save

    转码

    1

    2const iconv = require('iconv-lite')

    const cstr = iconv.encode(str, 'gbk')

    注意!使用encode转码后cstr为Buffer类,可直接作为当作uchar类型

    iconv.encode(str.’gbk’)中gbk默认使用的是unsigned char | 0 ~ 256储存。假如C代码需要的是signed char | -127 ~ 127,则需要将buffer中的数据使用int8类型转换。

    1

    2

    3

    4

    5

    6const Cstring100 = refArray(ref.types.char, 100)

    const cString = new Cstring100()js

    const uCstr = iconv.encode('你好世界', 'gbk')

    for (let i = 0; i < uCstr.length; i++) {

    cString[i] = uCstr.readInt8(i)

    }

    C代码为字符数组char[]/char *设置的返回值,通常返回的文本并不是定长,不会完全使用预分配的空间,末尾则会是无用的值。如果是预初始化的值,一般末尾是一大串的0x00,需要手动做trimEnd,如果不是预初始化的值,则末尾不定值,需要C代码明确返回字符串数组的长度returnValueLength。

    内置简写

    ffi中内置了一些简写

    1

    2

    3ref.types.int => 'int'

    ref.refType('int') => 'int*'

    char* => 'string'

    只建议使用’string’。

    字符串虽然在js中被认为是基本类型,但在C语言中是以对象的形式来表示的,所以被认为是引用类型。所以string其实是char* 而不是char

    聚合类型

    多维数组

    遇到定义为多维数组的基本类型 则需要使用ref-array进行创建

    C

    1char cName[50][100] // 创建一个cName变量储存级50个最大长度为100的名字

    JS

    1

    2

    3

    4

    5const ref = require('ref')

    const refArray = require('ref-array')

    const CName = refArray(refArray(ref.types.char, 100), 50)

    const cName = new CName()

    结构体

    结构体是C中常用的类型,需要用到ref-struct进行创建

    C

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10typedef struct {

    char cTMycher[100];

    int iAge[50];

    char cName[50][100];

    int iNo;

    } Class;

    typedef struct {

    Class class[4];

    } Grade;

    JS

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13const ref = require('ref')

    const Struct = require('ref-struct')

    const refArray = require('ref-array')

    const Class = Struct({ // 注意返回的`Class`是一个类型

    cTMycher: RefArray(ref.types.char, 100),

    iAge: RefArray(ref.types.int, 50),

    cName: RefArray(RefArray(ref.types.char, 100), 50)

    })

    const Grade = Struct({ // 注意返回的`Grade`是一个类型

    class: RefArray(Class, 4)

    })

    const grade3 = new Grade() // 新建实例

    指针指针是一个变量,其值为实际变量的地址,即内存位置的直接地址,有些类似于JS中的引用对象。

    C语言中使用*来代表指针

    例如 int* a 则就是 整数型a变量的指针 , &用于表示取地址

    1

    2

    3int a=10,

    int *p; // 定义一个指向整数型的指针`p`

    p=&a // 将变量`a`的地址赋予`p`,即`p`指向`a`

    node-ffi实现指针的原理是借助ref,使用Buffer类在C代码和JS代码之间实现了内存共享,让Buffer成为了C语言当中的指针。

    注意,一旦引用ref,会修改Buffer的prototype,替换和注入一些方法,请参考文档ref文档

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11const buf = new Buffer(4) // 初始化一个无类型的指针

    buf.writeInt32LE(12345, 0) // 写入值12345

    console.log(buf.hexAddress()) // 获取地址hexAddress

    buf.type = ref.types.int // 设置buf对应的C类型,可以通过修改`type`来实现C的强制类型转换

    console.log(buf.deref()) // deref()获取值12345

    const pointer = buf.ref() // 获取指针的指针,类型为`int **`

    console.log(pointer.deref().deref()) // deref()两次获取值12345

    要明确一下两个概念 一个是结构类型,一个是指针类型,通过代码来说明。

    1

    2

    3

    4

    5

    6

    7

    8

    9// 申明一个类的实例

    const grade3 = new Grade() // Grade 是结构类型

    // 结构类型对应的指针类型

    const GradePointer = ref.refType(Grade) // 结构类型`Grade`对应的指针的类型,即指向Grade

    // 获取指向grade3的指针实例

    const grade3Pointer = grade3.ref()

    // deref()获取指针实例对应的值

    console.log(grade3 === grade3Pointer.deref()) // 在JS层并不是同一个对象

    console.log(grade3['ref.buffer'].hexAddress() === grade3Pointer.deref()['ref.buffer'].hexAddress()) //但是实际上指向的是同一个内存地址,即所引用值是相同的

    可以通过ref.alloc(Object|String type, ? value) → Buffer直接得到一个引用对象

    1

    2const iAgePointer = ref.alloc(ref.types.int, 18) // 初始化一个指向`int`类的指针,值为18

    const grade3Pointer = ref.alloc(Grade) // 初始化一个指向`Grade`类的指针

    回调函数

    C的回调函数一般是用作入参传入。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19const ref = require('ref')

    const ffi = require('ffi')

    const testDLL = ffi.Library('./testDLL', {

    setCallback: ['int', [

    ffi.Function(ref.types.void, // ffi.Function申明类型, 用`'pointer'`申明类型也可以

    [ref.types.int, ref.types.CString])]]

    })

    const uiInfocallback = ffi.Callback(ref.types.void, // ffi.callback返回函数实例

    [ref.types.int, ref.types.CString],

    (resultCount, resultText) => {

    console.log(resultCount)

    console.log(resultText)

    },

    )

    const result = testDLL.uiInfocallback(uiInfocallback)

    注意!如果你的CallBack是在setTimeout中调用,可能存在被GC的BUG

    1

    2

    3

    4process.on('exit', () => {

    /* eslint-disable-next-line */

    uiInfocallback // keep reference avoid gc

    })

    代码实例

    举个完整引用例子

    C

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17// 头文件

    #pragma once

    //#include "../include/MacroDef.h"

    #defineCertMaxNumber 10

    typedef struct {

    int length[CertMaxNumber];

    char CertGroundId[CertMaxNumber][2];

    char CertDate[CertMaxNumber][2048];

    } CertGroud;

    #define DLL_SAMPLE_API __declspec(dllexport)

    extern "C"{

    //读取证书

    DLL_SAMPLE_API int My_ReadCert(char *pwd, CertGroud *data,int *iCertNumber);

    }

    JS

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29const CertGroud = Struct({

    certLen: RefArray(ref.types.int, 10),

    certId: RefArray(RefArray(ref.types.char, 2), 10),

    certData: RefArray(RefArray(ref.types.char, 2048), 10),

    curCrtID: RefArray(RefArray(ref.types.char, 12), 10),

    })

    const dll = ffi.Library(path.join(staticPath, '/key.dll'), {

    My_ReadCert: ['int', ['string', ref.refType(CertGroud), ref.refType(ref.types.int)]],

    })

    async function readCert({ ukeyPassword, certNum }){

    return new Promise(async (resolve) => {

    // ukeyPassword为string类型, c中指代 char*

    ukeyPassword = ukeyPassword.toString()

    // 根据结构体类型 开辟一个新的内存空间

    const certInfo = new CertGroud()

    // 开辟一个int 4字节内存空间

    const _certNum = ref.alloc(ref.types.int)

    // certInfo.ref()作为certInfo的指针传入

    dll.My_ucRMydCert.async(ukeyPassword, certInfo.ref(), _certNum, () => {

    // 清除无效空字段

    let cert = bufferTrim.trimEnd(new Buffer(certInfo.certData[certNum]))

    cert = cert.toString('binary')

    resolve(cert)

    })

    })

    }

    常见错误Dynamic Linking Error: Win32 error 126

    这个错误有三种原因

    通常是传入的DLL路径错误,找不到Dll文件,推荐使用绝对路径。

    如果是在x64的node/electron下引用32位的DLL,也会报这个错,反之亦然。要确保DLL要求的CPU架构和你的运行环境相同。

    DLL还有引用其他DLL文件,但是找不到引用的DLL文件,可能是VC依赖库或者多个DLL之间存在依赖关系。

    Dynamic Linking Error: Win32 error 127:DLL中没有找到对应名称的函数,需要检查头文件定义的函数名是否与DLL调用时写的函数名是否相同。

    Path设置

    如果你的DLL是多个而且存在相互调用问题,会出现Dynamic Linking Error: Win32 error 126错误3。这是由于默认的进程Path是二进制文件所在目录,即node.exe/electron.exe目录而不是DLL所在目录,导致找不到DLL同目录下的其他引用。可以通过如下方法解决:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10//方法一, 调用winapi SetDllDirectoryA设置目录

    const ffi = require('ffi')

    const kernel32 = ffi.Library("kernel32", {

    'SetDllDirectoryA': ["bool", ["string"]]

    })

    kernel32.SetDllDirectoryA("pathToAdd")

    //方法二(推荐),设置Path环境环境

    process.env.PATH = `${process.env.PATH}${path.delimiter}${pathToAdd}`

    闪崩问题

    实际node-ffi调试的时候,很容易出现内存错误闪崩,甚至会出现断点导致崩溃的情况。这个是往往是因为非法内存访问造成,可以通过Windows日志看到错误信息,但是相信我,那并没有什么用。C的内存差错是不是一件简单的事情。

    GetLastError

    简单说node-ffi通过winapi来调用DLL,这导致GetLastError永远返回0。最简单方法就是自己写个C++ addon来绕开这个问题。

    PVOID返回空

    PVOID返回空,即内存地址FFFFFFFF闪崩

    winapi中,经常通过判断返回的pvoid指针是否存在来判断是否成功,但是在node-ffi中,对FFFFFFFF的内存地址deref()会造成程序闪崩。必须迂回采用指针的指针类型进行特判

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13HDEVNOTIFY

    WINAPI

    RegisterDeviceNotificationA(

    _In_ HANDLE hRecipient,

    _In_ LPVOID NotificationFilter,

    _In_ DWORD Flags);

    HDEVNOTIFY hDevNotify = RegisterDeviceNotificationA(hwnd, &notifyFilter, DEVICE_NOTIFY_WINDOW_HANDLE);

    if (!hDevNotify) {

    DWORD le = GetLastError();

    printf("RegisterDeviceNotificationA() failed [Error: %x]\r\n", le);

    return 1;

    }

    JS

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11const apiDef = SetupDiGetClassDevsW: [

    W.PVOID_REF,

    [W.PVOID, W.PCTSTR, W.HWND, W.DWORD]

    ] // 注意返回类型`W.PVOID_REF`必须设置成pointer,就是不设置type,则node-ffi不会尝试`deref()`

    const hDEVINFOPTR = this.setupapi.SetupDiGetClassDevsW(null, typeBuffer, null,

    setupapiConst.DIGCF_PRESENT | setupapiConst.DIGCF_ALLCLASSES

    )

    const hDEVINFO = winapi.utils.getPtrValue(hDEVINFOPTR, W.PVOID) // getPtrValue特判,如果地址为全`FF`则返回空

    if (!hDEVINFO) {

    throw new ErrorWithCode(ErrorType.DEVICE_LIST_ERROR, ErrorCode.GET_HDEVINFO_FAIL)

    }

    简单范例

    DLL源码

    1

    2extern "C" int __declspec(dllexport)My_Test(char *a, int b, int c);

    extern "C" void __declspec(dllexport)My_Hello(char *a, int b, int c);

    调用DLL

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19import ffi from 'ffi'

    // `ffi.Library`用于注册函数,第一个入参为DLL路径,最好为文件绝对路径

    const dll = ffi.Library( './test.dll', {

    // My_Test是dll中定义的函数,两者名称需要一致

    // [a, [b,c....]] a是函数出参类型,[b,c]是dll函数的入参类型

    My_Test: ['int', ['string', 'int', 'int']], // 可以用文本表示类型

    My_Hello: [ref.types.void, ['string', ref.types.int, ref.types.int]] // 更推荐用`ref.types.xx`表示类型,方便类型检查,`char*`的特殊缩写下文会说明

    })

    //同步调用

    const result = dll.My_Test('hello', 3, 2)

    //异步调用

    dll.My_Test.async('hello', 3, 2, (err, result) => {

    if(err) {

    //todo

    }

    return result

    })

    实例-禁用右键菜单

    现在使用 ffi 调用 user32.dll 中的 GetSystemMenu 函数来解决这个问题,首先新建一个 user32.js 文件,为了展示 ffi ,我多定义了几个API函数:

    1

    2

    3

    4

    5

    6

    7

    8

    9//const ffi = require('ffi');

    const ffi = require('ffi-napi');

    exports.User32 = ffi.Library('user32', {

    'GetWindowLongPtrW': ['int', ['int', 'int']],

    'SetWindowLongPtrW': ['int', ['int', 'int', 'long']],

    'GetSystemMenu': ['int', ['int', 'bool']],

    'DestroyWindow': ['bool', ['int']]

    });

    修改 main.js 文件,首先导入 user32.js:

    1const user32 = require('./app/scripts/user32').User32

    然后修改如下内容:

    1

    2

    3

    4

    5

    6mainWindow.once('ready-to-show', () => {

    let hwnd = mainWindow.getNativeWindowHandle() //获取窗口句柄。

    console.log(hwnd);

    user32.GetSystemMenu(hwnd.readUInt32LE(0), true); //禁用系统菜单.

    mainWindow.show()

    });

    再运行项目,系统菜单就消失的无影无踪了。

    注意窗口初始化时设置 show: false 否则ready-to-show不会调用

    示例-获取窗口1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27const ffi = require('ffi');

    const ref = require('ref');

    const iconv = require('iconv-lite')

    var voidPtr = ref.refType(ref.types.void);

    var stringPtr = ref.refType(ref.types.CString);

    var user32 = ffi.Library('user32.dll', {

    EnumWindows: ['bool', [voidPtr, 'int32']],

    GetWindowTextA : ['long', ['long', stringPtr, 'long']]

    });

    windowProc = ffi.Callback('bool', ['long', 'int32'], function(hwnd, lParam) {

    var buf, name, ret;

    buf = new Buffer(255);

    ret = user32.GetWindowTextA(hwnd, buf, 255);

    name = ref.readCString(buf, 0);

    var text = iconv.decode(buf,'gbk');

    if(text.indexOf("走进河南.pptx")!=-1){

    console.log(text);

    console.log(hwnd);

    }

    return false;

    });

    user32.EnumWindows(windowProc, 0);

    附录

    DLL分析工具

    Dependency Walker

    可以查看DLL链接库的所有信息、以及DLL依赖关系的工具,但是很遗憾不支持WIN10。如果你不是WIN10用户,那么你只需要这一个工具即可,下面工具可以跳过。

    Viewdll

    只能查看DLL中的函数 支持WIN10

    Process Monitor

    可以查看进程执行时候的各种操作,如IO、注册表访问等。这里用它来监听node/electron进程的IO操作,用于排查Dynamic Linking Error: Win32 error错误原因3,可以查看ffi.Libary时的所有IO请求和对应结果,查看缺少了什么DLL。

    dumpbin

    dumpbin.exe为Microsoft COFF二进制文件转换器,它显示有关通用对象文件格式(COFF)二进制文件的信息。可用使用dumpbin检查COFF对象文件、标准COFF对象库、可执行文件和动态链接库等。

    以管理员身份运行适用于 VS 2017 的x86_x64 兼容工具命令提示 输入下面命令

    1

    2

    3

    4# 返回DLL头部信息,会说明是32 bit word Machine/64 bit word Machine

    dumpbin /headers C:\Windows\System32\user32.dll > c:\export1.txt

    # 返回DLL导出信息,name列表为导出的函数名

    dumpbin /exports C:\Windows\System32\user32.dll > c:\export2.txt

    自动转换工具

    tjfontaine大神提供了一个node-ffi-generate,可以根据头文件,自动生成node-ffi函数申明,注意这个需要Linux环境,简单用KOA包了一层改成了在线模式ffi-online,可以丢到VPS中运行。

    WINAPI

    winapi存在大量的自定义的变量类型,waitingsong大侠的轮子 node-win32-api中完整翻译了全套windef.h中的类型,而且这个项目采用TS来规定FFI的返回Interface,很值得借鉴。

    使用win32-api(放弃)

    安装

    1

    2npm install win32-api --save

    npm install ref-napi --save

    1

    2"win32-api": "^6.2.0",

    "ref-napi": "^1.4.2"

    安装

    1npm install

    这个库用的ffi也是不支持node v12 修改源码用支持node12的ffi也是rebuild不成功(原因是构建时依赖无法下载,哎国内这墙啊) 放弃使用该库了

    展开全文
  • 调用Dll需要安装node-ffi-napi,由于node-ffi-napi包含 C 原生代码,所以安装需要配置 Node 原生插件编译环境 https://github.com/node-ffi-napi/node-ffi-napi npm install --global --production windows-build-...

    一、环境准备:

    调用Dll需要安装node-ffi-napi,由于node-ffi-napi包含 C 原生代码,所以安装需要配置 Node 原生插件编译环境

    https://github.com/node-ffi-napi/node-ffi-napi

    npm install --global --production windows-build-tools
    npm install -g node-gyp
    npm install ffi-napi

    二、Delphi窗体Dll编写

    2.1 Project1代码

    library Project1;
    
    uses
      System.SysUtils,
      System.Classes,
      Unit1 in 'Unit1.pas' {Form1};
    
    {$R *.res}
    
    exports
      ShowForm;
    
    begin
    
    end.

    2.2 窗体单元代码Unit1

    unit Unit1;
    
    interface
    
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
      System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
    
    type
      TForm1 = class(TForm)
        Button1: TButton;
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    procedure ShowForm; stdcall;// 需要从DLL中导出的函数,一定要有stdcall关键字
    
    implementation
    
    
    procedure ShowForm; stdcall;
    begin
      try
        Form1 := TForm1.Create(nil);
        try
          Form1.ShowModal;
        finally
          Form1.Free;
        end;
      except
        on E: Exception do
          ShowMessage(E.ToString);
      end;
    end;
    
    {$R *.dfm}
    
    end.

    三、将编译好Dll放入程序目录下,然后再Electron主进程main.js中引用Dll

    // 调用Dll
    const ffi = require('ffi-napi');
    var libm = ffi.Library('Project1.dll', {
      'ShowForm': ['void', []],
    });
    libm.ShowForm();

    四、如果是渲染进程(web页面)要调用,则需要通过主进程与渲染进程通信,然后调用

    4.1 主进程main.js

    // 获取ipc
    const {ipcMain} = require('electron')
    // 调用Dll
    const ffi = require('ffi-napi');
    var libm = ffi.Library('Project1.dll', {
      'ShowForm': ['void', []],
    });
    // 监听ipc通道
    ipcMain.on('showForm', e => libm.ShowForm());

    4.2 渲染进程调用,以Vue为例

    <template>
      <div style="width: 100%;height: 100%;background-color:#b979ff;">
        <img src="../assets/logo.png" alt="" @click="showForm">
      </div>
    </template>
    
    <script>
        //if (window.require) {
         const {ipcRenderer} = window.require('electron');
       // }
        export default {
            name: "Setting",
            methods: {
                showForm: function () {
                    console.log("showForm");
                    ipcRenderer.send("showForm");
                    // if (window.require) {
                    //     console.log("max");
                    //     ipcRenderer.send("max");
                    // }
    
                }
            }
        }
    </script>
    
    <style scoped>
    
    </style>

     

    展开全文
  • 如何在Electron调用Dll

    千次阅读 2019-06-06 08:11:46
    如何Electron调用Dll 客户端有些硬件的接口需要调试,是在电脑上连了一些硬件的设备,比如打印机、扫描仪或者进行串口通信等等。单靠JS是完成不了了,我们决定通过把C++或者C#把这些功能打包成Dll,然后在Electron...
  • Electron调用C++ DLL文件

    2021-03-27 23:24:00
    一、Electron调用C++ DLL文件 二、遇到的一些报错处理 1.yarn : 无法加载文件..\yarn.ps1,因为在此系统上禁止运行脚本 2.没有注册类 一、Electron调用C++ DLL文件 Node.JS 调用 DLL 文件分两种方式,其一是...
  • electron 调用 dll ,webpack的配置 //重点配置项 module.exports = { output: { libraryTarget: 'commonjs2', }, //预加载 node electron externalsPresets: { node: true, electron: true, ...
  • 经过2天的试验,electron高版本调用dll不能使用node-ffi,node-ffi不支持nodejs10及以上的版本 高版本要调用dll用node-ffi-napi https://github.com/node-ffi-napi/node-ffi-napi ...
  • 记录一个S,B问题,windowss electron ffi 调用 C++ dll 依赖问题, 运行目录在 modules/electron/dist 需要将dll的依赖拷贝下去 不然 126错误 你懂滴 ! 暴力方法: varkernel32=ffi.Library("kernel32",{ '...
  • Electron框架利用Addon调用DLL

    千次阅读 2018-11-20 00:13:22
    书接上篇,利用插件调用DLL简单实例 1,做一个简单的DLL,根据系统位数编译成DLL // demo_dll.cpp: 定义 DLL 应用程序的导出函数。 // #include "stdafx.h" extern "C" _declspec(dllexport) ...
  • 查了一下,网上说ffi好几年没有更新了,基本上node新版都安不了,可以使用ffi-napi替代,ffi-napi是作者(node-ffi-napi)根据node-ffi修改而发布到npm仓库的, 可以直接通过npm安装, 支持node.js 12和electron高版本. ...
  • 调用C# DLL electron-updater在线更新 项目设置 npm install 为开发编译和热重载 npm run serve 为生产编译和缩小 npm run build Lints 和修复文件 npm run lint 自定义配置 请参阅。 地方发展(重要) npm run...
  • Electron通过ffi调用DLL

    万次阅读 2017-07-12 11:13:48
    第一步建立一个DLL int WINAPI CAM_Open(char *pIn, char* pOut); 第二步安装ffi npm install --save ffi 针对electron版本重新编译 cd node_modules\ffi node-gyp rebuild -target=1.6.11 -arch=x64 -dist-...
  • electron-vue项目环境 点击就下载就完事了,我用的是这个版本的,electron:8.1还行。 下载环境 npm install --global --production windows-build-tools 运行后如果一直卡在安装python这一步,请看这里 网址...
  • Electron使用NodeJS扩展模块调用DLL

    万次阅读 2017-07-11 17:51:53
    由于安装的electron是64位的,所以需要配置编译生成64位的DLL。 把生成的FaceRecognition.dll拷贝到electron应用的目录。 把FaceRecognition.h和FaceRecognition.lib拷贝到nodejs扩展模块的目录。 如果采用...
  • 开始是打算在c++中调用dll动态链接库,然后将c++通过node-gyp编译成在nodejs环境中可用的.node二进制文件。此方法的具体实现可以参考https://yijiebuyi.com/blog/d401dbebf2c1f9491440f1a6fd62f6b4.html 跟着文章一...
  • 由于老大的要求,需要把前端项目打包成客户端并且能够调用dll文件就要求我去做一下调研,我花了三天时间各种找资料...第一种:如果有c++的开发经验就可以直接看 Electron使用NodeJS扩展模块调用DLL貌似也是官方推荐...
  • Electron / NodeJs ffi-napi 调用Dll

    千次阅读 热门讨论 2020-09-24 19:13:58
    该文档主要是Electron-vue项目的背景下,记录ref-array-napi、ref-napi、ref-struct-napi、ffi-napi 使用基本类型组合类型调用Dll接口 。 基础准备 package.json中 "dependencies ":{ "ref-array-napi": "^1.2.0",...
  • 背景:在electron运行目录下新建res子目录,并将所有dll放置在res子目录中,此时通过node-ffi模块调用dll时候,即便是寻找路径写对,还是会出现win32 126的错误。在这里说明一下, 126的错误就是找不到对应模块。 ...
  • 2.调用C++的dll 参考关于在electron调用C++动态库的经验总结 3.使用addon(实际上addon也是一个动态链接库) 参考Node.js v14.8.0 文档 C++ 插件 参考Electron & C++ 快速开发桌面Web "混合"应用 ...
  • electron使用ffi调用动态链接库dll

    千次阅读 2019-05-08 15:18:54
    ##方法1:调用动态链接库dll(c++写的) electron桌面应用开发下调用: ###1.1 准备工作 编译软件:(编译c++/c的插件) - 安装npm install -g node-gyp 管理员身份运行: 如果本机已经装过vs2017,会失败,不建议...
  • electron集成node-ffi调用dll踩坑记录

    千次阅读 2020-05-07 09:44:30
    64位系统运行32位dll的时候,node和python2.7,electron都应该为32位。实际使用过程中,所有切成32位以后,node我换成了64位,没有影响。 报错 :Error: A dynamic link library (DLL) initialization routine .....
  • [杂乱]Electron通过node-ffi调用C++dll

    千次阅读 2017-12-18 18:31:39
    Electron通过node-ffi调用C++dllffi安装与调用安装node-gyp 安装前提条件 python(v2.7 ,3.x不支持); visual C++ Build Tools,或者 (vs2015以上(包含15)) .net framework 4.5.1 如果是干净的环境可以用下面命令...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 515
精华内容 206
关键字:

dll调用electron