Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом разделе показано, как преобразовать объекты двоичного интерфейса приложения SDK (ABI) и C++/WinRT . Эти методы можно использовать для взаимодействия между кодом, использующими эти два способа программирования с помощью среда выполнения Windows, или использовать их при постепенном перемещении кода из ABI в C++/WinRT.
Как правило, C++/WinRT представляет типы ABI в виде void*, чтобы вам не нужно было подключать файлы заголовков платформы.
Note
В примерах кода мы используем reinterpret_cast (а не static_cast), чтобы подчеркнуть, что это по своей сути небезопасные приведения типов.
Что такое среда выполнения Windows ABI и какие типы ABI?
Класс среда выполнения Windows (класс среды выполнения) — это абстракция. Эта абстракция определяет двоичный интерфейс (двоичный интерфейс приложения или ABI), который позволяет различным языкам программирования взаимодействовать с объектом. Независимо от языка программирования взаимодействие клиентского кода с объектом среда выполнения Windows происходит на самом низком уровне, с конструкциями клиентского языка, переведенными в вызовы ABI объекта.
Заголовки Windows SDK в папке "%WindowsSdkDir%Include\10.0.17134.0\winrt" (при необходимости измените номер версии SDK в вашем случае) — это файлы заголовков ABI среда выполнения Windows. Они были созданы компилятором MIDL. Ниже приведен пример включения одного из этих заголовков.
#include <windows.foundation.h>
Ниже приведен упрощенный пример одного из типов ABI, которые вы найдете в этом конкретном заголовке пакета SDK. Обратите внимание на пространство имен ABI; Windows::Foundation и все остальные пространства имен Windows объявляются заголовками пакета SDK в пространстве имен ABI.
namespace ABI::Windows::Foundation
{
IUriRuntimeClass : public IInspectable
{
public:
/* [propget] */ virtual HRESULT STDMETHODCALLTYPE get_AbsoluteUri(/* [retval, out] */__RPC__deref_out_opt HSTRING * value) = 0;
...
}
}
IUriRuntimeClass — это com-интерфейс. Но это ещё не всё: поскольку его базовым интерфейсом является IInspectable, IUriRuntimeClass представляет собой интерфейс среда выполнения Windows. Обратите внимание на тип возвращаемого значения HRESULT , а не на повышение исключений. И использование артефактов, таких как дескриптор HSTRING (рекомендуется задать этот дескриптор обратно nullptr , когда закончите работу с ним). Это дает представление о том, как выглядит среда выполнения Windows на уровне двоичного интерфейса приложения; другими словами, на уровне программирования COM.
среда выполнения Windows основан на API-интерфейсах объектной модели компонентов (COM). Вы можете получить доступ к среда выполнения Windows таким образом или получить к нему доступ через проекции языка. Проекция скрывает сведения COM и обеспечивает более естественный интерфейс программирования для заданного языка.
Например, если вы посмотрите в папке "%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt" (опять же, измените номер версии пакета SDK для вашего дела, если это необходимо), то вы найдете заголовки проекции языка C++/WinRT. Существует заголовок для каждого пространства имен Windows, как и один заголовок ABI для каждого пространства имен Windows. Ниже приведен пример включения одного из заголовков C++/WinRT.
#include <winrt/Windows.Foundation.h>
А вот из этого заголовка — эквивалент этого типа ABI в C++/WinRT, который мы только что рассмотрели (в упрощённом виде).
namespace winrt::Windows::Foundation
{
struct Uri : IUriRuntimeClass, ...
{
winrt::hstring AbsoluteUri() const { ... }
...
};
}
Интерфейс здесь современный, стандартный C++. Он избавляется от кодов HRESULT (C++/WinRT при необходимости вызывает исключения). И функция-аксессор возвращает обычный строковый объект, который очищается в конце своей области видимости.
В этом разделе рассматриваются случаи, когда вам нужно обеспечить взаимодействие с кодом или портировать код, работающий на уровне двоичного интерфейса приложения (ABI).
Преобразование в типы ABI и из них в коде
Для безопасности и простоты при преобразованиях в обоих направлениях можно просто использовать winrt::com_ptr, com_ptr::as и winrt::Windows::Foundation::IUnknown::as. Ниже приведен пример кода (на основе шаблона проекта консольного приложения ), который также иллюстрирует использование псевдонимов пространства имен для различных островов для решения возможных конфликтов пространства имен между проекцией C++/WinRT и ABI.
// pch.h
#pragma once
#include <windows.foundation.h>
#include <unknwn.h>
#include "winrt/Windows.Foundation.h"
// main.cpp
#include "pch.h"
namespace winrt
{
using namespace Windows::Foundation;
}
namespace abi
{
using namespace ABI::Windows::Foundation;
};
int main()
{
winrt::init_apartment();
winrt::Uri uri(L"http://aka.ms/cppwinrt");
// Convert to an ABI type.
winrt::com_ptr<abi::IStringable> ptr{ uri.as<abi::IStringable>() };
// Convert from an ABI type.
uri = ptr.as<winrt::Uri>();
winrt::IStringable uriAsIStringable{ ptr.as<winrt::IStringable>() };
}
Реализации функций as вызывают QueryInterface. Если нужны низкоуровневые преобразования, которые вызывают только AddRef, то можно использовать вспомогательные функции winrt::copy_to_abi и winrt::copy_from_abi. Следующий пример кода добавляет эти преобразования нижнего уровня в приведенный выше пример кода.
Это важно
При взаимодействии с типами ABI важно, чтобы используемый тип ABI соответствовал интерфейсу по умолчанию объекта C++/WinRT. В противном случае вызовы методов типа ABI фактически будут вызывать методы в том же слоте vtable в интерфейсе по умолчанию с очень неожиданными результатами. Обратите внимание, что winrt::copy_to_abi не защищает от этого на этапе компиляции, поскольку использует void* для всех типов ABI и предполагает, что вызывающая сторона позаботилась о том, чтобы не перепутать типы. Это позволяет избежать необходимости использовать заголовки C++/WinRT для ссылки на заголовки ABI, если типы ABI никогда не используются.
int main()
{
// The code in main() already shown above remains here.
// Lower-level conversions that only call AddRef.
// Convert to an ABI type.
ptr = nullptr;
winrt::copy_to_abi(uriAsIStringable, *ptr.put_void());
// Convert from an ABI type.
uri = nullptr;
winrt::copy_from_abi(uriAsIStringable, ptr.get());
ptr = nullptr;
}
Ниже приведены другие, столь же низкоуровневые методы преобразования, но на этот раз с использованием сырых указателей на типы интерфейсов ABI (те, которые определены заголовочными файлами Windows SDK).
// The code in main() already shown above remains here.
// Copy to an owning raw ABI pointer with copy_to_abi.
abi::IStringable* owning{ nullptr };
winrt::copy_to_abi(uriAsIStringable, *reinterpret_cast<void**>(&owning));
// Copy from a raw ABI pointer.
uri = nullptr;
winrt::copy_from_abi(uriAsIStringable, owning);
owning->Release();
Для самых низкоуровневых преобразований, которые лишь копируют адреса, можно использовать вспомогательные функции winrt::get_abi, winrt::detach_abi и winrt::attach_abi.
WINRT_ASSERT — это определение макроса, и оно расширяется до _ASSERTE.
// The code in main() already shown above remains here.
// Lowest-level conversions that only copy addresses
// Convert to a non-owning ABI object with get_abi.
abi::IStringable* non_owning{ reinterpret_cast<abi::IStringable*>(winrt::get_abi(uriAsIStringable)) };
WINRT_ASSERT(non_owning);
// Avoid interlocks this way.
owning = reinterpret_cast<abi::IStringable*>(winrt::detach_abi(uriAsIStringable));
WINRT_ASSERT(!uriAsIStringable);
winrt::attach_abi(uriAsIStringable, owning);
WINRT_ASSERT(uriAsIStringable);
функция convert_from_abi
Эта вспомогательная функция преобразует необработанный указатель на интерфейс ABI в эквивалентный объект C++/WinRT с минимальными накладными расходами.
template <typename T>
T convert_from_abi(::IUnknown* from)
{
T to{ nullptr }; // `T` is a projected type.
winrt::check_hresult(from->QueryInterface(winrt::guid_of<T>(),
winrt::put_abi(to)));
return to;
}
Функция просто вызывает QueryInterface для запроса к интерфейсу по умолчанию запрошенного типа C++/WinRT.
Как мы уже видели, вспомогательные функции не требуются для преобразования из объекта C++/WinRT в эквивалентный указатель интерфейса ABI. Просто используйте функцию-член winrt:Windows::Foundation::IUnknown::as (или try_as) для запроса запрошенного интерфейса. Функции as и try_as возвращают объект winrt::com_ptr, оборачивающий запрошенный тип ABI.
Пример кода с помощью convert_from_abi
Ниже приведен пример кода, показывающий эту вспомогательной функцию на практике.
// pch.h
#pragma once
#include <windows.foundation.h>
#include <unknwn.h>
#include "winrt/Windows.Foundation.h"
// main.cpp
#include "pch.h"
#include <iostream>
using namespace winrt;
using namespace Windows::Foundation;
namespace winrt
{
using namespace Windows::Foundation;
}
namespace abi
{
using namespace ABI::Windows::Foundation;
};
namespace sample
{
template <typename T>
T convert_from_abi(::IUnknown* from)
{
T to{ nullptr }; // `T` is a projected type.
winrt::check_hresult(from->QueryInterface(winrt::guid_of<T>(),
winrt::put_abi(to)));
return to;
}
inline auto put_abi(winrt::hstring& object) noexcept
{
return reinterpret_cast<HSTRING*>(winrt::put_abi(object));
}
}
int main()
{
winrt::init_apartment();
winrt::Uri uri(L"http://aka.ms/cppwinrt");
std::wcout << "C++/WinRT: " << uri.Domain().c_str() << std::endl;
// Convert to an ABI type.
winrt::com_ptr<abi::IUriRuntimeClass> ptr = uri.as<abi::IUriRuntimeClass>();
winrt::hstring domain;
winrt::check_hresult(ptr->get_Domain(sample::put_abi(domain)));
std::wcout << "ABI: " << domain.c_str() << std::endl;
// Convert from an ABI type.
winrt::Uri uri_from_abi = sample::convert_from_abi<winrt::Uri>(ptr.get());
WINRT_ASSERT(uri.Domain() == uri_from_abi.Domain());
WINRT_ASSERT(uri == uri_from_abi);
}
Взаимодействие с указателями интерфейса ABI COM
Шаблон вспомогательной функции ниже иллюстрирует копирование указателя интерфейса COM ABI заданного типа в эквивалентный тип проецируемого смарт-указателя C++/WinRT.
template<typename To, typename From>
To to_winrt(From* ptr)
{
To result{ nullptr };
winrt::check_hresult(ptr->QueryInterface(winrt::guid_of<To>(), winrt::put_abi(result)));
return result;
}
...
ID2D1Factory1* com_ptr{ ... };
auto cppwinrt_ptr {to_winrt<winrt::com_ptr<ID2D1Factory1>>(com_ptr)};
Следующий шаблон вспомогательной функции эквивалентен, за исключением того, что выполняет копирование из типа интеллектуального указателя из Windows Implementation Libraries (WIL).
template<typename To, typename From, typename ErrorPolicy>
To to_winrt(wil::com_ptr_t<From, ErrorPolicy> const& ptr)
{
To result{ nullptr };
if constexpr (std::is_same_v<typename ErrorPolicy::result, void>)
{
ptr.query_to(winrt::guid_of<To>(), winrt::put_abi(result));
}
else
{
winrt::check_result(ptr.query_to(winrt::guid_of<To>(), winrt::put_abi(result)));
}
return result;
}
См. также раздел "Использование COM-компонентов с помощью C++/WinRT".
Небезопасное взаимодействие с указателями интерфейса ABI COM
В следующей таблице показаны (помимо других операций) небезопасные преобразования между указателем интерфейса COM ABI заданного типа и его эквивалентным типом интеллектуального указателя C++/WinRT. Для кода, приведённого в таблице, предположим следующие объявления.
winrt::Sample s;
ISample* p;
void GetSample(_Out_ ISample** pp);
Предположим, что ISample является интерфейсом по умолчанию для примера.
Вы можете проверить это во время компиляции с помощью такого кода.
static_assert(std::is_same_v<winrt::default_interface<winrt::Sample>, winrt::ISample>);
| Operation | Как это сделать | Notes |
|---|---|---|
| Извлечение ISample* из winrt::Sample | p = reinterpret_cast<ISample*>(get_abi(s)); |
s по-прежнему владеет объектом. |
| Отключение ISample* от winrt::Sample | p = reinterpret_cast<ISample*>(detach_abi(s)); |
s больше не владеет объектом. |
| Передача ISample* в новый winrt::Sample | winrt::Sample s{ p, winrt::take_ownership_from_abi }; |
s берет на себя владение объектом. |
| Установите ISample* в winrt::Sample | *put_abi(s) = p; |
s берет на себя владение объектом. Любой объект, ранее принадлежавший s, считается утечкой (в режиме отладки сработает assert). |
| Преобразование ISample* в winrt::Sample | GetSample(reinterpret_cast<ISample**>(put_abi(s))); |
s берет на себя владение объектом. Любой объект, которым ранее владел s, считается утёкшим (в отладочной сборке будет срабатывать assert). |
| Замена ISample* в winrt::Sample | attach_abi(s, p); |
s берет на себя владение объектом. Объект, ранее принадлежащий s , освобождается. |
| Скопируйте ISample* в winrt::Sample | copy_from_abi(s, p); |
s делает новую ссылку на объект. Объект, ранее принадлежащий s , освобождается. |
| Скопируйте winrt::Sample в ISample* | copy_to_abi(s, reinterpret_cast<void*&>(p)); |
p получает копию объекта. Любой объект, ранее принадлежавший p, считается утраченным. |
Взаимодействие со структурой GUID в ABI
GUID (/previous-versions/aa373931(v%3Dvs.80)) проецируется как winrt::guid. Для реализуемых API необходимо использовать winrt::guid для параметров GUID. В противном случае существуют автоматические преобразования между winrt::guid и GUID, при условии, что вы включаете unknwn.h (он неявно включается через <windows.h> и многие другие файлы заголовков) до включения каких-либо заголовков C++/WinRT.
Если этого не сделать, то можно жёстко переreinterpret_castключаться между ними. Для таблицы ниже предположим, что используются следующие объявления.
winrt::guid winrtguid;
GUID abiguid;
| Conversion | С #include <unknwn.h> |
Без #include <unknwn.h> |
|---|---|---|
| Из winrt::guid в GUID | abiguid = winrtguid; |
abiguid = reinterpret_cast<GUID&>(winrtguid); |
| Из GUID в winrt::guid | winrtguid = abiguid; |
winrtguid = reinterpret_cast<winrt::guid&>(abiguid); |
Вы можете создать winrt::guid , как показано ниже.
winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} };
Пример того, как создать winrt::guid из строки, см. в make_guid.cpp.
Взаимодействие с HSTRING ABI
В следующей таблице показаны преобразования между winrt::hstring и HSTRING и другими операциями. Для кода, приведённого в таблице, предполагаются следующие объявления.
winrt::hstring s;
HSTRING h;
void GetString(_Out_ HSTRING* value);
| Operation | Как это сделать | Notes |
|---|---|---|
| Извлечение HSTRING из hstring | h = reinterpret_cast<HSTRING>(get_abi(s)); |
s по-прежнему владеет строкой. |
| Отключение HSTRING от hstring | h = reinterpret_cast<HSTRING>(detach_abi(s)); |
s больше не владеет строкой. |
| Установите HSTRING в hstring | *put_abi(s) = h; |
s берет на себя владение строкой. Любая строка, которой ранее владел объект s, приводит к утечке (в отладочной сборке сработает assert). |
| Получение HSTRING в hstring | GetString(reinterpret_cast<HSTRING*>(put_abi(s))); |
s берет на себя владение строкой. Любая строка, ранее принадлежавшая s, будет утечкой; в отладочной сборке сработает assert. |
| Замена HSTRING в hstring | attach_abi(s, h); |
s берет на себя владение строкой. Строка, ранее принадлежающая s , освобождается. |
| Копирование HSTRING в hstring | copy_from_abi(s, h); |
s делает частную копию строки. Строка, ранее принадлежающая s , освобождается. |
| Копирование hstring в HSTRING | copy_to_abi(s, reinterpret_cast<void*&>(h)); |
h получает копию строки. Происходит утечка любой строки, ранее принадлежавшей h. |
Кроме того, вспомогательные средства Windows библиотеки реализаций (WIL) выполняют основные операции со строками. Чтобы использовать вспомогательные функции WIL для строк, подключите <wil/resource.h> и см. таблицу ниже. Чтобы получить полные сведения, следуйте ссылкам в таблице.
| Operation | Вспомогательный средство строки WIL для получения дополнительных сведений |
|---|---|
| Укажите необработанный указатель на строку Unicode или ANSI и необязательную длину; получите подходящую специализированную обёртку unique_any | wil::make_something_string |
| Распаковка смарт-объекта до тех пор, пока не найден необработанный указатель строки Юникода, завершаемый пустым значением | wil::str_raw_ptr |
Получите строку, на которую ссылается объект интеллектуального указателя; или пустую строку L"", если интеллектуальный указатель пуст |
wil::string_get_not_null |
| Объединение любого числа строк | wil::str_concat |
| Получение строки из строки формата printf и соответствующего списка параметров | wil::str_printf |
Важные API
- Функция AddRef
- Функция QueryInterface
- функция winrt::attach_abi
- шаблон структуры winrt::com_ptr
- функция winrt::copy_from_abi
- функция winrt::copy_to_abi
- Функция winrt::detach_abi
- функция winrt::get_abi
- функция-член winrt::Windows::Foundation::IUnknown::as
- winrt::Windows::Foundation::IUnknown::try_as функция-член
Windows developer