使用ffi-napi引入C++的dll

引言

这一篇将介绍如何在node.js+electron环境中, 使用node-ffi/ffi-napi调用C/C++编写的动态链接库(即dll), 实现调用C/C++代码。

本教程适用于electron 4.x-6.x版本。

如electron 4.2.10版本, electron 5.0.6版本, electron 6.0.10版本。

ffi

实现这个功能, 主要使用的插件是ffiForeign Function Interface,外部函数接口

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

node-ffi通过Buffer类,在C代码和JS代码之间实现了内存共享,
类型转换则是通过ref、ref-array、ref-struct实现。

使用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来进行安装, 不必按照下载-拷贝-编译的流程来安装它。

node-ffi语法详解

变量类型

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

基础

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

类型最小范围
char0 ~ 127
signed char-127 ~ 127
unsigned char0 ~ 256

在不声明unsigned时 默认为signed型

refunsigned会缩写成u, 如 uchar 对应 usigned char

浮点型中有 float double long double

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

C++类型ref对应类型
voidref.types.void
int8ref.types.int8
uint8ref.types.uint8
int16ref.types.int16
uint16ref.types.uint16
floatref.types.float
doubleref.types.double
boolref.types.bool
charref.types.char
ucharref.types.uchar
shortref.types.short
ushortref.types.ushort
intref.types.int
uintref.types.uint
longref.types.long
ulongref.types.ulong
DWORDref.types.ulong

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

更多拓展可以去ref doc

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

字符型

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

const 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

npm install iconv-lite --save

转码

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

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

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

const 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中内置了一些简写

ref.types.int => 'int'
ref.refType('int') => 'int*'
char* => 'string'

只建议使用’string’。

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

聚合类型

多维数组

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

C

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

JS

const 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

typedef struct {char cTMycher[100];int iAge[50];char cName[50][100];int iNo;
} Class;typedef struct {Class class[4];
} Grade;

JS

const 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变量的指针 , &用于表示取地址

int a=10,
int *p; // 定义一个指向整数型的指针`p`
p=&a // 将变量`a`的地址赋予`p`,即`p`指向`a`

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

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

const buf = new Buffer(4) // 初始化一个无类型的指针
buf.writeInt32LE(12345, 0) // 写入值12345console.log(buf.hexAddress()) // 获取地址hexAddressbuf.type = ref.types.int // 设置buf对应的C类型,可以通过修改`type`来实现C的强制类型转换
console.log(buf.deref()) // deref()获取值12345const pointer = buf.ref() // 获取指针的指针,类型为`int **`console.log(pointer.deref().deref())  // deref()两次获取值12345

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

// 申明一个类的实例
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直接得到一个引用对象

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

回调函数

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

const 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

process.on('exit', () => {/* eslint-disable-next-line */uiInfocallback // keep reference avoid gc
})

代码实例

举个完整引用例子

C

// 头文件
#pragma  once//#include "../include/MacroDef.h"
#define	CertMaxNumber 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

const 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

这个错误有三种原因

  1. 通常是传入的DLL路径错误,找不到Dll文件,推荐使用绝对路径。
  2. 如果是在x64的node/electron下引用32位的DLL,也会报这个错,反之亦然。要确保DLL要求的CPU架构和你的运行环境相同
  3. 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同目录下的其他引用。可以通过如下方法解决:

//方法一, 调用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来绕开这个问题。

参考Issue GetLastError() always 0 when using Win32 API 参考PR github.com/node-ffi/no…

PVOID返回空

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

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

HDEVNOTIFY
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

const 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源码

extern "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

import 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函数:

//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

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

然后修改如下内容:

mainWindow.once('ready-to-show', () => {let hwnd = mainWindow.getNativeWindowHandle() //获取窗口句柄。console.log(hwnd);user32.GetSystemMenu(hwnd.readUInt32LE(0), true); //禁用系统菜单.mainWindow.show()
});

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

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

示例-获取窗口

const 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

链接:https://pan.baidu.com/s/19emkiUdbCdaPRKrY9ahyWw
提取码:i32n

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 兼容工具命令提示 输入下面命令

# 返回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(放弃)

安装

npm install win32-api --save
npm install ref-napi --save

"win32-api": "^6.2.0",
"ref-napi": "^1.4.2"

安装

npm install

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/255377.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

iOS各种设备识别码IDFA、UDID、UUID、MAC、OpenUDID、IDFV

今天在app服务端接口文档中看到2个接口名称&#xff0c;是和idfa相关的&#xff0c;就搜索了解了一下&#xff0c;顺便梳理了ios各种设备识别码。 *IDFA英文全称IdentifierforAdvertising&#xff0c;即广告标示符&#xff0c;目前是苹果生态内广告交易的主要标示符&#xff0…

RFID节点的编写

新项目开始咯&#xff0c;其实就是华清项目的换皮&#xff0c;增强了对传感器的控制加了大量的32采集与控制节点&#xff0c;由于时间紧迫先按照节点发布文章。以后有时间会发一些框架和整体数据流向分析的文章。 一、RFID节点框架 我的想法是使用超高频RFID技术&#xff0c;…

(七)DSP28335基础教程——I2C通信实验(OLED显示控制)

0 前言 本期实验目标&#xff1a;采用硬件I2C模块与OLED进行通信&#xff0c;完成显示控制。 目前发现28335比较缺乏关于I2C模块的实用例程&#xff0c;许多新手在配置使用该模块比较难上手&#xff0c;走弯路。那么通过该例程&#xff0c;大家则可以快速学会使用I2C的基本功…

安卓开发淘宝抢购界面!史上最全的Android面试题集锦,附带学习经验

前言 这是“拔剑金九银十”的第二篇文章&#xff0c;本文主要针对3年以上的Android开发者进阶面试中高级开发工程师而整理。 希望可以对你们有所帮助。不多废话&#xff0c;进入正题。 目录&#xff1a; Java中高级 计算机网络 Android高级面试—性能优化 Android优秀第三方…

Linux系统下imx6ull QT编程—— Ubuntu 下编写程序(一)

Linux QT编程 文章目录 Linux QT编程前言一、C简介二、C环境设置1.安装编译 C 语言和 C的环境。2.创建文件编写代码3.编译运行代码 总结 前言 绍在 Ubuntu 在终端窗口下使用 vi/vim 编辑一个 C源文件。通过编写最简单的示例“Hello,World QCX”。 一、C简介 C &#xff08;c…

0531最后的挣扎结束于传说中的段错误

部署训练后的缺陷检测模型 Linux Ubuntu18.04双机尝试 报错&#xff0c;Linux内核或是编译器版本不匹配&#xff0c;多次尝试更改18.04的gcc&#xff0c;g&#xff0c;gcc-arm-linux&#xff0c;garm-linux的代码&#xff0c;尝试在Makefile文件里更改编译器路径、添加LInux内…

用Python求最大公约数和最小公倍数(51)

小朋友们好&#xff0c;大朋友们好&#xff01; 我是猫妹&#xff0c;一名爱上Python编程的小学生。 和猫妹学Python&#xff0c;一起趣味学编程。 今日主题 什么是最大公约数&#xff1f; 如何用Python求最大公约数&#xff1f; 什么是最小公倍数&#xff1f; 如何用Pyt…

PDF免费压缩、在线压缩

找了很多个网站&#xff0c;发现说是免费&#xff0c;其实不然。 在线上传&#xff0c;在线压缩&#xff0c;然后直接下载&#xff0c;没有任何套路。 PDF Compressor – Compress PDF Files OnlineThis online PDF compressor allows compressing PDF files without degradi…

PDF文件怎么压缩大小

PDF文件怎么压缩大小&#xff1f;3个办法实现一键压缩 相信大家在工作中&#xff0c;会收到许多的PDF文件&#xff0c;但随着时间的推移&#xff0c;PDF文件便会越积越多&#xff0c;最终导致我们的电脑内存严重不足&#xff0c;这时候只好把一些PDF文件删除&#xff0c;来释放…

【C#图解教程】第四章 类型、存储和变量 学习笔记总结

类型 C#是一组类型声明&#xff0c;这个与第三章&#xff1a;命名空间就是一组类型声明可以一起理解。类型是一个用来创建数据结构的模板&#xff1a; 使用这个模板创建对象的过程叫做实例化&#xff0c;所以创建的对象也叫实例 类型成员 简单类型可能只包含一个数据成员&…

chatgpt赋能python:Python中的%怎么用

Python中的%怎么用 Python中的%是一个非常重要的运算符&#xff0c;也称为格式化运算符。它用于将一个值插入到另一个字符串中。在本文中&#xff0c;我们将深入了解Python中的%运算符的使用。 %运算符的语法 先看下%运算符的语法&#xff1a; string % values其中&#xf…

Mongo帮助文档

来自&#xff1a;http://xiaoshan5634.iteye.com/blog/1117702 Mongo 适合场景&#xff1a; 网站数据&#xff1a;适合实时的插入&#xff0c;更新与查询&#xff0c;并具备网站实时数据存储所需的复制 及高度伸缩性。 缓存&#xff1a;适合作为信息基础设施的缓存层。在系…

Erlang/OTP设计原则(文档翻译)

http://erlang.org/doc/design_principles/des_princ.html 图和代码皆源自以上链接中Erlang官方文档&#xff0c;翻译时的版本为20.1。 这个设计原则&#xff0c;其实是说用户在设计系统的时候应遵循的标准和规范。阅读前我一直以为写的是作者在设计 Erlang/OTP 框架时的一些原…

Spring Cloud Alibaba 参考文档-2021.0.4.0

Spring Cloud Alibaba 简介依赖管理Spring Cloud Alibaba Nacos Discovery3.1 服务注册/发现&#xff1a;Nacos Discovery3.2 如何引入Nacos Discovery进行服务注册/发现3.3 使用Nacos Discovery进行服务注册/发现和调用的示例3.3.1 Nacos 服务器启动3.3.2. 启动供应商申请3.3.…

autojs 开发文档集合

加入我们的QQ群553908361,和各作者同群交流 教程会优先发布于收费脚本v群。 该代码选自于aj开发者的文档,群里有人反馈开发文档打开慢.所以做了这个.方便搜索.如有侵权,请私信我进行删除 同时也上传了一份源码到点击网站,供大家更快的查询代码 Auto.js Pro-8.1.0 文档 索引 | …

云网络安全与数据中心安全

近年来&#xff0c;许多云架构师宣称随着公共云的采用&#xff0c;网络安全性将消亡。然而&#xff0c;网络安全仍然是最大的安全市场之一&#xff0c;并且是每个主要云服务提供商 (CSP) 在过去几年中推出重要新产品的领域。 网络对安全仍然至关重要&#xff0c;即使在云中也是…

Python中的导入模块

1&#xff0c;导入模块的的几种方式 模块是什么&#xff1f; 模块实际上就是 以.py为结尾的文件 注意点&#xff1a;自定义的模块尽量不要和系统模块重名 模块内部封装了很多实用的功能&#xff0c;有时在模块外部调用就需要将其导入&#xff0c;导入模块简单划分&#xff…

python导入模块错误-No module named XXX-图文解决方案

问题描述&#xff1a; 使用python ide执行脚本正常&#xff0c;使用python直接执行报错&#xff0c;报错“不存在对应模块” 问题原因分析&#xff1a; Q1: 等同于python脚本在ide和console中执行的区别 Q2: 因为在ide里执行脚本的时候&#xff0c;不仅会导入path环境变量中的…

python中导入模块使用哪个关键字_关于python导入模块的关键字介绍

关于python导入模块的关键字介绍 发布时间&#xff1a;2020-04-17 10:13:26 来源&#xff1a;亿速云 阅读&#xff1a;101 作者&#xff1a;小新 今天小编给大家分享的是关于python导入模块的关键字介绍&#xff0c;很多人都不太了解&#xff0c;今天小编为了让大家更加了解pyt…

【Python问题】Python 导入模块的三种方式ModuleNotFoundError: No module named ‘模块名‘

Python 导入模块的三种方式ModuleNotFoundError: No module named 模块名 问题一、Python解释器查找模块文件的过程。二、解决方法记录一下 大家可以参考这个文档的解释很清楚&#xff1a;python导入模块的三种方式超详细http://c.biancheng.net/view/4645.html 问题 写程序i…