/*************************************************************************
* Serial port enumeration routines
*
* The EnumSerialPort function will populate an array of SSerInfo structs,
* each of which contains information about one serial port present in
* the system. Note that this code must be linked with setupapi.lib,
* which is included with the Win32 SDK.
*
* by Zach Gorman <gormanjz@hotmail.com>
*
* Copyright (c) 2002 Archetype Auction Software, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following condition is
* met: Redistributions of source code must retain the above copyright
* notice, this condition and the following disclaimer.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ARCHETYPE AUCTION SOFTWARE OR ITS
* AFFILIATES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
************************************************************************/
// For MFC
#include "stdafx.h"
// The next 3 includes are needed for serial port enumeration
#include <objbase.h>
#include <initguid.h>
#include <Setupapi.h>
#include "EnumSerial.h"
#pragma comment(lib, "Setupapi.lib")
#pragma comment(lib, "LIBCMT.LIB")
// The following define is from ntddser.h in the DDK. It is also
// needed for serial port enumeration.
#ifndef GUID_CLASS_COMPORT
DEFINE_GUID(GUID_CLASS_COMPORT, 0x86e0d1e0L, 0x8089, 0x11d0, 0x9c, 0xe4, \
0x08, 0x00, 0x3e, 0x30, 0x1f, 0x73);
#endif
//---------------------------------------------------------------
// Helpers for enumerating the available serial ports.
// These throw a CString on failure, describing the nature of
// the error that occurred.
void EnumPortsWdm(CArray<SSerInfo,SSerInfo&> &asi);
void EnumPortsWNt4(CArray<SSerInfo,SSerInfo&> &asi);
void EnumPortsW9x(CArray<SSerInfo,SSerInfo&> &asi);
void SearchPnpKeyW9x(HKEY hkPnp, BOOL bUsbDevice,
CArray<SSerInfo,SSerInfo&> &asi);
//---------------------------------------------------------------
// Routine for enumerating the available serial ports.
// Throws a CString on failure, describing the error that
// occurred. If bIgnoreBusyPorts is TRUE, ports that can't
// be opened for read/write access are not included.
void EnumSerialPorts(CArray<SSerInfo,SSerInfo&> &asi, BOOL bIgnoreBusyPorts)
{
// Clear the output array
asi.RemoveAll();
// Use different techniques to enumerate the available serial
// ports, depending on the OS we're using
OSVERSIONINFO vi;
vi.dwOSVersionInfoSize = sizeof(vi);
if (!::GetVersionEx(&vi)) {
CString str;
str.Format("Could not get OS version. (err=%lx)",
GetLastError());
throw str;
}
// Handle windows 9x and NT4 specially
if (vi.dwMajorVersion < 5) {
if (vi.dwPlatformId == VER_PLATFORM_WIN32_NT)
EnumPortsWNt4(asi);
else
EnumPortsW9x(asi);
}
else {
// Win2k and later support a standard API for
// enumerating hardware devices.
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);
}
}
// Come up with a name for the device.
// If there is no friendly name, use the port name.
if (rsi.strFriendlyName.IsEmpty())
rsi.strFriendlyName = rsi.strPortName;
// If there is no description, try to make one up from
// the friendly name.
if (rsi.strPortDesc.IsEmpty()) {
// If the port name is of the form "ACME Port (COM3)"
// then strip off the " (COM3)"
rsi.strPortDesc = rsi.strFriendlyName;
int startdex = rsi.strPortDesc.Find(" (");
int enddex = rsi.strPortDesc.Find(")");
if (startdex > 0 && enddex ==
(rsi.strPortDesc.GetLength()-1))
// rsi.strPortDesc = rsi.strPortDesc.Left(startdex);
rsi.strPortDesc = rsi.strPortDesc.Left(startdex);
}
if (rsi.strPortName.IsEmpty()) {
// If the port name is of the form "ACME Port (COM3)"
// then strip off the " (COM3)"
rsi.strPortName = rsi.strFriendlyName;
int startdex = rsi.strPortName.Find(" (");
int enddex = rsi.strPortName.Find(")");
if (startdex > 0 && enddex ==
(rsi.strPortName.GetLength()-1))
// rsi.strPortDesc = rsi.strPortDesc.Left(startdex);
rsi.strPortName = rsi.strPortName.Mid(startdex + 2, 4);
}
}
}
// Helpers for EnumSerialPorts
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("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];
TCHAR desc[256];
BOOL bSuccess = SetupDiGetDeviceRegistryProperty(
hDevInfo, &devdata, SPDRP_FRIENDLYNAME, NULL,
(PBYTE)fname, sizeof(fname), NULL);
bSuccess = bSuccess && SetupDiGetDeviceRegistryProperty(
hDevInfo, &devdata, SPDRP_DEVICEDESC, NULL,
(PBYTE)desc, sizeof(desc), NULL);
BOOL bUsbDevice = FALSE;
TCHAR locinfo[256];
if (SetupDiGetDeviceRegistryProperty(
hDevInfo, &devdata, SPDRP_LOCATION_INFORMATION, NULL,
(PBYTE)locinfo, sizeof(locinfo), NULL))
{
// Just check the first three characters to determine
// if the port is connected to the USB bus. This isn't
// an infallible method; it would be better to use the
// BUS GUID. Currently, Windows doesn't let you query
// that though (SPDRP_BUSTYPEGUID seems to exist in
/