Меня заинтересовала тема создания класса, который можно было бы унаследовать в другом компоненте/приложении 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(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.
Автор: altk