Как Nodejs вызывает модули Dll

Node.js внешний интерфейс C++ Electron
  • суг команда
  • Автор: Томей

1. Зачем вам вызывать dll с помощью node.js?

Проект компании принимает Электрон (electronjs.org/) Разработка приложений для ПК будет включать связь с базовыми аппаратными устройствами, а упаковка SDK в основном реализуется через DLL-библиотеку динамической компоновки C ++.

Есть два варианта на выбор:

  • Вариант 1: Используйте node-ffi
  • Вариант 2. Напишите надстройку узла на C++ и вызовите dll через LoadLibrary.

Обе из двух вышеперечисленных схем могут решить проблему вызова dll.Выбор схемы требует личного владения С++.Если вы знакомы с разработкой на С++, то можете сразу выбрать вторую схему. Если вы вообще не знаете C++, вы можете использовать только вариант 1.

Поскольку автор мало что знает о C++, в итоге я выбрал первый вариант.

2. Что такое node-ffi?

(woohoo. эта лошадь plus.com/package/method-i…

node-ffi — надстройка узла, использующая чистый JavaScript для загрузки и вызова динамических библиотек.Его можно использовать для вызова API-интерфейсов динамически подключаемых библиотек без написания кода C++.

Что именно сделал фи? По сути, это скомпилированный аддон Node, node_modules/ffi/build/Release/ffi_bindings.node, ffi_bindings.node — аддон ffi действует как мост между nodejs и dll.

Ниже приведен простой демонстрационный пример загрузки dll:

var ffi = require('ffi');
var libpath = path.join(_dirname, '/test.dll');
var testLib = ffi.Library(libpath, {
	'start': ['bool', ['bool']]
});

testLib.start(true); // true

3. Установите узел-ffi

npm install ffi

Если среда для компиляции надстройки узла не установлена ​​локально, будет сообщено об ошибке, как показано на следующем рисунке.

image

Независимо от того, используете ли вы ffi или пишете надстройку узла напрямую, этап компиляции надстройки узла обязателен.Чтобы скомпилировать надстройку узла, есть два способа:

1. узел-гип(Уууу, эта лошадь plus.com/package/nod…).

npm install node-gyp

Конкретная ссылка на установку:GitHub.com/node будет /node…

Таким образом, необходимы четыре пункта:

  • Между python 2.7-3.0 (рекомендуется установить v2.7, v3.x.x не поддерживается)
  • NET Framework 4.5.1
  • Инструменты компиляции Visual C++ (в Windows вам не нужно устанавливать VS. Если вы устанавливаете VS2015 самостоятельно, это приведет к ошибке компиляции MSB4132: версия инструментов «2.0» не распознана. Доступные версии инструментов — «4.0». Эта проблема значит неправильно установлен компилятор, или компилятор неправильно распознан, Документация node-gyp рекомендует использовать конфигурационный набор npm msvs_version 2015, но некоторые машины недействительны, даже если он установлен, вам нужно установить msvs_version вручную, это должно быть написано так: node-gyp reboot --msvs_version=2015. Если вы не можете нормально скомпилировать, потому что установлен VS2015, вы можете напрямую восстановить точку восстановления перед установкой VS)
  • Конфигурация переменной среды. (Примечание. Место установки Python необходимо добавить в переменную среды)

2. электрон-перестроить (Ууууу, эта лошадь plus.com/package/hungry…)

Если вы используете электрон для разработки приложений, электрон также поддерживает собственные модули узла, но, поскольку он использует другой движок V8 по сравнению с официальным узлом, если вы хотите скомпилировать собственные модули, вам нужно вручную установить расположение заголовков электрона.

electronic-rebuild предоставляет простой способ распространения предварительно скомпилированных двоичных нативных модулей для нескольких версий узла и электрона. Он может перестроить электронный модуль, определить текущую электронную версию и автоматически выполнить шаги загрузки заголовков и компиляции собственных модулей для вас. Пример загрузки электронной сборки и перекомпиляции:

npm install --save-dev electron-rebuild
  
# 每次运行"npm install"时,也运行这条命令
./node_modules/.bin/electron-rebuild

# 在windows下如果上述命令遇到了问题,尝试这个:
.\node_modules\.bin\electron-rebuild.cmd

Подробнее см.электрон JS.org/docs/tutor i…

Здесь необходимо обратить внимание на проблему версии nodejs.Платформа nodejs должна быть согласована с dll, которая также является 32-битной или 64-битной.Если они несовместимы, вызов dll завершится ошибкой.

После того, как модуль ffi успешно установлен, мы можем запустить следующий пример приложения ffi, вызывающего dll.

4. Примеры применения

В требованиях к разработке необходимо вызвать SDK на основе сервиса пересылки данных TCP, написанного на C++.

Во-первых, давайте посмотрим на код объявления интерфейса файла заголовка dll следующим образом:

#ifndef JS_CONNECTION_SDK
#define JS_CONNECTION_SDK


#ifdef JS_SDK
#define C_EXPORT __declspec(dllexport)
#else
#define C_EXPORT __declspec(dllimport)
#endif


extern "C"
{
    typedef void(*ReceiveCallback) (int cmd, int seq, const char *data);

    /*设置读取数据回调*/
    C_EXPORT void _cdecl SetReceiveCallback(ReceiveCallback callback);

    /*
    *设置option
    */
    C_EXPORT void _cdecl SetOption(
        const char* appKey, 
        const char* tk,
        int lc, 
        int rm
    );

    /*
    *创建连接
    */
    C_EXPORT bool _cdecl CreateConnection();

    /*发送数据*/
    C_EXPORT bool _cdecl SendData(int cmd, int seq, const char *data, unsigned int len);

    /*释放连接*/
    C_EXPORT void _cdecl ReleaseConnection();
}

#endif

ffi вызывает инкапсуляцию модуля dll, код выглядит следующим образом:

try {
	const ffi = require('ffi');
	const path = require('path');
	const Buffer = require('buffer').Buffer;
	const libpath = path.join(APP_PATH, '..', '..', '/testSDK.dll');
	
	const sdkLib = ffi.Library(libpath, {
		'CreateConnection': ['bool', []],
		'SendData': ['bool', ['int', 'int', 'string', 'int']],
		'ReleaseConnection': ['void', []],
		'SetOption': ['void', ['string', 'string', 'int', 'int']],
		'SetReceiveCallback': ['void', ['pointer']]
	});
	
	module.exports = {
		createConnection: function(){
			sdkLib.CreateConnection();
		},
		setReceiveCallback(cb) {
			global.setReceiveCallback = ffi.Callback('void', ['int', 'int', 'string'], function(cmd, seq, data){
				cb && cb(cmd, seq, data && JSON.parse(data));
			});
			sdkLib.SetReceiveCallback(global.setReceiveCallback);
		},
		sendData: function(cmd, seq, data){
			data = JSON.stringify(data);
			sdkLib.SendData(cmd, seq, data, data.replace(/[^\x00-\xff]/g, '000').length, 0);
		},
		releaseConnection: function(){
			sdkLib.ReleaseConnection();
		},
		setOption: function (option) {
			sdkLib.SetOption(
				option.appKey,
				option.tk,
				option.lc,
				option.rm
			);
		}
	}	
} catch (error) {
	log.info(error);
}

Первый шаг: прописываем dll интерфейс через ffi

	const sdkLib = ffi.Library(libpath, {
		'CreateConnection': ['bool', []],
		'SendData': ['bool', ['int', 'int', 'string', 'int']],
		'ReleaseConnection': ['void', []],
		'SetOption': ['void', ['string', 'string', 'int', 'int']],
		'SetReceiveCallback': ['void', ['pointer']]
	});
	

В методе ffi.Library первый параметр передается в пути к dll, а второй параметр — это интерфейс, связанный с конфигурацией объекта JSON.

Ключ соответствует выводу интерфейса в заголовочном файле dll, например C_EXPORT bool _cdecl CreateConnection();

Массив значений настраивает тип параметра, массив [0] регистрирует тип возвращаемого значения функции интерфейса, а массив [1] регистрирует тип входящего параметра функции интерфейса.

1. Основные типы параметров: bool, char, short, int, long и т. д.

2. Для типов указателей необходимо ввести модуль ref следующим образом:

var ref = require('ref');
var intPointer = ref.refType('char');
var doublePointer = ref.refType('short');
var charPointer = ref.refType('int');
var stringPointer = ref.refType('long');
var boolPointer = ref.refType('bool');

3. Указатель указателя функции обратного вызова, который может быть создан с помощью ffi.Callback, выглядит следующим образом:

global.setReceiveCallback = ffi.Callback('void', ['int', 'int', 'string'], function(cmd, seq, data){
		cb && cb(cmd, seq, data && JSON.parse(data));
	});
sdkLib.SetReceiveCallback(global.setReceiveCallback);

Конфигурация типа параметра функции обратного вызова аналогична конфигурации типа параметра интерфейса dll, поэтому я не буду здесь больше говорить.

Здесь следует отметить, что функция обратного вызова может быть утилизирована автоматическим механизмом сборки мусора JavaScript, поэтому я здесь, чтобы смонтировать функцию обратного вызова в глобальный объект global.

Шаг 2: вызов интерфейса

Регистрация интерфейса через ffi.Library(libpath, {...}) может напрямую вызывать интерфейс стыковки через возвращаемый объект sdkLib. Например:

var bool = sdkLib.CreateConnection();
console.log(bool); // true or false;

var cmd = 0, seq = 0, data = {...};
var dataStr = JSON.stringify(data);
// JavaScript中文字符长度在C++中长度计算要*3
sdkLib.SendData(cmd, seq, data, data.replace(/[^\x00-\xff]/g, '000').length);

global.setReceiveCallback = ffi.Callback('void', ['int', 'int', 'string'], function(cmd, seq, data){
	cb(cmd, seq, data && JSON.parse(data));
});
sdkLib.SetReceiveCallback(global.setReceiveCallback);

На этом статья окончена.Цель написания данной статьи -зафиксировать процесс вызова dll с нуля через ноду и запись майнинговых ям.Если в статье что-то не так, прошу меня поправить~