Delphi: Работа с устройствами в Windows
ОГЛАВЛЕНИЕ
Получение списка устройств
Первая задача, с которой мы столкнёмся это получение списка устройств. Устройства в системе подразделяются на классы, например: класс видеоустройств, принтеров, модемы, клавиатуры и т.д. Любое устройство должно принадлежать как-нибудь классу. Каждый класс идентифицируется своим GUID’ом (глобальный уникальный идентификатор). GUID это 128 битная запись типа: {C06136A2-43EA-4F43-AF06-7413D07E28B7}. Для получения полного списка устройств сначала надо получить список классов. Для получения списка классов используется функция CM_Enumerate_Classes:CMAPI CONFIGRET WINAPIДля перечисления всех классов мы должны в цикле вызывать функцию, начиная с индекса 0. Если функция вернула значение CR_NO_SUCH_VALUE, значит, мы пришли к концу списка. Вторым параметром должен быть указатель на переменную TGUID, в которую будет сохранён GUID класса. Получение информации о классе осуществляет функция SetupDiGetClassDescription:
CM_Enumerate_Classes(
IN ULONG ulClassIndex,// индекс класса
OUT LPGUID ClassGuid,// указатель GUID класса
IN ULONG ulFlags //не используется
);
WINSETUPAPI BOOL WINAPI
SetupDiGetClassDescription(
IN LPGUID ClassGuid,//GUID класса
OUT PTSTR ClassDescription,//строка
IN DWORD ClassDescriptionSize,//размер строки
OUT PDWORD RequiredSize OPTIONAL//требуемый размер
);
Вторым параметром должен идти указатель на буфер, в который будет сохранена строка с именем класса. Третьим параметром должен идти размер передаваемого буфера. Если указанного буфера не хватит, то требуемый размер будет сохранён в переменной указатель, на которую мы передадим четвёртым параметром.
Список классов мы получили. Теперь нам надо получить список устройств, принадлежащих некоторому классу. Здесь к нам придёт на помощь функция SetupDiGetClassDevs:
HDEVINFOУ этой функции почти все параметры опциональны за исключением последнего. Первый параметр задаёт класс устройств для перечисления. Если этот параметр равен нулю, то перечисляться будут все устройства в системе. Второй и третий параметры (соответственно, имя PnP перечислителя и хендл формы) могут быть равны нулю. Последний параметр самый важный. Он может принимать одно из следующий значений или их комбинацию:
SetupDiGetClassDevs(
IN LPGUID ClassGuid, OPTIONAL
IN PCTSTR Enumerator, OPTIONAL
IN HWND hwndParent, OPTIONAL
IN DWORD Flags
);
- DIGCF_ALLCLASSES
Будет возвращён список всех устройств и всех классов, установленных в данный момент в системе. Первый параметр будет проигнорирован. - DIGCF_DEVICEINTERFACE
Возврат списка устройств, которые поддерживают интерфейсы. - DIGCF_DEFAULT
Возврат списка устройств, которые ассоциируются с системой по умолчанию. - DIGCF_PRESENT
Будет возвращён список устройств, которые в настоящее время присутствуют в системе. - DIGCF_PROFILE
Будет возвращён список устройств, которые являются частью текущего аппаратного профиля.
В нашем случае надо указать только класс устройств и указать последним параметром флаг DIGCF_PRESENT. При успешном вызове функция возвращает хендл полученного списка.
Итак, у нас есть список (вернее его хендл) и нам надо как-то перечислить все устройства находящиеся в нём. На помощь к нам придёт функция под названием SetupDiEnumDeviceInfo:
WINSETUPAPI BOOL WINAPIС первых параметром, я думаю, всё ясно. Второй парметр задаёт индекс в списке. Третий параметр это указатель на структуру SP_DEVINFO_DATA, в которой будет сохранена информация об устройстве. Если функция вернула значение TRUE, то информация извлечена успешно, а если FALSE, то в большинстве случаев это означает что мы пришли к концу списка. Для перечисления всего списка нам надо будет в цикле вызывать функцию SetupDiEnumDeviceInfo каждый раз увеличивая значение индекса на единицу до тех пор пока не получим отрицательный результат.
SetupDiEnumDeviceInfo(
IN HDEVINFO DeviceInfoSet,
IN DWORD MemberIndex,
OUT PSP_DEVINFO_DATA DeviceInfoData
);
Итак, у нас есть структура, в которой хранится информация об устройстве:
typedef struct _SP_DEVINFO_DATA {По сути, главным полем здесь является поле DevInst, которая и хранит хендл устройства. Для того чтобы получить имя устройства (или его описание) нам надо использовать функцию SetupDiGetDeviceRegistryProperty. Далее её описание
DWORD cbSize;
GUID ClassGuid;
DWORD DevInst;
ULONG_PTR Reserved;
} SP_DEVINFO_DATA, *PSP_DEVINFO_DATA;
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’ы. Потом производится вызов предыдущей функции для каждого класса.