// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef DEVICE_BLUETOOTH_EVENT_UTILS_WINRT_H_ #define DEVICE_BLUETOOTH_EVENT_UTILS_WINRT_H_ #include #include #include #include #include #include "base/bind.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/optional.h" #include "base/threading/thread_task_runner_handle.h" namespace device { namespace internal { // Utility function to pretty print enum values. constexpr const char* ToCString(AsyncStatus async_status) { switch (async_status) { case AsyncStatus::Started: return "AsyncStatus::Started"; case AsyncStatus::Completed: return "AsyncStatus::Completed"; case AsyncStatus::Canceled: return "AsyncStatus::Canceled"; case AsyncStatus::Error: return "AsyncStatus::Error"; } NOTREACHED(); return ""; } template using IMemberFunction = HRESULT (__stdcall Interface::*)(Args...); template using AsyncAbiT = typename ABI::Windows::Foundation::Internal::GetAbiType< typename ABI::Windows::Foundation::IAsyncOperation::TResult_complex>:: type; // Compile time switch to decide what container to use for the async results for // |T|. Depends on whether the underlying Abi type is a pointer to IUnknown or // not. It queries the internals of Windows::Foundation to obtain this // information. template using AsyncResultsT = std::conditional_t< std::is_convertible, IUnknown*>::value, Microsoft::WRL::ComPtr>>, AsyncAbiT>; // Obtains the results of the provided async operation. template AsyncResultsT GetAsyncResults( ABI::Windows::Foundation::IAsyncOperation* async_op) { AsyncResultsT results; HRESULT hr = async_op->GetResults(&results); if (FAILED(hr)) { VLOG(2) << "GetAsyncResults failed: " << logging::SystemErrorCodeToString(hr); } return results; } } // namespace internal // This method registers a completion handler for |async_op| and will post the // results to |callback|. The |callback| will be run on the same thread that // invoked this method. Callers need to ensure that this method is invoked in // the correct COM apartment, i.e. the one that created |async_op|. While a WRL // Callback can be constructed from callable types such as a lambda or // std::function objects, it cannot be directly constructed from a // base::OnceCallback. Thus the callback is moved into a capturing lambda, which // then posts the callback once it is run. Posting the results to the TaskRunner // is required, since the completion callback might be invoked on an arbitrary // thread. Lastly, the lambda takes ownership of |async_op|, as this needs to be // kept alive until GetAsyncResults can be invoked. template HRESULT PostAsyncResults( Microsoft::WRL::ComPtr> async_op, base::OnceCallback)> callback) { auto completion_cb = base::BindOnce( [](Microsoft::WRL::ComPtr> async_op, base::OnceCallback)> callback) { std::move(callback).Run(internal::GetAsyncResults(async_op.Get())); }, async_op, std::move(callback)); return async_op->put_Completed( Microsoft::WRL::Callback< ABI::Windows::Foundation::IAsyncOperationCompletedHandler>([ task_runner(base::ThreadTaskRunnerHandle::Get()), completion_cb(std::move(completion_cb)) ](auto&&, AsyncStatus async_status) mutable { if (async_status != AsyncStatus::Completed) { VLOG(2) << "Got unexpected AsyncStatus: " << internal::ToCString(async_status); } // Note: We are ignoring the passed in pointer to async_op, as the // completion callback has access to the initially provided |async_op|. // Since the code within the lambda could be executed on any thread, it // is vital that the completion callback gets posted to the original // |task_runner|, as this is guaranteed to be in the correct COM // apartment. task_runner->PostTask(FROM_HERE, std::move(completion_cb)); return S_OK; }) .Get()); } // Convenience template function to construct a TypedEventHandler from a // base::RepeatingCallback of a matching signature. In case of success, the // EventRegistrationToken is returned to the caller. A return value of // base::nullopt indicates a failure. Events are posted to the same thread the // event handler was created on. template base::Optional AddTypedEventHandler( Interface* i, internal::IMemberFunction< Interface, ABI::Windows::Foundation::ITypedEventHandler*, EventRegistrationToken*> function, base::RepeatingCallback callback) { EventRegistrationToken token; HRESULT hr = ((*i).*function)( Microsoft::WRL::Callback< ABI::Windows::Foundation::ITypedEventHandler>([ task_runner(base::ThreadTaskRunnerHandle::Get()), callback(std::move(callback)) ](SenderAbi * sender, ArgsAbi * args) { // Make sure we are still on the same thread. DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), task_runner); task_runner->PostTask( FROM_HERE, base::BindOnce(callback, Microsoft::WRL::ComPtr(sender), Microsoft::WRL::ComPtr(args))); return S_OK; }) .Get(), &token); if (FAILED(hr)) { VLOG(2) << "Adding EventHandler failed: " << logging::SystemErrorCodeToString(hr); return base::nullopt; } return token; } } // namespace device #endif // DEVICE_BLUETOOTH_EVENT_UTILS_WINRT_H_