[Из песочницы] Наследуемый класс компонента WinRT, написанный с использованием WRL

Меня заинтересовала тема создания класса, который можно было бы унаследовать в другом компоненте/приложении WinRT. Расширение C++/CX позволяет создать такой класс только если он унаследует уже другой незапечатанный класс. В любом другом случае компиляция завершается с ошибкой. Использование WRL позволяет обойти это ограничение и делает возможным написание незапечатанного класса.Описание интерфейсовДля начала необходимо описать интерфейс объекта и фабрики: import «inspectable.idl»;

namespace DataBinding { interface INumber; interface INumberOverrides; interface INumberFactory; runtimeclass Number; }

namespace DataBinding { [exclusiveto (Number)] [uuid (5b197688–2f57–4d01–92cd-a888f10dcd90)] [version (0×00000001)] interface INumber: IInspectable { [propget] HRESULT Value ([out, retval] INT32* value);

[propput] HRESULT Value ([in] INT32 value); }

[exclusiveto (Number)] [uuid (12b0eeee-76ed-47af-8247–610025184b58)] [version (0×00000001)] interface INumberOverrides: IInspectable { HRESULT GetValue ([out, retval] INT32* value); }

[exclusiveto (Number)] [uuid (29f9bd09-d452–49bf-99f9–59f328103cbd)] [version (0×00000001)] interface INumberFactory: IInspectable { [overload («CreateInstance»)] HRESULT CreateInstance0( [in] IInspectable* outer, [out] IInspectable** inner, [out, retval] Number** result);

[overload («CreateInstance»)] HRESULT CreateInstance1( [in] int value, [in] IInspectable* outer, [out] IInspectable** inner, [out, retval] Number** result); }

[composable (DataBinding.INumberFactory, public, 1.0)] [marshaling_behavior (agile)] [threading (both)] [version (0×00000001)] runtimeclass Number { [default] interface INumber; [overridable][version (0×00000001)] interface INumberOverrides; } } В описании можно заметить несколько интересных деталей: Определён интерфейс INumberOverrides, имеющий единственный метод GetValue. Класс Number реализует данный интерфейс и делает возможным его переопределение в дочерних классах. Интерфейс фабрики INumberFactory определяет два метода создания экземпляра объекта CreateInstance0(…) и CreateInstance1(…). Оба метода являются перегрузкой метода CreateInstance (…) — именно данный метод можно будет увидеть в файле метаданных *.winmd. В общем виде методы CreateInstance можно привести к форме: HRESULT CreateInstance ( … params, //список параметров, необходимых для создания объекта IInspectable *outer, //объект, переопределяющий виртуальные методы IInspectable **inner, //объект, предоставляющий базовую реализацию методов ISomeInterface **instance) //результирующий объект, комбинирующий outer и inner объект Класс Number имеет вспомогательный атрибут: [composable (DataBinding.INumberFactory, public, 1.0)] MIDL компилятор на основе данного кода создаст заголовочный файл, который необходимо будет подключить в файле кода.Реализация интерфейсов в C++ коде Следующей задачей является реализация заданных интерфейсов в коде. Для MIDL компилятора были заданы настройки создания *.h файлов по паттерну %(Filename)_h.h, а также указана опция /ns_prefix (которая добавляет ABI префикс к генерируемому коду). Интерфейсы были определены в файле DataBinding.idl, поэтому подключается заголовочный файл DataBinding_h.h.Итак, код реализации интерфейсов: #include #include #include «DataBinding_h.h»

using ABI: DataBinding: INumber; using ABI: DataBinding: INumberOverrides; using ABI: DataBinding: INumberFactory; using Microsoft: WRL: RuntimeClassFlags; using Microsoft: WRL: RuntimeClassType; using Microsoft: WRL: EventSource; using Microsoft: WRL: Make; using Microsoft: WRL: MakeAndInitialize; using Microsoft: WRL: RuntimeClass; using Microsoft: WRL: ActivationFactory; using Microsoft: WRL: ComPtr; using Microsoft: WRL: Wrappers: HStringReference;

class Number: public RuntimeClass < RuntimeClassFlags, INumber, INumberOverrides > { InspectableClass (RuntimeClass_DataBinding_Number, BaseTrust); private: INT32 _value; public: Number () : _value (0) { }

Number (INT32 value) : _value (value) { }

virtual HRESULT STDMETHODCALLTYPE get_Value (INT32* value) override { *value = _value; return S_OK; }

virtual HRESULT STDMETHODCALLTYPE put_Value (INT32 value) override { _value = value; return S_OK; }

virtual HRESULT STDMETHODCALLTYPE GetValue (INT32* value) override { *value = _value; return S_OK; } };

class NumberFactory: public ActivationFactory < INumberFactory > { InspectableClassStatic (RuntimeClass_DataBinding_Number, BaseTrust);

public: virtual HRESULT STDMETHODCALLTYPE CreateInstance0( IInspectable* outer, IInspectable** inner, INumber** result) override { … }

virtual HRESULT STDMETHODCALLTYPE CreateInstance1( INT32 value, IInspectable* outer, IInspectable** inner, INumber** result) override { … } };

ActivatableClassWithFactory (Number, NumberFactory); Расскажу про CreateInstance0 и CreateInstance1. Именно эти методы отвечают за «наследование».К сожалению, не смог найти в документации рекомендаций по реализации фабричных методов данного типа. Поэтому пришлось опытным путём исследовать предназначение параметров: IInspectable* outer, IInspectable** inner, INumber** result Для этого подключил компонент к приложения, написанному на C#. В метаданных *.winmd был определён незапечатанный класс Number. Именно то, чего я пытался добиться. Оставалось только понять, как реализовать методы. Для этого использовал следующий код: private class LocalNumber: Number { public LocalNumber () { } public LocalNumber (int value) : base (value) { } } … { var items = new List { new Number (), new Number (1), new LocalNumber (), new LocalNumber (1), }; } После нескольких проходов отладки пришёл к следующему варианту реализации фабричных методов: virtual HRESULT STDMETHODCALLTYPE CreateInstance0( IInspectable* outer, IInspectable** inner, INumber** result) override { auto pnumber = Make().Detach (); if (nullptr!= outer && S_OK!= outer→QueryInterface (ABI: DataBinding: IID_INumber, reinterpret_cast(result))) { *inner = reinterpret_cast(pnumber); } else { *result = pnumber; } return S_OK; }

virtual HRESULT STDMETHODCALLTYPE CreateInstance1( INT32 value, IInspectable* outer, IInspectable** inner, INumber** result) override { auto pnumber = Make(value).Detach (); if (nullptr!= outer && S_OK!= outer→QueryInterface (ABI: DataBinding: IID_INumber, reinterpret_cast(result))) { *inner = reinterpret_cast(pnumber); } else { *result = pnumber; } return S_OK; } Сначала создаём объект. Затем с помощью условия инициализируем возвращаемые значения. Выражение: outer→QueryInterface (ABI: DataBinding: IID_INumber, reinterpret_cast(result)) Опрашивает объект на реализацию интерфейса INumber, а в качестве возвращаемого значения передаётся указатель на параметр result. В случаем успешного выполнения, инициализируем параметр inner с помощью выражения: *inner = reinterpret_cast(pnumber); В любом другом случаем просто инициализируем параметр result.P.S. Данная статья носит исключительно справочный характер. Основной целью была демонстрация возможности написания «наследуемого» класса с использованием WRL.

© Habrahabr.ru