Interoperabilitas 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 menyajikan tipe ABI sebagai void*, sehingga Anda tidak perlu menyertakan berkas header platform.

Note

Dalam contoh kode, kami menggunakan reinterpret_cast (bukan static_cast) untuk menandai bahwa itu adalah cast yang pada dasarnya tidak aman.

Apa itu ABI Windows Runtime, 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 dalam folder "%WindowsSdkDir%Include\10.0.17134.0\winrt" (sesuaikan nomor versi SDK sesuai kebutuhan Anda, jika perlu) merupakan file header ABI Windows Runtime. 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 tipe pengembalian HRESULT, bukan pemunculan pengecualian. Dan penggunaan artefak seperti handle HSTRING (sebaiknya atur kembali handle tersebut ke nullptr setelah selesai menggunakannya). Ini memberikan gambaran sekilas tentang seperti apa tampilan 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 tersebut, berikut adalah padanan C++/WinRT (yang disederhanakan) untuk tipe ABI yang baru saja kita lihat.

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

Antarmuka di sini modern, standar C++. Ini menghilangkan HRESULT (C++/WinRT melempar pengecualian jika diperlukan). Dan fungsi aksesor mengembalikan objek string sederhana, yang dibersihkan di akhir cakupannya.

Topik ini ditujukan untuk kasus saat Anda ingin berinteroperasi dengan, atau mem-porting, kode yang bekerja pada lapisan Application Binary Interface (ABI).

Mengonversi ke dan dari tipe 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 mengilustrasikan bagaimana Anda dapat menggunakan alias namespace untuk berbagai island guna mengatasi potensi bentrokan namespace 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 pembantu winrt::copy_to_abi dan winrt::copy_from_abi . Contoh kode berikutnya ini menambahkan konversi tingkat bawah ini ke contoh kode di atas.

Important

Saat mengoperasikan dengan jenis ABI, sangat penting bahwa jenis ABI yang digunakan sesuai dengan antarmuka default objek C++/WinRT. Jika tidak, pemanggilan metode pada tipe ABI justru akan berakhir dengan memanggil metode 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 pemanggil telah berhati-hati untuk tidak salah mencocokkan jenis. Hal ini untuk menghindari keharusan header C++/WinRT mereferensikan header ABI ketika tipe ABI mungkin tidak akan 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 SDK Windows) 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 pembantu winrt::get_abi, winrt::detach_abi, dan winrt::attach_abi.

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

Berinteroperasi dengan pointer antarmuka ABI COM

Template fungsi pembantu di bawah ini menunjukkan cara menyalin pointer antarmuka ABI COM dari tipe tertentu ke tipe smart pointer proyeksi C++/WinRT yang ekuivalen.

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 berikut ini setara, tetapi menyalin dari tipe smart pointer 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 menampilkan (selain operasi lain) konversi yang tidak aman antara pointer antarmuka ABI COM dari tipe tertentu dan tipe smart pointer proyeksi C++/WinRT padanannya. 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 memastikannya saat kompilasi dengan kode ini.

static_assert(std::is_same_v<winrt::default_interface<winrt::Sample>, winrt::ISample>);
Operation Cara melakukannya Notes
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::Sample baru 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 yang sebelumnya dimiliki oleh s akan bocor (akan memicu assert saat mode 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 akan mengalami kebocoran (akan memicu assert dalam mode debug).
Ganti ISample* di winrt::Sample attach_abi(s, p); s mengambil kepemilikan objek. Objek yang sebelumnya dimiliki oleh s dibebaskan.
Salin ISample* ke winrt::Sample copy_from_abi(s, p); s membuat referensi baru ke objek. Objek yang sebelumnya dimiliki oleh s dibebaskan.
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 terekspos.

Berinteraksi 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 melakukannya, Anda dapat melakukan hard-reinterpret_cast di antara keduanya. Untuk tabel berikut, asumsikan deklarasi ini.

winrt::guid winrtguid;
GUID abiguid;
Conversion 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.

Berinteroperasi 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);
Operation Cara melakukannya Notes
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 alih kepemilikan string. String apa pun yang sebelumnya dimiliki s akan bocor (akan memicu assert dalam mode debug).
Menerima HSTRING ke hstring GetString(reinterpret_cast<HSTRING*>(put_abi(s))); s mengambil alih kepemilikan string. Setiap string yang sebelumnya dimiliki oleh s akan bocor (akan memicu assertion dalam mode debug).
Ganti HSTRING dalam hstring attach_abi(s, h); s mengambil alih kepemilikan string. String yang sebelumnya dimiliki oleh s dibebaskan.
Salin HSTRING ke hstring copy_from_abi(s, h); s membuat salinan pribadi dari string tersebut. String yang sebelumnya dimiliki oleh s dibebaskan.
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.

Operation Utilitas string WIL untuk informasi lebih lanjut
Berikan penunjuk string Unicode atau ANSI mentah dan panjang opsional; dapatkan pembungkus unique_any khusus yang sesuai wil::make_something_string
Urai objek pintar hingga ditemukan penunjuk ke string Unicode mentah yang diakhiri null wil::str_raw_ptr
Peroleh string yang dibungkus oleh objek smart pointer; atau string kosong L"" jika smart pointer kosong wil::string_get_not_null
Menggabungkan sejumlah string wil::str_concat
Memperoleh string dari string format bergaya printf dan daftar parameter yang sesuai wil::str_printf

API penting