Serial port enumeration in Delphi using SetupDiGetClassDevs

前端 未结 5 760
情书的邮戳
情书的邮戳 2021-01-01 04:32

I\'m trying to enumerate \"friendly names\" for COM ports. The ports may dynamically change as USB-serial devices are connected and disconnected at runtime.

Based on

相关标签:
5条回答
  • 2021-01-01 04:50

    Looks like some arguments of type PDWord were replaced by var DWord in SetupApi.pas. All you need is to remove '@' from these arguments in your code like that:

    if SetupDiGetDeviceRegistryProperty(DevInfoHandle,DeviceInfoData,
                                        RegProperty,
                                        PropertyRegDataType,
                                        @S1[1],RequiredSize,RequiredSize) then begin
    
    0 讨论(0)
  • 2021-01-01 04:51

    The following procedure is working correctly for me (in Windows 8.1). It is important to use the parameter KEY_READ in the 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 };
    
    0 讨论(0)
  • 2021-01-01 04:54

    Do you have "typed @ operator" turned on? Project options, Compiler tab under "Syntax options". A lot of third party code breaks if that option is enabled.

    0 讨论(0)
  • 2021-01-01 05:06

    I was able to get some more specific suggestions by asking the question a different way with different tags.

    It turns out there were errors in the delphi3000.com example code, and possibly errors in the JVCL code. After fixing the example code errors, I got it to work. I have not addressed the potential JVCL errors.

    Here is the working code (as a simple console app) for enumerating the names of com ports:

    {$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.
    
    0 讨论(0)
  • 2021-01-01 05:08

    For easier operation you might consider simply using the registry where those names are listed eg:

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

    (I've ommited the hand-waving stuff).

    You might also consider using WMI - see this example from Magenta Systems - you can get a pretty much everything hardware-related now.

    0 讨论(0)
提交回复
热议问题