Pindah ke C++/WinRT dari WRL

Topik ini menjelaskan cara mem-port kode Windows Runtime C++ Template Library (WRL) ke padanannya di C++/WinRT.

Langkah pertama dalam porting ke C++/WinRT adalah menambahkan dukungan C++/WinRT secara manual ke proyek Anda (lihat dukungan Visual Studio untuk C++/WinRT). Untuk melakukannya, instal paket NuGet Microsoft.Windows.CppWinRT ke proyek Anda. Buka project di Visual Studio, klik Project>Kelola Paket NuGet...>Telusuri, ketik, atau tempel Microsoft.Windows. CppWinRT di kotak pencarian, pilih item di hasil pencarian, lalu klik Instal untuk menginstal paket untuk project tersebut. Salah satu efek dari perubahan tersebut adalah bahwa dukungan untuk C++/CX dinonaktifkan dalam proyek. Jika Anda menggunakan C++/CX dalam proyek, maka Anda dapat membiarkan dukungan dinonaktifkan dan memperbarui kode C++/CX Anda ke C++/WinRT juga (lihat Pindah ke C++/WinRT dari C++/CX). Atau Anda dapat mengaktifkan kembali dukungan (di properti proyek, C/C++>General>Consume Windows Runtime Extension>Yes (/ZW)), dan fokus pertama pada porting kode WRL Anda. Kode C++/CX dan C++/WinRT dapat hidup berdampingan dalam proyek yang sama, dengan pengecualian dukungan kompilator XAML, dan komponen Windows Runtime (lihat Pindah ke C++/WinRT dari C++/CX).

Atur properti proyekVersi Platform Target> ke 10.0.17134.0 (Windows 10, versi 1803) atau yang lebih tinggi.

Dalam file header yang telah dikommpilasikan sebelumnya (biasanya pch.h), sertakan winrt/base.h.

#include <winrt/base.h>

Jika Anda menyertakan header C++/WinRT yang diproyeksikan Windows API (misalnya, winrt/Windows.Foundation.h), maka Anda tidak perlu secara eksplisit menyertakan winrt/base.h seperti ini karena akan disertakan secara otomatis untuk Anda.

Porting WRL COM smart pointers (Microsoft::WRL::ComPtr)

Port kode apa pun yang menggunakan Microsoft::WRL::ComPtr<T> untuk menggunakan winrt::com_ptr<T>. Berikut adalah contoh kode sebelum dan sesudahnya. Dalam versi setelah, fungsi anggota com_ptr::put mengambil pointer mentah yang mendasarinya agar dapat diatur.

ComPtr<IDXGIAdapter1> previousDefaultAdapter;
DX::ThrowIfFailed(m_dxgiFactory->EnumAdapters1(0, &previousDefaultAdapter));
winrt::com_ptr<IDXGIAdapter1> previousDefaultAdapter;
winrt::check_hresult(m_dxgiFactory->EnumAdapters1(0, previousDefaultAdapter.put()));

Important

Jika Anda memiliki winrt::com_ptr yang sudah terisi (pointer mentah internalnya sudah menunjuk ke suatu target) dan Anda ingin mengarahkannya kembali agar menunjuk ke objek lain, maka Anda harus terlebih dahulu menetapkan nullptr ke variabel tersebut—seperti yang ditunjukkan pada contoh kode di bawah ini. Jika tidak, com_ptr yang sudah terisi akan memberi tahu Anda tentang masalah tersebut (saat Anda memanggil com_ptr::put atau com_ptr::put_void) dengan memunculkan asersi bahwa pointer internalnya tidak null.

winrt::com_ptr<IDXGISwapChain1> m_pDXGISwapChain1;
...
// We execute the code below each time the window size changes.
m_pDXGISwapChain1 = nullptr; // Important because we're about to re-seat 
winrt::check_hresult(
    m_pDxgiFactory->CreateSwapChainForHwnd(
        m_pCommandQueue.get(), // For Direct3D 12, this is a pointer to a direct command queue, and not to the device.
        m_hWnd,
        &swapChainDesc,
        nullptr,
        nullptr,
        m_pDXGISwapChain1.put())
);

Dalam contoh berikut (dalam versi setelah), fungsi anggota com_ptr::put_void mengambil pointer mentah yang mendasarinya sebagai pointer ke pointer ke void.

ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
{
    debugController->EnableDebugLayer();
}
winrt::com_ptr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(__uuidof(debugController), debugController.put_void())))
{
    debugController->EnableDebugLayer();
}

Ganti ComPtr::Get dengan com_ptr::get.

m_d3dDevice->CreateDepthStencilView(m_depthStencil.Get(), &dsvDesc, m_dsvHeap->GetCPUDescriptorHandleForHeapStart());
m_d3dDevice->CreateDepthStencilView(m_depthStencil.get(), &dsvDesc, m_dsvHeap->GetCPUDescriptorHandleForHeapStart());

Ketika Anda ingin meneruskan penunjuk mentah yang mendasar ke fungsi yang mengharapkan pointer ke IUnknown, gunakan fungsi bebas winrt::get_unknown , seperti yang ditunjukkan dalam contoh berikutnya ini.

ComPtr<IDXGISwapChain1> swapChain;
DX::ThrowIfFailed(
    m_dxgiFactory->CreateSwapChainForCoreWindow(
        m_commandQueue.Get(),
        reinterpret_cast<IUnknown*>(m_window.Get()),
        &swapChainDesc,
        nullptr,
        &swapChain
    )
);
// Note: In a WinUI 3 desktop app, use CreateSwapChainForHwnd instead of
// CreateSwapChainForCoreWindow, since WinUI 3 doesn't use CoreWindow.
winrt::agile_ref<winrt::Windows::UI::Core::CoreWindow> m_window; 
winrt::com_ptr<IDXGISwapChain1> swapChain;
winrt::check_hresult(
    m_dxgiFactory->CreateSwapChainForCoreWindow(
        m_commandQueue.get(),
        winrt::get_unknown(m_window.get()),
        &swapChainDesc,
        nullptr,
        swapChain.put()
    )
);

Memigrasikan modul WRL (Microsoft::WRL::Module)

Bagian ini berkaitan dengan kode porting yang menggunakan jenis Microsoft::WRL::Module.

Anda dapat menambahkan kode C++/WinRT secara bertahap ke proyek yang ada yang menggunakan WRL untuk mengimplementasikan komponen, dan kelas WRL yang ada akan terus didukung. Bagian ini memperlihatkan caranya.

Jika Anda membuat jenis proyek komponen Windows Runtime baru (C++/WinRT) di Visual Studio, dan membangun, maka file Generated Files\module.g.cpp dibuat untuk Anda. File tersebut berisi definisi dua fungsi C++/WinRT yang berguna (tercantum di bawah), yang dapat Anda salin dan tambahkan ke proyek Anda. Fungsi-fungsi tersebut adalah WINRT_CanUnloadNow dan WINRT_GetActivationFactory dan, seperti yang Anda lihat, keduanya memanggil WRL secara bersyarat untuk mendukung Anda pada tahap porting apa pun yang sedang Anda jalani.

HRESULT WINRT_CALL WINRT_CanUnloadNow()
{
#ifdef _WRL_MODULE_H_
    if (!::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().Terminate())
    {
        return S_FALSE;
    }
#endif

    if (winrt::get_module_lock())
    {
        return S_FALSE;
    }

    winrt::clear_factory_cache();
    return S_OK;
}

HRESULT WINRT_CALL WINRT_GetActivationFactory(HSTRING classId, void** factory)
{
    try
    {
        *factory = nullptr;
        wchar_t const* const name = WINRT_WindowsGetStringRawBuffer(classId, nullptr);

        if (0 == wcscmp(name, L"MoveFromWRLTest.Class"))
        {
            *factory = winrt::detach_abi(winrt::make<winrt::MoveFromWRLTest::factory_implementation::Class>());
            return S_OK;
        }

#ifdef _WRL_MODULE_H_
        return ::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().GetActivationFactory(classId, reinterpret_cast<::IActivationFactory**>(factory));
#else
        return winrt::hresult_class_not_available().to_abi();
#endif
    }
    catch (...) { return winrt::to_hresult(); }
}

Setelah Anda memiliki fungsi-fungsi ini dalam proyek Anda, alih-alih memanggil Modul::GetActivationFactory secara langsung, panggil WINRT_GetActivationFactory (yang memanggil fungsi WRL secara internal). Berikut adalah contoh kode sebelum dan sesudahnya.

HRESULT WINAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _Out_ ::IActivationFactory **factory)
{
    auto & module = Microsoft::WRL::Module<Microsoft::WRL::InProc>::GetModule();
    return module.GetActivationFactory(activatableClassId, factory);
}
HRESULT __stdcall WINRT_GetActivationFactory(HSTRING activatableClassId, void** factory);
HRESULT WINAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _Out_ ::IActivationFactory **factory)
{
    return WINRT_GetActivationFactory(activatableClassId, reinterpret_cast<void**>(factory));
}

Alih-alih memanggil Modul::Hentikan secara langsung, panggil WINRT_CanUnloadNow (yang memanggil fungsi WRL secara internal). Berikut adalah contoh kode sebelum dan sesudahnya.

HRESULT __stdcall DllCanUnloadNow(void)
{
    auto &module = Microsoft::WRL::Module<Microsoft::WRL::InProc>::GetModule();
    HRESULT hr = (module.Terminate() ? S_OK : S_FALSE);
    if (hr == S_OK)
    {
        hr = ...
    }
    return hr;
}
HRESULT __stdcall WINRT_CanUnloadNow();
HRESULT __stdcall DllCanUnloadNow(void)
{
    HRESULT hr = WINRT_CanUnloadNow();
    if (hr == S_OK)
    {
        hr = ...
    }
    return hr;
}

Melakukan porting pembungkus Microsoft::WRL::Wrappers

Bagian ini berkaitan dengan kode porting yang menggunakan pembungkus Microsoft::WRL::Wrappers.

Seperti yang dapat Anda lihat pada tabel di bawah ini, untuk mengganti fungsi pembantu threading, kami sarankan agar Anda menggunakan pustaka dukungan thread Standard C++. Pemetaan satu-ke-satu dari wrapper WRL dapat menyesatkan, karena pilihan yang Anda ambil bergantung pada kebutuhan Anda. Selain itu, beberapa tipe yang mungkin tampak sebagai padanan yang jelas baru ada dalam standar C++20, jadi tipe-tipe tersebut tidak praktis digunakan jika Anda belum melakukan upgrade.

Type Catatan porting
Kelas CriticalSection Gunakan pustaka dukungan utas
Kelas peristiwa (WRL) Gunakan templat winrt::event struct
Kelas HandleT Gunakan struct winrt::handle atau struct winrt::file_handle
Kelas HString Gunakan struct winrt::hstring
Kelas HStringReference Tidak ada penggantian, karena C++/WinRT menangani ini secara internal dengan cara yang sama efisiennya dengan HStringReference dengan keuntungan yang tidak perlu Anda pikirkan.
Kelas mutex Gunakan pustaka dukungan utas
Kelas RoInitializeWrapper Gunakan winrt::init_apartment dan winrt::uninit_apartment; atau tulis pembungkus sepele Anda sendiri di sekitar CoInitializeEx dan CoUninitialize.
Kelas Semaphore Gunakan pustaka pendukung thread
Kelas SRWLock Gunakan pustaka dukungan utas

API penting