blob: 67c1ed213dc58c9727f4ec5c17a97e8eebf0b034 [file] [log] [blame]
// 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 <windows.foundation.h>
#include <wrl/client.h>
#include <wrl/event.h>
#include <type_traits>
#include <utility>
#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 <typename Interface, typename... Args>
using IMemberFunction = HRESULT (__stdcall Interface::*)(Args...);
template <typename T>
using AsyncAbiT = typename ABI::Windows::Foundation::Internal::GetAbiType<
typename ABI::Windows::Foundation::IAsyncOperation<T>::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 <typename T>
using AsyncResultsT = std::conditional_t<
std::is_convertible<AsyncAbiT<T>, IUnknown*>::value,
Microsoft::WRL::ComPtr<std::remove_pointer_t<AsyncAbiT<T>>>,
AsyncAbiT<T>>;
// Obtains the results of the provided async operation.
template <typename T>
AsyncResultsT<T> GetAsyncResults(
ABI::Windows::Foundation::IAsyncOperation<T>* async_op) {
AsyncResultsT<T> 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 <typename T>
HRESULT PostAsyncResults(
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<T>>
async_op,
base::OnceCallback<void(internal::AsyncResultsT<T>)> callback) {
auto completion_cb = base::BindOnce(
[](Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<T>>
async_op,
base::OnceCallback<void(internal::AsyncResultsT<T>)> 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<T>>([
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 <typename Interface,
typename Sender,
typename Args,
typename SenderAbi,
typename ArgsAbi>
base::Optional<EventRegistrationToken> AddTypedEventHandler(
Interface* i,
internal::IMemberFunction<
Interface,
ABI::Windows::Foundation::ITypedEventHandler<Sender*, Args*>*,
EventRegistrationToken*> function,
base::RepeatingCallback<void(SenderAbi*, ArgsAbi*)> callback) {
EventRegistrationToken token;
HRESULT hr = ((*i).*function)(
Microsoft::WRL::Callback<
ABI::Windows::Foundation::ITypedEventHandler<Sender*, Args*>>([
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<SenderAbi>(sender),
Microsoft::WRL::ComPtr<ArgsAbi>(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_