QianTools 2.9.0
dotnet add package QianTools --version 2.9.0
NuGet\Install-Package QianTools -Version 2.9.0
<PackageReference Include="QianTools" Version="2.9.0" />
<PackageVersion Include="QianTools" Version="2.9.0" />
<PackageReference Include="QianTools" />
paket add QianTools --version 2.9.0
#r "nuget: QianTools, 2.9.0"
#:package QianTools@2.9.0
#addin nuget:?package=QianTools&version=2.9.0
#tool nuget:?package=QianTools&version=2.9.0
QianTools
.NET 工业自动化与系统管理工具库,支持 net6.0 / net8.0。
作者:钱军涛 | 公司:浪涛科技有限公司 | 许可证:MIT
安装
dotnet add package QianTools
项目结构
QianTools/
├── Core/ # 基础设施(日志、配置)
│ ├── LogManager.cs # 日志管理器(支持 ILogger 接口、级别过滤、文件滚动)
│ ├── ILogger.cs # 日志接口
│ ├── LogLevel.cs # 日志级别枚举
│ ├── ConfigManager.cs # 配置管理器(JSON/XML,策略模式可扩展)
│ ├── IConfigParser.cs # 配置解析器接口
│ ├── JsonParser.cs # JSON 解析器
│ └── XmlParser.cs # XML 解析器
├── Helpers/ # 通用工具类
│ ├── AllTools.cs # 统一外观入口(FileTools / CryptoTools / NetworkTools)
│ ├── CryptoHelper.cs # 加密与哈希(SHA256、MD5)
│ ├── FileHelper.cs # 文件/目录复制与删除
│ └── NetworkHelper.cs # HTTP 文件下载
├── System/ # 系统硬件信息
│ ├── SystemHardwareInfo.cs # 串口、磁盘、CPU、操作系统、网络适配器
│ └── MachineCodeGenerator.cs # 机器码生成与授权验证
├── Plc/ # PLC 通信
│ ├── MelsecPlcService.cs # 三菱 FX 底层通信服务(ASCII 协议)
│ └── MelsecPlcHelper.cs # 上层静态封装
├── EtherNetIP/ # EtherNet/IP 与 Modbus TCP 通信
│ ├── EtherNetIPClient.cs # EtherNet/IP 显式消息客户端
│ ├── Encapsulation.cs # 封装协议头操作
│ ├── CipMessage.cs # CIP 协议消息构建与解析
│ ├── EdsAssemblyReader.cs # Assembly 数据读取器
│ └── Ex1121ModbusClient.cs# EX-1121 Modbus TCP IO 客户端
├── Ezd/ # JCZ激光打标板卡控制
│ ├── EzdClient.cs # 高层封装(单例),生命周期/异步/业务级 API
│ ├── EzdNative.cs # P/Invoke 底层接口声明
│ └── EzdErrorCode.cs # 故障码定义与中文消息
├── Application/ # 应用层服务
│ ├── AutoStartManager.cs # 注册表开机自启动
│ ├── ShortcutCreator.cs # 桌面快捷方式
│ ├── VerifyInstallation.cs# 软件安装与验证
│ ├── MesClient.cs # MES HTTP 客户端
│ └── MesTcpClient.cs # MES TCP 通信客户端(JSON 换行协议)
├── QianTools.csproj
└── README.md
功能模块
Core — 基础设施
日志管理 — LogManager
// 使用静态方法(默认单例)
LogManager.Info("设备启动成功");
LogManager.Error($"通信失败: {ex.Message}");
// 自定义日志路径和文件滚动
LogManager.Initialize(@"D:\AppLogs\myapp.log");
LogManager.MinLogLevel = LogLevel.Debug;
LogManager.MaxFileSizeBytes = 10 * 1024 * 1024; // 10MB 自动滚动
// 通过 ILogger 接口注入(支持单元测试)
ILogger logger = LogManager.Instance;
logger.LogInfo("消息");
配置管理 — ConfigManager
支持 JSON(默认)和 XML 两种格式,策略模式可扩展。
// ===== JSON 模式(默认) =====
var cfg = new ConfigManager(); // 或 ConfigManager.Default
cfg.Set("AppSettings", new AppSettings { Theme = "Dark" });
var settings = cfg.Get<AppSettings>("AppSettings"); // 从内存读取
cfg.Save(); // 持久化到文件
cfg.Load(); // 从文件加载到内存
cfg.Remove("AppSettings");
// ===== XML 模式 =====
var xmlCfg = new ConfigManager(useXml: true, customPath: @"D:\MyApp\Config");
xmlCfg.Set("AppSettings", new AppSettings { Theme = "Dark" });
xmlCfg.Save(); // → D:\MyApp\Config\config.xml
// ===== 错误监听 =====
ConfigManager.OnError += msg => Console.WriteLine(msg);
Helpers — 通用工具
统一入口 — AllTools
// 通过 AllTools 静态子类访问
AllTools.FileTools.CopyDirectory(@"C:\Source", @"D:\Backup", overwrite: true);
AllTools.FileTools.DeleteDirectory(@"C:\Temp\OldData");
string sha256 = AllTools.CryptoTools.ComputeSha256Hash("hello");
string md5 = AllTools.CryptoTools.ComputeMd5Hash("hello");
await AllTools.NetworkTools.DownloadFileAsync(url, destinationPath);
// Excel 操作
AllTools.ExcelTools.WriteToExcel(dataList, @"D:\data.xlsx", "Sheet1");
DataTable dt = AllTools.ExcelTools.ReadToDataTable(@"D:\data.xlsx");
List<MyClass> list = AllTools.ExcelTools.ReadToList<MyClass>(@"D:\data.xlsx");
文件操作 / 加密 / 网络 / Excel
// 文件
FileHelper.CopyDirectory(@"C:\Source", @"D:\Backup", overwrite: true);
FileHelper.DeleteDirectory(@"C:\Temp\OldData");
// 加密
string sha256 = CryptoHelper.ComputeSha256Hash("hello");
string md5 = CryptoHelper.ComputeMd5Hash("hello");
// 网络
await NetworkHelper.DownloadFileAsync("https://example.com/file.zip", @"D:\Download\file.zip");
// Excel(基于 ClosedXML,仅 .xlsx)
ExcelHelper.WriteToExcel(dataList, @"D:\data.xlsx", "Sheet1");
ExcelHelper.WriteDataTable(dataTable, @"D:\data.xlsx", "Sheet1");
DataTable dt = ExcelHelper.ReadToDataTable(@"D:\data.xlsx");
List<MyClass> list = ExcelHelper.ReadToList<MyClass>(@"D:\data.xlsx");
string[] sheets = ExcelHelper.GetSheetNames(@"D:\data.xlsx");
System — 系统信息
硬件信息 — SystemHardwareInfo
string ports = SystemHardwareInfo.GetPort(); // 串口列表
string disks = SystemHardwareInfo.GetDiskInfo(); // 磁盘信息
string cpu = SystemHardwareInfo.GetCpuInfo(); // CPU 信息
string os = SystemHardwareInfo.GetOsInfo(); // 操作系统
string network = SystemHardwareInfo.GetNetworkAdapterInfo(); // 网络适配器
机器码与授权 — MachineCodeGenerator
// 生成定长机器码(CPU + 硬盘 + 网卡,SHA256 定长哈希)
string machineCode = MachineCodeGenerator.GenerateMachineCode();
string hash = MachineCodeGenerator.GetMachineCodeHash(); // SHA256 大写
// ── 授权方生成注册码(口令必填;QT2 新格式,HMAC-SHA256 签名)──
string licenseKey = MachineCodeGenerator.GenerateLicenseKey(
machineCode, new DateTime(2026, 12, 31, 0, 0, 0, DateTimeKind.Utc),
password: "admin@123");
// ── 用户端验证(返回 LicenseResult)──
LicenseResult result = MachineCodeGenerator.ValidateLicenseKey(
licenseKey, machineCode, password: "admin@123");
// result.IsValid / result.ExpiryDate / result.ErrorReason
// result.MachineCodeOk / result.DateOk / result.PasswordOk / result.HasPassword
// ── 生产环境必须注入自有签名密钥(默认密钥仅测试用,随客户端分发)──
MachineCodeGenerator.SetLicenseSigningKeyOnce("your-own-strong-key");
Plc — PLC 通信
MelsecPlcHelper
支持三菱 FX 系列(X/Y/M/S/T/C/D 元件),ASCII 协议串口通信。
// 连接 PLC
MelsecPlcHelper.Connect("COM1", baudRate: 9600);
// 读取
bool x0 = MelsecPlcHelper.ReadX(0);
bool y1 = MelsecPlcHelper.ReadY(1);
bool m10 = MelsecPlcHelper.ReadM(10);
short d100 = MelsecPlcHelper.ReadD(100);
short t5 = MelsecPlcHelper.ReadT(5);
// 写入
MelsecPlcHelper.WriteY(1, true);
MelsecPlcHelper.WriteM(10, false);
MelsecPlcHelper.WriteD(100, 12345);
// 轮询 D 寄存器
MelsecPlcHelper.OnDataUpdated += snapshot => {
Console.WriteLine($"D100 = {snapshot.D[100]}");
};
MelsecPlcHelper.StartPolling(new[] { 100, 101, 102 }, intervalMs: 100);
// 日志监听
MelsecPlcHelper.OnLog += (msg, level) => Console.WriteLine($"[{level}] {msg}");
// 断开
MelsecPlcHelper.Shutdown();
Plc 协议库 — 三菱 MC / Modbus RTU-ASCII / 西门子 S7(v2.9 新增)
命名空间: QianTools.Plc / QianTools.Plc.Melsec / QianTools.Plc.Modbus / QianTools.Plc.Siemens
依据各协议官方手册从零实现(MIT 洁净室),与旧版 MelsecPlcHelper(FX 串口)/ Ex1121ModbusClient(Modbus TCP)并存、互不影响。所有客户端共用一套基座约定:
- 事件:
OnConnectionChanged(连接状态)/OnLog(日志,对接 LogManager) - 超时:请求-响应真实超时(
TimeoutException,超时后连接失效、下次操作自动重连) - 释放:
DisposeAsync()/Dispose()会取消并排空在途请求(安全幂等,回调内可调用) - 无实机验证:每个协议都带进程内模拟器(
*Simulator),配合帧级单元测试可实现无硬件闭环联调;生产使用前建议按 移植方案.md 的实机清单抓帧复核
三菱 MC(以太网,A/E/Q/L 系列)— MelsecMcClient
using QianTools.Plc.Melsec;
var plc = new MelsecMcClient("192.168.1.50"); // 默认端口 5000、二进制 3E 帧
await plc.ConnectAsync();
// 位软元件读写(X/Y/M/L/B/S/SM 等)
bool[] m = await plc.ReadBoolAsync("M", 0, 16);
await plc.WriteBoolAsync("M", 10, new[] { true, false, true });
// 字软元件读写(D/W/R/ZR/SD/Z 等,低 16 位在前)
short[] d = await plc.ReadInt16Async("D", 100, 4);
await plc.WriteInt16Async("D", 200, new short[] { 123, -456 });
// 32 位 / 浮点(相邻 2 寄存器)
int[] d32 = await plc.ReadInt32Async("D", 300, 2);
await plc.WriteFloatAsync("D", 310, new[] { 3.14f });
// 跨地址随机读(一次请求多组地址)
ushort[] rand = await plc.ReadRandomWordsAsync(
new[] { ("D", 100), ("D", 200), ("W", 0x10) });
// ASCII 帧模式(对应模块需配置为 ASCII)
var asciiPlc = new MelsecMcClient("192.168.1.50", 5000, McFrameFormat.Ascii);
await asciiPlc.ConnectAsync();
// 帧参数(多网络/多 CPU 场合)
plc.NetworkNumber = 0; // 网络号
plc.PLCNumber = 0xFF; // PLC 号
plc.StationNumber = 0; // 站号
plc.IOTarget = 0x03FF; // 请求目标 IO 号
plc.TimerValue = 0x0010; // 监视定时器
await plc.DisposeAsync();
- 批读自动分包(二进制 950 字/7168 位、ASCII 460 字/3584 位),无需关心单帧上限
- 设备错误码抛
McCommunicationException(含中文错误说明) - 位软元件按字读写采用 8 字节/字打包(与手册一致,实机复核项)
三菱 FX 串口升级(现有 MelsecPlcHelper 的新增 API)
// 系列型号校验(只收紧新增接口,老接口行为不变)
MelsecPlcHelper.FxSeries = FxSeries.FX3U;
// D 寄存器批量写(1~49 个,禁止跨 D7999/D8000 边界)
MelsecPlcHelper.WriteDBatch(100, new short[] { 1, 2, 3, 4, 5 });
// 位软元件批量读写(X/Y/M/S)
bool[] bits = MelsecPlcHelper.ReadBitBatch('M', 0, 16);
MelsecPlcHelper.WriteBitBatch('Y', 0, new[] { true, false, true });
// T/C 触点(线圈)访问(与现有 ReadT/ReadC=当前值语义区分)
bool tc = MelsecPlcHelper.ReadTContact(5);
MelsecPlcHelper.WriteCContact(8, true);
无实机闭环测试 — MelsecMcSimulator
using var sim = new MelsecMcSimulator(McFrameFormat.Binary);
sim.Start(); // 端口自动分配
sim.SetWord("D", 100, 0x1234); // 预置寄存器
sim.SetBits("M", 0, new[] { true, false, true });
using var plc = new MelsecMcClient("127.0.0.1", sim.Port);
await plc.ConnectAsync();
short v = (await plc.ReadInt16Async("D", 100, 1))[0]; // 0x1234
Modbus RTU / ASCII(串口)— ModbusRtuClient / ModbusAsciiClient
using QianTools.Plc.Modbus;
// 串口构造(从站地址、校验位、停止位可配)
using var rtu = new ModbusRtuClient("COM3", baudRate: 9600,
dataBits: 8, parity: System.IO.Ports.Parity.None,
stopBits: System.IO.Ports.StopBits.One, slaveAddress: 1);
// 功能码 01/02/03/04(读线圈/离散输入/保持寄存器/输入寄存器)
bool[] coils = await rtu.ReadCoilsAsync(0, 8);
ushort[] regs = await rtu.ReadHoldingRegistersAsync(0, 10);
// 功能码 05/06/0F/10(单/批量写)
await rtu.WriteSingleCoilAsync(10, true);
await rtu.WriteSingleRegisterAsync(100, 0xABCD);
await rtu.WriteMultipleCoilsAsync(20, new[] { true, false, true });
await rtu.WriteMultipleRegistersAsync(200, new ushort[] { 1, 2, 3 });
// ASCII 模式(帧格式 ":...CRLF",其余 API 相同)
using var ascii = new ModbusAsciiClient("COM3", 9600, 8,
System.IO.Ports.Parity.Even, System.IO.Ports.StopBits.One, slaveAddress: 1);
ushort[] v = await ascii.ReadHoldingRegistersAsync(0, 2);
- 支持注入自定义
IModbusByteTransport(网关/测试环回),超时由客户端统一管控 - 从站返回异常时抛
ModbusException(含中文异常码说明,01/02/03/04) - 上限:读 2000 线圈 / 125 寄存器,写 1968 线圈 / 123 寄存器(Modbus PDU≤253 字节)
西门子 S7(ISO-on-TCP,S7-300/400/1200/1500)— SiemensS7Client
using QianTools.Plc.Siemens;
// 默认端口 102;连接时自动完成 ISO-on-TCP 握手 + PDU 协商(建议 480)
using var s7 = new SiemensS7Client("192.168.0.1", pduSize: 480);
await s7.ConnectAsync();
Console.WriteLine(s7.NegotiatedPduSize); // 与 PLC 协商后的实际 PDU
// 字节读写(DB 区 / M 区 / I / Q)
await s7.WriteBytesAsync(S7Area.DataBlock, 1, 0, new byte[] { 0x11, 0x22 });
byte[] data = await s7.ReadBytesAsync(S7Area.DataBlock, 1, 0, 4);
byte[] mb = await s7.ReadBytesAsync(S7Area.Merker, 0, 10, 2);
// 位读写(读-改-写,客户端锁内原子)
bool b = await s7.ReadBitAsync(S7Area.DataBlock, 1, 0, 3); // DB1.DBX0.3
await s7.WriteBitAsync(S7Area.Merker, 0, 5, 1, true); // M5.1
// 类型化读写(大端)
await s7.WriteInt32Async(S7Area.DataBlock, 1, 8, new[] { 0x12345678 });
int[] dword = await s7.ReadInt32Async(S7Area.DataBlock, 1, 8, 1);
await s7.WriteFloatAsync(S7Area.DataBlock, 1, 20, new[] { 3.14f });
// 多区域批读(一次请求,自动按 PDU 分组)
var batch = await s7.ReadBytesBatchAsync(new[]
{
new S7ReadRequest(S7Area.DataBlock, 1, 0, 16),
new S7ReadRequest(S7Area.Merker, 0, 0, 4),
new S7ReadRequest(S7Area.Input, 0, 0, 2),
});
// 地址字符串解析("DB1.DBX0.0" / "M5.3" / "MB10" / "IB0" / "Q1.7")
var (area, db, byteAddr, bit) = S7Address.Parse("DB1.DBX4.2");
// TSAP(机架/槽)与 PDU 配置
s7.LocalTSAP = 0x0100;
s7.RemoteTSAP = 0x0100; // 部分设备为 0x0200/0x0300(见移植方案.md 实机清单)
- 断线自动重连并重新握手(透明恢复)
- 设备错误码抛
S7Exception(0x0005 地址越界 / 0x000A DB 不存在 / 0x8104 上下文不支持等,中文说明)
无实机闭环测试 — S7Simulator
using var sim = new S7Simulator();
sim.Start();
sim.CreateDb(1);
sim.SetBytes(S7Area.DataBlock, 1, 0, new byte[] { 0xAA, 0xBB });
using var s7 = new SiemensS7Client("127.0.0.1", sim.Port, pduSize: 480);
await s7.ConnectAsync();
byte[] v = await s7.ReadBytesAsync(S7Area.DataBlock, 1, 0, 2); // AA BB
实机验证提醒:协议实现已通过"帧级单测 + 内嵌模拟器闭环 + 独立实现交叉对拍"三重验证,但部分细节(三菱 T/C 触点基址与 E00 扩展帧、S7 偶数对齐填充与 TSAP 机架配置)仍需实机抓帧确认,清单见 移植方案.md。
EtherNetIP — EtherNet/IP 与 Modbus TCP 通信
命名空间: QianTools.EtherNetIP
支持 EX-1121(DECOWELL/Keyence 兼容)远程 IO 模块,提供 EtherNet/IP 显式消息和 Modbus TCP 两种通信方式。
EtherNet/IP 客户端
using QianTools.EtherNetIP;
var client = new EtherNetIPClient("192.168.1.100", timeout: TimeSpan.FromSeconds(3));
await client.ConnectAsync();
// 读取输入 Assembly(Instance 100)— 过程镜像输入数据
byte[] inputData = await client.ReadInputAssemblyAsync();
var reader = new EdsAssemblyReader(inputData);
// 按偏移量读取数据
ushort ch0 = reader.ReadUInt16(0); // 16 位无符号
short ch1 = reader.ReadInt16(2); // 16 位有符号
uint ch2 = reader.ReadUInt32(4); // 32 位无符号
bool bit0 = reader.ReadBool(0, 0); // 位读取
// 读取输出 Assembly(Instance 101)
byte[] outputData = await client.ReadOutputAssemblyAsync();
// 显式消息:GetAttributeSingle
byte[] data = await client.GetAttributeAsync(
classId: 0x04, instanceId: 100, attributeId: 0x03);
// 显式消息:SetAttributeSingle
await client.SetAttributeAsync(0x04, 101, 0x03, new byte[] { 0x00, 0x01 });
// Keyence / Decowell EDS 参数读取(服务 0x4C)
ushort param = await client.ReadParameterUInt16Async(slot: 1, paramNo: 512);
short param2 = await client.ReadParameterInt16Async(slot: 1, paramNo: 513);
uint param3 = await client.ReadParameterUInt32Async(slot: 1, paramNo: 514);
client.Disconnect();
Modbus TCP 客户端
var config = Ex1121Config.Default; // DI=0xA000, DO=0xB000, 可自定义
using var io = new Ex1121ModbusClient("169.254.193.30", config, port: 20001);
await io.ConnectAsync();
// 读 DI
ushort[] inputs = await io.ReadInputsAsync();
bool di15 = Ex1121ModbusClient.GetDiBit(inputs, 15);
// 读 DO
ushort[] outputs = await io.ReadOutputsAsync();
bool do0 = Ex1121ModbusClient.GetDoBit(outputs, 0);
// 写 DO
await io.WriteOutputsAsync(0x0001); // DO0 = ON
await io.SetDoBitAsync(3, true); // DO3 = ON
// 自定义寄存器地址读写
ushort[] regs = await io.ReadRegistersAsync(0xA000, 2);
await io.WriteRegistersAsync(0xB000, new ushort[] { 0x0001 });
io.Disconnect();
Application — 应用层服务
MES 通信 — MesClient
// GET 请求
string result = await MesClient.SendGetRequestAsync(
"https://mes-server/api", "production/orders", apiKey: "your-token");
// POST 请求
var data = new { OrderId = "P001", Quantity = 100 };
string response = await MesClient.SendPostRequestAsync(
"https://mes-server/api", "production/report", data, apiKey: "your-token");
MES TCP 通信 — MesTcpClient
基于 TCP + 换行分隔 JSON 协议,支持请求-响应模式和服务器主动推送。线程安全,适合与产线 MES 服务器进行长连接双向通信。
using QianTools.Mes;
var mes = MesTcpClient.Instance;
// ── 超时设置(连接前或连接后均可设置)──
mes.TimeoutMs = 10000; // 请求-响应超时 10 秒
// ── 连接 ──
bool ok = await mes.ConnectAsync("192.168.1.100", 8080); // 使用 TimeoutMs 属性值
// 也可在连接时显式传入超时
bool ok2 = await mes.ConnectAsync("192.168.1.100", 8080, timeoutMs: 3000);
// 连接状态通过事件通知
mes.OnConnectionChanged += connected => Console.WriteLine(connected ? "已连接" : "已断开");
mes.OnMessageReceived += json => Console.WriteLine($"服务器推送: {json}");
mes.OnError += err => Console.WriteLine($"异常: {err}");
// ── 测试连接(发送 ping)──
var (pingOk, pingResp) = await mes.TestAsync();
// ── 上报打标结果 ──
var (resultOk, resultResp) = await mes.SendResultAsync(
productCode: "PC001",
workOrder: "WO-20260523",
productionLine: "L1",
success: true,
entityName: "Text1",
fileName: @"C:\Files\product.ezd");
// ── 通用请求-响应 ──
string? response = await mes.SendAndReceiveAsync("{\"cmd\":\"query_status\"}");
// ── 单向发送(不等待响应)──
await mes.SendAsync("{\"cmd\":\"heartbeat\"}");
// ── 断开 ──
mes.Disconnect();
协议格式:每条 JSON 消息以 \n 结尾。常见消息类型:
| 方向 | cmd | 说明 |
|---|---|---|
| 客户端→服务端 | ping |
心跳/连通性测试 |
| 客户端→服务端 | mark_result |
上报打标结果(含产品代码、工单号、成功/失败等) |
| 服务端→客户端 | start_mark |
要求开始标刻 |
| 服务端→客户端 | select_entity |
切换打标对象 |
| 服务端→客户端 | update_product |
更新生产信息(产品代码/工单号等) |
线程安全:所有 I/O 通过 SemaphoreSlim 串行化。后台读循环自动处理服务器主动推送的消息。
桌面快捷方式 — ShortcutCreator
ShortcutCreator.CreateShortcut("我的应用", @"D:\App\MyApp.exe", arguments: "-start");
开机启动 — AutoStartManager
AutoStartManager.EnsureAutoStart("MyApp", @"D:\App\MyApp.exe", enable: true);
AutoStartManager.EnsureAutoStart("MyApp", null, enable: false); // 取消
安装验证 — VerifyInstallation
// 检查软件是否安装
bool installed = VerifyInstallation.IsSoftwareInstalled("MyApp", version: "2.0");
// 安装程序(需用户确认委托)
VerifyInstallation.Notify = (title, msg) => MessageBox.Show(msg, title) == DialogResult.OK;
await VerifyInstallation.InstallAsync("安装 MyApp", @"D:\Installer", "setup.exe");
Ezd — JCZ 激光打标板卡控制
命名空间: QianTools.Ezd
平台要求: MarkEzd.dll 为 32 位原生库,宿主程序必须编译为 x86(<PlatformTarget>x86</PlatformTarget>)。x64 进程调用会直接返回 EzdErrorCode.ERR_PLATFORM_X86_REQUIRED (2001),不会崩溃。
运行依赖: 初始化时传入的 EZCAD 目录需包含 MarkEzd.dll、EZCAD.CFG、plug/、PARAM/、FONT/ 等 EZCAD 运行时文件;运行前必须关闭 ezcad2.exe(否则初始化返回 1“EZCAD 正在运行”)。
通过 P/Invoke 封装 MarkEzd.dll(对照官方《EZCAD2 开发者手册》v3.1,__cdecl + Unicode),提供初始化/关闭、文件加载保存、实体管理、标刻/红光、笔号/填充/字体/条码/位图参数、IO/锁存、振镜/扩展轴、连续飞行缓冲区等功能。
using QianTools.Ezd;
var ezd = EzdClient.Instance;
// ── 初始化(失败不会把状态卡在 Initializing;重复初始化会被拒绝)──
int ret = ezd.Initialize2(@"C:\Laser\EzCad2", testMode: false);
if (!EzdErrorCode.IsSuccess(ret))
{
Console.WriteLine($"初始化失败: {EzdErrorCode.GetMessage(ret)}");
return;
}
// ── 文件操作 ──
ezd.LoadEzdFile(@"C:\Files\product.ezd");
ezd.SaveEzdFile(@"C:\Files\output.ezd");
// ── 实体管理 ──
int count = ezd.GetEntityCount();
string firstName = ezd.GetEntityName(0);
// AddText(text, name, x, y, z, align, angle(弧度), penNo, hatch)
ezd.AddText("Hello", "Text1", 0, 0, 0, 1, 0, 1, false);
// AddCircle(cx, cy, radius, name, penNo) —— 手册无 hatch 参数
ezd.AddCircle(0, 0, 10, "Circle1", 1);
ezd.MoveEntity("Text1", 10, 5);
ezd.RotateEntity("Text1", 0, 0, Math.PI / 4); // 角度单位:弧度
ezd.SetText("Text1", "NewText"); // 修改文本内容
ezd.DeleteEntity("Text1");
// ── 标刻控制(异步:阻塞的原生函数已用 Task.Run 包装)──
int r1 = await ezd.MarkAsync(flyMark: false);
int r2 = await ezd.MarkEntityAsync("Text1");
int r3 = await ezd.MarkLineAsync(0, 0, 50, 50, 1);
int r4 = await ezd.MarkPointAsync(100, 100, 1, 1);
ezd.StopMark();
// ── 红光预览 ──
await ezd.RedLightMarkAsync();
await ezd.RedLightContourAsync();
await ezd.RedLightEntityAsync("Text1", contour: true);
// 连续红光:在调用线程内循环,需自行 Task.Run 且提供停止条件
Task.Run(() => ezd.RedLightContourLoop(() => shouldStop));
// ── 预览图(HBITMAP 必须手动释放;Cbitmap* 版本不要用 DeleteObject)──
IntPtr hBmp = ezd.GetPreviewBitmap(320, 240);
IntPtr hBmp2 = ezd.GetEntityPreviewBitmap("Text1", 320, 240);
// 需要自定义视口时:GetPreviewBitmap3(width, height, originX, originY, scale)
if (hBmp != IntPtr.Zero) EzdNative.DeleteObject(hBmp);
if (hBmp2 != IntPtr.Zero) EzdNative.DeleteObject(hBmp2);
// ── 组合 / 复制 / 填充(source=填充参数来源对象,target=被填充对象)──
ezd.GroupEntities("A", "B", "AB", 1);
ezd.UnGroupEntity("AB");
ezd.CopyEntity("A", "A_copy");
ezd.SetDefaultHatchParam(enableContour: true, paramIndex: 1, enableHatch: true,
penNo: 1, hatchType: 0, allCalc: false, edge: false, averageLine: false,
Math.PI / 4, 0.05, 0, 0, 0, 0, 0, 1, false, false, 0);
ezd.HatchEntity("Source", "Target");
// ── 笔号参数(完整 32 参数版本)──
ezd.SetPenParam4(1, "MyPen", 0xFF0000, false, false, 1, 1000, 50, 10,
20000, 1, 0, 0, 0, 0, 5000, 0, 0, 0, 0, false, 0, 0, false, 0,
false, 0, 0, 0, false, 0, 0);
// ── IO / 锁存 / 振镜 / 扩展轴 ──
ezd.WritePort(0x00FF);
ezd.ReadPort(out ushort inData);
ezd.LaserOn(false);
ezd.EnableLock(true); // 参数 bLowToHigh:true=启用锁存
ezd.ClearLock();
ezd.GotoPos(100, 100);
ezd.GetCurCoor(out double gx, out double gy);
ezd.InitAxis(true, 0.5, 1, 50, false, 0, 0, 0); // 先 Reset 使能,再写入轴参数
ezd.AxisMoveTo(0, 120);
// ── 条码(barWidthScale / spaceWidthScale 对应手册 double[4],长度必须 ≥4)──
ezd.AddBarcode("123456", "BC1", 0, 0, 0, 1, 1, false, 0, 0, 10, 0.3,
new double[4], new double[4], 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Arial");
// ── 位图参数(按手册 8 参数版本,返回位图路径)──
ezd.GetBitmapParam("BC1", out string bmpPath, out int bmpAttrib, out int scanAttrib,
out double brightness, out double contrast, out double pointTime, out int dpi);
// 完整版:GetBitmapParam2 / SetBitmapParam2(含低灰度值)
// DPI + 256 级灰度映射:GetBitmapParam3 / SetBitmapParam3
// ── 连续飞行缓冲区(SetTextName 最多 6 个;FlyAdd = count + 最多 6 个数据)──
ezd.ContinueBufferSetTextName("T1", "T2");
ezd.ContinueBufferFlyAdd(2, "A", "B");
int flyStart = await ezd.ContinueBufferFlyStartAsync();
// ── 错误处理 / 事件 ──
ezd.OnError += msg => Console.WriteLine($"EZCAD 错误: {msg}");
if (!EzdErrorCode.IsSuccess(flyStart))
Console.WriteLine(EzdErrorCode.GetMessage(flyStart));
// ── 退出:关闭板卡并卸载 MarkEzd.dll ──
ezd.Shutdown();
说明:2.7.1 起按官方手册修正了
lmc1_GetPrevBitmap3、4 个位图参数函数、lmc1_AddBarCodeToLib、lmc1_AddCircleTextToLib、连续飞行、EnableLockInputPort、笔号基础版等声明,并修正了生命周期、x86 平台检查与文档示例。旧版错误签名导致的调用会崩溃,升级后请按上方示例调整调用。
2.8.0 行为变更(升级注意)
- MesTcpClient:请求超时/取消后连接会被重置,需重新
ConnectAsync(LastError会给出提示);响应按\n组帧,支持任意长度(不再有 4 KB 上限)。 - ConfigManager:
OnError由internal改为public;JSON/XML 反序列化默认TypeNameHandling.None;枚举/Nullable/Guid/TimeSpan 等类型可正确往返;Save原子写(临时文件 +File.Replace)。 - LogManager:默认改为异步批量写,新增
Flush/FlushAsync/Shutdown/SynchronousMode;进程退出时自动 flush;同一日志文件的多进程追加使用命名 Mutex 串行化(同会话;跨会话共享文件需改用Global\+ ACL)。 - Excel/CSV:
ExcelHelper.ReadToDataTable列类型统一为object、空文本读为DBNull、按FirstRowUsed/LastRowUsed定位(前导空行不再错位);CsvHelper数值/日期使用InvariantCulture/ ISO 8601,新增流式与编码重载、CsvParseError错误收集。 - NetworkHelper:两参下载方法保留,但不再带 100 秒总超时;请使用新增的
CancellationToken重载,下载失败不会留下半文件。 - Plc(MelsecPlcService):新增
ReadDBatch(轮询自动合并连续地址);轮询失败点保留上次有效值,用新增FailedAddresses/IsComplete/TimestampUtc判断质量;断线后自动退避重连;Dispose立即释放 COM 口;E00仅按 D≥8000 识别。 - 注册码/机器码:机器码改为
SHA256(CPU+硬盘+MAC)定长哈希(旧机器码仍可回退验证);注册码新格式QT2(HMAC-SHA256 签名,旧码可验证;旧客户端无法验证新码,请先升级验证端再发新码);生产环境可通过LicenseSigningKey覆盖内置签名密钥。 - EtherNetIP/Modbus:Modbus MBAP 头修正为标准布局(
TxID/Protocol/Length/UnitId/Func),Ex1121Config新增UnitId(默认 1)与TimeoutMs;收发改为一问一答串行化并校验txId/func;EIP 异步收发有真实超时(TimeoutException),超时后连接失效需重连。UnitId/MBAP 修正需实机抓包复验。 - RemoteIODevice:实现
IAsyncDisposable;同步Dispose()可在IODataUpdated回调内安全调用;即时输出刷新改为 dirty 循环,不再丢写;轮询连续失败自动退避重连。
质量与测试
- 单元测试:
tests/QianTools.Tests(xUnit)。本地运行:dotnet test tests/QianTools.Tests/QianTools.Tests.csproj -c Release - CI:GitHub Actions
CIworkflow 在 push/PR 时构建 net6.0-windows + net8.0-windows 并运行测试 - 打包:
dotnet pack -c Release同时生成QianTools.*.nupkg与QianTools.*.snupkg(SourceLink 已启用,仓库地址 https://github.com/juntaoqian/QianTools)
发布
dotnet pack -c Release
dotnet nuget push bin\Release\QianTools.*.nupkg --source https://api.nuget.org/v3/index.json
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0-windows7.0 is compatible. net7.0-windows was computed. net8.0-windows was computed. net8.0-windows7.0 is compatible. net9.0-windows was computed. net10.0-windows was computed. |
-
net6.0-windows7.0
- ClosedXML (>= 0.105.1)
- Microsoft.Data.Sqlite (>= 8.0.32)
- Newtonsoft.Json (>= 13.0.4)
- SerialPortStream (>= 2.4.2)
- SQLitePCLRaw.bundle_e_sqlite3 (>= 2.1.14)
- System.Diagnostics.PerformanceCounter (>= 8.0.0)
- System.IO.Ports (>= 8.0.0)
- System.Management (>= 8.0.0)
- System.Net.Http (>= 4.3.4)
- System.Text.RegularExpressions (>= 4.3.1)
-
net8.0-windows7.0
- ClosedXML (>= 0.105.1)
- Microsoft.Data.Sqlite (>= 8.0.32)
- Newtonsoft.Json (>= 13.0.4)
- SerialPortStream (>= 2.4.2)
- SQLitePCLRaw.bundle_e_sqlite3 (>= 2.1.14)
- System.Diagnostics.PerformanceCounter (>= 8.0.0)
- System.IO.Ports (>= 8.0.0)
- System.Management (>= 8.0.0)
- System.Net.Http (>= 4.3.4)
- System.Text.RegularExpressions (>= 4.3.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.9.0 | 0 | 10/25/2026 |
| 2.8.0 | 51 | 10/24/2026 |
| 2.7.1 | 50 | 10/23/2026 |
| 2.7.0 | 328 | 7/30/2026 |
| 2.6.5 | 127 | 7/26/2026 |
| 2.6.1 | 127 | 7/26/2026 |
| 2.6.0 | 127 | 7/21/2026 |
| 2.5.0 | 129 | 7/21/2026 |
| 2.4.0 | 123 | 7/21/2026 |
| 2.3.5 | 149 | 5/19/2026 |
| 2.3.4 | 119 | 5/19/2026 |
| 2.3.3 | 136 | 5/19/2026 |
| 2.3.2 | 133 | 5/16/2026 |
| 2.3.1 | 138 | 5/15/2026 |
| 2.3.0 | 138 | 5/15/2026 |
| 2.1.1 | 218 | 12/23/2025 |
| 1.1.3 | 211 | 12/23/2025 |
| 1.1.0 | 218 | 12/23/2025 |
| 1.0.0 | 203 | 7/20/2024 |
2.9.0:新增 QianTools.Plc 工业通信协议库(MIT 洁净室实现,依据官方协议手册,可无实机闭环测试)——① 三菱 MC 客户端 MelsecMcClient(MC 二进制/ASCII 3E 帧,A/E/Q/L 系列,位/字/32 位/浮点读写、跨地址随机读、批读自动分包、中文错误码)与内嵌模拟器 MelsecMcSimulator;② 三菱 FX 串口升级(WriteDBatch 批写、位软元件批读写、T/C 触点访问、FxSeries 系列地址校验,老 API 冻结);③ Modbus RTU/ASCII(ModbusRtuClient/ModbusAsciiClient,功能码 01/02/03/04/05/06/0F/10、CRC16/LRC、IModbusByteTransport 传输抽象);④ 西门子 S7(SiemensS7Client:ISO-on-TCP 握手、PDU 协商、DB/M/I/Q 字节与位读写、类型化大端读写、多区批读、S7Address 地址解析)与内嵌模拟器 S7Simulator;⑤ 公共基座 PlcDeviceBase/PlcSerialDeviceBase(真实超时、断线自动重连、dispose 排空)与 ByteTransform/PlcResult。全部协议经"帧级单测 + 模拟器闭环 + 独立实现交叉对拍"三重验证;同时完成 2.8.0 的评审修复(依赖漏洞清零、Dispose 竞态统一模板、授权时区/时钟回拨修复等,详见仓库 REVIEW.md)。