1. 程式人生 > >關於在electron中呼叫C++動態庫的經驗總結

關於在electron中呼叫C++動態庫的經驗總結

前言
      electron以nodejs作為底層執行環境,所以自然而然就想到了他能否呼叫C++編寫的動態庫,恰好我最近在做一個關於使用electron呼叫dll的專案,也就花了一點時間去了解和實踐,這期間走了不少彎路,這裡分享出來,希望能幫助到有遇到相關問題的小夥伴

      很多剛入門不久的小夥伴第一個問題可能就是electron能不能呼叫DLL動態庫?這裡給一個明確的答覆是可以的。為什麼?因為electron本身就集成了nodejs執行環境,而nodejs又是用C++實現的,相當於C++是他的原配。
      既然能呼叫,那麼第二個問題來了,怎麼呼叫?nodejs在官網也給出了方案

addon有興趣的可以看看,但是對於我們前端來說這個方案太過於複雜了,學習成本太高,如果沒有c++開發背景還是另闢蹊徑吧,electron是可以做到和C++混合開發的,如果團隊中有C++的開發人員也可以嘗試,結合C++本身的優勢,開發出來的產品效果可能會更好,我們常用的IDE工具VSCode其實有部分功能也是使用C++來時實現的,這其中規避了一些electron的缺點。另一種呼叫DLL動態庫的方案就是這裡要重點介紹的模組node-ffi
      其實這個模組時用起來非常的簡單,先貼一段官方使用案例,簡單加了幾句註釋:

var
ffi = require('ffi');//引入ffi模組 /* *使用ffi模組將dll和js打通,可以把它看做是RPC(遠端呼叫協議) *@libm 動態庫的絕對地址例如"C://plugin/test.dll" *@ceil 動態庫中方法的名稱 double返回值的資料型別 ['double'] 這是函式輸入引數的資料型別 *這裡提一下,應為C++是是屬於強型別語言這個js不同,所以這裡一定要指定返回引數和輸入引數的型別 */ var libm = ffi.Library('libm', { 'ceil': [ 'double', [ 'double' ] ] }); //通過上面的註冊的libm物件來呼叫dll中的ceil方法
libm.ceil(1.5); // 2

      這一切看起來就是這麼簡單,但是在這幾句簡單的程式碼後面,會讓剛入門的小夥伴非常的蛋疼,因為在使用的過程中你會遇到各種問題,而且這些問題會讓你對本身的目的產生動搖,目前公開的環境能夠支援nodejs呼叫的dll的npm包好像就只有ffi,而在使用中出現的問題,貌似你嘗試了所有解決問題的方案,最終還是解決不了。我自己在搞這個的時候也弄了大概一週時間,各種環境搗騰,各種測試。最後弄出來了,這裡總結一下這個過程中遇見的問題,開啟分為這麼3點:

  1. 安裝時報錯
  2. 安裝成功但是在執行electron時呼叫ffi時報錯
  3. 打包electron時報錯

安裝過程中報錯

//全域性安裝原生模組編譯模組,編譯ffi模組時需要用到的,這裡一定要安裝
npm install node-gyp -g
//安裝ffi模組
npm install ffi --save

     &nbsp這時安裝ffi會提示Python沒有安裝,需要安裝python,我安裝的是Python2.7.13。完成之後當我興高采烈的再去安裝ffi模組,結果失望的發現下一個錯誤來了:
在這裡插入圖片描述
錯誤非常明顯,就是C++的構建環境沒有,後面也告訴了我們要怎麼來解決:

1). 就是安裝.NET Framework 2.0 。一般我們使用的windows系統本本身會自動帶上.NET Framework的,只是版本可能會有所區別而已,我們可以去看看C:\Windows\Microsoft.NET\Framework目錄下看看到底有沒安裝,安裝了什麼版本 ,一般來說這裡不需要重新再去安裝的,所以這裡的第一條建議就可以忽略了。
2). 第二條建議就是安裝Microsoft Visual Stu,熟悉C++開發的人可能對它很熟悉,對前端來所就是比較陌生了,前端可以把他理解C++的編譯環境,這裡面需要下載很多C++的元件庫,在node-gyp編譯的時候會使用到,就好像nodejs一樣,如果沒有裝nodejs環境,在使用npm進行安裝的時候,及會提示命令找不到等等。咋一看,路子好像挺對的,因為node-gyp會重新編譯node-ffi模組,所以使用到C++的環境很正常,也是因為這樣我就吭呲吭吃去安裝了,因為Visual Stu很大,有好幾個G,像我這種家裡只裝了10M頻寬人來說,你懂的。晚上睡覺的時候開始下載,第二天一早起來開始安裝,安裝好了,又開始安裝ffi模組,心裡那種期待的感覺,只有程式設計師能理解。結果沒什麼意外還是失敗。
      經過各種嘗試發現,其實我門除了安裝VS外其實還可以通過npm 安裝c++在windows環境下的構建工具windows-build-tools,好了不多說了直接開始幹

npm install --global --production windows-build-tools 

這裡可能需要等待的時間比較久一點,有點耐心,好東西都是值得等待的。
在這裡插入圖片描述
      安裝完成後再來安裝ffi模組,結果還是讓人失望,不信你看,是不是和你遇見的一樣:
在這裡插入圖片描述
在這裡插入圖片描述
      通過日誌可以看出是在node-gyp rebuild的時候出錯了,這裡也耽擱了大量的時間,因為對C++的編譯環境不熟,我一直認為是我C++的本地編譯環境不對,最後各種論壇翻遍後得出的結論是這個ffi模組有問題,其實在報錯資訊中也說的比較明白了

“ForceSet”: 不是“v8::Object”的成員

      因為不明白什麼意思,就一直在哪裡坑次坑次的瞎忙活,其實有大牛早就發現這個問題,並在ffi的master上開了一個分支來解決這個問題,我們安裝這個分支就好了

npm install [email protected]/node-ffi#torycl/forceset-fix --save

peng一碰冷水潑來
在這裡插入圖片描述
      再一次安裝失敗,不過這個問題簡單,就是git沒裝啦,我們裝一個git就行了
在這裡插入圖片描述

      終於成功了,天啊,終於安裝成功了,怎麼安裝一個npm模組費了這麼大的勁。好了不管了,能安裝成功就謝天謝地了。寫段測試程式碼測試一下,看看使用node-ffi到底能不能呼叫DLL

const ffi = require("ffi");
const User32 =  ffi.Library('user32', {
                'GetWindowLongPtrW': ['int', ['int', 'int']],
                'SetWindowLongPtrW': ['int', ['int', 'int', 'long']],
                'GetSystemMenu': ['int', ['int', 'bool']],
                'DestroyWindow': ['bool', ['int']]
            })
console.log(User32.GetWindowLongPtrW);

測試結果

E:\code workplace\test>node app.js
{ [Function: proxy] async: [Function] }

確實呼叫到了。走到這裡心裡的大石頭好像可以放下來了,畢竟解決了能不呼叫的問題。但是這僅僅是完成了一半。因為這只是在nodejs成功呼叫的dll,還沒有在electron中呼叫,事實證明兩者還是有一定區別的,下面就來講講在electron呼叫時會出現什麼問題。

在electron中使用ffi模組時報錯

      首先說一下ffi.Library載入的dll路徑問題,上面使用到的user32,部分人可能開始犯迷糊了,前面不是說這裡應該是dll檔案的地址,這個user32哪裡冒出來的,其實這是系統的dll檔案,也就是說如果我們不寫成路徑的形式,ffi模組就會自動去系統資料夾中尋找這個檔案,有明確的路徑時才會載入該路徑下的dll,可以在C:\Windows\System32和C:\Windows\SysWOW64兩個路徑下發現這兩個檔案,這兩個資料夾是分別放的是32位和64位dll。那到底是使用的那個資料夾下dll那?這裡其實就出現了另外一個我們在開發是需要注意的問題了,因為我們的應用最終在那個環境上跑這個是不確定的,所以ffi模組載入的dll在編譯的時候需要編譯32位和64位兩個版本的,我們需要在程式中判斷系統的型別:

//x64代表64位,x86代表32位
const type = process.version.arch;

將不同的編譯環境編譯的dll放在不同的目錄下,然後根據系統型別去載入不同資料夾下的dll。如果載入錯誤的dll也是會報錯的,最開始我在這上面是吃過虧的。
      正確載入dll後我們將ffi模組用在electron專案中

//dll.js
const remote = require("electron").remote;
const ffi = remote.require("ffi");
const options = {
    User32:(()=>{
        return ffi.Library('user32', {
                'GetWindowLongPtrW': ['int', ['int', 'int']],
                'SetWindowLongPtrW': ['int', ['int', 'int', 'long']],
                'GetSystemMenu': ['int', ['int', 'bool']],
                'DestroyWindow': ['bool', ['int']]
            })
    })()
};
export default options;
import Dll from "@/static/js/dll.js";
console.log("Dll:",Dll.User32.GetSystemMenu)

執行

npm run dev

我們會發現應用開啟後一片空白並不是我們希望的那樣,開啟F12除錯視窗我們會發現一堆紅色報錯
在這裡插入圖片描述

      大致的意思就是ffi載入失敗了。納尼,前面已經成功安裝了ffi模組,並且測試了一下呼叫dll沒毛病啊,咋又作妖了呢?事實上electron在使用c++模時還需要根據electron的版本等資訊重新編譯一下,這樣在electron中才能執行,我們需要進入ffi模組執行重新編譯的命令,並注入引數

node-gyp rebuild -target=1.8.7 -arch=x64 -dist-url=https://npm.taobao.org/mirrors/atom/

target:electron的版本號
arch : 計算機的架構
dist-url :檔案的下載地址,編譯的時候回去這個地址上下載一些額外的檔案,具體作用我不是很清楚。這裡使用的是國內映象,不是官方給出的地址,至於為什麼,太慢了。然後再來執行啟動命令

npm run dev

然後,沒有然後了,應用跑起來了:
在這裡插入圖片描述
這樣就完了嗎?事實證明並沒有,要正在把應用打包出來,才能算結束了。下面說一下在打包的過程中他又可能出現的問題,

打包後執行electron,呼叫dll可能出現的問題

執行

//構建
npm run build

      完成打包後,安裝應用,然後啟動,發現還是出現了前面那種頁面白屏,ffi模組呼叫失敗的問題,原因是我們在執行npm run build的時候,是通過electron-rebuild 來build的,這時會再次執行node-gyp rebuild,這裡沒有新增任何引數,所以打包出來的應用可能還是會失敗,我們可以參照electron官方的方案來解決使用node原生模組,最直接的方式就是在專案根目錄下新增一個npm安裝配置檔案.npmrc,裡面包含了一下npm的執行需要注入的引數。
.npmrc:

# Electron 的版本。
set npm config --target=1.2.3
# Electron 的系統架構, 值為 ia32 或者 x64。
set npm config --arch=x64
# 下載 Electron 的 headers。
eset npm config --disturl=https://npm.taobao.org/mirrors/atom/
# 告訴 node-pre-gyp 我們是在為 Electron 生成模組。
set npm config --runtime=electron

通過上面的一系列填坑,差不多可以成功的再electron專案中使用DLL動態庫了,並且打包出來的應用呼叫也沒有問題了。

    上面可能講的有點雜,下面稍微捋一下整個的解決流程流程:

  1. 切換npm官方映象到國內映象
npm config set registry=https://registry.npm.taobao.org
  1. 安裝Python,配置Python的系統環境變數
  2. 安裝C++構建環境,這裡有兩種方案
    1. 安裝Visual Studio
    2. 通過npm的方式安裝windows環境的的C++構建工具包
    npm install --global --production windows-build-tools
    我推薦使用第二種方式,第一種方式,到目前為止,我也沒有完全跑起來,總是再各個環節會出現不同的問題。
  3. 安裝forceset問題已修復的ffi模組分支。
    npm install [email protected]/node-ffi#torycl/forceset-fix --save

      順便提一下,安裝的的使用一定要加–save,不能是–save-dev或不加,因為在build的時候,會將package.json裡dependencies下的所有檔案都打包到asar檔案中去,否則在應用中呼叫ffi模組的時候也會出現模組找不到的問題。

  1. 進入下載好的ffi模組中去重新編譯一下ffi模組,到此你就可以執行開發環境了
node-gyp rebuild -target=1.8.7 -arch=x64 -dist-url=https://npm.taobao.org/mirrors/atom/
  1. 再專案根目錄下建立npm執行環境配置引數檔案.npmrc
# Electron 的版本。
set npm config --target=1.2.3
# Electron 的系統架構, 值為 ia32 或者 x64。
set npm config --arch=x64
# 下載 Electron 的 headers。
eset npm config --disturl=https://npm.taobao.org/mirrors/atom/
# 告訴 node-pre-gyp 我們是在為 Electron 生成模組。
set npm config --runtime=electron

      通過上面這一系列的操作。基本上也就可以成功呼叫DLL動態庫了。據說這種方式比起官方給出的方式效能上有些損失,具體損失到什麼程度,我這邊還沒有去測過,也沒有明顯感覺出來。這種方式的優勢就是使用簡單,開發成本低,而且一般的需求也是能夠滿足的,如果團隊有足夠的資源,也可以去嘗試nodejs官方給出的方式,到時候教教我。

總結:
      上面寫了很多,其實幹貨感覺好像就是上面最後這幾條,寫前面的過程主要是記錄一下我在解決這個問題是遇到的各個問題,然後怎麼一步一步去尋找解決方案的,很多小夥伴在使用的過程中可能也會遇見相同的問題,關鍵在於我們遇見問題後不要輕易去下這個問題能不能解決的結論,多嘗試幾種方式,論壇,度娘,谷歌上面多查查,即使查出來的這條資訊最終可能並沒什麼用,但是多嘗試幾次,自己大概也就知道這個問題出現的原因,可能也會形成相關解決的思路,也許還會有意外的收穫。
      最後總結一句:多嘗試,多動手,多思考。
      忽略"通假字"…