"Johnny Bowlegs, pack your kit and trek!"
Device enumeration in the application, as a rule, means that enumeration should do the way as Windows do it in the Device Manager. For example, Windows "sees" Com1, Com2, Com3 and Com27 (it's very possible for Windows 2000/XP). Application also should "see" the same serial ports.
Where does Windows keep information about hardware (and software also) configuration? In the Registry. It is for Windows 98/Me, it is for Windows NT (at least 4.0 as I know) and for Windows 2000/XP also. Information is collected by PnP (Plug'n'Play) Manager and it is supported by Device Manager (setupapi.dll, SDK) and Configuration Manager (cfgmgr32.dll, DDK).
Configuration and Device managers are very tightly connected together via common data structure and some similar functions. Both kits works with Registry when they access to device configuration - they use device classes, create special handles for devices, open device keys for access to device resources (IRQ, etc).
Important Note: Sometimes (for example when you install some multiport cards) device keys for new ports will not be created in the Registry. It can be solved via open property dialog (Device Manager) for each port.
Functions
Following are the functions from Device manager:
WINSETUPAPI BOOL WINAPI SetupDiClassGuidsFromNameA (IN PCSTR ClassName,
OUT LPGUID ClassGuidList,IN DWORD ClassGuidListSize,OUT PDWORD RequiredSize);
Device class represents group of devices. For serial ports, parallel ports device class is "Ports". For representing device class in Registry, class name is not suitable enough. In this case GUID will be preferable and every device class has also GUID for the reference in the Registry. SetupDiClassGuidsFromNameA
"transforms" class name in GUID.
WINSETUPAPI HDEVINFO WINAPI SetupDiGetClassDevsA (IN CONST GUID *ClassGuid,
OPTIONAL IN PCSTR Enumerator, OPTIONAL IN HWND hwndParent,
OPTIONAL IN DWORD Flags);
SetupDiEnumDeviceInfo
returns a context structure for a device information element of a device information set. Each call returns information about one device; the function can be called repeatedly to get information about several devices.
WINSETUPAPI BOOL WINAPI SetupDiDestroyDeviceInfoList(IN HDEVINFO DeviceInfoSet);
The SetupDiDestroyDeviceInfoList
function destroys a device information set (SetupDiGetClassDevsA
) and frees all associated memory.
Following function is from Configuration Manager:
CMAPI CONFIGRET WINAPI CM_Open_DevNode_Key(IN DEVINST dnDevNode,
IN REGSAM samDesired, IN ULONG ulHardwareProfile,
IN REGDISPOSITION Disposition, OUT PHKEY phkDevice, IN ULONG ulFlags);
CM_Open_DevNode_Key
opens a registry storage key associated with a device instance.
Next figure demonstrates the way of using these functions.
Enumerating serial ports
So any device Registry has a device key. For this key exist some names that hold information about configuration parameters. Value of name "portname" is name of port. This value can be "LPN" for parallel ports or "COMN" for serial ports (where N is number of port).
On the next figure, you can see how system do enumeration:
I created following functions for enumerations: BeginEnumeratePorts
, EnumeratePortsNext
, EndEnumeratePorts
.
HANDLE BeginEnumeratePorts(VOID);
The BeginEnumeratePorts
function opens an enumeration handle for serial ports and returns this handle.
BOOL EnumeratePortsNext (HANDLE DeviceInfoSet, LPTSTR lpBuffer);
The EnumeratePortsNext
function continues enumeration from a previous call to the BeginEnumeratePorts
function. You need to prepare buffer (lpBuffer
) before calling this function. If the call is success, the function returns true
and sends name of port in buffer. Call this function till it returns false
.
BOOL EndEnumeratePorts(HANDLE DeviceInfoSet);
The EndEnumeratePorts
function closes the specified enumeration handle. Below you can see source code of these functions:
#include <setupapi.h>
#define MAX_NAME_PORTS 7
#define RegDisposition_OpenExisting (0x00000001)
#define CM_REGISTRY_HARDWARE (0x00000000)
typedef DWORD WINAPI
(* CM_Open_DevNode_Key)(DWORD, DWORD, DWORD, DWORD, ::PHKEY, DWORD);
HANDLE BeginEnumeratePorts(VOID)
{
BOOL guidTest=FALSE;
DWORD RequiredSize=0;
int j;
HDEVINFO DeviceInfoSet;
char* buf;
guidTest=SetupDiClassGuidsFromNameA(
"Ports",0,0,&RequiredSize);
if(RequiredSize < 1)return -1;
buf=malloc(RequiredSize*sizeof(GUID));
guidTest=SetupDiClassGuidsFromNameA(
"Ports",buf,RequiredSize*sizeof(GUID),&RequiredSize);
if(!guidTest)return -1;
DeviceInfoSet=SetupDiGetClassDevs(
buf,NULL,NULL,DIGCF_PRESENT);
if(DeviceInfoSet == INVALID_HANDLE_VALUE)return -1;
free(buf);
return DeviceInfoSet;
}
BOOL EnumeratePortsNext(HANDLE DeviceInfoSet, LPTSTR lpBuffer)
{
static CM_Open_DevNode_Key OpenDevNodeKey=NULL;
static HINSTANCE CfgMan;
int res1;
char DevName[MAX_NAME_PORTS]={0};
static int numDev=0;
int numport;
SP_DEVINFO_DATA DeviceInfoData={0};
DeviceInfoData.cbSize=sizeof(SP_DEVINFO_DATA);
if(!DeviceInfoSet || !lpBuffer)return -1;
if(!OpenDevNodeKey)
{
CfgMan=LoadLibrary("cfgmgr32");
if(!CfgMan)return FALSE;
OpenDevNodeKey=
(CM_Open_DevNode_Key)GetProcAddress(CfgMan,"CM_Open_DevNode_Key");
if(!OpenDevNodeKey)
{
FreeLibrary(CfgMan);
return FALSE;
}
}
while(TRUE)
{
HKEY KeyDevice;
DWORD len;
res1=SetupDiEnumDeviceInfo(
DeviceInfoSet,numDev,&DeviceInfoData);
if(!res1)
{
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
FreeLibrary(CfgMan);
OpenDevNodeKey=NULL;
return FALSE;
}
res1=OpenDevNodeKey(DeviceInfoData.DevInst,KEY_QUERY_VALUE,0,
RegDisposition_OpenExisting,&KeyDevice,CM_REGISTRY_HARDWARE);
if(res1 != ERROR_SUCCESS)return NULL;
len=MAX_NAME_PORTS;
res1=RegQueryValueEx(
KeyDevice,
"portname",
NULL,
NULL,
DevName,
&len
);
RegCloseKey(KeyDevice);
if(res1 != ERROR_SUCCESS)return NULL;
numDev++;
if(memicmp(DevName, "com", 3))continue;
numport=atoi(DevName+3);
if(numport > 0 && numport <= 256)
{
strcpy(lpBuffer,DevName);
return TRUE;
}
FreeLibrary(CfgMan);
OpenDevNodeKey=NULL;
return FALSE;
}
}
BOOL EndEnumeratePorts(HANDLE DeviceInfoSet)
{
if(SetupDiDestroyDeviceInfoList(DeviceInfoSet))return TRUE;
else return FALSE;
}
FIFO Control
To open serial port properties in the Device Manager on the second page, you can find button Additional (Advanced) that opens FIFO dialog:
The main idea of next part of article is the demonstration of control of this page from application. There is a little difference for various operating systems, but main idea is the same. Set/get FIFO control enable/disable and size are available via access to some values in the Registry (for serial port device key).
- Windows 2000/XP
To enable/disable FIFO the application, we should use the name "ForceFifoEnable". If value is equal to 0, FIFO is disabled. If 1, enabled.
To set/get FIFO size the application should use names:
- To receive buffer - RxFIFO
Possible values: 1,4,8,14 (that is mean size of buffer)
- for transmit buffer - TxFIFO
Possible values: from 1 to 16 (that is mean size of buffer)
- Windows 98/Me
Enable/disable FIFO and FIFO size are controlled via name "settings". Value of name "settings" consists of four bytes.
To enable/disable FIFO the application should use first byte of "settings". If value is equal to 0x00, FIFO is disabled, if 0x02, FIFO enabled.
To set/get FIFO size for receiving buffer, the application should use fourth byte of "settings". Possible values: 0x00,0x40,0x80,0xC0 (that is means 1, 4, 8, 16 for size of buffer).
To set/get FIFO size for transmitting buffer, the application should use second byte of "settings". Possible values: 0x01, 0x06, 0x0B, 0x10 (that means 1, 4, 8, 14 for size of buffer).
Function GetDeviceKey
returns device key using port name as input parameter:
HKEY GetDeviceKey(LPTSTR portName)
{
static HINSTANCE CfgMan;
static CM_Open_DevNode_Key OpenDevNodeKey=NULL;
static int numDev=0;
int res1;
BOOL guidTest=FALSE;
DWORD RequiredSize=0;
char* buf;
HDEVINFO DeviceInfoSet;
SP_DEVINFO_DATA DeviceInfoData={0};
char DevName[MAX_NAME_PORTS]={0};
guidTest=SetupDiClassGuidsFromNameA(
"Ports",0,0,&RequiredSize);
if(RequiredSize < 1)return -1;
buf=malloc(RequiredSize*sizeof(GUID));
guidTest=SetupDiClassGuidsFromNameA(
"Ports",buf,RequiredSize*sizeof(GUID),&RequiredSize);
if(!guidTest)return -1;
DeviceInfoSet=SetupDiGetClassDevs(
buf,NULL,NULL,DIGCF_PRESENT);
if(DeviceInfoSet == INVALID_HANDLE_VALUE)return -1;
free(buf);
if(!OpenDevNodeKey)
{
CfgMan=LoadLibrary("cfgmgr32");
if(!CfgMan)return -1;
OpenDevNodeKey=
(CM_Open_DevNode_Key)GetProcAddress(CfgMan,"CM_Open_DevNode_Key");
if(!OpenDevNodeKey)
{
FreeLibrary(CfgMan);
return -1;
}
}
DeviceInfoData.cbSize=sizeof(SP_DEVINFO_DATA);
while(TRUE)
{
HKEY KeyDevice;
DWORD len;
res1=SetupDiEnumDeviceInfo(
DeviceInfoSet,numDev,&DeviceInfoData);
if(!res1)
{
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
FreeLibrary(CfgMan);
OpenDevNodeKey=NULL;
return -1;
}
res1=OpenDevNodeKey(DeviceInfoData.DevInst,KEY_QUERY_VALUE | KEY_WRITE,0,
RegDisposition_OpenExisting,&KeyDevice,CM_REGISTRY_HARDWARE);
if(res1 != ERROR_SUCCESS)
{
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
FreeLibrary(CfgMan);
OpenDevNodeKey=NULL;
return -1;
}
len=MAX_NAME_PORTS;
res1=RegQueryValueEx(
KeyDevice,
"portname",
NULL,
NULL,
DevName,
&len
);
if(res1 != ERROR_SUCCESS)
{
RegCloseKey(ERROR_SUCCESS);
FreeLibrary(CfgMan);
OpenDevNodeKey=NULL;
return -1;
}
if(!stricmp(DevName,portName))
{
FreeLibrary(CfgMan);
OpenDevNodeKey=NULL;
return KeyDevice;
}
numDev++;
}
}
When device key is known, Registry functions (RegQueryValueEx
, RegSetValueEx
) are used to access FIFO parameters. And last function, RegCloseKey
, to close device key in Registry.
Demo application
After running push button "Enumerate", names of serial ports will appear in list box. Clicking on name, you will obtain information about current FIFO status. Change this status via controls and pushing button "Set". You can download source code (C++ Builder) and exe file that is attached to article.
Additional Links
TPortControl v.2.00 for synchronous RS232 serial communication (C++ Builder component, registration required only)
Vladimir Afanasyev.