简介:ComPort 6.6 是面向 Delphi 串口通信开发的经典控件集,覆盖 D5~D11 多个版本,适合需要在 Win32 桌面程序中收发串口数据、进行设备联调的开发者。压缩包共含 276 个文件,体积仅 1.84MB,内部按用途划分:dcu 为预制编译单元,pas 为源码,dpk/bpk 为包工程文件,bpl/bpi 为运行期与设计期包,chm 为帮助文档,此外还有少量 hpp、obj、res 等辅助文件,安装、查阅、二次扩展都较方便。针对较新 Delphi 版本编译时容易遇到的 DesignIntf/DesignEditors 引用缺失或默认搜索路径不识别的问题,包内附带了具体配置参考,可帮助正确加装设计期包并完成编译,让控件在 Delphi 11 中顺利安装运行。已有 155 人学习下载,对于跨版本部署串口控件或排查安装报错的 Delphi 开发者,这份资源能节省不少尝试时间。
1. 先搞清楚 ComPort 6.6 是个什么东西
第一次见到“ComPort 6.6 D5~~D11”这套组件的时候,很多人第一反应是:都什么年代了,还有人折腾串口?实际上但凡做过工控、仪器仪表、嵌入式设备联调,或者写过老式读卡器、扫码枪、称重仪表上位机软件的人,都会明白串口通信在现实项目里一直没死,反而因为物联网的普及,USB转串口、虚拟串口、蓝牙串口这些东西也变得比过去更常见。ComPort 就是 Delphi 生态里用起来最顺手的一套串口通信组件包,6.6 这个版本支持从 Delphi 5 到 Delphi 11 的全系列 IDE,也就是标题里写的“D5~~D11”。
这套组件本质上解决的是一个问题:你不需要自己调用 Windows 的 CreateFile、GetCommState、ReadFile、WriteFile 等一系列 Win32 API 去操作串口,也不用面对重叠 I/O、事件驱动、线程同步这些容易把人绕晕的概念。ComPort 把这堆底层细节全部封装成了几个拖拽即用的 VCL 组件,你只要在窗体上放一个 TComPort,设置好端口号和波特率,打开端口之后直接往里面写字符串就行。对于大部分业务型上位机项目来说,这个抽象层级刚刚好:既有足够的底层控制能力,又不会像直接用 API 那样消耗大量开发时间。
我最早接触 ComPort 是当年做汽车检测线项目,需要同时管理十几台检测设备,每台设备一条串口线,数据格式还各不相同。那时候用的还是 Delphi 7,网上能找到的串口控件很多,比如 MSComm、SPComm、AsyncPro,但对比下来,ComPort 给我最直观的感觉是稳定,而且源码开放,出问题能直接看源码排查,这在国内工控圈子里一直口碑不错。后来从 Delphi 7 一路升到 Delphi 10.3、11,这套组件依然在维护更新,6.6 版本更是直接追平了 RAD Studio 11 Alexandria 的兼容性,所以直到今天,它依然是很多老项目改造和新项目选型的首选。
说句实在话,ComPort 6.6 并不是什么“最新最潮”的技术,但它恰恰是那种在项目里能安安静静干活、不给你惹麻烦的库。如果你正在做串口相关的上位机开发,或者维护一个用了很多年的 Delphi 老系统,这篇东西值得花几分钟看完,里面有不少是文档里不会写的实战细节。
2. 版本兼容性,为什么 D5~~D11 这么重要
标题里的“D5~~D11”看着简简单单,但它其实反映了一个非常现实的问题:Delphi 的版本跨度极大,从 Delphi 5(1999 年发布)到 Delphi 11(2021 年发布)中间隔了二十多年,编译器、RTL、VCL 都有翻天覆地的变化。单说字符串类型,D5 到 D7 时代默认是 AnsiString,D2009 开始默认变成了 UnicodeString,这一个变化就曾经让大量第三方控件“死”在升级路上。ComPort 6.6 敢在标题里写明支持 D5 到 D11,说明它内部的字符串处理、字符集转换逻辑做了非常细致的兼容处理,这对于那些还在维护老项目的开发团队来说,省掉的不只是安装上的折腾,更是升级路上的一个大坑。
装过老组件的人应该都有体会,很多控件在某个版本的 Delphi 上能用,换一个 IDE 版本就编译不过去,要么是单元文件名变了,要么是某个 API 签名不兼容。ComPort 6.6 的安装包里针对不同版本做了条件编译,你打开 Delphi 后直接打开对应版本的包工程文件,编译安装一般不会有太大问题。具体来说,安装包里会看到类似“ComPortD5.dpk”“ComPortD6.dpk”直到“ComPortD11.dpk”这样按版本区分的文件,如果你是 Delphi 10.4,就打开那个对应的 D10.4 包,而不是随便找一个最近的版本硬编。
另外一个很多人没注意到的点是,ComPort 6.6 对 Unicode 的处理方式。从 Delphi 2009 之后,默认字符串都是 UTF-16 编码,但串口设备大多数只认 ANSI 编码或者最原始的字节流。ComPort 组件的 WriteStr 方法内部做了编码转换,你可以通过组件的 RXD 事件和 TXD 事件拿到实际发送和接收的数据。如果你在 D5 时代写的老代码里直接用 String 类型的变量收发数据,升到新版 Delphi 后还能不能像以前一样工作?我的经验是,大部分情况可以,但如果你传输的是中文或者高位 ASCII 字符,必须重新测试一遍数据完整性,这个细节后文我会专门讲。
还有一点值得说一下:ComPort 6.6 不止兼容 Win32,它在 RAD Studio 的 Win64 编译目标下同样可用。很多老项目以前只能在 32 位下编译,迁移到 64 位后至少省了重新找串口库的麻烦。虽然 64 位下串口通信逻辑跟 32 位没有本质区别,但如果你用到了内联汇编或者指针强转这类技巧去处理缓冲区,在 64 位下可能需要调整。ComPort 纯 Pascal 实现,没有内嵌汇编,所以这块它反而比较省心。
3. 安装过程与常见坑,照着做就能一次成功
3.1 准备工作
拿到 ComPort 6.6 的压缩包后,先不要急着双击打开某个 dpk 文件。我的习惯是先把整个目录解压到一个固定的第三方库目录,比如“D:\Components\ComPort”,并且路径里不要带中文、不要带空格。很多 Delphi 组件安装失败的案例,最后排查下来都是路径问题——要么是路径里有中文导致 IDE 的搜索路径解析出错,要么是路径太长超过系统限制。这个习惯我保持了十几年,几乎没有因为安装环境本身踩过坑。
解压之后,打开目录结构,你会看到几个关键的文件夹:Source 目录存放核心的 Pas 文件,Packages 目录按照 Delphi 版本组织了各个版本的包工程文件,还有 Demo 目录放了一些示例。动手编译之前,建议先确认一下你的 Delphi 版本对应的包文件是什么名字。以 Delphi 10.4 为例,包文件在 Packages 下一般命名为“ComPort_D10_4.dpk”或类似命名规则;Delphi 11 则对应“ComPort_D11.dpk”。如果你用的是 Delphi 2007 这种老版本,找到 D2007 的包文件即可。
3.2 编译安装的具体步骤
打开 Delphi 后,建议先把 Source 目录添加到 IDE 的 Library Path 中,这样后面新建工程时 IDE 才能自动找到这些单元。操作路径是:Tools -> Options -> Language -> Delphi -> Library -> Library Path,把 Source 目录加进去。
然后通过 File -> Open 打开对应版本的 dpk 文件,Delphi 会弹出一个 Package 编辑窗口。在 Project Manager 面板里右键点击这个包,选择 Compile,正常情况下应该能编译通过。如果编译报错,大概率是某些 RTL 单元名在版本间有变化,你可以在 Project Manager 中右键点击 Requires 节点,检查一下引用的包是不是当前 IDE 需要的。编译通过后,再右键点击包选择 Install,看到“Package ... installed”的提示就说明成功了,此时 Component Palette 里会出现一个“CPort”或“ComPort”页签,里面放着 TComPort、TComDataPacket、TComLed 等几个组件。
有一个常见的坑是:有些版本的包文件里默认勾选了 Runtime only 选项,安装后工具箱里找不到组件。解决办法是在 Package 编辑器里,把包的 Usage Options 改为 Design time and runtime,重新编译安装一次即可。
3.3 老版本 Delphi 的注意事项
如果你还在用 Delphi 5、Delphi 6 或 Delphi 7 维护老项目,安装 ComPort 6.6 基本也能走通,但有几个个体差异需要留意。比如 Delphi 5 的年代还没有 Unicode,也没有后来的 Package 管理机制那么完善,建议在安装时把 Source 目录里的文件全部复制到项目自己的目录下,直接以源码方式参与编译,而不是依赖设计期组件。这种“源码级集成”的方式对于老项目反而是最稳的——不污染 IDE 环境,也不会因为组件版本和 IDE 版本混淆而出问题。
另一个要点是:老版本的 Delphi 在编译大型包时偶尔会报“Out of memory”之类的错误,通常是因为 IDE 的内存模式设置问题。把 Delphi 安装目录里的 Delphi32.ide 配置文件修改一下,增大堆大小,或者关掉一部分 IDE 插件,基本就能解决。当然,如果你只是偶尔用一次,直接在命令行用 dcc32 编译相关的 Pas 文件,生成 DCU 后手动加到搜索路径里,也是一种非常干净的方案。
4. 核心组件与关键属性,串口编程的地基
4.1 TComPort 组件的核心设置
TComPort 是整个库的心脏,它负责串口的具体打开、读写、配置。在设计期双击 TComPort,会弹出一个串口属性设置窗口,这个窗口基本上对应了 Win32 的 DCB 结构体,里面的每一项都可以决定通信行为是否正常。常用的几个属性我再三强调:
- Port:串口号。可以是“COM1”“COM2”这种字符串,也可以选择实际的设备名。如果项目里需要自动识别 USB 转串口的设备,可以在运行时枚举注册表或者直接让用户选择。
- BaudRate:波特率。常见的有 9600、19200、115200 等。这个必须和对方设备保持一致,否则收到的数据全是乱码。6.6 里可以在属性下拉框里直接选,但也可以用代码赋值自定义波特率。
- DataBits、Parity、StopBits:数据位、校验位、停止位。绝大部分设备默认是 8 位数据、无校验、1 位停止位(8N1)。但 RS-485 的很多仪表会用到 7 位数据加偶校验,这个要在项目启动前跟硬件工程师确认清楚。
- BufferSize:接收缓冲区大小。如果设备会一次性发送大量数据,或者发送频率很高,建议把这个值调大,默认值可能不够。我自己习惯设成 8192 或 16384。
属性窗口里还有事件页签,其中最常用的是 OnRxChar 事件,当串口接收到一个字节或一组数据时会触发这个事件。注意,OnRxChar 是在后台线程里触发的,不是在主线程的消息循环里。这就直接引出了一个经典问题:你在这个事件里操作了 VCL 的界面控件,程序就会偶发性崩溃。解决办法是用 Synchronize 或 Queue 把界面更新的逻辑切回主线程,或者干脆事件里只把原始数据放到一个线程安全的队列中,再由主线程的定时器去取。这是我的一个核心经验,后文的示例代码里也会重点演示这一点。
4.2 TComDataPacket 的粘包封包处理
TComDataPacket 是一个非常有用的辅助组件,它的作用是帮你从串口接收到的“裸字节流”中提取出完整的一帧数据。串口通信是面向字节流的,对方设备可能一条完整的报文被分段发送,或者两个报文粘在一起发过来。TComDataPacket 提供了几种识别“完整一帧”的方式:固定长度(IncludeSize)、以某个结束符结束(IncludeStrings 中设置结束标志)、或者符合某种正则特征。它内部做了一套状态机,会把不完整的数据暂存起来,拼接好之后才触发 OnPacket 事件。
很多人第一次用 TComDataPacket 会觉得有点绕,因为它和 TComPort 的关系是:TComPort 先把原始字节交给它,它判断出一个完整的数据包之后再交给业务逻辑。在设计期把 TComDataPacket 的 ComPort 属性指向窗体上的 TComPort,然后在 IncludeStrings 中加入结束符,比如设备以回车换行“\r\n”作为帧结束,那这里就填“#13#10”。如果设备是按固定长度发帧,比如每帧 32 字节,就可以用 IncludeSize 并填 32,这样每次收到 32 字节后才会触发 OnPacket。这种方式比自己在 OnRxChar 里拼接收缓冲区再手动判断帧边界要省力得多,而且不容易出错。
4.3 TComLed 的通信指示灯模拟
ComPort 6.6 还附带了一个 TComLed 组件,用来在界面上模拟串口的收发指示灯。它的用法很简单,把 ComPort 属性指向 TComPort 之后,它就能自动感知到数据的收发并在 On 和 Off 两种状态之间切换。设计上你可以把它做成一个 LED 小圆形控件,收到数据变绿、发送数据变红,这样用户在看上位机运行状态时,一眼就能确认通信是否正常。由于它不是一个真实硬件,只是界面模拟,所以你可以随意调整颜色和闪烁时间,不影响任何通信性能。
很多做界面的人忽略了这个组件的价值。在实际调试工业设备时,有一个可视化的通信指示其实非常有用——尤其在接线松动或者设备掉线的时候,有没有数据一眼就能看出来。省去你用串口监视器抓包排查的时间。这也算是 ComPort 库比较贴心的地方,小部件不复杂,但确实解决实际问题。
5. 一次完整的串口调试工具实现,代码可直接抄
5.1 界面搭建
光讲理论容易飘,直接上一个我实际用过的“最小可用串口调试助手”的实现思路,主旨是帮你把 TComPort 的用法串起来。界面上的控件按从上到下的顺序是:端口选择 ComboBox、波特率 ComboBox、打开/关闭按钮 Button、接收数据 Memo、发送内容 Edit、发送按钮 Button,以及一个 TComPort 和一个 TComDataPacket。TComPort 和 TComDataPacket 是组件面板里拖出来的,不需要写创建代码。
端口选择下拉框里的 COM 列表可以手工填几个常见的,也可以利用 TComPort 的 EnumPorts 方法来动态枚举。我一般写一个 FormShow 事件,用 EnumPorts 把当前系统里所有的串口列出来,这样用户插拔 USB 转串口后,重新打开窗口就能看到新的端口号,体验比手动输入好很多。
5.2 打开串口与发送数据
打开串口的逻辑,核心就是给 TComPort 的几个属性赋值,然后调用 Open。伪代码如下,使用的语言是 Delphi:
procedure TForm1.btnOpenClick(Sender: TObject); begin if ComPort1.Connected then begin ComPort1.Close; btnOpen.Caption := '打开串口'; Exit; end; ComPort1.Port := cbPort.Text; ComPort1.BaudRate := StrToInt(cbBaud.Text); ComPort1.DataBits := db8; ComPort1.Parity := paNone; ComPort1.StopBits := sb1; ComPort1.Open; if ComPort1.Connected then btnOpen.Caption := '关闭串口' else ShowMessage('打开失败,请检查端口是否被占用'); end;发送数据时,建议使用 WriteStr 方法而不是 WriteBuffer。WriteStr 内部会做字符串与字节数组的转换,对于普通 ACSII 字符串来说最方便。
procedure TForm1.btnSendClick(Sender: TObject); begin if not ComPort1.Connected then begin ShowMessage('串口未打开'); Exit; end; ComPort1.WriteStr(edtSend.Text); end;如果要发送十六进制数据,比如发“AA 55 01 00”,可以先把字符串转换成字节数组,再调用 WriteBuffer。这个场景常见于走自定义协议的控制板,实现方法不复杂,用 HexToBuf 之类的辅助函数即可,ComPort 源码中自带了一些字符转换工具函数,直接调用就好。
5.3 接收数据的关键点
接收端最需要小心的就是跨线程更新 UI 的问题。 OnRxChar 在后台线程里,你可以在里面把原字节转成字符串并追加到 Memo,但这么做偶尔会报“Canvas does not allow drawing”之类的 GDI 错误。更稳的写法是定义一个线程安全的队列,在 OnRxChar 里只 push 数据,用一个 TTimer 在主线程里定时 pop 并更新界面。
procedure TForm1.ComPort1RxChar(Sender: TObject; Count: Integer); var buf: TBytes; s: string; begin SetLength(buf, Count); ComPort1.ReadBuffer(buf[0], Count); s := TEncoding.Default.GetString(buf); TThread.Queue(nil, procedure begin mmoReceive.Lines.Add(s); end); end;这里用了 TThread.Queue,它的好处是即使当前线程不是主线程,也能把匿名方法投递到主线程执行,而且是异步排队,不会像 Synchronize 那样容易造成线程阻塞和死锁。Count 表示当前接收缓冲区里有多少个字节,调用一次 ReadBuffer 可以一次性把所有数据读出来,然后按设备的实际编码方式转成字符串。
如果你用了 TComDataPacket,那 OnRxChar 里的逻辑会更简单,只要把读到的原始字节交给 TComDataPacket 去“喂养”状态机即可,然后业务逻辑全部移到 OnPacket 事件中处理。这里尤其要提一句,如果你用的是 D2009 之后的版本,从字节流转字符串时用 TEncoding.Default 还是 TEncoding.ANSI,确实要结合设备字符集去验证。大部分国产仪表走 GB2312/GBK,TEncoding.Default 在新版 Windows 下可能已经是 UTF-8,如果你的设备发过来的是中文,显示乱码的话,改成 TEncoding.GetEncoding(936) 往往就能解决。
5.4 小细节:你不一定知道的 TComLed 用法
把 TComLed 组件的 ComPort 属性关联到 ComPort1 后,打开串口通信,这个光点就会随收发数据闪烁。在 TComLed 上右键有个 ActiveLevel 属性,可以设置是高电平点亮还是低电平点亮。虽然它只是模拟,但逻辑上跟真实硬件 LED 是一致的。界面调试的时候,我看它闪烁,就知道数据确实在流动,配合 Memo 中的内容排错非常高效。
6. 常见问题与排查技巧实录
6.1 串口打开失败,提示“端口被占用”
这是最常遇到的问题。打开失败的原因几乎都是串口被其他程序占用,比如串口调试助手没关、设备管理器的“串口监视”功能开着、或者上一次程序异常退出时没有释放串口句柄。排查方法很简单:关掉所有可能占用串口的程序后重新打开,如果还不行,看看 COM 号是否已经变化。USB 转串口设备重新插拔后 COM 号很可能会变,在代码里不要硬编码 COM1,用枚举方式让用户选择,或者加上记忆上次使用的 COM 号功能。
6.2 能打开串口但收到的全是乱码
乱码这个事十有八九是波特率、数据位、校验位、停止位四项参数有某一项不一致。最常见的错误是波特率不匹配,或者设备用了偶校验但你把校验位设成了 None。另外,如果设备发送的是二进制协议,你直接按字符串显示,那看到的自然是“乱码”。这种情况可以用十六进制显示来辅助判断。我之前碰到过一个血压计模块,设备数据手册写的是 115200 8N1,但实际跑起来必须 9600 才正常,这种参数以实测为准。
6.3 接收数据时偶尔漏字节,或者一个包被拆成两半
漏字节的常见原因是程序没有及时读取缓冲区,导致数据超出内核缓冲区后丢失。尤其是通信量大、界面又比较卡的时候更容易发生。我的建议是:OnRxChar 事件里不要做耗时处理,不要写数据库、不要解析 XML、不要同步操作界面,只管把数据读进内存队列。耗时的解析逻辑放到另外的工作线程里执行。一个包被拆成两半的问题,用 TComDataPacket 就能解决,它会把半个包暂存拼接,直到凑成完整一帧才触发 OnPacket。
6.4 升级到新版 Delphi 后,原有项目编译不过
D5 时代的老项目直接拿到 D11 里编译,往往会碰到一堆和 Unicode 字符串相关的报错,ComPort 本身倒不太会成为问题,因为它内部已经处理好了兼容。问题大多出在你自己的业务代码里,比如把 Char 和 Byte 混用。建议迁移时在 Project Options 里打开“Unicode”相关的编译开关,先让编译器帮你扫一遍类型不匹配的地方,然后逐个修正。如果项目实在太大,一个过渡方案是:先用运行时转换函数,把涉及收发数据的关键路径全部改成显式的字节流操作,而不是依赖隐式转换。
6.5 运行一段时间后程序无响应
这类问题多见于接收事件里用 Synchronize 同步更新主线程 UI,当设备高频发数据时,后台线程频繁等待主线程响应,主线程一忙,两者互相等待就可能出现界面卡死。我把所有 UI 更新的代码都改成 TThread.Queue 之后,这个问题基本消失了。如果 Queue 不能完全解决,还可以设置 TComPort 的 Interrupt 事件处理方式,或者干脆用独立的收发线程来管理数据流。要注意的是,TComPort 的底层虽然是事件驱动,但如果你用 WriteBuffer 一次写入太多的数据,在 BufSize 较小的情况下也可能阻塞住 UI,这时可以把写超时时间调大,或者分片写入。
7. 在 D5~~D11 之间的几个实操心得
我做过一个比较极端的迁移案例:一套跑在 Delphi 7 下的老版生产管理系统,底层用的 ComPort 6.5 对应的更老版本,后来整机换新电脑、系统也升级到 Windows 10,CPU 和操作系统都换了。起初担心串口控件在新系统上会有问题,后来我把运行时库升级到 ComPort 6.6,代码几乎没怎么动,基本功能直接就在 Delphi 10.3 上跑通了。这次迁移让我对“D5~~D11”这个兼容性标注有了切身体会——它不是停留在文档层面的支持,而是真的从 Delphi 5 到 Delphi 11 都消化过编译器的差异。
如果你也在维护老项目,我的建议是分阶段走:先在原版本下把 ComPort 升级到 6.6,再考虑整体迁移 IDE。这样做的好处是,一旦迁移后出现问题,就能分辨出到底是控件升级引入的问题,还是新编译器对你源码的兼容性问题。两个变量叠加一起改,排查问题的难度会成倍上升,这一点在实际现场吃过亏。
另外,你是不是觉得“串口通信”这个东西很老套,学了没用?其实现在很多智能硬件项目、树莓派/嵌入式 Linux 板子的调试串口、工业网关的上行数据采集,底层依然还是那套串口协议逻辑。掌握了 TComPort 这套组件的写法,尤其是缓冲区处理、帧拼接、线程切换这些思路,换到别的语言或别的串口库,也只是换了个皮,核心心智模型完全一样。
最后再分享一个我个人的小经验:写串口调试工具时,界面上的接收框最好提供“按十六进制显示”的开关。因为设备协议排错时,纯字符串显示的歧义太大,而十六进制里 0x00、0xFF 这类控制字符一目了然。ComPort 自带的示例工程里就有这样的参考写法,直接拿过来改改就能用。另外,所有调试现场的数据最好加一个时间戳再落盘,这在追踪间歇性故障时能帮你省下大把时间。
本文还有配套的精品资源,点击获取