简介:EzCad二次开发源代码(二)是一套基于EzCad平台实现激光标刻功能扩展的VS工程,适合正在为EzCad编写自定义标刻组件的C++开发者,尤其适合需要动态生成序列号、日期、时间、文本等打标内容的项目。压缩包内共63个文件、55.55MB,包含h/cpp源文件、sln/vcxproj工程配置、rc资源脚本、DLL与LIB等编译产物,同时保留obj、pdb、ilk等中间文件,便于理解编译链接和定位问题。工程核心为XFST_Attribute模块,围绕OLETime、OLEDate、OLENumber、OLEText、EnableText等多个ATL COM类展开,涉及COM接口定义、类型库生成、属性页扩展和注册表写入等环节,可直观看到EzCad扩展组件从源码到DLL注册的完整结构。已有1988人学习下载,对于想通过实际工程研究EzCad二次开发、掌握激光标刻自定义内容实现方式的开发者,具有明确参考价值。
1. 项目概述
EzCad二次开发这个话题,在激光打标行业里算是老生常谈但又绕不开的硬骨头。金橙子EzCad在国内激光打标控制卡市场占有率极高,从光纤打标机到紫外打标机,再到CO₂打标机,几乎都是它家的天下。不管你是设备厂商想把打标功能嵌入自己的软件,还是终端用户想实现自动化产线对接,二次开发都是必经之路。
上一篇文章我讲了EzCad二次开发的基础概念、SDK包目录结构以及最简单的DLL调用示例,算是给刚入门的朋友搭了个台阶。这篇文章(二)我打算深入一层,从实际源代码层面拆解整个二次开发的链路,包括核心结构体、eZd文件解析、实体数据结构、以及几个高频场景的代码实现。技术这东西,光看文档永远学不会,一定要动手调试,踩坑,然后再回头看文档,才会有质的提升。
这篇内容适合两类人:一是已经跑通了第一个Demo、但想进一步理解EzCad内部数据模型的开发者,二是正在做自动化集成、需要在外部程序里动态生成或者修改打标内容的工程师。如果你完全没接触过EzCad,建议先翻一下我之前那篇基础篇,把SDK环境搭起来再回来看,否则有些地方可能跟不上。
2. 二次开发的核心链路与源码骨架
2.1 从调用方式看EzCad的程序架构
EzCad二次开发基本可以分成两条技术路线:一是把我们的程序做成DLL插件,加载到EzCad主程序里直接运行;二是写一个独立的EXE程序,通过EzCad提供的服务接口去控制和通讯。这两条路线在实际项目中应用场景完全不同,插件方式适合在原有打标界面上扩展功能,比如加一个专门的打标参数界面;而独立EXE方式则更适合自动化产线的集成,比如对接PLC、对接MES系统,把打标数据从数据库里取出来直接发送。
我个人的经验是,做设备集成的场景里,独立EXE的方式更主流,因为它不依赖EzCad主界面的操作流程,也不占用License的交互时间,逻辑更清晰。但独立EXE方式要控制打标,需要走EzCad的Service模式,或者通过调用ezcad2.dll的接口进行指令下发。看网上搜索热度很高的“solidworks二次开发”“NX二次开发”也能理解到,工业软件的二次开发在调内核和操作封装层上,逻辑上都差不多。
2.2 源码工程结构规划
规划二次开发源码工程时,我习惯把工程拆成三个层次,这样做的好处非常明显——代码清晰、可维护性强,也不会因为同一个变量的命名问题把自己搞晕。
底层是SDK封装层,主要把ezcad2.dll导出的接口函数重新用typedef定义函数指针,再用一个类统一封装成C++接口,比如连接设备、加载文件、打标、停止等操作。封装层是整个二次开发的基石,能少走很多弯路,尤其是后续版本升级时,只需要替换封装层,业务层基本不用动。
中间层是数据结构层,也就是我们常说的eZd文件读写与实体解析层。EzCad图形文件格式一般是eZd格式,程序员需要能读取这里的图层、实体、参数,甚至有时候要直接操作文件里的文本对象来改打标内容。
最上层是业务逻辑层,这一层和我们自己的具体业务直接挂钩,比如从数据库读订单号生成序列号、从称重传感器读取重量信息配合打标、从视觉系统获取坐标进行位置校准等。业务层代码要尽可能独立于底层SDK,这样就算换了一家打标卡方案,也能最大程度复用现有逻辑。
2.3 为什么需要理解eZd文件格式
很多做EzCad二次开发的朋友刚开始容易忽略一个问题:以为二次开发就是去调用导出函数,然后传文件路径就行。但这种思路在处理动态内容时就会碰壁。比如客户要求每个产品上打标一个不同的二维码内容,这些数据不能每次都在EzCad界面里手改,那就必须在自己的程序里动态修改eZd文件的结构,或者通过SDK接口去调整文档里文本对象、条码对象的属性。
如果不懂eZd文件的数据结构布局,就不知道去哪里改文本内容、去哪里调整条码的数据源、又怎么去控制它拉伸或旋转。源码里默认模板文件虽然是可视化的XML风格,但真正设备里运行的eZd文件是二进制编码结构。要做一个成熟的二次开发项目,必须啃下这一块。
3. 核心数据结构与源码关键模块分析
3.1 EzCad SDK的主要导出函数族
先回顾一下ezcad2.dll对外导出的几组核心API,这是二次开发源码里调用最频繁的部分。网上的资料很多都罗列了函数名,但很少说明这些函数族在源码工程中如何组织使用,我在这里按功能分成几组,方便大家对照实际工程代码来理解。
一组是初始化与连接类,包括Config()、GetDevStatus()这些,用于初始化SDK、获取当前软件连接状态;另一组是文件操作与实体操作类,比如LoadEzdFile()、SaveEzdFile()、Mark()等,负责文件加载、保存和触发打标;还有一组是参数设置类,如SetMarkParam()、SetTextParam()等,用于修改打标参数和文字内容。
实际写代码时建议做一层包装类,以C++风格面向对象的接口暴露给业务层调用,这样不用每次一个个裸调用这些C风格接口,代码可读性也会好很多。举例来说,我封装的CEzCadEngine类里有类似LoadFie、SetText、SetTime、Mark这些方法,内部再映射到对应的DLL导出函数。
3.2 结构体数据模型
源码中反复出现的数据结构主要有MarkData、HatchData、EntData,以及各种图形实体对应的数据结构。如果不搞懂这几个结构体之间是什么关系,调试代码时很容易出现内存错乱或者写错字段的尴尬情况。
MarkData结构描述的是打标参数集,比如打标速度、激光功率、频率、开光延时、关光延时等,这些参数直接决定打标质量;HatchData结构对应填充参数,比如填充角度、填充线间距、填充边界参数等;EntData封装了所有实体的公共属性,比如图层号、线宽、颜色、坐标变换矩阵等。简单理解就是:一个文档里包含多个实体,每个实体都有自己的公共属性(EntData)和专用数据(文本实体的字体、内容,条码实体的码制、数据),而整个打标过程共用一组MarkData和HatchData作为默认参数。
3.3 eZd文件二进制结构解析
二进制层面去看,eZd文件本身有清晰的块结构。分析源码会发现文件头保存了版本信息和总实体数量,紧接着是图层信息区,然后每段对应一个图形实体。每个实体块内,先是实体类型ID和公共属性块,然后根据不同实体类型加载不同参数段。
用二进制编辑器打开一个最简单的eZd文件,你会发现文本对象基本靠偏移量定位数据区,不同版本的EzCad对这些偏移量定义略有不同。所以二次开发时,我更推荐通过SDK提供的接口来读取和修改实体,而不是直接去解析二进制文件,因为格式一旦因为版本升级出现微调,硬解析的代码就崩了。但是在没有动态库源码、只有离线文件需要批量修改的场景下,手工解析eZd文件也不可避免,后面我会给一个解析示例。
4. 一个可编译的EzCad插件模块源码解析
4.1 工程搭建和入口函数
这里我直接以C++开发环境为例,给大家一个最小可用的插件源码骨架。无论你是用VC6.0还是VS2019,第一步都是新建一个Win32 DLL项目,然后配置包含目录指向EzCad的SDK头文件,在链接器里加入ezcad2.lib。
先看入口部分:
// EzCadPluginDemo.cpp : 定义 DLL 应用程序的导出函数 // #include "stdafx.h" #include "EzCadPluginDemo.h" // 全局句柄,供后续导出函数使用 HMODULE g_hModule = NULL; BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: g_hModule = hModule; // 插件加载时做必要的初始化 break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: // 插件卸载时释放资源 break; } return TRUE; }这段代码本身不复杂,但你得知道DllMain里最好只做简单的初始化操作。比如加载配置参数、初始化全局状态等,不要在DllMain里做耗时操作,也不要去调用可能会加载其他DLL的函数,否则很容易死锁或者拉长加载时间。
4.2 导出函数实现
插件核心是导出几个函数,EzCad主程序会在特定时机回调这些函数。以经典的回调函数为例,接口声明为:
// EzCad插件导出函数 extern "C" __declspec(dllexport) int __stdcall EzCadPlugin_Init(void) { // 初始化插件资源、读取配置文件 return 0; // 0表示成功 } extern "C" __declspec(dllexport) int __stdcall EzCadPlugin_Mark(const char* pszText) { // 当主程序触发打标时,这个函数会被调用 // 通常在这里根据外部传入的文本内容,调用SDK的文本修改接口 return 0; }真正在项目里,你可能要处理的点还很多,比如回调函数里拿到主程序的窗口句柄,或者通过主程序提供的接口去访问当前文档里的实体列表。使用DbgView或者OutputDebugString输出调试信息在DLL开发阶段非常重要,因为断点调试会被主程序干扰,调试体验不太好。
4.3 动态修改文本并触发打标
下面这个例子是实打实高频使用的场景:外部程序传入字符串,插件自动修改当前eZd文件里的文本实体内容,然后直接触发打标。核心逻辑如下:
// 修改指定文本项内容 void SetTextContent(const char* pszOriginText, const char* pszNewText) { // 1. 获取当前文档实体管理器 // 2. 遍历实体,查找文本内容与pszOriginText匹配的实体 // 3. 调用文本实体的设置接口,写入pszNewText // 4. 刷新显示 }很多第一次写EzCad插件的人会遇到一个问题:自己通过SDK接口改了文本内容,但主程序界面上的显示并没有刷新,打标出来的数据依旧是最初的。那是因为修改完成后需要通知主程序做视图刷新,有些SDK版本要调用特定的刷新函数,有些则需要模拟界面事件。我们在源码里封装的RefreshView()函数就是为了解决这个问题的。
5. 高频应用场景的二次封装与踩坑记录
5.1 外部程序独立控制EzCad实现自动打标
除了插件方式,还有一种场景是把EzCad当作独立的服务去控制,比如用户程序里手动输入产品信息,点击按钮后自动打标。这个场景我通常直接用SDK的二次开发接口做外部控制,整体流程是:连接设备,加载模板文件,修改模板里的文本或者条码内容,设置打标参数,触发打标,然后等待打标完成事件。
// 外部控制示例代码 bool AutoMark(const char* pszEzdFile, const char* pszContent, int nCount) { // 连接设备 if (!ConnectDevice()) return false; // 加载文件 if (!LoadEzdFile(pszEzdFile)) return false; // 循环打标 for (int i = 0; i < nCount; i++) { char szBuf[256]; sprintf_s(szBuf, "%s_%03d", pszContent, i + 1); // 修改文本对象 SetTextContent("DefaultText", szBuf); // 触发打标 if (!Mark()) return false; // 等待打标结束 WaitForMarkFinished(10000); } return true; }这段流程看起来很简单,但实际上的坑都在细节里。WaitForMarkFinished这个函数,不同的EzCad版本实现方式不一样,有的版本通过回调事件实现,有的版本需要轮询某个状态查询接口。如果你拿到的SDK文档里没有提供事件回调,那就老老实实轮询,轮询间隔建议不要小于50ms,否则CPU占用率会飙升。
5.2 序列号自动累加和条码内容动态生成
另一个高频场景是序列号打标。比如做铭牌或者PCB板打标,每块板子都要打一个递增的序列号。这个场景的核心不是图形设计,而是序列号的生成规则和重复性检查。
我的做法是在业务层单独维护一个序列号生成器,每次打标前从数据库或者本地文件读取当前值,生成新序列号后写入文本对象,打标完成再把新值回写。这个过程中最容易犯的错误是:多个线程同时调用打标接口,导致序列号重复。所以我们的源码里给打标流程加了一个互斥锁,保证同一时间只有一个打标任务在运行。
对于条码内容,EzCad的条码实体支持多种码制,比如Code128、QRCode、DataMatrix等。动态修改条码内容时需要注意一点:不同的码制对字符集有要求,比如有些条码不支持中文,有些条码有长度限制。这个不是EzCad的限制,而是条码本身的规范。真遇到这种问题,要么换码制,要么做内容转换。
5.3 坐标变换与位置校准
做过视觉定位打标的人应该深有体会,坐标变换是整个系统中容易出错但又不容有失的环节。EzCad里图形坐标系以毫米为单位,视觉系统给出的坐标往往也是毫米,但这个“毫米”对应的原点和旋转方向可能不一致。
我的建议是:所有坐标变换统一在一个独立的模块里处理,不要散落在各个业务函数里。比如视觉给一个坐标(x,y,angle),我们先用一个TransformCoord函数把它换算成EzCad坐标系下的坐标,再调用SDK接口移动指定实体到这个位置。这样即使后续换了视觉品牌或者换了安装方式,也只有这个模块需要修改。
struct Point2D { double x; double y; double angle; }; Point2D TransformCoord(const Point2D& raw) { Point2D result; // 这里实现平移、旋转、缩放变换 // 参数来自标定阶段计算出的变换矩阵 result.x = ...; result.y = ...; result.angle = raw.angle + offset_angle; return result; }6. 常见问题与排查技巧实录
6.1 问题速查表
下面列一些我这些年做EzCad二次开发时遇到的典型问题,大家对照排查。整理成表格,方便直接参考。
| 问题现象 | 常见原因 | 处理办法 |
|---|---|---|
| DLL插件加载失败 | 编译平台位数和主程序不一致 | 确认编译成x86还是x64,必须和EzCad主程序一致 |
| 调用打标接口无反应 | 设备未连接,或打标参数错误 | 检查设备连接状态,输出MarkData参数确认合法性 |
| 修改文字不生效 | 修改后未刷新界面 | 调用对应刷新接口或模拟界面刷新事件 |
| 条码内容变成乱码 | 字符编码不匹配 | 统一使用UTF-8或按SDK要求传GBK编码 |
| 打标速度变慢 | 填充参数设置不合理 | 调整填充类型和间距,避免过密填充 |
| 二次开发程序崩溃 | 指针或句柄未判空 | 每次调用SDK接口前检查返回值和指针有效性 |
| eZd文件解析出错 | 版本不匹配 | 先检查文件头版本号,再按对应版本结构解析 |
6.2 调试技巧与独家心得
我调试EzCad二次开发程序时的核心原则是:能输出日志绝不只靠断点。因为DLL运行在主程序进程内,一旦崩溃,整个主程序都退出了,分析现场很难。所以我在封装层里做了日志模块,关键接口调用前后都会记录参数和返回值,定位问题会快很多。
另外有一点值得注意,EzCad二次开发里的文件路径,尤其是模板文件路径,建议都用绝对路径。因为相对路径的当前目录是主程序的工作目录,不是你外部程序的工作目录,很容易出现“我在电脑上运行好好的,部署到客户机器上就找不到文件”的尴尬情况。
还有一个经验是:每次发布前,把SDK附带的所有DLL都拷贝到输出目录,哪怕你暂时用不到。因为主程序启动时加载DLL是有顺序的,文件缺失可能导致整个程序打不开,而且报错信息很隐晦,排查起来非常浪费时间。
6.3 版本兼容性处理
EzCad本身经历过几次大版本迭代,比如ezcad2和ezcad3的接口差异就比较大。如果你的客户既有老设备又有新设备,建议在封装层做一层兼容适配。我这里会预先准备几个接口适配宏,根据编译时选择的SDK版本自动决定调用哪套接口函数,这样业务层代码就可以完全不用关心底层是哪个版本。
#if defined(EZCAD2_SDK) typedef int (*PFN_MARK)(void); #elif defined(EZCAD3_SDK) typedef int (*PFN_MARK)(MARK_PARAM* pParam); #endif这种做法在大型项目中会节省大量时间和精力,不然每次切换设备型号都要改业务代码,改到后来自己都分不清哪些代码是给哪台设备用的。
7. 从二次开发到系统集成的进阶思考
当二次开发做到一定深度,你会发现代码本身已经不是最核心的壁垒了,反而对业务场景的理解决定了方案的上限。比如客户车间里有多台激光打标机,操作系统也不统一,这个时候你设计的外部控制程序就必须考虑网络分发、任务队列、重试机制、权限管理、数据追溯这些工程化的问题,而不仅仅是调用打标接口那么简单。
我的源码布局里会单独建一个TaskManager模块,把所有打标任务统一排入队列,由工作线程逐条执行;同时建立对应的数据表记录任务状态,方便现场操作员随时查看每一个任务是否完成、是否成功。这套模式基本可以应对市面上绝大多数激光打标自动化需求。
参考网上其他二次开发热词,比如“solidworks二次开发”“海康威视二次开发手册”等,大家会发现,软件二次开发的核心逻辑是相通的。EzCad的二次开发也一样,初期学习重点是语法、接口和数据结构的理解,中期重点是如何把点、线、文字、条码组合出可落地的整套工艺方案,后期反而拼的是业务理解力和架构能力。
8. 写在最后
我团队内部做EzCad二次开发的一个原则是:不要在别人封装好的SDK基础上盲目堆代码,先花半天时间把官方提供的示例源码完整读一遍,再对照自己的需求去裁剪和扩展。很多人一上来就写业务逻辑,出了问题再去翻示例,效率非常低。
还有一个小技巧值得分享:源码版本管理一定要从一开始就做好。EzCad的二次开发项目迭代速度不慢,而且不同客户的模板文件、SDK版本、定制需求各有差异,如果没有Git或SVN管理,版本回退会非常痛苦。我们吃过这个亏,后来把所有客户的定制版本都打了Tag,现在维护起来很省心。
这篇文章的侧重点是源码结构和实战代码,下一篇文章我打算专门拆解eZd文件二进制格式的详细解析过程,包括每个数据块的偏移定义、实体参数区的字节布局、以及如何绕过SDK直接离线生成打标文件。如果你们有在实际二次开发中遇到特别棘手的问题,也可以在评论区留言,我会挑典型问题在后续文章里展开分析。
本文还有配套的精品资源,点击获取