Программирование arrow Delphi arrow Delphi: Работа с устройствами в Windows

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

Оглавление

Функции, которые осуществляют работу с устройствами, находятся в системных библиотеках cfgmgr32.dll и setupapi.dll. К сожалению, в стандартных заголовочных файлах Delphi нет объявлений функций, констант и структур которые используются этими библиотеками. Эти заголовочные файлы можно скачать с сайта проекта Delphi-JEDI. Те, кому не нравятся модули от проекта Delphi-JEDI могут воспользоваться моим модулем setupapi.pas, но в нём далеко не полный список функции и структур.

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

Первая задача, с которой мы столкнёмся это получение списка устройств. Устройства в системе подразделяются на классы, например: класс видеоустройств, принтеров, модемы, клавиатуры и т.д. Любое устройство должно принадлежать как-нибудь классу. Каждый класс идентифицируется своим 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’ы. Потом производится вызов предыдущей функции для каждого класса.


 
« Предыдущая статья   Следующая статья »


  • Delphi, Написание внешних компонент для 1С на Delphi
    Насколько мне известно, многие 1С-ники хотели бы изучить написание внешних компонент, чтобы поднять свое магическое искусство 1С на качественно иную ступень. Что этому может помешать? Во-первых, известный синдром компонентофобии (который исторически берет свое начало от криво написанных внешних компонент). Во-вторых –синдром  клинически запутанного кода. OLE-программирование – это не самая простая штука, и, как говорится, «не всякая птица долетит до середины Днепра&...
  • Delphi, Вывод графиков функций в Delphi
    Изучая доступную литературу по программированию, которую я нашел в Интернете, а также некоторые программы, я пришел к выводу, что программисты то ли не осознают, то ли не хотят напрягаться на эту тему, и всё делают, как в школе учили. Строят графики, как на бумаге. Тем самым умаляя возможности компьютера. Оставляя те же недостатки метода построения, и даже усугубляя их....
  • Delphi, Приемы работы с базами данных в Delphi
    Данная статья предназначена в основном для тех, кто начинает работать с базами данных. Здесь собраны приемы, направленные на оптимизацию и ускорение работы с базами данных. Описанные примеры являются результатом многолетней работы автора с СУБД MS SQL Server, Oracle и Access. Примеры описываются в общем виде, без привязки к какой-либо конкретной СУБД....
  • Delphi, Работа с потоками в Delphi
    Данная статья предназначена для начинающих программистов, которые никогда не работали с потоками, и хотели бы узнать основы работы с ними. Желательно, чтоб читатель знал основы ООП и имел какой-нибудь опыт работы в Delphi. Для начала давайте определимся, что под словом "поток" я подразумеваю именно Thread, который еще имеет название "нить"....
  • Delphi, Message методы, или обработка сообщений классами в Delphi
    Данная статья предназначения для более глубокого понимания того, как реализована обработка сообщений Windows в VCL и как это можно и нужно использовать в своих целях и использовать правильно....
  • Delphi, Запись CD-DVD дисков в Delphi
    Доброго времени суток уважаемые любители Delphi. В этой статье я расскажу про запись CD\DVD дисков в среде Delphi. Общие принципы, изложенные в этой статье подойдут не только для языка Delphi, но и для языка С++. Для прочтения этой статьи с максимальной пользой, читателю рекомендуется получить базовые понятия об OLE\COM, впрочем даже незнание этих понятий вряд ли помешает понимаю этой статьи, так как классы и компоненты Delphi (так же как и классы С++), которые мы будет использовать полностью ск...
  • Delphi, Хуки в Windows на Delphi
    Тема про хуки является популярной на многих форумах программистов. Материал этих статей рассчитан на начинающего пользователя, примеры будут на Delphi. В этой статье будут изложены основные принципы механизма хуков, и будет написан пример клавиатурного шпиона....
  • Delphi, Перехват API функций в Delphi с помощью сплайсинга
    Сегодня я расскажу довольно таки эффективную методику перехвата API функций. Не следует думать, что если мы хотим перехватить API функции, то мы пишем либо троян, вирус и ещё какую-нибудь заразу, с помощью перехвата API функций осуществляются многие защитные механизмы, перехват API функций это довольно-таки нужная и полезная вещь. Для прочтения данной статьи с максимальной пользой обязательны, нужны, как минимум, начальные знания низкоуровневого программирования и хотя бы какие-нибудь знания арх...