|
Hi. There are at least 15 types of USB/Serial cables available at the moment. Very few of them seem to conform to ANYthing "standard".
For example, the KEYSPAN USA19QW has its own non-conforming registry key: HKLM\Enum\KEYSPAN\*USA19WMAP\00_00. This key cannot be found in Win98 and WinME by EnumSerial currently because of this line:
BOOL bMatch = (strcmp(acSubSubEnum,"*PNP0500")==0 ||
strcmp(acSubSubEnum,"*PNP0501")==0 ||
bUsbDevice);
I was able to find the KEYSPAN cable and presumably all other USB/Serial cables (assuming they do not depart even further from the "standard") by setting:
bMatch = TRUE;
(This will slow down the performance slightly of course since now more subkeys must be searched. However this is apparently what Device Manager is doing or KEYSPAN would not have been able to place their key where they have placed it...)
I also then had to filter out LPT ports and anything non-"COM" in SearchPnpKeyW9x:
if (!bDup) {
// Add an entry to the array
if(strPortName.Left(3) == "COM"){//Add COM ports only
asi.Add(si);
}
}
Of course if there is some serial port that has a name that does NOT start with COM, it would then not be listed... (Something I read indicated that COM ports do not HAVE to be named "COM" ports... Why are "standards" so non-standard?)
This is not the appropriate thread but I have discovered MANY problems with USB/Serial cables. Very few behave like a UART. For example, if you wire CTS to RTS on the Keyspan cable then assert RTS, the cable cannot see its own RTS signal on the CTS pin until 24 milliseconds have elapsed... (We use this to see if the user has our board plugged in...) Lots of little odds and ends that make these cables a nightmare if your software has to support their use...
Thanks,
Howard
Howard in Arizona
http://www.astroshow.com
|
|
|
|
|
For diagnosis it could be helpfull to get the application which
blocks an comprt.
Any idea?
|
|
|
|
|
I wish I knew how - I've often wanted this for files. Windows tells me a file is in use, but not which process has it open.
|
|
|
|
|
Many thanks to Zach Gorman.
You did really a good job.
Because of my need for using it on Windows NT machines I've tried to replace the fixed linking to setupapi.lib to the LoadLibrary stuff.
(Don't know if it really works on W2K / not testet at the moment)
Know I' searching for a possibility to get the friendly names on NT, too?
How can I do this?
|
|
|
|
|
On the LoadLibrary note, it turns out that there is an easier way in Visual Studio 6 (I assume it's there in .net too but haven't tried it). In the Link tab of 'Project Settings' for all builds, include 'delayimp.lib' under "Object/library modules." Then, at the bottom of "Project Options" add '/delayload:setupapi.dll'
This invokes the compiler's built-in support for delay loading; your program will not attempt to load setupapi.dll until you call a function that lives in it. Since no code in the NT4 execution path calls SetupDi functions, this enables NT4 support.
Regarding friendly names on NT4, I wasn't able to find any during my preliminary search. If they exist at all, you may be able to find them by digging around in the registry; my suggestion is to crack open a copy of regedit and search for COM1 on an NT4 box.
If they exist in the registry, you can add a function to dig them out based on EnumSerial's W98 code. If not, you can always just make up a friendly name - most ports end up being called "Communications Port" anyway.
|
|
|
|
|
Zach Gorman wrote:
On the LoadLibrary note, it turns out that there is an easier way in Visual Studio 6 (I assume it's there in .net too but haven't tried it). In the Link tab of 'Project Settings' for all builds, include 'delayimp.lib' under "Object/library modules." Then, at the bottom of "Project Options" add '/delayload:setupapi.dll'
Thank you for this tip. It works good.
But for my use it will be better to use it with LoadLibrary because it will be easier to share the code between different projects or with other people here on this site.
It seems that on NT no friendly names are used. But there is another way to enumerate serial ports using "QueryDosDevice()".
I'll try to add it to your code on weekend and send it to you by email.
Bye
|
|
|
|
|
It doesn't found USB <-> Serial converter in Win 98. The key of FTDI converter is placed in the HKLM\Enum\FTDIBUS section.
Other thing: I'm looking for a solution to exclude modems from obtained list. I have PCI modem wich was also detected as a port in Win XP. There is a problem with it, because it takes a lot of time to operate on it.
P_awe_L
|
|
|
|
|
Sorry for the delayed reply. I tested the prolific & aten usb2serial converters; I haven't tried the ftdi. If you have a code patch to add ftdi compatibility, post it here.
Regarding filtering out modems, the only approach I can think of off the top of my head is to hack it and look for the word "modem" in the device description.
|
|
|
|
|
Hi,
Regarding modems, why not try to enumerate the MODEMS class instead of the PORTS class.
regards
// Christian
|
|
|
|
|
I have an Edgeport USB Converter with 4 serial ports, which are not found by the code.
I can sometimes find the ports reading straight from registry HKLM\HARDWARE\DEVICEMAP\SERIALCOMM,
but this seems not to be foolproof, as sometimes the ports are not found.
Anyone know why?
|
|
|
|
|
|
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
|
|
|
|
|