Навигация
Категории
C++,C#,C [19]
Уроки по средам разработки приложений с использованием C,C++,C#, а также .Net, DirectX, OpenGL и других
Delphi [14]
Уроки работы в среде программирования Delphi
Basic [13]
Уроки разработки приложений в среде Basic
DirectX [8]
Уроки по работе с DirectX, включая Direct3D и другие инструменты
Web [7]
Уроки по "Веб-программированию"
JavaScript [12]
Уроки по кодингу в Java и JavaScript
XNA [9]
Статьи и уроки программирование в среде XNA C++&C#
Профиль
Статистика
Rambler's Top100

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Locations of visitors to this page
Главная » Статьи » Программирование » 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’ы. Потом производится вызов предыдущей функции для каждого класса.

Включение и отключение устройств

Состоянием устройства управляет функция SetupDiSetClassInstallParams. Её описание:

WINSETUPAPI BOOL WINAPI
  SetupDiSetClassInstallParams(
 IN HDEVINFO DeviceInfoSet,
 IN PSP_DEVINFO_DATA DeviceInfoData, OPTIONAL
 IN PSP_CLASSINSTALL_HEADER ClassInstallParams, OPTIONAL
 IN DWORD ClassInstallParamsSize
 );

С первыми двумя параметрами я думаю всё ясно. Третий параметр задаёт указатель на структуру SP_CLASSINSTALL_HEADER. Четвёртый параметр задаёт размер третьего параметра. С помощью этой функции можно производить различные действия с устройствами и, разумеется, для каждого действия используются различные структуры. Но у каждой из структур первая составляющая одинаковая – структура SP_CLASSINSTALL_HEADER, вот она:

typedef struct _SP_CLASSINSTALL_HEADER {
  DWORD cbSize;
  DI_FUNCTION InstallFunction;
} SP_CLASSINSTALL_HEADER, *PSP_CLASSINSTALL_HEADER;

Поле InstallFunction задаёт производимую над устройством операцию. Для включения/отключения это поле будет равно константе DIF_PROPERTYCHANGE. Для включения/отключения устройства используется следующая структура:

typedef struct _SP_PROPCHANGE_PARAMS {
  SP_CLASSINSTALL_HEADER ClassInstallHeader;
  DWORD StateChange;
  DWORD Scope;
  DWORD HwProfile;
} SP_PROPCHANGE_PARAMS, *PSP_PROPCHANGE_PARAMS;

Если поле StateChange будет равно DICS_ENABLE, то устройство будет включено иначе DICS_DISABLE. Если поле Scope равно DICS_FLAG_GLOBAL, то изменения вступят в силу для всех аппаратных профилей, если DICS_FLAG_CONFIGSPECIFIC, то изменения вступят в силу только для указанного аппаратного профиля. Поле HwProfile задаёт ID аппаратного профиля, к которому будут применяться изменения, если он равен нулю, то текущий аппаратный профиль. Все параметры нуждаются в «утверждении» перед любыми изменениями. Поэтому функцию надо вызывать два раза. Если после первого вызова функция возвратила истинное значение, значит можно вызывать функцию второй раз.

После изменения состояния устройства надо вызвать установщик класса, т.к. после изменения состояния устройства может потребоваться перезагрузка системы или другие действия, для того чтобы изменения вступили в силу. Это осуществляется функцией SetupDiCallClassInstaller

WINSETUPAPI BOOL WINAPI
  SetupDiCallClassInstaller(
 IN DI_FUNCTION InstallFunction,
 IN HDEVINFO DeviceInfoSet,
 IN PSP_DEVINFO_DATA DeviceInfoData OPTIONAL
 );

Первый параметр задаёт код произведённой операции. Два остальных параметра я думаю, проблем не вызовут.

В качестве примера можно привести код включения и отключения сетевого подключения. Для того чтобы включить/выключить сетевое подключение достаточно включить/выключить сетевое устройство, через которое осуществляется сетевое подключение. Это производит следующая функция:

procedure EnableNetDevice(aState:boolean;index:integer);
var
  NetPnPHandle:HDEVINFO;
  PCHP:TSPPropChangeParams;
  DeviceData:TSPDevInfoData;
begin
  NetPnPHandle:=SetupDiGetClassDevs(@GUID_DEVCLASS_NET, 0, 0, DIGCF_PRESENT);
  if NetPnPHandle=INVALID_HANDLE_VALUE then exit;
  DeviceData.cbSize:=sizeof(TSPDevInfoData);
  SetupDiEnumDeviceInfo(NetPnPHandle, index, DeviceData);
  PCHP.ClassInstallHeader.cbSize:=sizeof(TSPClassInstallHeader);
  if SetupDiSetClassInstallParams(NetPnPHandle,@DeviceData,@PCHP,sizeof(TSPPropChangeParams)) then
 begin
   PCHP.ClassInstallHeader.cbSize := sizeof(TSPClassInstallHeader);
   PCHP.ClassInstallHeader.InstallFunction := DIF_PROPERTYCHANGE;
   PCHP.Scope := DICS_FLAG_CONFIGSPECIFIC;
 if aState then
   PCHP.StateChange := DICS_ENABLE
   else
   PCHP.StateChange := DICS_DISABLE;
 SetupDiSetClassInstallParams(NetPnPHandle,@DeviceData,@PCHP,sizeof(TSPPropChangeParams));
 SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,NetPnPHandle,@DeviceData);
  end;
  DeviceData.cbSize := sizeof(TSPDevInfoData);
  SetupDiDestroyDeviceInfoList(NetPnPHandle);
end;
Параметр index задаёт индекс сетевого устройства в списке сетевых устройств.

Безопасное извлечение устройства

Итак, с включением/отключением устройств мы разобрались. А как безопасно извлекать устройство? Безопасное извлечение устройства осуществляет функция CM_Request_Device_Eject. Вот её описание:

CMAPI CONFIGRET WINAPI
  CM_Request_Device_Eject(
 IN DEVINST dnDevInst,
 OUT PPNP_VETO_TYPE pVetoType,
 OUT LPTSTR pszVetoName,
 IN ULONG ulNameLength,
 IN ULONG ulFlags
 );

Первый параметр это хендл устройства. Второй параметр это указатель на переменную, в которую будет сохранён код причины при неудаче. Третий параметр это указатель на строку, в которую будет сохранена причина неудачи при неудачном вызове. Оба этих параметра опциональны и могут быть равны нулю. Пятый параметр это максимальная длина строки. Шестой не используется. Если pszVetoName равен нулю, то при неудаче сообщение выведет сама система. Вот и сама функция, которая осуществляет безопасное извлечение устройства.

procedure RemoveDrive(index:integer);
var
  DrivesPnPHandle: HDEVINFO;
  DevInfo: SP_DEVINFO_DATA;
  i: Integer;
  Parent: DWORD;
  VetoName:array[0..MAX_PATH] of char;
begin
  DevInfo.cbSize := sizeof(SP_DEVINFO_DATA);
  DrivesPnPHandle := SetupDiGetClassDevsA(@GUID_DEVCLASS_DISKDRIVE, nil, 0, 2);
  if DrivesPnPHandle = INVALID_HANDLE_VALUE then exit;
  if SetupDiEnumDeviceInfo(DrivesPnPHandle, index, DevInfo) then
 begin
   if (IsUSBDevice(DevInfo.DevInst)) and (CM_Get_Parent(Parent, DevInfo.DevInst, 0) = CR_SUCCESS)
     then
       begin
         CM_Request_Device_Eject(Parent, nil, @VetoName, MAX_PATH, 0);
         if VetoName=” then
           ShowMessage(’устройство можно извлечь’)
             else
           ShowMessage(’устройство нельзя извлечь’);
       end
                 else
       ShowMessage(’Не USB устройство’);
 end;
  SetupDiDestroyDeviceInfoList(DrivesPnPHandle);
end;
Единственный параметр передаваемой функции это индекс дискового устройства в списке дисковых устройств. Единственное что модет быть здесь непонятно - это функция CM_Get_Parent. Она получает родитель устройства. Ведь любая «флешка» или внешний дисковый накопитель это составное устройство и отключать надо именно родительское устройство. Код функции IsUSBDevice есть в исходнике, который прилагается к статье.

Отслеживание изменений в аппаратной конфигурации

Каждый раз, когда происходят какие-либо изменения в аппаратном профиле, главному окну приложения посылается сообщение WM_DEVICECHANGE.При получении этого сообщения WParam содержит код события. Нас интересуют только три кода: DBT_DEVICEARRIVAL, DBT_DEVICEREMOVECOMPLETE и DBT_DEVNODES_CHANGED.

Событие DBT_DEVNODES_CHANGED обозначает, что произошли изменения в аппаратном профиле. LParam в данном случае равен нулю. События DBT_DEVICEARRIVAL и DBT_DEVICEREMOVECOMPLETE идентичны и различаются тем, что первое событие обозначает присоединение устройства и второе отсоединение устройства. LParam отличен от нуля при этих событиях и указывает на структуру DEV_BROADCAST_HDR. В зависимости от поля dbch_devicetype в жтой структуре дальнейшие поля могут варьироваться. Например, если dbch_devicetype равен DBT_DEVTYP_VOLUME, то LParam в этом случае указывает на структуру DEV_BROADCAST_VOLUME и поле dbcv_unitmask в этой структуре содержит битовую маску новых дисков. (нулевой бит обозначает букву А, второй букву B, третий букву C и так далее).

Приводить код обработчика этого сообщения не имеет смысла, он содержится в исходнике, который прилагается к статье.

Для того чтобы «подписаться» на сообщения системы надо вызвать функцию RegisterDeviceNotification:

HDEVNOTIFY WINAPI RegisterDeviceNotification(
  __in HANDLE hRecipient,
  __in LPVOID NotificationFilter,
  __in DWORD Flags
);

Первый параметр это хендл статуса сервиса либо хендл формы. Параметр NotificationFilter является указателем на структуру DEV_BROADCAST_HDR и задаёт тип устройств для отслеживания. Для отслеживания всех устройств поле dbch_devicetype должно быть равно значению DBT_DEVTYP_DEVICEINTERFACE. Для получения изменений всех классов устройств. Если третий параметр равен DEVICE_NOTIFY_WINDOW_HANDLE, то первый параметр должен быть хендлом окна, если DEVICE_NOTIFY_SERVICE_HANDLE, то первый параметр это хендл статуса сервиса. Также для получения сообщений об изменении всех классов устройств этот параметр должен включать флагDEVICE_NOTIFY_ALL_INTERFACE_CLASSES.

Вот и конец этой статьи. К статье прилагается исходник на Delphi с примером получения списка устройств, примером отключения устройства, безопасным отключением устройства, а также ведением лога изменений в аппаратном профиле.

Автор: rpy3uH, pblog.ru

Исходный код примеров




Источник: http://www.cyberguru.ru/programming/delphi/windows-devices-page4.html
Категория: Delphi | Добавил: gforcer (19.06.2010) | Автор: Админ
Просмотров: 2834 | Комментарии: 1 | Теги: Урок, создание, оборудованием, утсройства, работа, драйвера, написание, основы, статья, Delphi | Рейтинг: 4.0/2
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Поиск
Друзья сайта
Демотиваторы
Copyright Зямаев Денис © 2017