Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
В этом разделе показано, как работать с API C++/WinRT, будь они частью Windows, реализованными сторонними поставщиками компонентов или вами лично.
Это важно
Чтобы примеры кода в этом разделе были короткими и простыми для вас, их можно воспроизвести, создав новый консольное приложение Windows (C++/WinRT) проект и код вставки копирования. Однако вы не можете использовать произвольные сторонние пользовательские типы среды выполнения Windows из непакованного приложения таким образом. Таким образом можно использовать только типы Windows.
Чтобы использовать пользовательские (сторонние) типы среды выполнения Windows из консольного приложения, необходимо задать приложению идентификатор пакета , чтобы можно было обработать регистрацию используемых пользовательских типов. Дополнительные сведения см. в Проекте упаковки приложений для Windows.
Кроме того, создайте новый проект, используя шаблон проектов Blank App (C++/WinRT), Core App (C++/WinRT)или Windows Runtime Component (C++/WinRT). Эти типы приложений уже обладают идентификатором пакета .
Если API находится в пространстве имен Windows
Это наиболее распространенный случай, в котором вы будете использовать API среды выполнения Windows. Для каждого типа в пространстве имен Windows, определенном в метаданных, C++/WinRT определяет эквивалент, удобный для C++ (называемый проецируемым типом ). Проецируемый тип имеет то же полное имя, что и тип Windows, но он помещается в пространство имен winrt C++ с помощью синтаксиса C++. Например, Windows::Foundation::Uri проектируется в C++/WinRT как winrt::Windows::Foundation::Uri.
Ниже приведен простой пример кода. Если вы хотите скопировать приведенные ниже примеры кода непосредственно в основно й файл исходного кода консольного приложения Windows (C++/WinRT) проекта, сначала задайте не использовать предварительно скомпилированные заголовки в свойствах проекта.
// main.cpp
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace Windows::Foundation;
int main()
{
winrt::init_apartment();
Uri contosoUri{ L"http://www.contoso.com" };
Uri combinedUri = contosoUri.CombineUri(L"products");
}
Включенный заголовок winrt/Windows.Foundation.h является частью пакета SDK, найденного в папке %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt\. Заголовки в этой папке содержат типы пространств имен Windows, проецируемые в C++/WinRT. В этом примере winrt/Windows.Foundation.h содержит winrt::Windows::Foundation::Uri, который является проекцией типа класса среды выполнения Windows::Foundation::Uri.
Подсказка
Если вы хотите использовать тип из пространства имен Windows, включите заголовок C++/WinRT, соответствующий этому пространству имен. Директивы using namespace являются необязательными, но удобными.
В приведенном выше примере кода, после инициализации C++/WinRT, мы выделили в стеке значение проецируемого типа winrt::Windows::Foundation::Uri, используя один из его общедоступных документированных конструкторов (в этом примереUri(String)). Для этого наиболее распространенный вариант использования, это обычно все, что вам нужно сделать. После получения проецируемого значения типа C++/WinRT его можно рассматривать как экземпляр фактического типа среды выполнения Windows, так как он имеет все те же члены.
На самом деле прогнозируемое значение является прокси; это по сути просто умный указатель на резервный объект. Конструкторы проецируемого значения вызывают RoActivateInstance для создания экземпляра класса среды выполнения Windows (Windows.Foundation.Uri, в данном случае) и сохраняют интерфейс по умолчанию этого объекта внутри нового проецируемого значения. Как показано ниже, вызовы элементов проецируемого значения фактически делегируются через умный указатель на резервный объект; где происходят изменения состояния.
Когда значение contosoUri выходит из области видимости, оно разрушается и освобождает ссылку на интерфейс по умолчанию. Если эта ссылка является последней ссылкой на поддерживающий объект среды выполнения Windows Windows.Foundation.Uri, то поддерживающий объект также уничтожается.
Подсказка
Проецируемый тип — это оболочка над типом среды выполнения Windows для применения его API. Например, проецируемый интерфейс — это оболочка над интерфейсом платформы Windows Runtime.
Заголовки проекции C++/WinRT
Чтобы использовать API пространства имен Windows из C++/WinRT, вы включаете заголовки из папки %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt. Необходимо включить заголовки, соответствующие каждому используемому пространству имен.
Например, для пространства имен Windows::Security::Cryptography::Certificates эквивалентные определения типов C++/WinRT находятся в winrt/Windows.Security.Cryptography.Certificates.h. Включение этого заголовка предоставляет доступ ко всем типам в пространстве имен Windows::Security::Cryptography::Certificates.
Иногда один заголовок пространства имен будет включать части связанных заголовков пространства имен, но не следует полагаться на эти сведения о реализации. Явно включите заголовки для используемых пространств имен.
Например, метод Certificate::GetCertificateBlob возвращает интерфейс Windows::Storage::Streams::IBuffer .
Перед вызовом метода Certificate::GetCertificateBlob необходимо включить winrt/Windows.Storage.Streams.h файл заголовка пространства имен, чтобы убедиться, что вы можете получать и работать с возвращенным методом Windows::Storage::Streams::IBuffer.
Если забыть включить необходимые директивы пространства имен перед использованием типов, это может стать распространенной причиной ошибок сборки.
Доступ к членам через объект, через интерфейс или через ABI
При проекции C++/WinRT представление класса среды выполнения Windows не превышает базовых интерфейсов ABI. Но, для удобства, вы можете работать с классами так, как задумал их автор. Например, можно вызвать метод ToString объекта Uri, как если бы это был метод класса (в действительности, за кулисами это метод отдельного интерфейса IStringable).
WINRT_ASSERT — это определение макроса, и оно расширяется до _ASSERTE.
Uri contosoUri{ L"http://www.contoso.com" };
WINRT_ASSERT(contosoUri.ToString() == L"http://www.contoso.com/"); // QueryInterface is called at this point.
Это удобство достигается с помощью запроса к соответствующему интерфейсу. Но вы всегда держите ситуацию под контролем. Вы можете пожертвовать частью удобства ради некоторой производительности, получив интерфейс IStringable самостоятельно и используя его напрямую. В приведенном ниже примере кода вы получите фактический указатель интерфейса IStringable во время выполнения (с помощью одноразового запроса). После этого ваш вызов ToString становится непосредственным и теперь избегает дальнейшего вызова QueryInterface.
...
IStringable stringable = contosoUri; // One-off QueryInterface.
WINRT_ASSERT(stringable.ToString() == L"http://www.contoso.com/");
Этот метод можно выбрать, если вы знаете, что вы будете вызывать несколько методов в одном интерфейсе.
Кстати, если вы хотите получить доступ к членам на уровне ABI, вы можете. В примере кода ниже показано, как это делается, а также предоставлены более подробные сведения и примеры кода в разделе , посвященном взаимодействию между C++/WinRT и ABI.
#include <Windows.Foundation.h>
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>
using namespace winrt::Windows::Foundation;
int main()
{
winrt::init_apartment();
Uri contosoUri{ L"http://www.contoso.com" };
int port{ contosoUri.Port() }; // Access the Port "property" accessor via C++/WinRT.
winrt::com_ptr<ABI::Windows::Foundation::IUriRuntimeClass> abiUri{
contosoUri.as<ABI::Windows::Foundation::IUriRuntimeClass>() };
HRESULT hr = abiUri->get_Port(&port); // Access the get_Port ABI function.
}
Задержка инициализации
В C++/WinRT каждый проецируемый тип имеет специальный конструктор C++/WinRT std::nullptr_t . За исключением этого, все проецируемые конструкторы типа , включая конструктор по умолчанию, вызывают создание резервного объекта среды выполнения Windows и дают вам умный указатель на него. Таким образом, это правило применяется в любом месте, где используется конструктор по умолчанию, например неинициализированные локальные переменные, неинициализированные глобальные переменные и неинициализированные переменные-члены.
Если, с другой стороны, вы хотите создать переменную проецируемого типа без создания связанного объекта среды выполнения Windows (чтобы можно было отложить эту работу до более позднего момента), то это вполне возможно. Объявите переменную или поле с помощью специального конструктора C++/WinRT std::nullptr_t (который проекция C++/WinRT внедряется в каждый класс среды выполнения). Мы используем этот специальный конструктор с m_gamerPicBuffer в приведенном ниже примере кода.
#include <winrt/Windows.Storage.Streams.h>
using namespace winrt::Windows::Storage::Streams;
#define MAX_IMAGE_SIZE 1024
struct Sample
{
void DelayedInit()
{
// Allocate the actual buffer.
m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
}
private:
Buffer m_gamerPicBuffer{ nullptr };
};
int main()
{
winrt::init_apartment();
Sample s;
// ...
s.DelayedInit();
}
Все конструкторы в проецируемом типе , кроме конструкторе std::nullptr_t вызывают создание резервного объекта среды выполнения Windows. Конструктор std::nullptr_t по сути является no-op. Он ожидает, что проектируемый объект будет инициализирован в последующее время. Таким образом, имеет ли класс среды выполнения конструктор по умолчанию или нет, этот метод можно использовать для эффективной отложенной инициализации.
Это касается других мест, где вы вызываете конструктор по умолчанию, например в векторах и картах. Рассмотрим этот пример кода, для которого потребуется проект Blank App (C++/WinRT).
std::map<int, TextBlock> lookup;
lookup[2] = value;
Назначение создает новый TextBlock, а затем немедленно перезаписывает его value. Вот исправление.
std::map<int, TextBlock> lookup;
lookup.insert_or_assign(2, value);
См. также , как конструктор по умолчанию влияет на коллекции.
Не задерживайте инициализацию по ошибке
Убедитесь, что вы не вызываете по ошибке конструктор std::nullptr_t. Разрешение конфликтов компилятора отдает предпочтение этому по сравнению с фабричными конструкторами. Например, рассмотрим эти два определения классов среды выполнения.
// GiftBox.idl
runtimeclass GiftBox
{
GiftBox();
}
// Gift.idl
runtimeclass Gift
{
Gift(GiftBox giftBox); // You can create a gift inside a box.
}
Предположим, что мы хотим создать Подарок, который находится вне коробки (Подарок, построенный с неинициализированной Подарочной коробкой). Во-первых, давайте посмотрим на неправильный способ сделать это. Мы знаем, что есть конструктор Gift, который принимает GiftBox. Но если у нас есть соблазн передать null-значение GiftBox (вызов конструктор Gift через единую инициализацию, как показано ниже), то мы не получим нужный результат.
// These are *not* what you intended. Doing it in one of these two ways
// actually *doesn't* create the intended backing Windows Runtime Gift object;
// only an empty smart pointer.
Gift gift{ nullptr };
auto gift{ Gift(nullptr) };
Вы получаете здесь неинициализированный Подарок. Вы не получите подарок с неинициализированной подарочной коробкой. Вот правильный способ это сделать.
// Doing it in one of these two ways creates an initialized
// Gift with an uninitialized GiftBox.
Gift gift{ GiftBox{ nullptr } };
auto gift{ Gift(GiftBox{ nullptr }) };
В неправильном примере передача nullptr литерала разрешается в пользу конструктора отложенной инициализации. Чтобы устранить проблему в пользу конструктора фабрики, тип параметра должен быть GiftBox. Вы по-прежнему можете передать явно инициируемый GiftBox, как показано в правильном примере.
Следующий пример также правильный, так как параметр имеет тип GiftBox, а не std::nullptr_t.
GiftBox giftBox{ nullptr };
Gift gift{ giftBox }; // Calls factory constructor.
Только при передаче литерала nullptr возникает неоднозначность.
Не выполняйте копирующее построение по ошибке.
Это предупреждение аналогично описанному в разделе Не задерживайте инициализацию по ошибке выше.
Помимо конструктора задержки инициализации проекция C++/WinRT также внедряет конструктор копирования в каждый класс среды выполнения. Это конструктор с одним параметром, который принимает тот же тип, что и созданный объект. Результирующий умный указатель указывает на тот же базовый объект среды выполнения Windows, на который указывает параметр конструктора. Результатом является два объекта интеллектуального указателя, указывающие на один и тот же резервный объект.
Ниже приведено определение класса среды выполнения, которое мы будем использовать в примерах кода.
// GiftBox.idl
runtimeclass GiftBox
{
GiftBox(GiftBox biggerBox); // You can place a box inside a bigger box.
}
Предположим, что мы хотим построить GiftBox внутри более крупного GiftBox.
GiftBox bigBox{ ... };
// These are *not* what you intended. Doing it in one of these two ways
// copies bigBox's backing-object-pointer into smallBox.
// The result is that smallBox == bigBox.
GiftBox smallBox{ bigBox };
auto smallBox{ GiftBox(bigBox) };
правильный способ сделать это — в явной форме вызвать фабрику активации.
GiftBox bigBox{ ... };
// These two ways call the activation factory explicitly.
GiftBox smallBox{
winrt::get_activation_factory<GiftBox, IGiftBoxFactory>().CreateInstance(bigBox) };
auto smallBox{
winrt::get_activation_factory<GiftBox, IGiftBoxFactory>().CreateInstance(bigBox) };
Если API реализован в компоненте среды выполнения Windows
Этот раздел применяется, независимо от того, создали ли вы компонент самостоятельно или он был получен от поставщика.
Замечание
Для получения информации об установке и использовании расширения Visual Studio C++/WinRT (VSIX) и пакета NuGet (которые вместе обеспечивают поддержку шаблона проекта и сборки), см. подраздел Поддержка Visual Studio для C++/WinRT.
В проекте приложения обратитесь к файлу метаданных среды выполнения Windows (.winmd) компонента среды выполнения Windows и сборке. Во время сборки средство cppwinrt.exe создает стандартную библиотеку C++, которая полностью описывает — или проектирует— область API для компонента. Другими словами, созданная библиотека содержит проецируемые типы для компонента.
Затем, как и для типа пространства имен Windows, вы включаете заголовок и создаете проецируемый тип с помощью одного из его конструкторов. Код запуска проекта приложения регистрирует класс среды выполнения, а конструктор проецируемого типа вызывает RoActivateInstance для активации класса среды выполнения из указанного компонента.
#include <winrt/ThermometerWRC.h>
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
ThermometerWRC::Thermometer thermometer;
...
};
Дополнительные сведения, код и пошаговое руководство по использованию API, реализованных в компоненте среды выполнения Windows, см. в разделе «Компоненты среды выполнения Windows с C++/WinRT» и разделе «Создание событий в C++/WinRT».
Если API реализован в потребляющем проекте
Пример кода в этом разделе взят из темы "Управляющие элементы XAML "; привязка к свойству C++/WinRT. См. эту тему для получения дополнительных сведений, включая код и пошаговое руководство по использованию класса среды выполнения, который реализуется и используется в одном и том же проекте.
Тип, используемый из пользовательского интерфейса XAML, должен быть классом среды выполнения, даже если он находится в том же проекте, что и XAML. В этом сценарии создается проецируемый тип из метаданных среды выполнения Windows (.winmd). Опять же, вы включаете заголовок, но у вас есть выбор между способами создания экземпляра класса среды выполнения C++/WinRT версии 1.0 или версии 2.0. Метод версии 1.0 использует winrt::make; метод версии 2.0 известен как унифицированное создание. Давайте рассмотрим каждый из них в свою очередь.
Создание с использованием winrt::make
Начнем с метода по умолчанию (C++/WinRT версии 1.0), так как рекомендуется по крайней мере ознакомиться с этим шаблоном. Вы создаете проецируемый тип с помощью конструктора std::nullptr_t. Этот конструктор не выполняет инициализацию, поэтому необходимо присвоить экземпляру значение с помощью вспомогательной функции winrt::make, передавая все необходимые аргументы конструктора. Класс среды выполнения, реализованный в том же проекте, что и используемый код, не нужно регистрировать или создавать экземпляры с помощью активации Windows Runtime/COM.
См. элементы управления XAML; привязка к свойству C++/WinRT для полного пошагового руководства. В этом разделе показано извлечение из этого пошагового руководства.
// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
BookstoreViewModel MainViewModel{ get; };
}
}
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
private:
Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
};
...
// MainPage.cpp
...
#include "BookstoreViewModel.h"
MainPage::MainPage()
{
m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
...
}
Единообразная конструкция
В C++/WinRT версии 2.0 и более поздних версиях доступна оптимизированная форма создания, известная как единообразная конструкция (см. Новости и изменения в C++/WinRT 2.0).
См. элементы управления XAML; привязка к свойству C++/WinRT для полного пошагового руководства. В этом разделе показано извлечение из этого пошагового руководства.
Чтобы использовать унифицированную конструкцию вместо winrt::make, вам потребуется фабрика активации. Хороший способ создать его — добавить конструктор в IDL.
// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
MainPage();
BookstoreViewModel MainViewModel{ get; };
}
}
Затем в MainPage.h объявите и инициализируйте m_mainViewModel всего за один шаг, как показано ниже.
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
private:
Bookstore::BookstoreViewModel m_mainViewModel;
...
};
}
...
А затем в конструкторе MainPage в MainPage.cppнет необходимости использовать код m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.
Дополнительные сведения о единообразном построении и примерах кода см. в подключении к единообразному построению и прямом доступе к реализации.
Создание экземпляров и возврат проецируемых типов и интерфейсов
Ниже приведен пример того, какие проецируемые типы и интерфейсы могут выглядеть в используемом проекте. Помните, что проецируемый тип (например, тот, который в этом примере), создается инструментом и не является тем, что вы сами создаете.
struct MyRuntimeClass : MyProject::IMyRuntimeClass, impl::require<MyRuntimeClass,
Windows::Foundation::IStringable, Windows::Foundation::IClosable>
MyRuntimeClass — это проецируемый тип; проецируемые интерфейсы включают IMyRuntimeClass, IStringable и IClosable. В этом разделе показаны различные способы создания экземпляра проецируемого типа. Ниже приведены напоминания и сводка с помощью MyRuntimeClass в качестве примера.
// The runtime class is implemented in another compilation unit (it's either a Windows API,
// or it's implemented in a second- or third-party component).
MyProject::MyRuntimeClass myrc1;
// The runtime class is implemented in the same compilation unit.
MyProject::MyRuntimeClass myrc2{ nullptr };
myrc2 = winrt::make<MyProject::implementation::MyRuntimeClass>();
- Вы можете получить доступ к членам всех интерфейсов проецируемого типа.
- Вы можете вернуть проецируемый тип вызывающему.
- Проецируемые типы и интерфейсы являются производными от winrt::Windows::Foundation::IUnknown. Таким образом, можно вызвать IUnknown::как для проецируемого типа или интерфейса, чтобы запросить другие проецируемые интерфейсы, которые также можно использовать или вернуть вызывающему. Функция-член как работает так, как QueryInterface.
void f(MyProject::MyRuntimeClass const& myrc)
{
myrc.ToString();
myrc.Close();
IClosable iclosable = myrc.as<IClosable>();
iclosable.Close();
}
Фабрики активации
Удобный, прямой способ создания объекта C++/WinRT выглядит следующим образом.
using namespace winrt::Windows::Globalization::NumberFormatting;
...
CurrencyFormatter currency{ L"USD" };
Но могут возникнуть случаи, когда вы захотите создать фабрику активации самостоятельно, а затем создать объекты из неё, когда вам будет удобно. Ниже приведены некоторые примеры, показывающие вам, как использовать шаблон функции winrt::get_activation_factory
using namespace winrt::Windows::Globalization::NumberFormatting;
...
auto factory = winrt::get_activation_factory<CurrencyFormatter, ICurrencyFormatterFactory>();
CurrencyFormatter currency = factory.CreateCurrencyFormatterCode(L"USD");
using namespace winrt::Windows::Foundation;
...
auto factory = winrt::get_activation_factory<Uri, IUriRuntimeClassFactory>();
Uri uri = factory.CreateUri(L"http://www.contoso.com");
Классы в двух приведенных выше примерах являются типами из пространства имен Windows. В следующем примере ThermometerWRC::Thermometer — это пользовательский тип, реализованный в компоненте среды выполнения Windows.
auto factory = winrt::get_activation_factory<ThermometerWRC::Thermometer>();
ThermometerWRC::Thermometer thermometer = factory.ActivateInstance<ThermometerWRC::Thermometer>();
Неоднозначность элементов и типов
Если функция-член имеет то же имя, что и тип, существует неоднозначность. Правила поиска имен C++ в функциях членов заставляют сначала искать в классе, а затем в пространствах имен. Сбой подстановки не является ошибкой. Правило (SFINAE) не применяется (оно применяется при разрешении перегрузки шаблонов функций). Таким образом, если имя внутри класса не имеет смысла, компилятор не ищет лучшего соответствия— он просто сообщает об ошибке.
struct MyPage : Page
{
void DoWork()
{
// This doesn't compile. You get the error
// "'winrt::Windows::Foundation::IUnknown::as':
// no matching overloaded function found".
auto style{ Application::Current().Resources().
Lookup(L"MyStyle").as<Style>() };
}
}
Выше компилятор считает, что вы передаете FrameworkElement.Style(), которая в C++/WinRT является функцией-членом в качестве параметра шаблона в IUnknown::as. Решение заключается в том, чтобы принудительно интерпретировать имя Style как тип Windows::UI::Xaml::Style.
struct MyPage : Page
{
void DoWork()
{
// One option is to fully-qualify it.
auto style{ Application::Current().Resources().
Lookup(L"MyStyle").as<Windows::UI::Xaml::Style>() };
// Another is to force it to be interpreted as a struct name.
auto style{ Application::Current().Resources().
Lookup(L"MyStyle").as<struct Style>() };
// If you have "using namespace Windows::UI;", then this is sufficient.
auto style{ Application::Current().Resources().
Lookup(L"MyStyle").as<Xaml::Style>() };
// Or you can force it to be resolved in the global namespace (into which
// you imported the Windows::UI::Xaml namespace when you did
// "using namespace Windows::UI::Xaml;".
auto style = Application::Current().Resources().
Lookup(L"MyStyle").as<::Style>();
}
}
Подстановка неквалифицированного имени имеет специальное исключение, если за именем следует ::; в этом случае игнорируются функции, переменные и значения перечислений. Это позволяет выполнять такие действия.
struct MyPage : Page
{
void DoSomething()
{
Visibility(Visibility::Collapsed); // No ambiguity here (special exception).
}
}
Вызов Visibility() определяется как имя метода UIElement.Visibility. Но параметр Visibility::Collapsed следует слову Visibility с ::, поэтому имя метода игнорируется, и компилятор находит класс перечисления.
Важные API
- функция QueryInterface
- функция RoActivateInstance
- класс Windows::Foundation::Uri
- шаблон функции winrt::get_activation_factory
- шаблон функции winrt::make
- winrt::Windows::Foundation::IUnknown struct