几年前,直接使用IE 调用相关的插件执行打印,可,IE在win11已经找不到入口了,需要单独设置edge浏览器,且使用的人,经常“不小心”重置网络的安全,导致检查需要远程帮他们开启,故此需要个桌面软件,一次解决,永不烦人。
经过查看tsc打印机官网,找到了SDK下载到入口
tsc官方SDK下载地址
(有些人很贱,从官网下载了资料,上传到其他收费平台上去“赚钱”);找到了nodejs相关的样例。
nodejs的样例,需要用到
edge-js
,但经过多日的入坑,找到的资料是使用:
electron-edge-js
;
用
electron-edge-js
参考官方的
edge-js
样例,确实可以打印,但是:
如果循环打印大量数据,会卡死。
tsclibnet.dll
打印方法;结果:页面卡白了。
tsclibnet.dll
是调用、循环打印,新开启一个窗口来执行;结果,该新开的窗口,如果打印数量超过3条同样还是卡死了。
以上是我用
electron-edge-js
调用
tsclibnet.dll
遇到的问题,我不确定是不是因为我执行打印代码写的太垃圾,而导致卡死,或者我对
electron
了解太少,垃圾代码贴出,便于交流:
(此代码不要直接全部复制粘贴使用,我有删改,不保证可执行)
//关于库方法的说明,我在官方的站点中有pdf中文文档 //为了方便交流,我找到一个html版本,方便阅读的 https://www.e-learn.cn/topic/3539545 (以官方为准,此仅方便交流) var openport = edge.func({ assemblyFile: './tsc_dll/tsclibnet.dll', typeName: 'TSCSDK.node_driver', methodName: 'openport' }); //省略了一堆官方样例给到的如上动态库引入方法 // windowsfont sendcommand barcode sendcommand printlabel 都省略了,看官方样例 let data = [ //文本类坐标信息 "type": "text", "printVal": { "x": 0, "y": 11, "fontheight": 40, "rotation": 0, "fontstyle": 0, "fontunderline": 0, "szFaceName": "黑体", "content": "打印内容1" }, { "type": "qrcode", "printVal": "QRCODE 102,364,H,5,A,0,M2,S5,\"https://www.baidu.com\"" //此处省略了n组如上结构数据 //此处也省略了n组如上数据 // data的解释 // data的第一纬:出纸的总张数,即循环一次完,打印机打印完成,出一次纸 // data的第二纬度:每个需要打印内容和动态库相应方法匹配, openport('打印机名或网络地址', true); // 打印机相关配置 setup(conf,true); for (let i = 0; i < data.length; i++) { clearbuffer('', true); let reverseArr = []; data[i].forEach(ele => { // 文本类型处理 if (ele.type == 'text') { windowsfont(ele.printVal, true); } else if (ele.type == 'qrcode'){// 二维码类处理 sendcommand(ele.printVal, true); } else if (ele.type == 'barcode'){// 条码类处理 barcode(ele.printVal, true); } else if (ele.type == 'reverse'){// 反相打印(黑底白字)类处理 // 此处为什么不直接执行sendcommand()执行打印命令? // 因为反相打印如果在文字之前执行命令,只会打印一个黑块,没有字;如果需要黑底白字,就要放在所有指令的最后执行 if (Array.isArray(ele.printVal) && ele.printVal.length > 0) { reverseArr.push.apply(reverseArr, ele.printVal); }); if (reverseArr.length > 0) { for (let ri = 0; ri < reverseArr.length; ri++) { sendcommand(reverseArr[ri], true); let label_variable = { quantity: '1', copy: '1' }; // 此方法的参数的含义,我没理解透 printlabel(label_variable, true); // 关闭打印机 closeport('', true);
以上就是我执行打印,页面卡死的代码。
解决方法尝试
思来想去,翻来覆去搜索资料,开启系统任务管理器,打印的时候CPU占用突增,是不是进程资源占用不符合系统规则,系统弄死该进程呢?如何重新开启进程?略熟悉的语言也只有js?
nodejs 好像可以开启临时web服务,我把数据发送到另外一个软件上执行,对吧。且还可以打包exe可执行文件->应该可以完美解决打印:对,可行,没有卡死,打印流程完美按照我的想法工作;(在本地 执行
node server.js
的时候服务完全正常启动,打印正常完美执行;)
打印执行打循环体,还是上述代码;web 服务我用node的框架express
(完全不懂,参照网络样例使用)package.json 文件
"name": "print_exe", "version": "0.0.1", "description": "执行打印服务", "main": "server.js", "author": { "name": "programmer" "dependencies": { // 重新自己安装,根据自己的版本来 "edge-js": "^18.4.0", "express": "^4.18.1" "pkg": { "scripts": "build/**/*.js", "assets": "views/**/*", "targets": [ "node14-win-x64" "outputPath": "dist"server.js
// 'use strict'; const fs = require('fs'); const crypto = require('crypto'); const path = require ('path'); var edge = require('edge-js'); var express = require('express'); var bodyParser = require('body-parser') var app = express() ; var urlencodedParser = bodyParser.urlencoded({ extended: false }); var urlencodedJsonParser = bodyParser.json(); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static('./')); app.listen(5000, function () { console.log("Server Start!!"); log("Server Start!!"); // 接受打印数据方法入口 app.post('/printing', urlencodedJsonParser, (req, res) =>{ // 数据合理性验证 // 节约页面,省去我的垃圾代码 res.json({code:0,message:'打印执行中请等待',data:null}); printfile(req.body); }); var openport; var setup; var about; var sendcommand; var clearbuffer; var printerfont; var barcode; var printlabel; var closeport; var sendcommand_utf8; var sendcommand_binary; var windowsfont; try { openport = edge.func({ assemblyFile: 'tsclibnet.dll', typeName: 'TSCSDK.node_driver', methodName: 'openport' }); setup = edge.func({ assemblyFile: 'tsclibnet.dll', typeName: 'TSCSDK.node_driver', methodName: 'setup' }); about = edge.func({ assemblyFile: 'tsclibnet.dll', typeName: 'TSCSDK.node_driver', methodName: 'about' }); sendcommand = edge.func({ assemblyFile: 'tsclibnet.dll', typeName: 'TSCSDK.node_driver', methodName: 'sendcommand' }); clearbuffer = edge.func({ assemblyFile: 'tsclibnet.dll', typeName: 'TSCSDK.node_driver', methodName: 'clearbuffer' }); printerfont = edge.func({ assemblyFile: 'tsclibnet.dll', typeName: 'TSCSDK.node_driver', methodName: 'printerfont' }); barcode = edge.func({ assemblyFile: 'tsclibnet.dll', typeName: 'TSCSDK.node_driver', methodName: 'barcode' }); printlabel = edge.func({ assemblyFile: 'tsclibnet.dll', typeName: 'TSCSDK.node_driver', methodName: 'printlabel' }); closeport = edge.func({ assemblyFile: 'tsclibnet.dll', typeName: 'TSCSDK.node_driver', methodName: 'closeport' }); sendcommand_utf8 = edge.func({ assemblyFile: 'tsclibnet.dll', typeName: 'TSCSDK.node_driver', methodName: 'sendcommand_utf8' }); sendcommand_binary = edge.func({ assemblyFile: 'tsclibnet.dll', typeName: 'TSCSDK.node_driver', methodName: 'sendcommand_binary' }); windowsfont = edge.func({ assemblyFile: 'tsclibnet.dll', typeName: 'TSCSDK.node_driver', methodName: 'windowsfont' }); }catch (error) { log(error,'error'); // 日志处理方法 function log(logContent,type='info') { //省去垃圾代码, * 执行打印 * @param {Object} printData 打印数据 function printfile(printData){ try { if (! printData.hasOwnProperty('print_name')) { throw "缺少打印机"; if (! printData.hasOwnProperty('printer_config') ) { throw "缺少打印纸配置"; if (! printData.hasOwnProperty('print_data')) { throw "缺少打印数据"; const {print_name,printer_config,print_data} = printData; let {width,height,speed,consistence,sensor,spacing,offsetDistance,column} = printer_config; // 传入的单位为cm 此处是单位处理 width = width * 10; height = height * 10; spacing = spacing * 10; // 总宽 mm let totalWidth = width * column + spacing * (column-1); // 打印纸配置数据 const conf = { width: Math.ceil(totalWidth).toString(),//a height: Math.ceil(height).toString(),//b speed: speed.toString(),//c density: consistence.toString(),//d sensor: sensor.toString(),//e vertical: spacing.toString(),//f vertical offset: offsetDistance.toString(),//g for (let i = 0; i < print_data.length; i++) { openport(print_name, true); setup(conf,true); clearbuffer('', true); sendcommand('DIRECTION 1'); let reverseArr = []; print_data[i].forEach(ele => { log(ele.printVal); if (ele.type == 'text') { windowsfont(ele.printVal, true); } else if (ele.type == 'qrcode'){ sendcommand(ele.printVal, true); } else if (ele.type == 'barcode'){ barcode(ele.printVal, true); } else if (ele.type == 'reverse'){ if (Array.isArray(ele.printVal) && ele.printVal.length > 0) { reverseArr.push.apply(reverseArr, ele.printVal); }); if (reverseArr.length > 0) { for (let ri = 0; ri < reverseArr.length; ri++) { sendcommand(reverseArr[ri], true); let label_variable = { quantity: '1', copy: '1' }; printlabel(label_variable, true); // 关闭打印机 closeport('', true); log('打印关闭'); } catch (error) { // console.log(error); log(error,'error');
但是!但是!打包后的exe文件的启动错误问题让我无法解决:
Error: Module did not self-register: '\\?\E:\a\node_modules\edge-js\lib\native\win32\x64\14.19.3\edge_nativeclr.node'.
打包工具是pkg
网络上查找了原因,说需要重新针对自己的node版本打包动态库!不想再去研究了,项目需要使用(向老板汇报)。
最终解决方法
之前简单了解过Python 且也尝试过Python 调用该库,(很早之前记录的踩坑记 Electron-vue开发桌面应用调用TSCLIB.dll)
只是之前 通过发送命令调用Python打包的 exe 程序;这次用Python的exe启动了一个web服务,这样用http方式发送打印数据到该服务器即可。打印数据结构和循环体的逻辑,和最早代码的逻辑一致:
特别注意:Python 的TSCLIB.dll
动态库,和nodejs的不一致,去前文给到的官方地址下载Python 的例子,另外Python样例的动态库参数数据类型 没有描述,参照 java 样例的代码可以知道参数类型。我遇到的问题,都写在代码注释中:(我的
TSCLIB.dll
库和.py
在同级)# coding=utf-8 from flask import Flask,request,json,jsonify # import ctypes from ctypes import * import logging import os import datetime from time import strftime # import chardet app = Flask(__name__) 是否是开发 True-开发 False-生产 # develop = False # 应用执行目录,该应用存在于调用应用下的地址 appExePath = "" @app.route('/') def hello_world(): # 用于应用检测是否可以正常访问 return 'Hello World' @app.route('/printing',methods=['POST']) def exeprint(): app.logger.info('info log') p_ddata = request.get_data() data = json.loads(p_ddata) # 的数据格式完全和最开始体到代码 data 数据格式一致 print_data = data['print_data'] # 打印机名 printer_name = data['print_name'] # 打印机相关配置 printer_config = data['printer_config'] column = printer_config['column'] width = printer_config['width'] * 10 height = printer_config['height'] * 10 vertical = printer_config['spacing'] * 10 totalWidth = width * column + vertical * (column-1) speed = printer_config['speed'] density = printer_config['consistence'] sensor = printer_config['sensor'] offset = printer_config['offsetDistance'] # print(totalWidth) try: path = os.getcwd() dllPath = path+"\\TSCLIB.dll" tsclibrary = CDLL(dllPath) # 如果用了 tsclibrary.setup() 来设置,好像会出错,不执行,我不清楚是否是传入参数的错误,所以我用 tsclibrary.sendcommandW() 来执行我需要的设置命令 # tsclibrary.setup(str(totalWidth),str(height),str(speed),str(density),str(sensor),str(spacing),str(offset)) # setup (String width,String height,String speed,String density,String sensor,String vertical,String offset); # java 的 # tsclibrary.sendcommandW("DIRECTION 1") for item in print_data: tsclibrary.openportW(printer_name) tsclibrary.sendcommandW("DIRECTION 1") tsclibrary.sendcommandW("SIZE "+str(totalWidth)+" mm, "+str(height)+" mm") tsclibrary.sendcommandW("GAP "+str(vertical)+" mm, 0 mm") tsclibrary.sendcommandW("SPEED "+str(speed)) tsclibrary.sendcommandW("DENSITY "+str(density)) # tsclibrary.setup(str(round(totalWidth)),str(round(height)),str(round(speed)),str(round(density)),str(round(sensor)),str(round(vertical)),str(round(offset))) tsclibrary.clearbuffer() reverseList = [] for pd in item: if pd['type'] == 'text': x = pd['printVal']['x'] y = pd['printVal']['y'] fontheight = pd['printVal']['fontheight'] rotation = pd['printVal']['rotation'] fontstyle = pd['printVal']['fontstyle'] fontunderline = pd['printVal']['fontunderline'] szFaceName = pd['printVal']['szFaceName'] szFaceName = 'simhei' content = pd['printVal']['content'] # 官方给的 tsclibrary.windowsfontW() 好像调用无效(打印数据好像没传到打印机) # tsclibrary.windowsfontW 和 tsclibrary.windowsfont 有什么区别吗? # 还是官方给到例子太老了?? # tsclibrary.windowsfontW(x,y,fontheight,rotation, fontstyle, fontunderline, szFaceName,content)#无法打印 # tsclibrary.windowsfont(x,y,fontheight,rotation, fontstyle, fontunderline, szFaceName,content)#打印乱码 tsclibrary.windowsfontUnicode( x, y, fontheight, rotation, fontstyle, fontunderline, szFaceName, content)#字体为默认宋 elif pd['type'] == 'qrcode': tsclibrary.sendcommandW(pd['printVal']) elif pd['type'] == 'reverse': reverseList.extend(pd['printVal']) for revCmd in reverseList: tsclibrary.sendcommandW(revCmd) # tsclibrary.sendcommand(revCmd) # tsclibrary.printlabelW("1","1") tsclibrary.printlabel("1","1") tsclibrary.closeport() except OSError as err: app.logger.warning(err) print(err) return jsonify(status="success") if __name__ == '__main__': # 日志配置 不需要的话此处可以删 now=datetime.datetime.now() logdate = now.strftime("%Y-%m-%d") handler = logging.FileHandler(logdate+'.log', encoding='UTF-8') handler.setLevel(logging.DEBUG) logging_format = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s') handler.setFormatter(logging_format) app.logger.addHandler(handler) # 日志配置end app.run()
Python 打印服务程序 全部代码。
打包 exe 命令pyinstaller -F -w -i my.ico PrintServer.py
参考地址应用程序启动打印服务(python 打包的exe)
打包后的EXE 和
TSCLIB.dll
库都和应用打包后的启动exe位于同级目录(如果要放在子目录或者其他目录 Python 动态库地址,需要再处理,否则electon 无法启动打印服务)我的此段代码是位于electron主js文件的最后。
try { // 启动打印服务 if (是生产) { //注意每个人的端口号可能不一样 child_process.exec('netstat -ano|findstr "5000"',(err, stdout, stderr)=>{ if (stdout === "") { child_process.exec(`${homeDir}\\PrintServer.exe`,(err, stdout, stderr)=>{ logger.info(`命令执行err:${err}`); logger.info(`命令执行stdout:${stdout}`); logger.info(`命令执行stderr:${stderr}`); }); }else{ logger.info('5000 端口被占用,或打印服务已启动'); // 如果是开发,手动启动打印服务 } catch (e) { logger.info(`命令执行异常:${e}`);
记录我的坑,便于后来者。希望我遇到的问题您能避免,且把您的解决方案留言告诉我们有共同问题的人。
我用electron-edge-js调用tsclibnet.dll遇到的问题,我不确定是不是因为我执行打印代码写的太垃圾,而导致卡死,或者我对electron了解太少;最终用Python开启一个打印服务,执行TSCLIB.dll动态库调用实现打印...依赖库选择 当收到这个需求的时候,肯定也是一脸蒙😌,我一个前端也没写过dll,我怎么来调用啊?做过nodejs的同学应该能清楚它提供了这样的能力🤙,如果是用c++生成的dll可能会用到ffi-napi这个库,如果是c#生成的dll就会使用到edge这个库😄,因为我们公司都是用c#编写的dll,所以我会主要介绍一下edge,c++调用dll的文章可以看这篇我有好多坑都是看了这篇文章才清楚,抱拳了✊。 NodeJs调用dll 创建demo项目 #找到一个空目录执行 npm init --yes Electron 3.x-Node.js v10.2.0。 Electron 4.0.4+-Node.js v10.11.0。 Electron 5.x-Node.js v12.0.0。 Electron 6.x-Node.js v12.4.0。 电子7.x-Node.js v12.8.1 Electron 8.x-Node.js v12.13.0 电子9.x-Node.js v12.14.1 电子10.x-Node.js v12.16.3 电子11.x-Node.js v12.18.3文章目录前言使用模板搭建electron_edgeJS_vue_typescript1. 安装@vue/cli与@vue/cli-init2. 创建项目3. 安装electron-edge-js4. 安装typescript5. 配置typescript与vue6. 运行 使用模板搭建 electron与edgeJS与vue与typescript 使用模板搭建electron_edgeJS_vue_typescript 1. 安装@vue/cli与@vue/cli-init npm install -电子边缘js快速启动 安装依赖项npm install 使用Visual Studio 2017或JetBrains Rider构建src\QuickStart.sln或运行dotnet build src/QuickStart.sln 要使用.NET Core运行应用程序,请使用npm start或npm run start:core 要使用.NET Standard运行应用程序,请使用npm run start:standard原因:要做个防伪标签打印软件,打印数据是来自服务器。最开始用的是JavaScript调用TSC来做打印(只能用IE浏览器,使用人员总会无意关闭IE的相关设置,使用麻烦)。干脆就做成桌面软件麻烦事少。 最开始看了TSC大陆官网的dll动态连接库,有Python的例子,想用pyqt来做桌面程序;试了试之后,发现自己对Python还是太不熟悉了,无法搞出勉强好看的桌面;所以决定使用Electron。 但是Electron底层是nodejs,度娘找了一部分关于node调用dll的方法来试验(有ffi).....我在使用npm安装electron-edge-js时出现了以下问题: Error: The edge module has not been pre-compiled for Electron version 15.0.0 . You must build a custom version of edge.node. Please refer to https://github.com/agracio/edge-js for building instructions.最近在开发一个桌面客户端。心血来潮想用Node.js来做。然后就跳进了坑了。天天在爬坑。开篇博文记录一下最几天遇到的各种坑。 VUE+Electron+Edge开发中遇到的坑1.Electron安装的坑2.Electron打包的坑3.Asar打包的坑4.使用require('XXXXX')加载的坑5.使用Edge调用C# 代码的坑 1.Electron安装的坑 在使用vue的时候,安装electron不能选用原装的electron,也不要使用网上很多人说的SimulatedGREG/electron-vue"electron-edge-js": "^14.16.1" 问题描述: 调试开发electron-edge-js的调用是正常的;打包后调用 electron-edge-js的页面白屏!刚开始没做异常的捕获,没找到问题的根源。 异常描述如下: Error: Cannot find module 'electron-edge-js' Require stack: - electron/js2c/renderer_initTSCLIB.DLL函式库使用说明方面的问题。注意:使用动态库TSCLIB.DLL前,安装TSC条码印表机驱动。 1. openport(a) 说明:指定电脑端的输出端 a:单机列印时,请指定印表机驱动程式名称,例如: TSC CLEVER TTP-243 这里我是用的是GP-9025T 若连接印表机伺服器,请指定伺服器路径及共用印表机名称,例如: \\SERVER\TTP243 2. closeport() 说明:关闭指定的电脑端输出埠 3. setup...要在 Electron-Vite-Vue 中调用 Python 代码,你可以使用 Node.js 的 `child_process` 模块来执行 Python 命令。 首先,你需要在你的项目中安装 Python,然后将 Python 的路径添加到环境变量中。你可以在终端中输入 `python --version` 来检查你的 Python 版本是否正确安装。如果 Python 安装正确,你应该能够在终端中看到 Python 版本号。 接下来,你可以使用 `child_process` 模块来执行 Python 命令。下面是一个简单的示例: ```js const { exec } = require('child_process'); exec('python your_python_script.py', (error, stdout, stderr) => { if (error) { console.error(`exec error: ${error}`); return; console.log(`stdout: ${stdout}`); console.error(`stderr: ${stderr}`); 在这个示例中,我们使用 `exec` 函数来执行一个 Python 脚本。如果 Python 脚本运行出错,我们会在控制台中输出错误信息。如果 Python 脚本运行成功,我们将在控制台中输出标准输出和标准错误输出。 你可以将这段代码放在你的 Vue 组件中,然后在需要的时候调用它来执行 Python 代码。