//********************************************************* // // 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 struct call_adapter { template static HRESULT call(TArgs&& ... args) { return wistd::invoke(wistd::forward(args)...); } }; template<> struct call_adapter { template static HRESULT call(TArgs&& ... args) { wistd::invoke(wistd::forward(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 state; HRESULT hr = wil::invoke_rpc_nothrow(GetKittenState, binding.get(), L"fluffy", state.put()); RETURN_IF_FAILED(hr); ~~~ */ template 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)' using result_t = typename wistd::__invoke_of::type; RETURN_IF_FAILED(details::call_adapter::call(wistd::forward(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 HRESULT invoke_rpc_result_nothrow(TResult& result, TCall&&... args) WI_NOEXCEPT { RpcTryExcept { result = wistd::invoke(wistd::forward(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 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; 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 using unique_rpc_context_handle = unique_any::Close), details::rpc_closer_t::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 state; wil::invoke_rpc(GetKittenState, binding.get(), L"fluffy", state.put()); // use 'state' ~~~ */ template void invoke_rpc(TCall&& ... args) { THROW_IF_FAILED(invoke_rpc_nothrow(wistd::forward(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 auto invoke_rpc_result(TCall&& ... args) { using result_t = typename wistd::__invoke_of::type; result_t result{}; THROW_IF_FAILED(invoke_rpc_result_nothrow(result, wistd::forward(args)...)); return result; } #endif } #endif