Меня заинтересовала тема создания класса, который можно было бы унаследовать в другом компоненте/приложении WinRT. Расширение C++/CX позволяет создать такой класс только если он унаследует уже другой незапечатанный класс. В любом другом случае компиляция завершается с ошибкой. Использование WRL позволяет обойти это ограничение и делает возможным написание незапечатанного класса.
Для начала необходимо описать интерфейс объекта и фабрики:
В описании можно заметить несколько интересных деталей:
MIDL компилятор на основе данного кода создаст заголовочный файл, который необходимо будет подключить в файле кода.
Следующей задачей является реализация заданных интерфейсов в коде. Для MIDL компилятора были заданы настройки создания *.h файлов по паттерну %(Filename)_h.h, а также указана опция /ns_prefix(которая добавляет ABI префикс к генерируемому коду). Интерфейсы были определены в файле DataBinding.idl, поэтому подключается заголовочный файл DataBinding_h.h.
Итак, код реализации интерфейсов:
Расскажу про CreateInstance0 и CreateInstance1. Именно эти методы отвечают за «наследование».
К сожалению, не смог найти в документации рекомендаций по реализации фабричных методов данного типа. Поэтому пришлось опытным путём исследовать предназначение параметров:
Для этого подключил компонент к приложению, написанному на C#. В метаданных *.winmd был определён незапечатанный класс Number. Именно то, чего я пытался добиться. Оставалось только понять, как реализовать методы. Для этого использовал следующий код:
После нескольких проходов отладки пришёл к следующему варианту реализации фабричных методов:
Сначала создаём объект. Затем с помощью условия инициализируем возвращаемые значения. Выражение:
Опрашивает объект на реализацию интерфейса INumber, а в качестве возвращаемого значения передаётся указатель на параметр result. В случаем успешного выполнения, инициализируем параметр inner с помощью выражения:
В любом другом случаем просто инициализируем параметр result.
Данная статья носит исключительно справочный характер. Основной целью была демонстрация возможности написания «наследуемого» класса с использованием 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(0x00000001)] 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(0x00000001)] interface INumberOverrides : IInspectable { HRESULT GetValue([out, retval] INT32* value); } [exclusiveto(Number)] [uuid(29f9bd09-d452-49bf-99f9-59f328103cbd)] [version(0x00000001)] 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(0x00000001)] runtimeclass Number { [default] interface INumber; [overridable][version(0x00000001)] 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 <wrl.h> #include <wrl/wrappers/corewrappers.h> #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<RuntimeClassType::WinRt>, 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<Number> { new Number(), new Number(1), new LocalNumber(), new LocalNumber(1), }; }
После нескольких проходов отладки пришёл к следующему варианту реализации фабричных методов:
virtual HRESULT STDMETHODCALLTYPE CreateInstance0( IInspectable* outer, IInspectable** inner, INumber** result) override { auto pnumber = Make<Number>().Detach(); if (nullptr != outer && S_OK != outer->QueryInterface(ABI::DataBinding::IID_INumber, reinterpret_cast<void**>(result))) { *inner = reinterpret_cast<IInspectable*>(pnumber); } else { *result = pnumber; } return S_OK; } virtual HRESULT STDMETHODCALLTYPE CreateInstance1( INT32 value, IInspectable* outer, IInspectable** inner, INumber** result) override { auto pnumber = Make<Number>(value).Detach(); if (nullptr != outer && S_OK != outer->QueryInterface(ABI::DataBinding::IID_INumber, reinterpret_cast<void**>(result))) { *inner = reinterpret_cast<IInspectable*>(pnumber); } else { *result = pnumber; } return S_OK; }
Сначала создаём объект. Затем с помощью условия инициализируем возвращаемые значения. Выражение:
outer->QueryInterface(ABI::DataBinding::IID_INumber, reinterpret_cast<void**>(result))
Опрашивает объект на реализацию интерфейса INumber, а в качестве возвращаемого значения передаётся указатель на параметр result. В случаем успешного выполнения, инициализируем параметр inner с помощью выражения:
*inner = reinterpret_cast<IInspectable*>(pnumber);
В любом другом случаем просто инициализируем параметр result.
P.S.
Данная статья носит исключительно справочный характер. Основной целью была демонстрация возможности написания «наследуемого» класса с использованием WRL.
