- 蘇格團隊
- 做者:Tomey
公司項目採用Electron( electronjs.org/ )開發pc應用,會涉及到與底層硬件設備的通訊,而sdk封裝 基本上都是經過 C++ 動態連接庫dll實現的。node
以上兩種方案均可以解決dll調用問題,方案選型要我的對C++ 的掌握程度,若是熟悉C++開發,能夠直接選擇方案二最方便。若是徹底不瞭解C++,那麼只能採用方案一。python
因爲筆主不太懂C++,最終選擇第一種方案。git
(www.npmjs.com/package/ffi…github
node-ffi是使用純JavaScript加載和調用動態庫的node addon,它能夠用來在不寫任何C++代碼的狀況下調用動態連接庫的API 接口。npm
ffi究竟幹了什麼?其實它本質上仍是一個編譯後的Node addon,node_modules/ffi/build/Release/ffi_bindings.node, ffi_bindings.node就是一個addon ffi充當了nodejs和dll之間的橋樑。windows
下面是一個簡單的加載dll的demo實例:bash
var ffi = require('ffi');
var libpath = path.join(_dirname, '/test.dll');
var testLib = ffi.Library(libpath, {
'start': ['bool', ['bool']]
});
testLib.start(true); // true
複製代碼
npm install ffi
複製代碼
若是本地沒有安裝編譯node addon的環境會報錯,以下圖所示 app
不管是使用ffi,仍是直接寫node addon,都缺乏不了編譯node Addon這個步驟,要編譯node addon,有兩種方法:electron
npm install node-gyp
複製代碼
具體安裝參考:github.com/nodejs/node…函數
總結來講須要如下四點:
若是採用electron開發應用程序,electron一樣也支持node原生模塊,但因爲和官方的node 相比使用了不一樣的 V8 引擎,若是你想編譯原生模塊,則須要手動設置electron的headers的位置。
electron-rebuild爲多個版本的node和electron提供了一種簡單發佈預編譯二進制原生模塊的方法。 它能夠重建electron模塊,識別當前electron版本,幫你自動完成了下載 headers、編譯原生模塊等步驟。 一個下載 electron-rebuild 並從新編譯的例子:
npm install --save-dev electron-rebuild
# 每次運行"npm install"時,也運行這條命令
./node_modules/.bin/electron-rebuild
# 在windows下若是上述命令遇到了問題,嘗試這個:
.\node_modules\.bin\electron-rebuild.cmd
複製代碼
詳情請看 electronjs.org/docs/tutori…
這裏須要注意nodejs版本問題,nodejs平臺必須跟dll保持一致,一樣是32位或者64位,若是二者不一致,會致使調用dll失敗。
成功安裝ffi模塊以後,就能夠開始咱們下面的ffi調用dll的實例應用。
在開發需求中,須要調用基於C++編寫的TCP數據轉發服務的SDK。
首先咱們來看一下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);
}
複製代碼
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對象配置相關接口。
key對應dll頭文件中輸出的接口,例如C_EXPORT bool _cdecl CreateConnection();
value array配置參數類型,array[0]註冊接口函數返回值類型,array[1]註冊接口函數傳入形參類型。
一、基礎參數類型bool, char, short, int, long等。
二、指針類型,須要引入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');
複製代碼
三、回調函數指針pointer,能夠經過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上。
經過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);
複製代碼
文章到此結束,寫這篇文章目標主要是記錄本身經過node調用dll從無到有以的過程以及採坑記錄,文章有誤的地方,歡迎各位大佬指正~