From e4a2c85fd78a47feabcf78284f2f1111fd37d06a Mon Sep 17 00:00:00 2001 From: Jan Wilken Doerrie Date: Wed, 18 Apr 2018 20:41:01 +0000 Subject: [PATCH] [base] Provide IAsyncOperation Implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change implements Windows::Foundation::IAsyncOperation. It provides a simple interface and allows clients to specify desired results via a callback. This is mostly useful for tests, when custom fake implementations of UWP interfaces are required. Some of these implementations must provide Async methods, that return an instantiation of IAsyncOperation. Bug: 821766 Change-Id: I728c0bb5311898fac4ee5f5e54d8a64b2aa61d94 Reviewed-on: https://chromium-review.googlesource.com/984194 Reviewed-by: Robert Liao Reviewed-by: Daniel Cheng Commit-Queue: Jan Wilken Dörrie Cr-Commit-Position: refs/heads/master@{#551811} --- base/BUILD.gn | 2 + base/win/async_operation.h | 244 +++++++++++++++++++++++++++ base/win/async_operation_unittest.cc | 174 +++++++++++++++++++ 3 files changed, 420 insertions(+) create mode 100644 base/win/async_operation.h create mode 100644 base/win/async_operation_unittest.cc diff --git a/base/BUILD.gn b/base/BUILD.gn index 150e929600b6c7..0af4db0bca22d3 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn @@ -1063,6 +1063,7 @@ jumbo_component("base") { "version.h", "vlog.cc", "vlog.h", + "win/async_operation.h", "win/com_init_check_hook.cc", "win/com_init_check_hook.h", "win/com_init_util.cc", @@ -2352,6 +2353,7 @@ test("base_unittests") { "values_unittest.cc", "version_unittest.cc", "vlog_unittest.cc", + "win/async_operation_unittest.cc", "win/com_init_check_hook_unittest.cc", "win/com_init_util_unittest.cc", "win/core_winrt_util_unittest.cc", diff --git a/base/win/async_operation.h b/base/win/async_operation.h new file mode 100644 index 00000000000000..2c41ddf61227de --- /dev/null +++ b/base/win/async_operation.h @@ -0,0 +1,244 @@ +// 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 BASE_WIN_ASYNC_OPERATION_H_ +#define BASE_WIN_ASYNC_OPERATION_H_ + +#include +#include +#include +#include + +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/threading/thread_checker.h" + +namespace base { +namespace win { + +// This file provides an implementation of Windows::Foundation::IAsyncOperation. +// Specializations exist for "regular" types and interface types that inherit +// from IUnknown. Both specializations expose a callback() method, which can be +// used to provide the result that will be forwarded to the registered +// completion handler. For regular types it expects an instance of that type, +// and for interface types it expects a corresponding ComPtr. This class is +// thread-affine and all member methods should be called on the same thread that +// constructed the object. In order to offload heavy result computation, +// base's PostTaskAndReplyWithResult() should be used with the ResultCallback +// passed as a reply. +// +// Example usages: +// +// // Regular types +// auto regular_op = WRL::Make>(); +// auto cb = regular_op->callback(); +// regular_op->put_Completed(...event handler...); +// ... +// // This will invoke the event handler. +// std::move(cb).Run(123); +// ... +// // Results can be queried: +// int results = 0; +// regular_op->GetResults(&results); +// EXPECT_EQ(123, results); +// +// // Interface types +// auto interface_op = WRL::Make>(); +// auto cb = interface_op->callback(); +// interface_op->put_Completed(...event handler...); +// ... +// // This will invoke the event handler. +// std::move(cb).Run(WRL::Make()); +// ... +// // Results can be queried: +// WRL::ComPtr results; +// interface_op->GetResults(&results); +// // |results| points to the provided IFooBarImpl instance. +// +// // Offloading a heavy computation: +// auto my_op = WRL::Make>(); +// base::PostTaskAndReplyWithResult( +// base::BindOnce(MakeFooBar), my_op->callback()); + +namespace internal { + +// Template tricks needed to dispatch to the correct implementation below. +// +// For all types which are neither InterfaceGroups nor RuntimeClasses, the +// following three typedefs are synonyms for a single C++ type. But for +// InterfaceGroups and RuntimeClasses, they are different types: +// LogicalT: The C++ Type for the InterfaceGroup or RuntimeClass, when +// used as a template parameter. Eg "RCFoo*" +// AbiT: The C++ type for the default interface used to represent the +// InterfaceGroup or RuntimeClass when passed as a method parameter. +// Eg "IFoo*" +// ComplexT: An instantiation of the Internal "AggregateType" template that +// combines LogicalT with AbiT. Eg "AggregateType" +// +// windows.foundation.collections.h defines the following template and +// semantics in Windows::Foundation::Internal: +// +// template +// struct AggregateType; +// +// LogicalType - the Windows Runtime type (eg, runtime class, inteface group, +// etc) being provided as an argument to an _impl template, when +// that type cannot be represented at the ABI. +// AbiType - the type used for marshalling, ie "at the ABI", for the +// logical type. +template +using ComplexT = + typename ABI::Windows::Foundation::IAsyncOperation::TResult_complex; + +template +using AbiT = + typename ABI::Windows::Foundation::Internal::GetAbiType>::type; + +template +using LogicalT = typename ABI::Windows::Foundation::Internal::GetLogicalType< + ComplexT>::type; + +template +using InterfaceT = std::remove_pointer_t>; + +// Implementation of shared functionality. +template +class AsyncOperationBase + : public Microsoft::WRL::RuntimeClass< + Microsoft::WRL::RuntimeClassFlags< + Microsoft::WRL::WinRt | Microsoft::WRL::InhibitRoOriginateError>, + ABI::Windows::Foundation::IAsyncOperation> { + public: + using Handler = ABI::Windows::Foundation::IAsyncOperationCompletedHandler; + + AsyncOperationBase() = default; + ~AsyncOperationBase() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); } + + // ABI::Windows::Foundation::IAsyncOperation: + IFACEMETHODIMP put_Completed(Handler* handler) override { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + handler_ = handler; + return S_OK; + } + + IFACEMETHODIMP get_Completed(Handler** handler) override { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + return handler_.CopyTo(handler); + } + + protected: + void InvokeCompletedHandler() { + handler_->Invoke(this, ABI::Windows::Foundation::AsyncStatus::Completed); + } + + THREAD_CHECKER(thread_checker_); + + private: + Microsoft::WRL::ComPtr handler_; + + DISALLOW_COPY_AND_ASSIGN(AsyncOperationBase); +}; + +} // namespace internal + +template +class AsyncOperation; + +template +class AsyncOperation< + T, + std::enable_if_t>::value>> + : public internal::AsyncOperationBase { + public: + using InterfacePointer = Microsoft::WRL::ComPtr>; + using ResultCallback = base::OnceCallback; + + AsyncOperation() : weak_factory_(this) { + // Note: This can't be done in the constructor initializer list. This is + // because it relies on weak_factory_ to be initialized, which needs to be + // the last class member. Also applies below. + callback_ = + base::BindOnce(&AsyncOperation::OnResult, weak_factory_.GetWeakPtr()); + } + + ResultCallback callback() { + // Note: `this->` here and below is necessary due to the + // -Wmicrosoft-template compiler warning. + DCHECK_CALLED_ON_VALID_THREAD(this->thread_checker_); + DCHECK(!callback_.is_null()); + return std::move(callback_); + } + + // ABI::Windows::Foundation::IAsyncOperation: + IFACEMETHODIMP GetResults(internal::AbiT* results) override { + DCHECK_CALLED_ON_VALID_THREAD(this->thread_checker_); + return ptr_ ? ptr_.CopyTo(results) : E_PENDING; + } + + private: + void OnResult(InterfacePointer ptr) { + DCHECK_CALLED_ON_VALID_THREAD(this->thread_checker_); + DCHECK(!ptr_); + ptr_ = std::move(ptr); + this->InvokeCompletedHandler(); + } + + ResultCallback callback_; + InterfacePointer ptr_; + base::WeakPtrFactory weak_factory_; +}; + +template +class AsyncOperation< + T, + std::enable_if_t< + !std::is_base_of>::value>> + : public internal::AsyncOperationBase { + public: + using ResultCallback = base::OnceCallback; + + AsyncOperation() : weak_factory_(this) { + callback_ = + base::BindOnce(&AsyncOperation::OnResult, weak_factory_.GetWeakPtr()); + } + + ResultCallback callback() { + DCHECK_CALLED_ON_VALID_THREAD(this->thread_checker_); + DCHECK(!callback_.is_null()); + return std::move(callback_); + } + + // ABI::Windows::Foundation::IAsyncOperation: + IFACEMETHODIMP GetResults(internal::AbiT* results) override { + DCHECK_CALLED_ON_VALID_THREAD(this->thread_checker_); + if (!value_) + return E_PENDING; + + *results = *value_; + return S_OK; + } + + private: + void OnResult(T result) { + DCHECK_CALLED_ON_VALID_THREAD(this->thread_checker_); + DCHECK(!value_); + value_.emplace(std::move(result)); + this->InvokeCompletedHandler(); + } + + ResultCallback callback_; + base::Optional value_; + base::WeakPtrFactory weak_factory_; +}; + +} // namespace win +} // namespace base + +#endif // BASE_WIN_ASYNC_OPERATION_H_ diff --git a/base/win/async_operation_unittest.cc b/base/win/async_operation_unittest.cc new file mode 100644 index 00000000000000..b29e181db3f618 --- /dev/null +++ b/base/win/async_operation_unittest.cc @@ -0,0 +1,174 @@ +// 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. + +#include "base/win/async_operation.h" + +#include + +#include "base/test/gtest_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace WRL = Microsoft::WRL; + +using ABI::Windows::Foundation::IAsyncOperation; +using ABI::Windows::Foundation::IAsyncOperationCompletedHandler; + +// In order to exercise the interface logic of AsyncOperation we define an empty +// dummy interface, its implementation, and the necessary boilerplate to hook it +// up with IAsyncOperation and IAsyncOperationCompletedHandler. +namespace { + +// Chosen by fair `uuidgen` invocation. Also applies to the UUIDs below. +MIDL_INTERFACE("756358C7-8083-4D78-9D27-9278B76096d4") +IFooBar : public IInspectable{}; + +class FooBar + : public WRL::RuntimeClass< + WRL::RuntimeClassFlags, + IFooBar> {}; + +} // namespace + +namespace ABI { +namespace Windows { +namespace Foundation { + +// Provide the required template specializations to register +// IAsyncOperation as an AggregateType. This is similar to how it is +// done for UWP classes. +template <> +struct DECLSPEC_UUID("124858e4-f97e-409c-86ae-418c4781144c") + IAsyncOperation + : IAsyncOperation_impl> { + static const wchar_t* z_get_rc_name_impl() { + return L"Windows.Foundation.IAsyncOperation"; + } +}; + +template <> +struct DECLSPEC_UUID("9e49373c-200c-4715-abd7-4214ba669c81") + IAsyncOperationCompletedHandler + : IAsyncOperationCompletedHandler_impl< + Internal::AggregateType> { + static const wchar_t* z_get_rc_name_impl() { + return L"Windows.Foundation.AsyncOperationCompletedHandler"; + } +}; + +} // namespace Foundation +} // namespace Windows +} // namespace ABI + +namespace base { +namespace win { + +namespace { + +// Utility method to add a completion callback to |async_op|. |*called_cb| will +// be set to true once the callback is invoked. +template +void PutCallback(AsyncOperation* async_op, bool* called_cb) { + async_op->put_Completed( + WRL::Callback>( + [=](IAsyncOperation* iasync_op, AsyncStatus status) { + EXPECT_EQ(async_op, iasync_op); + *called_cb = true; + return S_OK; + }) + .Get()); +} + +} // namespace + +TEST(AsyncOperationTest, TestInt) { + bool called_cb = false; + + auto int_op = WRL::Make>(); + PutCallback(int_op.Get(), &called_cb); + + int results; + EXPECT_TRUE(FAILED(int_op->GetResults(&results))); + EXPECT_FALSE(called_cb); + int_op->callback().Run(123); + + EXPECT_TRUE(called_cb); + EXPECT_TRUE(SUCCEEDED(int_op->GetResults(&results))); + EXPECT_EQ(123, results); + + // GetResults should be idempotent. + EXPECT_TRUE(SUCCEEDED(int_op->GetResults(&results))); + EXPECT_EQ(123, results); +} + +TEST(AsyncOperationTest, TestBool) { + bool called_cb = false; + + auto bool_op = WRL::Make>(); + PutCallback(bool_op.Get(), &called_cb); + + // AsyncOperation is an aggregate of bool and boolean, and requires a + // pointer to the latter to get the results. + boolean results; + EXPECT_TRUE(FAILED(bool_op->GetResults(&results))); + EXPECT_FALSE(called_cb); + bool_op->callback().Run(true); + + EXPECT_TRUE(called_cb); + EXPECT_TRUE(SUCCEEDED(bool_op->GetResults(&results))); + EXPECT_TRUE(results); +} + +TEST(AsyncOperationTest, TestInterface) { + bool called_cb = false; + + auto foobar_op = WRL::Make>(); + PutCallback(foobar_op.Get(), &called_cb); + + // AsyncOperation is an aggregate of FooBar* and IFooBar*. + WRL::ComPtr results; + EXPECT_TRUE(FAILED(foobar_op->GetResults(&results))); + EXPECT_FALSE(called_cb); + + auto foobar = WRL::Make(); + IFooBar* foobar_ptr = foobar.Get(); + foobar_op->callback().Run(std::move(foobar)); + + EXPECT_TRUE(called_cb); + EXPECT_TRUE(SUCCEEDED(foobar_op->GetResults(&results))); + EXPECT_EQ(foobar_ptr, results.Get()); +} + +TEST(AsyncOperationTest, TestIdempotence) { + bool called_cb = false; + + auto int_op = WRL::Make>(); + PutCallback(int_op.Get(), &called_cb); + + int results; + EXPECT_TRUE(FAILED(int_op->GetResults(&results))); + EXPECT_FALSE(called_cb); + // Calling GetResults twice shouldn't change the result. + EXPECT_TRUE(FAILED(int_op->GetResults(&results))); + EXPECT_FALSE(called_cb); + + int_op->callback().Run(42); + + EXPECT_TRUE(called_cb); + EXPECT_TRUE(SUCCEEDED(int_op->GetResults(&results))); + EXPECT_EQ(42, results); + // Calling GetResults twice shouldn't change the result. + EXPECT_TRUE(SUCCEEDED(int_op->GetResults(&results))); + EXPECT_EQ(42, results); +} + +TEST(AsyncOperationTest, DoubleCallbackFails) { + auto int_op = WRL::Make>(); + auto cb = int_op->callback(); + + // Obtaining another callback should result in a DCHECK failure. + EXPECT_DCHECK_DEATH(int_op->callback()); +} + +} // namespace win +} // namespace base