Discovering serial ports with EnumPorts()

Windows specific forum
RichardL
Enthusiast
Enthusiast
Posts: 532
Joined: Sat Sep 11, 2004 11:54 am
Location: UK

Discovering serial ports with EnumPorts()

Post by RichardL »

I have spent some time investigating methods of reliably detecting available serial ports on a PC and so far have focussed on using the EnumPorts() call in the MS API. This creates a linear array of PORT_INFO_2 structures each with the following format:
;
struct PORT_INFO_2 {
lpStr pPortName - Pointer to string
lpStr pMonitorName - Pointer to string
lpStr pDescription - Pointer to string
DWORD fPortType - Port type
DWORD reserved
}

Examining the contents shows a large number of possible COM ports as well as printers etc.

The next step was to test each putative COM port by attempting to open it as a serial device and then requesting its status. Ports that opened successfully were the written to an output list.

• The fixed hardware ports in my desk PC were detected OK.
• FTDI based USB Serial adaptors successfully reported when hot plugged / removed. Good.
• Prolific USB Serial adaptors did not appear in the port list in a tidy way, despite being present the in hardware device manager list. After re-booting the PC the Prolific devices moved in and out of the port list satisfactorily.
• Installing a USB Bluetooth interface resulted in an additional port appearing on my list, BUT plugging / removing the Bluetooth hardware unit did not result in the appearance / disappearance of the port in my list; it stayed there and nothing made it go away!

The next step was to try and write a string to each device and examine the number of bytes that were reported as having been sent.

• For the fixed PC ports sending ten characters reported that they were all sent, regardless of a connection being present to hardware. I would expect this in the case of a non-handshake setup.
• The FTDI based serial adaptor disappeared properly and sending bytes was not attempted.
• The same applied to the Prolific serial adaptor
• The Bluetooth serial port always reported as present and accepting bytes regardless of being plugged in or not.

At this stage I am not drawing any conclusions. I observe that EnumPorts() does not always reflect the state of the devices reported in the hardware manager and that a reboot can change the devices that are reported. In my ignorance of the workings of the hardware management I guess that somewhere there is a way of making the OS re-establish the device list that EnumPorts() reports.

Here is the test bed code I have been using. It is based upon material provided by other contributors to other threads, for which many thanks. I will report any more findings of import and would welcome any other inputs.

See also: (http://www.purebasic.fr/english/viewtop ... 600#276600 )

Code: Select all

; Find available serial ports
; Does NOT find ports that are already open

  ; struct PORT_INFO_2 {
  ;   lpStr pPortName       - Pointer to string 
  ;   lpStr pMonitorName    - Pointer to string
  ;   lpStr pDescription    - Pointer to string
  ;   DWORD fPortType       - Port type
  ;   DWORD reserved        
  ; }

Global NewList ComPorts()

Procedure.l GetAvailablePorts(ServerName.s="") 
  
  Protected pcbNeeded.l 
  Protected pcReturned.l 
  Protected *TempBuff 
  Protected i.l 
  Protected *pName 
  Protected ports$
  Protected NumPorts.l
  Protected ComID
  
  If ServerName = "" 
    *pName = 0 
  Else 
    *pName = @ServerName 
  EndIf 
  
  EnumPorts_(*pName, 2, 0, 0, @pcbNeeded, @pcReturned) ; With no buffer address, gets size of temp buffer...
  If pcbNeeded                                         ; and if needed....
    *TempBuff = AllocateMemory(pcbNeeded)              ; Allocate it
    
    If EnumPorts_(*pName, 2, *TempBuff, pcbNeeded, @pcbNeeded, @pcReturned); Gets all data into temp buffer
      
      *P = *TempBuff                                   ; Init pointer to start of buffer
      Sz = SizeOf(PORT_INFO_2)                         ; Size of each group of entries in the buffer
      For i = 0 To pcReturned - 1                      ; For each port...
        
        ; Buffer contains LONGs as POINTER to strings, or LONG as a value
        PortName$    = PeekS(PeekL(*P+00))             ; Get Port name
        MonitorName$ = PeekS(PeekL(*P+04))             ; and Monitor name...
        Description$ = PeekS(PeekL(*P+08))             ; and Description...
         ; Debug PortName$
         ; Debug MonitorName$
         ; Debug Description$
         ; Debug "-----------"
        PortType     = PeekL(*P+12)                    ; and Type.
        *P + Sz                                        ; Move pointer to next entry
        
        ; Test each COM port as it is found by checking it opens properly
        If  Not FindString(LCase(Description$),"infrared",1) ; Not interested in this type
          If Not FindString(LCase(Description$),"modem",1)  ; Not interested in this type
            If Left(PortName$,3) = "COM" And FindString(Description$," Port",1) 
              ComPort$ = RemoveString(PortName$,":") 
              ComID    = OpenSerialPort(#PB_Any,ComPort$,19200,#PB_SerialPort_NoParity,8,1,#PB_SerialPort_NoHandshake,1024,1024)                                          
              If IsSerialPort(ComID) 
                AddElement(ComPorts()) 
                ComPorts() = Val(Mid(PortName$,4,3)) 
                
                ;Debug WriteSerialPortString(ComID,Chr(7)+Str(ElapsedMilliseconds())+Chr(13))
                
                CloseSerialPort(ComID) : Delay(100) 
              EndIf  
            EndIf  
          EndIf 
        EndIf 
        
      Next 
    EndIf
    
    ; 
    SortList(ComPorts(),0)                             ; Sort list of ports
    ports$ = ""                                        ; Null string to receive all port names.
    ForEach ComPorts()                                 ; For each port we found...
      item$ = "COM"+Str(ComPorts())+":"                ; Build in style 'COMn:'
      If FindString(ports$,item$,1) = 0                ; If not already found...
        ports$ + item$                                 ; build string of all found ports so far...
        NumPorts + 1                                   ; and count it.
      EndIf 
    Next 
    
    If *TempBuff : FreeMemory(*TempBuff) : EndIf       ;Free the temp buffer 
  EndIf 
  
  ProcedureReturn NumPorts.l 
EndProcedure 

Repeat
n = GetAvailablePorts("") 
If n
  Debug "Found : "+Str(n)+" Serial ports"
  ForEach ComPorts()
    Debug ComPorts()
  Next
  Debug "---------------"
  ClearList(ComPorts())
EndIf 
Until GetAsyncKeyState_(#VK_ESCAPE)
User avatar
dhouston
Enthusiast
Enthusiast
Posts: 430
Joined: Tue Aug 21, 2007 2:44 pm
Location: USA (Cincinnati)
Contact:

Post by dhouston »

EnumPorts() lists printer ports - serial ports fall into that category although there aren't many serial printers around these days.

I was told by one of the RealBasic people that there is a function in a hardware installation DLL that duplicates the DeviceManager list but I wasn't able to find out any more - he shared neither the DLL nor function names. Since my procedure worked for my purposes I did not pursue it.
User avatar
tinman
PureBasic Expert
PureBasic Expert
Posts: 1102
Joined: Sat Apr 26, 2003 4:56 pm
Location: Level 5 of Robot Hell
Contact:

Post by tinman »

You could have a look here for various methods for enumerating serial ports: http://www.naughter.com/enumser.html

Although it's C++, it uses the Win32 API.

I've had perfect results using the SetupAPI methods (I think that's the device installation DLL you're referring to - it's part of the Win DDK), and a colleague of mine uses the registry hardware key successfully.
If you paint your butt blue and glue the hole shut you just themed your ass but lost the functionality.
(WinXPhSP3 PB5.20b14)
User avatar
dhouston
Enthusiast
Enthusiast
Posts: 430
Joined: Tue Aug 21, 2007 2:44 pm
Location: USA (Cincinnati)
Contact:

Post by dhouston »

tinman wrote:I've had perfect results using the SetupAPI methods (I think that's the device installation DLL you're referring to - it's part of the Win DDK), and a colleague of mine uses the registry hardware key successfully.
I think you're right - the DDK is probably what the fella at RealBasic referred to - I don't have the MS developer tools so did not dig deeper. I mentioned it in case RichardL wants to explore further. My method (which is what RichardL used) works fine for me but, as I noted, I've never tested it with Bluetooth devices.

I initially used the registry but found it to be frequently in error - anybody can write to it and if they don't clean up afterwards it gets filled with junk.
Post Reply