Delphi: Работа с устройствами в Windows

ОГЛАВЛЕНИЕ

Получение списка устройств

Первая задача, с которой мы столкнёмся это получение списка устройств. Устройства в системе подразделяются на классы, например: класс видеоустройств, принтеров, модемы, клавиатуры и т.д. Любое устройство должно принадлежать как-нибудь классу. Каждый класс идентифицируется своим GUID’ом (глобальный уникальный идентификатор). GUID это 128 битная запись типа: {C06136A2-43EA-4F43-AF06-7413D07E28B7}. Для получения полного списка устройств сначала надо получить список классов. Для получения списка классов используется функция CM_Enumerate_Classes:
CMAPI CONFIGRET WINAPI
  CM_Enumerate_Classes(
 IN ULONG ulClassIndex,// индекс класса
 OUT LPGUID ClassGuid,// указатель GUID класса
 IN ULONG ulFlags //не используется
 );
Для перечисления всех классов мы должны в цикле вызывать функцию, начиная с индекса 0. Если функция вернула значение CR_NO_SUCH_VALUE, значит, мы пришли к концу списка. Вторым параметром должен быть указатель на переменную TGUID, в которую будет сохранён GUID класса. Получение информации о классе осуществляет функция SetupDiGetClassDescription:
WINSETUPAPI BOOL WINAPI
  SetupDiGetClassDescription(
 IN LPGUID ClassGuid,//GUID класса
 OUT PTSTR ClassDescription,//строка
 IN DWORD ClassDescriptionSize,//размер строки
 OUT PDWORD RequiredSize OPTIONAL//требуемый размер
 );

Вторым параметром должен идти указатель на буфер, в который будет сохранена строка с именем класса. Третьим параметром должен идти размер передаваемого буфера. Если указанного буфера не хватит, то требуемый размер будет сохранён в переменной указатель, на которую мы передадим четвёртым параметром.

Список классов мы получили. Теперь нам надо получить список устройств, принадлежащих некоторому классу. Здесь к нам придёт на помощь функция SetupDiGetClassDevs:

HDEVINFO
  SetupDiGetClassDevs(
 IN LPGUID ClassGuid, OPTIONAL
 IN PCTSTR Enumerator, OPTIONAL
 IN HWND hwndParent, OPTIONAL
 IN DWORD Flags
 );
У этой функции почти все параметры опциональны за исключением последнего. Первый параметр задаёт класс устройств для перечисления. Если этот параметр равен нулю, то перечисляться будут все устройства в системе. Второй и третий параметры (соответственно, имя PnP перечислителя и хендл формы) могут быть равны нулю. Последний параметр самый важный. Он может принимать одно из следующий значений или их комбинацию:
  • DIGCF_ALLCLASSES
    Будет возвращён список всех устройств и всех классов, установленных в данный момент в системе. Первый параметр будет проигнорирован.
  • DIGCF_DEVICEINTERFACE
    Возврат списка устройств, которые поддерживают интерфейсы.
  • DIGCF_DEFAULT
    Возврат списка устройств, которые ассоциируются с системой по умолчанию.
  • DIGCF_PRESENT
    Будет возвращён список устройств, которые в настоящее время присутствуют в системе.
  • DIGCF_PROFILE
    Будет возвращён список устройств, которые являются частью текущего аппаратного профиля.

В нашем случае надо указать только класс устройств и указать последним параметром флаг DIGCF_PRESENT. При успешном вызове функция возвращает хендл полученного списка.

Итак, у нас есть список (вернее его хендл) и нам надо как-то перечислить все устройства находящиеся в нём. На помощь к нам придёт функция под названием SetupDiEnumDeviceInfo:

WINSETUPAPI BOOL WINAPI
  SetupDiEnumDeviceInfo(
 IN HDEVINFO DeviceInfoSet,
 IN DWORD MemberIndex,
 OUT PSP_DEVINFO_DATA DeviceInfoData
 );
С первых параметром, я думаю, всё ясно. Второй парметр задаёт индекс в списке. Третий параметр это указатель на структуру SP_DEVINFO_DATA, в которой будет сохранена информация об устройстве. Если функция вернула значение TRUE, то информация извлечена успешно, а если FALSE, то в большинстве случаев это означает что мы пришли к концу списка. Для перечисления всего списка нам надо будет в цикле вызывать функцию SetupDiEnumDeviceInfo каждый раз увеличивая значение индекса на единицу до тех пор пока не получим отрицательный результат.
Итак, у нас есть структура, в которой хранится информация об устройстве:
typedef struct _SP_DEVINFO_DATA {
  DWORD cbSize;
  GUID ClassGuid;
  DWORD DevInst;
  ULONG_PTR Reserved;
} SP_DEVINFO_DATA, *PSP_DEVINFO_DATA;
По сути, главным полем здесь является поле DevInst, которая и хранит хендл устройства. Для того чтобы получить имя устройства (или его описание) нам надо использовать функцию SetupDiGetDeviceRegistryProperty. Далее её описание
WINSETUPAPI BOOL WINAPI
  SetupDiGetDeviceRegistryProperty(
 IN HDEVINFO DeviceInfoSet,
 IN PSP_DEVINFO_DATA DeviceInfoData,
 IN DWORD Property,
 OUT PDWORD PropertyRegDataType, OPTIONAL
 OUT PBYTE PropertyBuffer,
 IN DWORD PropertyBufferSize,
 OUT PDWORD RequiredSize OPTIONAL
 );

Второй параметр это указатель на структуру SP_DEVINFO_DATA. Третий параметр задаёт тип информации, которую мы хотим получить. Для нас важны два флага: SPDRP_FRIENDLYNAME и SPDRP_DEVICEDESC. Далее идёт опциональный параметр который задаёт указатель на переменную в которой будет сохранён тип данных ключа реестра, из которого была извлечена информация. Далее идёт ещё три параметра которые задают соответственно указатель на буфер для сохранения информации, размер буфера и размер реально скопированных данных в ненр. Если мы будем использовать флаг SPDRP_FRIENDLYNAME, то получим вместо модели жёсткого диска «дисковый накопитель», а при использовании флага SPDRP_DEVICEDESC мы получим модель жёсткого диска. Не всегда информация для обоих параметров представлена, иногда есть только для SPDRP_FRIENDLYNAME, а иногда есть только для SPDRP_DEVICEDESC. Если при использовании первого флага мы получили пустую строку, то надо получить информацию с использованием второго флага.

Следующая функция получает имя устройства по хендлу перечисления и структуре SP_DEVINFO_DATA.

function GetDeviceName(PnPHandle: HDEVINFO; const DevData: TSPDevInfoData): string;
var
  BytesReturned: DWORD;
  RegDataType: DWORD;
  Buffer: array [0..256] of CHAR;
begin
  BytesReturned := 0;
  RegDataType := 0;
  Buffer[0] := #0;
  SetupDiGetDeviceRegistryProperty(PnPHandle, DevData, SPDRP_FRIENDLYNAME,
RegDataType, PByte(@Buffer[0]), SizeOf(Buffer), BytesReturned);
  Result := Buffer;
  if Result<>” then exit;
  BytesReturned := 0;
  RegDataType := 0;
  Buffer[0] := #0;
  SetupDiGetDeviceRegistryProperty(PnPHandle, DevData, SPDRP_DEVICEDESC,
RegDataType, PByte(@Buffer[0]), SizeOf(Buffer), BytesReturned);
  Result:=Buffer;
end;

В итоге у нас вырисовывается функция, которая получает список устройств по заданному GUID’у класса.

procedure TForm1.AddDevices(aNode: TTreeNode; aGUID: TGUID);
var
  PnPHandle: HDEVINFO;
  DevData: TSPDevInfoData;
  RES: LongBool;
  Devn: Integer;
  _DN,_PN:ULONG;
begin
  PnPHandle := SetupDiGetClassDevs(@aGUID, nil, 0, DIGCF_PRESENT);
  if PnPHandle = INVALID_HANDLE_VALUE then Exit;
  Devn := 0;
  repeat
 DevData.cbSize := SizeOf(DevData);
 RES := SetupDiEnumDeviceInfo(PnPHandle, Devn, DevData);
 if (RES) and (_DN<>DN_ROOT_ENUMERATED) then
   begin
     DeviceTreeView.Items.AddChild(aNode, GetDeviceName(PnPHandle, DevData));
     Inc(Devn);
   end;
 if Devn=0 then
   begin
     DeviceTreeView.Items.Delete(aNode);
     break;
   end;
  until not RES;
  SetupDiDestroyDeviceInfoList(PnPHandle);
end;

Данная функция выводит список устройств заданного класса в компонент TreeView. Узел дерева TreeView задаётся первым параметром. Теперь мы можем написать функцию которая и произведёт вывод сего списка устройств в компонент TreeView. Вот она:

procedure TForm1.AddAllDevices;
var
  _i:DWORD;
  Res:CONFIGRET;
  GUID: PGUID;
  Buffer: array [0..1023] of CHAR;
  BufSize: DWORD;
  Node:TTreeNode;
begin
  DeviceClassesList:=TStringList.Create;
  _i:=0;
  repeat
 GetMem(GUID, SizeOf(TGUID));
 Res := CM_Enumerate_Classes(_i, GUID^, 0);
 if Res <> CR_NO_SUCH_VALUE then
   begin
     SetupDiGetClassDescription(GUID^, @Buffer[0], Length(Buffer), BufSize);
     DeviceClassesList.AddObject(Pchar(@Buffer[0]), TObject(GUID));
   end;
 Inc(_i);
  until Res = CR_NO_SUCH_VALUE;
  for _i:=0 to DeviceClassesList.Count-1 do
 begin
   Node:=DeviceTreeView.Items.AddChild(nil,DeviceClassesList.Strings[_i]);
   GUID := PGUID(DeviceClassesList.Objects[_i]);
   AddDevices(Node,GUID^);
 end;
end;

Сначала формируется список строк с имена классов и указателей на их GUID’ы. Потом производится вызов предыдущей функции для каждого класса.