C++/WinRT と ABI 間の相互運用Interop 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 ランタイムでこれら 2 つの手法によるプログラミングを使用するコード間を相互運用するか、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.

Windows ランタイム ABI および 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.

フォルダー "%WindowsSdkDir%Include\10.0.17134.0\winrt" (必要に応じて、状況に合った SDK バージョン番号に調整) 内の Windows SDK ヘッダーは、Windows ランタイム ABI ヘッダー ファイルです。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>

次に、特定の SDK ヘッダー内にあるいずれかの ABI 型の例を簡単に示します。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 名前空間は、ABI 名前空間内で SDK ヘッダーによって宣言される点に注意してください。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). これにより、アプリケーション バイナリ レベル、つまり、COM プログラミング レベルで Windows ランタイムの内容を把握できます。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 ランタイムはコンポーネント オブジェクト モデル (COM) API に基づいています。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 名前空間ごとに 1 つの ABI ヘッダーがあるように、Windows 名前空間それぞれにヘッダーがあります。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>

このヘッダーから、上記の ABI 型に相当する C++/WinRT を簡略化して次に示します。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_ptrcom_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. 次に、(Console App プロジェクト テンプレートに基づく) コード例を示します 。このコード例では、異なる断片の名前空間のエイリアスを使用して、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_abiwinrt::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 では、すべての ABI 型に対して void* が使用され、呼び出し元が一致しない型を誤って使用することはないものと想定しているため、コンパイル時にこれを防ぐことはできないことにご注意ください。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. これは、ABI 型がまったく使用されない可能性があるのに、C++/WinRT ヘッダーに 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_abiwinrt::detach_abiwinrt::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_abi 関数convert_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 関数は、要求された ABI 型をラップする winrt::com_ptr オブジェクトを返します。The as and try_as functions return a winrt::com_ptr object wrapping the requested ABI type.

convert_from_abi を使用したコード例Code 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);
}

ABI の COM インターフェイス ポインターとの相互運用Interoperating with ABI COM interface pointers

次のヘルパー関数テンプレートでは、特定の型の ABI COM インターフェイス ポインターを、それと同等の 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;
}

C++/WinRT での COM コンポーネントの使用」も参照してください。Also see Consume COM components with C++/WinRT.

ABI の COM インターフェイス ポインターとの安全でない相互運用Unsafe interop with ABI COM interface pointers

この後の表では、(他の操作に加えて) 特定の型の ABI COM インターフェイス ポインターと、それに相当する 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);

さらに、ISampleSample の既定のインターフェイスとします。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
winrt::Sample から ISample* を抽出するExtract ISample* from winrt::Sample p = reinterpret_cast<ISample*>(get_abi(s)); s はオブジェクトをまだ所有しています。s still owns the object.
winrt::Sample から ISample* をデタッチするDetach ISample* from winrt::Sample p = reinterpret_cast<ISample*>(detach_abi(s)); s はオブジェクトを所有しなくなります。s no longer owns the object.
ISample* を新しい winrt::Sample に転送するTransfer ISample* to new winrt::Sample winrt::Sample s{ p, winrt::take_ownership_from_abi }; s はオブジェクトの所有権を取得します。s takes ownership of the object.
ISample*winrt::Sample に設定するSet 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::Sample に受け取るReceive 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).
winrt::Sample 内の ISample* を置き換えるReplace 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::Sample にコピーするCopy 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::SampleISample* にコピーするCopy winrt::Sample to ISample* copy_to_abi(s, reinterpret_cast<void*&>(p)); p はオブジェクトのコピーを受け取ります。p receives a copy of the object. s によって前に所有されていたすべてのオブジェクトはリークされます。Any object previously owned by p is leaked.

ABI の GUID 構造体との相互運用Interoperating with the ABI's GUID struct

GUIDwinrt::guid として投影されます。GUID is projected as winrt::guid. 実装する API の場合、GUID パラメーターに対して winrt::guid を使用する必要があります。For APIs that you implement, you must use winrt::guid for GUID parameters. それ以外の場合は、C++/WinRT のヘッダーをインクルードする前に、unknwn.h をインクルードしている限り (<windows.h> および他の多くのヘッダー ファイルによって暗黙的にインクルードされます)、winrt::guidGUID の間はは自動的に変換されます。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 を構築する方法を示す gist については、make_guid.cpp のページを参照してください。For a gist showing how to construct a winrt::guid from a string, see make_guid.cpp.

ABI の HSTRING との相互運用Interoperating with the ABI's HSTRING

次の表では、winrt::hstringHSTRING の間の変換、およびその他の操作を示します。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 から HSTRING を抽出するExtract HSTRING from hstring h = static_cast<HSTRING>(get_abi(s)); s は文字列をまだ所有しています。s still owns the string.
hstring から HSTRING をデタッチするDetach HSTRING from hstring h = reinterpret_cast<HSTRING>(detach_abi(s)); s は文字列を所有しなくなります。s no longer owns the string.
HSTRINGhstring に設定するSet 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).
HSTRINGhstring に受け取るReceive 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 内の HSTRING を置き換えるReplace HSTRING in hstring attach_abi(s, h); s は文字列の所有権を取得します。s takes ownership of string. s によって前に所有されていた文字列は解放されます。The string previously owned by s is freed.
HSTRINGhstring にコピーするCopy 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.
hstringHSTRING にコピーするCopy 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