2015年7月27日 星期一

BLE in Linux Two : Set a Simple Peripheral


      Resuming from last article , I modify the bluez code to be a simple peripheral (GATT server).


Step 0.
  download the last bluez .

 and
    
Step 1.

use the parameters to configurate it.

gaiger@i5-3210m:~/sandbox/bluez-5.32/src$./configure --enable-experimental --enable-maintainer-mode --prefix=$PWD/built CFLAGS="$CFLAGS -Wno-sign-compare -Wno-unused-function"  

If you encounter some error like that :

configure: error: libical is required

or

checking for UDEV... no
configure: error: libudev >= 172 is required

 just install those libraries. I note here, in fedora, the libudev has been included in systemd package, you should use that command line:


gaiger@i5-3210m:~/sandbox/bluez-5.32/src$sudo yum install systemd-devel

To install libudev-dev.


After configuration has been done, the building should be passed without error.

Step2.

   Stop current bluetoothd program:

In fedora :


gaiger@i5-3210m:~/sandbox/bluez-5.32/src$sudo killall -p bluetoothd

In Ubuntu :


gaiger@i5-3210m:~/sandbox/bluez-5.32/src$sudo service bluetooth  status
bluetooth start/running, process 2619


gaiger@i5-3210m:~/sandbox/bluez-5.32/src$ sudo service bluetooth stop
bluetooth stop/waiting


Then, goto the src folder under your bluez package, and execute that:


[gaiger@localhost src]$ sudo ./bluetoothd -p xx -n

If the output log be :


bluetoothd[12465]: Bluetooth daemon 5.30
bluetoothd[12465]: Starting SDP server
bluetoothd[12465]: Bluetooth management interface 1.7 initialized
bluetoothd[12465]: Failed to obtain handles for "Service Changed" characteristic
bluetoothd[12465]: Failed to register org.bluez.LEAdvertisingManager1
bluetoothd[12465]: Failed to register LEAdvertisingManager1 interface for adapter
bluetoothd[12465]: Not enough free handles to register service
bluetoothd[12465]: gatt-example-adapter-driver: Input/output error (5)
bluetoothd[12465]: Not enough free handles to register service
bluetoothd[12465]: Error adding Link Loss service
bluetoothd[12465]: Not enough free handles to register service
bluetoothd[12465]: Not enough free handles to register service
bluetoothd[12465]: Not enough free handles to register service
bluetoothd[12465]: Current Time Service could not be registered
bluetoothd[12465]: gatt-time-server: Input/output error (5)
bluetoothd[12465]: Not enough free handles to register service
bluetoothd[12465]: Not enough free handles to register service
bluetoothd[12465]: Sap driver initialization failed.
bluetoothd[12465]: sap-server: Operation not permitted (1)
bluetoothd[12465]: Endpoint registered: sender=:1.122 path=/MediaEndpoint/A2DPSource
bluetoothd[12465]: Endpoint registered: sender=:1.122 path=/MediaEndpoint/A2DPSink


    That is,  the gatt-server fails in start. The only solution I know currently, is to use the Bluez version which is not later than 5.28. please back to Step 0, use version 5.28 and do all again.


    if your result be :


[gaiger@localhost src]$ sudo ./bluetoothd -p xx -n
bluetoothd[31541]: Bluetooth daemon 5.28
bluetoothd[31541]: Starting SDP server
bluetoothd[31541]: Ignoring (cli) hostname
bluetoothd[31541]: Ignoring (cli) wiimote
bluetoothd[31541]: Ignoring (cli) autopair
bluetoothd[31541]: Ignoring (cli) policy
bluetoothd[31541]: Ignoring (cli) gatt_example
bluetoothd[31541]: Ignoring (cli) neard
bluetoothd[31541]: Ignoring (cli) sap
bluetoothd[31541]: Ignoring (cli) a2dp
bluetoothd[31541]: Ignoring (cli) avrcp
bluetoothd[31541]: Ignoring (cli) network
bluetoothd[31541]: Ignoring (cli) input
bluetoothd[31541]: Ignoring (cli) hog
bluetoothd[31541]: Ignoring (cli) health
bluetoothd[31541]: Ignoring (cli) gap
bluetoothd[31541]: Ignoring (cli) scanparam
bluetoothd[31541]: Ignoring (cli) deviceinfo
bluetoothd[31541]: Ignoring (cli) alert
bluetoothd[31541]: Ignoring (cli) time
bluetoothd[31541]: Ignoring (cli) proximity
bluetoothd[31541]: Ignoring (cli) thermometer
bluetoothd[31541]: Ignoring (cli) heartrate
bluetoothd[31541]: Ignoring (cli) cyclingspeed
bluetoothd[31541]: Ignoring (cli) external_dummy
bluetoothd[31541]: Bluetooth management interface 1.7 initialized

  Congratulation, you could go below.

Step 3.
  Excute the command :


[gaiger@localhost src]$ sudo ./bluetoothd -p gatt_example -n

And run this script, bleAdvertise:



#!/bin/bash
# ref: http://www.theregister.co.uk/Print/2013/11/29/feature_diy_apple_ibeacons/
set -x
# inquiry local bluetooth device
#hcitool dev
export BLUETOOTH_DEVICE=hci0
#sudo hcitool -i hcix cmd <OGF> <OCF> <No. Significant Data Octets> <iBeacon Prefix> <UUID> <Major> <Minor> <Tx Power> <Placeholder Octets>

#OGF = Operation Group Field = Bluetooth Command Group = 0x08
#OCF = Operation Command Field = HCI_LE_Set_Advertising_Data = 0x0008
#No. Significant Data Octets (Max of 31) = 1E (Decimal 30)
#iBeacon Prefix (Always Fixed) = 02 01 1A 1A FF 4C 00 02 15

export OGF="0x08"
export OCF="0x0008"
#export IBEACONPROFIX="1E 02 01 1A 1A FF 4C 00 02 15"
export IBEACONPROFIX="1E 02 01 16 1A FF 4C 00 02 15"


#uuidgen  could gerenate uuid
export UUID="00 00 FF FF 00 00 10 00 80 00 00 80 5F 9B 34 FB"
#export UUID="B9 40 7F 30 F5 F8 46 6E AF F9 25 55 6B 57 FE 6D"
#export UUID="76 E8 B4 E0 7E B5 11 E4 B4 A9 08 00 20 0C 9A 66"

export MAJOR="00 01"
export MINOR="00 00"
export POWER="C5 00"

 initialize device
sudo hciconfig $BLUETOOTH_DEVICE up
# disable advertising 
sudo hciconfig $BLUETOOTH_DEVICE noleadv
# stop the dongle looking for other Bluetooth devices
sudo hciconfig $BLUETOOTH_DEVICE noscan

sudo hciconfig $BLUETOOTH_DEVICE pscan

#hciconfig $BLUETOOTH_DEVICE iscan
#sudo hciconfig $BLUETOOTH_DEVICE name "i5-3210M "$BLUETOOTH_DEVICE
sudo hciconfig $BLUETOOTH_DEVICE leadv

# advertise 
sudo hcitool -i $BLUETOOTH_DEVICE cmd 0x08 0x0008 $IBEACONPROFIX $UUID $MAJOR $MINOR $POWER
#sudo hcitool -i $BLUETOOTH_DEVICE cmd 0x08 0x0006 A0 00 A0 00 00 00 00 00 00 00 00 00 00 07 00
#sudo hcitool -i $BLUETOOTH_DEVICE cmd 0x08 0x000a 01

#sudo hciconfig $BLUETOOTH_DEVICE leadv 
#sudo hciconfig $BLUETOOTH_DEVICE pscan
echo "complete" 

 You should be able to use your mobile application (BLE scanner for Android, LightBlue for iOS) 
to scan and connect to this Bluez peripheral.
 




 

That shows the bluez gatt-server works.

 Step 4.
 Backup the plugin/wiimote.c , and replace that as below code completely:
  
 

/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2011-2012 David Herrmann <dh.herrmann@googlemail.com>
 *
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <unistd.h>
 
 
#include <stdbool.h>

#include <bluetooth/bluetooth.h>
#include <glib.h>

#include "lib/uuid.h"
#include "src/plugin.h"
#include "src/adapter.h"
#include "src/shared/util.h"
#include "src/log.h"
#include "attrib/gattrib.h"
#include "attrib/gatt-service.h"
#include "attrib/att.h"
#include "attrib/gatt.h"
#include "attrib/att-database.h"
#include "src/attrib-server.h"


#define MAX_STR_LEN       (256)

#define SIMPLE_SVC_UUID      0xfff0
#define SIMPLE_READ1_CHAR_UUID    0xfff1
#define SIMPLE_READ2_CHAR_UUID    0xfff2
#define SIMPLE_WRITE_CHAR_UUID    0xfff3  
#define SIMPLE_NOTIFY_CHAR_UUID    0xfff4


static char read1Data[MAX_STR_LEN];
static char read2Data[MAX_STR_LEN];

static int notifyData;

static uint8_t SimpleCharacteristic1Read(struct attribute *a,
      struct btd_device *device, gpointer user_data)
{
 struct btd_adapter *adapter;
 
 printf("__FILE__ = %s, __FUNCTION__ = %s, __LINE__ =%d\n", 
  __FILE__, __FUNCTION__, __LINE__);
 
 
 adapter = user_data;
 
 attrib_db_update(adapter, a->handle, NULL, 
  (uint8_t*)&read1Data[0], strlen(&read1Data[0]), NULL);
 
 return 0;
}/*Characteristic1Read*/      

     

static uint8_t SimpleCharacteristic2Read(struct attribute *a,
      struct btd_device *device, gpointer user_data)
{
 struct btd_adapter *adapter;
 
 printf("__FILE__ = %s, __FUNCTION__ = %s, __LINE__ =%d\n", 
  __FILE__, __FUNCTION__, __LINE__);
 
  
 adapter = user_data;
 
 attrib_db_update(adapter, a->handle, NULL, 
  (uint8_t*)&read2Data[0], strlen(&read2Data[0]), NULL);
  
 return 0;
}/*Characteristic2Read*/ 


static uint8_t SimpleCharacteristicWrite(struct attribute *a,
      struct btd_device *device, gpointer user_data)
{

 unsigned char data[MAX_STR_LEN];
 int i;
 
 printf("__FILE__ = %s, __FUNCTION__ = %s, __LINE__ =%d\n", 
  __FILE__, __FUNCTION__, __LINE__);
 
 memset(&data[0], 0, MAX_STR_LEN);
 
 memcpy(&data[0], a->data, a->len);
  
 printf("written data : %s \n", &data[0]); 

 for(i = 0; i< a->len;i++)
  printf("%#1x ", (unsigned char)(data[i]));
 printf("\n"); 
 
 return 0;
}/*CharacteristicWrite*/


static uint8_t SimpleCharacteristicNotify(struct attribute *a,
      struct btd_device *device, gpointer user_data)
{
 struct btd_adapter *adapter;
 
 adapter = user_data;
 
 
 printf("__FILE__ = %s, __FUNCTION__ = %s, __LINE__ =%d\n", 
  __FILE__, __FUNCTION__, __LINE__);
 
 do
 { 
  attrib_db_update(adapter, a->handle, NULL, 
   (uint8_t*)&notifyData, sizeof(notifyData), NULL);  
   
  //usleep(1*1000*1000);
  notifyData++; 
 }while(0); 
 
 return 0;
}/*CharacteristicNotify*/


static void RegisterSimpleService(struct btd_adapter *adapter)
{
 bt_uuid_t uuid;
 printf("__FILE__ = %s, __FUNCTION__ = %s, __LINE__ =%d\n", 
  __FILE__, __FUNCTION__, __LINE__);
 
 bt_uuid16_create(&uuid, SIMPLE_SVC_UUID);
 
 gatt_service_add(adapter, GATT_PRIM_SVC_UUID, &uuid,
 
 /* characteristic register*/
 
   /*read 1*/
   GATT_OPT_CHR_UUID16, SIMPLE_READ1_CHAR_UUID,
   GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ ,
   GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
     SimpleCharacteristic1Read, adapter,
     
   /*read 2*/
   GATT_OPT_CHR_UUID16, SIMPLE_READ1_CHAR_UUID,
   GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ ,
   GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
     SimpleCharacteristic2Read, adapter,

   /*write*/
   GATT_OPT_CHR_UUID16, SIMPLE_WRITE_CHAR_UUID,
   GATT_OPT_CHR_PROPS, GATT_CHR_PROP_WRITE_WITHOUT_RESP,
   GATT_OPT_CHR_VALUE_CB, ATTRIB_WRITE,
     SimpleCharacteristicWrite, adapter,

   /*NOTIFY*/
   GATT_OPT_CHR_UUID16, SIMPLE_NOTIFY_CHAR_UUID,
   GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ|GATT_CHR_PROP_NOTIFY,
   GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
     SimpleCharacteristicNotify, adapter,     
   /*end*/    
 GATT_OPT_INVALID);
 
 return ;      
}/*RegisterSimpleService*/

#define DEVICEINFO_SVC_UUID     0x180a 

char versionStr[MAX_STR_LEN] = "0.0.1";
char manufacturerStr[MAX_STR_LEN] = "Gaiger";   


static uint8_t SoftwareRevisionStringRead(struct attribute *a,
      struct btd_device *device, gpointer user_data)
{
 struct btd_adapter *adapter;
 
 
 printf("__FILE__ = %s, __FUNCTION__ = %s, __LINE__ =%d\n", 
  __FILE__, __FUNCTION__, __LINE__);
   
 adapter = user_data;
 
 attrib_db_update(adapter, a->handle, NULL, 
  (uint8_t*)&versionStr[0], strlen(&versionStr[0]), NULL);
  
 return 0;
}/*SoftwareRevisionStringRead*/ 


static uint8_t ManufacturerStringRead(struct attribute *a,
      struct btd_device *device, gpointer user_data)
{
 struct btd_adapter *adapter;
 
 printf("__FILE__ = %s, __FUNCTION__ = %s, __LINE__ =%d\n", 
  __FILE__, __FUNCTION__, __LINE__);
   
 adapter = user_data;
 
 attrib_db_update(adapter, a->handle, NULL, 
  (uint8_t*)&manufacturerStr[0], strlen(&manufacturerStr[0]), NULL);
  
 return 0;
}/*ManufacturerStringRead*/ 


static void RegisterDeviceInfo(struct btd_adapter *adapter)
{
 bt_uuid_t uuid;
 printf("__FILE__ = %s, __FUNCTION__ = %s, __LINE__ =%d\n", 
  __FILE__, __FUNCTION__, __LINE__);
 
 bt_uuid16_create(&uuid, DEVICEINFO_SVC_UUID);
 
 gatt_service_add(adapter, GATT_PRIM_SVC_UUID, &uuid,
 
 /* characteristic register*/
     
   /*GATT_CHARAC_SOFTWARE_REVISION_STRING*/
   GATT_OPT_CHR_UUID16, GATT_CHARAC_SOFTWARE_REVISION_STRING,
   GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ ,
   GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
     SoftwareRevisionStringRead, adapter,
   
   /*GATT_CHARAC_MANUFACTURER_NAME_STRING*/
   GATT_OPT_CHR_UUID16, GATT_CHARAC_MANUFACTURER_NAME_STRING,
   GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ ,
   GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
     ManufacturerStringRead, adapter, 
   /*end*/    
 GATT_OPT_INVALID);
 
 return ;      
}/*RegisterSimpleService*/

static void update_name(struct btd_adapter *adapter, gpointer user_data)
{
 adapter_set_name(adapter, (char*)user_data);
}/*update_name*/



static int wii_probe(struct btd_adapter *adapter)
{
  
 update_name(adapter, "SimplePeripherial");
 RegisterDeviceInfo(adapter);
 RegisterSimpleService(adapter);
 
 return 0;
}/*wii_probe*/


static void wii_remove(struct btd_adapter *adapter)
{

}/*wii_remove*/

/*function pointers*/
static struct btd_adapter_driver wii_driver = {
 .name = "wiimote",
 .probe = wii_probe,
 .remove = wii_remove,
};


static int wii_init(void)
{
 printf("__FUNCTION__ = %s\n", __FUNCTION__);
 
 
 memset(&read1Data[0], 0, MAX_STR_LEN);
 memset(&read2Data[0], 0, MAX_STR_LEN);
 notifyData = 0;
 
 snprintf(&read1Data[0], MAX_STR_LEN, "it is read 1");
 snprintf(&read2Data[0], MAX_STR_LEN, "it is read 2");
 
 return btd_register_adapter_driver(&wii_driver);
}

static void wii_exit(void)
{
 printf("__FUNCTION__ = %s\n", __FUNCTION__);
 btd_unregister_adapter_driver(&wii_driver);
}

BLUETOOTH_PLUGIN_DEFINE(wiimote, VERSION,
  BLUETOOTH_PLUGIN_PRIORITY_LOW, wii_init, wii_exit)


 

And re-compiler the bluez library (you could just delete plugins/bluetoothd-wiimote.o and 
type make again to boost the compilation time).

Step 5.
    Similar with Step 3
Execute the command line under src folder:

[gaiger@localhost src]$ sudo ./bluetoothd -p wiimote -n


And run the BleAdvertise script again : you may need to run it twice, to ensure that has been executed indeedly.

  Now you could use your BLE Scanner or LightBlue to confirm your Pheripheral has been run:














Known Insuffiecient :
   If a central device (moblie) access the Notify characteristic, the bluez peripheral would  be led to crash.
  After the disconneciton event occurs, the Bluez perihpheral would not start again, it is,you need to run the BleAdvertise script again.
 

2015年7月26日 星期日

CC2540/CC2541 : Set the Peripheral Being Advertising While It is Being Connected


 There is possible to set your CC254X be scanable when it is in connection. But, based on my test, the connecting action would be sluggish if you modify the code as below steps:

  0. Check your BLE stack being 1.3.2 , if that is not, I apologize. if it is,  Backup the file :  TiBLEStackInstallationFolder\BLE-CC254x-1.3.2\Projects\ble\Profiles\Roles\peripheralBroadcaster.c.

 1. Substitute the file peripheral.c as  peripheralBroadcaster.c in your IAR project. That is, enable PLUS_BROADCASTER macro in preprocessor define, exclude peripheral.c, and include peripheralBroadcaster.c in your project.


Open the peripheralBroadcaster.c, it should be modified.

2. for line 45 : add the line :

    #include "hci_tl.h"


:
#include "hci.h"
#include "hci_tl.h"
#include "l2cap.h"
: 
 
it is for passing compilation.

3. For line 641, change the line :


// Initialize the Profile Advertising and Connection Parameters
gapRole_profileRole = GAP_PROFILE_PERIPHERAL;

As :

#if(1)  //GAIGER 
  gapRole_profileRole = GAP_PROFILE_PERIPHERAL;
#else  
  gapRole_profileRole = (GAP_PROFILE_PERIPHERAL | GAP_PROFILE_BROADCASTER);
#endif 

The peripheral could advertise now.


3. At the line 927 :

// Since gapRole_AdvertOffTime is set to 0, the device should not
              // automatically become discoverable again after a period of time.
              // Set enabler to FALSE; device will become discoverable again when
              // this value gets set to TRUE              
              gapRole_AdvEnabled = FALSE;  


Modify it as :


              // Since gapRole_AdvertOffTime is set to 0, the device should not
              // automatically become discoverable again after a period of time.
              // Set enabler to FALSE; device will become discoverable again when
              // this value gets set to TRUE              
#if(0)    //GAIGER  
              gapRole_AdvEnabled = FALSE;  
#else    
              osal_start_timerEx( gapRole_TaskID, START_ADVERTISING_EVT, 1000);
#endif 


The device would keep advertising which it is connected.

4. For the line 937 :


  // Check whether update parameter request is enabled, and check the connection parameters
          if ( ( gapRole_ParamUpdateEnable == TRUE ) &&
               ( (pPkt->connInterval < gapRole_MinConnInterval) ||
                 (pPkt->connInterval > gapRole_MaxConnInterval) ||
                 (pPkt->connLatency != gapRole_SlaveLatency)    ||
                 (pPkt->connTimeout != gapRole_TimeoutMultiplier) ))
          {
            gapRole_SendUpdateParam( pPkt->connInterval, pPkt->connLatency );
          }
              
          // Notify the Bond Manager to the connection
          VOID GAPBondMgr_LinkEst( pPkt->devAddrType, pPkt->devAddr, pPkt->connectionHandle, GAP_PROFILE_PERIPHERAL ); 

There should be insert a line for resuming advertise after disconnection event occuring:


  // Check whether update parameter request is enabled, and check the connection parameters
          if ( ( gapRole_ParamUpdateEnable == TRUE ) &&
               ( (pPkt->connInterval < gapRole_MinConnInterval) ||
                 (pPkt->connInterval > gapRole_MaxConnInterval) ||
                 (pPkt->connLatency != gapRole_SlaveLatency)    ||
                 (pPkt->connTimeout != gapRole_TimeoutMultiplier) ))
          {
            gapRole_SendUpdateParam( pPkt->connInterval, pPkt->connLatency );
          }
          
#if(1)    //GAIGER
          VOID osal_set_event( gapRole_TaskID, START_ADVERTISING_EVT );
#endif          
          // Notify the Bond Manager to the connection
          VOID GAPBondMgr_LinkEst( pPkt->devAddrType, pPkt->devAddr, pPkt->connectionHandle, GAP_PROFILE_PERIPHERAL ); 


   It is all, now the goal has been reached. Your cc254x could keep advertising while it is on connection.

  I note again, that would entail your peripheral being slow on connecting: it costs 5~8 seconds to discover services by using BLE scaner on my Sony Xperia Z3 compact, but for pure peripheral, it costs 1 second only at worst case.

  This method refers to Texas  forum threads:

 https://e2e.ti.com/support/wireless_connectivity/f/538/t/197769

http://www.deyisupport.com/question_answer/wireless_connectivity/bluetooth/f/103/t/88540.aspx?keyMatch=gaiger&tisearch=Search-CN-Everything

2015年7月15日 星期三

Methods Collection of Enumerating Com Port in Windows, by C


     According to this stack overflow thread, PJ Naughter has implemented 9 methods to emunerate com port in Windows. That code was named EnumSerialPorts.


     As my using, I found some method would list non-existed com ports. for example:

My computer com port be :






 But the output of  PJ Naughte's EnumSerialPorts be :


CreateFile method reports
COM1
COM4
COM5
COM98
COM99
QueryDosDevice method reports
COM5
COM1
COM98
COM99
COM4
GetDefaultCommConfig method reports
COM1
COM4
COM5
COM98
COM99
Device Manager (SetupAPI - GUID_DEVINTERFACE_COMPORT) reports
COM1 <通訊連接埠>
COM4 <USB Serial Port>
Device Manager (SetupAPI - Ports Device information set) reports
COM99 <Bluetooth Serial Port>
COM4 <USB Serial Port>
COM1 <通訊連接埠>
COM98 <Bluetooth Serial Port>
COM5 <USB-SERIAL CH340>
EnumPorts method reports
COM1
COM2
COM4
COM7
COM8
COM10
COM6
COM5
COM98
COM99
COM9
COM11
COM12
COM13
COM14
COM16
COM17
COM18
COM19
COM20
COM21
COM22
COM23
COM24
COM25
COM26
COM27
COM28
COM29
COM30
COM31
COM32
COM3
WMI method reports
COM1 <通訊連接埠 (COM1)>
ComDB method reports
COM1
COM3
COM4
COM5
COM6
COM7
COM8
COM9
COM22
COM23
COM24
COM25
COM26
COM27
COM28
COM29
COM30
COM31
COM32
COM98
COM99
Registry method reports
COM98
COM99
COM5
COM1
COM4

    Otherwise, that code are too extravagant, those is hard to separate into an individual function: it is too much dependency on the other functions.

I has reorganized those functions being independent forms in C(instead of C++), those are more portable and useful. I also eliminated some methods which would report unused com ports.

My code be :


/*
 Enumerating com ports in Windows 

 original by PJ Naughter, 1998 - 2013 
 http://www.naughter.com/enumser.html  (Web: www.naughter.com, Email: pjna@naughter.com)

 reorganize by Gaiger Chen , Jul, 2015

 NO COPYRIGHT,  welcome to use for everyone.
*/

#include <windows.h>

#include <stdio.h>
#include <tchar.h>
#include <setupapi.h>
#include <locale.h>

#define MAX_PORT_NUM      (256)
#define MAX_STR_LEN       (256*sizeof(TCHAR))


/*assure portName be double ARRAY , not double point*/
BOOL EnumerateComPortByCreateFile(UINT *pNumber, TCHAR *pPortName, int strMaxLen)
{
 UINT i, jj;
 INT ret; 
 TCHAR *pTempPortName; 
 
 *pNumber = 0;
 jj = 0;
 pTempPortName = (TCHAR*)HeapAlloc(GetProcessHeap(),
    HEAP_GENERATE_EXCEPTIONS|HEAP_ZERO_MEMORY, 
    strMaxLen*sizeof(pTempPortName));

 ret = FALSE;

 for (i = 1; i<= 255; i++){    
  HANDLE hSerial;

  _stprintf_s(pTempPortName, strMaxLen, TEXT("\\\\.\\COM%u"), i);

  hSerial = CreateFile(pTempPortName, GENERIC_READ | GENERIC_WRITE, 
   0, 0, OPEN_EXISTING, 0, 0);

  if(INVALID_HANDLE_VALUE == hSerial)
   continue;

  _tcsncpy(pPortName + jj*strMaxLen, pTempPortName, 
   _tcsnlen(pTempPortName, strMaxLen));

  jj++;
 }/*for [i MAX_PORT_NUM] */

 HeapFree(GetProcessHeap(), 0, pTempPortName); pTempPortName = NULL;
 *pNumber = jj;

 if(0 <jj)
  ret = TRUE;

 return ret;
}/*EnumerateComPortByCreateFile*/


BOOL EnumerateComPortQueryDosDevice(UINT *pNumber, TCHAR *pPortName, int strMaxLen)
{
 UINT i, jj;
 INT ret;

 OSVERSIONINFOEX osvi;
 ULONGLONG dwlConditionMask;
 DWORD dwChars;
 
 TCHAR *pDevices;   
 UINT nChars;

 ret = FALSE;
 
 memset(&osvi, 0, sizeof(osvi));
 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
 osvi.dwPlatformId = VER_PLATFORM_WIN32_NT;
 dwlConditionMask = 0;

 VER_SET_CONDITION(dwlConditionMask, VER_PLATFORMID, VER_EQUAL);

 if(FALSE == VerifyVersionInfo(&osvi, VER_PLATFORMID, dwlConditionMask))
 {
   DWORD dwError = GetLastError();
  _tprintf(TEXT("VerifyVersionInfo error, %d\n", dwError));
  return -1;
 }/*if*/

   
 pDevices = NULL;

 nChars = 4096;   
 pDevices = (TCHAR*)HeapAlloc(GetProcessHeap(), 
  HEAP_GENERATE_EXCEPTIONS, nChars*sizeof(TCHAR));

 while(0 < nChars)
 {
  dwChars = QueryDosDevice(NULL, pDevices, nChars);

  if(0 == dwChars)
  {
   DWORD dwError = GetLastError();

   if(ERROR_INSUFFICIENT_BUFFER == dwError)
   {      
    nChars *= 2;    
    HeapFree(GetProcessHeap(), 0, pDevices);    
    pDevices = (TCHAR*)HeapAlloc(GetProcessHeap(), 
     HEAP_GENERATE_EXCEPTIONS, nChars*sizeof(TCHAR));

    continue;
   }/*if ERROR_INSUFFICIENT_BUFFER == dwError*/
   
   _tprintf(TEXT("QueryDosDevice error, %d\n", dwError));
   return -1;     
  }/*if */


  //printf("dwChars = %d\n", dwChars);
  i = 0;
  jj = 0;
  while (TEXT('\0') != pDevices[i] )
  {
   TCHAR* pszCurrentDevice;
   size_t nLen;
   pszCurrentDevice = &(pDevices[i]);
   nLen = _tcslen(pszCurrentDevice);

   //_tprintf(TEXT("%s\n"), &pTargetPathStr[i]);
   if (3 < nLen)
            {
              if ((0 == _tcsnicmp(pszCurrentDevice, TEXT("COM"), 3))
      && FALSE != isdigit(pszCurrentDevice[3]) )
              {
                //Work out the port number                
    _tcsncpy(pPortName + jj*strMaxLen, 
     pszCurrentDevice, strMaxLen);
    jj++; 
    
              }
            }

   i += (nLen + 1);
  }
  
  break;
 }/*while*/
 
 if(NULL != pDevices)
  HeapFree(GetProcessHeap(), 0, pDevices); 
 
  
 *pNumber = jj;

 if(0 < jj)
  ret = TRUE;

 return ret;
}/*EnumerateComPortByQueryDosDevice*/


BOOL EnumerateComPortByGetDefaultCommConfig(UINT *pNumber, TCHAR *pPortName, 
  int strMaxLen)
{
 UINT i, jj;
 INT ret;
 
 TCHAR *pTempPortName; 

 pTempPortName = (TCHAR*)HeapAlloc(GetProcessHeap(), 
  HEAP_GENERATE_EXCEPTIONS| HEAP_ZERO_MEMORY, strMaxLen);

 *pNumber = 0;
 jj = 0;
 ret = FALSE;

 for (i = 1; i<=255; i++){    

  //Form the Raw device name    
  COMMCONFIG cc;
  DWORD dwSize ;

  dwSize = sizeof(COMMCONFIG);

  _stprintf_s(pTempPortName, strMaxLen/2, TEXT("COM%u"), i);

  if (FALSE == GetDefaultCommConfig(pTempPortName, &cc, &dwSize))
    continue;
    
  _tcsncpy(pPortName + jj*strMaxLen, pTempPortName, 
   _tcsnlen(pTempPortName, strMaxLen));
  jj++;
 }/*for [1 255] */

 HeapFree(GetProcessHeap(), 0, pTempPortName);
  pTempPortName = NULL;
 
 *pNumber = jj;

 if(0 <jj)
  ret = TRUE;

 return ret;
}/*EnumerateComPortByGetDefaultCommConfig*/


BOOL EnumerateComPortSetupAPI_GUID_DEVINTERFACE_COMPORT(UINT *pNumber, 
   TCHAR *pPortName, int strMaxLen, TCHAR *pFriendName)
{ 
 UINT i, jj;
 INT ret;
 
 TCHAR *pTempPortName;
 HMODULE hLibrary;
 TCHAR szFullPath[_MAX_PATH];

 GUID guid;
 HDEVINFO hDevInfoSet;
  

 typedef HKEY (__stdcall SetupDiOpenDevRegKeyFunType)
  (HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM);
 
 //typedef BOOL (__stdcall SetupDiClassGuidsFromNameFunType)
 // (LPCTSTR, LPGUID, DWORD, PDWORD);

 typedef BOOL (__stdcall SetupDiDestroyDeviceInfoListFunType)
  (HDEVINFO);
 typedef BOOL (__stdcall SetupDiEnumDeviceInfoFunType)
  (HDEVINFO, DWORD, PSP_DEVINFO_DATA);

 typedef HDEVINFO (__stdcall SetupDiGetClassDevsFunType)
  (LPGUID, LPCTSTR, HWND, DWORD);

 typedef BOOL (__stdcall SetupDiGetDeviceRegistryPropertyFunType)
  (HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD);

 SetupDiOpenDevRegKeyFunType* SetupDiOpenDevRegKeyFunPtr;

 SetupDiGetClassDevsFunType *SetupDiGetClassDevsFunPtr;
 SetupDiGetDeviceRegistryPropertyFunType *SetupDiGetDeviceRegistryPropertyFunPtr;

 SetupDiDestroyDeviceInfoListFunType *SetupDiDestroyDeviceInfoListFunPtr;  
 SetupDiEnumDeviceInfoFunType *SetupDiEnumDeviceInfoFunPtr; 

 BOOL bMoreItems;
 SP_DEVINFO_DATA devInfo;

 ret = FALSE;
 jj = 0;
 szFullPath[0] = _T('\0'); 

 //Get the Windows System32 directory
 
 
 if(0 == GetSystemDirectory(szFullPath, _countof(szFullPath)))
 {
  _tprintf(TEXT("CEnumerateSerial::UsingSetupAPI1 failed, Error:%u\n"), 
   GetLastError());
  return FALSE;
 }/*if*/
 

 //Setup the full path and delegate to LoadLibrary    
#pragma warning(suppress: 6102) //There is a bug with the SAL annotation of GetSystemDirectory in the Windows 8.1 SDK
 _tcscat_s(szFullPath, _countof(szFullPath), _T("\\"));
 _tcscat_s(szFullPath, _countof(szFullPath), TEXT("SETUPAPI.DLL"));
 hLibrary = LoadLibrary(szFullPath);

 
 SetupDiOpenDevRegKeyFunPtr = 
  (SetupDiOpenDevRegKeyFunType*)GetProcAddress(hLibrary, "SetupDiOpenDevRegKey");
  
#if defined _UNICODE 
 SetupDiGetClassDevsFunPtr = 
  (SetupDiGetClassDevsFunType*)GetProcAddress(hLibrary, "SetupDiGetClassDevsW");
 SetupDiGetDeviceRegistryPropertyFunPtr = (SetupDiGetDeviceRegistryPropertyFunType*)
  GetProcAddress(hLibrary, "SetupDiGetDeviceRegistryPropertyW");
#else
 SetupDiGetClassDevsFunPtr = 
  (SetupDiGetClassDevsFunType*)GetProcAddress(hLibrary, "SetupDiGetClassDevsA");

 SetupDiGetDeviceRegistryPropertyFunPtr = (SetupDiGetDeviceRegistryPropertyFunType*)
  GetProcAddress(hLibrary, "SetupDiGetDeviceRegistryPropertyA");
#endif

  SetupDiDestroyDeviceInfoListFunPtr = (SetupDiDestroyDeviceInfoListFunType*)
  GetProcAddress(hLibrary, "SetupDiDestroyDeviceInfoList");

 SetupDiEnumDeviceInfoFunPtr = (SetupDiEnumDeviceInfoFunType*)
  GetProcAddress(hLibrary, "SetupDiEnumDeviceInfo");

 guid = GUID_DEVINTERFACE_COMPORT;

 hDevInfoSet = SetupDiGetClassDevsFunPtr(&guid, NULL, NULL, 
  DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);


 if (INVALID_HANDLE_VALUE == hDevInfoSet)
 {
  //Set the error to report
  _tprintf(TEXT("error lpfnSETUPDIGETCLASSDEVS, %d"), GetLastError());
  return FALSE;
 }/*if */
 

 //bMoreItems = TRUE;
 devInfo.cbSize = sizeof(SP_DEVINFO_DATA);
 i = 0;
 jj = 0;

 do
 {
  HKEY hDeviceKey;  
  BOOL isFound;

  isFound = FALSE;
  bMoreItems = SetupDiEnumDeviceInfoFunPtr(hDevInfoSet, i, &devInfo); 

  if(FALSE == bMoreItems)
   break;   

  i++;

  hDeviceKey = SetupDiOpenDevRegKeyFunPtr(hDevInfoSet, &devInfo, 
   DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
 
  if (INVALID_HANDLE_VALUE != hDeviceKey)
  {
   int nPort;    
   size_t nLen;
   LPTSTR pszPortName;

   nPort = 0;
   pszPortName = NULL;

   {    
    //First query for the size of the registry value 
    DWORD dwType;
    DWORD dwDataSize;
    LONG err;
    DWORD dwAllocatedSize;
    DWORD dwReturnedSize;
    dwType = 0; dwDataSize = 0; 

    err = RegQueryValueEx(hDeviceKey, TEXT("PortName"), NULL, 
     &dwType, NULL, &dwDataSize);

    if (ERROR_SUCCESS != err)    
     continue;    
    
    //Ensure the value is a string
    if (dwType != REG_SZ)    
     continue;
    
    //Allocate enough bytes for the return value
    dwAllocatedSize = dwDataSize + sizeof(TCHAR);

    /* +sizeof(TCHAR) is to allow us to NULL terminate 
     the data if it is not null terminated in the registry
    */

    pszPortName = (LPTSTR)LocalAlloc(LMEM_FIXED, dwAllocatedSize); 

    if (pszPortName == NULL)
     continue;

    //Recall RegQueryValueEx to return the data
    pszPortName[0] = _T('\0');
    dwReturnedSize = dwAllocatedSize;

    err = RegQueryValueEx(hDeviceKey, TEXT("PortName"), NULL, 
     &dwType, (LPBYTE)pszPortName, &dwReturnedSize);

    if (ERROR_SUCCESS != err)
    {
     LocalFree(pszPortName);
     pszPortName = NULL;     
     continue;
    }

    //Handle the case where the data just returned is the same size as the allocated size. This could occur where the data
    //has been updated in the registry with a non null terminator between the two calls to ReqQueryValueEx above. Rather than
    //return a potentially non-null terminated block of data, just fail the method call
    if (dwReturnedSize >= dwAllocatedSize)
     continue;        

    //NULL terminate the data if it was not returned NULL terminated because it is not stored null terminated in the registry
    if (pszPortName[dwReturnedSize/sizeof(TCHAR) - 1] != _T('\0'))
     pszPortName[dwReturnedSize/sizeof(TCHAR)] = _T('\0');
   }/*local varable*/

   //If it looks like "COMX" then
   //add it to the array which will be returned
   nLen = _tcslen(pszPortName);

   if (3 < nLen)
   {
     if (0 == _tcsnicmp(pszPortName, TEXT("COM"), 3))
     {
     if(FALSE == isdigit(pszPortName[3]) )
      continue;

    //Work out the port number
     _tcsncpy(pPortName + jj*strMaxLen, pszPortName, 
     _tcsnlen(pszPortName, strMaxLen));

    //_stprintf_s(&portName[jj][0], strMaxLen, TEXT("%s"), pszPortName);          
     } else
     {
      continue;
     }/*if 0 == _tcsnicmp(pszPortName, TEXT("COM"), 3)*/
   }/*if 3 < nLen*/

   LocalFree(pszPortName);    
   isFound = TRUE;   

   //Close the key now that we are finished with it
   RegCloseKey(hDeviceKey);
  }/*INVALID_HANDLE_VALUE != hDeviceKey*/

  if(FALSE == isFound)
   continue;

  //If the port was a serial port, then also try to get its friendly name  
  {
   TCHAR szFriendlyName[1024];     
   DWORD dwSize;
   DWORD dwType;
   szFriendlyName[0] = _T('\0');
   dwSize = sizeof(szFriendlyName);
   dwType = 0;

   if( (TRUE == SetupDiGetDeviceRegistryPropertyFunPtr(hDevInfoSet, &devInfo, 
       SPDRP_DEVICEDESC, &dwType, (PBYTE)(szFriendlyName), 
       dwSize, &dwSize) ) && (REG_SZ == dwType)        
   )
   {
    _tcsncpy(pFriendName + jj*strMaxLen, &szFriendlyName[0], 
     _tcsnlen(&szFriendlyName[0], strMaxLen));
   }
   else
   {
    _stprintf_s(pFriendName + jj*strMaxLen, strMaxLen, TEXT("")); 
   }/*if SetupDiGetDeviceRegistryPropertyFunPtr */    
  }/*local variable */

  jj++;
 }while(1);

 *pNumber = jj;
 if(0 <jj)
  ret = TRUE;

 return ret;
}/*EnumerateComPortSetupAPI_GUID_DEVINTERFACE_COMPORT*/


BOOL EnumerateComPortSetupAPISetupDiClassGuidsFromNamePort(UINT *pNumber, 
  TCHAR *pPortName, int strMaxLen, TCHAR *pFriendName)
{ 
 UINT i, jj;
 INT ret;
 
 TCHAR *pTempPortName;
 HMODULE hLibrary;
 TCHAR szFullPath[_MAX_PATH];

 GUID *pGuid;
 DWORD dwGuids;
 HDEVINFO hDevInfoSet;
  

 typedef HKEY (__stdcall SetupDiOpenDevRegKeyFunType)
  (HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM);
 
 typedef BOOL (__stdcall SetupDiClassGuidsFromNameFunType)
  (LPCTSTR, LPGUID, DWORD, PDWORD);

 typedef BOOL (__stdcall SetupDiDestroyDeviceInfoListFunType)
  (HDEVINFO);
 typedef BOOL (__stdcall SetupDiEnumDeviceInfoFunType)
  (HDEVINFO, DWORD, PSP_DEVINFO_DATA);

 typedef HDEVINFO (__stdcall SetupDiGetClassDevsFunType)
  (LPGUID, LPCTSTR, HWND, DWORD);

 typedef BOOL (__stdcall SetupDiGetDeviceRegistryPropertyFunType)
  (HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD);

 SetupDiOpenDevRegKeyFunType* SetupDiOpenDevRegKeyFunPtr;

 SetupDiClassGuidsFromNameFunType *SetupDiClassGuidsFromNameFunPtr;
 SetupDiGetClassDevsFunType *SetupDiGetClassDevsFunPtr;
 SetupDiGetDeviceRegistryPropertyFunType *SetupDiGetDeviceRegistryPropertyFunPtr; 

 SetupDiDestroyDeviceInfoListFunType *SetupDiDestroyDeviceInfoListFunPtr;  
 SetupDiEnumDeviceInfoFunType *SetupDiEnumDeviceInfoFunPtr; 

 BOOL bMoreItems;
 SP_DEVINFO_DATA devInfo;

 ret = FALSE;
 jj = 0;
 szFullPath[0] = _T('\0'); 

 //Get the Windows System32 directory
 
 
 if(0 == GetSystemDirectory(szFullPath, _countof(szFullPath)))
 {
  _tprintf(TEXT("CEnumerateSerial::UsingSetupAPI1 failed, Error:%u\n"), 
   GetLastError());
  return FALSE;
 }/*if*/
 

 //Setup the full path and delegate to LoadLibrary    
#pragma warning(suppress: 6102) //There is a bug with the SAL annotation of GetSystemDirectory in the Windows 8.1 SDK
 _tcscat_s(szFullPath, _countof(szFullPath), _T("\\"));
 _tcscat_s(szFullPath, _countof(szFullPath), TEXT("SETUPAPI.DLL"));
 hLibrary = LoadLibrary(szFullPath);

 
 SetupDiOpenDevRegKeyFunPtr = 
  (SetupDiOpenDevRegKeyFunType*)GetProcAddress(hLibrary, "SetupDiOpenDevRegKey");
  
#if defined _UNICODE 
 SetupDiClassGuidsFromNameFunPtr = (SetupDiClassGuidsFromNameFunType*)
  GetProcAddress(hLibrary, "SetupDiGetDeviceRegistryPropertyW");
 SetupDiGetClassDevsFunPtr = 
  (SetupDiGetClassDevsFunType*)GetProcAddress(hLibrary, "SetupDiGetClassDevsW");
 SetupDiGetDeviceRegistryPropertyFunPtr
  = (SetupDiGetDeviceRegistryPropertyFunType*)GetProcAddress(hLibrary, "SetupDiGetDeviceRegistryPropertyW");
#else
 SetupDiClassGuidsFromNameFunPtr = (SetupDiClassGuidsFromNameFunType*)
  GetProcAddress(hLibrary, "SetupDiClassGuidsFromNameA");
 SetupDiGetClassDevsFunPtr = (SetupDiGetClassDevsFunType*)
  GetProcAddress(hLibrary, "SetupDiGetClassDevsA");
 SetupDiGetDeviceRegistryPropertyFunPtr = (SetupDiGetDeviceRegistryPropertyFunType*)
  GetProcAddress(hLibrary, "SetupDiGetDeviceRegistryPropertyA");
#endif

  SetupDiDestroyDeviceInfoListFunPtr = (SetupDiDestroyDeviceInfoListFunType*)
  GetProcAddress(hLibrary, "SetupDiDestroyDeviceInfoList");

 SetupDiEnumDeviceInfoFunPtr = (SetupDiEnumDeviceInfoFunType*)
  GetProcAddress(hLibrary, "SetupDiEnumDeviceInfo");

 
 //First need to convert the name "Ports" to a GUID using SetupDiClassGuidsFromName
 dwGuids = 0;
 SetupDiClassGuidsFromNameFunPtr(TEXT("Ports"), NULL, 0, &dwGuids);

 if(0 == dwGuids)  
  return FALSE;
 
 //Allocate the needed memory
 pGuid = (GUID*)HeapAlloc(GetProcessHeap(), 
  HEAP_GENERATE_EXCEPTIONS, dwGuids * sizeof(GUID));
 
 if(NULL == pGuid) 
  return FALSE;
 

 //Call the function again
 
 if (FALSE == SetupDiClassGuidsFromNameFunPtr(TEXT("Ports"), 
   pGuid, dwGuids, &dwGuids))  
 {
  return FALSE;
 }/*if*/


 hDevInfoSet = SetupDiGetClassDevsFunPtr(pGuid, NULL, NULL, 
  DIGCF_PRESENT /*| DIGCF_DEVICEINTERFACE*/);


 if (INVALID_HANDLE_VALUE == hDevInfoSet)
 {
  //Set the error to report
  _tprintf(TEXT("error SetupDiGetClassDevsFunPtr, %d"), GetLastError());
  return FALSE;
 }/*if */
 

 //bMoreItems = TRUE;
 devInfo.cbSize = sizeof(SP_DEVINFO_DATA);
 i = 0;
 jj = 0;

 do
 {
  HKEY hDeviceKey;  
  BOOL isFound;

  isFound = FALSE;
  bMoreItems = SetupDiEnumDeviceInfoFunPtr(hDevInfoSet, i, &devInfo);  
  if(FALSE == bMoreItems)
   break;   

  i++;

  hDeviceKey = SetupDiOpenDevRegKeyFunPtr(hDevInfoSet, &devInfo, 
   DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
 
  if (INVALID_HANDLE_VALUE != hDeviceKey)
  {
   int nPort;    
   size_t nLen;
   LPTSTR pszPortName;

   nPort = 0;
   pszPortName = NULL;

   {    
    //First query for the size of the registry value 
    DWORD dwType;
    DWORD dwDataSize;
    LONG err;
    DWORD dwAllocatedSize;
    DWORD dwReturnedSize;
    dwType = 0; dwDataSize = 0; 

    err = RegQueryValueEx(hDeviceKey, TEXT("PortName"), NULL, 
     &dwType, NULL, &dwDataSize);

    if (ERROR_SUCCESS != err)    
     continue;    
    
    //Ensure the value is a string
    if (dwType != REG_SZ)    
     continue;
    
    //Allocate enough bytes for the return value
    dwAllocatedSize = dwDataSize + sizeof(TCHAR);

    /* +sizeof(TCHAR) is to allow us to NULL terminate 
     the data if it is not null terminated in the registry
    */

    pszPortName = (LPTSTR)LocalAlloc(LMEM_FIXED, dwAllocatedSize); 

    if (pszPortName == NULL)
     continue;

    //Recall RegQueryValueEx to return the data
    pszPortName[0] = TEXT('\0');
    dwReturnedSize = dwAllocatedSize;

    err = RegQueryValueEx(hDeviceKey, TEXT("PortName"), NULL, 
     &dwType, (LPBYTE)pszPortName, &dwReturnedSize);

    if (ERROR_SUCCESS != err)
    {
     LocalFree(pszPortName);
     pszPortName = NULL;     
     continue;
    }

    //Handle the case where the data just returned is the same size as the allocated size. This could occur where the data
    //has been updated in the registry with a non null terminator between the two calls to ReqQueryValueEx above. Rather than
    //return a potentially non-null terminated block of data, just fail the method call
    if (dwReturnedSize >= dwAllocatedSize)
     continue;        

    //NULL terminate the data if it was not returned NULL terminated because it is not stored null terminated in the registry
    if (pszPortName[dwReturnedSize/sizeof(TCHAR) - 1] != _T('\0'))
     pszPortName[dwReturnedSize/sizeof(TCHAR)] = _T('\0');
   }/*local varable*/

   //If it looks like "COMX" then
   //add it to the array which will be returned
   nLen = _tcslen(pszPortName);

   if (3 < nLen)
   {
     if (0 == _tcsnicmp(pszPortName, TEXT("COM"), 3))
     {
     if(FALSE == isdigit(pszPortName[3]) )
      continue;

    //Work out the port number
     _tcsncpy(pPortName + jj*strMaxLen, pszPortName, 
     _tcsnlen(pszPortName, strMaxLen));

    //_stprintf_s(&portName[jj][0], strMaxLen, TEXT("%s"), pszPortName);          
     }
     else
     {
      continue;
     }/*if 0 == _tcsnicmp(pszPortName, TEXT("COM"), 3)*/

   }/*if 3 < nLen*/

   LocalFree(pszPortName);    
   isFound = TRUE;  

   //Close the key now that we are finished with it
   RegCloseKey(hDeviceKey);
  }/*INVALID_HANDLE_VALUE != hDeviceKey*/

  if(FALSE == isFound)
   continue;

  //If the port was a serial port, then also try to get its friendly name  
  {
   TCHAR szFriendlyName[1024];     
   DWORD dwSize;
   DWORD dwType;
   szFriendlyName[0] = _T('\0');
   dwSize = sizeof(szFriendlyName);
   dwType = 0;

   if( (TRUE == SetupDiGetDeviceRegistryPropertyFunPtr(hDevInfoSet, &devInfo, 
       SPDRP_DEVICEDESC, &dwType, (PBYTE)(szFriendlyName), 
       dwSize, &dwSize) ) && (REG_SZ == dwType)        
   )
   {
    _tcsncpy(pFriendName + jj*strMaxLen, &szFriendlyName[0], 
     _tcsnlen(&szFriendlyName[0], strMaxLen));
   }
   else
   {
    _stprintf_s(pFriendName + jj*strMaxLen, strMaxLen, TEXT("")); 
   }/*if SetupDiGetDeviceRegistryPropertyFunPtr */    
  }/*local variable */

  jj++;
 }while(1);

 HeapFree(GetProcessHeap(), 0, pGuid);

 *pNumber = jj;

 if(0 <jj)
  ret = TRUE;

 return ret;
}/*EnumerateComPortSetupAPISetupDiClassGuidsFromNamePort*/


BOOL EnumerateComPortRegistry(UINT *pNumber, TCHAR *pPortName, int strMaxLen)
{
  //What will be the return value from this function (assume the worst) 
 UINT jj;
 BOOL ret;

 HKEY hSERIALCOMM;
 ret = FALSE;

 if ( ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
  TEXT("HARDWARE\\DEVICEMAP\\SERIALCOMM"), 0, KEY_QUERY_VALUE, &hSERIALCOMM) 
  )
 {
    //Get the max value name and max value lengths
  DWORD dwMaxValueNameLen;
  DWORD dwMaxValueLen;
  DWORD dwQueryInfo;

  dwQueryInfo = RegQueryInfoKey(hSERIALCOMM, NULL, NULL,
  NULL, NULL, NULL, NULL, NULL, 
  &dwMaxValueNameLen, &dwMaxValueLen, NULL, NULL);

  if(ERROR_SUCCESS == dwQueryInfo)
  {
   DWORD dwMaxValueNameSizeInChars, dwMaxValueNameSizeInBytes,
    dwMaxValueDataSizeInChars, dwMaxValueDataSizeInBytes;

   DWORD *pValueName;
   DWORD *pValueData;

   dwMaxValueNameSizeInChars = dwMaxValueNameLen + 1; //Include space for the NULL terminator
   dwMaxValueNameSizeInBytes = dwMaxValueNameSizeInChars * sizeof(TCHAR);
   dwMaxValueDataSizeInChars = dwMaxValueLen/sizeof(TCHAR) + 1; //Include space for the NULL terminator
   dwMaxValueDataSizeInBytes = dwMaxValueDataSizeInChars * sizeof(TCHAR);
    
   //Allocate some space for the value name and value data      

   pValueName = (GUID*)HeapAlloc(GetProcessHeap(), 
    HEAP_GENERATE_EXCEPTIONS| HEAP_ZERO_MEMORY, dwMaxValueNameSizeInBytes);
   pValueData = (GUID*)HeapAlloc(GetProcessHeap(), 
    HEAP_GENERATE_EXCEPTIONS| HEAP_ZERO_MEMORY, dwMaxValueDataSizeInBytes);

   if(NULL != pValueName && NULL != pValueData)
   {    
    //Enumerate all the values underneath HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM
    DWORD i;
    DWORD dwType;
    DWORD dwValueNameSize;
    DWORD dwDataSize;
    LONG nEnum;

    
    dwValueNameSize = dwMaxValueNameSizeInChars;
    dwDataSize = dwMaxValueDataSizeInBytes;

    i = 0; 

    nEnum = RegEnumValue(hSERIALCOMM, i, 
     pValueName, &dwValueNameSize, NULL, &dwType,
     pValueData, &dwDataSize);

    jj = 0;
    while (ERROR_SUCCESS == nEnum)
    {
      //If the value is of the correct type, then add it to the array
     if (REG_SZ == dwType)
     {     
      _stprintf_s(pPortName + jj*strMaxLen, 
       strMaxLen, TEXT("%s"), pValueData);
      jj++;      
     }/*if */

     //Prepare for the next time around
     dwValueNameSize = dwMaxValueNameSizeInChars;
     dwDataSize = dwMaxValueDataSizeInBytes;
     ZeroMemory(pValueName, dwMaxValueNameSizeInBytes);
     ZeroMemory(pValueData, dwMaxValueDataSizeInBytes);
     i++;
     nEnum = RegEnumValue(hSERIALCOMM, i, pValueName, 
       &dwValueNameSize, NULL, &dwType, pValueData, &dwDataSize);
    }/*while*/
   }
   else
   {
    return FALSE;
   }/*if NULL != pValueName && NULL != pValueData*/

   HeapFree(GetProcessHeap(), 0, pValueName);
   HeapFree(GetProcessHeap(), 0, pValueData);
  }/*ERROR_SUCCESS == dwQueryInfo*/
    
  //Close the registry key now that we are finished with it    
  RegCloseKey(hSERIALCOMM);
     
  if (dwQueryInfo != ERROR_SUCCESS)
    return FALSE;
 }/*ERROR_SUCCESS == RegOpenKeyEx*/
  
 *pNumber = jj;

 if(0 <jj)
  ret = TRUE;

 return ret;
}/*EnumerateComPortRegistry*/



#define TIMER_BEGIN(TIMER_LABEL) \
   { unsigned int tBegin##TIMER_LABEL, tEnd##TIMER_LABEL; \
   tBegin##TIMER_LABEL = GetTime();
          
#define TIMER_END(TIMER_LABEL)  \
   tEnd##TIMER_LABEL = GetTime();\
   fprintf(stderr, "%s cost time = %d ms\n", \
   #TIMER_LABEL, tEnd##TIMER_LABEL - tBegin##TIMER_LABEL); \
   }

unsigned int GetTime(void)
{
 /*winmm.lib*/
 return ( unsigned int)timeGetTime();
}/*GetTime*/

int main(int argc, TCHAR argv[])
{ 
 TCHAR portName[MAX_PORT_NUM][MAX_STR_LEN];
 TCHAR friendlyName[MAX_PORT_NUM][MAX_STR_LEN];
 UINT i;
 UINT n;

 char* nativeLocale;

 nativeLocale = _strdup( setlocale(LC_CTYPE,NULL) );

 

 for(i = 0; i< MAX_PORT_NUM; i++)
  ZeroMemory(&portName[i][0], MAX_STR_LEN);

 _tprintf(TEXT("\nCreateFile method : \n"));
TIMER_BEGIN(EnumerateComPortByCreateFile);
 EnumerateComPortByCreateFile(&n, &portName[0][0], MAX_STR_LEN);
TIMER_END(EnumerateComPortByCreateFile);
 _tprintf(TEXT("sought %d:\n"), n);
 for(i = 0; i< n; i++) 
  _tprintf(TEXT("\t%s\n"), &portName[i][0]);
 



 for(i = 0; i< MAX_PORT_NUM; i++)
  ZeroMemory(&portName[0][0], MAX_STR_LEN);

 _tprintf(TEXT("\nQueryDosDevice method : ")); 
TIMER_BEGIN(EnumerateComPortQueryDosDevice); 
 EnumerateComPortQueryDosDevice(&n, &portName[0][0], MAX_STR_LEN);
TIMER_END(EnumerateComPortQueryDosDevice);
 _tprintf(TEXT("sought %d:\n"), n);

 for(i = 0; i< n; i++)
  _tprintf("\t%s\n", &portName[i][0]);
 


 

 for(i = 0; i< MAX_PORT_NUM; i++)
  ZeroMemory(&portName[i][0], MAX_STR_LEN);

 _tprintf(TEXT("\nGetDefaultCommConfig method : \n"));
TIMER_BEGIN(EnumerateComPortByGetDefaultCommConfig);
 EnumerateComPortByGetDefaultCommConfig(&n, &portName[0][0], MAX_STR_LEN);
TIMER_END(EnumerateComPortByGetDefaultCommConfig);
 _tprintf(TEXT("sought %d:\n"), n);

 for(i = 0; i< n; i++) 
  _tprintf(TEXT("\t%s\n"), &portName[i][0]);
 



 for(i = 0; i< MAX_PORT_NUM; i++){
  ZeroMemory(&portName[i][0], MAX_STR_LEN);
  ZeroMemory(&friendlyName[i][0], MAX_STR_LEN);
 }/*for i[i MAX_PORT_NUM]*/

 _tprintf(TEXT("\nSetupAPI GUID_DEVINTERFACE_COMPORT method : \n")); 

TIMER_BEGIN(EnumerateComPortSetupAPI_GUID_DEVINTERFACE_COMPORT);
 EnumerateComPortSetupAPI_GUID_DEVINTERFACE_COMPORT(&n, &portName[0][0], 
  MAX_STR_LEN, &friendlyName[0][0]);
TIMER_END(EnumerateComPortSetupAPI_GUID_DEVINTERFACE_COMPORT);

 _tprintf(TEXT("sought %d:\n"), n);
 
 setlocale(LC_CTYPE, "" );   
 for(i = 0; i< n; i++) 
  _tprintf(TEXT("\t%s <%s> \n"), &portName[i][0],  &friendlyName[i][0]);
 setlocale(LC_CTYPE, nativeLocale);
 



 for(i = 0; i< MAX_PORT_NUM; i++){
  ZeroMemory(&portName[i][0], MAX_STR_LEN);
  ZeroMemory(&friendlyName[i][0], MAX_STR_LEN);
 }/*for i[i MAX_PORT_NUM]*/

 _tprintf(TEXT("\nSetupAPI SetupDiClassGuidsFromNamePort method : \n"));
 
TIMER_BEGIN(EnumerateComPortSetupAPISetupDiClassGuidsFromNamePort);
 EnumerateComPortSetupAPISetupDiClassGuidsFromNamePort(&n, &portName[0][0], 
  MAX_STR_LEN, &friendlyName[0][0]);
TIMER_END(EnumerateComPortSetupAPISetupDiClassGuidsFromNamePort);

 _tprintf(TEXT("sought %d:\n"), n);
 
 setlocale(LC_CTYPE, "" );   
 for(i = 0; i< n; i++) 
  _tprintf(TEXT("\t%s <%s> \n"), &portName[i][0],  &friendlyName[i][0]);
 setlocale(LC_CTYPE, nativeLocale);


 

 for(i = 0; i< MAX_PORT_NUM; i++)
  ZeroMemory(&portName[i][0], MAX_STR_LEN);

 _tprintf(TEXT("\nRegistry method : \n"));

TIMER_BEGIN(EnumerateComPortRegistry);
 EnumerateComPortRegistry(&n, &portName[0][0], MAX_STR_LEN);
TIMER_END(EnumerateComPortRegistry);
 _tprintf(TEXT("sought %d:\n"), n);
 for(i = 0; i< n; i++) 
  _tprintf(TEXT("\t%s\n"), &portName[i][0]);


 return 0; 
}/*main*/



The output be :


CreateFile method :
EnumerateComPortByCreateFile cost time = 35 ms
sought 5:
        \\.\COM1
        \\.\COM4
        \\.\COM5
        \\.\COM98
        \\.\COM99

QueryDosDevice method : EnumerateComPortQueryDosDevice cost time = 7 ms
sought 5:
        COM5
        COM1
        COM98
        COM99
        COM4

GetDefaultCommConfig method :
EnumerateComPortByGetDefaultCommConfig cost time = 772 ms
sought 5:
        COM1
        COM4
        COM5
        COM98
        COM99

SetupAPI GUID_DEVINTERFACE_COMPORT method :
EnumerateComPortSetupAPI_GUID_DEVINTERFACE_COMPORT cost time = 12 ms
sought 2:
        COM1 <通訊連接埠>
        COM4 <USB Serial Port>

SetupAPI SetupDiClassGuidsFromNamePort method :
EnumerateComPortSetupAPISetupDiClassGuidsFromNamePort cost time = 10 ms
sought 5:
        COM99 <Bluetooth Serial Port>
        COM4 <USB Serial Port>
        COM1 <通訊連接埠>
        COM98 <Bluetooth Serial Port>
        COM5 <USB-SERIAL CH340>

Registry method :
EnumerateComPortRegistry cost time = 0 ms
sought 5:
        COM98
        COM99
        COM5
        COM1
        COM4


      Note that: EnumerateComPortSetupAPI_GUID_DEVINTERFACE_COMPORT
would not list CH340 port, which is on an Arduino UNO board. (But the same function could list other CH340 port, if it is not on this Arduino). Besides,  this function could not list Bluetooth virtual com ports.
     
     If you would like to use my code, I suggest you use function

BOOL EnumerateComPortSetupAPISetupDiClassGuidsFromNamePort(UINT *pNumber, 
  TCHAR *pPortName, int strMaxLen, TCHAR *pFriendName)

     That is the best one which balances speed and detail; but this one are most complicated in implementation. Otherwise, if you want to implement a function which could list com ports as simple as possible, I recommend use the CreateFile  method: that is rudimentary, but sufficient.