существуетnodejs
/elctron
, пройти можноnode-ffi,пройти черезForeign Function Interface
Вызов библиотеки динамической компоновки, обычно известный как вызов DLL, реализует вызов кода C/C++, чтобы реализовать многие функции, которые node не легко реализовать, или повторно использовать многие функции, которые были реализованы.
node-ffi — это плагин Node.js для загрузки и вызова динамических библиотек с использованием чистого JavaScript. Его можно использовать для создания привязок к собственным библиотекам DLL без написания кода на C++. Он также обрабатывает преобразования типов в JavaScript и C.
а такжеNode.js Addons
По сравнению с этим методом, этот метод имеет следующие преимущества:
1. 不需要源代码。
2. 不需要每次重编译`node`,`Node.js Addons`引用的`.node`会有文件锁,会对`electron应用热更新造成麻烦。
3. 不要求开发者编写C代码,但是仍要求开发者具有一定C的知识。
слабость это:
1. 性能有折损
2. 类似其他语言的FFI调试,此方法近似黑盒调用,差错比较困难。
Установить
node-ffi
пройти черезBuffer
класс, который реализует совместное использование памяти между кодом C и кодом JS, а преобразование типов выполняется черезref,ref-array,ref-structвыполнить. из-заnode-ffi
/ref
Содержит собственный код C, поэтому при установке необходимо настроить среду компиляции собственных подключаемых модулей Node.
// 管理员运行bash/cmd/powershell,否则会提示权限不足
npm install --global --production windows-build-tools
npm install -g node-gyp
Установите соответствующие библиотеки по мере необходимости
npm install ffi
npm install ref
npm install ref-array
npm install ref-struct
еслиelectron
проект, проект может установить плагин для восстановления электронов, который можно легко пройтиnode-modules
все необходимоеrebuild
Библиотека перекомпилирована.
npm install electron-rebuild
Настройте ярлыки в package.json
package.json
"scripts": {
"rebuild": "cd ./node_modules/.bin && electron-rebuild --force --module-dir=../../"
}
выполнить послеnpm run rebuildоперация завершенаelectron
перекомпилировать.
Простой пример
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);
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
})
тип переменной
В языке C есть 4 основных типа данных: целочисленный, с плавающей запятой, указатель и агрегатный тип.
Основание
Существует два типа целочисленных и символьных типов: со знаком и без знака.
Типы | Минимальный диапазон |
---|---|
char | 0 ~ 127 |
signed char | -127 ~ 127 |
unsigned char | 0 ~ 256 |
По умолчанию используется подписанный тип, если unsigned не объявлен
ref
серединаunsigned
будет сокращено доu
, Такие какuchar
вести перепискуusigned char
.
с плавающей запятойfloat
double
long
double
.
ref
Библиотека подготовила для нас соответствующие отношения базовых типов.
типы С++ | 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 байта в кодировке и 3~4 байта в UTF-8. Один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
const iconv = require('iconv-lite')
const cstr = iconv.encode(str, 'gbk')
Уведомление! После перекодирования с помощью encodecstr
дляBuffer
класс, который может быть непосредственно использован какuchar
Типы
В iconv.encode(str.'gbk') gbk использует по умолчанию
unsigned char | 0 ~ 256
хранить. Если код C должен бытьsigned char | -127 ~ 127
, вам нужно преобразовать данные в буфере в тип int8.
const Cstring100 = refArray(ref.types.char, 100)
const cString = new Cstring100()
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'
Рекомендуется только «строка».
Хотя в js строки считаются базовыми типами, в языке C они представлены в виде объектов, поэтому считаются ссылочными типами. такstringФактическиchar* вместоchar
Тип агрегации
Многомерные массивы
Если вы столкнулись с базовым типом, определенным как многомерный массив, вам нужно использовать ref-array для его создания.
char cName[50][100] // 创建一个cName变量储存级50个最大长度为100的名字
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
создавать
typedef struct {
char cTMycher[100];
int iAge[50];
char cName[50][100];
int iNo;
} Class;
typedef struct {
Class class[4];
} Grade;
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
, изменитBuffer
изprototype
, замените и внедрите некоторые методы, обратитесь к документациисправочная документация
const 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
Чтобы было понятно о двух концепциях, одна — это тип структуры, а другая — тип указателя, который объясняется в коде.
// 申明一个类的实例
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
process.on('exit', () => {
/* eslint-disable-next-line */
uiInfocallback // keep reference avoid gc
})
пример кода
Приведите пример полной цитаты
// 头文件
#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);
}
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
Есть три причины этой ошибки
- Обычно входящий путь DLL неверен, и файл DLL не может быть найден, рекомендуется использовать абсолютный путь.
- если на х64
node
/electron
Следующая ссылка на 32-разрядную DLL также сообщит об этой ошибке, и наоборот. Убедитесь, что DLL требует той же архитектуры ЦП, что и среда выполнения. - Библиотека DLL также ссылается на другие файлы DLL, но указанный файл DLL не может быть найден. Это может быть библиотека зависимостей VC или отношение зависимости между несколькими библиотеками DLL.
- Ошибка динамической компоновки: ошибка Win32 127: Функция с соответствующим именем не найдена в DLL.Необходимо проверить, совпадает ли имя функции, определенное в заголовочном файле, с именем функции, записанным при вызове DLL.
Настройка пути
Если у вас несколько 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}`
Инструмент анализа DLL
Инструменты, которые могут просматривать всю информацию о библиотеках ссылок DLL и зависимостях DLL, но, к сожалению, не поддерживаютWIN10
. если вы неWIN10
Пользователи, тогда вам нужен только этот один инструмент, а следующие инструменты можно пропустить.
Вы можете просматривать различные операции во время выполнения процесса, такие как ввод-вывод, доступ к реестру и т. д. используйте его здесь для мониторингаnode
/electron
Обработка операций ввода-вывода для устранения неполадокDynamic Linking Error: Win32 error
Причина ошибки 3, вы можете просмотретьffi.Libary
Все запросы ввода-вывода и соответствующие результаты в то время, чтобы увидеть, чего не хватаетDLL
.
dumpbin.exe — это двоичный преобразователь Microsoft COFF, который отображает информацию о двоичных файлах Common Object File Format (COFF). Объектные файлы COFF, стандартные объектные библиотеки COFF, исполняемые файлы и библиотеки динамической компоновки можно проверить с помощью dumpbin. Запуск через меню «Пуск» -> Visual Studio 20XX -> Инструменты Visual Studio -> Собственная командная строка VS20XX x86.
dumpbin /headers [dll路径] // 返回DLL头部信息,会说明是32 bit word Machine/64 bit word Machine
dumpbin /exports [dll路径] // 返回DLL导出信息,name列表为导出的函数名
проблема со сбоем флешки
действительныйnode-ffi
При отладке легко могут возникнуть сбои флэш-памяти из-за ошибок памяти, и даже точки останова могут вызвать сбои. Это часто вызвано несанкционированным доступом к памяти, к которому можно получить доступ черезWindows
Журнал видит сообщение об ошибке, но поверьте мне, это не помогает. Ошибки памяти в C — непростая проблема.
приложение
инструмент автоматического преобразования
tjfontaine предоставилnode-ffi-generate, который может быть сгенерирован автоматически на основе заголовочного файлаnode-ffi
Объявление функции, обратите внимание на эту необходимостьLinux
Окружающая среда, просто используйте KOA, чтобы упаковать слой и перевести его в онлайн-режим.ffi-online
WINAPI
node-win32-apiwindef.h
GetLastError
node-ffi
GetLastError
C++ addon
GetLastError() always 0 when using Win32 API
FFFFFFFF
pvoid
node-ffi
FFFFFFFF
deref()
HDEVNOTIFY
WINAPI
RegisterDeviceNotificationA(
_In_ HANDLE hRecipient,
_In_ LPVOID NotificationFilter,
_In_ DWORD Flags);
HDEVNOTIFY hDevNotify = RegisterDeviceNotificationA(hwnd, ¬ifyFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
if (!hDevNotify) {
DWORD le = GetLastError();
printf("RegisterDeviceNotificationA() failed [Error: %x]\r\n", le);
return 1;
}
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)
}