检测 Windows 上所有可用串行端口的正确方法是什么?

发布于 2024-08-29 11:45:46 字数 357 浏览 6 评论 0原文

有多种方法可以在 Windows 下列出串行端口,但我不确定什么是正确的方法:检测所有可用串行端口的方法。

一个很好的代码示例是 http://www.naughter.com/enumser.html - 那里有 9 种(九种!)枚举串行设备的方法。

问题是:最好的方法是什么。

要求:

  • 不打开端口以检查它们是否可用。
  • 能够检测与 COMx 名称不同的端口。
  • 在 Windows XP SP2 或更高版本上工作

There are several ways to list serial ports under Windows but I'm not sure what is the proper way: the way that does detect all serial ports that are available.

One good code example is http://www.naughter.com/enumser.html - where there are 9 (nine!) ways of enumerating serial devices.

The question is: what is the optimal way of doing it.

Requirements:

  • to not open ports in order to check if they are available.
  • to be able to detect ports with different names than COMx.
  • to work on Windows XP SP2 or above

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

浅暮の光 2024-09-05 11:45:46
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

}
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

}
心如荒岛 2024-09-05 11:45:46

修改了 @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;
}

Modified @Dženan answer to use wide characters and returning list of ints

#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;
}
枫林﹌晚霞¤ 2024-09-05 11:45:46

如果您可以访问注册表,则 HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM 项包含 Windows 当前支持的 COM 端口列表(在某些情况下,此信息可能已过时/不正确;例如,我怀疑,当提供串行端口的即插即用设备尚未完成检测/安装或最近被删除时)。

这就是 .NET Framework 的 SerialPort 的方式.GetPortNames()方法报告可用的COM端口,上述信息来自链接页面。

If you can access the registry, the HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM key contains a list of COM ports Windows currently supports (in some cases, this information may be stale/incorrect; like, I suspect, when a plug & play device providing serial ports has not completed detection/installation or has been recently removed).

This is the way .NET Framework's SerialPort.GetPortNames() method reports available COM ports, and the above information is derived from the linked page.

风追烟花雨 2024-09-05 11:45:46

这是 @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

This is a modernized version of @michael-jacob-mathew's answer:

#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;
}

It produces the following output on my computer:

COM1: \Device\Serial0
COM3: \Device\VCP0
糖果控 2024-09-05 11:45:46

串行端口是非常简单的设备,可以追溯到计算硬件的石器时代。他们不支持即插即用玩,没有办法知道有人插入了设备。您唯一能做的就是发现哪些端口可用,SerialPort.GetPortNames() 返回列表。一些 USB 模拟器可以生成一个描述性名称来配合端口名称,您可以通过 WMI、Win32_SerialPort 类来发现这些名称。

这些都不能帮助您发现哪个 COM 端口连接到特定设备。只有人类知道,她将电缆实际插入了连接器。您需要提供一个配置 UI,让用户选择端口号。组合框即可完成工作。将选择保存在配置数据中,下次程序启动时设备很可能仍连接到同一端口。

Serial ports are very simple devices, dating from the stone age of computing hardware. They don't support Plug & Play, there is no way to tell that somebody plugged in a device. The only thing you can do is discover what ports are available, the SerialPort.GetPortNames() returns the list. Some USB emulators can generate a descriptive name to go with the port name, you can discover those with WMI, Win32_SerialPort class.

None of which helps you discover what COM port is connected to a particular device. Only a human knows, she physically plugged the cable in the connector. You'll need to provide a config UI that lets the user select the port number. A combo box gets the job done. Save the selection in your config data, it is very likely that the device is still connected to the same port the next time your program starts.

猛虎独行 2024-09-05 11:45:46

枚举 COM 端口的正确方法是使用 SetupDi 函数。
对于最多 256 个端口名称,尝试使用 CreateFile() 打开 COM 端口太慢,并且会跳过已打开的端口名称。

对于 Windows,所有串行端口都有一个与“COM%u”匹配的别名。当然!
从Windows 98开始就提供了必要的SetupDi函数集,因此您的软件可以保持相当高的向后兼容性级别。
仅当 DOS 或 Windows < 时需要98,打开枚举就可以,而且速度很快,因为此类系统最多只支持COM4。

此处填写 ComboBoxEx 的示例:

#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

显示 3 个不同父节点和 2 个无法访问的 COM 端口的屏幕​​截图

The right way to enumerate COM ports is using SetupDi functions.
Trying to open COM ports using CreateFile() is too slow for up to 256 port names, and will skip over already-open ones.

For Windows, all serial ports have an alias matching to "COM%u". Sure!
The necessary set of SetupDi functions is available since Windows 98, so your software can keep a quite high backward-compatibility level.
Only when DOS or Windows < 98 is required, enumerate-by-opening is OK and fast as such systems only support up to COM4.

An example for filling a ComboBoxEx here:

#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

Screenshot showing 3 different parent nodes and 2 inaccessible COM ports

扎心 2024-09-05 11:45:46

您可以检查 Windows 注册表以列出所有 COM 端口。这是我的代码> github 文件

You can check the windows registry base to list all COM ports. Here is my code > github file

滴情不沾 2024-09-05 11:45:46
CUIntArray ports;
EnumerateSerialPorts(ports);

for (int i = 0; i<ports.GetSize(); i++)
{
    CString str;
    str.Format(_T("COM%d"), ports.ElementAt(i));
    m_ctlPort.AddString(str);
}
CUIntArray ports;
EnumerateSerialPorts(ports);

for (int i = 0; i<ports.GetSize(); i++)
{
    CString str;
    str.Format(_T("COM%d"), ports.ElementAt(i));
    m_ctlPort.AddString(str);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文