
简介ComPort 6.6 是面向 Delphi 串口通信开发的经典控件集覆盖 D5D11 多个版本适合需要在 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 51999 年发布到 Delphi 112021 年发布中间隔了二十多年编译器、RTL、VCL 都有翻天覆地的变化。单说字符串类型D5 到 D7 时代默认是 AnsiStringD2009 开始默认变成了 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。伪代码如下使用的语言是 Delphiprocedure 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/GBKTEncoding.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 10CPU 和操作系统都换了。起初担心串口控件在新系统上会有问题后来我把运行时库升级到 ComPort 6.6代码几乎没怎么动基本功能直接就在 Delphi 10.3 上跑通了。这次迁移让我对“D5~~D11”这个兼容性标注有了切身体会——它不是停留在文档层面的支持而是真的从 Delphi 5 到 Delphi 11 都消化过编译器的差异。如果你也在维护老项目我的建议是分阶段走先在原版本下把 ComPort 升级到 6.6再考虑整体迁移 IDE。这样做的好处是一旦迁移后出现问题就能分辨出到底是控件升级引入的问题还是新编译器对你源码的兼容性问题。两个变量叠加一起改排查问题的难度会成倍上升这一点在实际现场吃过亏。另外你是不是觉得“串口通信”这个东西很老套学了没用其实现在很多智能硬件项目、树莓派/嵌入式 Linux 板子的调试串口、工业网关的上行数据采集底层依然还是那套串口协议逻辑。掌握了 TComPort 这套组件的写法尤其是缓冲区处理、帧拼接、线程切换这些思路换到别的语言或别的串口库也只是换了个皮核心心智模型完全一样。最后再分享一个我个人的小经验写串口调试工具时界面上的接收框最好提供“按十六进制显示”的开关。因为设备协议排错时纯字符串显示的歧义太大而十六进制里 0x00、0xFF 这类控制字符一目了然。ComPort 自带的示例工程里就有这样的参考写法直接拿过来改改就能用。另外所有调试现场的数据最好加一个时间戳再落盘这在追踪间歇性故障时能帮你省下大把时间。本文还有配套的精品资源点击获取