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

в 9:56, , рубрики: c++, windows runtime, WRL, разработка под windows

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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js