Pindah ke C++/WinRT dari WRL

Topik ini menunjukkan cara memindahkan kode Windows Runtime C++ Template Library (WRL) ke yang setara 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 dalam proyek Anda. Buka proyek di Visual Studio, klik Kelola Proyek>Paket NuGet...>Telusuri, ketik, atau tempel Microsoft.Windows.CppWinRT di kotak pencarian, pilih item di hasil pencarian, lalu klik Instal untuk menginstal paket untuk proyek 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>Gunakan Ekstensi>Runtime Windows Ya (/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 proyek Versi Platform Target Umum>ke 10.0.17134.0 (Windows 10, versi 1803) atau lebih tinggi.

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

#include <winrt/base.h>

Jika Anda menyertakan header WINDOWS API yang diproyeksikan C++/WinRT (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::p ut mengambil penunjuk mentah yang mendasar sehingga 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()));

Penting

Jika Anda memiliki winrt::com_ptr yang sudah duduk (pointer mentah internalnya sudah memiliki target) dan Anda ingin menempatkannya kembali untuk menunjuk ke objek yang berbeda, maka Anda harus menetapkannya nullptr terlebih dahulu—seperti yang ditunjukkan pada contoh kode di bawah ini. Jika tidak, maka com_ptr yang sudah duduk akan menarik masalah ke perhatian Anda (ketika Anda memanggil com_ptr::p ut atau com_ptr::p ut_void) dengan menegaskan bahwa penunjuk 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 berikutnya ini (dalam versi setelah), fungsi anggota com_ptr::p ut_void mengambil pointer mentah yang mendasar sebagai penunjuk ke penunjuk ke batal.

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

Porting 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 Windows Runtime Component (C++/WinRT) baru 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 tersebut WINRT_CanUnloadNow dan WINRT_GetActivationFactory dan, seperti yang Anda lihat, mereka secara kondisional memanggil WRL untuk mendukung Anda tahap port apa pun yang Anda gunakan.

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

Porting Microsoft::WRL::Wrappers wrappers

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

Seperti yang Anda lihat dalam tabel di bawah ini, untuk mengganti pembantu utas, kami sarankan Anda menggunakan pustaka dukungan utas C++ Standar. Pemetaan satu-ke-satu dari pembungkus WRL bisa menyesatkan, karena pilihan Anda tergantung pada kebutuhan Anda. Selain itu, beberapa jenis yang mungkin tampak jelas pemetaan baru untuk standar C++20, sehingga itu tidak akan praktis jika Anda belum meningkatkannya.

Jenis Catatan porting
Kelas CriticalSection Menggunakan pustaka dukungan utas
Kelas peristiwa (WRL) Gunakan templat winrt::event struct
Kelas HandleT Gunakan struct winrt::handle atau winrt::file_handle struct
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 Menggunakan 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 Menggunakan pustaka dukungan utas
Kelas SRWLock Menggunakan pustaka dukungan utas

API penting