[Из песочницы] Наследуемый класс компонента 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
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
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
virtual HRESULT STDMETHODCALLTYPE CreateInstance1(
INT32 value,
IInspectable* outer,
IInspectable** inner,
INumber** result) override
{
auto pnumber = Make