Наслідуваний клас компонента 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)]
[версія(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)]
[версія(0x00000001)]
interface INumberOverrides : IInspectable
{
HRESULT GetValue([out, retval] INT32* value);
}

[exclusiveto(Number)]
[uuid(29f9bd09-d452-49bf-99f9-59f328103cbd)]
[версія(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)]
[версія(0x00000001)]
runtimeclass Number
{
[default] interface INumber;
[overridable][версія(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.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.