Skip to content

Commit

Permalink
C++/WinRT 2.0 updates
Browse files Browse the repository at this point in the history
  • Loading branch information
dunhor committed May 22, 2019
1 parent 34f0c96 commit 9b7ae96
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 79 deletions.
9 changes: 9 additions & 0 deletions CMakeLists.txt
Expand Up @@ -7,5 +7,14 @@ if (NOT DEFINED WIL_BUILD_VERSION)
set(WIL_BUILD_VERSION "0.0.0")
endif()

# Detect the Windows SDK version. If we're using the Visual Studio generator, this will be provided for us. Otherwise
# we'll need to assume that this value comes from the command line (e.g. through the VS command prompt)
if (DEFINED CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION)
set(WIL_WINDOWS_SDK_VERSION ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION})
else()
# This has a trailing backslash for whatever reason...
string(REGEX REPLACE "\\\\$" "" WIL_WINDOWS_SDK_VERSION "$ENV{WindowsSDKVersion}")
endif()

add_subdirectory(packaging)
add_subdirectory(tests)
102 changes: 78 additions & 24 deletions include/wil/cppwinrt.h
Expand Up @@ -16,47 +16,70 @@
#include <unknwn.h>
#include <hstring.h>

#ifdef CPPWINRT_VERSION
#error Please include wil/cppwinrt.h before including any C++/WinRT headers
#endif
// WIL and C++/WinRT use two different exception types for communicating HRESULT failures. Thus, both libraries need to
// understand how to translate these exception types into the correct HRESULT values at the ABI boundary. Prior to
// C++/WinRT "2.0" this was accomplished by injecting the WINRT_EXTERNAL_CATCH_CLAUSE macro - that WIL defines below -
// into its exception handler (winrt::to_hresult). Starting with C++/WinRT "2.0" this mechanism has shifted to a global
// function pointer - winrt_to_hresult_handler - that WIL sets automatically when this header is included and
// 'RESULT_SUPPRESS_STATIC_INITIALIZERS' is not defined.

#ifdef __WIL_RESULTMACROS_INCLUDED
#error Please include wil/cppwinrt.h before including result_macros.h
#endif
/// @cond
namespace wil::details
{
// Since the C++/WinRT version macro is a string...
inline constexpr int major_version_from_string(const char* versionString)
{
int result = 0;
auto str = versionString;
while ((*str >= '0') && (*str <= '9'))
{
result = result * 10 + (*str - '0');
++str;
}

#ifdef WINRT_EXTERNAL_CATCH_CLAUSE
#error C++/WinRT external catch clause already defined outside of WIL
#endif
return result;
}
}
/// @endcond

// WIL and C++/WinRT use two different exception types for communicating HRESULT failures. Thus, both libraries need to
// understand how to translate these exception types into the correct HRESULT values at the ABI boundary. C++/WinRT
// accomplishes this by injecting the WINRT_EXTERNAL_CATCH_CLAUSE macro - that WIL defines below - into its exception
// handler. WIL accomplishes this by detecting this file's inclusion in result_macros.h and modifies its behavior to
// account for the C++/WinRT exception type.
#ifdef CPPWINRT_VERSION
// Prior to C++/WinRT "2.0" this header needed to be included before 'winrt/base.h' so that our definition of
// 'WINRT_EXTERNAL_CATCH_CLAUSE' would get picked up in the implementation of 'winrt::to_hresult'. This is no longer
// problematic, so only emit an error when using a version of C++/WinRT prior to 2.0
static_assert(::wil::details::major_version_from_string(CPPWINRT_VERSION) >= 2,
"Please include wil/cppwinrt.h before including any C++/WinRT headers");
#endif

// NOTE: Will eventually be removed once C++/WinRT 2.0 use can be assumed
#ifdef WINRT_EXTERNAL_CATCH_CLAUSE
#define __WI_CONFLICTING_WINRT_EXTERNAL_CATCH_CLAUSE 1
#else
#define WINRT_EXTERNAL_CATCH_CLAUSE \
catch (const wil::ResultException& e) \
{ \
return winrt::hresult_error(e.GetErrorCode(), winrt::to_hstring(e.what())).to_abi(); \
}

namespace wil::details
{
// Due to header dependency issues, result_macros.h cannot reference winrt::hresult_error, so instead declare
// functions that we can define after including base.h
HRESULT __stdcall ResultFromCppWinRTException(
_Inout_updates_opt_(debugStringChars) PWSTR debugString,
_When_(debugString != nullptr, _Pre_satisfies_(debugStringChars > 0)) size_t debugStringChars) WI_NOEXCEPT;
}
#endif

#include "result_macros.h"
#include <winrt/base.h>

#if __WI_CONFLICTING_WINRT_EXTERNAL_CATCH_CLAUSE
static_assert(::wil::details::major_version_from_string(CPPWINRT_VERSION) >= 2,
"C++/WinRT external catch clause already defined outside of WIL");
#endif

// In C++/WinRT 2.0 and beyond, this function pointer exists. In earlier versions it does not. It's much easier to avoid
// linker errors than it is to SFINAE on variable existence, so we declare the variable here, but are careful not to
// use it unless the version of C++/WinRT is high enough
extern std::int32_t(__stdcall* winrt_to_hresult_handler)(void*) noexcept;

/// @cond
namespace wil::details
{
inline HRESULT __stdcall ResultFromCppWinRTException(
_Inout_updates_opt_(debugStringChars) PWSTR debugString,
_When_(debugString != nullptr, _Pre_satisfies_(debugStringChars > 0)) size_t debugStringChars) WI_NOEXCEPT
_When_(debugString != nullptr, _Pre_satisfies_(debugStringChars > 0)) size_t debugStringChars) noexcept
{
try
{
Expand All @@ -78,9 +101,40 @@ namespace wil::details
}
}
}
/// @endcond

namespace wil
{
inline std::int32_t __stdcall winrt_to_hresult(void* returnAddress) noexcept
{
// C++/WinRT only gives us the return address (caller), so pass along an empty 'DiagnosticsInfo' since we don't
// have accurate file/line/etc. information
return static_cast<std::int32_t>(details::ReportFailure_CaughtException(__R_DIAGNOSTICS_RA(DiagnosticsInfo{}, returnAddress), FailureType::Return));
}

inline void WilInitialize_CppWinRT()
{
details::g_pfnResultFromCaughtException_CppWinRt = details::ResultFromCppWinRTException;
if constexpr (details::major_version_from_string(CPPWINRT_VERSION) >= 2)
{
WI_ASSERT(winrt_to_hresult_handler == nullptr);
winrt_to_hresult_handler = winrt_to_hresult;
}
}

/// @cond
namespace details
{
#ifndef RESULT_SUPPRESS_STATIC_INITIALIZERS
WI_HEADER_INITITALIZATION_FUNCTION(WilInitialize_CppWinRT, []
{
::wil::WilInitialize_CppWinRT();
return 1;
});
#endif
}
/// @endcond

// Provides an overload of verify_hresult so that the WIL macros can recognize winrt::hresult as a valid "hresult" type.
inline long verify_hresult(winrt::hresult hr) noexcept
{
Expand Down
86 changes: 48 additions & 38 deletions include/wil/result_macros.h
Expand Up @@ -1204,6 +1204,9 @@ namespace wil
__declspec(selectany) void(__stdcall *g_pfnThrowResultException)(const FailureInfo& failure) = nullptr;
extern "C" __declspec(selectany) HRESULT(__stdcall *g_pfnResultFromCaughtExceptionInternal)(_Out_writes_opt_(debugStringChars) PWSTR debugString, _When_(debugString != nullptr, _Pre_satisfies_(debugStringChars > 0)) size_t debugStringChars, _Out_ bool* isNormalized) WI_PFN_NOEXCEPT = nullptr;

// C++/WinRT additions
extern "C" __declspec(selectany) HRESULT(__stdcall *g_pfnResultFromCaughtException_CppWinRt)(_Out_writes_opt_(debugStringChars) PWSTR debugString, _When_(debugString != nullptr, _Pre_satisfies_(debugStringChars > 0)) size_t debugStringChars) WI_PFN_NOEXCEPT = nullptr;

// C++/cx compiled additions
extern "C" __declspec(selectany) void(__stdcall *g_pfnThrowPlatformException)(FailureInfo const &failure, PCWSTR debugString) = nullptr;
extern "C" __declspec(selectany) _Always_(_Post_satisfies_(return < 0)) HRESULT(__stdcall *g_pfnResultFromCaughtException_WinRt)(_Inout_updates_opt_(debugStringChars) PWSTR debugString, _When_(debugString != nullptr, _Pre_satisfies_(debugStringChars > 0)) size_t debugStringChars, _Out_ bool* isNormalized) WI_PFN_NOEXCEPT = nullptr;
Expand Down Expand Up @@ -2481,6 +2484,24 @@ namespace wil
return hr;
}

inline HRESULT ResultFromKnownException_CppWinRT(const DiagnosticsInfo& diagnostics, void* returnAddress)
{
if (g_pfnResultFromCaughtException_CppWinRt)
{
wchar_t message[2048];
message[0] = L'\0';
auto hr = g_pfnResultFromCaughtException_CppWinRt(message, ARRAYSIZE(message));
if (FAILED(hr))
{
ReportFailure(__R_DIAGNOSTICS_RA(diagnostics, returnAddress), FailureType::Log, hr, message);
return hr;
}
}

// Indicate that this either isn't a C++/WinRT exception or a handler isn't configured by returning success
return S_OK;
}

inline HRESULT RecognizeCaughtExceptionFromCallback(_Inout_updates_opt_(debugStringChars) PWSTR debugString, _When_(debugString != nullptr, _Pre_satisfies_(debugStringChars > 0)) size_t debugStringChars)
{
HRESULT hr = g_pfnResultFromCaughtException();
Expand Down Expand Up @@ -2587,13 +2608,15 @@ namespace wil
catch (...)
{
HRESULT hr;
#ifdef __WIL_CPPWINRT_INCLUDED
hr = ResultFromCppWinRTException(debugString, debugStringChars);
if (FAILED(hr))

if (g_pfnResultFromCaughtException_CppWinRt)
{
return hr;
hr = g_pfnResultFromCaughtException_CppWinRt(debugString, debugStringChars);
if (FAILED(hr))
{
return hr;
}
}
#endif

hr = RecognizeCaughtExceptionFromCallback(debugString, debugStringChars);
if (FAILED(hr))
Expand Down Expand Up @@ -2631,13 +2654,14 @@ namespace wil
}
catch (...)
{
#ifdef __WIL_CPPWINRT_INCLUDED
auto hr = ResultFromCppWinRTException(debugString, debugStringChars);
if (FAILED(hr))
if (g_pfnResultFromCaughtException_CppWinRt)
{
return hr;
auto hr = g_pfnResultFromCaughtException_CppWinRt(debugString, debugStringChars);
if (FAILED(hr))
{
return hr;
}
}
#endif
}
}

Expand Down Expand Up @@ -2675,21 +2699,13 @@ namespace wil
}
catch (...)
{
HRESULT hr;
wchar_t message[2048] = L"";

#ifdef __WIL_CPPWINRT_INCLUDED
hr = ResultFromCppWinRTException(message, ARRAYSIZE(message));
auto hr = ResultFromKnownException_CppWinRT(diagnostics, returnAddress);
if (FAILED(hr))
{
ReportFailure(__R_DIAGNOSTICS_RA(diagnostics, returnAddress), FailureType::Log, hr, message);
return hr;
}
#endif

// Unknown exception
UNREFERENCED_PARAMETER(hr);
UNREFERENCED_PARAMETER(message);
throw;
}
break;
Expand Down Expand Up @@ -2792,13 +2808,14 @@ namespace wil
catch (...)
{
HRESULT hr;
#ifdef __WIL_CPPWINRT_INCLUDED
hr = ResultFromCppWinRTException(debugString, debugStringChars);
if (FAILED(hr))
if (g_pfnResultFromCaughtException_CppWinRt)
{
return hr;
hr = g_pfnResultFromCaughtException_CppWinRt(debugString, debugStringChars);
if (FAILED(hr))
{
return hr;
}
}
#endif

hr = RecognizeCaughtExceptionFromCallback(debugString, debugStringChars);
if (FAILED(hr))
Expand Down Expand Up @@ -2831,13 +2848,14 @@ namespace wil
}
catch (...)
{
#ifdef __WIL_CPPWINRT_INCLUDED
auto hr = ResultFromCppWinRTException(debugString, debugStringChars);
if (FAILED(hr))
if (g_pfnResultFromCaughtException_CppWinRt)
{
return hr;
auto hr = g_pfnResultFromCaughtException_CppWinRt(debugString, debugStringChars);
if (FAILED(hr))
{
return hr;
}
}
#endif
}
}

Expand Down Expand Up @@ -2882,21 +2900,13 @@ namespace wil
}
catch (...)
{
HRESULT hr;
wchar_t message[2048] = L"";

#ifdef __WIL_CPPWINRT_INCLUDED
hr = ResultFromCppWinRTException(message, ARRAYSIZE(message));
auto hr = ResultFromKnownException_CppWinRT(diagnostics, returnAddress);
if (FAILED(hr))
{
ReportFailure(__R_DIAGNOSTICS_RA(diagnostics, returnAddress), FailureType::Log, hr, message);
return hr;
}
#endif

// Unknown exception
UNREFERENCED_PARAMETER(hr);
UNREFERENCED_PARAMETER(message);
throw;
}

Expand Down
28 changes: 28 additions & 0 deletions tests/CppWinRT20Tests.cpp
@@ -0,0 +1,28 @@

#include "common.h"

// Prior to C++/WinRT 2.0 this would cause issues since we're not including wil/cppwinrt.h in this translation unit.
// However, since we're going to link into the same executable as 'CppWinRTTests.cpp', the 'winrt_to_hresult_handler'
// global function pointer should be set, so these should all run successfully

#include <winrt/base.h>
#include <wil/result.h>

TEST_CASE("CppWinRTTests::CppWinRT20Test", "[cppwinrt]")
{
auto test = [](HRESULT hr)
{
try
{
THROW_HR(hr);
}
catch (...)
{
REQUIRE(hr == winrt::to_hresult());
}
};

test(E_OUTOFMEMORY);
test(E_INVALIDARG);
test(E_UNEXPECTED);
}

0 comments on commit 9b7ae96

Please sign in to comment.