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.