Interop antara C++/WinRT dan ABI

Topik ini menunjukkan cara mengonversi antara antarmuka biner aplikasi SDK (ABI) dan objek C++/WinRT . Anda dapat menggunakan teknik ini untuk menginteropsi antara kode yang menggunakan dua cara pemrograman ini dengan Windows Runtime, atau Anda dapat menggunakannya saat Anda secara bertahap memindahkan kode Anda dari ABI ke C++/WinRT.

Secara umum, C++/WinRT mengekspos jenis ABI sebagai batal*, sehingga Anda tidak perlu menyertakan file header platform.

Catatan

Dalam contoh kode, kita menggunakan reinterpret_cast (bukan static_cast) dalam upaya untuk men-telegraf apa yang secara inheren tidak aman.

Apa itu Windows Runtime ABI, dan apa itu jenis ABI?

Kelas Windows Runtime (kelas runtime) benar-benar abstraksi. Abstraksi ini mendefinisikan antarmuka biner (Antarmuka Biner Aplikasi, atau ABI) yang memungkinkan berbagai bahasa pemrograman berinteraksi dengan objek. Terlepas dari bahasa pemrograman, interaksi kode klien dengan objek Windows Runtime terjadi pada tingkat terendah, dengan konstruksi bahasa klien diterjemahkan ke dalam panggilan ke ABI objek.

Header Windows SDK di folder "%WindowsSdkDir%Include\10.0.17134.0\winrt" (sesuaikan nomor versi SDK untuk kasus Anda, jika perlu), adalah file header Windows Runtime ABI. Mereka diproduksi oleh kompilator MIDL. Berikut adalah contoh menyertakan salah satu header ini.

#include <windows.foundation.h>

Dan berikut adalah contoh yang disederhanakan dari salah satu jenis ABI yang akan Anda temukan di header SDK tertentu. Perhatikan namespace layanan ABI; Windows::Foundation, dan semua namespace Windows lainnya, dideklarasikan oleh header SDK dalam namespace layanan 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 adalah antarmuka COM. Tetapi lebih dari itu—karena basisnya adalah IInspectable—IUriRuntimeClass adalah antarmuka Windows Runtime. Perhatikan jenis pengembalian HRESULT, daripada menaikkan pengecualian. Dan penggunaan artefak seperti handel HSTRING (adalah praktik yang baik untuk mengatur handel itu kembali ke nullptr ketika Anda selesai dengannya). Ini memberikan rasa seperti apa Windows Runtime pada tingkat biner aplikasi; dengan kata lain, pada tingkat pemrograman COM.

Windows Runtime didasarkan pada API Model Objek Komponen (COM). Anda dapat mengakses Windows Runtime dengan cara itu, atau Anda dapat mengaksesnya melalui proyeksi bahasa. Proyeksi menyembunyikan detail COM, dan memberikan pengalaman pemrograman yang lebih alami untuk bahasa tertentu.

Misalnya, jika Anda melihat di folder "%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt" (sekali lagi, sesuaikan nomor versi SDK untuk kasus Anda, jika perlu), maka Anda akan menemukan header proyeksi bahasa C++/WinRT. Ada header untuk setiap namespace Windows, sama seperti ada satu header ABI per namespace Windows. Berikut adalah contoh menyertakan salah satu header C++/WinRT.

#include <winrt/Windows.Foundation.h>

Dan, dari header itu, di sini (disederhanakan) adalah C++/WinRT yang setara dengan jenis ABI yang baru saja kita lihat.

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

Antarmuka di sini modern, standar C++. Ini tidak jauh dengan HRESULTs (C++/WinRT menimbulkan pengecualian jika perlu). Dan fungsi aksesor mengembalikan objek string sederhana, yang dibersihkan di akhir cakupannya.

Topik ini untuk kasus ketika Anda ingin melakukan interop dengan, atau port, kode yang berfungsi di lapisan Application Binary Interface (ABI).

Mengonversi ke dan dari jenis ABI dalam kode

Untuk keamanan dan kesederhanaan, untuk konversi di kedua arah Anda cukup menggunakan winrt::com_ptr, com_ptr::as, dan winrt::Windows::Foundation::IUnknown::as. Berikut adalah contoh kode (berdasarkan templat proyek Aplikasi Konsol), yang juga menggambarkan bagaimana Anda dapat menggunakan alias namespace layanan untuk berbagai pulau untuk menangani potensi tabrakan namespace layanan antara proyeksi C++/WinRT dan 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>() };
}

Implementasi fungsi as memanggil QueryInterface. Jika Anda menginginkan konversi tingkat bawah yang hanya memanggil AddRef, maka Anda dapat menggunakan fungsi winrt::copy_to_abi dan winrt::copy_from_abi helper. Contoh kode berikutnya ini menambahkan konversi tingkat bawah ini ke contoh kode di atas.

Penting

Saat mengoperasikan dengan jenis ABI, sangat penting bahwa jenis ABI yang digunakan sesuai dengan antarmuka default objek C++/WinRT. Jika tidak, pemanggilan metode pada jenis ABI sebenarnya akan berakhir dengan metode panggilan di slot vtable yang sama pada antarmuka default dengan hasil yang sangat tidak terduga. Perhatikan bahwa winrt::copy_to_abi tidak melindungi ini pada waktu kompilasi karena menggunakan void* untuk semua jenis ABI dan mengasumsikan bahwa penelepon telah berhati-hati untuk tidak salah mencocokkan jenis. Ini untuk menghindari memerlukan header C++/WinRT untuk mereferensikan header ABI ketika jenis ABI mungkin tidak pernah digunakan.

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

Berikut adalah teknik konversi tingkat rendah lainnya yang serupa tetapi menggunakan pointer mentah ke jenis antarmuka ABI (yang ditentukan oleh header Windows SDK) kali ini.

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

Untuk konversi tingkat terendah, yang hanya menyalin alamat, Anda dapat menggunakan fungsi winrt::get_abi, winrt::d etach_abi, dan winrt::attach_abi helper.

WINRT_ASSERT adalah definisi makro, dan meluas ke _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);

fungsi convert_from_abi

Fungsi pembantu ini mengonversi penunjuk antarmuka ABI mentah ke objek C++/WinRT yang setara, dengan overhead minimal.

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

Fungsi ini hanya memanggil QueryInterface untuk mengkueri antarmuka default jenis C++/WinRT yang diminta.

Seperti yang telah kita lihat, fungsi pembantu tidak diperlukan untuk mengonversi dari objek C++/WinRT ke penunjuk antarmuka ABI yang setara. Cukup gunakan fungsi anggota winrt::Windows::Foundation::IUnknown::as (atau try_as) untuk mengkueri antarmuka yang diminta. Fungsi as dan try_as mengembalikan objek winrt::com_ptr yang membungkus jenis ABI yang diminta.

Contoh kode menggunakan convert_from_abi

Berikut adalah contoh kode yang menunjukkan fungsi pembantu ini dalam praktiknya.

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

Mengoperasikan dengan penunjuk antarmuka ABI COM

Templat fungsi pembantu di bawah ini menggambarkan cara menyalin penunjuk antarmuka ABI COM dari jenis tertentu ke jenis penunjuk pintar yang diproyeksikan C++/WinRT yang setara.

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

Templat fungsi pembantu berikutnya ini setara, kecuali bahwa templat ini menyalin dari jenis penunjuk pintar dari 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;
}

Lihat juga Mengonsumsi komponen COM dengan C++/WinRT.

Interop tidak aman dengan penunjuk antarmuka ABI COM

Tabel berikut menunjukkan (selain operasi lain) konversi tidak aman antara penunjuk antarmuka ABI COM dari jenis tertentu dan jenis pointer pintar yang diproyeksikan C++/WinRT yang setara. Untuk kode dalam tabel, asumsikan deklarasi ini.

winrt::Sample s;
ISample* p;

void GetSample(_Out_ ISample** pp);

Asumsikan lebih lanjut bahwa ISample adalah antarmuka default untuk Sampel.

Anda dapat menegaskan bahwa pada waktu kompilasi dengan kode ini.

static_assert(std::is_same_v<winrt::default_interface<winrt::Sample>, winrt::ISample>);
Operasi Cara melakukannya Catatan
Ekstrak ISample* dari winrt::Sample p = reinterpret_cast<ISample*>(get_abi(s)); s masih memiliki objek .
Lepaskan ISample* dari winrt::Sample p = reinterpret_cast<ISample*>(detach_abi(s)); s tidak lagi memiliki objek.
Transfer ISample* ke winrt baru ::Sample winrt::Sample s{ p, winrt::take_ownership_from_abi }; s mengambil kepemilikan objek.
Atur ISample* ke winrt ::Sample *put_abi(s) = p; s mengambil kepemilikan objek. Objek apa pun yang sebelumnya dimiliki oleh s bocor (akan menegaskan dalam debug).
Terima ISample* ke winrt ::Sample GetSample(reinterpret_cast<ISample**>(put_abi(s))); s mengambil kepemilikan objek. Objek apa pun yang sebelumnya dimiliki oleh s bocor (akan menegaskan dalam debug).
Ganti ISample* di winrt::Sample attach_abi(s, p); s mengambil kepemilikan objek. Objek yang sebelumnya dimiliki oleh s dibeberkan.
Salin ISample* ke winrt::Sample copy_from_abi(s, p); s membuat referensi baru ke objek. Objek yang sebelumnya dimiliki oleh s dibeberkan.
Salin winrt::Sample to ISample* copy_to_abi(s, reinterpret_cast<void*&>(p)); p menerima salinan objek. Objek apa pun yang sebelumnya dimiliki oleh p bocor.

Mengoperasikan dengan struktur GUID ABI

GUID (/previous-versions/aa373931(v%3Dvs.80)) diproyeksikan sebagai winrt::guid. Untuk API yang Anda terapkan, Anda harus menggunakan winrt::guid untuk parameter GUID. Jika tidak, ada konversi otomatis antara winrt::guid dan GUID selama Anda menyertakan unknwn.h (secara implisit disertakan oleh <windows.h> dan banyak file header lainnya) sebelum Anda menyertakan header C++/WinRT apa pun.

Jika Anda tidak melakukan itu, maka Anda dapat keras -reinterpret_cast di antara mereka. Untuk tabel berikut, asumsikan deklarasi ini.

winrt::guid winrtguid;
GUID abiguid;
Konversi Dengan #include <unknwn.h> Tanpa #include <unknwn.h>
Dari winrt::guid ke GUID abiguid = winrtguid; abiguid = reinterpret_cast<GUID&>(winrtguid);
Dari GUID ke winrt::guid winrtguid = abiguid; winrtguid = reinterpret_cast<winrt::guid&>(abiguid);

Anda dapat membuat winrt::guid seperti ini.

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

Untuk inti yang menunjukkan cara membuat winrt::guid dari string, lihat make_guid.cpp.

Mengoperasikan dengan HSTRING ABI

Tabel berikut menunjukkan konversi antara winrt::hstring dan HSTRING, dan operasi lainnya. Untuk kode dalam tabel, asumsikan deklarasi ini.

winrt::hstring s;
HSTRING h;

void GetString(_Out_ HSTRING* value);
Operasi Cara melakukannya Catatan
Ekstrak HSTRING dari hstring h = reinterpret_cast<HSTRING>(get_abi(s)); s masih memiliki string.
Lepaskan HSTRING dari hstring h = reinterpret_cast<HSTRING>(detach_abi(s)); s tidak lagi memiliki string.
Atur HSTRING ke hstring *put_abi(s) = h; s mengambil kepemilikan string. Setiap string yang sebelumnya dimiliki oleh s bocor (akan menegaskan dalam debug).
Menerima HSTRING ke hstring GetString(reinterpret_cast<HSTRING*>(put_abi(s))); s mengambil kepemilikan string. Setiap string yang sebelumnya dimiliki oleh s bocor (akan menegaskan dalam debug).
Ganti HSTRING dalam hstring attach_abi(s, h); s mengambil kepemilikan string. String yang sebelumnya dimiliki oleh s dibeberkan.
Salin HSTRING ke hstring copy_from_abi(s, h); s membuat salinan privat string. String yang sebelumnya dimiliki oleh s dibeberkan.
Salin hstring ke HSTRING copy_to_abi(s, reinterpret_cast<void*&>(h)); h menerima salinan string. Setiap string yang sebelumnya dimiliki oleh h bocor.

Selain itu, pembantu string Windows Implementation Libraries (WIL) melakukan manipulasi string dasar. Untuk menggunakan pembantu string WIL, sertakan <wil/resource.h>, dan lihat tabel di bawah ini. Ikuti tautan dalam tabel untuk detail selengkapnya.

Operasi Pembantu string WIL untuk info lebih lanjut
Berikan penunjuk string Unicode atau ANSI mentah dan panjang opsional; mendapatkan pembungkus unique_any khusus yang sesuai wil::make_something_string
Buka bungkus objek pintar hingga penunjuk string Unicode mentah yang dihentikan null ditemukan wil::str_raw_ptr
Dapatkan string yang dibungkus oleh objek penunjuk cerdas; atau string L"" kosong jika penunjuk pintar kosong wil::string_get_not_null
Menggabungkan sejumlah string wil::str_concat
Mendapatkan string dari string format gaya printf dan daftar parameter yang sesuai wil::str_printf

API penting