Link to home
Start Free TrialLog in
Avatar of LucHoltkamp
LucHoltkamp

asked on

Query available COM-ports

How to retrieve from windows what hardware is connected. I need to know how many COM-ports the system has.
Thanks,
Luc
Avatar of jhance
jhance

Windows version?
You could use something like:

BOOL CommPortAvailable( unsigned int index ) {

    HANDLE hFile = NULL;
    TCHAR com_port[MAX_PATH];
    sprintf( com_port, "COM%d", index );
    hFile = CreateFile( com_port,
            0,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            NULL,
            OPEN_EXISTING,
            0,
            NULL );
    if( INVALID_FILE_HANDLE != hFile ) {
        CloseHandle();
        return( TRUE );
    }
    return( FALSE );
}

unsigned int GetCommCount() {
    unsigned int count = 0;
    while( CommPortAvailable( ++count ) );
    return( --count );
}

There may be a better approach -- probably a system call. But this one should work at least....
Avatar of jkr
>>There may be a better approach

Nope, that's it - I'd have suggested the same...
Avatar of LucHoltkamp

ASKER

I see you use 0 as desired access mode. Would this also work if another program already opened the com-port with exclusive access??
But how is it possible you can't get any information about the hardware in the PC?, There must be some bios/plug&play info somewhere??
The MSDN states for dwDesiredAccess:

0 : Specifies device query access to the object. An application can query device attributes without accessing the device.

So I believe there shouldn't be a problem if any other application has the comm port in use. I agree on your second question though -- I didn't think that my code would be the best you can get. I don't know if jkr is really into this stuff and knows for sure. I certainly don't. I have looked through system calls to directly access hardware drivers by sending commands but couldn't find anything about comm ports.

I guess either jkr or some other expert would be the one to give you a definate answer.
If another program opened a port, this port is no longer available until that other program realeases it. Period. :o)

(you would need certain serial protocols and at at least an arbiter/demux if not)
So are you saying that it is no longer available for access or cannot not even be queried?
Hmm, it is simply that "CreateFile()" on a "port in use" will fail.
In that case the above code doesn't return the number of COM-Ports in a system, but only the subset that currently isn't in use.

Would you know of a registry key that could be queried to find out the number of physically present com-ports?
Let me give you abit of background-info:
We are making a product that can be configured via RS232. The question from the marketing-department is to use auto-detection. So I made a little class that installs a thread for each available COMM-port, that looks for the device. If it finds one it returns the COMM bort and baudrate to the client-code. To anticipate the fact that a user can have several devices already running it releases and reclaims the port if it finds nothing. In this way it will also find devices that were used by another program before. However if I do not know which ports are present on the system I have to test the whole possible range of COMM-ports over and over again. This is wastefull of course.

But the OS itself knows what is present on the system... Or must I assume Microsoft doesn't want user-mode apps to know what hardware is installed?

Luc
Actually....

   DWORD dwNeeded,dwReturned;

    EnumPorts(
  NULL,       // pointer to server name
  1,        // specifies type of port info structure
  szTarget,      // pointer to buffer to receive array of port
                      // info. structures
  10000,        // specifies size, in bytes, of buffer
  &dwNeeded,  // pointer to number of bytes stored into
                      // buffer (or required buffer size)
  &dwReturned  // pointer to number of PORT_INFO_*.
                      // structures stored into buffer
);

    unsigned char* pb = szTarget;
    for(int ix = 0; ix < dwReturned; ix++)
    {
        PORT_INFO_1* p = (PORT_INFO_1*)pb;
        pb += sizeof(PORT_INFO_1);
    }
That will return you ALL ports on the system, including COM, LPT and others you may have.  All you need to do is to look for ones that start with "COM".  Only then I would try to open them with CreateFile to see if they "really" present, cause it is possible that some junk will be returned from EnumPorts....

Of cause 10000 is just some "VERY BIG" number.  Look at EnumPorts for proper use of it...


Hope it helps...
Are you sure this enumerates com ports? The MSDN says: "The EnumPorts function enumerates the ports that are available for printing on a specified server." Being located in the "Platform SDK: Windows GDI"-section makes me a bit suspicious.
ASKER CERTIFIED SOLUTION
Avatar of mblat
mblat

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
It IS possible to open any file/device with access 0 even when it is already opened exclusively for read and write.
It is even explicitly allowed to do DeviceIoControl calls on the file descriptor.
Thank you guys for this great information!
I made a nice little function from it that I will share with you all, it work just great :-)
The function returns the devices in a vector<string> and has some search options:

Usage:

vector<string> vec;
QueryDosDevice(vec);              // find all devices
QueryDosDevice(vec, NULL, "COM"); // find COM devices
//etc

//START OF CPP FILE
#include <windows.h>
#include <string.h>
#include <string>
#include <vector>

using namespace std;

//*********************************************************************
/*!
    \brief Query dos devices
    \pre None
    \post None
    \param ret            [OUT] This vector will be filled
                                with the found devices
    \param pzsDevice      [IN]  Device to Query, if NULL all
                                devices are returned
    \param pzsIncludeMask [IN]  Only devices that contain this
                                string are added to the vector.
                                If NULL or empty it is ignored.
    \param pzsExcludeMask [IN]  Devices that contain this
                                string are \b NOT added to the vector.
                                If NULL or empty it is ignored.
    \param bCaseSensitive [IN]  If true, the masks are case-sensitive
    \return \c true if succesfull, \c false otherwise.
*/
bool
QueryDosDevice( vector<string>& ret,
                const char* pzsDevice      = NULL,
                const char* pzsIncludeMask = NULL,
                const char* pzsExcludeMask = NULL,
                bool        bCaseSensitive = false
              )
{
    char* pBuffer;
    DWORD dwSize    = 256;
    DWORD dwLength  = 0;

    // try ::QueryDosDevice with an increasing buffer-size
    // until we know for certain we have all data
    while (0 == dwLength)
    {
        pBuffer = new char[dwSize];
        if (NULL == pBuffer)
        {
            return false;   // out of memory
        }

        dwLength = ::QueryDosDeviceA(pzsDevice, pBuffer, dwSize);

        if (0 == dwLength)
        {
            delete []pBuffer;
            if (ERROR_INSUFFICIENT_BUFFER != GetLastError())
            {
                return false; // QueryDosDevice failed
            }
            // try a bigger size
            dwSize *= 2;
        }
    }
    // ok, we have our devices, now fill the vector
    ret.clear();

    // make our masks
    char *pzsIM = _strdup(NULL ==  pzsIncludeMask ? "" : pzsIncludeMask);
    if (NULL == pzsIM)
    {
        delete []pBuffer;
        return false;    // out of mem
    }
    char *pzsEM = _strdup(NULL ==  pzsExcludeMask ? "" : pzsExcludeMask);
    if (NULL == pzsEM)
    {
        delete []pBuffer;
        free(pzsIM);
        return false;    // out of mem
    }
    if (!bCaseSensitive)
    {
        _strupr(pzsIM);
        _strupr(pzsEM);
    }

    DWORD dwPos = 0;
    while ('\0' != pBuffer[dwPos])
    {
        char *ps = pBuffer + dwPos;

        // make string
        string str(bCaseSensitive ? ps : _strupr(ps));

        // filter
        if ( ( '\0' == *pzsIM  || string::npos != str.find(pzsIM) ) &&
             ( '\0' == *pzsEM  || string::npos == str.find(pzsEM) )
           )
        {
            ret.push_back(str);
        }

        // next string
        dwPos += strlen(ps) + 1;
    }  

    // cleanup
    delete []pBuffer;
    free(pzsIM);
    free(pzsEM);
    return true;
}
//END OF CPP FILE
Aiiii, so you're using Doxygen, too. I have encountered a problem with it though. I haven't updated lately, so maybe you could tell me if it has been fixed, or whether I'm doing something wrong. I usually like to put \todo tags right in the function at the place where it applies. If I do so, the tag is either connected to the next or previous function (don't remember which one). I'd like to hear from you.

.f
About doxygen:

I make sure I do not place any doxygen comments INSIDE functions, only in headers etc.

Sometimes if doxygen loses track (with some macro's for instance), it often helps to set an explicit tag. So if you generate documentation, and you see it misplaces a comment, just add a \class or \def or \fn tag in the comment.

For \todo, I place it in the header of the function and I also use my own tags inside functions that I simply find with grep. Something like:
//@LH 3-10-2003: Commented out next line to fix PAR 3114, need to investigate further