|
|
The "strPortName" part of the SSerInfo structure does not seem to get populated in the EnumPortsWdm method. It does in SearchPnpKeyW9x and EnumPortsWNt4 (although that one is a bit of a no-brainer.) Is this an oversight? What is the "reccomended" method for pulling the port name using the Wdm method? I imagine it could be stripped off of the Firendly Name, but only when the port is not taken up by a device (a practical example is a friendly name of "Xircom Cardbus Ethernet 100 + Modem 56 (Modem Interface)" when the same item is inserted into the PCMCIA slot of a laptop. It "ties up" COM2, but I cannot determine COM2 from the Friendly Name or the Port Desc...)
Ideas? Suggestions? Thanks much
--JMG
|
|
|
|
|
The SetupDi API didn't provide any simple port name, so I left it out of the WDM method. I didn't need it for my application, so I gave up without much of a fight.
Perhaps the information you need is available through the SetupDiGetDeviceRegistryProperty( ..., SPDRP_LOCATION_INFORMATION, ...) call.
|
|
|
|
|
Hi,
Attached is my version of portlister for w2k that I used a couple of years ago. There is a SetupApi for W9x aswell that can be used with the same result, not suplied here.
Pros:
It uses the ClassName "PORTS" instead of the hard-coded guid.
It also extracts the PortName from the registry in a proper way.
Enjoy!
// Christian
// portlister.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "tchar.h"
#include "setupapi.h"
int main(int argc, char* argv[])
{
GUID ClassGuid[1];
DWORD dwRequiredSize;
BOOL bRet;
HDEVINFO DeviceInfoSet = NULL;
SP_DEVINFO_DATA DeviceInfoData;
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
DWORD dwMemberIndex = 0;
// Get ClassGuid from ClassName for PORTS class
bRet = SetupDiClassGuidsFromName(_T("PORTS"), (LPGUID)&ClassGuid, 1, &dwRequiredSize);
if (!bRet) goto cleanup;
// Get class devices
DeviceInfoSet = SetupDiGetClassDevs(ClassGuid, NULL, NULL, DIGCF_PROFILE);
if (DeviceInfoSet)
{
// Enumerate devices
dwMemberIndex = 0;
while (SetupDiEnumDeviceInfo(DeviceInfoSet, dwMemberIndex++, &DeviceInfoData))
{
TCHAR szFriendlyName[MAX_PATH];
TCHAR szPortName[MAX_PATH];
TCHAR szMessage[MAX_PATH];
DWORD dwReqSize = 0;
DWORD dwPropType;
DWORD dwType = REG_SZ;
HKEY hKey = NULL;
// Get friendlyname
bRet = SetupDiGetDeviceRegistryProperty(DeviceInfoSet,
&DeviceInfoData,
SPDRP_FRIENDLYNAME,
&dwPropType,
(LPBYTE)szFriendlyName,
sizeof(szFriendlyName),
&dwReqSize);
// Open device parameters reg key
hKey = SetupDiOpenDevRegKey(DeviceInfoSet,
&DeviceInfoData,
DICS_FLAG_GLOBAL,
0,
DIREG_DEV,
KEY_READ);
if (hKey)
{
// Qurey for portname
dwReqSize = sizeof(szPortName);
long lRet = RegQueryValueEx(hKey,
_T("PortName"),
0,
&dwType,
(LPBYTE)&szPortName,
&dwReqSize);
// Close reg key
RegCloseKey(hKey);
}
wsprintf(szMessage, _T("Name: %s\nPort: %s\n"), szFriendlyName, szPortName);
MessageBox(NULL, szMessage, _T("Found Port"), MB_OK);
}
}
cleanup:
// Destroy device info list
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
return 0;
}
|
|
|
|
|
Hi,
I have added parts of the proposed code to my EnumSerial, in
void EnumPortsWdm(CArray<sserinfo,sserinfo&> &asi)
right before
if
(bSuccess) { // ...
// Open device parameters reg key
HKEY hKey = SetupDiOpenDevRegKey
(hDevInfo, &devdata, DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ);
TCHAR szPortName[MAX_PATH];
if (hKey)
{
DWORD dwType = REG_SZ;
DWORD dwReqSize = sizeof(szPortName);
// Query for portname
long lRet = RegQueryValueEx
(hKey,_T("PortName"), 0, &dwType, (LPBYTE)&szPortName, &dwReqSize);
if (lRet == ERROR_SUCCESS)
bSuccess &= TRUE;
else
bSuccess &= FALSE;
}
else
bSuccess &= FALSE;
and add the folllowing line in
if (bSuccess) {
si.strPortName = szPortName;
|
|
|
|
|
Why don't you simply call the Win32 API GetDefaultCommConfig() to figure out whether a specific port exists. Optionally followed by CreateFile() if you like to know if another process currently has opened that port.
Works on all Win32 platforms and is much simpler.
Attention:
COM10 or greater must be preceeded with "\\.\" for calls to CreateFile(). See Q115831 for details. WinCE needs a trailing ":" for both GetDefaultCommConfig() and CreateFile().
|
|
|
|
|
If all you need to know is which com #'s exist and whether or not they can be opened, your solution is indeed simpler and more elegant.
If you would like to know the "friendly name" of the serial port, whether it is built-in or comes from a USB adapter, and the full WDM device name, I know of no better way than what I have. The friendly name is especially useful since this is the only piece of information you can get from a WM_DEVINFO message when a new serial port is connected.
Anyway, the fact the the implementation is complicated is no problem for someone who is just trying to use the thing (as long as it's not buggy, which it isn't).
|
|
|
|
|
Zach Gorman wrote:
If all you need to know is which com #'s exist and whether or not they can be opened, your solution is indeed simpler and more elegant.
Another reason against this method is: it takes to much time (10ms per port).
It will be faster to open all ports with CreateFile(...) and if it fails look at the error code with GetLastError().
For example:
void EnumSerialPortsWithoutFriendlyNames( CArray<SSerInfo,SSerInfo&> &asi, BOOL bIgnoreInvalid )
{
SSerInfo si;
for( int i=1; i <= 256; i++ )
{
si.strDevPath.Format( _T("\\\\.\\COM%d"), i );
si.strPortName.Format( _T("COM%d"), i );
si.strFriendlyName = si.strPortName;
si.bIsInvalid = FALSE;
si.dwError = 0L;
HANDLE hSerialPort = CreateFile( si.strDevPath, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL );
if( hSerialPort == INVALID_HANDLE_VALUE )
{
si.dwError = GetLastError();
si.bIsInvalid = TRUE;
LPVOID lpMsgBuf;
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
si.dwError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0,
NULL
);
TRACE( "%s\n", (LPCTSTR)lpMsgBuf );
LocalFree( lpMsgBuf );
}
else
{
::CloseHandle(hSerialPort);
}
if( (!bIgnoreInvalid && (ERROR_FILE_NOT_FOUND!=si.dwError))
|| 0==si.dwError )
{
asi.Add(si);
}
}
}
Zach Gorman wrote:
If you would like to know the "friendly name" of the serial port, whether it is built-in or comes from a USB adapter, and the full WDM device name, I know of no better way than what I have. The friendly name is especially useful since this is the only piece of information you can get from a WM_DEVINFO message when a new serial port is connected.
Yes, it would be very nice to have information about the friendly names of the serial ports. But what's about virutal ports which have no friendly name and which will not be listet in the device manager?
Any idea?
I've tried this software from Wiesemann & Theis GmbH to add some virtual ports:
COM Port Redirector for Windows NT/2000/XP
COM port redirector for Win 95 / 98 / Me
The ports will not be found by using your function EnumPortsWdm().
BTW this ports was not found on Windows 98 until I add this to the EnumPortsW9x(...) function:
BOOL bMatch = ( strcmp(acSubSubEnum,"*PNP0500") == 0 ||
strcmp(acSubSubEnum,"*PNP0501") == 0 ||
strcmp(acSubSubEnum,"Ports" ) == 0 ||
bUsbDevice
);
I don't know if this will be a solution for all virtual ports but for me it works good.
Here is another sugestion to add support for enumerationg serial ports on NT4:
(without friendly names):
void EnumPortsWNt4(CArray<SSerInfo,SSerInfo&> &asi)
{
SSerInfo si;
vector<string> vec;
HKEY hKey;
TCHAR caDevName[40], caPortName[20];
DWORD dwDevNameSize, dwPortNameSize, dwType;
int i;
try
{
if( RegOpenKeyEx( HKEY_LOCAL_MACHINE, "Hardware\\DeviceMap\\SerialComm", 0, KEY_READ,
&hKey) == ERROR_SUCCESS)
{
for( i = 0; TRUE; i++ )
{
dwDevNameSize = sizeof( caDevName );
dwPortNameSize = sizeof( caPortName );
if( RegEnumValue( hKey, i, caDevName, &dwDevNameSize, NULL,
&dwType, (LPBYTE)caPortName, &dwPortNameSize ) != ERROR_SUCCESS )
{
break;
}
si.strDevPath = CString("\\\\.\\") + CString(caPortName);
si.strPortName = CString(caPortName);
si.bIsBusy = FALSE;
si.dwError = 0;
asi.Add(si);
}
RegCloseKey( hKey );
}
}
catch (CString strError)
{
if (hKey != NULL)
RegCloseKey(hKey);
throw strError;
}
}
Perhaps you can use this for future versions of your great code.
Have a nice weekend.
Joerg Hoffmann
|
|
|
|
|
Are you saying that virtual ports can systematically not report themselves?
If they are enumerated on the system then you can find them with the setup api.
To get friendly names or any other registry item is simple with the setup api.
Look if you have an application that needs to list which ports are presently on the system
or have ever enumerated on the system ie USB to Serial then the setup api is best.
All you have to do is call to setupdigetclassdevs what is so complicated?
Mike "Cop" Pulice
|
|
|
|
|
The WDM functionality in the posted code uses the Setup API. Because the Setup API does not exist prior to Windows 2000, there is separate code for enumerating serial ports on NT4 and 98.
|
|
|
|
|
If the actual aim is to enumerate the existing/available COM ports, I'd say the MSDN section about COM ports is sufficient in itself.
Just do an openfile (read/write, exclusive access) using "COM1" to "COM9" for filename (and "\\.\COMxx" if you think your PC has so many UARTs to spare ), check the error code (not forgetting to close the handle if the port exists and is available) and Bob's your uncle!
Maybe the article name is misleading, though. The function proposed here is supposed to get the "decorated" names of all serial devices (alas, no real mean of using them is provided hereafter, which seriously reduces the usefulness of the whole bunch of code IMHO).
EDIT : oops, sorry, hit the wrong button while trying to post my own 2 cents of code
TCHAR port_name[5] = _T("COMx");
for (TCHAR i = _T('1') ; i <= _T('9') ; i++)
{
port_name[3] = i;
HANDLE hport = CreateFile (
port_name,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
switch (GetLastError())
{
case ERROR_SUCCESS :
CloseHandle (hport);
break;
case ERROR_ACCESS_DENIED :
break;
case ERROR_FILE_NOT_FOUND :
break;
default :
;
}
modified on Monday, August 31, 2009 7:09 PM
|
|
|
|
|