What is proper way to detect all available serial ports on Windows?
Asked Answered
A

8

10

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
Ashti answered 20/4, 2010 at 9:49 Comment(0)
B
9
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

}
Bourne answered 9/9, 2013 at 5:22 Comment(2)
This line does not do anything: lpTargetPath[10000];Endorsement
Would prefer to work with dynamic lpTargetPath, then in the INSUFFICIENT_BUFFER branch: lpTargetPath = realloc(lpTargetPath, bufSize); bufSize*=2; i--;Clardy
D
6

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;
}
Dresden answered 31/3, 2020 at 11:59 Comment(0)
P
5

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.

Papain answered 10/2, 2015 at 12:16 Comment(2)
Don't see this registry path in windows 10Clardy
@SamGinrich: I'm not seeing it on W11 eitherPapain
W
4

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
Whitten answered 9/7, 2019 at 19:5 Comment(0)
T
3

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.

Thereupon answered 20/4, 2010 at 12:1 Comment(4)
I use SerialPort.GetPortNames().Walloper
Serial ports are so under-rated! Sometimes I don't want any of that complicated plug & play malarky.. I just want to plug in a cable and type. Long live RS232!Landers
@LightnessRacesinOrbit Com ports are fine until you're in the position where you have to try to detect when one has been added, then they're hell.Afroasian
There is a way to detect that a new COM port is available using RegisterDeviceNotificationA (learn.microsoft.com/en-us/windows/win32/api/winuser/…)Sappy
C
1

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

Cabriolet answered 3/1 at 15:18 Comment(0)
F
0

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

Fader answered 27/12, 2020 at 18:38 Comment(0)
T
-3
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);
}
Thorvaldsen answered 6/1, 2016 at 6:3 Comment(1)
Or exactly which library this referencesKingcraft

© 2022 - 2024 — McMap. All rights reserved.