Page 1 of 1

Discovering serial ports with EnumPorts()

Posted: Fri Feb 06, 2009 2:21 pm
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)

Posted: Fri Feb 06, 2009 4:19 pm
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.

Posted: Fri Feb 06, 2009 9:54 pm
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.

Posted: Fri Feb 06, 2009 10:46 pm
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.