Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4507.html
 

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 я создал простенький класс, и попробовал перебрать все устройства:
    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).
Метки:  DLNA  |  Function Discovery API  |  Media Renderer  |  PnP  |  SSDP 

Комментарии

Salvador
12.09.2012 в 04:27
Hi very nice article, i'm using your code, but I,m stuck with the use of the IFunctionDiscoveryNotification interface, can you share the implementation of the TFDNotification class? Thanks.
Andrew
13.03.2015 в 20:23
http://teran.karelia.pro/articles/item_4507.html
а можете подробнее описать тип используемых данных или прислать исходник. Не могу определить тип TFDNotification и остальных переменных.
Спасибо!
ter
25.03.2015 в 17:49
в последнем абзаце статьи есть ссылка на архив, там разве нет описания типов?
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно