在 Windows 下有多种方法可以列出串行端口,但我不确定哪种方法是正确的:检测所有可用串行端口的方法。
一个很好的代码示例是 http://www.naughter.com/enumser.html - 其中有 9 种(九种!)枚举串行设备的方法。
问题是:最佳方法是什么。
要求:
COMx
不同名称的端口。void SelectComPort() //added function to find the present serial
{
TCHAR lpTargetPath[5000]; // buffer to store the path of the COMPORTS
DWORD test;
bool gotPort=0; // in case the port is not found
for(int i=0; i<255; i++) // checking ports from COM0 to COM255
{
CString str;
str.Format(_T("%d"),i);
CString ComName=CString("COM") + CString(str); // converting to COM0, COM1, COM2
test = QueryDosDevice(ComName, (LPSTR)lpTargetPath, 5000);
// Test the return value and error if any
if(test!=0) //QueryDosDevice returns zero if it didn't find an object
{
m_MyPort.AddString((CString)ComName); // add to the ComboBox
gotPort=1; // found port
}
if(::GetLastError()==ERROR_INSUFFICIENT_BUFFER)
{
lpTargetPath[10000]; // in case the buffer got filled, increase size of the buffer.
continue;
}
}
if(!gotPort) // if not port
m_MyPort.AddString((CString)"No Active Ports Found"); // to display error message incase no ports found
}
修改了 @Dženan 答案以使用宽字符并返回整数列表
#include <string>
#include <list>
list<int> getAvailablePorts()
{
wchar_t lpTargetPath[5000]; // buffer to store the path of the COM PORTS
list<int> portList;
for (int i = 0; i < 255; i++) // checking ports from COM0 to COM255
{
wstring str = L"COM" + to_wstring(i); // converting to COM0, COM1, COM2
DWORD res = QueryDosDevice(str.c_str(), lpTargetPath, 5000);
// Test the return value and error if any
if (res != 0) //QueryDosDevice returns zero if it didn't find an object
{
portList.push_back(i);
//std::cout << str << ": " << lpTargetPath << std::endl;
}
if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
}
}
return portList;
}
如果您可以访问注册表,则
HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM
键包含 Windows 当前支持的 COM 端口列表(在某些情况下,此信息可能已过时/不正确;例如,我怀疑,当提供串行端口的即插即用设备具有未完成检测/安装或最近已被删除)。
SerialPort.GetPortNames()
方法报告可用 COM 端口的方式,上述信息来自链接页面。
这是 @michael-jacob-mathew 答案的现代化版本:
#include <iostream>
#include <string>
#include <Windows.h>
bool SelectComPort() //added function to find the present serial
{
char lpTargetPath[5000]; // buffer to store the path of the COMPORTS
bool gotPort = false; // in case the port is not found
for (int i = 0; i < 255; i++) // checking ports from COM0 to COM255
{
std::string str = "COM" + std::to_string(i); // converting to COM0, COM1, COM2
DWORD test = QueryDosDevice(str.c_str(), lpTargetPath, 5000);
// Test the return value and error if any
if (test != 0) //QueryDosDevice returns zero if it didn't find an object
{
std::cout << str << ": " << lpTargetPath << std::endl;
gotPort = true;
}
if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
}
}
return gotPort;
}
它在我的计算机上产生以下输出:
COM1: \Device\Serial0
COM3: \Device\VCP0
串行端口是非常简单的设备,可以追溯到计算硬件的石器时代。它们不支持即插即用,无法判断有人插入了设备。您唯一能做的就是发现哪些端口可用,SerialPort.GetPortNames() 返回列表。一些 USB 模拟器可以生成一个描述性名称来配合端口名称,您可以使用 WMI、Win32_SerialPort 类来发现这些名称。
这些都无法帮助您发现哪个 COM 端口连接到特定设备。只有人类知道,她将电缆实际插入了连接器。您需要提供一个配置 UI,让用户选择端口号。组合框即可完成工作。将选择保存在配置数据中,下次程序启动时设备很可能仍连接到同一端口。
对于 Windows,所有串行端口都有一个与“COM%u”匹配的别名。当然! 从Windows 98开始就提供了必要的SetupDi函数集,因此您的软件可以保持相当高的向后兼容性级别。 仅当 DOS 或 Windows 时
此处填写 ComboBoxEx 的示例:< 98 is required, enumerate-by-opening is OK and fast as such systems only support up to COM4.
#include <windowsx.h>
#include <setupapi.h>
#include <devguid.h>
#include <cfgmgr32.h> //CM_Get_Parent
// Fills or re-fills (after WM_DEVICECHANGE) a ComboBoxEx with all COM ports
// and their descriptive names.
// <nr> is the zero-based current (i.e. to be selected) COM port number.
void FillComboComPorts(HWND hCombo,UINT nr) {
ComboBox_ResetContent(hCombo);
HANDLE devs=SetupDiGetClassDevs((LPGUID)&GUID_DEVCLASS_PORTS,0,0,DIGCF_PRESENT);
if (devs==INVALID_HANDLE_VALUE) return;
SP_DEVINFO_DATA devInfo;
devInfo.cbSize=sizeof devInfo;
TCHAR s[80];
COMBOBOXEXITEM cbei;
cbei.mask=CBEIF_IMAGE|CBEIF_LPARAM|CBEIF_OVERLAY|CBEIF_SELECTEDIMAGE|CBEIF_TEXT;
cbei.pszText=s;
for (DWORD i=0; SetupDiEnumDeviceInfo(devs,i,&devInfo); i++) {
HKEY hKey=SetupDiOpenDevRegKey(devs,&devInfo,DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ);
if (hKey==INVALID_HANDLE_VALUE) continue;
TCHAR t[16]; // The COM port name will be placed here
*t=0;
DWORD len=sizeof(t);
RegQueryValueEx(hKey,T("PortName"),0,0,(LPBYTE)t,&len);
RegCloseKey(hKey);
if (*t!='C') continue; // bail out on errors and LPT%u
cbei.lParam=StrToInt(t+3)-1; // I use zero-based numbering
// Already open COM ports are marked with an overlay
// If your <nr> is currently open by design, change code here.
HANDLE h=myOpen((UINT)cbei.lParam);
if (h) CloseHandle(h);
cbei.iOverlay=h?0:2; // 2 is the "not available" overlay.
DEVINST parent; // Graying text would require ownerdrawn combobox and much more code
const GUID*pguid=&GUID_DEVCLASS_PORTS;
// Show class icon for parent(!) device class, so get a clue where your COM port is connected (USB or onboard, or somewhere else)
if (!CM_Get_Parent(&parent,devInfo.DevInst,0)) {
ULONG l=sizeof s;
if (!CM_Get_DevNode_Registry_Property(parent,CM_DRP_CLASSGUID,0,s,&l,0)) {
GUID guid;
if (!CLSIDFromString(s,&guid)) pguid=&guid; // change pointer on success
}
}
SetupDiGetClassImageIndex(&ild,pguid,&cbei.iImage);
cbei.iSelectedImage=cbei.iImage;
// Show descriptive string, not only COM%u
SetupDiGetDeviceRegistryProperty(devs,&devInfo,SPDRP_FRIENDLYNAME,0,(PBYTE)s,sizeof s,0); // ab Windows 2k?
// The COM number should bei included by help of device driver
// If not, append it
if (!StrStr(s,t)) { // Caution! StrStr(Haystack,Needle) vs. strstr(needle,haystack)
int l=lstrlen(s);
wnsprintf(s+l,elemof(s)-l,T(" (%s)"),t);
}
cbei.iItem=myFindIndex(hCombo,cbei.lParam); // helper for sorting by COM number
SendMessage(hCombo,CBEM_INSERTITEM,0,(LPARAM)&cbei);
if (UINT(cbei.lParam)!=nr) continue;
ComboBox_SetCurSel(hCombo,cbei.iItem);
}
SetupDiDestroyDeviceInfoList(devs);
}
// Locates upcoming <t> in sorted list by ItemData, convergates fast
static int myFindIndex(HWND hCombo, LPARAM t) {
int l=0, r=ComboBox_GetCount(hCombo);
while(l!=r) {
int m=(l+r)>>1;
COMBOBOXEXITEM cbei;
cbei.mask=CBEIF_LPARAM;
cbei.iItem=m;
SendMessage(hCombo,CBEM_GETITEM,0,(LPARAM)&cbei);
if (cbei.lParam>t) r=m;
else l=m+1;
}
return l;
}
// My personal zero-based COM port opener; returns 0 on failure
static HANDLE myOpen(UINT ComNr,DWORD dwFlags=0) {
TCHAR s[12];
wnsprintf(s,elemof(s),T("\\\\.\\COM%u"),ComNr+1);
HANDLE h=CreateFile(s,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,dwFlags,0);
if (h==INVALID_HANDLE_VALUE) h=0;
return h;
}
// The ComboBoxEx needs such an ImageList
static SP_CLASSIMAGELIST_DATA ild;
// initialization somewhere before
ild.cbSize=sizeof ild;
SetupDiGetClassImageList(&ild);
SendMessage(hCombo,CBEM_SETIMAGELIST,0,(LPARAM)ild.ImageList);
// and SetupDiDestroyClassImageList(&ild) when done