简介:这份PDF面向需要在Visual Studio 2010环境下开发串口通信程序的C++/MFC初学者与工程人员,系统讲解如何借助MSComm控件完成串口收发功能,解决传统VC6.0注册ocx控件繁琐、上手困难的问题。资源包共1个文件,为PDF格式,大小约1.91MB,内容以图文步骤形式呈现,便于对照操作。文档从建立基于对话框的MFC工程CommTest入手,依次覆盖控件添加与命名、按钮及编辑框变量绑定、OnCommMscomm1事件处理、串口参数设置(串口号、波特率、数据位、停止位、缓冲区)以及数据发送与接收的完整代码示例,并给出打开与关闭串口的实现思路。目前已有354人学习,适合希望快速掌握VS2010串口通信开发流程、需要可直接参考代码与排错思路的读者。
1. 在 VS(VC)2010 里用 MSComm 控件打通串口:为什么老方案今天还有人用
如果你手上有一台 2010 年前后的工控机、一块 RS232 采集板,或者一套只肯跑在 Windows XP/Win7 上的老上位机,那么“在 VS(VC)2010 中使用 MSComm 控件实现串口通信”这件事,大概率不是你想不想做,而是你必须做。MSComm 是微软早期随 Visual Basic 6.0 一起发布的串口 ActiveX 控件,全称 Microsoft Communications Control,注册后可以在 MFC 对话框工程里直接拖进界面,用属性页配波特率、数据位、校验位,用OnComm事件收数据。它把 Win32 的CreateFile、SetCommState、ReadFile这一整套 API 包成了几个属性和一个事件,对当年只会拖控件的工程师来说,是唯一能快速交付的路子。
放到今天看,它确实老:只支持 32 位、依赖mscomm32.ocx注册、在高版本 VS 里连“插入 ActiveX 控件”的入口都藏得很深。但它的价值在于——存量项目迁移成本极低。你不需要重写通信层,只要把控件注册好、类向导挂上、事件映射写对,老代码就能继续跑。这篇笔记面向三类人:维护老上位机的现场工程师、被要求“把串口功能加进 MFC 对话框”的在校生、以及想搞清楚 ActiveX 串口控件到底怎么落地的新手。下面从环境准备一路讲到参数配置和踩坑,全部按可复现的步骤写。
2. 环境准备:让 VS2010 认出 MSComm 控件
这一章解决的是“控件根本不在工具箱里”的问题。很多人第一次打开 VS2010,右键对话框想“插入 ActiveX 控件”,翻遍列表也找不到 Microsoft Communications Control,于是以为 VS2010 不支持。其实不是不支持,是mscomm32.ocx没注册,或者注册了但位数不对。MSComm 是 32 位控件,必须用 32 位的方式注册,64 位系统上尤其要注意这一点。
2.1 确认 mscomm32.ocx 的来源与位数
先找文件。常见位置有三个:系统目录C:\Windows\System32\mscomm32.ocx、C:\Windows\SysWOW64\mscomm32.ocx,或者你从老安装包里解压出来的独立文件。判断位数最直接的办法是看它注册到哪个目录——64 位系统上,32 位控件应该注册进SysWOW64,因为 VS2010 本身是 32 位进程,它只能加载 32 位控件。
如果你手上只有一份mscomm32.ocx,先确认它是不是 32 位。用记事本打开看不了,可以用dumpbin:
:: 在 VS2010 命令提示符下执行,查看文件头 dumpbin /headers C:\Windows\SysWOW64\mscomm32.ocx | findstr "machine" :: 输出 x86 表示 32 位,x64 表示 64 位dumpbin是 VS 自带的工具,在“Visual Studio 2010 命令提示符”里能直接调用。看到x86就对了,MSComm 没有 64 位版本,任何声称 64 位的都是假的。这一步的逻辑是:先确认文件本身可用,再谈注册,否则后面注册成功但加载失败,你会以为是代码问题,白白浪费半天。
2.2 用 regsvr32 注册控件并验证
注册命令本身很简单,但路径和权限是两个关键参数:
:: 必须以管理员身份运行 cmd :: 64 位系统注册 32 位控件,用 SysWOW64 下的 regsvr32 C:\Windows\SysWOW64\regsvr32.exe C:\Windows\SysWOW64\mscomm32.ocx :: 如果控件放在其他目录,写全路径 C:\Windows\SysWOW64\regsvr32.exe D:\tools\mscomm32.ocx执行后会弹一个“DllRegisterServer 成功”的对话框。如果报“模块加载失败”,八成是位数不对或者文件损坏;如果报“拒绝访问”,就是没用管理员权限。注册成功后,还要补一个注册表项,否则 VS2010 的“插入 ActiveX 控件”列表里可能仍然不显示:
:: 添加控件授权项,键名固定 reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0\Controls" /v MSComm /t REG_SZ /d "{648A5600-2C6E-101B-82B6-000000000014}" /f这里的 CLSID{648A5600-2C6E-101B-82B6-000000000014}是 MSComm 控件的固定标识,不要改。Wow6432Node是 64 位系统上 32 位程序的注册表重定向节点,VS2010 读的就是这里。做完这两步,重启 VS2010,再打开对话框资源,右键“插入 ActiveX 控件”,列表里应该能看到 “Microsoft Communications Control, version 6.0”。
提示:如果列表里还是没有,检查 VS2010 是否以管理员身份运行,以及“工具 → 选择工具箱项 → COM 组件”里是否勾选了该控件。
2.3 把控件拖进对话框并生成包装类
控件出现在列表里之后,操作就回到 MFC 的老套路了。在资源视图里打开你的对话框,右键“插入 ActiveX 控件”,选中 MSComm,确定。此时对话框上会出现一个图标,位置随意,运行时可以隐藏。接着右键这个控件 → “添加变量”,变量类型选CMSComm,变量名比如m_ctrlComm。VS2010 会自动生成mscomm.h和mscomm.cpp两个包装类文件,里面封装了put_CommPort、put_Settings、get_Input等方法。
这一步的参数说明:变量类型必须是CMSComm,这是 VS2010 根据控件类型库生成的 MFC 包装类;如果你手滑选了CButton之类,后面调用串口方法会直接编译不过。生成包装类后,在对话框的DoDataExchange里会多一行DDX_Control(pDX, IDC_MSCOMM1, m_ctrlComm);,这是控件和变量绑定的关键,不要手动删。
3. 核心配置:端口、波特率与 OnComm 事件映射
控件挂上之后,真正决定能不能通的是参数配置和事件处理。MSComm 的配置集中在Settings属性里,格式是一个字符串"BBBB,P,D,S",分别代表波特率、校验位、数据位、停止位。事件处理则靠OnComm,它会在收到数据、发送完成、出现错误时触发。这一章把这两块拆开讲透。
3.1 Settings 字符串的四个参数怎么填
Settings的默认值是"9600,N,8,1",意思是 9600 波特、无校验、8 数据位、1 停止位。这是最常见的组合,但现场设备千奇百怪,必须按设备手册改。四个参数的取值规则如下:
| 位置 | 含义 | 常见取值 | 说明 |
|---|---|---|---|
| 第 1 段 | 波特率 | 9600 / 19200 / 38400 / 115200 | 必须与下位机完全一致 |
| 第 2 段 | 校验位 | N / E / O / M / S | N 无校验最常用,E 偶校验、O 奇校验 |
| 第 3 段 | 数据位 | 7 / 8 | 8 位最常用,7 位多见于老仪表 |
| 第 4 段 | 停止位 | 1 / 1.5 / 2 | 1 位最常用 |
配置代码通常放在对话框的OnInitDialog里,在CDialogEx::OnInitDialog()之后:
// 打开串口并配置参数 m_ctrlComm.put_CommPort(3); // 参数:COM3,按实际端口号改 m_ctrlComm.put_InputMode(1); // 1 表示以二进制方式接收 m_ctrlComm.put_Settings(_T("9600,N,8,1")); // 波特率9600,无校验,8数据位,1停止位 m_ctrlComm.put_RThreshold(1); // 每收到 1 个字节就触发 OnComm m_ctrlComm.put_SThreshold(0); // 发送不触发事件 if (!m_ctrlComm.get_PortOpen()) { m_ctrlComm.put_PortOpen(TRUE); // 打开串口 }逐行说明:put_CommPort(3)里的 3 对应 COM3,如果设备在 COM1 就写 1;put_InputMode(1)是关键参数,1 表示二进制接收,0 表示文本接收,收十六进制协议帧必须用 1;put_RThreshold(1)表示接收缓冲区每满 1 个字节就触发一次OnComm,设大了会攒包,设 0 则不触发;put_PortOpen(TRUE)是真正打开端口,打开前用get_PortOpen()判断一下,避免重复打开报错。
3.2 OnComm 事件里怎么正确取数据
OnComm是 MSComm 唯一的事件入口,所有接收、发送、错误都从这里进。它通过get_CommEvent()返回一个事件码,接收数据对应常量2(comEvReceive)。取数据用get_Input(),返回一个VARIANT类型,需要转成COleSafeArray再逐字节读。这是最容易翻车的地方,很多人直接get_Input()当字符串用,结果收到0x00就截断了。
// 在对话框头文件里声明事件处理函数 afx_msg void OnCommMscomm1(); // 在 cpp 文件的消息映射里绑定 BEGIN_EVENTSINK_MAP(CMyDlg, CDialogEx) ON_EVENT(CMyDlg, IDC_MSCOMM1, 1, OnCommMscomm1, VTS_NONE) END_EVENTSINK_MAP() // 事件处理实现 void CMyDlg::OnCommMscomm1() { VARIANT variant_inp; COleSafeArray safearray_inp; long len, k; BYTE rxdata[2048]; if (m_ctrlComm.get_CommEvent() == 2) { // 2 = comEvReceive variant_inp = m_ctrlComm.get_Input(); safearray_inp = variant_inp; len = safearray_inp.GetOneDimSize(); for (k = 0; k < len; k++) { safearray_inp.GetElement(&k, rxdata + k); } // rxdata 里就是原始字节,按协议解析 } }逻辑说明:ON_EVENT宏的第三个参数1是 MSComm 事件的固定分发 ID,不能改;get_CommEvent() == 2只处理接收事件,其他事件码(如 3 表示发送完成、4 表示 CTS 变化)按需处理;COleSafeArray的GetOneDimSize()拿到本次收到的字节数,GetElement逐字节取出。参数上,rxdata数组开 2048 字节够一般帧用,如果协议帧更长要相应加大。这样取出来的数据是纯二进制,不会因为0x00或0x1A被截断。
3.3 发送数据与关闭串口的正确姿势
发送用put_Output(),参数是VARIANT,可以直接传CString或字节数组。关闭串口在对话框的OnDestroy里做,顺序是先关端口再销毁控件:
// 发送字符串 CString strSend = _T("AT+RST\r\n"); m_ctrlComm.put_Output(_variant_t(strSend)); // 发送十六进制字节 BYTE cmd[4] = {0x01, 0x03, 0x00, 0x00}; COleSafeArray sa; sa.CreateOneDim(VT_UI1, 4, cmd); m_ctrlComm.put_Output(_variant_t(sa)); // 关闭串口 void CMyDlg::OnDestroy() { if (m_ctrlComm.get_PortOpen()) { m_ctrlComm.put_PortOpen(FALSE); } CDialogEx::OnDestroy(); }参数说明:_variant_t(strSend)把CString包成VARIANT,适合发文本命令;发二进制帧要用COleSafeArray,CreateOneDim(VT_UI1, 4, cmd)表示创建 4 字节的无符号字节数组。关闭时先判断get_PortOpen()再关,避免对已关闭端口操作导致异常。这一步不做的话,程序退出后串口可能被占用,下次打开报“端口已被占用”。
4. 避坑与排查:MSComm 在 VS2010 下的五个血泪坑
这一章全是现场踩出来的。MSComm 本身不复杂,但它在 64 位系统、高 DPI、多线程场景下的表现很“玄学”,下面五条按“现象 → 原因 → 解决”写,遇到问题直接对号入座。
4.1 插入 ActiveX 控件列表里没有 MSComm
现象:右键对话框,“插入 ActiveX 控件”里翻不到 Microsoft Communications Control。原因:三种可能——控件没注册、注册到了 64 位节点、VS2010 没以管理员运行。解决:先用SysWOW64\regsvr32.exe重新注册,再补Wow6432Node下的 Controls 注册表项,最后以管理员身份重启 VS2010。三步做完基本必现。
4.2 编译报错“无法加载类型库”或 CMSComm 未定义
现象:添加变量后编译,提示mscomm.h找不到或CMSComm未声明。原因:包装类没生成成功,通常是控件注册不完整,或者项目字符集与控件不匹配。解决:删掉对话框上已插入的控件,重新插入一次;确认项目属性里“字符集”设为“使用多字节字符集”或“Unicode”都行,但包装类要重新生成。如果还不行,手动在stdafx.h里#include "mscomm.h"。
4.3 收到数据但全是乱码或截断
现象:OnComm触发了,但rxdata里数据不对,或者只收到前半截。原因:InputMode设成了 0(文本模式),遇到0x00就截断;或者RThreshold设得太大,攒包了。解决:put_InputMode(1)改成二进制模式,put_RThreshold(1)改成每字节触发。如果协议帧本身有帧头帧尾,建议在OnComm里先存缓冲区,再按帧解析,不要假设一次OnComm就是一整帧。
4.4 程序退出后串口被占用,下次打不开
现象:第一次运行正常,关掉程序再运行,put_PortOpen(TRUE)失败。原因:OnDestroy里没关串口,或者程序异常退出没走到关闭逻辑。解决:在OnDestroy里强制关闭,并且加异常处理;调试阶段可以在打开前先put_PortOpen(FALSE)再put_PortOpen(TRUE),相当于强制复位。另外,任务管理器里如果还有残留进程,先结束掉。
4.5 在 64 位系统上注册成功但运行时报“类未注册”
现象:regsvr32提示成功,但程序运行到Create控件时弹“类未注册”。原因:控件注册到了 64 位注册表节点,而 VS2010 程序是 32 位,读的是Wow6432Node。解决:确认用的是C:\Windows\SysWOW64\regsvr32.exe而不是System32下的;注册表项要写在HKLM\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0\Controls下。这两处对了,运行时就不会再报。
5. 进阶技巧:用 MSComm 收十六进制协议帧并做校验
基础收发跑通后,真正的活是解析协议帧。工业设备常见帧格式是“帧头 + 长度 + 数据 + 校验 + 帧尾”,比如0xAA 0x55开头,最后一位是前面所有字节的异或校验。MSComm 本身不提供帧解析,得自己在OnComm里攒缓冲区。下面给一个可复用的解析骨架,处理“转义字符”和“校验失败”两个高频问题。
// 成员变量:CByteArray m_rxBuf; 用于攒帧 void CMyDlg::OnCommMscomm1() { if (m_ctrlComm.get_CommEvent() != 2) return; VARIANT variant_inp = m_ctrlComm.get_Input(); COleSafeArray safearray_inp = variant_inp; long len = safearray_inp.GetOneDimSize(); BYTE rxdata[2048]; for (long k = 0; k < len; k++) { safearray_inp.GetElement(&k, rxdata + k); m_rxBuf.Add(rxdata[k]); // 先全部入缓冲区 } // 按帧解析:帧头 0xAA 0x55,第 3 字节为长度 while (m_rxBuf.GetSize() >= 5) { if (m_rxBuf[0] != 0xAA || m_rxBuf[1] != 0x55) { m_rxBuf.RemoveAt(0); // 不是帧头,丢弃一个字节继续找 continue; } int dataLen = m_rxBuf[2]; int frameLen = 3 + dataLen + 1; // 帧头2 + 长度1 + 数据 + 校验1 if (m_rxBuf.GetSize() < frameLen) break; // 帧不完整,等下次 BYTE checksum = 0; for (int i = 0; i < frameLen - 1; i++) { checksum ^= m_rxBuf[i]; // 异或校验 } if (checksum == m_rxBuf[frameLen - 1]) { // 校验通过,处理 m_rxBuf 中前 frameLen 个字节 ProcessFrame(m_rxBuf.GetData(), frameLen); } m_rxBuf.RemoveAt(0, frameLen); // 移除已处理帧 } }逻辑说明:先把所有收到的字节追加到m_rxBuf,再循环找帧头;0xAA 0x55是示例帧头,按你的协议改;dataLen从第 3 字节取,frameLen算出整帧长度;校验用异或,覆盖除校验位外的所有字节。参数上,m_rxBuf用CByteArray自动扩容,但要注意如果一直收不到完整帧,缓冲区会无限增长,建议加一个上限判断,超过 4096 字节就清空重来。
关于“转义字符”,如果协议里用0x7D做转义符(类似 PPP 协议),解析时要在入缓冲区后先做反转义:遇到0x7D就看下一个字节,0x7D 0x5E还原成0x7E,0x7D 0x5D还原成0x7D。这一步放在m_rxBuf.Add之前做,否则帧头判断会错位。我一般会在OnComm开头加一个Unescape()函数处理,保持主解析逻辑干净。
最后说个习惯:MSComm 这套东西,参数配置和事件映射是死的,真正花时间的是协议解析和异常处理。我自己的做法是,每接一个新设备,先用串口助手把帧格式抓清楚,再往OnComm里填解析代码,绝不凭手册直接写。另外,OnComm里不要做耗时操作,比如弹对话框、写大文件,否则会丢数据——这是当年翻车最惨的一次,收了 200 帧只解析出 30 帧,查了一下午才发现是MessageBox阻塞了事件。希望帮到你。
本文还有配套的精品资源,点击获取