做上位机,与设备通信,经常会用到串口。看到一些串口助手,像SSCOM,能自动扫描枚举PC的串口。所以后面的应用,我也加上自动枚举串口。 在网上找的资料,最多的是读取注册表里的内容,HKEY_LOCAL_MACHINE\ Hardware\DeviceMap\SerialComm"
这个方法很简单,能读到本机串口,虚拟串口,或外扩USB串口等的COM号。代码如下:
typedef struct _SERIALNAMEBUF { int SerialNum; TCHAR ppSerialName[128][10]; } SERIALNAMEBUF, *PSERIALNAMEBUF; int EnumSerialPort(SERIALNAMEBUF *pSerialNameBuf) { int i = 0; HKEY hKey; TCHAR RegKeyName[128],SerialPortName[10]; int Num=0; memset(pSerialNameBuf, 0, sizeof(SERIALNAMEBUF)); if(ERROR_SUCCESS == ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("Hardware\\DeviceMap\\SerialComm"),NULL, KEY_READ, &hKey ))//打开串口注册表对应的键值 { DWORD dwLong,dwSize; while(1) { dwLong = dwSize = sizeof(RegKeyName); if(ERROR_NO_MORE_ITEMS == ::RegEnumValue(hKey,i,RegKeyName,&dwLong,NULL,NULL,(PUCHAR)SerialPortName, &dwSize))//枚举串口 { break; } _tcscpy(pSerialNameBuf->ppSerialName[Num], SerialPortName); Num ++; if (Num == 128) { break; } i++; } } RegCloseKey(hKey); pSerialNameBuf->SerialNum = Num; return Num; }后来,发现在台湾那边的WIN10用了,COM号不正常,WIN7可以。看了SSCOMM的枚举,它是跟设备管理器出来的是一样的,还能把串口描述列出来。于是,我也不去追查注册表法为什么在台湾WIN10用不了,转而看新方法。 一番搜索后,发现SetupAPI这种东西,API里边有SetupDi开头的函数,就是用来做设备管理的。后面找到外国人写的相当不错的源码,就下载来研究,并改良成支持unicode字符的。
外国人的源码链接
具体的代码没研究,呵呵,然后进行改良,直接上改良的代码了,给读者们去研究。SSerInfo中的变量,strFriendlyName,是显示在设备管理器中的信息,如USB-SERIAL CH340 (COM6);strPortDesc,是从strFriendlyName中提取出来的()前的部分,如SERIAL CH340;strPortName是提取出来的COM号,如COM6。
struct SSerInfo { CString strDevPath; // Device path for use with CreateFile() CString strPortName; // Simple name (i.e. COM1) CString strFriendlyName; // Full name to be displayed to a user CString strPortDesc; // friendly name without the COMx }; void EnumSerialPorts(CArray<SSerInfo,SSerInfo&> &asi, BOOL bIgnoreBusyPorts) { // Clear the output array asi.RemoveAll(); EnumPortsWdm(asi); for (int ii=0; ii<asi.GetSize(); ii++) { SSerInfo& rsi = asi[ii]; if (bIgnoreBusyPorts) { // Only display ports that can be opened for read/write HANDLE hCom = CreateFile(rsi.strDevPath, GENERIC_READ | GENERIC_WRITE, 0, /* comm devices must be opened w/exclusive-access */ NULL, /* no security attrs */ OPEN_EXISTING, /* comm devices must use OPEN_EXISTING */ 0, /* not overlapped I/O */ NULL /* hTemplate must be NULL for comm devices */ ); if (hCom == INVALID_HANDLE_VALUE) { // It can't be opened; remove it. asi.RemoveAt(ii); ii--; continue; } else { // It can be opened! Close it and add it to the list ::CloseHandle(hCom); } } if (rsi.strFriendlyName.IsEmpty()) { asi.RemoveAt(ii); ii--; continue; } CString fName = rsi.strFriendlyName; TCHAR *pt, *pt1; pt = fName.GetBuffer() + fName.GetLength() - 1; while (*pt != _T('(')) { pt --; } if (_tcsncmp(pt, _T("(COM"), 4)) { asi.RemoveAt(ii); ii--; continue; } rsi.strPortName.Empty(); rsi.strPortName = _T("COM"); pt1 = pt + 4; while ((*pt1>=_T('0')) && (*pt1<=_T('9'))) { rsi.strPortName += *pt1++; } pt -= 2; pt1 = fName.GetBuffer(); rsi.strPortDesc.Empty(); while (pt1 <= pt) { rsi.strPortDesc += *pt1++; } } } void EnumPortsWdm(CArray<SSerInfo,SSerInfo&> &asi) { CString strErr; // Create a device information set that will be the container for // the device interfaces. GUID *guidDev = (GUID*) &GUID_CLASS_COMPORT; HDEVINFO hDevInfo = INVALID_HANDLE_VALUE; SP_DEVICE_INTERFACE_DETAIL_DATA *pDetData = NULL; try { hDevInfo = SetupDiGetClassDevs( guidDev, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE ); if(hDevInfo == INVALID_HANDLE_VALUE) { strErr.Format(_T("SetupDiGetClassDevs failed. (err=%lx)"), GetLastError()); throw strErr; } // Enumerate the serial ports BOOL bOk = TRUE; SP_DEVICE_INTERFACE_DATA ifcData; DWORD dwDetDataSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + 256; pDetData = (SP_DEVICE_INTERFACE_DETAIL_DATA*) new char[dwDetDataSize]; // This is required, according to the documentation. Yes, // it's weird. ifcData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); pDetData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); for (DWORD ii=0; bOk; ii++) { bOk = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, guidDev, ii, &ifcData); if (bOk) { // Got a device. Get the details. SP_DEVINFO_DATA devdata = {sizeof(SP_DEVINFO_DATA)}; bOk = SetupDiGetDeviceInterfaceDetail(hDevInfo, &ifcData, pDetData, dwDetDataSize, NULL, &devdata); if (bOk) { CString strDevPath(pDetData->DevicePath); // Got a path to the device. Try to get some more info. TCHAR fname[256]; BOOL bSuccess = SetupDiGetDeviceRegistryProperty( hDevInfo, &devdata, SPDRP_FRIENDLYNAME, NULL, (PBYTE)fname, sizeof(fname), NULL); if (bSuccess) { // Add an entry to the array SSerInfo si; si.strDevPath = strDevPath; si.strFriendlyName = fname; asi.Add(si); } } else { strErr.Format(_T("SetupDiGetDeviceInterfaceDetail failed. (err=%lx)"), GetLastError()); throw strErr; } } else { DWORD err = GetLastError(); if (err != ERROR_NO_MORE_ITEMS) { strErr.Format(_T("SetupDiEnumDeviceInterfaces failed. (err=%lx)"), err); throw strErr; } } } } catch (CString strCatchErr) { strErr = strCatchErr; } if (pDetData != NULL) delete [] (char*)pDetData; if (hDevInfo != INVALID_HANDLE_VALUE) SetupDiDestroyDeviceInfoList(hDevInfo); if (!strErr.IsEmpty()) throw strErr; }DEMO的效果 下面,再把例程放出来给大家研究。
windows下枚举串口的方法