在串口编程中经常需要知道串口号,用来配置串口,可是没有好的办法,只能进到设备管理器中去看,那么如何能够实现软件的自动检测串口号,并且可支持热插拔检测那?
下面将讲述两种方法来实现这种效果:一种是遍历设备列表中的所有串口0-255,二是通过读去注册表来实现检测
在这篇文章中将只讲述循环遍历方法的实现,在下一篇文章中将讲述利用注册表方法的实现
---------------------------------------------------
第一种方法是一种比较简单也比较笨的方法,对于每一个串口一个一个的去试,试完了也就知道了,计算机一般支持0-255即256个串口,可是利用CreateFile创建串口,只能成功创建COM0-COM9串口设备,而COM10及以上的串口创建CreateFile就会返回-1,因为10及以上已经超出的设备的命名规范,需要使用\$device\COM10方式,作为参数传递给CreateFile。
You can use paths longer than MAX_PATH characters by calling the wide (W) version of CreateFile and prepending "\\?\" to the path. The "\\?\" tells the function to turn off path parsing. This lets you use paths that are nearly 32,000 Unicode characters long. However, each component in the path cannot be more than MAX_PATH characters long. You must use fully-qualified paths with this technique. This also works with UNC names. The "\\?\" is ignored as part of the path. For example, "\\?\C:\myworld\private" is seen as "C:\myworld\private", and "\\?\UNC\tom_1\hotstuff\coolapps" is seen as "\\tom_1\hotstuff\coolapps".
1.遍历所有串口源码:
OnInitDialog初始化窗口
BOOL CDetectComDlg::OnInitDialog() { CDialog::OnInitDialog(); ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { CString strAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } SetIcon(m_hIcon, TRUE); // 设置大图标 SetIcon(m_hIcon, FALSE); // 设置小图标 DWORD sStyle = m_ListShow.GetExtendedStyle(); sStyle |= LVS_EX_GRIDLINES; sStyle |= LVS_EX_FULLROWSELECT; m_ListShow.SetExtendedStyle(sStyle); m_ListShow.InsertColumn(0,_T("索引"),LVCFMT_LEFT,100); m_ListShow.InsertColumn(1,_T("串口号"),LVCFMT_LEFT,100); TraversalCom(); //遍历Com口 return TRUE; }TraversalCom函数
void CDetectComDlg::TraversalCom(void) { EnumerateSerialPorts(ports,portse,portsu); unsigned short Counter; unsigned short Setcom; CString str; //获取可用串口个数 Counter = portse.GetSize(); //如果个数大于0 if(Counter > 0) { //初始化串口列表框 for(int i=0; i<Counter; i++) { Setcom = portse.ElementAt(i); str.Format("%d",i); m_ListShow.InsertItem(i,str); str.Format(_T("COM%d "),Setcom); m_ListShow.SetItemText(i,1,str); } } }EnumerateSerialPorts函数
void CDetectComDlg::EnumerateSerialPorts(CUIntArray& ports, CUIntArray& portse, CUIntArray& portsu) { //清除串口数组内容 ports.RemoveAll(); portse.RemoveAll(); portsu.RemoveAll(); //因为至多有255个串口,所以依次检查各串口是否存在 //如果能打开某一串口,或打开串口不成功,但返回的是 ERROR_ACCESS_DENIED错误信息, //都认为串口存在,只不过后者表明串口已经被占用,否则串口不存在 for (int i=1; i<256; i++) { //Form the Raw device name CString sPort; sPort.Format(_T("\\\\.\\COM%d"), i); //Try to open the port BOOL bSuccess = FALSE; HANDLE hPort = ::CreateFile(sPort, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0); if (hPort == INVALID_HANDLE_VALUE) { DWORD dwError = GetLastError(); if (dwError == ERROR_ACCESS_DENIED) { bSuccess = TRUE; portsu.Add(i); //已占用的串口 } } else { //The port was opened successfully bSuccess = TRUE; portse.Add(i); ////可用的串口 //Don't forget to close the port, since we are going to do nothing with it anyway CloseHandle(hPort); } //Add the port number to the array which will be returned if (bSuccess) ports.Add(i); //所有存在的串口 } }
2.检测串口的热插拔
这里主要利用Cwnd的ON_WM_DEVICECHANGE消息来处理。
ON_WM_DEVICECHANGE消息在VS是通过手动添加的
注意:此消息只有顶层窗口可以捕获到
因此,首先得手动添加ON_WM_DEVICECHANGE消息:
第一步:在消息映射BEGIN_MESSAGE_MAP(Ctbox_debug_viewDlg, CDialogEx)中添加:
afx_msg BOOL OnDeviceChange(UINT nEventType, DWORD dwData);
afx_msg BOOL OnDeviceChange(UINT nEventType, DWORD dwData);
BOOL CDetectComDlg::OnDeviceChange(UINT nEventType,DWORD dwData) { //DEV_BROADCAST_DEVICEINTERFACE* dbd = (DEV_BROADCAST_DEVICEINTERFACE*) dwData; switch (nEventType) { case DBT_DEVICEREMOVECOMPLETE://移除设备 case DBT_DEVICEARRIVAL://添加设备 ReDetectCom();//刷新列表框的内容 break; default: break; } return TRUE; }ReDetectCom的内容
void CDetectComDlg::ReDetectCom(void) { m_ListShow.DeleteAllItems(); TraversalCom(); }
由于DEV_BROADCAST_DEVICEINTERFACE,DBT_DEVICEREMOVECOMPLETE,DBT_DEVICEARRIVAL这几个东东在头文件Dbt.h中定义的,所以包含Dbt.h头文件
#include <Dbt.h>
源码:遍历方法的实现:遍历方法的实现