Delphi: Робота з пристроями в Windows, Різне, Програмування, статті

http://pblog.ru/?p=105
Функції, які здійснюють роботу з пристроями, знаходяться в системних бібліотеках 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_PRESENT. При успішному виклику функція повертає хендл отриманого списку.

Отже, у нас є список (вірніше його хендл) і нам треба якось перерахувати всі пристрої знаходяться в ньому. На допомогу до нас прийде функція під назвою SetupDiEnumDeviceInfo:


WINSETUPAPI BOOL WINAPI
  SetupDiEnumDeviceInfo(
 IN HDEVINFO DeviceInfoSet,
 IN DWORD MemberIndex,
 OUT PSP_DEVINFO_DATA DeviceInfoData
 );

З перших параметром, я думаю, все ясно. Другий парметр задає індекс в списку. Третій параметр це покажчик на структуру SP_DEVINFO_DATA, в якій буде збережена інформація про пристрій. Якщо функція повернула значення TRUE, то інформація витягнута успішно, а якщо FALSE, то в більшості випадків це означає що ми прийшли до кінця списку. Для перерахування всього списку нам треба буде в циклі викликати функцію SetupDiEnumDeviceInfo щоразу збільшуючи значення індексу на одиницю до тих пір поки не отримаємо негативний результат.
Отже, вDelphiу нас є структура, в якій зберігається інформація про пристрій:


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;

У підсумку в Delphiу нас вимальовується функція, яка отримує список пристроїв за заданим 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 “и. Потім проводиться виклик попередньої функції для кожного класу.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*