From ac5f4d7ba0ca378e6ce0c5e73cb71fbef623cbcc Mon Sep 17 00:00:00 2001 From: Raymond Chen Date: Thu, 16 Sep 2021 13:10:16 -0700 Subject: [PATCH] Improve diagnostics when trying to use classic COM without Before this change, if you tried to use classic COM without first including , C++/WinRT spit out really confusing error messages like ```cpp auto widget = object.as<::IClassicComInterface>(); ``` > error: 'unbox_value_type': no matching overloaded function found or worse: Just ignored you! ```cpp struct Widget : WidgetT { // IPersistStream is simply ignored! }; ``` This change relaxes the rules around `` and improves diagnostics. 1. If you want to use `com_ptr`, `as`, etc. with classic COM interfaces, you need only include `` at *some point* before using them. Doesn't have to be included before `winrt/base.h`. (And really, since you need to include `` in order to define *any* classic COM interfaces, the prerquisite is automatically satisfied in practice.) 2. If you want to use `implements<...>` with classic COM interfaces, you stlil need to include `` before `winrt/base.h`, but we improved the diagnostics so that if you forget, you get a compiler error instead of a runtime error: > To implement classic COM interfaces, you must `#include ` before including C++/WinRT headers. Implementation notes ==================== The key step is forward-declaring the missing types `IUnknown` and `GUID` so that we can talk *about* them without needing to know what they are. This trick allows us to remove a large number of `#ifdef WINRT_IUNKNOWN_DEFINED` tests, which unlocks 80% of this feature. To avoid repetition, introduced a new type trait `is_classic_com_interface`, which factors out many uses of `is_base_of<::IUnknown, T> && !is_implements`. Having a forward declaration of `IUnknown` lets us detect that a type is a classic COM interface by seeing if it derives from `::IUnknown`. Having a forward declaration of `GUID` lets us reinterpret-cast them to `guid`, which covers nearly everything. The only place where we really need to know what's in a `GUID` is the `constexpr guid(GUID const&)` constructor. For that, the constructor is templated with a dummy default parameter. The dummy default parameter delays expansion until the constructor is invoked. We then forward the `GUID` to a templated helper as a dependent type `T`, so the compiler accepts the helper despite not knowing what T's members are. This change found a bug in the unit tests, because Nested.HierarchyD used classic COM interface `IReferenceTrackerExtension` without having first included `unknwn.h`, so it was ignored! (Amusing note: The implementation of this feature got shorter the longer I worked on it, as I gradually realized that nearly all of the places we needed to protect the use of `GUID` didn't need the duck-typing trick employed in the `guid(GUID const&)` constructor.) --- strings/base_implements.h | 22 ++++++---------------- strings/base_macros.h | 4 ++++ strings/base_meta.h | 8 +++++--- strings/base_reference_produce.h | 10 +--------- strings/base_types.h | 23 +++++++++++------------ strings/base_windows.h | 14 ++++---------- test/test/guid.cpp | 29 ++++++++++++++++++----------- test/test_component_derived/pch.h | 1 + 8 files changed, 50 insertions(+), 61 deletions(-) diff --git a/strings/base_implements.h b/strings/base_implements.h index 7bc85220b..b2300edc5 100644 --- a/strings/base_implements.h +++ b/strings/base_implements.h @@ -44,17 +44,8 @@ namespace winrt::impl template