Взаимодействие между C++/WinRT и интерфейсом ABIInterop between C++/WinRT and the ABI

В этом разделе показано, как выполнять преобразование между объектами двоичного интерфейса приложений SDK (ABI) и C++/WinRT.This topic shows how to convert between SDK application binary interface (ABI) and C++/WinRT objects. Эти методики можно использовать для взаимодействия между кодом, который использует эти два способа программирования, и средой выполнения Windows, или для постепенного переноса кода с ABI на C++/WinRT.You can use these techniques to interop between code that uses these two ways of programming with the Windows Runtime, or you can use them as you gradually move your code from the ABI to C++/WinRT.

В общем случае C++/WinRT предоставляет типы ABI как void* , поэтому файлы заголовков платформы добавлять не нужно.In general, C++/WinRT exposes ABI types as void*, so that you don't need to include platform header files.

Что такое ABI среды выполнения Windows и что такое типы ABI?What is the Windows Runtime ABI, and what are ABI types?

Класс среды выполнения Windows (класс среды выполнения) — это, на самом деле, абстракция.A Windows Runtime class (runtime class) is really an abstraction. Такая абстракция определяет двоичный интерфейс (двоичный интерфейс приложения или ABI), позволяющий различным языкам программирования взаимодействовать с объектом.This abstraction defines a binary interface (the Application Binary Interface, or ABI) that allows various programming languages to interact with an object. Независимо от языка программирования, взаимодействие клиентского кода с объектом среды выполнения Windows происходит на самом низком уровне, при этом языковые конструкции клиента преобразуются в вызовы ABI объекта.Regardless of programming language, client code interaction with a Windows Runtime object happens at the lowest level, with client language constructs translated into calls into the object's ABI.

Заголовки пакета Windows SDK в папке "%WindowsSdkDir%Include\10.0.17134.0\winrt" (при необходимости измените номер версии пакета SDK на используемый), представляют собой файлы заголовков ABI среды выполнения Windows.The Windows SDK headers in the folder "%WindowsSdkDir%Include\10.0.17134.0\winrt" (adjust the SDK version number for your case, if necessary), are the Windows Runtime ABI header files. Они были созданы с помощью компилятора MIDL.They were produced by the MIDL compiler. Далее приведен пример включения одного из таких заголовков.Here's an example of including one of these headers.

#include <windows.foundation.h>

А также упрощенный пример одного из типов ABI, которые можно найти в этом конкретном заголовке SDK.And here's a simplified example of one of the ABI types that you'll find in that particular SDK header. Обратите внимание, что пространство имен ABI, Windows::Foundation и все остальные пространства имен Windows объявляются заголовками SDK внутри пространства имен ABI.Note the ABI namespace; Windows::Foundation, and all other Windows namespaces, are declared by the SDK headers within the ABI namespace.

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-интерфейс.IUriRuntimeClass is a COM interface. И более того—, поскольку он основан на IInspectable—, IUriRuntimeClass — это интерфейс среды выполнения Windows.But more than that—since its base is IInspectableIUriRuntimeClass is a Windows Runtime interface. Обратите внимание на тип возврата HRESULT, а не на вызов исключений,Note the HRESULT return type, rather than the raising of exceptions. а также на использование таких артефактов, как дескриптор HSTRING (рекомендуется снова задать значение nullptr для этого дескриптора после окончания работы с ним).And the use of artifacts such as the HSTRING handle (it's good practice to set that handle back to nullptr when you're finished with it). Это дает понять, как выглядит среда выполнения Windows на двоичном уровне приложения; другими словами, на уровне в COM-программирования.This gives a taste of what the Windows Runtime looks like at the application binary level; in other words, at the COM programming level.

Среда выполнения Windows основывается на API-интерфейсы модели COM.The Windows Runtime is based on Component Object Model (COM) APIs. Доступ к среде выполнения Windows можно получить таким образом или через языковые проекции.You can access the Windows Runtime that way, or you can access it through language projections. Использование проекции позволяет скрывать подробности COM и обеспечить более естественный подход к программированию на используемом языке.A projection hides the COM details, and provides a more natural programming experience for a given language.

Например, в папке "%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt" (при необходимости можно изменить номер версии пакета SDK на используемый) вы обнаружите заголовки проекции языка C++/WinRT.For example, if you look in the folder "%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt" (again, adjust the SDK version number for your case, if necessary), then you'll find the C++/WinRT language projection headers. Для каждого пространства имен Windows существует заголовок, так же, как для каждого пространства имен Windows есть один заголовок ABI.There's a header for each Windows namespace, just like there's one ABI header per Windows namespace. Пример включения одного из заголовков C++/WinRT.Here's an example of including one of the C++/WinRT headers.

#include <winrt/Windows.Foundation.h>

А вот (из этого заголовка) упрощенный эквивалент C++/WinRT типа ABI, который мы только что видели.And, from that header, here (simplified) is the C++/WinRT equivalent of that ABI type we just saw.

namespace winrt::Windows::Foundation
{
    struct Uri : IUriRuntimeClass, ...
    {
        winrt::hstring AbsoluteUri() const { ... }
        ...
    };
}

Интерфейс здесь представляет собой современный стандартный C++.The interface here is modern, standard C++. Он избавляет от необходимости в HRESULT (при необходимости C++/WinRT вызывает исключения).It does away with HRESULTs (C++/WinRT raises exceptions if necessary). Функция доступа возвращает простой строковый объект, который очищается в конце его области видимости.And the accessor function returns a simple string object, which is cleaned up at the end of its scope.

Этот раздел предназначен для тех случаев, когда вам требуется взаимодействие с кодом, который работает на уровне двоичного интерфейса приложения (ABI), и необходимо портировать этот код.This topic is for cases when you want to interop with, or port, code that works at the Application Binary Interface (ABI) layer.

Преобразование в типы ABI и обратно в кодеConverting to and from ABI types in code

Для безопасности и простоты во время преобразования в обоих направлениях можно использовать winrt::com_ptr, com_ptr::as и winrt::Windows::Foundation::IUnknown::as.For safety and simplicity, for conversions in both directions you can simply use winrt::com_ptr, com_ptr::as, and winrt::Windows::Foundation::IUnknown::as. Далее приведен пример кода (на основе шаблона проекта Консольное приложение), в котором также показано, как использовать псевдонимы пространств имен для различных островов, чтобы разрешить конфликты пространств имен между проекцией C++/WinRT и ABI.Here's a code example (based on the Console App project template), which also illustrates how you can use namespace aliases for the different islands to deal with otherwise potential namespace collisions between the C++/WinRT projection and the 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.The implementations of the as functions call QueryInterface. Если вам нужны преобразования более низкого уровня, которые вызывают только AddRef, вы можете использовать вспомогательные функции winrt::copy_to_abi и winrt::copy_from_abi.If you want lower-level conversions that only call AddRef, then you can use the winrt::copy_to_abi and winrt::copy_from_abi helper functions. В следующем примере кода эти преобразования более низкого уровня добавлены к примеру, приведенному выше.This next code example adds these lower-level conversions to the code example above.

Важно!

При взаимодействии с типами ABI очень важно предусмотреть, чтобы используемый тип ABI соответствовал интерфейсу по умолчанию объекта C++/WinRT.When interoperating with ABI types it's critical that the ABI type used corresponds to the default interface of the C++/WinRT object. В противном случае вызовы методов типа ABI фактически приводят к вызову методов в том же слоте vtable в интерфейсе по умолчанию с непредвиденными результатами.Otherwise, invocations of methods on the ABI type will actually end up calling methods in the same vtable slot on the default interface with very unexpected results. Обратите внимание, что использование winrt::copy_to_abi не защищает от этой проблемы во время компиляции, так как используется void* для всех типов ABI и предполагается, что вызывающий объект правильно сопоставил типы.Note that winrt::copy_to_abi does not protect against this at compile time since it uses void* for all ABI types and assumes that the caller has been careful not to mis-match the types. Это позволяет избежать ситуации, когда заголовки C++/WinRT должны ссылаться на заголовки ABI, когда типы ABI могут никогда не использоваться.This is to avoid requiring C++/WinRT headers to reference ABI headers when ABI types may never be used.

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).Here are other similarly low-level conversions techniques but using raw pointers to ABI interface types (those defined by the Windows SDK headers) this time.

    // 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.For the lowest-level conversions, which only copy addresses, you can use the winrt::get_abi, winrt::detach_abi, and winrt::attach_abi helper functions.

WINRT_ASSERT — это макроопределение, которое передается в _ASSERTE.WINRT_ASSERT is a macro definition, and it expands to _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{ static_cast<abi::IStringable*>(winrt::get_abi(uriAsIStringable)) };
    WINRT_ASSERT(non_owning);

    // Avoid interlocks this way.
    owning = static_cast<abi::IStringable*>(winrt::detach_abi(uriAsIStringable));
    WINRT_ASSERT(!uriAsIStringable);
    winrt::attach_abi(uriAsIStringable, owning);
    WINRT_ASSERT(uriAsIStringable);

Функция convert_from_abiconvert_from_abi function

Эта вспомогательная функция преобразует необработанный указатель интерфейса ABI в эквивалентный объект C++/WinRT с минимальными затратами.This helper function converts a raw ABI interface pointer to an equivalent C++/WinRT object, with minimal overhead.

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.The function simply calls QueryInterface to query for the default interface of the requested C++/WinRT type.

Как мы уже выяснили для преобразования объекта C++/WinRT в эквивалентный указатель интерфейса ABI вспомогательной функции не требуется.As we've seen, a helper function is not required to convert from a C++/WinRT object to the equivalent ABI interface pointer. Просто используйте функцию-член winrt::Windows::Foundation::IUnknown::as (или try_as) для запроса нужного интерфейса.Simply use the winrt::Windows::Foundation::IUnknown::as (or try_as) member function to query for the requested interface. Функции as и try_as возвращают объект winrt::com_ptr, в который упакован запрошенный тип ABI.The as and try_as functions return a winrt::com_ptr object wrapping the requested ABI type.

Пример кода, в котором используется convert_from_abiCode example using convert_from_abi

Ниже приведен пример кода, показывающий работу этой вспомогательной функции на практике.Here's a code example showing this helper function in practice.

// 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);
}

Взаимодействие с указателями интерфейса СОМ ABIInteroperating with ABI COM interface pointers

Показанный ниже шаблон вспомогательной функции демонстрирует, как скопировать указатель интерфейса COM ABI заданного типа в его эквивалент C++/WinRT, представляющий собой смарт-указатель типа проекции.The helper function template below illustrates how to copy an ABI COM interface pointer of a given type to its equivalent C++/WinRT projected smart pointer type.

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 (WIL).This next helper function template is equivalent, except that it copies from the smart pointer type from the 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.Also see Consume COM components with C++/WinRT.

Небезопасное взаимодействие с указателями интерфейса СОМ ABIUnsafe interop with ABI COM interface pointers

В таблице ниже показаны (наряду с другими операциями) небезопасные преобразования указателя интерфейса COM ABI заданного типа и его эквивалента C++/WinRT, представляющего собой смарт-указатель типа проекции.The table that follows shows (in addition to other operations) unsafe conversions between an ABI COM interface pointer of a given type and its equivalent C++/WinRT projected smart pointer type. Предполагается, что в коде используются следующие объявления.For the code in the table, assume these declarations.

winrt::Sample s;
ISample* p;

void GetSample(_Out_ ISample** pp);

Также предполагается, что ISample — это интерфейс по умолчанию для Sample.Assume further that ISample is the default interface for Sample.

Сделать это можно во время компиляции этого кода.You can assert that at compile time with this code.

static_assert(std::is_same_v<winrt::default_interface<winrt::Sample>, winrt::ISample>);
ОперацияOperation Способ выполненияHow to do it ПримечанияNotes
Извлечение ISample* из winrt::SampleExtract ISample* from winrt::Sample p = reinterpret_cast<ISample*>(get_abi(s)); s по-прежнему является владельцем объекта.s still owns the object.
Отсоединение ISample* от winrt::SampleDetach ISample* from winrt::Sample p = reinterpret_cast<ISample*>(detach_abi(s)); s больше не является владельцем объекта.s no longer owns the object.
Передача ISample* в новый объект winrt::SampleTransfer ISample* to new winrt::Sample winrt::Sample s{ p, winrt::take_ownership_from_abi }; s становится владельцем объекта.s takes ownership of the object.
Указание ISample* в winrt::SampleSet ISample* into winrt::Sample *put_abi(s) = p; s становится владельцем объекта.s takes ownership of the object. Высвобождается любой объект, которые ранее принадлежал s (происходит при отладке).Any object previously owned by s is leaked (will assert in debug).
Получение ISample* в winrt::SampleReceive ISample* into winrt::Sample GetSample(reinterpret_cast<ISample**>(put_abi(s))); s становится владельцем объекта.s takes ownership of the object. Высвобождается любой объект, которые ранее принадлежал s (происходит при отладке).Any object previously owned by s is leaked (will assert in debug).
Замена ISample* в winrt::SampleReplace ISample* in winrt::Sample attach_abi(s, p); s становится владельцем объекта.s takes ownership of the object. Высвобождается объект, который ранее принадлежал s.The object previously owned by s is freed.
Копирование ISample* в winrt::SampleCopy ISample* to winrt::Sample copy_from_abi(s, p); s создает новую ссылку на объект.s makes a new reference to the object. Высвобождается объект, который ранее принадлежал s.The object previously owned by s is freed.
Копирование winrt::Sample в ISample*Copy winrt::Sample to ISample* copy_to_abi(s, reinterpret_cast<void*&>(p)); p получает копию объекта.p receives a copy of the object. Высвобождается любой объект, который ранее принадлежал p.Any object previously owned by p is leaked.

Взаимодействие со структурой GUID ABIInteroperating with the ABI's GUID struct

GUID теперь проецируется как winrt::guid.GUID is projected as winrt::guid. Для API-интерфейсов, которые вы реализуете, необходимо использовать winrt::guid для параметров GUID.For APIs that you implement, you must use winrt::guid for GUID parameters. В противном случае выполняется автоматическое преобразование winrt::guid и GUID, если добавлено unknwn.h (добавлено неявно с помощью <windows.h> или других файлов заголовков) до добавления заголовков C++/WinRT.Otherwise, there are automatic conversions between winrt::guid and GUID as long as you include unknwn.h (implicitly included by <windows.h> and many other header files) before you include any C++/WinRT headers.

Но вы также можете использовать reinterpret_cast.If you don't do that, then you can hard-reinterpret_cast between them. Предполагается, что в коде используются следующие объявления.For the table that follows, assume these declarations.

winrt::guid winrtguid;
GUID abiguid;
ПреобразованиеConversion С добавлением #include <unknwn.h>With #include <unknwn.h> Без добавления #include <unknwn.h>Without #include <unknwn.h>
Из winrt::guid в GUIDFrom winrt::guid to GUID abiguid = winrtguid; abiguid = reinterpret_cast<GUID&>(winrtguid);
Из GUID в winrt::guidFrom GUID to winrt::guid winrtguid = abiguid; winrtguid = reinterpret_cast<winrt::guid&>(abiguid);

Вы можете создать winrt::guid следующим образом.You can construct a winrt::guid like this.

winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} };

Сведения о том, как создать winrt::guid из строки, см. в разделе make_guid.cpp.For a gist showing how to construct a winrt::guid from a string, see make_guid.cpp.

Взаимодействие с HSTRING ABIInteroperating with the ABI's HSTRING

В следующей таблице показано, как выполнять преобразования winrt::hstring и HSTRING, а также другие операции.The table that follows shows conversions between winrt::hstring and HSTRING, and other operations. Предполагается, что в коде используются следующие объявления.For the code in the table, assume these declarations.

winrt::hstring s;
HSTRING h;

void GetString(_Out_ HSTRING* value);
ОперацияOperation Способ выполненияHow to do it ПримечанияNotes
Извлечение HSTRING из hstringExtract HSTRING from hstring h = static_cast<HSTRING>(get_abi(s)); s по-прежнему является владельцем строки.s still owns the string.
Отсоединение HSTRING от hstringDetach HSTRING from hstring h = reinterpret_cast<HSTRING>(detach_abi(s)); s больше не является владельцем строки.s no longer owns the string.
Указание HSTRING в hstringSet HSTRING into hstring *put_abi(s) = h; s становится владельцем строки.s takes ownership of string. Высвобождается любая строка, которая ранее принадлежала s (происходит при отладке).Any string previously owned by s is leaked (will assert in debug).
Получение HSTRING в hstringReceive HSTRING into hstring GetString(reinterpret_cast<HSTRING*>(put_abi(s))); s становится владельцем строки.s takes ownership of string. Высвобождается любая строка, которая ранее принадлежала s (происходит при отладке).Any string previously owned by s is leaked (will assert in debug).
Замена HSTRING в hstringReplace HSTRING in hstring attach_abi(s, h); s становится владельцем строки.s takes ownership of string. Высвобождается строка, которая ранее принадлежала s.The string previously owned by s is freed.
Копирование HSTRING в hstringCopy HSTRING to hstring copy_from_abi(s, h); s создает закрытую копию строки.s makes a private copy of the string. Высвобождается строка, которая ранее принадлежала s.The string previously owned by s is freed.
Копирование hstring в HSTRINGCopy hstring to HSTRING copy_to_abi(s, reinterpret_cast<void*&>(h)); h получает копию строки.h receives a copy of the string. Высвобождается любая строка, которая ранее принадлежала h.Any string previously owned by h is leaked.

Важные APIImportant APIs