最近公司決定進軍 Mac 市場,所以開始搞搞 Mac 下的開發。為了實現 GUI 的共用,決定使用 Electron 做界面。但我們是顯卡外接盒的應用,所以就需要把硬件控制的部分用 C++ 封裝成類庫給 Electron 調用了。這篇文章,將重點講一下這方面的應用和開發過程遇到過的問題。
使用 node-ffi 可以讓 Node.js 調用 C++ 的 Library 。在 Windows 下是 dll
,在 Mac OS 下是 dylib
,Linux 則是 so
。node-ffi 加載 Library 是有限制的,只能處理 C 風格的 Library 。也就是函數要被放在 extern "C"
里。
安裝 node-ffi 對於不同操作系統,會有不同的環境要求。具體可以參看:https://github.com/nodejs/node-gyp#installation
for electron 編譯
而對於 electron ,需要對 node-ffi 重新編譯。我們安裝 electron-rebuild 和 electron-prebuilt 進行編譯。
shell
npm i --save-dev electron-rebuild
npm i -g electron-prebuilt
設定環境變量:
bash
# Electron 的版本 export npm_config_target=1.8.4 # 要構建的 electron 類型. export npm_config_arch=x64 export npm_config_target_arch=x64 # Electron 下載地址頭,可以用鏡像. export npm_config_disturl=https://atom.io/download/electron # 告訴 node-pre-gyp 是為 Electron 編譯. export npm_config_runtime=electron # 告訴 node-pre-gyp 從源碼編譯. export npm_config_build_from_source=true # 下載的緩存路徑. HOME=~/.electron-gyp npm install
然后 rebuild:
shell
./node_modules/.bin/electron-rebuild -e /usr/local/lib/node_modules/electron-prebuilt
-e
是本地 electron-prebuilt 的絕對路徑。
載入 Library
假設有 Library 函數如下:
cpp
int add(int n1, int n2);
int div(int d1, int d2, int* r);
那么 node-ffi 應該這樣調用:
javascript
var ref = require("ref");
var ffi = require("ffi");
var intPtr = ref.refType(ref.types.int); // 創建一個 int 指針類型
var lib = ffi.Library('mylib', {
"add": [ 'int', [ 'int', 'int' ] ],
"div": [ 'int', [ 'int', 'int', intPtr ] ]
});
let sum = lib.add(1, 2);
console.log(`1 + 2 = ${sum}`); let remainder = ref.alloc(ref.types.int, 0); let quotient = lib.div(10, 3, remainder); console.log(`10 ÷ 3 = ${quotient} ...... ${remainder.deref()}`);
關於 ffi
的其他類型,可以參考:https://github.com/ffi/ffi/wiki/Types
數組參數的調用
對於參數有包含數組的函數,如:
cpp
int analysis(int number, int factor[]);
我們則需要使用到 ref-array
來創建一個數組類型加載函數:
javascript
var ffi = require("ffi");
var ArrayType = require('ref-array');
var IntArray = ArrayType(ref.types.int);
var lib = ffi.Library('mylib', {
"analysis": [ 'int', [ 'int', IntArray ] ]
});
var factors = new IntArray(new Array(100));
lib.analysis(32, factors);
console.log(`The factors of 32 are ${factors.join(',')}`);
回調函數的使用
有些 C++ Library 會包含有回調函數作為參數的調用。比如:
cpp
typedef void (*ioCallback) (int uVID, int uPID);
int device_listen(ioCallback MatchingCallback, ioCallback RemovalCallback);
node-ffi 對此有專門的用於生成回調函數參數的方法 Callback
,示例:
javascript
var ffi = require("ffi");
var lib = ffi.Library('mylib', {
'device_listen': ['int', ['pointer', 'pointer']],
});
let matchCallback = ffi.Callback('void', ['int', 'int'], (vid, pid) => {
console.log('match device, VID: ', vid, ', PID: ', pid) }); let removeCallback = ffi.Callback('void', ['int', 'int'], (vid, pid) => { console.log('remove device, VID: ', vid, ', PID: ', pid) }); lib.device_listen(matchCallback, removeCallback);
這個地方有個坑,如果你回調函數是用於持續監聽,在程序運行過程中隨時可能被調用的話(比如監聽設備插入拔出),可能會在程序啟動一段時間后,執行回調時引起程序崩潰退出。
這是因為一段時間后,回調函數被垃圾回收了。這里可以在程序最后添加:
javascript
process.on('exit', function() {
matchCallback; removeCallback; });
這樣在程序退出前都會保持引用,就不會被垃圾回收了。
原文地址:https://io.hancel.org/2018/05/02/building-an-electron-hybrid-application.html