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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;点击 更新…