다음을 통해 공유


WRL에서 C++/WinRT로 이동

이 주제에서는 Windows 런타임 C++ 템플릿 라이브러리(WRL) 코드를 C++/WinRT에 해당하는 코드로 옮기는 방법을 보여 줍니다.

C++/WinRT로 포팅하는 첫 번째 단계는 프로젝트에 C++/WinRT 지원을 수동으로 추가하는 것입니다( C++/WinRT에 대한 Visual Studio 지원 참조). 이렇게 하려면 프로젝트에 Microsoft.Windows.CppWinRT NuGet 패키지를 설치합니다. Visual Studio에서 프로젝트를 열고 ProjectNuGet 패키지 관리...찾아보기, 검색 상자에 Microsoft.Windows.CppWinRT 입력하거나 붙여넣고 검색 결과에서 항목을 선택한 다음 설치 클릭하여 해당 프로젝트에 대한 패키지를 설치합니다. 이러한 변경의 한 가지 효과는 프로젝트에서 C++/CX 에 대한 지원이 꺼져 있다는 것입니다. 프로젝트에서 C++/CX를 사용하는 경우 지원을 해제한 상태로 두고 C++/CX 코드를 C++/WinRT로 업데이트할 수 있습니다( C++/CX에서 C++/WinRT로 이동 참조). 또는 지원을 다시 켜고(프로젝트 속성에서 C/C++ >일반>Windows 런타임 확장 사용>예(/ZW)), 먼저 WRL 코드 포팅에 집중할 수 있습니다. C++/CX 및 C++/WinRT 코드는 XAML 컴파일러 지원 및 Windows 런타임 구성 요소를 제외하고 동일한 프로젝트에서 공존할 수 있습니다( C++/CX에서 C++/WinRT로 이동 참조).

프로젝트 속성 일반>대상 플랫폼 버전을 10.0.17134.0(Windows 10 버전 1803) 이상으로 설정합니다.

미리 컴파일된 헤더 파일(일반적으로 pch.h)에 다음을 포함합니다 winrt/base.h.

#include <winrt/base.h>

C++/WinRT로 프로젝션된 Windows API 헤더(예: winrt/Windows.Foundation.h)를 포함하는 경우, winrt/base.h을 명시적으로 포함할 필요가 없습니다. 이는 자동으로 포함되기 때문입니다.

WRL COM 스마트 포인터 포팅(Microsoft::WRL::ComPtr)

microsoft::WRL::ComPtrT 사용하여 winrt::com_ptrT사용하는 코드를 포틀합니다. 다음은 전후 코드 예제입니다. 버전 이후 com_ptr::put 멤버 함수는 내부 원시 포인터를 검색하여 설정할 수 있도록 합니다.

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

중요합니다

이미 내부 원시 포인터에 대상을 가진 winrt::com_ptr가 있고, 그것을 다른 개체로 재설정하려면, 먼저 아래 코드 예제와 같이 nullptr를 할당해야 합니다. 그렇지 않은 경우, 이미 초기화된 com_ptr은 내부 포인터가 null이 아님을 어설션하며, com_ptr::put 또는 com_ptr::put_void를 호출할 때 해당 문제를 경고합니다.

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

다음 예제( 버전 이후의 )에서 com_ptr::put_void 멤버 함수는 기본 원시 포인터를 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();
}

ComPtr::Getcom_ptr::get로 대체합니다.

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

IUnknown포인터를 요구하는 함수에 기본 원시 포인터를 전달하려면, 다음 예제와 같이 winrt::get_unknown free 함수를 사용하십시오.

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

WRL 모듈 포팅(Microsoft::WRL::Module)

이 섹션은 Microsoft::WRL::Module 형식을 사용하는 코드 포팅과 관련이 있습니다.

WRL을 사용하여 구성 요소를 구현하는 기존 프로젝트에 C++/WinRT 코드를 점진적으로 추가할 수 있으며 기존 WRL 클래스는 계속 지원됩니다. 이 섹션에서는 방법을 보여줍니다.

Visual Studio에서 새 Windows 런타임 구성 요소(C++/WinRT) 프로젝트 형식을 만들고 빌드하면, 파일 Generated Files\module.g.cpp가 생성됩니다. 이 파일에는 복사하여 프로젝트에 추가할 수 있는 두 가지 유용한 C++/WinRT 함수(아래 나열됨)의 정의가 포함되어 있습니다. 이들 함수는 WINRT_CanUnloadNowWINRT_GetActivationFactory이며, 보시다시피, 포팅의 어떤 단계에서든지 지원하기 위해 조건부로 WRL을 호출합니다.

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

프로젝트에 이러한 함수가 있으면 Module::GetActivationFactory 를 직접 호출하는 대신 WINRT_GetActivationFactory (WRL 함수를 내부적으로 호출)을 호출합니다. 다음은 전후 코드 예제입니다.

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

Module::Terminate를 직접 호출하는 대신, 내부적으로 WRL 함수를 호출하는 WINRT_CanUnloadNow를 호출합니다. 다음은 전후 코드 예제입니다.

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

포팅 Microsoft::WRL::래퍼 래퍼

이 섹션은 Microsoft::WRL::Wrappers 래퍼를 사용하는 코드를 이식하는 것과 관련이 있습니다.

아래 표에서 볼 수 있듯이 스레딩 도우미를 바꾸려면 표준 C++ 스레드 지원 라이브러리를 사용하는 것이 좋습니다. WRL 래퍼의 일대일 매핑은 사용자의 필요에 따라 선택이 달라질 수 있으므로 혼란을 초래할 수 있습니다. 또한 명백한 매핑으로 보일 수 있는 일부 형식은 C++20 표준에 새로 추가되었으므로 아직 업그레이드하지 않은 경우 비실용적입니다.

유형 포팅 관련 노트
CriticalSection 클래스 스레드 지원 라이브러리 사용
이벤트 클래스(WRL) winrt::event 구조체 템플릿 사용
HandleT 클래스 winrt::handle 구조체 또는 winrt::file_handle 구조체 사용
HString 클래스 winrt::hstring 구조체 사용
HStringReference 클래스 C++/WinRT는 HStringReference 만큼 효율적인 방식으로 이를 내부적으로 처리하므로, 사용자가 신경 쓸 필요가 없다는 장점이 있어 교체할 필요가 없습니다.
뮤텍스 클래스 스레드 지원 라이브러리 사용
RoInitializeWrapper 클래스 winrt::init_apartmentwinrt::uninit_apartment를 사용하거나 CoInitializeExCoUninitialize을 위한 간단한 래퍼를 작성합니다.
세마포 클래스 스레드 지원 라이브러리 사용
SRWLock 클래스 스레드 지원 라이브러리 사용

중요 API