Function Discovery API
Опубликовано 12.02.2012 г. 15:26
Речь в статье пойдет об обзоре инструментария Function Discovery Api для работы с устройствами и ресурсами ПК. Данный API появился в Windows Vista и предназначен перечисления и поиска по заданным условиям ресурсов и аппаратного обеспечения системы, в том числе и подключенных по сети.
Вообще как такового интересна именно к этому API у меня не было. Я тут было озадачился вопросом, как программно отправить видео воспроизводится на ТВ по DLNA. Как вы, возможно, знаете Windows 7 умеет такое делать (Воспроизвести на/Play To):


Но при этом сама библиотека Windows Media Foundation такого функционала не предоставляет. Гугл в поисках конкретного решения не помог, но на одном из форумов прочитал, что устройство вывода (DLNA Media Renderer) можно обнаружить как и обычное PnP-устройство (plug and paly) системы. За этот пост и уцепился, и решил начать с изучения того, как вообще перечислить все устройства, и хоть как то обнаружить телевизор подключенный по DLNA. Что после недолгих поисков и привело меня к Function Discovery API. Переписанных на паскаль заголовочных файлов я не нашел, так что пару вечеров пришлось потратить на то, чтобы переписать заголовочные файлы из Windows SDK. Кстати, в нем есть демонстрационное приложение fdbrowser.exe (у меня в program files\Microsoft SDKs\Windows\v6.0A\bin):

Исходные коды приложения не доступны, но оно построено на FD API. В общем то первым делом я и решил разобраться с API, что бы так же научиться перебирать различные устройства и получать их свойства. API позволяет не только перечислять устройства, но и как видно на скриншоте получать различную мета-информацию о них, а также предоставляет механизм уведомлений. Например, можно получать уведомление, когда подключается новое устройство и т.п. В частности при поиске устройств для PnP список возвращается сразу, а для сетевых устройств через уведомление (поскольку поиск требует некоторого времени). Как это обычно бывает API выполнен в виде COM. Главным объектом здесь является IFunctionDiscovery, который предоставляет доступ к "устройством", которые представлены интерфейсов IFunctionInstance. Вообще в API всего 6 различных интерфейсов, и в дополнение к двум вышеперечисленным есть еще интерфейс для получения уведомлений IFunctionNotification, а также коллекции устройств (IFunctionInstanceCollection) и два интерфейса для осуществления поиска ресурсов по критериям: IFunctionInstanceQuery и IFunctionInstanceCollectionQuery. Устройства разделяются на категории Layered и Provider. первые могут содержать подкатегории, вторые привязаны к конкретным провайдерам (их тоже можно разрабатывать). Базовые категории и подкатегории перечислены в виде констант. А вообще список доступен в реестре в ветке HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Function Discovery. Для получения первого представления об API я создал простенький класс, и попробовал перебрать все устройства:

Как видно из кода, сначала создается COM-объект IFunctionDiscovery (FFd), затем извлекается список устройств (FItem : IFunctionInstanceCollection) для категории PnP. Каждый элемент доступен через интерфейс IFunctionInstance (f), свойства которого можно получать через IPropertyStorе (ps) используя набор ключей TPropertyKey (PKEY_NAME) и значения TPropVariant (pv). Так что перечислить все PnP-устройства весьма просто. Для категории сетевых устройств результаты возвращаются через уведомления, и функция возвращает NULL. При получении информации асинхронно используется интерфейс IFunctionDiscoveryNotification, мы должны реализовать его, и передать в качестве параметра вызова метода CreateInstanceCollectionQuery у IFunctionDiscovery. Интерфейс прост. имеет всего 3 метода: OnUpdate, onError и OnEvent. Событие OnUpdate возникает при обнаружении, добавлении или удалении устройства. В качестве параметра мы получаем экземпляр устройства, и можем сохранить его в свою коллекцию для дальнейшей работы. После того как поиск устройств завершен, мы получим событие OnEvent с параметром FD_EVENTID_SEARCHCOMPLETE, после чего можем работать с нашей коллекцией. Не буду описывать реализацию IFunctionDiscvoveryNotification т.к. интереса она не представляет. Ниже приведет код для поиска SSDP (simple service discovery protocol) устройств:

Теперь, после того как устройство найдено, нужно подумать о том, как его использовать. С помощью метода IFunctionInstance.QueryService мы можем получить ссылку на интерфейс IUPnPDevice для дальнейшей работы с устройством, но это уже другая история, которую еще предстоит изучить. Кому надо, можете скачать архив с хэдерами Function Discovery API для Delphi (PChar = PWideChar).


Но при этом сама библиотека Windows Media Foundation такого функционала не предоставляет. Гугл в поисках конкретного решения не помог, но на одном из форумов прочитал, что устройство вывода (DLNA Media Renderer) можно обнаружить как и обычное PnP-устройство (plug and paly) системы. За этот пост и уцепился, и решил начать с изучения того, как вообще перечислить все устройства, и хоть как то обнаружить телевизор подключенный по DLNA. Что после недолгих поисков и привело меня к Function Discovery API. Переписанных на паскаль заголовочных файлов я не нашел, так что пару вечеров пришлось потратить на то, чтобы переписать заголовочные файлы из Windows SDK. Кстати, в нем есть демонстрационное приложение fdbrowser.exe (у меня в program files\Microsoft SDKs\Windows\v6.0A\bin):

Исходные коды приложения не доступны, но оно построено на FD API. В общем то первым делом я и решил разобраться с API, что бы так же научиться перебирать различные устройства и получать их свойства. API позволяет не только перечислять устройства, но и как видно на скриншоте получать различную мета-информацию о них, а также предоставляет механизм уведомлений. Например, можно получать уведомление, когда подключается новое устройство и т.п. В частности при поиске устройств для PnP список возвращается сразу, а для сетевых устройств через уведомление (поскольку поиск требует некоторого времени). Как это обычно бывает API выполнен в виде COM. Главным объектом здесь является IFunctionDiscovery, который предоставляет доступ к "устройством", которые представлены интерфейсов IFunctionInstance. Вообще в API всего 6 различных интерфейсов, и в дополнение к двум вышеперечисленным есть еще интерфейс для получения уведомлений IFunctionNotification, а также коллекции устройств (IFunctionInstanceCollection) и два интерфейса для осуществления поиска ресурсов по критериям: IFunctionInstanceQuery и IFunctionInstanceCollectionQuery. Устройства разделяются на категории Layered и Provider. первые могут содержать подкатегории, вторые привязаны к конкретным провайдерам (их тоже можно разрабатывать). Базовые категории и подкатегории перечислены в виде констант. А вообще список доступен в реестре в ветке HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Function Discovery. Для получения первого представления об API я создал простенький класс, и попробовал перебрать все устройства:
TFDApi = class(TObject) strict private FFd : IFunctionDiscovery; FItems : IFunctionInstanceCollection; public constructor Create(); destructor Destroy(); override; procedure Enumerate(); end; { TFDApi } constructor TFDApi.Create(); begin inherited; CoInitialize(nil); FFd := CreateComObject(CLSID_FunctionDiscovery) as IFunctionDiscovery; end; destructor TFDApi.Destroy(); begin FItems := nil; FFd := nil; CoUninitialize(); inherited; end; procedure TFDApi.Enumerate(); var hr : HResult; i : integer; f : IFunctionInstance; ps : IPropertyStore; pv : TPropVariant; begin hr := FFd.GetInstanceCollection(FCTN_CATEGORY_PNP, nil, true, Fitems); writeln('found ', fitems.Count, ' items'); for i := 0 to FItems.Count - 1 do begin fitems.Item(i, f); f.OpenPropertyStore(STGM_READ, ps); ps.GetValue(PKEY_NAME, pv); writeln(i, ' ' , pv.pwszVal); end; end;В результате выполнения кода находится 175 устройств/ресурсов:

Как видно из кода, сначала создается COM-объект IFunctionDiscovery (FFd), затем извлекается список устройств (FItem : IFunctionInstanceCollection) для категории PnP. Каждый элемент доступен через интерфейс IFunctionInstance (f), свойства которого можно получать через IPropertyStorе (ps) используя набор ключей TPropertyKey (PKEY_NAME) и значения TPropVariant (pv). Так что перечислить все PnP-устройства весьма просто. Для категории сетевых устройств результаты возвращаются через уведомления, и функция возвращает NULL. При получении информации асинхронно используется интерфейс IFunctionDiscoveryNotification, мы должны реализовать его, и передать в качестве параметра вызова метода CreateInstanceCollectionQuery у IFunctionDiscovery. Интерфейс прост. имеет всего 3 метода: OnUpdate, onError и OnEvent. Событие OnUpdate возникает при обнаружении, добавлении или удалении устройства. В качестве параметра мы получаем экземпляр устройства, и можем сохранить его в свою коллекцию для дальнейшей работы. После того как поиск устройств завершен, мы получим событие OnEvent с параметром FD_EVENTID_SEARCHCOMPLETE, после чего можем работать с нашей коллекцией. Не буду описывать реализацию IFunctionDiscvoveryNotification т.к. интереса она не представляет. Ниже приведет код для поиска SSDP (simple service discovery protocol) устройств:
procedure TMainForm.FormCreate(Sender: TObject); var hr : HResult; begin FFd := CreateComObject(CLSID_FunctionDiscovery) as IFunctionDiscovery; FNotification := TFDNotification.Create(); hr := FFd.CreateInstanceCollectionQuery(FCTN_CATEGORY_SSDP, nil, false, FNotification, FContext, FICQuery); FICQuery.AddQueryConstraint(PROVIDERSSDP_QUERYCONSTRAINT_TYPE, SSDP_CONSTRAINTVALUE_TYPE_DEV_MDARNDR); FICQuery.Execute(FItems); FData := TList.Create(); FNotification.FData := FData; end;Как видите здесь приложение уже не консольное. В коде используется запрос к коллекции FICQuery : IFunctionInstanceCollectionQuery. Мы запрашиваем категорию SSDP ресурсов, и передаем ссылку на наш callback-объект для получения уведомлений FNotification. Далее мы устанавливаем ограничения на запрос. Ограничения могут быть двух видов: либо это ограничение запроса, либо ограничения свойств устройства. В данном случае используется ограничение запроса, и в результат попадают только устройства имеющие тип SSDP_CONSTRAINTVALUE_TYPE_DEV_MDARNDR - Media Renderer - т.е устройства воспроизведения информации. После запуска такого кода я получаю список лишь из одного устройства - моего ТВ:

Теперь, после того как устройство найдено, нужно подумать о том, как его использовать. С помощью метода IFunctionInstance.QueryService мы можем получить ссылку на интерфейс IUPnPDevice для дальнейшей работы с устройством, но это уже другая история, которую еще предстоит изучить. Кому надо, можете скачать архив с хэдерами Function Discovery API для Delphi (PChar = PWideChar).
12.09.2012 в 04:27
13.03.2015 в 20:23
а можете подробнее описать тип используемых данных или прислать исходник. Не могу определить тип TFDNotification и остальных переменных.
Спасибо!
25.03.2015 в 17:49