» Работа с устройствами в Windows Borland Delphi. Win Api. . Блог программистов


Блог программистов






20083 Июн

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

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

   Функции, которые осуществляют работу с устройствами, находятся в системных библиотеках 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 с примером получения списка устройств, примером отключения устройства, безопасным отключением устройства, а также ведением лога изменений в аппаратном профиле. Оставляйте свои комментарии, если я допустил ошибки ли неточности в тексте этой статьи. Также если хотите, чтобы я осветил ещё какие-нибудь аспекты при работе с устройствами, то тоже оставляйте «заявки» в комментариях к статье.

Скачать исходник к статье

Комментарии

  1. 12 июля, 2008 | 16:37

    Чесно слово, спасибо! Очень нужна была эта статья. Спасибо! 🙂

  2. ilmar
    22 июля, 2008 | 15:12

    пасибо!!!

  3. MAcK
    27 августа, 2008 | 18:56

    Мда, долго искал и вот нечаяно зашёл в этот раздел, огромное спсибо

  4. def32
    20 ноября, 2008 | 05:05

    привет всем!
    прочитал статью об устройствах, очень познавательно, спасибо!
    у меня есть вопрос…
    имеется плата видеозахвата canopus mvr1000
    идущий с ней софт работатет замечательно, но мне надо получить картинку с платы в свой проект
    ни DSPack ни VideoLAb не видят устройство, да и само оно в винде светится как kernel mode driver…
    подскажите как мне с ним работать плиз!
    или может есть вариант отослать свои данные в софт который идет с платой?
    суть: мне необходимо выводить надпись на видео

  5. rpy3uH
    29 марта, 2009 | 20:06

    SetupAPI не позволяет получать информацию (звук, картинку) с различных устройств. SetupAPI предназначено для установки и удаления устройств, для получения характеристик устройств и т.д.

  6. Владимир
    20 июня, 2009 | 09:20

    Доброе время суток. Вы пишете «Для получения полного списка устройств сначала надо получить список классов. Для получения списка классов используется функция CM_Enumerate_Classes:» как и чем вызывается данная функция? И как пройти обратный процесс — от установленного драйвера до кода?
    Как вы считаете, каким образом лучше всего проверить корректность установки драйверов на устройства? Как и где прочитать ошибки устройств, где найти расшифровку кодов ошибок которые выдают ОС ХР/98/2000/МЕ/95?
    Я понимаю так что независимо от от ОС код одинаков, правильно ли?

  7. rpy3uH
    30 июня, 2009 | 09:18

    Ответы на ваши вопросы есть в MSDN.
    А по поводу кода, то на системах ХР/2000 всё идентично. А системы 98/МЕ/95 я вообще не понимаю кому они вообще нужны.

  8. Aleksss
    8 декабря, 2009 | 23:57

    Спасибо огромное, подобную статью искал две недели, но ничего даже близко лежащего не нашел. Большое спасибо!

  9. Constantine
    17 декабря, 2009 | 14:28

    Спасибо! Очень толково!

  10. Алькозар
    14 января, 2010 | 11:19

    Все день добрый, 😥 помогите пожалуста,у меня такая проблема:был жосткий диск на 40 GB, а теперь он показывает 4GB, чего с ним делать ? инфа мне с него не нужна, мне нужно место, за рание огромное спасибо!!!!

  11. rpy3uH
    14 января, 2010 | 19:03

    все вопросы на форум http://programmersforum.ru/

  12. 12 марта, 2010 | 16:17

    Хорошая статья для начинания работы с устройствами.

  13. sandrey
    19 марта, 2010 | 13:28

    Статья отличная. У меня остался единственный вопрос — можно ли до вызова SetupDiSetClassInstallParams узнать состояние устройства — включено или отключено ? После вызова состояние мне известно, но у меня задача не просто выключить, а еще и организационными методами наказать за то, что включили без разрешения. Если я просто буду выключать каждую минуту, то во-первых буду грузить систему ненужной задачей, а во-вторых, и в главных — не смогу вычислить врагов. Речь конечно же о «любимых» переносных устройствах хранения информации — флешки, CD, DVD и даже (не поверите, наверное) — дискеты. Очень прошу помочь — нигде не могу найти эту информацию.

  14. Dyckiy
    20 апреля, 2010 | 20:39

    откуда взять setupapi.pas ?

  15. Dyckiy
    20 апреля, 2010 | 20:41

    извиняюсь, сразу не посмотрел какие файлы есть. В проекте путь нужен относительный

  16. felleer
    27 мая, 2010 | 14:42

    Хорошая статья, очень помогла.
    Но есть маленькая неточность — в примере реализации процедуры вкл/выкл сетевого интерфейса
    procedure EnableNetDevice(aState:boolean;index:integer);
    не хватает
    PCHP.HwProfile:=0;

  17. Macmep
    6 октября, 2010 | 12:13

    А где можно взять функцию IsUSBDevice()?

  18. rpy3uH
    6 октября, 2010 | 13:07

    скачай исходник, там всё есть

  19. Василий
    10 октября, 2010 | 21:08

    Наконец нашел то, что надо! Автор молоток 😉

  20. Сергей
    21 февраля, 2011 | 13:58

    Очень интересная статья. А почему все надписи в приложении иероглифами???

  21. IlidanVile
    13 апреля, 2011 | 15:58

    felleer абсолютно прав.

    Если не добавить строку PCHP.HwProfile:=0; в процедуру EnableNetDevice, то устройство выключаться не будет.

  22. amidtv
    25 мая, 2011 | 19:37

    Подскажите пожалуйста:
    как добраться до номера СОМ порта который в Enum и далее в Device Parameters в PortName находится

  23. Алекс
    27 марта, 2012 | 19:18

    Статья — то что доктор прописал!
    Огромное спасибо автору, за исходник отдельно 😉

  24. Mary
    2 апреля, 2012 | 12:45

    Как его корректно откомпилить под XE2? Трабл с кодировкой — русские символы отображаются кракозяброй везде, кроме лога событий

  25. Mary
    2 апреля, 2012 | 12:51

    А всё, проблема решена — всезде при выводе символов, XE почему-то ставит PWideChar. Принудительно делаем вывод в PAnsiChar и все работает как надо 😎

  26. Verg
    13 апреля, 2012 | 13:32

    Народ как заставить это работать под Win7 x64 ?

  27. Alex
    13 апреля, 2012 | 14:50

    Очень полезная статья. Рад что на нее наткнулся.

    А как получить при обработке сообщения WMDEVICECHANGE хэгдл только что подключенного устройства?

    как отловить подключение понятно:

    procedure TForm1.WMDEVICECHANGE(var Msg: TMessage);
    begin
    if (MSG.WParam = DBT_DEVICEARRIVAL) then
    if (PDEV_BROADCAST_HDR(Msg.LParam)^.dbch_devicetype = //тип устр-ва) then
    begin

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

  28. Alex
    13 апреля, 2012 | 14:51

    хотел написать хэндл* ))

  29. rpy3uH
    20 апреля, 2012 | 14:46

    никак, эта информация не передаётся обработчику WM_DEVICECHANGE

  30. Alex
    23 апреля, 2012 | 09:00

    Интересно тогда почему в исходнике, в обработчике собития WM_DEVICECHANGE есть такая интересная строка:

    DBT_DEVTYP_HANDLE:
    LogMemo.Lines.Add(‘новый системный хендл’);

    Правда в listbox заявленный в ней хендл не заносится.

  31. Иван
    2 июля, 2012 | 22:42

    Вопрос- а хэндлы девайсов при разных запусках одинаковы?

  32. Дефлоратороз
    26 января, 2013 | 23:45

    Mary
    April 2nd, 2012 | 12:45

    Как его корректно откомпилить под XE2? Трабл с кодировкой – русские символы отображаются кракозяброй везде, кроме лога событий
    Mary
    April 2nd, 2012 | 12:51

    А всё, проблема решена – всезде при выводе символов, XE почему-то ставит PWideChar. Принудительно делаем вывод в PAnsiChar и все работает как надо 😎

    Как? что где нужно вставлять? два часа возился и результат ноль…

  33. WizAlx
    10 марта, 2013 | 08:25

    Не буду для всех моментов описывать, где это поставить надо. Напишу только для примера.
    Вот исходный код:
    DeviceTreeView.Items.AddChild(aNode, GetDeviceName(PnPHandle, DevData));

    Что бы выводилось корректно в XE над переписать на следующее:
    DeviceTreeView.Items.AddChild(aNode, PAnsiChar(GetDeviceName(PnPHandle, DevData)));

    То есть для каждого вывода добавить PAnsiChar();

  34. AlexAndErr
    20 декабря, 2013 | 15:23

    А можно ли юзать в С++ Builder. Если включить setupapi.h, то не находит функцию CM_Enumerate_Classes. Если подключить в проект setupapi.pas, то link error. Подключение setupapi.lib не помогает? Заранее спасибо.

  35. AlexAndErr
    20 декабря, 2013 | 16:40

    Вообщем создал заголовочный файл такого содержания и все заработало:

    #ifndef MySetupApiH
    #define MySetupApiH
    //—————————————————————————
    #include
    #include
    //#include «dlldeclare.h»
    //—————————————————————————
    typedef unsigned CONFIGRET;
    static const Shortint CR_NO_SUCH_VALUE = 0x25;
    //—————————————————————————
    extern «C» unsigned __stdcall CM_Enumerate_Classes(unsigned ulClassIndex, GUID &ClassGuid, unsigned ulFlags);
    //—————————————————————————
    #endif

  36. AlexAndErr
    20 декабря, 2013 | 16:43

    Первый инклуд vcl.h, второй SetupApi.h 🙂

  37. Andretti
    31 июля, 2014 | 14:47

    ОГРОМНОЕ СПАСИБО!!!! ИСКАЛ ДАВНО ЭТУ ИНФОРМАЦИЮ!!!!

  38. den3z
    18 ноября, 2014 | 12:07

    Спасибо автору, очень выручил … !!!