Electron 桌面程序讀取dll動態庫

序幕:被GFW狙擊的第一次構建

當我在工位上輸入npm install electron時,控制臺跳出的紅色警報如同數字柏林墻上的一道彈痕:

Error: connect ETIMEDOUT 104.20.22.46:443

網絡問題不用愁,請移步我的另外文章進行配置:

electron 客戶端 windows linux(麒麟V10)多系統離線打包 最新版 <一>_electron linux 離線打包-CSDN博客

第一章:構建electron-builder

builder排除文件夾,簡單配置如下(package.json中):

"build": {"appId": "com.example.win7app","win": {"target": "nsis","defaultArch": "ia32"},"extraFiles": [{"from": "resources","to": "Resources","filter": ["**/*"]}],"nsis": {"oneClick": false,"allowToChangeInstallationDirectory": true}},

我們需要在這個文件夾中讀取dll文件,同時希望它打包后在安裝目錄下。

第二章:跨維度通信協議——主進程與渲染進程的量子糾纏

根目錄添加preload.js,添加如下代碼:


// preload.js
const { contextBridge, ipcRenderer } = require('electron');// 向渲染進程暴露安全的 API 方法
contextBridge.exposeInMainWorld('electronAPI',{// 示例:調用 Node.js 文件系統 APIreadFile: async (path) => {const fs = await import('fs/promises');return fs.readFile(path, 'utf-8');},// 示例:進程間通信(IPC)openDialog: () => ipcRenderer.invoke('dialog:open'),// 檢查是否存在加密狗并且是否匹配成功checkIfLock: () => ipcRenderer.invoke('checkIfLock'),captureUKey: () => ipcRenderer.invoke('captureUKey'),// 關閉窗口closeWindow: () => ipcRenderer.invoke('closeWindow'),// 監聽打開設置onAction: (callback) => {ipcRenderer.on('renderer-action', (event, arg) => callback(arg))},}
)

然后再mainjs(electron主進程)中配置文件:

let mainWindow, tray = null;
function createWindow() {mainWindow = new BrowserWindow({width: 800,height: 600,webPreferences: {preload: path.join(__dirname, 'preload.js'), // 指定預加載腳本contextIsolation: true, // 開啟上下文隔離(安全必備)nodeIntegration: false  // 禁用直接 Node.js 訪問}})// 隱藏菜單mainWindow.setMenu(null);// 加載本地頁面(開發時可替換為本地服務地址,如 http://localhost:3000)mainWindow.loadFile(path.join(__dirname, 'src', 'index.html'));// 窗口關閉事件處理mainWindow.on('close', (event) => {if (!app.isQuiting) {event.preventDefault()const choice = dialog.showMessageBoxSync(mainWindow, {type: 'question',buttons: ['直接退出', '最小化到托盤'],title: '確認',message: '您要如何操作?',defaultId: 1})if (choice === 0) {app.isQuiting = trueapp.quit()} else {mainWindow.hide()}}})
}

最后在html中使用上述方法(html中使用):

<script>//【IPC通信】檢測開關(true false)
const checkIfLock = window.electronAPI.checkIfLock;
const checkArm = window.electronAPI.captureUKey;
const closeWindow = window.electronAPI.closeWindow;
// 打開設置
window.electronAPI.onAction(({ type, data }) => {switch(type) {case 'openSettings':showSetting()break;}
})// 顯示設置
function showSetting () {try {var remortroot = localStorage.dpm_root;var remortport = localStorage.dpm_port;if (remortroot != null) {$("#remortroot").val(remortroot);}if (remortport != null) {$("#remortport").val(remortport);}$('#myModal').modal('show');} catch (err) {$('#myModal').modal('show');}
}// ===========================================
var timeout;
var lockState = false;
$(function () {checkIfLock().then(res => {lockState = res;})//初始化設置var remortroot = localStorage.dpm_root;//服務器IP地址var remortport = localStorage.dpm_port;//端口號if (remortroot == null || remortroot == "" || remortroot == "undefined"|| remortroot == null || remortroot == "" || remortroot == "undefined") {var err = "首次登錄,請填寫網絡配置";showConfirmMsg(err, function (r) {if (r) {$('#myModal').modal('show');}});} else {loadLoginPage(remortroot, remortport);}
});function isPort(str) {var parten = /^(\d)+$/g;if (parten.test(str) && parseInt(str) <= 65535 && parseInt(str) >= 0) {return true;} else {return false;}
}
function isIP(strIP) {var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/gif (re.test(strIP)) {if (RegExp.$1 < 256 && RegExp.$2 < 256 && RegExp.$3 < 256 && RegExp.$4 < 256) return true;}return false;
}
function sendUrlPort(remortroot, remortport) {var err = "";if (isIP(remortroot)) {} else {err += "服務器地址無效,";}if (isPort(remortport)) {} else {err += "端口號無效,";}if (err != "" && err.length > 0) {err = err.substring(0, err.length - 1);showConfirmMsg(err, function (r) {if (r) {$('#myModal').modal('show');}});} else {layer.msg('正在加載登錄頁面,請稍候。。。', {icon: 16,shade: 0.01,time: 5000000,shadeClose: false});var dpmHid = $("#eam_hid").text();var ifUd = "";if (false == lockState) {//false代表 u盾登錄 需要驗證uidifUd = "xxx"}var url = "https://" + remortroot + ":" + remortport$.ajax({url: url,type: 'GET',timeout: 100000,datatype: "json",complete: function (response, textStatus) {//啟動u盾檢測timeout = setInterval("testUd()", 3000);layer.closeAll();if (response.status == 200) {localStorage.dpm_root = remortroot;localStorage.dpm_port = remortport;$('#github-iframe').attr('src', url);} else if (textStatus == 'timeout') {showConfirmMsg('未能成功連接系統(超時),請檢查網絡配置或聯系管理員!', function (r) {if (r) {$('#myModal').modal('show');}});} else {showConfirmMsg('未能成功連接系統,請檢查網絡配置或聯系管理員!', function (r) {if (r) {$('#myModal').modal('show');}});}}});}
}//啟動時:加載登錄頁,并判斷u端是否存在
function loadLoginPage(remortroot, remortport) {checkArm().then(res => {if (true == lockState) {if (false == res.flag) {$("#eam_hid").text("");}sendUrlPort(remortroot, remortport);} else {if (true == res.flag) {$("#eam_hid").text(res.randomNum);sendUrlPort(remortroot, remortport);} else {showLongErrorMsg("未插入U盾");}}})}
//啟動后 監測u盾是否插入,未插入則退出系統
function testUd() {checkArm().then(res => {if (true == lockState) {if (false == res) {$("#eam_hid").text("");}} else {if (false == res) {clearInterval(timeout);showLongErrorMsg("未插入U盾");}}})
}
function showConfirmMsg(msg, callBack) {art.dialog({id: 'confirmId',title: '系統提示',content: msg,icon: 'warning',background: '#000000',opacity: 0.1,lock: true,button: [{name: '確定',callback: function () {callBack(true);},focus: true}]});
}
//錯誤提示
function showErrorMsg(msg) {top.art.dialog({id: 'errorId',title: '系統提示',content: msg,icon: 'error',time: 5,background: '#000',opacity: 0.1,lock: true,okVal: '關閉',ok: true});
}
function showLongErrorMsg(msg) {top.art.dialog({id: 'errorId',title: '5秒后自動關閉客戶端...',content: msg,icon: 'error',time: 5,background: '#000',opacity: 0.1,lock: true,cancelVal: '關閉',cancel: function () {closeWindow();},close: function () {closeWindow();}});
}//彈出框事件 
$("#initsetbtn").click(function () {var remortroot = $("#remortroot").val();var remortport = $("#remortport").val();$('#myModal').modal('hide');loadLoginPage(remortroot, remortport);
});</script>

第三章:調用dll

調用dll推薦使用koffi。另一篇文章也有說明:

Electron馴龍記:在Win7的廢墟上喚醒32位DLL古老巨龍-CSDN博客

示例代碼:
?

// 在mainjs中// 是否捕獲U盾
ipcMain.handle('captureUKey', () => {return new Promise((resolve, reject) => {let promiseAry = [];var count = 0;while (count++ < 20) { //連續20次都失敗 才認為失敗promiseAry.push(checkOnceUKey());}Promise.all(promiseAry).then((results) => {console.log('所有檢查結果:', results);if (Array.isArray(results) && results.length > 0) {resolve({flag: results.filter(item => item.flag == false).length == 0 ? true: false,randomNum: results[results.length - 1].randomNum || ''})} else {resolve({flag: false,randomNum: ''})}}) }).catch(error => {console.error('是否捕獲U盾出錯:', error);return false; // 讀取失敗})
})
// 單次檢查U盾
function checkOnceUKey() {return new Promise((resolve, reject) => {let flag = false;let randomNum = ''; // 隨機數// 常量定義const DONGLE_SUCCESS = 0;const koffi = require('koffi');// 加載 DLLconst dllPath = path.join(getDataPath(), 'Dongle_d.dll');const dongleLib = koffi.load(dllPath);// 定義結構體字段偏移量(單位:字節)const InfoStructOffsets = {m_Ver: 0,m_Type: 2,m_BirthDay: 4,m_Agent: 12,m_PID: 16,m_UserID: 20,m_HID: 24,m_IsMother: 32,m_DevType: 36};const InfoStructSize = 40;const Dongle_Enum = dongleLib.func('int Dongle_Enum(void*, int*)');const Dongle_Open = dongleLib.func('int Dongle_Open(int*, int)');const Dongle_ResetState = dongleLib.func('int Dongle_ResetState(int)');const Dongle_GenRandom = dongleLib.func('int Dongle_GenRandom(int, int, void*)');const Dongle_Close = dongleLib.func('int Dongle_Close(int)');// 初始化緩沖區const dongleInfo = Buffer.alloc(1024); // 假設最多 25 個設備(1024 / 40 ≈ 25)const countBuffer = Buffer.alloc(4);countBuffer.writeInt32LE(0, 0);// 1?? 枚舉設備let result = Dongle_Enum(dongleInfo, countBuffer);console.log(`** Dongle_Enum **: 0x${result.toString(16).padStart(8, '0')}`);if (result !== DONGLE_SUCCESS) {console.error(`** Enum errcode **: 0x${result.toString(16).padStart(8, '0')}`);flag = false;}const deviceCount = countBuffer.readInt32LE(0);console.log(`** Find Device **:  ${deviceCount}`);if (deviceCount === 0) {console.log('** No Device **');flag = false;}// 3?? 打開設備const handleBuffer = Buffer.alloc(4);result = Dongle_Open(handleBuffer, 0);const handle = handleBuffer.readInt32LE(0);console.log(`** Dongle_Open **: 0x${result.toString(16).padStart(8, '0')}`);if (result !== DONGLE_SUCCESS) {console.error(`** Open Failed **`);flag = false;} else {console.log(`** Open Success **: [handle=0x${handle.toString(16).padStart(8, '0')}]`);randomNum = `0x${handle.toString(16).padStart(8, '0')}`;Dongle_Close(handle);flag = true;}// 4?? 重置 COS 狀態/*result = Dongle_ResetState(handle);console.log(`Dongle_ResetState 返回值: 0x${result.toString(16).padStart(8, '0')}`);if (result !== DONGLE_SUCCESS) {console.error(`重置 COS 狀態失敗`);Dongle_Close(handle);return;}console.log('重置 COS 狀態成功');*/// 5?? 生成隨機數// const randomLen = 16;// const randomBuffer = Buffer.alloc(randomLen);// result = Dongle_GenRandom(handle, randomLen, randomBuffer);// console.log(`Dongle_GenRandom : 0x${result.toString(16).padStart(8, '0')}`);// if (result !== DONGLE_SUCCESS) {//   console.error(`生成隨機數失敗`);//   Dongle_Close(handle);// } else {//   randomNum = randomBuffer.toJSON().data.map(b => PrefixZero(b, 2)).join(' ').toUpperCase();//   Dongle_Close(handle);// }//console.log(`隨機數據: ${randomBuffer.toJSON().data.map(b => PrefixZero(b, 2)).join(' ').toUpperCase()}`);/*// 6?? 關閉設備result = Dongle_Close(handle);console.log(`Dongle_Close 返回值: 0x${result.toString(16).padStart(8, '0')}`);if (result !== DONGLE_SUCCESS) {console.error(`關閉設備失敗`);return;}console.log('成功關閉設備');*/resolve({flag,randomNum})}).catch(err => {console.error('單次讀取U盾失敗:', err);return false; // 讀取失敗})
}

后記:與時間賽跑的混亂代碼之旅?
回首這次Electron的改造征程,更像是一場與編譯警告共舞的午夜狂奔。由于項目周期緊張,某些技術方案難免帶著「先跑起來再優化」的倉促痕跡——就像在暴雨中搭建帳篷,難免會有幾處漏水的接縫。

過程中那些臨時添加的Webpack補丁、為繞過環境問題硬編碼的路徑、甚至為了緊急交付保留的TODO注釋,都如同代碼迷宮中未清理的記號。雖然最終功能得以實現,但我深知這座代碼大廈的某些承重墻上,或許還留著需要加固的裂縫。

在此特別懇請各位同行:若您在閱讀中發現任何邏輯漏洞、安全隱患或架構缺陷,請務必通過Issue或郵件指正。您的一條建議,或許就能避免某個深夜的生產環境告警。技術之路本就如履薄冰,唯有開放交流才能讓我們的每一步走得更穩。

~ end

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/81632.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/81632.shtml
英文地址,請注明出處:http://en.pswp.cn/web/81632.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

javascript中運算符的優先級

優先級運算類型關聯性運算符19圓括號n/a( … )18成員訪問從左到右… . …Computed Member Access從左到右… [ … ]new (帶參數列表)n/anew … ( … )17函數調用從左到右… ( … )new (無參數列表)從右到左new …16后置遞增(運算符在后)n/a… 后置遞減(運算符在后)n/a… –15邏…

Linux的交換區

Linux 交換區&#xff08;Swap&#xff09;詳解 交換區&#xff08;Swap&#xff09;是 Linux 系統用于擴展內存的一種機制&#xff0c;它將部分磁盤空間虛擬成內存使用。當物理內存&#xff08;RAM&#xff09;不足時&#xff0c;系統會將不活躍的內存頁移動到交換區&#xf…

閱讀筆記——理解什么是LLM大語言模型

閱讀筆記&#xff1a; 理解LLM deepseek創新了什么 什么是多模態 什么是token ?? 定義??&#xff1a;Token是LLM處理文本的最小單位&#xff0c;相當于語言的"原子"??類比??&#xff1a; 中文&#xff1a;1個token ≈ 1個漢字或常見詞&#xff08;如"…

(自用)Java學習-5.14(注冊,鹽值加密,模糊查詢)

一、核心功能實現 1. 用戶注冊功能 前端實現 用戶名實時校驗&#xff1a;通過AJAX異步請求檢查用戶名是否已存在。 function checkName() {$.ajax({url: /users/checkUserName?uname uname,success: function(resp) {if (resp.code 200) alert("用戶名可用");el…

【雜談】STM32使用快速傅里葉變換庫函數后如何比較準確地找到n次諧波幅值

目錄 1.簡單介紹傅里葉變換的作用 2.諧波是什么 3.解決方法 1.簡單介紹傅里葉變換的作用 任何復雜的波形歸根結底都是由多個頻率和相位不一樣的正弦波組成的 通過傅里葉變換可以找到組成一個復雜的波形的所有正弦波的頻率和幅度信息 2.諧波是什么 假設有一個復雜的波形&a…

芯科科技推出首批第三代無線開發平臺SoC,高度集成的解決方案推動下一波物聯網實現突破

SiXG301和SiXG302是芯科科技采用22納米工藝節點推出的首批無線SoC系列產品&#xff0c;在計算能力、功效、集成度和安全性方面實現突破性進展 低功耗無線解決方案領導性創新廠商Silicon Labs&#xff08;亦稱“芯科科技”&#xff0c;NASDAQ&#xff1a;SLAB&#xff09;近日宣…

寫作即是生活

一個問題 “我是什么時候開始寫作的呢&#xff1f;”請你先暫停一下&#xff0c;別往下讀&#xff0c;先想想這個問題。 什么才是寫作&#xff1f; 或許在想上個問題之后&#xff0c;你就會開始想問另外一個問題&#xff0c;什么才算是寫作呢&#xff1f; 我的回答是&#x…

SpringBoot 執行Lua腳本 服務端執行 減少性能損耗 優化性能 優化連接性能

介紹 通過 Redis 執行 Lua 腳本時&#xff0c;所有的操作都在服務器端完成&#xff0c;而不是多次通過網絡進行請求。這可以顯著減少網絡延遲&#xff0c;尤其是在需要多次與 Redis 交互的場景中。多個操作可以在 Lua 腳本中組合成一個操作&#xff0c;而不是分多次請求&#…

Nginx代理、緩存與Rewrite

目錄 一、正向代理 &#xff08;一&#xff09;核心概念與應用場景 &#xff08;二&#xff09;Nginx正向代理編譯安裝&#xff08;以OpenEuler為例&#xff09; &#xff08;三&#xff09;驗證正向代理 二、反向代理 &#xff08;一&#xff09;七層代理&#xff08;HT…

PortSwigger-02-XXE

一&#xff1a;漏洞原理 1、XXE XXE全稱xml外部實體注入 XML&#xff1a;是一種用于標記電子文件使其具有結構性的標記語言&#xff0c;提供統一的方法來描述和交換獨立于應用程序或者供應商的結構化數據&#xff0c;它可以用來標記數據&#xff0c;定義數據類型、是一種允許…

常規算法學習

算法 1. 排序算法1. 歸并排序1.1 普通歸并排序1.2 優化后的歸并排序&#xff08;TimSort&#xff09; 2. 插入排序2.1 直接插入排序2.2 二分插入排序2.3 成對插入排序 3. 快速排序3.1 單軸快速排序3.2 雙軸快排 4. 計數排序 2. 樹1. 紅黑樹&#xff08;Red Black Tree&#xff…

關于線程死鎖的相關知識

前言 今天學習了線程死鎖的相關知識。線程死鎖是非常重要的知識&#xff0c;寫成博客&#xff0c;加深自己對于知識的理解。 線程死鎖 結語 希望可以幫助到大家~

EMQX啟用單向認證的SSl/TLS連接的配置步驟

先確保您已經安裝了 OpenSSL 執行openssl version -a 獲取 openssl.cnf 目錄 生成自簽名服務端證書 CA 證書生成 server-ca.crt openssl req \-new \-newkey rsa:2048 \-days 365 \-nodes \-x509 \-subj "/CCN/OEMQ Technologies Co., Ltd/CNEMQ CA" \-keyout s…

依賴nacos實例動態創建線程池并監聽服務上下線

版本 Spring Booot 版本 3.2.4Spring Cloud 版本 2023.0.1Spring Cloud Alibaba 版本 2023.0.1.2 依賴 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </depe…

全面指南:使用Node.js和Python連接與操作MongoDB

在現代Web開發中&#xff0c;數據庫是存儲和管理數據的核心組件。MongoDB作為一款流行的NoSQL數據庫&#xff0c;以其靈活的數據模型、高性能和易擴展性廣受開發者歡迎。無論是使用Node.js還是Python&#xff0c;MongoDB都提供了強大的官方驅動和第三方庫&#xff0c;使得數據庫…

LeetCode 3068.最大節點價值之和:腦筋急轉彎+動態規劃(O(1)空間)

【LetMeFly】3068.最大節點價值之和&#xff1a;腦筋急轉彎動態規劃&#xff08;O(1)空間&#xff09; 力扣題目鏈接&#xff1a;https://leetcode.cn/problems/find-the-maximum-sum-of-node-values/ 給你一棵 n 個節點的 無向 樹&#xff0c;節點從 0 到 n - 1 編號。樹以長…

HTTPS加密通信詳解及在Spring Boot中的實現

HTTPS&#xff08;Hyper Text Transfer Protocol Secure&#xff09;是HTTP的安全版本&#xff0c;通過SSL/TLS協議為通訊提供加密、身份驗證和數據完整性保護。 一、HTTPS核心原理 1.加密流程概述 客戶端發起HTTPS請求&#xff08;連接到服務器443端口&#xff09;服務器返…

解決線程安全問題

前言 昨天學習了如何去解決線程不安全的問題。一般方法都是通過加鎖來處理&#xff0c;跟大家分享一波 。 解決線程安全問題 結語 希望可以幫助到大家~ byebye

網絡常識:網線和光纖的區別

網絡常識&#xff1a;網線和光纖的區別 一. 介紹二. 網線2.1 什么是網線&#xff1f;2.2 網線的主要類別2.3 網線的優勢2.4 網線的劣勢 三. 光纖3.1 什么是光纖&#xff1f;3.2 光纖的主要類別3.3 光纖的優勢3.4 光纖的劣勢 四. 網線 vs 光纖&#xff1a;誰更適合你&#xff1f…

win11 禁用/恢復 內置筆記本鍵盤(保證管用)

文章目錄 禁用啟用 禁用 1&#xff09;按下 win x&#xff0c;點擊 設備管理器 2&#xff09;拔掉所有筆記本外設&#xff08;一定要都拔掉&#xff0c;不然后面禁用設備會混淆&#xff09;&#xff0c;然后右鍵點擊 鍵盤 > HID Keyboard Device 2&#xff09;點擊 更新…