金橙子防重码二次开发:C#与SQLite实现打标断点续传与异常恢复

📅 发布时间:2026/9/29 17:08:03
金橙子防重码二次开发:C#与SQLite实现打标断点续传与异常恢复
简介一份面向工业打标场景的金橙子防重码打标软件单机版二次开发资源包适合需要在.NET平台下对打标软件进行功能扩展、界面定制或性能优化的开发人员。资源核心在于完整呈现了基于MDB数据表的防重码机制通过“西泵MDB_V14”数据库文件可直观了解特定业务环境下的数据组织与版本迭代思路为二次开发提供了真实的研究样本。包内共374个文件压缩后约7.95MB以C#源码cs、界面图片bmp、ico、resx、动态链接库dll以及可执行程序exe为主同时包含大量ini、config配置文件与Visual Studio工程文件便于定位打标参数、启动流程和界面逻辑。已有2988人学习下载。对于希望通过真实项目源码快速理解防重码打标业务流程的开发者本包提供了从数据库结构、配置管理到核心算法的完整分析线索尤其适合在此基础上进行功能增加、规则调整或界面汉化等二次开发实践。1. 防重码二次开发这不是“防止手滑”而是防“软件自己失忆”汽车零部件厂一条阀体打标线质检反馈两件产品的追溯码完全相同整批冻结逐件翻工单才能放行。金橙子防重码打标软件二次开发冲的正是这个洞把“下一个该打什么号”这件事从打标进程的临时记忆里拿出来放进一个断电、死机、人为重开都不容易丢的地方。打标软件一旦断电重启、激光器中途报警、操作工手动终止上位机还能准确续上断点而不是回头把上一个号再打一遍。这篇文章适合激光打标设备的上位机工程师、非标集成商和产线改造方。你要做的不是改金橙子的界面而是用它的动态库把它对接到你们自己的防重码数据流里。2. 金橙子API选型与最小调用链防重码功能要落在哪一层2.1 三条开发路线对比为什么防重码必须走动态库做金橙子二次开发之前先分清三条路线直接用金橙子界面配“变量文本”做递增号调 EzCad2 动态库自己做上位机或者用 ActiveX 控件快速搭一个演示程序。三者在防重码这件事上的能力差别非常大我见过不少集成商在第一条路线上反复翻车。路线适合场景防重码上的短板金橙子界面 变量文本单机人工操作、客户自己维护递增计数器的状态存在进程内存或图档属性里断电重启容易回到旧值EzCad2 动态库上位机自动化、多工位产线需要自己管理编码分配但这类 API 能拿到完整流程控制权ActiveX 控件快速原型、演示、工具类软件带界面和弹窗无人值守时容易被错误对话框卡住图档内脚本简单规则运算脚本宿主一崩状态一并丢失不适合做账本实际产线里最危险的做法是把金橙子“变量文本”的当前值定时写回一个配置文件开机后再读回来。比如每打一个号就写一行文本看起来已经有“记录”了但只要写回动作发生在打标中途最后一行到底是“已打”还是“未打”就变成一笔糊涂账。金橙子做二次开发时把这些接口放开意思很明确打标动作归它负责业务闭环必须由你在外面接住。防重码功能的正确落点一定是在你的上位机程序里而不是在金橙子的图档属性里。你的程序负责决定“打哪个号”金橙子负责“把那个号标记到工件上”。这种分工才能让编码分配具备事务性。2.2 最小调用链一个C#程序把图档里的“编号文本”打出来防重码系统的第一个技术动作是在自己的程序里把金橙子图档加载起来替换序列号文本然后触发打标。下面这段是 C# 里最常见的 P/Invoke 声明接口名称以你拿到的 EzCad2Api.h 为准但整体调用链在几个版本间差别不大。[DllImport(EzCad2.dll, CharSet CharSet.Ansi, CallingConvention CallingConvention.StdCall)] internal static extern IntPtr EzCad2BeginSession(string serverPath, string regCode); [DllImport(EzCad2.dll, CharSet CharSet.Ansi, CallingConvention CallingConvention.StdCall)] internal static extern IntPtr EzCad2LoadFromFile(IntPtr hSession, string fileName); [DllImport(EzCad2.dll, CharSet CharSet.Ansi, CallingConvention CallingConvention.StdCall)] internal static extern bool EzCad2SetTextValue(IntPtr hEzdFile, int entIdx, int textIdx, string newValue); [DllImport(EzCad2.dll, CharSet CharSet.Ansi, CallingConvention CallingConvention.StdCall)] internal static extern bool EzCad2MarkTheText(IntPtr hEzdFile, int entIdx, int textIdx); [DllImport(EzCad2.dll, CharSet CharSet.Ansi, CallingConvention CallingConvention.StdCall)] internal static extern bool EzCad2EndSession(IntPtr hSession);调用链如下建立会话、加载 .eud 图档、替换文本对象、触发打标、关闭会话。public bool MarkSerialText(string eudPath, string serialCode) { IntPtr hSession EzCad2BeginSession(C:\\Program Files\\金橙子\\EzCad2, ); if (hSession IntPtr.Zero) throw new InvalidOperationException(会话建立失败检查加密狗和ServerPath); IntPtr hEzd EzCad2LoadFromFile(hSession, eudPath); if (hEzd IntPtr.Zero) throw new FileNotFoundException(图档加载失败检查EUD文件路径是否可读); bool setOk EzCad2SetTextValue(hEzd, 0, 0, serialCode); bool markOk EzCad2MarkTheText(hEzd, 0, 0); EzCad2EndSession(hSession); return setOk markOk; }几个参数值得单独说明。serverPath 是金橙子安装目录不是 DLL 所在目录也不是图档目录这个目录里放着字体、激光参数、板卡配置路径错了会话直接失败。entIdx 是文本对象在图档对象列表里的序号textIdx 是同一个文本对象内的子文本序号多数序列号图档只有一个文本对象所以写成 0,0 就能工作。如果你的图档里还有 LOGO、二维码、静态图形不要用整图打标接口只替换编号文本再打文本可以避免把整个图层重复标记。还有一点要提醒EzCad2MarkTheText 返回 true 只表示打标流程执行完了不一定代表激光真的出了光。这个黑匣子问题会在后面单独展开。开发环境那边先确认控制卡是 32 位还是 64 位程序的目标平台要和 DLL 一致用 x64 程序直接调老版本 32 位 EzCad2.dll第一轮测试通常就崩在加载阶段。3. 防重码状态机设计把“下一个编码”变成数据库里不可丢的记录3.1 先想清楚防重码防的不是重是“无法恢复”很多人第一次听到“防重码”第一反应是“发号的时候不重就行”。但真正上线过就知道一切正常时谁都不会重复出问题永远出在异常时刻断电、重启、程序被杀、网络瞬断。所以防重码的核心能力不是“避免重复”而是“断电之后还能判断出哪个号打过了、哪个号没打并且把断点续到正确位置”。金橙子自带的变量文本递增长度、批次起始值它把“下一个号”放在一个进程内部。你正常一口气打完 1000 件没问题可一旦打到 537 件时突然断电重启后金橙子可能从 500 或者 536 重新开始。这时产品已经流到下一道工序你根本没有机会把重复的码拦下来。损失不是软件崩溃本身而是后续追溯链里出现了两条相同的记录。正确的思路是编码的分配权和确认权全部从打标进程里拿出来。金橙子只保留“把这个字符串打上去”的执行权。你的数据库里永远有一条记录写明这个编码当前处于什么状态。电脑断电了没关系重启后看数据库每一笔账都还在。3.2 记录表与三状态预占、已打、已确认本地数据库用一个轻量 SQLite 文件就够不需要一上来就接 MES。表结构可以直接参考下面这份关键字段是 serial_code 的唯一约束和 state 状态位。CREATE TABLE IF NOT EXISTS mark_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_no TEXT NOT NULL, serial_code TEXT NOT NULL UNIQUE, state INTEGER NOT NULL DEFAULT 0, machine_no TEXT NOT NULL DEFAULT M01, create_time TEXT NOT NULL, mark_time TEXT, confirm_time TEXT ); CREATE UNIQUE INDEX IF NOT EXISTS ux_mark_serial ON mark_log(serial_code);三个状态的含义如下state含义写入时机0已预占未打标取号成功、写入数据库之后1已打标待确认金橙子 DLL 打标动作返回之后2已完成扫码枪或人工确认之后“预占”这两个字是整个防重码方案里最核心的动作。只要一个编码先写进了数据库别的工位再想用同一个编码INSERT 就会被唯一约束弹回来。哪怕程序只有一个工位预占也保证了“取号”和“打标”之间如果断电数据库里至少留下了一条待确认记录而不是什么都不剩。state 从 0 到 2 的迁移顺序就是产品在产线上移动的顺序。每打一件先取号占位再打标最后确认。确认这个动作不该省因为打标动作返回不代表工件上真的有字这个后面避坑部分再细说。3.3 断电对账开机第一件事是查遗留任务不是续打最容易犯的错误是开机后直接读数据库里“最大的编号”然后继续往下发。如果断电发生在打标已经完成、数据库还没更新状态那一刻那一件产品的实际码比数据库里记录的大一号此时你从记录的最大号继续走下一件产品必然重码。正确的开机流程是找出所有 state0 且没有被确认的记录先做一次“遗留任务确认”。这些编码都是上次程序中断时留下的其中一部分可能已经打到了工件上另一部分根本没打。确认手段可以是操作工手持扫码枪扫一遍也可以是由视觉系统把工件码读回来比对。我一般会把这条逻辑做成一个独立的“恢复对账”界面列出所有遗留编码操作工确认“已打”的置成 state1确认“未打”的退回 state0重新进入待取号队列。整个过程不静默不自动续打宁可让产线多停两分钟也不能让一个坏号溜到后面去。防重码系统只要在这个环节挺住了后续重复码的概率基本趋近于零。4. 用C#实现取号-打标-确认闭环可直接照抄的核心代码4.1 数据库层SQLite加WAL本地先落盘再与MES同步实际产线环境网络质量参差不齐工位电脑离交换机远、车间里有变频器和电焊机干扰网络瞬断几乎是常态。如果每一步取号都直接请求远程 MES断网时整条线直接卡死。这就是我坚持先用本地 SQLite 的原因本地先记账再通过后台任务同步到 MES、金蝶或者 ERP。SQLite 连接最好加上两行关键配置WAL 日志模式和 busy timeout。WAL 模式在断电后恢复能力更好busy_timeout 可以避免多线程写库时立刻抛“database is locked”。public sealed class MarkDatabase { private SQLiteConnection _conn; public MarkDatabase(string dbPath) { _conn new SQLiteConnection($Data Source{dbPath};PoolingTrue;); } public void Initialize() { _conn.Execute(PRAGMA journal_modeWAL;); _conn.Execute(PRAGMA busy_timeout5000;); string ddl CREATE TABLE IF NOT EXISTS mark_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_no TEXT NOT NULL, serial_code TEXT NOT NULL UNIQUE, state INTEGER NOT NULL DEFAULT 0, machine_no TEXT NOT NULL DEFAULT M01, create_time TEXT NOT NULL, mark_time TEXT, confirm_time TEXT ); CREATE UNIQUE INDEX IF NOT EXISTS ux_mark_serial ON mark_log(serial_code);; _conn.Execute(ddl); } }serial_code 上的唯一索引就是防重码的“最后一道保险”。不管上层代码怎么并发、怎么出 bug同一把编码第二次 INSERT 时数据库会直接拒绝。这个约束必须放在数据库层不能只靠业务代码判断因为业务代码判断永远存在时间窗口。4.2 取号与打标一个事务把“预占打标回写”串起来核心方法分两步先用 BEGIN IMMEDIATE 获取写锁再 INSERT 占号。SQLite 的 BEGIN IMMEDIATE 会立即拿写锁多个工位、多个线程同时取号时后面的人会排队不会出现两个人读到同一个“当前最大值”的情况。public string ReserveAndMark(string taskNo, string eudPath) { string serialCode BuildSerialCode(taskNo); _conn.Execute(BEGIN IMMEDIATE); try { _conn.Execute( INSERT INTO mark_log(task_no, serial_code, state, create_time) VALUES(t,c,0,now), new { t taskNo, c serialCode, now DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss) }); } catch (SQLiteException ex) { _conn.Execute(ROLLBACK); if (ex.ResultCode SQLiteErrorCode.Constraint_PrimaryKey) throw new InvalidOperationException($编号{serialCode}已被占用取号失败); throw; } bool markOk MarkSerialText(eudPath, serialCode); if (markOk) { _conn.Execute( UPDATE mark_log SET state1, mark_timenow WHERE serial_codec, new { c serialCode, now DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss) }); _conn.Execute(COMMIT); return serialCode; } else { _conn.Execute(DELETE FROM mark_log WHERE serial_codec AND state0, new { c serialCode }); _conn.Execute(COMMIT); throw new InvalidOperationException(打标失败已释放该编码); } }这段代码有三个细节需要注意。第一取号用 INSERT 而不是 SELECT MAX1 再去 UPDATE因为 INSERT 天然受唯一约束保护。第二打标失败时删除的是 state0 的记录这个号相当于“退还”了下次取号还能再分配如果客户要求编码绝对连续不许跳号就把 DELETE 改成 UPDATE state 保持 0让它在恢复环节被重新处理。第三markOk 为 true 后立即更新 state1这只是“打标动作已完成”不代表“工件确认合格”后面再通过扫码把状态推进到 2。BuildSerialCode 的生成规则建议至少包含日期和序号两部分例如D DateTime.Now.ToString(yyMMdd) M01 seq:D5避免跨工单、跨机台时出现自然重复。数据库的唯一索引在此时负责兜底真的出现冲突它就是最直接的报警信号。4.3 确认环节人工确认和扫码确认的两种接法state 从 1 变成 2 的确认动作主要接两条路人工扫码确认或者自动视觉确认。人工确认最简单操作工在下料工位用手持扫码枪扫工件上的码扫码内容作为参数调 Confirm 方法即可。public bool Confirm(string serialCode) { _conn.Execute(BEGIN IMMEDIATE); try { _conn.Execute( UPDATE mark_log SET state2, confirm_timenow WHERE serial_codec, new { c serialCode, now DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss) }); _conn.Execute(COMMIT); return true; } catch { _conn.Execute(ROLLBACK); return false; } }自动确认通常走串口扫码器或者视觉平台。扫码枪用串口接工控机时数据接收事件里读一行、确认一行逻辑非常直接。视觉方案则要等相机或 OCR 软件返回读码结果我见过不少产线会同时做国产视觉平台的二次开发让视觉系统读回工件码之后直接从后台调 Confirm整个流程不需要人为干预。确认环节无论如何不能省它补的正是金橙子 DLL 返回 true 但工件上实际没有字这条缝隙。5. 避坑金橙子防重码二次开发最容易翻车的五处细节5.1 现象一会话初始化崩溃十有八九是位数和加密狗问题现象程序一调 EzCad2BeginSession 就报 DllNotFoundException或者返回的句柄是空再往下调用直接崩溃。原因最常见的是进程位数和 DLL 位数不一致。很多工控机上还跑着 32 位的数据库驱动整个程序被设成了 x64结果 32 位的 EzCad2.dll 加载不进来。第二种原因是 serverPath 写错金橙子安装目录里的字体和板卡配置找不到会话自然起不来。第三种是加密狗驱动没装regCode 传了什么都不管用。解决把项目目标平台固定成 x86 或 x64 中与 DLL 一致的那个别用 AnyCPU。serverPath 用安装目录全路径不要用相对路径。首次调试前先手动双击打开一次金橙子打标软件确认加密狗能被识别再用空字符串 regCode 初始化会话。如果程序是 Windows 服务方式运行还要注意 Session 0 隔离服务里调 UI 型 ActiveX 控件基本不可行老老实实用动态库。5.2 现象二断电后第一件产品出现重码现象前一天最后一个号打到了 A1006第二天开机第一个工件打出来的还是 A1006操作工当场叫停。原因你的程序还在从金橙子图档的变量文本里取“当前值”或者从自己写的记录文件里读“最后一行”。这两种来源在断电瞬间都可能停留在“还没写最新值”的状态。换句话说你信了一个不能保证持久化的记忆体。解决打标程序里所有编码的唯一来源是外部数据库。变量文本只负责显示不负责计数记录文件只做调试日志不参与取号。每次开机先做 3.3 节里的遗留任务对账确认没有 state0 的残留记录再允许进入自动打标循环。把这个流程固化成代码逻辑不要依赖操作工“记得昨天打到几号”。5.3 现象三DLL返回true工件上却没字现象程序日志显示 EzCad2MarkTheText 返回 true但工件表面干干净净或者只有一道很浅的痕迹。原因金橙子的“打标文本”接口返回时只代表打标流程已经执行完不代表激光器真的出光了。图档里文本对象可能被设成不可见激光器出光使能信号没打开或者打标参数为空都会出现流程走完了但工件上什么都没有的情况。解决把防重码系统的确认点放在外界反馈上而不是 DLL 返回值上。要么读取激光电源的“出光反馈”IO 信号要么在打标工位后面放一把扫码枪或者视觉相机读到码才算成功。产线上如果已经接了视觉平台的二次开发正好把这个结果当作 state 1 到 2 的推进依据。DLL 返回值可以当“执行完毕”信号但不能当“质量合格”信号。5.4 现象四双工位同时取号取到了同一个号现象两台打标机同时开工运行一小时后查记录A 工位和 B 工位在某一段出现了完全相同的编码。原因代码里用了典型的“先查最大值再加一”写法两个进程同时执行 SELECT MAX拿到同一个值再把各自的号写进本地文件等发现时货已经打完。解决所有工位共用同一个数据库文件严格走 4.2 节里的 INSERT 预占流程。数据库唯一索引会在第二个工位插入同码时直接抛异常代码里捕获这个异常后让该工位停止并报警。另外各工位最好在编码里带上自己的机台号比如 M01、M02即使数据库被误拆成两份也能从编码结构上区分来源。5.5 现象五和MES或ERP联动时网络一抖整条线停掉现象MES 数据库连着车间网络某天交换机闪断工位取号超时整条打标线停摆前后工序一起堵料。原因把取号动作做成了远程强依赖。很多集成商做 MES 对接时直接把远程表当成本地表用每打一个号都去远程库 INSERT网络异常时没有任何降级方案。解决本地 SQLite 做主库MES、金蝶这类业务系统只做同步。本地打标完成后后台线程把新记录同步到远程同步不成功就标记一个同步状态字段网络恢复后自动补传。远程数据库同样要给 serial_code 建唯一索引同步时如果发现远程库里已有同码说明本地和远程曾经各发过一次号立刻生成冲突报表。防重码系统可以允许暂时断网但绝不能允许两边各记一笔账。6. 让这套防重码系统真正上产线的最后一公里给防重码系统的验收清单和开线习惯6.1 一张验收清单把“防重码”从口号变成可测指标开发完成后不要急着上产线先在调试机上跑一轮异常注入测试。下面这张清单是我每次做防重码验收时必跑的项缺一项都不签字。验证项做法通过标准断电恢复打标过程中直接拉掉工控机电源重启后弹出待确认记录确认后从正确断点续打全流程无同码进程被杀打标循环中强制结束上位机进程数据库残留记录可被恢复流程识别并处理并发取号三台工位同时启动 3000 件任务无同码数据库中无唯一约束报警打标失败回滚人为关闭激光出光信号再触发打标当前编码被释放或保留待处理不重复发放扫码确认打标完成后全部工件扫码回读300 件连续扫码码内容与数据库导出清单完全一致这套清单的目的很简单在真正生产前把最恶劣的情况都先模拟一遍。产线上不会有预告的断电防重码系统就是为没有预告的瞬间设计的。6.2 开线习惯我保留的两个“土办法”自动化做得再好我仍要求操作工保留两个每天必做的动作。第一早上开工前跑一条查重 SQL把前一天的 mark_log 按 serial_code 分组找出重复项SELECT serial_code, COUNT(*) AS cnt FROM mark_log GROUP BY serial_code HAVING COUNT(*) 1;这条 SQL 在 SQLite 命令行里就能跑不需要任何额外工具。正常情况下返回 0 行一旦返回任何记录开线立刻暂停先把问题查清再继续。第二每天下班时导出当日打标清单跟最后一箱工件的实物码核对尾号。人工对账不先进但它是最后一道不依赖任何程序的防线。序列号这东西打错了没有后悔药。防重码二次开发做到最后比代码更重要的是把“账”留清楚哪个号分给了哪台机、什么时候打的、谁确认的、后来怎么处理的。数据库里不物理删除记录所有失败、作废、重打的码都保留状态以后追溯时能完整还原那一条编码的一生。这是我做了几年产线追溯系统后保留下来的习惯希望对你有用。本文还有配套的精品资源点击获取