| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| # Windows Implementation Libraries (WIL) | ||
|
|
||
| [](https://dev.azure.com/msft-wil/Windows%20Implementation%20Library/_build/latest?definitionId=1&branchName=master) | ||
|
|
||
| The Windows Implementation Libraries (WIL) is a header-only C++ library created to make life easier | ||
| for developers on Windows through readable type-safe C++ interfaces for common Windows coding patterns. | ||
|
|
||
| Some things that WIL includes to whet your appetite: | ||
|
|
||
| - [`include/wil/resource.h`](include/wil/resource.h) | ||
| ([documentation](https://github.com/Microsoft/wil/wiki/RAII-resource-wrappers)): | ||
| Smart pointers and auto-releasing resource wrappers to let you manage Windows | ||
| API HANDLEs, HWNDs, and other resources and resource handles with | ||
| [RAII](https://en.cppreference.com/w/cpp/language/raii) semantics. | ||
| - [`include/wil/win32_helpers.h`](include/wil/win32_helpers.h): Wrappers for API functions | ||
| that save you the work of manually specifying buffer sizes, calling a function twice | ||
| to get the needed buffer size and then allocate and pass the right-size buffer, | ||
| casting or converting between types, and so on. | ||
| - [`include/wil/registry.h`](include/wil/registry.h): Registry watchers that can | ||
| call a lambda function or callback you provide whenever a certain tree within | ||
| the Windows registry changes. | ||
| - [`include/wil/result.h`](include/wil/result.h) | ||
| ([documentation](https://github.com/Microsoft/wil/wiki/Error-handling-helpers)): | ||
| Preprocessor macros to help you check for errors from Windows API functions, | ||
| in many of the myriad ways those errors are reported, and surface them as | ||
| error codes or C++ exceptions in your code. | ||
|
|
||
| WIL can be used by C++ code that uses C++ exceptions as well as code that uses returned | ||
| error codes to report errors. All of WIL can be used from user-space Windows code, | ||
| and some (such as the RAII resource wrappers) can even be used in kernel mode. | ||
|
|
||
| # Documentation | ||
|
|
||
| This project is documented in [its GitHub wiki](https://github.com/Microsoft/wil/wiki). Feel free to contribute to it! | ||
|
|
||
| # Consuming WIL | ||
| WIL follows the "live at head" philosophy, so you should feel free to consume WIL directly from the GitHub repo however you please: as a GIT submodule, symbolic link, download and copy files, etc. and update to the latest version at your own cadence. Alternatively, WIL is available using a few package managers, mentioned below. These packages will be updated periodically, likely to average around once or twice per month. | ||
|
|
||
| ## Consuming WIL via NuGet | ||
| WIL is available on nuget.org under the name [Microsoft.Windows.ImplementationLibrary](https://www.nuget.org/packages/Microsoft.Windows.ImplementationLibrary/). This package includes the header files under the [include](include) directory as well as a [.targets](packaging/nuget/Microsoft.Windows.ImplementationLibrary.targets) file. | ||
|
|
||
| ## Consuming WIL via vcpkg | ||
| WIL is also available using [vcpkg](https://github.com/microsoft/vcpkg) under the name [wil](https://github.com/microsoft/vcpkg/blob/master/ports/wil/portfile.cmake). Instructions for installing packages can be found in the [vcpkg GitHub docs](https://github.com/microsoft/vcpkg/blob/master/docs/examples/installing-and-using-packages.md). In general, once vcpkg is set up on the system, you can run: | ||
| ```cmd | ||
| C:\vcpkg> vcpkg install wil:x86-windows | ||
| C:\vcpkg> vcpkg install wil:x64-windows | ||
| ``` | ||
| Note that even though WIL is a header-only library, you still need to install the package for all architectures/platforms you wish to use it with. Otherwise, WIL won't be added to the include path for the missing architectures/platforms. Execute `vcpkg help triplet` for a list of available options. | ||
|
|
||
| # Building/Testing | ||
| To get started testing WIL, first make sure that you have a recent version of [Visual Studio](https://visualstudio.microsoft.com/downloads/) and the most recent [Windows SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) installed. If you are doing | ||
| any non-trivial work, also be sure to have a recent version of [Clang](http://releases.llvm.org/download.html) installed. Once everything is installed, open a VS | ||
| native command window (e.g. "x64 Native Tools Command Prompt for VS 2019"). If you are familiar with CMake you can get started building normally. Otherwise, or if you prefer to skip all of the boilerplate, you can use one of the scripts in the [scripts](scripts) directory: | ||
| ```cmd | ||
| C:\wil> scripts\init.cmd -c clang -g ninja -b debug | ||
| ``` | ||
| You can execute `init.cmd --help` for a summary of available options. The scripts use a common directory pattern of `build/$(compiler)$(arch)$(type)` for the build output root. E.g. `build/clang64debug` when using Clang as the compiler, x64 as the architecture, and Debug as the build type. It is this directory where you will want to build from. For example, if you initialized using the command above, you can build the tests like so: | ||
| ```cmd | ||
| C:\wil\build\clang64debug> ninja | ||
| ``` | ||
| Or, if you want to only build a single test (e.g. for improved compile times): | ||
| ```cmd | ||
| C:\wil\build\clang64debug> ninja witest.noexcept | ||
| ``` | ||
| If you initialized using MSBuild as the generator, there will be a `.sln` file in the root of the build directory. You | ||
| can either open the solution in Visual Studio or invoke MSBuild directly to build. | ||
|
|
||
| The output is a number of test executables. If you used the initialization script(s) mentioned above, or if you followed | ||
| the same directory naming convention of those scripts, you can use the [runtests.cmd](scripts/runtests.cmd) script, | ||
| which will execute any test executables that have been built, erroring out - and preserving the exit code - if any test | ||
| fails. Note that MSBuild will modify the output directory names, so this script is only compatible with using Ninja as the | ||
| generator. If you are at the tail end of of a change, you can execute the following to get a wide range of coverage: | ||
| ```cmd | ||
| C:\wil> scripts\init_all.cmd | ||
| C:\wil> scripts\build_all.cmd | ||
| C:\wil> scripts\runtests.cmd | ||
| ``` | ||
| Note that this will only test for the architecture that corresponds to the command window you opened. You will want to | ||
| repeat this process for the other architecture (e.g. by using the "x86 Native Tools Command Prompt for VS 2019") | ||
|
|
||
| # Contributing | ||
|
|
||
| This project welcomes contributions and suggestions. Most contributions require you to agree to a | ||
| Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us | ||
| the rights to use your contribution. For details, visit https://cla.microsoft.com. | ||
|
|
||
| When you submit a pull request, a CLA-bot will automatically determine whether you need to provide | ||
| a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions | ||
| provided by the bot. You will only need to do this once across all repos using our CLA. | ||
|
|
||
| This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). | ||
| For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or | ||
| contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| THIRD PARTY SOFTWARE NOTICES AND INFORMATION | ||
| Do Not Translate or Localize | ||
|
|
||
| This software incorporates material from third parties. Microsoft makes certain open source code available at http://3rdpartysource.microsoft.com, or you may send a check or money order for US $5.00, including the product name, the open source component name, and version number, to: | ||
|
|
||
| Source Code Compliance Team | ||
| Microsoft Corporation | ||
| One Microsoft Way | ||
| Redmond, WA 98052 | ||
| USA | ||
|
|
||
| Notwithstanding any other terms, you may reverse engineer this software to the extent required to debug changes to any libraries licensed under the GNU Lesser General Public License. | ||
|
|
||
| Libc++ | ||
|
|
||
| Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT | ||
|
|
||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
|
|
||
| The above copyright notice and this permission notice shall be included in | ||
| all copies or substantial portions of the Software. | ||
|
|
||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| THE SOFTWARE. | ||
|
|
||
| Catch2 | ||
|
|
||
| Boost Software License - Version 1.0 - August 17th, 2003 | ||
|
|
||
| Permission is hereby granted, free of charge, to any person or organization | ||
| obtaining a copy of the software and accompanying documentation covered by | ||
| this license (the "Software") to use, reproduce, display, distribute, | ||
| execute, and transmit the Software, and to prepare derivative works of the | ||
| Software, and to permit third-parties to whom the Software is furnished to | ||
| do so, all subject to the following: | ||
|
|
||
| The copyright notices in the Software and this entire statement, including | ||
| the above license grant, this restriction and the following disclaimer, | ||
| must be included in all copies of the Software, in whole or in part, and | ||
| all derivative works of the Software, unless such copies or derivative | ||
| works are solely in the form of machine-executable object code generated by | ||
| a source language processor. | ||
|
|
||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT | ||
| SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE | ||
| FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, | ||
| ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
| DEALINGS IN THE SOFTWARE. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
|
|
||
| # E.g. replace_cxx_flag("/W[0-4]", "/W4") | ||
| macro(replace_cxx_flag pattern text) | ||
| foreach (flag | ||
| CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE | ||
| CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) | ||
|
|
||
| string(REGEX REPLACE "${pattern}" "${text}" ${flag} "${${flag}}") | ||
|
|
||
| endforeach() | ||
| endmacro() | ||
|
|
||
| macro(append_cxx_flag text) | ||
| foreach (flag | ||
| CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE | ||
| CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) | ||
|
|
||
| string(APPEND ${flag} " ${text}") | ||
|
|
||
| endforeach() | ||
| endmacro() | ||
|
|
||
| # Fixup default compiler settings | ||
|
|
||
| # Be as strict as reasonably possible, since we want to support consumers using strict warning levels | ||
| replace_cxx_flag("/W[0-4]" "/W4") | ||
| append_cxx_flag("/WX") | ||
|
|
||
| # We want to be as conformant as possible, so tell MSVC to not be permissive (note that this has no effect on clang-cl) | ||
| append_cxx_flag("/permissive-") | ||
|
|
||
| # wistd::function has padding due to alignment. This is expected | ||
| append_cxx_flag("/wd4324") | ||
|
|
||
| if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") | ||
| # Ignore a few Clang warnings. We may want to revisit in the future to see if any of these can/should be removed | ||
| append_cxx_flag("-Wno-switch") | ||
| append_cxx_flag("-Wno-c++17-compat-mangling") | ||
| append_cxx_flag("-Wno-missing-field-initializers") | ||
|
|
||
| # For tests, we want to be able to test self assignment, so disable this warning | ||
| append_cxx_flag("-Wno-self-assign-overloaded") | ||
| append_cxx_flag("-Wno-self-move") | ||
|
|
||
| # clang-cl does not understand the /permissive- flag (or at least it opts to ignore it). We can achieve similar | ||
| # results through the following flags. | ||
| append_cxx_flag("-fno-delayed-template-parsing") | ||
|
|
||
| # NOTE: Windows headers not clean enough for us to realistically attempt to start fixing these errors yet. That | ||
| # said, errors that originate from WIL headers may benefit | ||
| # append_cxx_flag("-fno-ms-compatibility") | ||
| # append_cxx_flag("-ferror-limit=999") | ||
| # append_cxx_flag("-fmacro-backtrace-limit=0") | ||
| # -fno-ms-compatibility turns off preprocessor compatability, which currently only works when __VA_OPT__ support is | ||
| # available (i.e. >= C++20) | ||
| # append_cxx_flag("-Xclang -std=c++2a") | ||
| else() | ||
| # Flags that are either ignored or unrecognized by clang-cl | ||
| # TODO: https://github.com/Microsoft/wil/issues/6 | ||
| # append_cxx_flag("/experimental:preprocessor") | ||
|
|
||
| # CRT headers are not yet /experimental:preprocessor clean, so work around the known issues | ||
| # append_cxx_flag("/Wv:18") | ||
|
|
||
| append_cxx_flag("/bigobj") | ||
|
|
||
| # NOTE: Temporary workaround while https://github.com/microsoft/wil/issues/102 is being investigated | ||
| append_cxx_flag("/d2FH4-") | ||
| endif() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,251 @@ | ||
| //********************************************************* | ||
| // | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // This code is licensed under the MIT License. | ||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF | ||
| // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED | ||
| // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | ||
| // PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
| // | ||
| //********************************************************* | ||
| #ifndef __WIL_CPPWINRT_INCLUDED | ||
| #define __WIL_CPPWINRT_INCLUDED | ||
|
|
||
| #include "common.h" | ||
| #include <windows.h> | ||
| #include <unknwn.h> | ||
| #include <hstring.h> | ||
|
|
||
| // 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 | ||
| // 'CPPWINRT_SUPPRESS_STATIC_INITIALIZERS' is not defined. | ||
|
|
||
| /// @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; | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
| } | ||
| /// @endcond | ||
|
|
||
| #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(); \ | ||
| } | ||
| #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 void MaybeGetExceptionString( | ||
| const winrt::hresult_error& exception, | ||
| _Out_writes_opt_(debugStringChars) PWSTR debugString, | ||
| _When_(debugString != nullptr, _Pre_satisfies_(debugStringChars > 0)) size_t debugStringChars) | ||
| { | ||
| if (debugString) | ||
| { | ||
| StringCchPrintfW(debugString, debugStringChars, L"winrt::hresult_error: %ls", exception.message().c_str()); | ||
| } | ||
| } | ||
|
|
||
| inline HRESULT __stdcall ResultFromCaughtException_CppWinRt( | ||
| _Inout_updates_opt_(debugStringChars) PWSTR debugString, | ||
| _When_(debugString != nullptr, _Pre_satisfies_(debugStringChars > 0)) size_t debugStringChars, | ||
| _Inout_ bool* isNormalized) noexcept | ||
| { | ||
| if (g_pfnResultFromCaughtException) | ||
| { | ||
| try | ||
| { | ||
| throw; | ||
| } | ||
| catch (const ResultException& exception) | ||
| { | ||
| *isNormalized = true; | ||
| MaybeGetExceptionString(exception, debugString, debugStringChars); | ||
| return exception.GetErrorCode(); | ||
| } | ||
| catch (const winrt::hresult_error& exception) | ||
| { | ||
| MaybeGetExceptionString(exception, debugString, debugStringChars); | ||
| return exception.code().value; | ||
| } | ||
| catch (const std::bad_alloc& exception) | ||
| { | ||
| MaybeGetExceptionString(exception, debugString, debugStringChars); | ||
| return E_OUTOFMEMORY; | ||
| } | ||
| catch (const std::out_of_range& exception) | ||
| { | ||
| MaybeGetExceptionString(exception, debugString, debugStringChars); | ||
| return E_BOUNDS; | ||
| } | ||
| catch (const std::invalid_argument& exception) | ||
| { | ||
| MaybeGetExceptionString(exception, debugString, debugStringChars); | ||
| return E_INVALIDARG; | ||
| } | ||
| catch (...) | ||
| { | ||
| auto hr = RecognizeCaughtExceptionFromCallback(debugString, debugStringChars); | ||
| if (FAILED(hr)) | ||
| { | ||
| return hr; | ||
| } | ||
| } | ||
| } | ||
| else | ||
| { | ||
| try | ||
| { | ||
| throw; | ||
| } | ||
| catch (const ResultException& exception) | ||
| { | ||
| *isNormalized = true; | ||
| MaybeGetExceptionString(exception, debugString, debugStringChars); | ||
| return exception.GetErrorCode(); | ||
| } | ||
| catch (const winrt::hresult_error& exception) | ||
| { | ||
| MaybeGetExceptionString(exception, debugString, debugStringChars); | ||
| return exception.code().value; | ||
| } | ||
| catch (const std::bad_alloc& exception) | ||
| { | ||
| MaybeGetExceptionString(exception, debugString, debugStringChars); | ||
| return E_OUTOFMEMORY; | ||
| } | ||
| catch (const std::out_of_range& exception) | ||
| { | ||
| MaybeGetExceptionString(exception, debugString, debugStringChars); | ||
| return E_BOUNDS; | ||
| } | ||
| catch (const std::invalid_argument& exception) | ||
| { | ||
| MaybeGetExceptionString(exception, debugString, debugStringChars); | ||
| return E_INVALIDARG; | ||
| } | ||
| catch (const std::exception& exception) | ||
| { | ||
| MaybeGetExceptionString(exception, debugString, debugStringChars); | ||
| return HRESULT_FROM_WIN32(ERROR_UNHANDLED_EXCEPTION); | ||
| } | ||
| catch (...) | ||
| { | ||
| // Fall through to returning 'S_OK' below | ||
| } | ||
| } | ||
|
|
||
| // Tell the caller that we were unable to map the exception by succeeding... | ||
| return S_OK; | ||
| } | ||
| } | ||
| /// @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<FailureType::Return>(__R_DIAGNOSTICS_RA(DiagnosticsInfo{}, returnAddress))); | ||
| } | ||
|
|
||
| inline void WilInitialize_CppWinRT() | ||
| { | ||
| details::g_pfnResultFromCaughtException_CppWinRt = details::ResultFromCaughtException_CppWinRt; | ||
| 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 CPPWINRT_SUPPRESS_STATIC_INITIALIZERS | ||
| WI_ODR_PRAGMA("CPPWINRT_SUPPRESS_STATIC_INITIALIZERS", "0") | ||
| WI_HEADER_INITITALIZATION_FUNCTION(WilInitialize_CppWinRT, [] | ||
| { | ||
| ::wil::WilInitialize_CppWinRT(); | ||
| return 1; | ||
| }); | ||
| #else | ||
| WI_ODR_PRAGMA("CPPWINRT_SUPPRESS_STATIC_INITIALIZERS", "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 | ||
| { | ||
| return hr; | ||
| } | ||
|
|
||
| // Provides versions of get_abi and put_abi for genericity that directly use HSTRING for convenience. | ||
| template <typename T> | ||
| auto get_abi(T const& object) noexcept | ||
| { | ||
| return winrt::get_abi(object); | ||
| } | ||
|
|
||
| inline auto get_abi(winrt::hstring const& object) noexcept | ||
| { | ||
| return static_cast<HSTRING>(winrt::get_abi(object)); | ||
| } | ||
|
|
||
| template <typename T> | ||
| auto put_abi(T& object) noexcept | ||
| { | ||
| return winrt::put_abi(object); | ||
| } | ||
|
|
||
| inline auto put_abi(winrt::hstring& object) noexcept | ||
| { | ||
| return reinterpret_cast<HSTRING*>(winrt::put_abi(object)); | ||
| } | ||
| } | ||
|
|
||
| #endif // __WIL_CPPWINRT_INCLUDED |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,277 @@ | ||
| //********************************************************* | ||
| // | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // This code is licensed under the MIT License. | ||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF | ||
| // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED | ||
| // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | ||
| // PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
| // | ||
| //********************************************************* | ||
| #ifndef __WIL_REGISTRY_INCLUDED | ||
| #define __WIL_REGISTRY_INCLUDED | ||
|
|
||
| #ifdef _KERNEL_MODE | ||
| #error This header is not supported in kernel-mode. | ||
| #endif | ||
|
|
||
| #include <winreg.h> | ||
| #include <new.h> // new(std::nothrow) | ||
| #include "resource.h" // unique_hkey | ||
|
|
||
| namespace wil | ||
| { | ||
| //! The key name includes the absolute path of the key in the registry, always starting at a | ||
| //! base key, for example, HKEY_LOCAL_MACHINE. | ||
| size_t const max_registry_key_name_length = 255; | ||
|
|
||
| //! The maximum number of characters allowed in a registry value's name. | ||
| size_t const max_registry_value_name_length = 16383; | ||
|
|
||
| // unique_registry_watcher/unique_registry_watcher_nothrow/unique_registry_watcher_failfast | ||
| // These classes make it easy to execute a provided function when a | ||
| // registry key changes (optionally recursively). Specify the key | ||
| // either as a root key + path, or an open registry handle as wil::unique_hkey | ||
| // or a raw HKEY value (that will be duplicated). | ||
| // | ||
| // Example use with exceptions base error handling: | ||
| // auto watcher = wil::make_registry_watcher(HKEY_CURRENT_USER, L"Software\\MyApp", true, wil::RegistryChangeKind changeKind[] | ||
| // { | ||
| // if (changeKind == RegistryChangeKind::Delete) | ||
| // { | ||
| // watcher.reset(); | ||
| // } | ||
| // // invalidate cached registry data here | ||
| // }); | ||
| // | ||
| // Example use with error code base error handling: | ||
| // auto watcher = wil::make_registry_watcher_nothrow(HKEY_CURRENT_USER, L"Software\\MyApp", true, wil::RegistryChangeKind[] | ||
| // { | ||
| // // invalidate cached registry data here | ||
| // }); | ||
| // RETURN_IF_NULL_ALLOC(watcher); | ||
|
|
||
| enum class RegistryChangeKind | ||
| { | ||
| Modify = 0, | ||
| Delete = 1, | ||
| }; | ||
|
|
||
| /// @cond | ||
| namespace details | ||
| { | ||
| struct registry_watcher_state | ||
| { | ||
| registry_watcher_state(unique_hkey &&keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)> &&callback) | ||
| : m_callback(wistd::move(callback)), m_keyToWatch(wistd::move(keyToWatch)), m_isRecursive(isRecursive) | ||
| { | ||
| } | ||
| wistd::function<void(RegistryChangeKind)> m_callback; | ||
| unique_hkey m_keyToWatch; | ||
| unique_event_nothrow m_eventHandle; | ||
|
|
||
| // While not strictly needed since this is ref counted the thread pool wait | ||
| // should be last to ensure that the other members are valid | ||
| // when it is destructed as it will reference them. | ||
| unique_threadpool_wait m_threadPoolWait; | ||
| bool m_isRecursive; | ||
|
|
||
| volatile long m_refCount = 1; | ||
| srwlock m_lock; | ||
|
|
||
| // Returns true if the refcount can be increased from a non zero value, | ||
| // false it was zero impling that the object is in or on the way to the destructor. | ||
| // In this case ReleaseFromCallback() should not be called. | ||
| bool TryAddRef() | ||
| { | ||
| return ::InterlockedIncrement(&m_refCount) > 1; | ||
| } | ||
|
|
||
| void Release() | ||
| { | ||
| auto lock = m_lock.lock_exclusive(); | ||
| if (0 == ::InterlockedDecrement(&m_refCount)) | ||
| { | ||
| lock.reset(); // leave the lock before deleting it. | ||
| delete this; | ||
| } | ||
| } | ||
|
|
||
| void ReleaseFromCallback(bool rearm) | ||
| { | ||
| auto lock = m_lock.lock_exclusive(); | ||
| if (0 == ::InterlockedDecrement(&m_refCount)) | ||
| { | ||
| // Destroy the thread pool wait now to avoid the wait that would occur in the | ||
| // destructor. That wait would cause a deadlock since we are doing this from the callback. | ||
| ::CloseThreadpoolWait(m_threadPoolWait.release()); | ||
| lock.reset(); // leave the lock before deleting it. | ||
| delete this; | ||
| // Sleep(1); // Enable for testing to find use after free bugs. | ||
| } | ||
| else if (rearm) | ||
| { | ||
| ::SetThreadpoolWait(m_threadPoolWait.get(), m_eventHandle.get(), nullptr); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| inline void delete_registry_watcher_state(_In_opt_ registry_watcher_state *watcherStorage) { watcherStorage->Release(); } | ||
|
|
||
| typedef resource_policy<registry_watcher_state *, decltype(&details::delete_registry_watcher_state), | ||
| details::delete_registry_watcher_state, details::pointer_access_none> registry_watcher_state_resource_policy; | ||
| } | ||
| /// @endcond | ||
|
|
||
| template <typename storage_t, typename err_policy = err_exception_policy> | ||
| class registry_watcher_t : public storage_t | ||
| { | ||
| public: | ||
| // forward all base class constructors... | ||
| template <typename... args_t> | ||
| explicit registry_watcher_t(args_t&&... args) WI_NOEXCEPT : storage_t(wistd::forward<args_t>(args)...) {} | ||
|
|
||
| // HRESULT or void error handling... | ||
| typedef typename err_policy::result result; | ||
|
|
||
| // Exception-based constructors | ||
| registry_watcher_t(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function<void(RegistryChangeKind)> &&callback) | ||
| { | ||
| static_assert(wistd::is_same<void, result>::value, "this constructor requires exceptions; use the create method"); | ||
| create(rootKey, subKey, isRecursive, wistd::move(callback)); | ||
| } | ||
|
|
||
| registry_watcher_t(unique_hkey &&keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)> &&callback) | ||
| { | ||
| static_assert(wistd::is_same<void, result>::value, "this constructor requires exceptions; use the create method"); | ||
| create(wistd::move(keyToWatch), isRecursive, wistd::move(callback)); | ||
| } | ||
|
|
||
| // Pass a root key, sub key pair or use an empty string to use rootKey as the key to watch. | ||
| result create(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function<void(RegistryChangeKind)> &&callback) | ||
| { | ||
| // Most use will want to create the key, consider adding an option for open as a future design change. | ||
| unique_hkey keyToWatch; | ||
| HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyExW(rootKey, subKey, 0, nullptr, 0, KEY_NOTIFY, nullptr, &keyToWatch, nullptr)); | ||
| if (FAILED(hr)) | ||
| { | ||
| return err_policy::HResult(hr); | ||
| } | ||
| return err_policy::HResult(create_common(wistd::move(keyToWatch), isRecursive, wistd::move(callback))); | ||
| } | ||
|
|
||
| result create(unique_hkey &&keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)> &&callback) | ||
| { | ||
| return err_policy::HResult(create_common(wistd::move(keyToWatch), isRecursive, wistd::move(callback))); | ||
| } | ||
|
|
||
| private: | ||
| // Factored into a standalone function to support Clang which does not support conversion of stateless lambdas | ||
| // to __stdcall | ||
| static void __stdcall callback(PTP_CALLBACK_INSTANCE, void *context, TP_WAIT *, TP_WAIT_RESULT) | ||
| { | ||
| #ifndef __WIL_REGISTRY_CHANGE_CALLBACK_TEST | ||
| #define __WIL_REGISTRY_CHANGE_CALLBACK_TEST | ||
| #endif | ||
| __WIL_REGISTRY_CHANGE_CALLBACK_TEST | ||
| auto watcherState = static_cast<details::registry_watcher_state *>(context); | ||
| if (watcherState->TryAddRef()) | ||
| { | ||
| // using auto reset event so don't need to manually reset. | ||
|
|
||
| // failure here is a programming error. | ||
| const LSTATUS error = RegNotifyChangeKeyValue(watcherState->m_keyToWatch.get(), watcherState->m_isRecursive, | ||
| REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_THREAD_AGNOSTIC, | ||
| watcherState->m_eventHandle.get(), TRUE); | ||
|
|
||
| // Call the client before re-arming to ensure that multiple callbacks don't | ||
| // run concurrently. | ||
| switch (error) | ||
| { | ||
| case ERROR_SUCCESS: | ||
| case ERROR_ACCESS_DENIED: | ||
| // Normal modification: send RegistryChangeKind::Modify and re-arm. | ||
| watcherState->m_callback(RegistryChangeKind::Modify); | ||
| watcherState->ReleaseFromCallback(true); | ||
| break; | ||
|
|
||
| case ERROR_KEY_DELETED: | ||
| // Key deleted, send RegistryChangeKind::Delete, do not re-arm. | ||
| watcherState->m_callback(RegistryChangeKind::Delete); | ||
| watcherState->ReleaseFromCallback(false); | ||
| break; | ||
|
|
||
| case ERROR_HANDLE_REVOKED: | ||
| // Handle revoked. This can occur if the user session ends before | ||
| // the watcher shuts-down. Disarm silently since there is generally no way to respond. | ||
| watcherState->ReleaseFromCallback(false); | ||
| break; | ||
|
|
||
| default: | ||
| FAIL_FAST_HR(HRESULT_FROM_WIN32(error)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // This function exists to avoid template expansion of this code based on err_policy. | ||
| HRESULT create_common(unique_hkey &&keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)> &&callback) | ||
| { | ||
| wistd::unique_ptr<details::registry_watcher_state> watcherState(new(std::nothrow) details::registry_watcher_state( | ||
| wistd::move(keyToWatch), isRecursive, wistd::move(callback))); | ||
| RETURN_IF_NULL_ALLOC(watcherState); | ||
| RETURN_IF_FAILED(watcherState->m_eventHandle.create()); | ||
| RETURN_IF_WIN32_ERROR(RegNotifyChangeKeyValue(watcherState->m_keyToWatch.get(), | ||
| watcherState->m_isRecursive, REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_THREAD_AGNOSTIC, | ||
| watcherState->m_eventHandle.get(), TRUE)); | ||
|
|
||
| watcherState->m_threadPoolWait.reset(CreateThreadpoolWait(®istry_watcher_t::callback, watcherState.get(), nullptr)); | ||
| RETURN_LAST_ERROR_IF(!watcherState->m_threadPoolWait); | ||
| storage_t::reset(watcherState.release()); // no more failures after this, pass ownership | ||
| SetThreadpoolWait(storage_t::get()->m_threadPoolWait.get(), storage_t::get()->m_eventHandle.get(), nullptr); | ||
| return S_OK; | ||
| } | ||
| }; | ||
|
|
||
| typedef unique_any_t<registry_watcher_t<details::unique_storage<details::registry_watcher_state_resource_policy>, err_returncode_policy>> unique_registry_watcher_nothrow; | ||
| typedef unique_any_t<registry_watcher_t<details::unique_storage<details::registry_watcher_state_resource_policy>, err_failfast_policy>> unique_registry_watcher_failfast; | ||
|
|
||
| inline unique_registry_watcher_nothrow make_registry_watcher_nothrow(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function<void(RegistryChangeKind)> &&callback) WI_NOEXCEPT | ||
| { | ||
| unique_registry_watcher_nothrow watcher; | ||
| watcher.create(rootKey, subKey, isRecursive, wistd::move(callback)); | ||
| return watcher; // caller must test for success using if (watcher) | ||
| } | ||
|
|
||
| inline unique_registry_watcher_nothrow make_registry_watcher_nothrow(unique_hkey &&keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)> &&callback) WI_NOEXCEPT | ||
| { | ||
| unique_registry_watcher_nothrow watcher; | ||
| watcher.create(wistd::move(keyToWatch), isRecursive, wistd::move(callback)); | ||
| return watcher; // caller must test for success using if (watcher) | ||
| } | ||
|
|
||
| inline unique_registry_watcher_failfast make_registry_watcher_failfast(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function<void(RegistryChangeKind)> &&callback) | ||
| { | ||
| return unique_registry_watcher_failfast(rootKey, subKey, isRecursive, wistd::move(callback)); | ||
| } | ||
|
|
||
| inline unique_registry_watcher_failfast make_registry_watcher_failfast(unique_hkey &&keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)> &&callback) | ||
| { | ||
| return unique_registry_watcher_failfast(wistd::move(keyToWatch), isRecursive, wistd::move(callback)); | ||
| } | ||
|
|
||
| #ifdef WIL_ENABLE_EXCEPTIONS | ||
| typedef unique_any_t<registry_watcher_t<details::unique_storage<details::registry_watcher_state_resource_policy>, err_exception_policy >> unique_registry_watcher; | ||
|
|
||
| inline unique_registry_watcher make_registry_watcher(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function<void(RegistryChangeKind)> &&callback) | ||
| { | ||
| return unique_registry_watcher(rootKey, subKey, isRecursive, wistd::move(callback)); | ||
| } | ||
|
|
||
| inline unique_registry_watcher make_registry_watcher(unique_hkey &&keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)> &&callback) | ||
| { | ||
| return unique_registry_watcher(wistd::move(keyToWatch), isRecursive, wistd::move(callback)); | ||
| } | ||
| #endif // WIL_ENABLE_EXCEPTIONS | ||
| } // namespace wil | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| //********************************************************* | ||
| // | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // This code is licensed under the MIT License. | ||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF | ||
| // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED | ||
| // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | ||
| // PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
| // | ||
| //********************************************************* | ||
|
|
||
| // Note: When origination is enabled by including this file, origination is done as part of the RETURN_* and THROW_* macros. Before originating | ||
| // a new error we will observe whether there is already an error payload associated with the current thread. If there is, and the HRESULTs match, | ||
| // then a new error will not be originated. Otherwise we will overwrite it with a new origination. The ABI boundary for WinRT APIs will check the | ||
| // per-thread error information. The act of checking the error clears it, so there should be minimal risk of failing to originate distinct errors | ||
| // simply because the HRESULTs match. | ||
| // | ||
| // For THROW_ macros we will examine the thread-local error storage once per throw. So typically once, with additional calls if the exception is | ||
| // caught and re-thrown. | ||
| // | ||
| // For RETURN_ macros we will have to examine the thread-local error storage once per frame as the call stack unwinds. Because error conditions | ||
| // -should- be uncommon the performance impact of checking TLS should be minimal. The more expensive part is originating the error because it must | ||
| // capture the entire stack and some additional data. | ||
|
|
||
| #ifndef __WIL_RESULT_ORIGINATE_INCLUDED | ||
| #define __WIL_RESULT_ORIGINATE_INCLUDED | ||
|
|
||
| #include "result.h" | ||
| #include <OleAuto.h> // RestrictedErrorInfo uses BSTRs :( | ||
| #include "resource.h" | ||
| #include "com.h" | ||
| #include <roerrorapi.h> | ||
|
|
||
| #ifndef __cplusplus_winrt // The CX runtime likes to originate errors already so we would conflict with them. | ||
| namespace wil | ||
| { | ||
| namespace details | ||
| { | ||
| // Note: The name must begin with "Raise" so that the !analyze auto-bucketing will ignore this stack frame. Otherwise this line of code gets all the blame. | ||
| inline void __stdcall RaiseRoOriginateOnWilExceptions(wil::FailureInfo const& failure) WI_NOEXCEPT | ||
| { | ||
| if ((failure.type == FailureType::Return) || (failure.type == FailureType::Exception)) | ||
| { | ||
| bool shouldOriginate = true; | ||
|
|
||
| wil::com_ptr_nothrow<IRestrictedErrorInfo> restrictedErrorInformation; | ||
| if (GetRestrictedErrorInfo(&restrictedErrorInformation) == S_OK) | ||
| { | ||
| // This thread already has an error origination payload. Don't originate again if it has the same HRESULT that we are | ||
| // observing right now. | ||
| wil::unique_bstr descriptionUnused; | ||
| HRESULT existingHr = failure.hr; | ||
| wil::unique_bstr restrictedDescriptionUnused; | ||
| wil::unique_bstr capabilitySidUnused; | ||
| if (SUCCEEDED(restrictedErrorInformation->GetErrorDetails(&descriptionUnused, &existingHr, &restrictedDescriptionUnused, &capabilitySidUnused))) | ||
| { | ||
| shouldOriginate = (failure.hr != existingHr); | ||
| } | ||
| } | ||
|
|
||
| if (shouldOriginate) | ||
| { | ||
| #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) | ||
| wil::unique_hmodule errorModule; | ||
| if (GetModuleHandleExW(0, L"api-ms-win-core-winrt-error-l1-1-1.dll", &errorModule)) | ||
| { | ||
| auto pfn = reinterpret_cast<decltype(&::RoOriginateError)>(GetProcAddress(errorModule.get(), "RoOriginateError")); | ||
| if (pfn != nullptr) | ||
| { | ||
| pfn(failure.hr, nullptr); | ||
| } | ||
| } | ||
| #else // DESKTOP | SYSTEM | ||
| ::RoOriginateError(failure.hr, nullptr); | ||
| #endif // DESKTOP | SYSTEM | ||
| } | ||
| else if (restrictedErrorInformation) | ||
| { | ||
| // GetRestrictedErrorInfo returns ownership of the error information. If we aren't originating, and an error was already present, | ||
| // then we need to restore the error information for later observation. | ||
| SetRestrictedErrorInfo(restrictedErrorInformation.get()); | ||
| } | ||
| } | ||
| } | ||
| } // namespace details | ||
| } // namespace wil | ||
|
|
||
| // Automatically call RoOriginateError upon error origination by including this file | ||
| WI_HEADER_INITITALIZATION_FUNCTION(ResultStowedExceptionInitialize, [] | ||
| { | ||
| ::wil::SetOriginateErrorCallback(::wil::details::RaiseRoOriginateOnWilExceptions); | ||
| return 1; | ||
| }); | ||
| #endif // __cplusplus_winrt | ||
|
|
||
| #endif // __WIL_RESULT_ORIGINATE_INCLUDED |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| //********************************************************* | ||
| // | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // This code is licensed under the MIT License. | ||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF | ||
| // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED | ||
| // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | ||
| // PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
| // | ||
| //********************************************************* | ||
| #ifndef __WIL_RPC_HELPERS_INCLUDED | ||
| #define __WIL_RPC_HELPERS_INCLUDED | ||
|
|
||
| #include "result.h" | ||
| #include "resource.h" | ||
| #include "wistd_functional.h" | ||
| #include "wistd_type_traits.h" | ||
|
|
||
| namespace wil | ||
| { | ||
|
|
||
| /// @cond | ||
| namespace details | ||
| { | ||
| // This call-adapter template converts a void-returning 'wistd::invoke' into | ||
| // an HRESULT-returning 'wistd::invoke' that emits S_OK. It can be eliminated | ||
| // with 'if constexpr' when C++17 is in wide use. | ||
| template<typename TReturnType> struct call_adapter | ||
| { | ||
| template<typename... TArgs> static HRESULT call(TArgs&& ... args) | ||
| { | ||
| return wistd::invoke(wistd::forward<TArgs>(args)...); | ||
| } | ||
| }; | ||
|
|
||
| template<> struct call_adapter<void> | ||
| { | ||
| template<typename... TArgs> static HRESULT call(TArgs&& ... args) | ||
| { | ||
| wistd::invoke(wistd::forward<TArgs>(args)...); | ||
| return S_OK; | ||
| } | ||
| }; | ||
|
|
||
| // Some RPC exceptions are already HRESULTs. Others are in the regular Win32 | ||
| // error space. If the incoming exception code isn't an HRESULT, wrap it. | ||
| constexpr HRESULT map_rpc_exception(DWORD code) | ||
| { | ||
| return IS_ERROR(code) ? code : __HRESULT_FROM_WIN32(code); | ||
| } | ||
| } | ||
| /// @endcond | ||
|
|
||
| /** Invokes an RPC method, mapping structured exceptions to HRESULTs | ||
| Failures encountered by the RPC infrastructure (such as server crashes, authentication | ||
| errors, client parameter issues, etc.) are emitted by raising a structured exception from | ||
| within the RPC machinery. This method wraps the requested call in the usual RpcTryExcept, | ||
| RpcTryCatch, and RpcEndExcept sequence then maps the exceptions to HRESULTs for the usual | ||
| flow control machinery to use. | ||
| Many RPC methods are defined as returning HRESULT themselves, where the HRESULT indicates | ||
| the result of the _work_. HRESULTs returned by a successful completion of the _call_ are | ||
| returned as-is. | ||
| RPC methods that have a return type of 'void' are mapped to returning S_OK when the _call_ | ||
| completes successfully. | ||
| For example, consider an RPC interface method defined in idl as: | ||
| ~~~ | ||
| HRESULT GetKittenState([in, ref, string] const wchar_t* name, [out, retval] KittenState** state); | ||
| ~~~ | ||
| To call this method, use: | ||
| ~~~ | ||
| wil::unique_rpc_binding binding = // typically gotten elsewhere; | ||
| wil::unique_midl_ptr<KittenState> state; | ||
| HRESULT hr = wil::invoke_rpc_nothrow(GetKittenState, binding.get(), L"fluffy", state.put()); | ||
| RETURN_IF_FAILED(hr); | ||
| ~~~ | ||
| */ | ||
| template<typename... TCall> HRESULT invoke_rpc_nothrow(TCall&&... args) WI_NOEXCEPT | ||
| { | ||
| RpcTryExcept | ||
| { | ||
| // Note: this helper type can be removed with C++17 enabled via | ||
| // 'if constexpr(wistd::is_same_v<void, result_t>)' | ||
| using result_t = typename wistd::__invoke_of<TCall...>::type; | ||
| RETURN_IF_FAILED(details::call_adapter<result_t>::call(wistd::forward<TCall>(args)...)); | ||
| return S_OK; | ||
| } | ||
| RpcExcept(RpcExceptionFilter(RpcExceptionCode())) | ||
| { | ||
| RETURN_HR(details::map_rpc_exception(RpcExceptionCode())); | ||
| } | ||
| RpcEndExcept | ||
| } | ||
|
|
||
| /** Invokes an RPC method, mapping structured exceptions to HRESULTs | ||
| Failures encountered by the RPC infrastructure (such as server crashes, authentication | ||
| errors, client parameter issues, etc.) are emitted by raising a structured exception from | ||
| within the RPC machinery. This method wraps the requested call in the usual RpcTryExcept, | ||
| RpcTryCatch, and RpcEndExcept sequence then maps the exceptions to HRESULTs for the usual | ||
| flow control machinery to use. | ||
| Some RPC methods return results (such as a state enumeration or other value) directly in | ||
| their signature. This adapter writes that result into a caller-provided object then | ||
| returns S_OK. | ||
| For example, consider an RPC interface method defined in idl as: | ||
| ~~~ | ||
| GUID GetKittenId([in, ref, string] const wchar_t* name); | ||
| ~~~ | ||
| To call this method, use: | ||
| ~~~ | ||
| wil::unique_rpc_binding binding = // typically gotten elsewhere; | ||
| GUID id; | ||
| HRESULT hr = wil::invoke_rpc_result_nothrow(id, GetKittenId, binding.get(), L"fluffy"); | ||
| RETURN_IF_FAILED(hr); | ||
| ~~~ | ||
| */ | ||
| template<typename TResult, typename... TCall> HRESULT invoke_rpc_result_nothrow(TResult& result, TCall&&... args) WI_NOEXCEPT | ||
| { | ||
| RpcTryExcept | ||
| { | ||
| result = wistd::invoke(wistd::forward<TCall>(args)...); | ||
| return S_OK; | ||
| } | ||
| RpcExcept(RpcExceptionFilter(RpcExceptionCode())) | ||
| { | ||
| RETURN_HR(details::map_rpc_exception(RpcExceptionCode())); | ||
| } | ||
| RpcEndExcept | ||
| } | ||
|
|
||
| namespace details | ||
| { | ||
| // Provides an adapter around calling the context-handle-close method on an | ||
| // RPC interface, which itself is an RPC call. | ||
| template<typename TStorage, typename close_fn_t, close_fn_t close_fn> | ||
| struct rpc_closer_t | ||
| { | ||
| static void Close(TStorage arg) WI_NOEXCEPT | ||
| { | ||
| LOG_IF_FAILED(invoke_rpc_nothrow(close_fn, &arg)); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| /** Manages explicit RPC context handles | ||
| Explicit RPC context handles are used in many RPC interfaces. Most interfaces with | ||
| context handles have an explicit `FooClose([in, out] CONTEXT*)` method that lets | ||
| the server close out the context handle. As the close method itself is an RPC call, | ||
| it can fail and raise a structured exception. | ||
| This type routes the context-handle-specific `Close` call through the `invoke_rpc_nothrow` | ||
| helper, ensuring correct cleanup and lifecycle management. | ||
| ~~~ | ||
| // Assume the interface has two methods: | ||
| // HRESULT OpenFoo([in] handle_t binding, [out] FOO_CONTEXT*); | ||
| // HRESULT UseFoo([in] FOO_CONTEXT context; | ||
| // void CloseFoo([in, out] PFOO_CONTEXT); | ||
| using unique_foo_context = wil::unique_rpc_context_handle<FOO_CONTEXT, decltype(&CloseFoo), CloseFoo>; | ||
| unique_foo_context context; | ||
| RETURN_IF_FAILED(wil::invoke_rpc_nothrow(OpenFoo, m_binding.get(), context.put())); | ||
| RETURN_IF_FAILED(wil::invoke_rpc_nothrow(UseFoo, context.get())); | ||
| context.reset(); | ||
| ~~~ | ||
| */ | ||
| template<typename TContext, typename close_fn_t, close_fn_t close_fn> | ||
| using unique_rpc_context_handle = unique_any<TContext, decltype(&details::rpc_closer_t<TContext, close_fn_t, close_fn>::Close), details::rpc_closer_t<TContext, close_fn_t, close_fn>::Close>; | ||
|
|
||
| #ifdef WIL_ENABLE_EXCEPTIONS | ||
| /** Invokes an RPC method, mapping structured exceptions to C++ exceptions | ||
| See `wil::invoke_rpc_nothrow` for additional information. Failures during the _call_ | ||
| and those returned by the _method_ are mapped to HRESULTs and thrown inside a | ||
| wil::ResultException. Using the example RPC method provided above: | ||
| ~~~ | ||
| wil::unique_midl_ptr<KittenState> state; | ||
| wil::invoke_rpc(GetKittenState, binding.get(), L"fluffy", state.put()); | ||
| // use 'state' | ||
| ~~~ | ||
| */ | ||
| template<typename... TCall> void invoke_rpc(TCall&& ... args) | ||
| { | ||
| THROW_IF_FAILED(invoke_rpc_nothrow(wistd::forward<TCall>(args)...)); | ||
| } | ||
|
|
||
| /** Invokes an RPC method, mapping structured exceptions to C++ exceptions | ||
| See `wil::invoke_rpc_result_nothrow` for additional information. Failures during the | ||
| _call_ are mapped to HRESULTs and thrown inside a `wil::ResultException`. Using the | ||
| example RPC method provided above: | ||
| ~~~ | ||
| GUID id = wil::invoke_rpc_result(GetKittenId, binding.get()); | ||
| // use 'id' | ||
| ~~~ | ||
| */ | ||
| template<typename... TCall> auto invoke_rpc_result(TCall&& ... args) | ||
| { | ||
| using result_t = typename wistd::__invoke_of<TCall...>::type; | ||
| result_t result{}; | ||
| THROW_IF_FAILED(invoke_rpc_result_nothrow(result, wistd::forward<TCall>(args)...)); | ||
| return result; | ||
| } | ||
| #endif | ||
| } | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| //********************************************************* | ||
| // | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // This code is licensed under the MIT License. | ||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF | ||
| // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED | ||
| // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | ||
| // PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
| // | ||
| //********************************************************* | ||
| #ifndef __WIL_STL_INCLUDED | ||
| #define __WIL_STL_INCLUDED | ||
|
|
||
| #include "common.h" | ||
| #include "resource.h" | ||
| #include <memory> | ||
| #include <string> | ||
|
|
||
| #if defined(WIL_ENABLE_EXCEPTIONS) | ||
| namespace std | ||
| { | ||
| template<class _Ty, class _Alloc> | ||
| class vector; | ||
|
|
||
| template<class _Elem> | ||
| struct char_traits; | ||
|
|
||
| template<class _Elem, class _Traits, class _Alloc> | ||
| class basic_string; | ||
| } // namespace std | ||
|
|
||
| namespace wil | ||
| { | ||
| /** Secure allocator for STL containers. | ||
| The `wil::secure_allocator` allocator calls `SecureZeroMemory` before deallocating | ||
| memory. This provides a mechanism for secure STL containers such as `wil::secure_vector`, | ||
| `wil::secure_string`, and `wil::secure_wstring`. */ | ||
| template <typename T> | ||
| struct secure_allocator | ||
| : public std::allocator<T> | ||
| { | ||
| template<typename Other> | ||
| struct rebind | ||
| { | ||
| typedef secure_allocator<Other> other; | ||
| }; | ||
|
|
||
| secure_allocator() | ||
| : std::allocator<T>() | ||
| { | ||
| } | ||
|
|
||
| ~secure_allocator() = default; | ||
|
|
||
| secure_allocator(const secure_allocator& a) | ||
| : std::allocator<T>(a) | ||
| { | ||
| } | ||
|
|
||
| template <class U> | ||
| secure_allocator(const secure_allocator<U>& a) | ||
| : std::allocator<T>(a) | ||
| { | ||
| } | ||
|
|
||
| T* allocate(size_t n) | ||
| { | ||
| return std::allocator<T>::allocate(n); | ||
| } | ||
|
|
||
| void deallocate(T* p, size_t n) | ||
| { | ||
| SecureZeroMemory(p, sizeof(T) * n); | ||
| std::allocator<T>::deallocate(p, n); | ||
| } | ||
| }; | ||
|
|
||
| //! `wil::secure_vector` will be securely zeroed before deallocation. | ||
| template <typename Type> | ||
| using secure_vector = std::vector<Type, secure_allocator<Type>>; | ||
| //! `wil::secure_wstring` will be securely zeroed before deallocation. | ||
| using secure_wstring = std::basic_string<wchar_t, std::char_traits<wchar_t>, wil::secure_allocator<wchar_t>>; | ||
| //! `wil::secure_string` will be securely zeroed before deallocation. | ||
| using secure_string = std::basic_string<char, std::char_traits<char>, wil::secure_allocator<char>>; | ||
|
|
||
| /// @cond | ||
| namespace details | ||
| { | ||
| template<> struct string_maker<std::wstring> | ||
| { | ||
| HRESULT make(_In_reads_opt_(length) PCWSTR source, size_t length) WI_NOEXCEPT try | ||
| { | ||
| m_value = source ? std::wstring(source, length) : std::wstring(length, L'\0'); | ||
| return S_OK; | ||
| } | ||
| catch (...) | ||
| { | ||
| return E_OUTOFMEMORY; | ||
| } | ||
|
|
||
| wchar_t* buffer() { return &m_value[0]; } | ||
|
|
||
| std::wstring release() { return std::wstring(std::move(m_value)); } | ||
|
|
||
| static PCWSTR get(const std::wstring& value) { return value.c_str(); } | ||
|
|
||
| private: | ||
| std::wstring m_value; | ||
| }; | ||
| } | ||
| /// @endcond | ||
|
|
||
| // str_raw_ptr is an overloaded function that retrieves a const pointer to the first character in a string's buffer. | ||
| // This is the overload for std::wstring. Other overloads available in resource.h. | ||
| inline PCWSTR str_raw_ptr(const std::wstring& str) | ||
| { | ||
| return str.c_str(); | ||
| } | ||
|
|
||
| } // namespace wil | ||
|
|
||
| #endif // WIL_ENABLE_EXCEPTIONS | ||
|
|
||
| #endif // __WIL_STL_INCLUDED |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| //********************************************************* | ||
| // | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // This code is licensed under the MIT License. | ||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF | ||
| // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED | ||
| // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | ||
| // PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
| // | ||
| //********************************************************* | ||
| #ifndef __WIL_WRL_INCLUDED | ||
| #define __WIL_WRL_INCLUDED | ||
|
|
||
| #include <wrl.h> | ||
| #include "result.h" | ||
| #include "common.h" // wistd type_traits helpers | ||
|
|
||
| namespace wil | ||
| { | ||
|
|
||
| #ifdef WIL_ENABLE_EXCEPTIONS | ||
| #pragma region Object construction helpers that throw exceptions | ||
|
|
||
| /** Used to construct a RuntimeClass based object that uses 2 phase construction. | ||
| Construct a RuntimeClass based object that uses 2 phase construction (by implementing | ||
| RuntimeClassInitialize() and returning error codes for failures. | ||
| ~~~~ | ||
| // SomeClass uses 2 phase initialization by implementing RuntimeClassInitialize() | ||
| auto someClass = MakeAndInitializeOrThrow<SomeClass>(L"input", true); | ||
| ~~~~ */ | ||
|
|
||
| template <typename T, typename... TArgs> | ||
| Microsoft::WRL::ComPtr<T> MakeAndInitializeOrThrow(TArgs&&... args) | ||
| { | ||
| Microsoft::WRL::ComPtr<T> obj; | ||
| THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<T>(&obj, Microsoft::WRL::Details::Forward<TArgs>(args)...)); | ||
| return obj; | ||
| } | ||
|
|
||
| /** Used to construct an RuntimeClass based object that uses exceptions in its constructor (and does | ||
| not require 2 phase construction). | ||
| ~~~~ | ||
| // SomeClass uses exceptions for error handling in its constructor. | ||
| auto someClass = MakeOrThrow<SomeClass>(L"input", true); | ||
| ~~~~ */ | ||
|
|
||
| template <typename T, typename... TArgs> | ||
| Microsoft::WRL::ComPtr<T> MakeOrThrow(TArgs&&... args) | ||
| { | ||
| // This is how you can detect the presence of RuntimeClassInitialize() and find dangerous use. | ||
| // Unfortunately this produces false positives as all RuntimeClass derived classes have | ||
| // a RuntimeClassInitialize() method from their base class. | ||
| // static_assert(!std::is_member_function_pointer<decltype(&T::RuntimeClassInitialize)>::value, | ||
| // "class has a RuntimeClassInitialize member, use MakeAndInitializeOrThrow instead"); | ||
| auto obj = Microsoft::WRL::Make<T>(Microsoft::WRL::Details::Forward<TArgs>(args)...); | ||
| THROW_IF_NULL_ALLOC(obj.Get()); | ||
| return obj; | ||
| } | ||
| #pragma endregion | ||
|
|
||
| #endif // WIL_ENABLE_EXCEPTIONS | ||
|
|
||
| /** By default WRL Callback objects are not agile, use this to make an agile one. Replace use of Callback<> with MakeAgileCallback<>. | ||
| Will return null on failure, translate that into E_OUTOFMEMORY using XXX_IF_NULL_ALLOC() | ||
| from wil\result.h to test the result. */ | ||
| template<typename TDelegateInterface, typename ...Args> | ||
| ::Microsoft::WRL::ComPtr<TDelegateInterface> MakeAgileCallbackNoThrow(Args&&... args) WI_NOEXCEPT | ||
| { | ||
| using namespace Microsoft::WRL; | ||
| return Callback<Implements<RuntimeClassFlags<ClassicCom>, TDelegateInterface, FtmBase>>(wistd::forward<Args>(args)...); | ||
| } | ||
|
|
||
| #ifdef WIL_ENABLE_EXCEPTIONS | ||
| template<typename TDelegateInterface, typename ...Args> | ||
| ::Microsoft::WRL::ComPtr<TDelegateInterface> MakeAgileCallback(Args&&... args) | ||
| { | ||
| auto result = MakeAgileCallbackNoThrow<TDelegateInterface, Args...>(wistd::forward<Args>(args)...); | ||
| THROW_IF_NULL_ALLOC(result); | ||
| return result; | ||
| } | ||
| #endif // WIL_ENABLE_EXCEPTIONS | ||
| } // namespace wil | ||
|
|
||
| #endif // __WIL_WRL_INCLUDED |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
|
|
||
| add_subdirectory(nuget) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
|
|
||
| file(TO_NATIVE_PATH "${CMAKE_BINARY_DIR}/build_tools/nuget.exe" nuget_exe) | ||
| file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}" nupkg_dir) | ||
| file(TO_NATIVE_PATH "${nupkg_dir}/Microsoft.Windows.ImplementationLibrary.${WIL_BUILD_VERSION}.nupkg" wil_nupkg) | ||
|
|
||
| # The build servers don't have an up-to-date version of nuget, so pull it down ourselves... | ||
| file(DOWNLOAD https://dist.nuget.org/win-x86-commandline/latest/nuget.exe ${nuget_exe}) | ||
|
|
||
| file(GLOB_RECURSE wil_headers ${CMAKE_SOURCE_DIR}/include/*.h) | ||
|
|
||
| add_custom_command(OUTPUT ${wil_nupkg} | ||
| COMMAND ${nuget_exe} pack ${CMAKE_CURRENT_SOURCE_DIR}/Microsoft.Windows.ImplementationLibrary.nuspec -OutputDirectory ${nupkg_dir} -Version ${WIL_BUILD_VERSION} -NonInteractive | ||
| DEPENDS | ||
| ${CMAKE_CURRENT_SOURCE_DIR}/Microsoft.Windows.ImplementationLibrary.nuspec | ||
| ${CMAKE_CURRENT_SOURCE_DIR}/Microsoft.Windows.ImplementationLibrary.targets | ||
| ${wil_headers} | ||
| ${CMAKE_SOURCE_DIR}/LICENSE | ||
| ${CMAKE_SOURCE_DIR}/ThirdPartyNotices.txt) | ||
|
|
||
| add_custom_target(make_wil_nupkg DEPENDS ${wil_nupkg}) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| <?xml version="1.0"?> | ||
| <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> | ||
| <metadata> | ||
| <id>Microsoft.Windows.ImplementationLibrary</id> | ||
| <version>0.0.0</version> | ||
| <title>Windows Implementation Library</title> | ||
| <authors>Microsoft</authors> | ||
| <requireLicenseAcceptance>false</requireLicenseAcceptance> | ||
| <description>The Windows Implementation Libraries (wil) were created to improve productivity and solve problems commonly seen by Windows developers.</description> | ||
| <tags>windows utility wil native</tags> | ||
| <copyright>© Microsoft Corporation. All rights reserved.</copyright> | ||
| <license type="file">LICENSE</license> | ||
| <projectUrl>https://github.com/Microsoft/wil</projectUrl> | ||
| </metadata> | ||
| <files> | ||
| <file src="..\..\LICENSE"/> | ||
| <file src="..\..\ThirdPartyNotices.txt"/> | ||
| <file src="..\..\include\**" target="include\" /> | ||
| <file src="Microsoft.Windows.ImplementationLibrary.targets" target="build\native\" /> | ||
| </files> | ||
| </package> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <?xml version="1.0"?> | ||
| <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
| <ItemDefinitionGroup> | ||
| <ClCompile> | ||
| <AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | ||
| </ClCompile> | ||
| </ItemDefinitionGroup> | ||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # Windows Implementation Library Pipeline | ||
|
|
||
| trigger: | ||
| - master | ||
|
|
||
| jobs: | ||
| - job: BuildAndTest | ||
| timeoutInMinutes: 360 | ||
|
|
||
| pool: | ||
| vmImage: 'windows-2019' | ||
|
|
||
| steps: | ||
| - script: | | ||
| choco install llvm | ||
| if %ERRORLEVEL% NEQ 0 goto :eof | ||
| echo ##vso[task.setvariable variable=PATH]%PATH%;C:\Program Files\LLVM\bin | ||
| displayName: 'Install Clang' | ||
| - script: | | ||
| call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars32.bat" | ||
| if %ERRORLEVEL% NEQ 0 goto :eof | ||
| call scripts\init_all.cmd --fast | ||
| if %ERRORLEVEL% NEQ 0 goto :eof | ||
| call scripts\build_all.cmd | ||
| displayName: 'Build x86' | ||
| - script: | | ||
| call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" | ||
| if %ERRORLEVEL% NEQ 0 goto :eof | ||
| call scripts\init_all.cmd --fast | ||
| if %ERRORLEVEL% NEQ 0 goto :eof | ||
| call scripts\build_all.cmd | ||
| displayName: 'Build x64' | ||
| - script: call scripts\runtests.cmd | ||
| displayName: 'Run Tests' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| @echo off | ||
| setlocal EnableDelayedExpansion | ||
|
|
||
| set BUILD_ROOT=%~dp0\..\build | ||
|
|
||
| if "%Platform%"=="x64" ( | ||
| set BUILD_ARCH=64 | ||
| ) else if "%Platform%"=="x86" ( | ||
| set BUILD_ARCH=32 | ||
| ) else if [%Platform%]==[] ( | ||
| echo ERROR: The build_all.cmd script must be run from a Visual Studio command window | ||
| exit /B 1 | ||
| ) else ( | ||
| echo ERROR: Unrecognized/unsupported platform %Platform% | ||
| exit /B 1 | ||
| ) | ||
|
|
||
| call :build clang debug | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :build clang release | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :build clang relwithdebinfo | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :build clang minsizerel | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
|
|
||
| call :build msvc debug | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :build msvc release | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :build msvc relwithdebinfo | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :build msvc minsizerel | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
|
|
||
| echo All build completed successfully! | ||
|
|
||
| goto :eof | ||
|
|
||
| :: build [compiler] [type] | ||
| :build | ||
| set BUILD_DIR=%BUILD_ROOT%\%1%BUILD_ARCH%%2 | ||
| if not exist %BUILD_DIR% ( | ||
| goto :eof | ||
| ) | ||
|
|
||
| pushd %BUILD_DIR% | ||
| echo Building from %CD% | ||
| ninja | ||
| popd | ||
| goto :eof |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| @echo off | ||
| setlocal | ||
| setlocal EnableDelayedExpansion | ||
|
|
||
| :: Globals | ||
| set BUILD_ROOT=%~dp0\..\build | ||
|
|
||
| goto :init | ||
|
|
||
| :usage | ||
| echo USAGE: | ||
| echo init.cmd [--help] [-c^|--compiler ^<clang^|msvc^>] [-g^|--generator ^<ninja^|msbuild^>] | ||
| echo [-b^|--build-type ^<debug^|release^|relwithdebinfo^|minsizerel^>] [-l^|--linker link^|lld-link] | ||
| echo [--fast] [-v^|--version X.Y.Z] | ||
| echo. | ||
| echo ARGUMENTS | ||
| echo -c^|--compiler Controls the compiler used, either 'clang' (the default) or 'msvc' | ||
| echo -g^|--generator Controls the CMake generator used, either 'ninja' (the default) or 'msbuild' | ||
| echo -b^|--build-type Controls the value of 'CMAKE_BUILD_TYPE', either 'debug' (the default), 'release', | ||
| echo 'relwithdebinfo', or 'minsizerel' | ||
| echo -v^|--version Specifies the version of the NuGet package produced. Primarily only used by the CI | ||
| echo build and is typically not necessary when building locally | ||
| echo --fast Used to (slightly) reduce compile times and build output size. This is primarily | ||
| echo used by the CI build machines where resources are more constrained. This switch is | ||
| echo temporary and will be removed once https://github.com/microsoft/wil/issues/9 is fixed | ||
| goto :eof | ||
|
|
||
| :init | ||
| :: Initialize values as empty so that we can identify if we are using defaults later for error purposes | ||
| set COMPILER= | ||
| set GENERATOR= | ||
| set BUILD_TYPE= | ||
| set LINKER= | ||
| set CMAKE_ARGS= | ||
| set BITNESS= | ||
| set VERSION= | ||
| set FAST_BUILD=0 | ||
|
|
||
| :parse | ||
| if /I "%~1"=="" goto :execute | ||
|
|
||
| if /I "%~1"=="--help" call :usage & goto :eof | ||
|
|
||
| set COMPILER_SET=0 | ||
| if /I "%~1"=="-c" set COMPILER_SET=1 | ||
| if /I "%~1"=="--compiler" set COMPILER_SET=1 | ||
| if %COMPILER_SET%==1 ( | ||
| if "%COMPILER%" NEQ "" echo ERROR: Compiler already specified & call :usage & exit /B 1 | ||
|
|
||
| if /I "%~2"=="clang" set COMPILER=clang | ||
| if /I "%~2"=="msvc" set COMPILER=msvc | ||
| if "!COMPILER!"=="" echo ERROR: Unrecognized/missing compiler %~2 & call :usage & exit /B 1 | ||
|
|
||
| shift | ||
| shift | ||
| goto :parse | ||
| ) | ||
|
|
||
| set GENERATOR_SET=0 | ||
| if /I "%~1"=="-g" set GENERATOR_SET=1 | ||
| if /I "%~1"=="--generator" set GENERATOR_SET=1 | ||
| if %GENERATOR_SET%==1 ( | ||
| if "%GENERATOR%" NEQ "" echo ERROR: Generator already specified & call :usage & exit /B 1 | ||
|
|
||
| if /I "%~2"=="ninja" set GENERATOR=ninja | ||
| if /I "%~2"=="msbuild" set GENERATOR=msbuild | ||
| if "!GENERATOR!"=="" echo ERROR: Unrecognized/missing generator %~2 & call :usage & exit /B 1 | ||
|
|
||
| shift | ||
| shift | ||
| goto :parse | ||
| ) | ||
|
|
||
| set BUILD_TYPE_SET=0 | ||
| if /I "%~1"=="-b" set BUILD_TYPE_SET=1 | ||
| if /I "%~1"=="--build-type" set BUILD_TYPE_SET=1 | ||
| if %BUILD_TYPE_SET%==1 ( | ||
| if "%BUILD_TYPE%" NEQ "" echo ERROR: Build type already specified & call :usage & exit /B 1 | ||
|
|
||
| if /I "%~2"=="debug" set BUILD_TYPE=debug | ||
| if /I "%~2"=="release" set BUILD_TYPE=release | ||
| if /I "%~2"=="relwithdebinfo" set BUILD_TYPE=relwithdebinfo | ||
| if /I "%~2"=="minsizerel" set BUILD_TYPE=minsizerel | ||
| if "!BUILD_TYPE!"=="" echo ERROR: Unrecognized/missing build type %~2 & call :usage & exit /B 1 | ||
|
|
||
| shift | ||
| shift | ||
| goto :parse | ||
| ) | ||
|
|
||
| set LINKER_SET=0 | ||
| if /I "%~1"=="-l" set LINKER_SET=1 | ||
| if /I "%~1"=="--linker" set LINKER_SET=1 | ||
| if %LINKER_SET%==1 ( | ||
| if "%LINKER%" NEQ "" echo ERROR: Linker already specified & call :usage & exit /B 1 | ||
|
|
||
| if /I "%~2"=="link" set LINKER=link | ||
| if /I "%~2"=="lld-link" set LINKER=lld-link | ||
| if "!LINKER!"=="" echo ERROR: Unrecognized/missing linker %~2 & call :usage & exit /B 1 | ||
|
|
||
| shift | ||
| shift | ||
| goto :parse | ||
| ) | ||
|
|
||
| set VERSION_SET=0 | ||
| if /I "%~1"=="-v" set VERSION_SET=1 | ||
| if /I "%~1"=="--version" set VERSION_SET=1 | ||
| if %VERSION_SET%==1 ( | ||
| if "%VERSION%" NEQ "" echo ERROR: Version alread specified & call :usage & exit /B 1 | ||
| if /I "%~2"=="" echo ERROR: Version string missing & call :usage & exit /B 1 | ||
|
|
||
| set VERSION=%~2 | ||
|
|
||
| shift | ||
| shift | ||
| goto :parse | ||
| ) | ||
|
|
||
| if /I "%~1"=="--fast" ( | ||
| if %FAST_BUILD% NEQ 0 echo ERROR: Fast build already specified | ||
| set FAST_BUILD=1 | ||
| shift | ||
| goto :parse | ||
| ) | ||
|
|
||
| echo ERROR: Unrecognized argument %~1 | ||
| call :usage | ||
| exit /B 1 | ||
|
|
||
| :execute | ||
| :: Check for conflicting arguments | ||
| if "%GENERATOR%"=="msbuild" ( | ||
| if "%COMPILER%"=="clang" echo ERROR: Cannot use Clang with MSBuild & exit /B 1 | ||
|
|
||
| :: While CMake won't give an error, specifying the linker won't actually have any effect with the VS generator | ||
| if "%LINKER%"=="lld-link" echo ERROR: Cannot use lld-link with MSBuild & exit /B 1 | ||
| ) | ||
|
|
||
| :: Select defaults | ||
| if "%GENERATOR%"=="" set GENERATOR=ninja | ||
| if %GENERATOR%==msbuild set COMPILER=msvc | ||
|
|
||
| if "%COMPILER%"=="" set COMPILER=clang | ||
|
|
||
| if "%BUILD_TYPE%"=="" set BUILD_TYPE=debug | ||
|
|
||
| if "%LINKER%"=="" set LINKER=link | ||
|
|
||
| :: Formulate CMake arguments | ||
| if %GENERATOR%==ninja set CMAKE_ARGS=%CMAKE_ARGS% -G Ninja | ||
|
|
||
| if %COMPILER%==clang set CMAKE_ARGS=%CMAKE_ARGS% -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl | ||
| if %COMPILER%==msvc set CMAKE_ARGS=%CMAKE_ARGS% -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl | ||
|
|
||
| if %GENERATOR% NEQ msbuild ( | ||
| if %BUILD_TYPE%==debug set CMAKE_ARGS=%CMAKE_ARGS% -DCMAKE_BUILD_TYPE=Debug | ||
| if %BUILD_TYPE%==release set CMAKE_ARGS=%CMAKE_ARGS% -DCMAKE_BUILD_TYPE=Release | ||
| if %BUILD_TYPE%==relwithdebinfo set CMAKE_ARGS=%CMAKE_ARGS% -DCMAKE_BUILD_TYPE=RelWithDebInfo | ||
| if %BUILD_TYPE%==minsizerel set CMAKE_ARGS=%CMAKE_ARGS% -DCMAKE_BUILD_TYPE=MinSizeRel | ||
| ) else ( | ||
| :: The Visual Studio generator, by default, will use the most recent Windows SDK version installed that is not | ||
| :: greater than the host OS version. This decision is to ensure that anything built will be able to run on the | ||
| :: machine that built it. This experience is generally okay, if not desired, but affects us since we build with | ||
| :: '/permissive-' etc. and older versions of the SDK are typically not as clean as the most recent versions. | ||
| :: This flag will force the generator to select the most recent SDK version independent of host OS version. | ||
| set CMAKE_ARGS=%CMAKE_ARGS% -DCMAKE_SYSTEM_VERSION=10.0 | ||
| ) | ||
|
|
||
| if %LINKER%==lld-link ( | ||
| set CMAKE_ARGS=%CMAKE_ARGS% -DCMAKE_LINKER=lld-link | ||
| ) | ||
|
|
||
| if "%VERSION%" NEQ "" set CMAKE_ARGS=%CMAKE_ARGS% -DWIL_BUILD_VERSION=%VERSION% | ||
|
|
||
| if %FAST_BUILD%==1 set CMAKE_ARGS=%CMAKE_ARGS% -DFAST_BUILD=ON | ||
|
|
||
| :: Figure out the platform | ||
| if "%Platform%"=="" echo ERROR: The init.cmd script must be run from a Visual Studio command window & exit /B 1 | ||
| if "%Platform%"=="x86" ( | ||
| set BITNESS=32 | ||
| if %COMPILER%==clang set CFLAGS=-m32 & set CXXFLAGS=-m32 | ||
| ) | ||
| if "%Platform%"=="x64" set BITNESS=64 | ||
| if "%BITNESS%"=="" echo ERROR: Unrecognized/unsupported platform %Platform% & exit /B 1 | ||
|
|
||
| :: Set up the build directory | ||
| set BUILD_DIR=%BUILD_ROOT%\%COMPILER%%BITNESS%%BUILD_TYPE% | ||
| mkdir %BUILD_DIR% > NUL 2>&1 | ||
|
|
||
| :: Run CMake | ||
| pushd %BUILD_DIR% | ||
| echo Using compiler....... %COMPILER% | ||
| echo Using linker......... %LINKER% | ||
| echo Using architecture... %Platform% | ||
| echo Using build type..... %BUILD_TYPE% | ||
| echo Using build root..... %CD% | ||
| echo. | ||
| cmake %CMAKE_ARGS% ..\.. | ||
| popd | ||
|
|
||
| goto :eof |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| @echo off | ||
|
|
||
| :: NOTE: Architecture is picked up from the command window, so we can't control that here :( | ||
|
|
||
| call %~dp0\init.cmd -c clang -g ninja -b debug %* | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call %~dp0\init.cmd -c clang -g ninja -b relwithdebinfo %* | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
|
|
||
| call %~dp0\init.cmd -c msvc -g ninja -b debug %* | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call %~dp0\init.cmd -c msvc -g ninja -b relwithdebinfo %* | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| @echo off | ||
| setlocal EnableDelayedExpansion | ||
|
|
||
| set BUILD_ROOT=%~dp0\..\build | ||
|
|
||
| :: Unlike building, we don't need to limit ourselves to the Platform of the command window | ||
| call :execute_tests clang64debug | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :execute_tests clang64release | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :execute_tests clang64relwithdebinfo | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :execute_tests clang64minsizerel | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
|
|
||
| call :execute_tests clang32debug | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :execute_tests clang32release | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :execute_tests clang32relwithdebinfo | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :execute_tests clang32minsizerel | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
|
|
||
| call :execute_tests msvc64debug | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :execute_tests msvc64release | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :execute_tests msvc64relwithdebinfo | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :execute_tests msvc64minsizerel | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
|
|
||
| call :execute_tests msvc32debug | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :execute_tests msvc32release | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :execute_tests msvc32relwithdebinfo | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
| call :execute_tests msvc32minsizerel | ||
| if %ERRORLEVEL% NEQ 0 ( goto :eof ) | ||
|
|
||
| goto :eof | ||
|
|
||
| :execute_tests | ||
| set BUILD_DIR=%BUILD_ROOT%\%1 | ||
| if not exist %BUILD_DIR% ( goto :eof ) | ||
|
|
||
| pushd %BUILD_DIR% | ||
| echo Running tests from %CD% | ||
| call :execute_test app witest.app.exe | ||
| if %ERRORLEVEL% NEQ 0 ( popd && goto :eof ) | ||
| call :execute_test cpplatest witest.cpplatest.exe | ||
| if %ERRORLEVEL% NEQ 0 ( popd && goto :eof ) | ||
| call :execute_test noexcept witest.noexcept.exe | ||
| if %ERRORLEVEL% NEQ 0 ( popd && goto :eof ) | ||
| call :execute_test normal witest.exe | ||
| if %ERRORLEVEL% NEQ 0 ( popd && goto :eof ) | ||
| popd | ||
|
|
||
| goto :eof | ||
|
|
||
| :execute_test | ||
| if not exist tests\%1\%2 ( goto :eof ) | ||
| echo Running %1 tests... | ||
| tests\%1\%2 | ||
| goto :eof |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
|
|
||
| include(${CMAKE_SOURCE_DIR}/cmake/common_build_flags.cmake) | ||
|
|
||
| # All projects need to reference the WIL headers | ||
| include_directories(${CMAKE_SOURCE_DIR}/include) | ||
|
|
||
| # TODO: Might be worth trying to conditionally do this on SDK version, assuming there's a semi-easy way to detect that | ||
| include_directories(BEFORE SYSTEM ./workarounds/wrl) | ||
|
|
||
| # The build pipelines have limitations that local development environments do not, so turn a few knobs | ||
| if (${FAST_BUILD}) | ||
| replace_cxx_flag("/GR" "/GR-") # Disables RTTI | ||
| add_definitions(-DCATCH_CONFIG_FAST_COMPILE -DWIL_FAST_BUILD) | ||
| endif() | ||
|
|
||
| add_subdirectory(app) | ||
| add_subdirectory(cpplatest) | ||
| add_subdirectory(noexcept) | ||
| add_subdirectory(normal) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,243 @@ | ||
|
|
||
| #include <wil/common.h> | ||
| #include <wil/resource.h> | ||
| #include <wrl/client.h> | ||
|
|
||
| #include "common.h" | ||
|
|
||
| TEST_CASE("CommonTests::OutParamHelpers", "[common]") | ||
| { | ||
| int i = 2; | ||
| int *pOutTest = &i; | ||
| int *pNullTest = nullptr; | ||
|
|
||
| SECTION("Value type") | ||
| { | ||
| wil::assign_to_opt_param(pNullTest, 3); | ||
| wil::assign_to_opt_param(pOutTest, 3); | ||
| REQUIRE(*pOutTest == 3); | ||
| } | ||
|
|
||
| SECTION("Pointer to value type") | ||
| { | ||
| int **ppOutTest = &pOutTest; | ||
| int **ppNullTest = nullptr; | ||
| wil::assign_null_to_opt_param(ppNullTest); | ||
| wil::assign_null_to_opt_param(ppOutTest); | ||
| REQUIRE(*ppOutTest == nullptr); | ||
| } | ||
|
|
||
| SECTION("COM out pointer") | ||
| { | ||
| Microsoft::WRL::ComPtr<IUnknown> spUnk; | ||
| IUnknown **ppunkNull = nullptr; | ||
| IUnknown *pUnk = reinterpret_cast<IUnknown *>(1); | ||
| IUnknown **ppUnkValid = &pUnk; | ||
|
|
||
| wil::detach_to_opt_param(ppunkNull, spUnk); | ||
| wil::detach_to_opt_param(ppUnkValid, spUnk); | ||
| REQUIRE(*ppUnkValid == nullptr); | ||
| } | ||
| } | ||
|
|
||
| TEST_CASE("CommonTests::TypeValidation", "[common]") | ||
| { | ||
| std::unique_ptr<BYTE> boolCastClass; | ||
| std::vector<int> noBoolCastClass; | ||
| HRESULT hr = S_OK; | ||
| BOOL bigBool = true; | ||
| bool smallBool = true; | ||
| DWORD dword = 1; | ||
| Microsoft::WRL::ComPtr<IUnknown> comPtr; | ||
| (void)dword; | ||
|
|
||
| // NOTE: The commented out verify* calls should give compilation errors | ||
| SECTION("verify_bool") | ||
| { | ||
| REQUIRE(wil::verify_bool(smallBool)); | ||
| REQUIRE(wil::verify_bool(bigBool)); | ||
| REQUIRE_FALSE(wil::verify_bool(boolCastClass)); | ||
| REQUIRE_FALSE(wil::verify_bool(comPtr)); | ||
| //wil::verify_bool(noBoolCastClass); | ||
| //wil::verify_bool(dword); | ||
| //wil::verify_bool(hr); | ||
| } | ||
|
|
||
| SECTION("verify_hresult") | ||
| { | ||
| //wil::verify_hresult(smallBool); | ||
| //wil::verify_hresult(bigBool); | ||
| //wil::verify_hresult(boolCastClass); | ||
| //wil::verify_hresult(noBoolCastClass); | ||
| //wil::verify_hresult(dword); | ||
| //wil::verify_hresult(comPtr); | ||
| REQUIRE(wil::verify_hresult(hr) == S_OK); | ||
| } | ||
|
|
||
| SECTION("verify_BOOL") | ||
| { | ||
| //wil::verify_BOOL(smallBool); | ||
| REQUIRE(wil::verify_BOOL(bigBool)); | ||
| //wil::verify_BOOL(boolCastClass); | ||
| //wil::verify_BOOL(noBoolCastClass); | ||
| //wil::verify_BOOL(dword); | ||
| //wil::verify_BOOL(comPtr); | ||
| //wil::verify_BOOL(hr); | ||
| } | ||
| } | ||
|
|
||
| template <typename T> | ||
| static void FlagsMacrosNonStatic(T none, T one, T two, T three, T four) | ||
| { | ||
| T eval = one | four; | ||
|
|
||
| REQUIRE(WI_AreAllFlagsSet(MDEC(eval), MDEC(one | four))); | ||
| REQUIRE_FALSE(WI_AreAllFlagsSet(eval, one | three)); | ||
| REQUIRE_FALSE(WI_AreAllFlagsSet(eval, three | two)); | ||
| REQUIRE(WI_AreAllFlagsSet(eval, none)); | ||
|
|
||
| REQUIRE(WI_IsAnyFlagSet(MDEC(eval), MDEC(one))); | ||
| REQUIRE(WI_IsAnyFlagSet(eval, one | four | three)); | ||
| REQUIRE_FALSE(WI_IsAnyFlagSet(eval, two)); | ||
|
|
||
| REQUIRE(WI_AreAllFlagsClear(MDEC(eval), MDEC(three))); | ||
| REQUIRE(WI_AreAllFlagsClear(eval, three | two)); | ||
| REQUIRE_FALSE(WI_AreAllFlagsClear(eval, one | four)); | ||
| REQUIRE_FALSE(WI_AreAllFlagsClear(eval, one | three)); | ||
|
|
||
| REQUIRE(WI_IsAnyFlagClear(MDEC(eval), MDEC(three))); | ||
| REQUIRE(WI_IsAnyFlagClear(eval, three | two)); | ||
| REQUIRE(WI_IsAnyFlagClear(eval, four | three)); | ||
| REQUIRE_FALSE(WI_IsAnyFlagClear(eval, one)); | ||
| REQUIRE_FALSE(WI_IsAnyFlagClear(eval, one | four)); | ||
|
|
||
| REQUIRE_FALSE(WI_IsSingleFlagSet(MDEC(eval))); | ||
| REQUIRE(WI_IsSingleFlagSet(eval & one)); | ||
|
|
||
| REQUIRE(WI_IsSingleFlagSetInMask(MDEC(eval), MDEC(one))); | ||
| REQUIRE(WI_IsSingleFlagSetInMask(eval, one | three)); | ||
| REQUIRE_FALSE(WI_IsSingleFlagSetInMask(eval, three)); | ||
| REQUIRE_FALSE(WI_IsSingleFlagSetInMask(eval, one | four)); | ||
|
|
||
| REQUIRE_FALSE(WI_IsClearOrSingleFlagSet(MDEC(eval))); | ||
| REQUIRE(WI_IsClearOrSingleFlagSet(eval & one)); | ||
| REQUIRE(WI_IsClearOrSingleFlagSet(none)); | ||
|
|
||
| REQUIRE(WI_IsClearOrSingleFlagSetInMask(MDEC(eval), MDEC(one))); | ||
| REQUIRE(WI_IsClearOrSingleFlagSetInMask(eval, one | three)); | ||
| REQUIRE(WI_IsClearOrSingleFlagSetInMask(eval, three)); | ||
| REQUIRE_FALSE(WI_IsClearOrSingleFlagSetInMask(eval, one | four)); | ||
|
|
||
| eval = none; | ||
| WI_SetAllFlags(MDEC(eval), MDEC(one)); | ||
| REQUIRE(eval == one); | ||
| WI_SetAllFlags(eval, one | two); | ||
| REQUIRE(eval == (one | two)); | ||
|
|
||
| eval = one | two; | ||
| WI_ClearAllFlags(MDEC(eval), one); | ||
| REQUIRE(eval == two); | ||
| WI_ClearAllFlags(eval, two); | ||
| REQUIRE(eval == none); | ||
|
|
||
| eval = one | two; | ||
| WI_UpdateFlagsInMask(MDEC(eval), MDEC(two | three), MDEC(three | four)); | ||
| REQUIRE(eval == (one | three)); | ||
|
|
||
| eval = one; | ||
| WI_ToggleAllFlags(MDEC(eval), MDEC(one | two)); | ||
| REQUIRE(eval == two); | ||
| } | ||
|
|
||
| enum class EClassTest | ||
| { | ||
| None = 0x0, | ||
| One = 0x1, | ||
| Two = 0x2, | ||
| Three = 0x4, | ||
| Four = 0x8, | ||
| }; | ||
| DEFINE_ENUM_FLAG_OPERATORS(EClassTest); | ||
|
|
||
| enum ERawTest | ||
| { | ||
| ER_None = 0x0, | ||
| ER_One = 0x1, | ||
| ER_Two = 0x2, | ||
| ER_Three = 0x4, | ||
| ER_Four = 0x8, | ||
| }; | ||
| DEFINE_ENUM_FLAG_OPERATORS(ERawTest); | ||
|
|
||
| TEST_CASE("CommonTests::FlagsMacros", "[common]") | ||
| { | ||
| SECTION("Integral types") | ||
| { | ||
| FlagsMacrosNonStatic<char>(static_cast<char>(0), static_cast<char>(0x1), static_cast<char>(0x2), static_cast<char>(0x4), static_cast<char>(0x40)); | ||
| FlagsMacrosNonStatic<unsigned char>(0, 0x1, 0x2, 0x4, 0x80u); | ||
| FlagsMacrosNonStatic<short>(0, 0x1, 0x2, 0x4, 0x4000); | ||
| FlagsMacrosNonStatic<unsigned short>(0, 0x1, 0x2, 0x4, 0x8000u); | ||
| FlagsMacrosNonStatic<long>(0, 0x1, 0x2, 0x4, 0x80000000ul); | ||
| FlagsMacrosNonStatic<unsigned long>(0, 0x1, 0x2, 0x4, 0x80000000ul); | ||
| FlagsMacrosNonStatic<long long>(0, 0x1, 0x2, 0x4, 0x8000000000000000ull); | ||
| FlagsMacrosNonStatic<unsigned long long>(0, 0x1, 0x2, 0x4, 0x8000000000000000ull); | ||
| } | ||
|
|
||
| SECTION("Raw enum") | ||
| { | ||
| FlagsMacrosNonStatic<ERawTest>(ER_None, ER_One, ER_Two, ER_Three, ER_Four); | ||
| } | ||
|
|
||
| SECTION("Enum class") | ||
| { | ||
| FlagsMacrosNonStatic<EClassTest>(EClassTest::None, EClassTest::One, EClassTest::Two, EClassTest::Three, EClassTest::Four); | ||
|
|
||
| EClassTest eclass = EClassTest::One | EClassTest::Two; | ||
| REQUIRE(WI_IsFlagSet(MDEC(eclass), EClassTest::One)); | ||
| REQUIRE(WI_IsFlagSet(eclass, EClassTest::Two)); | ||
| REQUIRE_FALSE(WI_IsFlagSet(eclass, EClassTest::Three)); | ||
|
|
||
| REQUIRE(WI_IsFlagClear(MDEC(eclass), EClassTest::Three)); | ||
| REQUIRE_FALSE(WI_IsFlagClear(eclass, EClassTest::One)); | ||
|
|
||
| REQUIRE_FALSE(WI_IsSingleFlagSet(MDEC(eclass))); | ||
| REQUIRE(WI_IsSingleFlagSet(eclass & EClassTest::One)); | ||
|
|
||
| eclass = EClassTest::None; | ||
| WI_SetFlag(MDEC(eclass), EClassTest::One); | ||
| REQUIRE(eclass == EClassTest::One); | ||
|
|
||
| eclass = EClassTest::None; | ||
| WI_SetFlagIf(eclass, EClassTest::One, false); | ||
| REQUIRE(eclass == EClassTest::None); | ||
| WI_SetFlagIf(eclass, EClassTest::One, true); | ||
| REQUIRE(eclass == EClassTest::One); | ||
|
|
||
| eclass = EClassTest::None; | ||
| WI_SetFlagIf(eclass, EClassTest::One, false); | ||
| REQUIRE(eclass == EClassTest::None); | ||
| WI_SetFlagIf(eclass, EClassTest::One, true); | ||
| REQUIRE(eclass == EClassTest::One); | ||
|
|
||
| eclass = EClassTest::One | EClassTest::Two; | ||
| WI_ClearFlag(eclass, EClassTest::Two); | ||
| REQUIRE(eclass == EClassTest::One); | ||
|
|
||
| eclass = EClassTest::One | EClassTest::Two; | ||
| WI_ClearFlagIf(eclass, EClassTest::One, false); | ||
| REQUIRE(eclass == (EClassTest::One | EClassTest::Two)); | ||
| WI_ClearFlagIf(eclass, EClassTest::One, true); | ||
| REQUIRE(eclass == EClassTest::Two); | ||
|
|
||
| eclass = EClassTest::None; | ||
| WI_UpdateFlag(eclass, EClassTest::One, true); | ||
| REQUIRE(eclass == EClassTest::One); | ||
| WI_UpdateFlag(eclass, EClassTest::One, false); | ||
| REQUIRE(eclass == EClassTest::None); | ||
|
|
||
| eclass = EClassTest::One; | ||
| WI_ToggleFlag(eclass, EClassTest::One); | ||
| WI_ToggleFlag(eclass, EClassTest::Two); | ||
| REQUIRE(eclass == EClassTest::Two); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
|
|
||
| // 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> | ||
|
|
||
| #include "common.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); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
|
|
||
| #include <wil/cppwinrt.h> | ||
|
|
||
| #include "catch.hpp" | ||
|
|
||
| // HRESULT values that C++/WinRT throws as something other than winrt::hresult_error - e.g. a type derived from | ||
| // winrt::hresult_error, std::*, etc. | ||
| static const HRESULT cppwinrt_mapped_hresults[] = | ||
| { | ||
| E_ACCESSDENIED, | ||
| RPC_E_WRONG_THREAD, | ||
| E_NOTIMPL, | ||
| E_INVALIDARG, | ||
| E_BOUNDS, | ||
| E_NOINTERFACE, | ||
| CLASS_E_CLASSNOTAVAILABLE, | ||
| E_CHANGED_STATE, | ||
| E_ILLEGAL_METHOD_CALL, | ||
| E_ILLEGAL_STATE_CHANGE, | ||
| E_ILLEGAL_DELEGATE_ASSIGNMENT, | ||
| HRESULT_FROM_WIN32(ERROR_CANCELLED), | ||
| E_OUTOFMEMORY, | ||
| }; | ||
|
|
||
| TEST_CASE("CppWinRTTests::WilToCppWinRTExceptionTranslationTest", "[cppwinrt]") | ||
| { | ||
| auto test = [](HRESULT hr) | ||
| { | ||
| try | ||
| { | ||
| THROW_HR(hr); | ||
| } | ||
| catch (...) | ||
| { | ||
| REQUIRE(hr == winrt::to_hresult()); | ||
| } | ||
| }; | ||
|
|
||
| for (auto hr : cppwinrt_mapped_hresults) | ||
| { | ||
| test(hr); | ||
| } | ||
|
|
||
| // A non-mapped HRESULT | ||
| test(E_UNEXPECTED); | ||
| } | ||
|
|
||
| TEST_CASE("CppWinRTTests::CppWinRTToWilExceptionTranslationTest", "[cppwinrt]") | ||
| { | ||
| auto test = [](HRESULT hr) | ||
| { | ||
| try | ||
| { | ||
| winrt::check_hresult(hr); | ||
| } | ||
| catch (...) | ||
| { | ||
| REQUIRE(hr == wil::ResultFromCaughtException()); | ||
| } | ||
| }; | ||
|
|
||
| for (auto hr : cppwinrt_mapped_hresults) | ||
| { | ||
| test(hr); | ||
| } | ||
|
|
||
| // A non-mapped HRESULT | ||
| test(E_UNEXPECTED); | ||
| } | ||
|
|
||
| TEST_CASE("CppWinRTTests::ResultFromExceptionDebugTest", "[cppwinrt]") | ||
| { | ||
| auto test = [](HRESULT hr, wil::SupportedExceptions supportedExceptions) | ||
| { | ||
| auto result = wil::ResultFromExceptionDebug(WI_DIAGNOSTICS_INFO, supportedExceptions, [&]() | ||
| { | ||
| winrt::check_hresult(hr); | ||
| }); | ||
| REQUIRE(hr == result); | ||
| }; | ||
|
|
||
| for (auto hr : cppwinrt_mapped_hresults) | ||
| { | ||
| test(hr, wil::SupportedExceptions::Known); | ||
| test(hr, wil::SupportedExceptions::All); | ||
| } | ||
|
|
||
| // A non-mapped HRESULT | ||
| test(E_UNEXPECTED, wil::SupportedExceptions::Known); | ||
| test(E_UNEXPECTED, wil::SupportedExceptions::All); | ||
|
|
||
| // Uncomment any of the following to validate SEH failfast | ||
| //test(E_UNEXPECTED, wil::SupportedExceptions::None); | ||
| //test(E_ACCESSDENIED, wil::SupportedExceptions::Thrown); | ||
| //test(E_INVALIDARG, wil::SupportedExceptions::ThrownOrAlloc); | ||
| } | ||
|
|
||
| TEST_CASE("CppWinRTTests::CppWinRTConsistencyTest", "[cppwinrt]") | ||
| { | ||
| // Since setting 'winrt_to_hresult_handler' opts us into _all_ C++/WinRT exception translation handling, we need to | ||
| // make sure that we preserve behavior, at least with 'check_hresult', especially when C++/WinRT maps a particular | ||
| // HRESULT value to a different exception type | ||
| auto test = [](HRESULT hr) | ||
| { | ||
| try | ||
| { | ||
| winrt::check_hresult(hr); | ||
| } | ||
| catch (...) | ||
| { | ||
| REQUIRE(hr == winrt::to_hresult()); | ||
| } | ||
| }; | ||
|
|
||
| for (auto hr : cppwinrt_mapped_hresults) | ||
| { | ||
| test(hr); | ||
| } | ||
|
|
||
| // A non-mapped HRESULT | ||
| test(E_UNEXPECTED); | ||
|
|
||
| // C++/WinRT also maps a few std::* exceptions to various HRESULTs. We should preserve this behavior | ||
| try | ||
| { | ||
| throw std::out_of_range("oopsie"); | ||
| } | ||
| catch (...) | ||
| { | ||
| REQUIRE(winrt::to_hresult() == E_BOUNDS); | ||
| } | ||
|
|
||
| try | ||
| { | ||
| throw std::invalid_argument("daisy"); | ||
| } | ||
| catch (...) | ||
| { | ||
| REQUIRE(winrt::to_hresult() == E_INVALIDARG); | ||
| } | ||
|
|
||
| // NOTE: C++/WinRT maps other 'std::exception' derived exceptions to E_FAIL, however we preserve the WIL behavior | ||
| // that such exceptions become HRESULT_FROM_WIN32(ERROR_UNHANDLED_EXCEPTION) | ||
| } |