# 🚀node调用DLL

📅 2024/7/8

# 环境清单

system:win10
nodejs:32位 14.4.0
python:2.7.18
node-gyp:6.1.0
pkg:4.5.1
ffi-napi:4.0.3
pkg使用的node:32位 14.4.0
Visual Studio Installer 2017:C++ 开发组件
nvm 管理node版本(非必须)
上述环境为我通过node调用DLL所摸索出来的整套适配win7 32位系统的版本,兼容性较好,可供参考。如果在安装和编译过程中出现无法解决的异常可尝试更换我提供的环境版本。

# 安装python、C++ 开发组件

node调用DLL所使用的ffi-napi需要通过python进行编译,安装 Python 和 C++开发组件 是为了能够创建一个可以与 Node.js 交互的中间层。由于ffi-napi已经很长时间没有更新了,并且其中有对于vc版本的判断,因此版本太新可能导致报错“找不到vc”,这里建议使用2015或2017版本。网上有些文章推荐使用windows-build-tools来安装必备的环境,这里我不推荐,因为我在尝试安装windows-build-tools过程中也会由于各种原因导致安装失败,我认为直接安装环境的方式更为稳妥与清晰。

# 安装node-gyp、ffi-napi

ffi-napi为调用DLL的核心库,node-gyp是一个用于编译 Node.js原生模块的工具。其实就是node-gyp将ffi-napi编译为可以提供DLL调用方法的node原生addon模块。其中node-gyp 可以全局安装。iconv-lite 用于进行编码转换。

npm i node-gyp -g
npm i ffi-napi
npm i iconv-lite

# 测试DLL调用

上述全部正确安装后,可以通过调用windows系统的user32.dll来进行测试。ffi.Library用于加载DLL,注意加载的的DLL必须要与使用的node同为32位或64位。

/*ffi.Library('DLL路径',{
    'DLL中的方法':['返回值类型',['参数1类型','参数2类型',参数3类型',...]]
})
参数类型包含在ref-napi这个包中,在安装ffi-napi时自动安装了,其中与C/C++语言中的类型可以参考类型对照表。常见的类型都提供了简写:例如:'void','string','int','char',也可以自定义类型,详细情况可以参考https://github.com/node-ffi-napi/ref-napi
*/
const ffi = require('ffi-napi')
const dll = ffi.Library('user32.dll', {
    'MessageBoxA': ['int', ['int', 'string', 'string', 'int']]
})
//iconv.encode()是对显示的信息进行了GBK转码,防止汉字出现乱码的情况。调用以下方法应该会出现系统弹窗。
 dll.MessageBoxA(0, iconv.encode('你好,DLL', 'GBK'), iconv.encode('nodejs', 'GBK'), 0)

类型对照表

C++类型 ref对应类型
void ref.types.void
int8_t ref.types.int8
uint8_t ref.types.uint8
int16_t ref.types.int16
uint16_t 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

# 指针类型

在使用过程最常遇到的为返回值为字符串指针的情况,这时候返回值如果定义为string会发现只能接收到第一个字符,这时候我们就需要定义一个字符串指针去接收返回结果。安装ref-array-napi用来定义数组。ref.refType用来将数组转换为字符串指针。

npm i ref-array-napi
const ref = require('ref-napi')
const ArrayType = require('ref-array-napi')
const stringPointer = ref.refType(ArrayType('char', 1024))

这类定义了一个包含1024个字符的内存空间用于存储返回的字符串结果,具体大小可以根据需求自行调整,这样我们只要把返回类型设置为stringPointer即可接收到返回的字符串,但是由于node时utf-8编码,需要根据DLL的编码方式通过iconv进行转换,解决返回结果乱码问题。其他类型的指针也都可以通过ref.refType()进行转换。

# 字符串截断

在使用上述stringPointer类型的过程中可能会出现由于分配的内存空间比较大,导致接收到的字符串正确结果后会跟着一些乱码,这时候我们就需要将返回的结果转换为buffer,根据二进制数据进行截断,我遇到情况为通过两个连续的0 进行截断,这类我不清楚是否都需要这样,但是遇到此类情况,可以以此为参考。

# 字符串作为参数传递

在进行传参过程中,需要将参数通过iconv进行编码以达到与DLL编码格式的同一,除此之外,可能还需要在字符串后面加上\u0000来进行传递。

# 结构体

对于结构体,我暂时没有用到,但是有需要的可以使用ref-struct-napi。

# 调用DLL常见错误

*.node is not a valid Win32 application
依赖库和目标应用类型不一致,需要在对应的node环境下对node addon 进行重新编译。 类似ffi-napi等模块可能会出现此类问题。 Error: Dynamic Linking Error: Win32 error 193
1.调用的动态库dll是32位的,而目标模块需要的是64位的。2.缺少依赖库
Error: Dynamic Linking Error: Win32 error 126
路径问题,很可能就是没有找需要的dll动态库。
Error: Dynamic Symbol Retrieval Error: Win32 error 127
一般是因为调用的DLL没有导出方法,从而调用DLL的时候找不到对应的方法。

# 参考文档

ref-napi (opens new window)

ffi-napi (opens new window)

ref-array-napi (opens new window)

ref-struct-napi (opens new window)

ref API (opens new window)