Electron 桌面程序读取dll动态库

news2025/6/1 5:52:34

序幕:被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 文件系统 API
    readFile: 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 = true
        app.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+)$/g
  if (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盾登录 需要验证uid
      ifUd = "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');

    // 加载 DLL
    const 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.coloradmin.cn/o/2392221.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTTP 与 HTTPS 深度解析:原理、实践与大型项目应用

1. HTTP 与 HTTPS 基础概念 1.1 HTTP&#xff08;超文本传输协议&#xff09; 定义&#xff1a;应用层协议&#xff0c;基于 TCP/IP 通信&#xff0c;默认端口 80 特点&#xff1a; 无状态协议&#xff08;需 Cookie/Session 维护状态&#xff09; 明文传输&#xff08;易被…

API Gateway CLI 实操入门笔记(基于 LocalStack)

API Gateway CLI 实操入门笔记&#xff08;基于 LocalStack&#xff09; Categories: Cloud Google Rank Proof: No Last edited time: May 26, 2025 4:18 AM Status: Early draft Tags: aws 主要先简单的走一下流程&#xff0c;熟悉一下在 terminal 操作 API Gateway local…

数据分析案例-基于红米和华为手机的用户评论分析

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

深度学习入门:从零搭建你的第一个神经网络

深度学习入门&#xff1a;从零搭建你的第一个神经网络 系统化学习人工智能网站&#xff08;收藏&#xff09;&#xff1a;https://www.captainbed.cn/flu 文章目录 深度学习入门&#xff1a;从零搭建你的第一个神经网络摘要引言第一章&#xff1a;神经网络基础原理1.1 神经元…

【Python办公】Excel简易透视办公小工具

目录 专栏导读1. 背景介绍2. 功能介绍3. 库的安装4. 界面展示5. 使用方法6. 实际应用场景7. 优化方向完整代码总结专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️‍🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注 👍 该系…

Linux系列-2 Shell常用命令收集

背景 本文用于收集Linux常用命令(基于Centos7)&#xff0c;是一个持续更新的博客&#xff0c;建议收藏&#xff0c;编写shell时遇到问题可以随时查阅。 1.Shell类型 shell是用C语言编写的程序&#xff0c;作为命令解释器连接着用户和操作系统内核。常见的shell有sh(Bourne She…

MATLAB使用多个扇形颜色变化表示空间一个点的多种数值

MATLAB使用多个扇形颜色变化表示空间一个点的多种数值 excel中表格中数据格式&#xff0c;多行 lonlatdata1data2data3117380.11100 clear;close all; figure(Position,[100 100 800 800]);num_points 14; [num,txt,raw] xlsread(test.xlsx); x num(:,1); y num(:,2);d…

CAD精简多段线顶点、优化、删除多余、重复顶点——CAD c#二次开发

附部分代码如下: public static void Pl精简(){Document doc Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;Database db doc.Database;Editor ed doc.Editor;var plOrigon db.SelectCurve("\n选择多段线&#xff1a;");…

输电线路的“智慧之眼”:全天候可视化监测如何赋能电网安全运维

在电力需求持续攀升、电网规模日益庞大的今天&#xff0c;输电线路的安全稳定运行面临着前所未有的挑战。线路跨越地形复杂多变&#xff0c;尤其是在偏远山区、铁路沿线及恶劣天气条件下&#xff0c;传统的人工巡检方式显得力不从心——效率低、风险高、覆盖有限。如何实现更智…

两阶段法目标检测发展脉络

模式识别期末展示大作业&#xff0c;做个记录&#xff0c;希望大家喜欢。 R-CNN Fast R-CNN R-FCN 整个过程可以分解为以下几个步骤&#xff1a; 输入图像 (image) 和初步特征提取 (conv, feature maps)&#xff1a; 首先&#xff0c;输入一张原始图像&#xff0c;经过一系列…

小白的进阶之路系列之六----人工智能从初步到精通pytorch数据集与数据加载器

本文将介绍以下内容: 数据集与数据加载器 数据迁移 如何建立神经网络 数据集与数据加载器 处理数据样本的代码可能会变得混乱且难以维护;理想情况下,我们希望我们的数据集代码与模型训练代码解耦,以获得更好的可读性和模块化。PyTorch提供了两个数据原语:torch.utils…

NestJS——重构日志、数据库、配置

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

c++数据结构8——二叉树的性质

一、二叉树的基本性质 示图1&#xff1a; 性质1&#xff1a;层节点数上限 在一棵二叉树中&#xff0c;第i层至多有2^{i-1}个节点&#xff08;首层是第1层&#xff09; 这个性质可以通过数学归纳法证明&#xff1a; 第1层&#xff1a;2^{1-1}2^01个节点&#xff08;根节点&am…

Window Server 2019--08 网络负载均衡与Web Farm

本章要点 1、了解网络负载均衡技术 2、掌握Web Farm核心原理 3、掌握如何使用Windows NLB搭建Web Farm环境 网络负载均衡技术将外部计算机发送的连接请求均匀的分配到服务器集群中的每台服务器上&#xff0c;接受到请求的服务器独立地响应客户的请求。 网络负载均衡技术还…

SpringBoot:统一功能处理、拦截器、适配器模式

文章目录 拦截器什么是拦截器&#xff1f;为什么要使用拦截器&#xff1f;拦截器的使用拦截路径执行流程典型应用场景DispatcherServlet源码分析 适配器模式适配器模式定义适配器模式角色适配器模式的实现适配器模式应用场景 统⼀数据返回格式优点 统一处理异常总结 拦截器 什…

AI Agent工具全景解析:从Coze到RAGflow,探索智能体自动化未来!

在人工智能技术持续深入行业应用的背景下&#xff0c;越来越多的企业和个人寻求通过自动化技术来提高效率和减少重复性劳动&#xff0c;AI Agent的崛起已经成为了不可忽视的趋势。AI Agent&#xff0c;即人工智能代理&#xff0c;是一种基于先进的人工智能技术&#xff0c;特别…

Onvif协议:IPC客户端开发-IPC相机控制(c语言版)

前言&#xff1a; 本博文主要是借鉴OceanStar大神的博文&#xff0c;在他的博文的基础之上做了一部分修改与简化。 博文链接&#xff1a; Onvif协议&#xff1a;IPC客户端开发之鉴权_onvif鉴权方式-CSDN博客 Onvif协议&#xff1a;IPC客户端开发之PTZ控制_onvif ptz-CSDN博客…

如何最简单、通俗地理解Pytorch?神经网络中的“梯度”是怎么自动求出来的?PyTorch的动态计算图是如何实现即时执行的?

PyTorch是一门科学——现代深度学习工程中的一把锋利利器。它的简洁、优雅、强大,正在让越来越多的AI研究者、开发者深度应用。 1. PyTorch到底是什么?为什么它重要? PyTorch是一个开源的深度学习框架,由Facebook AI Research(FAIR)于2016年发布,它的名字由两个部分组成…

QT+opecv如何更改图片的拍摄路径

如何更改相机拍摄图片的路径 前言&#xff1a;基础夯实&#xff1a;效果展示&#xff1a;实现功能&#xff1a;遇到问题&#xff1a;未解决&#xff1a; 核心代码&#xff1a; 前言&#xff1a; 最近在项目开发中遇到需要让用户更改相机拍摄路径的问题&#xff0c;用户可自己选…

秋招Day11 - JVM - 类加载机制

了解类的加载机制吗&#xff1f; JVM是运行Java字节码&#xff0c;也就是运行.class文件的虚拟机&#xff0c;JVM把.class文件中描述类的数据结构加载到内存中&#xff0c;并对数据进行校验&#xff0c;解析和初始化&#xff0c;最终转化为JVM可以使用的类型&#xff08;Klass…