news 2026/10/20 15:39:40

Delphi 通过 ActiveX 调用 Bartender 实现产线标签打印实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Delphi 通过 ActiveX 调用 Bartender 实现产线标签打印实战

简介:这份资源围绕Delphi环境下调用Bartender标签打印软件展开,面向使用Delphi进行桌面应用开发、需要集成标签设计与打印功能的开发者,尤其适合已具备一定Delphi基础、希望借助ActiveX控件打通Bartender接口的中级技术人员。资源包内共1个doc文档,压缩包约392KB,以图文形式整理了Bartender的基本概念、标签设计、打印与数据库管理等核心功能,并给出在Delphi中安装ActiveX控件、创建项目、添加控件、调用API及测试功能的完整思路,同时分析了该方案在提升工作效率、增强灵活性和降低成本方面的优势。目前已有389人学习,内容虽为单文档,但聚焦实际集成场景,可帮助读者快速理解Delphi与Bartender的对接方式,为后续开发标签打印模块提供参考。

1. 从一张产线标签说起:Delphi 调 Bartender 到底在调什么

车间里经常有这种场景:MES 或 ERP 里点一下「打印标签」,操作员面前的条码打印机就吐出一张带序列号、批次号、二维码的成品标签。界面是 Delphi 写的,标签模板是 Bartender 设计的,中间那根「线」就是 ActiveX 自动化。很多人第一次听到「Delphi 调用 Bartender」会以为是调一个 DLL 或命令行,其实核心是 Bartender 暴露的BarTender.Application COM 对象,Delphi 通过CreateOleObject拿到这个对象,再驱动它打开.btw模板、塞数据、触发打印。它解决的是「业务系统不重写标签排版,直接复用 Bartender 模板」的问题,适合做产线打印、仓储标签、固定资产标签的桌面端开发者。下面按「能跑起来 → 参数怎么给 → 坑在哪」的顺序拆一遍。

2. 环境准备与 ActiveX 接入:把 COM 对象拉进 Delphi

2.1 先确认 Bartender 版本与自动化权限

Bartender 分 Automation 版和普通版,只有Automation 版才允许被外部程序通过 COM 调用,普通版在CreateOleObject时会直接抛异常。常见做法是装 Bartender Automation 2016 或更高版本,安装时勾选「BarTender 打印引擎」和「ActiveX 自动化支持」。装完后在 Windows 的「组件服务 → COM+ 应用程序」里能看到BarTender.Application这个 ProgID,说明注册成功。如果看不到,多半是安装时没选自动化组件,或者用了绿色版——绿色版通常不注册 COM,这是第一个翻车点。

权限方面,Bartender 的自动化进程默认以当前登录用户身份运行。如果 Delphi 程序以服务方式跑在LocalSystem下,而 Bartender 的授权是按用户绑定的,就会报「未授权」。稳妥做法是让打印程序以交互用户身份运行,或在 Bartender 的「管理 → 授权」里确认授权模式支持服务账户。

2.2 Delphi 里引入类型库还是动态创建

两种接法:

  • 引入类型库:IDE 里Component → Import Component → Import Type Library,选BarTender 20xx Type Library,生成BarTender_TLB.pas,之后可以用CoBarTenderApplication.Create这种强类型写法,编译期能补全。
  • 动态创建:不引类型库,直接用CreateOleObject('BarTender.Application'),代码少、部署简单,但方法名拼错要到运行时才发现。

我一般推荐动态创建,因为产线机器上 Bartender 版本可能不一致,强类型单元反而容易因版本差异编译不过。下面这段是动态创建的最小可运行骨架:

uses ComObj, Variants; var BtApp: OleVariant; BtFormat: OleVariant; begin // 启动 Bartender 自动化实例 BtApp := CreateOleObject('BarTender.Application'); try // 隐藏主窗口,避免弹窗干扰操作员 BtApp.Visible := False; // 打开已设计好的标签模板,路径必须是绝对路径 BtFormat := BtApp.Formats.Open('D:\Labels\product_label.btw', False, ''); // 给模板里的具名数据源赋值 BtFormat.SetNamedSubStringValue('SerialNo', 'SN20240501001'); BtFormat.SetNamedSubStringValue('BatchNo', 'B240501'); // 打印到默认打印机,1 份 BtFormat.PrintOut(False, False); // 关闭模板但不退出 Bartender BtFormat.Close(0); finally // 退出自动化实例,释放 COM 引用 BtApp.Quit(0); BtApp := Unassigned; end; end;

逻辑说明:CreateOleObject启动的是独立的 Bartender 进程,Visible := False让它后台运行。Formats.Open第二个参数False表示不弹出模板设计窗口,第三个参数是密码,模板没加密就传空串。SetNamedSubStringValue是给模板里「命名子串」类型的数据源赋值,名字必须和 Bartender 里定义的完全一致,大小写敏感。PrintOut第一个参数True表示弹出打印对话框,产线自动打印必须传False。Close(0)的0表示不保存修改,避免误改模板。

参数说明:Quit(0)里的0是btQuitSaveChanges枚举,0 为不保存、1 为保存、2 为提示。产线场景一律用 0,防止模板被意外改写。

2.3 数据源类型决定赋值方法

Bartender 模板里的数据源分好几种,赋值方法不一样,这是新手最容易搞混的地方:

数据源类型赋值方法典型场景
命名子串 Named SubStringSetNamedSubStringValue序列号、批次号
数据库字段 Database FieldSetNamedSubStringValue同名从 SQL 查询带出
全局变量 Global VariableBtApp.SetGlobalVariable跨模板共享的工位号
日期时间模板内自动生成生产日期

如果模板里用的是数据库字段,赋值前要确保数据库连接已建立,否则SetNamedSubStringValue会静默失败——不报错,但标签上还是旧数据。这是第二个血泪坑,后面避坑章节细说。

3. 打印流程与参数控制:从模板打开到出纸

3.1 完整打印流程的五个阶段

一个健壮的打印流程应该拆成:启动实例 → 打开模板 → 赋值 → 打印 → 释放。每个阶段都要能单独失败并回滚。下面是一个带错误处理的完整封装:

function PrintLabel(const TplPath: string; const Data: array of const): Boolean; var BtApp, BtFormat: OleVariant; I: Integer; begin Result := False; BtApp := CreateOleObject('BarTender.Application'); try BtApp.Visible := False; // 打开模板,True 表示只读打开,防止误改 BtFormat := BtApp.Formats.Open(TplPath, True, ''); try // Data 是 名称/值 交替的数组 I := 0; while I < High(Data) do begin BtFormat.SetNamedSubStringValue( string(Data[I]), string(Data[I + 1])); Inc(I, 2); end; // 指定打印机并打印 BtFormat.Printer := 'Zebra ZT230'; BtFormat.PrintOut(False, False); Result := True; finally BtFormat.Close(0); end; finally BtApp.Quit(0); BtApp := Unassigned; end; end;

逻辑说明:Formats.Open第二个参数传True是只读打开,产线程序不应该改模板。Printer属性可以覆盖模板里保存的打印机,适合一台机器切换多台打印机的场景。PrintOut是同步调用,函数返回时打印任务已提交到打印队列,但不代表纸已出完。

参数说明:PrintOut(ShowPrintDialog, WaitForCompletion)。第二个参数传True会等打印完成才返回,适合需要确认出纸后再写数据库的场景,但会阻塞界面;传False立即返回,适合高节拍产线。

3.2 打印份数与序列化

批量打印时不要循环调用PrintOut,那样每次都要走一遍打印引擎初始化,慢且容易卡队列。正确做法是用PrintOut的份数参数,或者用 Bartender 的序列化功能:

// 方式一:直接指定份数 BtFormat.PrintOut(False, False); // 打印 1 份 // 如果要打印 N 份,改模板里的份数或循环赋值序列号 // 方式二:模板内序列号自动递增 // 在 Bartender 里把序列号数据源设为「序列化」, // 每次 PrintOut 自动 +1,Delphi 侧不用管 BtFormat.SetNamedSubStringValue('SerialNo', 'SN2405001'); BtFormat.PrintOut(False, False); // 出 SN2405001 BtFormat.PrintOut(False, False); // 出 SN2405002

序列化放在模板里做比在 Delphi 里做可靠,因为 Bartender 的序列化支持补零、进制转换、跳过含 4 的号段等规则,自己写容易漏边界。

3.3 数据库连接的两种模式

如果标签数据来自 SQL 查询,有两种接法:

  • 模板内建数据库连接:在 Bartender 里配好 ODBC 或 SQL 连接,Delphi 只负责触发打印。优点是 Delphi 侧代码极简;缺点是连接字符串写死在模板里,换环境要改模板。
  • Delphi 查库后赋值:Delphi 用ADOQuery查出数据,再逐字段SetNamedSubStringValue。优点是灵活;缺点是字段多时赋值代码长。

我一般选第二种,因为产线换数据库地址时只改 Delphi 配置,不用动几十个模板。查库赋值时注意字段值要转成字符串,日期字段要按模板里的格式转,否则 Bartender 会按本地格式解析,出现「2024/5/1」和「05/01/2024」的歧义。

4. 避坑与排查:那些让产线停线的细节

4.1 现象:赋值成功但标签数据没变

原因:模板里数据源类型是「数据库字段」,而 Delphi 用SetNamedSubStringValue赋值。数据库字段类型的数据源在打印时会重新查库,覆盖掉手动赋的值。

解决:在 Bartender 里把该数据源改成「命名子串」,或者在打印前调用BtFormat.Database.Reset断开数据库连接。更稳妥的做法是模板设计阶段就统一用命名子串,数据库查询全部放 Delphi 侧。

4.2 现象:CreateOleObject报「类未注册」

原因:Bartender 没装 Automation 组件,或装的是绿色版没注册 COM,或 32/64 位不匹配——Delphi 编译的是 32 位程序,而注册的是 64 位 COM 组件。

解决:确认安装的是 Automation 版;用regedit查HKEY_CLASSES_ROOT\BarTender.Application是否存在;如果 Delphi 是 32 位,必须用 32 位的 Bartender 组件,或在项目里改成 64 位编译。

4.3 现象:打印任务提交了但打印机不出纸

原因:Printer属性设的打印机名和系统里的实际名称不一致,或者打印机处于暂停/脱机状态。Bartender 不会因为打印机名错误而报错,它只是把任务丢进队列。

解决:打印前用Printer.Printers枚举系统打印机名做校验;在PrintOut后查打印队列状态。产线程序最好加一个「打印确认」环节,比如让操作员扫一下刚出的标签条码,反写数据库确认。

4.4 现象:连续打印几百张后 Bartender 进程卡死

原因:每次打印都CreateOleObject+Quit,频繁启停 COM 进程导致资源泄漏。或者模板打开后没Close,句柄越积越多。

解决:把 Bartender 实例做成全局单例,程序启动时创建,退出时销毁;每次打印只Open/Close模板。如果必须每次新建,至少在finally里确保Quit被调用,并加Sleep(50)给进程退出留时间。

4.5 现象:中文变量赋值后显示乱码

原因:Delphi 的string是AnsiString(老版本)或UnicodeString(2009 之后),而 COM 接口期望BSTR。老版本 Delphi 传中文时编码转换丢失。

解决:Delphi 2009 之后统一用string即可;老版本用WideString中转。模板里的字体也要选支持中文的,比如宋体或微软雅黑,用 Arial 会显示方框。

5. 进阶技巧:把打印封装成可复用的打印服务

5.1 用配置文件驱动模板与字段映射

产线标签种类多,硬编码模板路径和字段名维护起来很痛苦。我一般用一个 INI 或 JSON 配置,把「业务单据类型 → 模板路径 → 字段映射」存起来:

[Label_Product] Template=D:\Labels\product_label.btw Printer=Zebra ZT230 Map_SerialNo=SN Map_BatchNo=LOT Map_Qty=QTY

Delphi 侧读配置后,把业务字段名翻译成模板字段名再赋值。这样新增标签只改配置,不用重新编译程序。

5.2 打印日志与重打机制

产线最怕「打没打不知道」。我的习惯是每次PrintOut前先往本地 SQLite 写一条打印日志,状态为「已提交」,打印成功回调后改为「已出纸」。操作员发现漏打时,按单据号查日志重打,重打次数也记进去,方便追溯。

// 打印前写日志 LogPrint(OrderNo, LabelType, 'SUBMITTED'); try BtFormat.PrintOut(False, True); // 等待完成 LogPrint(OrderNo, LabelType, 'DONE'); except LogPrint(OrderNo, LabelType, 'FAILED'); raise; end;

PrintOut第二个参数传True会等打印任务完成,虽然慢一点,但能拿到准确的完成状态。高节拍场景可以传False,靠打印机反馈或人工确认。

5.3 验证方法:三步确认调用链通了

新环境部署后,别急着上产线,按这三步验:

  1. COM 层:写个最小 Delphi 程序,只做CreateOleObject和Quit,能跑通说明组件注册没问题。
  2. 模板层:打开一个测试模板,赋固定值打印一张,确认打印机和模板匹配。
  3. 数据层:接真实业务数据,打印后扫标签条码反查数据库,确认字段没串位。

这三步走完,基本能排除 90% 的部署问题。从那以后我每次上新机器都强制走一遍这个清单,再也没出现过「程序说打了、产线说没打」的扯皮。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 14:59:37

前端版本更新检测:轮询机制与Web应用优化实践

前端上线新版本之后&#xff0c;用户在浏览器里还在跑旧代码&#xff0c;这种问题估计不少同行都遇到过。最近我把一个用于实时检测 web 应用更新的 JavaScript 库从想法落地成了可复用工具&#xff0c;这里把整个设计和实现过程完整分享一下。这个库主要解决前端资源在用户端“…

作者头像 李华
网站建设 2026/10/18 1:11:40

4GB显存运行7B大模型:AirLLM分层推理实战指南

如果你手头只有一块4GB显存的显卡&#xff0c;却想完整跑一遍70亿参数的大语言模型&#xff0c;大多数时候你会被四面八方的人劝退&#xff1a;显存放不下、量化也白搭、换卡才是正路。我第一次上手试跑7B模型时&#xff0c;加载权重那一步就直接爆显存&#xff0c;显卡驱动弹窗…

作者头像 李华
网站建设 2026/10/17 22:06:54

Jetpack Compose底层机制与重组优化全解析

做Android开发这几年&#xff0c;我越来越觉得传统View体系下的UI编写方式&#xff0c;本质上是一种“手工同步”的活。你写了一个布局&#xff0c;接着要在代码里findViewById&#xff0c;再在各种回调里setText、改背景、控制可见性&#xff1b;数据一变&#xff0c;你还得操…

作者头像 李华
网站建设 2026/10/18 18:54:44

三微网协调优化调度:Matlab建模与程序实现解析

做微电网优化的朋友应该都有体会&#xff1a;单个微网的调度已经够折腾&#xff0c;要再把它放进微网群里&#xff0c;和其他几个微网协同优化&#xff0c;难度完全是另一个量级。前阵子我在整理一套用Matlab编写的微网群优化调度程序&#xff0c;核心就是实现三个微网的协调优…

作者头像 李华
网站建设 2026/10/19 4:27:48

从WWDC拆解苹果产品发展规律:一份开发者视角的观察指南

我从多年前开始&#xff0c;就把每年六月的WWDC当成一个“不看会后悔”的固定节目。最初纯粹是追新系统、看新界面&#xff0c;后来自己开始做产品规划、接触团队管理&#xff0c;慢慢发现WWDC带给我的信息量和秋季发布会完全不是一个量级——秋季发布会告诉你“接下来你要买什…

作者头像 李华
网站建设 2026/10/18 8:21:42

主从配电网分布式优化:ADMM串并行实现与收敛调参实战

1. 什么逼着我把主从配电网问题拆成分布式&#xff1a;集中式优化在现场的三个软肋1.1 数据采集与通信链路的现实约束很多刚接触配电网优化的人&#xff0c;第一反应一定是做集中式&#xff1a;把主变关口、馈线、分布式电源、储能、柔性负荷全部建模&#xff0c;丢给一个大规模…

作者头像 李华