delphi serial-port enumerate
JVCL340CompleteJCL221-Build3845

Enumeración del puerto serie en Delphi usando SetupDiGetClassDevs



serial-port enumerate (5)

Estoy tratando de enumerar "nombres descriptivos" para los puertos COM. Los puertos pueden cambiar dinámicamente a medida que los dispositivos serie USB se conectan y desconectan en tiempo de ejecución.

En función de los posibles métodos descritos en esta pregunta , estoy intentando usar el método SetupDiGetClassDevs.

Encontré este código de ejemplo , pero está escrito para una versión anterior de la unidad setupapi (el enlace original a homepages.borland.com no funciona, por supuesto).

Intenté usar la unidad setupapi de la JVCL actual ( JVCL340CompleteJCL221-Build3845 ), pero no parece ser compatible con Delphi 7. Obtengo errores de compilación:

if SetupDiGetDeviceRegistryProperty(DevInfoHandle,DeviceInfoData, RegProperty, @PropertyRegDataType, @S1[1],RequiredSize,@RequiredSize) then begin

En la llamada a la función SetupDiGetDeviceRegistryProperty , aparece el error "Los tipos de parámetros reales y formales deben ser idénticos" en los parámetros @PropertyRegDataType y @RequiredSize .

El sitio Delphi3000 dice que el código fue escrito en 2004 y está destinado a Delphi 7, por lo que no estoy seguro de por qué no funciona con Delphi 7 ahora, a menos que setupapi haya cambiado. ¿Alguien está familiarizado con los cambios en setupapi que podrían causar estos problemas?

Estoy probando con un programa de consola simple. La instrucción uses es "windows, sysutils, classes, setupAPI, Registry;"

El programa principal es:

begin ComPortStringList := SetupEnumAvailableComPorts; for Index := 0 to ComPortStringList.Count - 1 do writeln(ComPortStringList[Index]); end; end.


¿Tiene "tipeado @ operador" encendido? Opciones de proyecto, pestaña Compilador en "Opciones de sintaxis". Una gran cantidad de códigos de terceros se rompe si esa opción está habilitada.


El siguiente procedimiento funciona correctamente para mí (en Windows 8.1). Es importante utilizar el parámetro KEY_READ en TRegistry.Constructor .

procedure EnumComPorts(const Ports: TStringList); var nInd: Integer; begin { EnumComPorts } with TRegistry.Create(KEY_READ) do try RootKey := HKEY_LOCAL_MACHINE; if OpenKey(''hardware/devicemap/serialcomm'', False) then try Ports.BeginUpdate(); try GetValueNames(Ports); for nInd := Ports.Count - 1 downto 0 do Ports.Strings[nInd] := ReadString(Ports.Strings[nInd]); Ports.Sort() finally Ports.EndUpdate() end { try-finally } finally CloseKey() end { try-finally } else Ports.Clear() finally Free() end { try-finally } end { EnumComPorts };


Para una operación más fácil, puede considerar simplemente usar el registro donde se enumeran esos nombres, por ejemplo:

ErrCode := RegOpenKeyEx( HKEY_LOCAL_MACHINE, ''HARDWARE/DEVICEMAP/SERIALCOMM'', 0, KEY_READ, KeyHandle);

(He omitido las cosas que agitan la mano).

También podría considerar usar WMI, vea este ejemplo de Magenta Systems, ahora puede obtener casi todo lo relacionado con el hardware.


Parece que algunos argumentos de tipo PDWord fueron reemplazados por var DWord en SetupApi.pas . Todo lo que necesita es eliminar ''@'' de estos argumentos en su código así:

if SetupDiGetDeviceRegistryProperty(DevInfoHandle,DeviceInfoData, RegProperty, PropertyRegDataType, @S1[1],RequiredSize,RequiredSize) then begin


Pude obtener algunas sugerencias más específicas al hacer la pregunta de otra manera con diferentes etiquetas .

Resulta que hubo errores en el código de ejemplo delphi3000.com y posiblemente errores en el código JVCL. Después de corregir los errores del código de ejemplo, lo hice funcionar. No he abordado los posibles errores de JVCL.

Aquí está el código de trabajo (como una aplicación de consola simple) para enumerar los nombres de los puertos de comunicación:

{$APPTYPE CONSOLE} program EnumComPortsTest; uses windows, sysutils, classes, setupAPI, Registry; {$R *.RES} var ComPortStringList : TStringList; (* The function below returns a list of available COM-ports (not open by this or an other process), with friendly names. The list is formatted as follows: COM1: = Communications Port (COM1) COM5: = NI Serial Port (Com5) COM6: = NI Serial Port (Com6) COM7: = USB Serial Port (COM7) COM8: = Bluetooth Communications Port (COM8) COM9: = Bluetooth Communications Port (COM9) This code originally posted at http://www.delphi3000.com/articles/article_4001.asp?SK= errors have been fixed so it will work with Delphi 7 and SetupAPI from JVCL *) function SetupEnumAvailableComPorts:TstringList; // Enumerates all serial communications ports that are available and ready to // be used. // For the setupapi unit see // http://homepages.borland.com/jedi/cms/modules/apilib/visit.php?cid=4&lid=3 var RequiredSize: Cardinal; GUIDSize: DWORD; Guid: TGUID; DevInfoHandle: HDEVINFO; DeviceInfoData: TSPDevInfoData; MemberIndex: Cardinal; PropertyRegDataType: DWord; RegProperty: Cardinal; RegTyp: Cardinal; Key: Hkey; Info: TRegKeyInfo; S1,S2: string; hc: THandle; begin Result:=Nil; //If we cannot access the setupapi.dll then we return a nil pointer. if not LoadsetupAPI then exit; try // get ''Ports'' class guid from name GUIDSize := 1; // missing from original code - need to tell function that the Guid structure contains a single GUID if SetupDiClassGuidsFromName(''Ports'',@Guid,GUIDSize,RequiredSize) then begin //get object handle of ''Ports'' class to interate all devices DevInfoHandle:=SetupDiGetClassDevs(@Guid,Nil,0,DIGCF_PRESENT); if Cardinal(DevInfoHandle)<>Invalid_Handle_Value then begin try MemberIndex:=0; result:=TStringList.Create; //iterate device list repeat FillChar(DeviceInfoData,SizeOf(DeviceInfoData),0); DeviceInfoData.cbSize:=SizeOf(DeviceInfoData); //get device info that corresponds to the next memberindex if Not SetupDiEnumDeviceInfo(DevInfoHandle,MemberIndex,DeviceInfoData) then break; //query friendly device name LIKE ''BlueTooth Communication Port (COM8)'' etc RegProperty:=SPDRP_FriendlyName;{SPDRP_Driver, SPDRP_SERVICE, SPDRP_ENUMERATOR_NAME,SPDRP_PHYSICAL_DEVICE_OBJECT_NAME,SPDRP_FRIENDLYNAME,} SetupDiGetDeviceRegistryProperty(DevInfoHandle, DeviceInfoData, RegProperty, PropertyRegDataType, NIL,0,RequiredSize); SetLength(S1,RequiredSize); if SetupDiGetDeviceRegistryProperty(DevInfoHandle,DeviceInfoData, RegProperty, PropertyRegDataType, @S1[1],RequiredSize,RequiredSize) then begin KEY:=SetupDiOpenDevRegKey(DevInfoHandle,DeviceInfoData,DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ); if key<>INValid_Handle_Value then begin FillChar(Info, SizeOf(Info), 0); //query the real port name from the registry value ''PortName'' if RegQueryInfoKey(Key, nil, nil, nil, @Info.NumSubKeys,@Info.MaxSubKeyLen, nil, @Info.NumValues, @Info.MaxValueLen, @Info.MaxDataLen, nil, @Info.FileTime) = ERROR_SUCCESS then begin RequiredSize:= Info.MaxValueLen + 1; SetLength(S2,RequiredSize); if RegQueryValueEx(KEY,''PortName'',Nil,@Regtyp,@s2[1],@RequiredSize)=Error_Success then begin If (Pos(''COM'',S2)=1) then begin //Test if the device can be used hc:=CreateFile(pchar(''//./'+S2+#0), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if hc<> INVALID_HANDLE_VALUE then begin Result.Add(Strpas(PChar(S2))+'': = ''+StrPas(PChar(S1))); CloseHandle(hc); end; end; end; end; RegCloseKey(key); end; end; Inc(MemberIndex); until False; //If we did not found any free com. port we return a NIL pointer. if Result.Count=0 then begin Result.Free; Result:=NIL; end finally SetupDiDestroyDeviceInfoList(DevInfoHandle); end; end; end; finally UnloadSetupApi; end; end; var index : integer; begin ComPortStringList := SetupEnumAvailableComPorts; if (ComPortStringList <> nil) and (ComPortStringList.Count > 0) then for Index := 0 to ComPortStringList.Count - 1 do writeln(ComPortStringList[Index]); end.