Repository: microsoft/wil (C++)

Issue:
Helpers for implementing Out Of Proc COM server in cppwinrt, without WRL
Context:
https://github.com/microsoft/cppwinrt/issues/601

This comment summarize the request in a succinct way:
![image](https://github.com/microsoft/wil/assets/16918354/6765c363-0533-42c8-a17e-c34e96b99a01)

For brevity, here are the original requirement as described in the above thread:
1. Simple object registration
2. Automatic outstanding object ref counting
3. Synchronized COM shutdown and event when all objects have disconnected.

As analyzed by kenny, (2) is already handled by cppwinrt. Some extra work is needed for (1) and (3).

I have created a [sample repo](https://github.com/roxk/NotifiableModuleLock) that demonstrates that both (1) and (3) is possible with just cppwinrt, with the help of some small helpers:

|ask|helper|
|--|--|
| simple object registration | register function with variadic template |
| synchronized COM shutdown | notifiable module lock |

You can read the sample repo for more details. In short, here is how it works:
1. cppwinrt exposes a configuration WINRT_CUSTOM_MODULE_LOCK that allows users to hook into the module lock mechanism
2. Write a custom module lock which, (a) notifies when module count reaches 0, and (b) provides customization point for the notification
3. cppwinrt also provides winrt::no_module_lock so that class factory implementation can be omitted from module object count
4. Write variadic functions that take in authored type and register class factory recursively

This issue asks that these helpers be added to wil for mass adoption so that cppwinrt-only Out Of Proc COM server becomes a reality. Once these helpers get into wil, `IExplorerCommand` and `IWidgetProvider` sample should be updated so that they are cppwinrt-only. Say goodbye to WRL for good.

---

### API Spec

#### Notifiable module lock
```
namespace wil
{
    template <typename Func>
    struct notifiable_module_lock
    {
        // Other ref count details omitted...
        static void set_notifier(Func& func)
        {
            notifier = func;
        }
    private:
        static inline Func notifier;
    };
}
```

#### Object registration
```
// NOTE: this has to be in another header file, or in the same file guarded by some MACRO
// This is because the module lock has to be defined _before_ including winrt/base.h
// but object registration support requires the use of winrt::implements, which means it has to be
// defined _after_ including winrt/base.h
namespace wil::details
{
  template <typename T>
  struct Factory : winrt::implements<Factory<T>, IClassFactory, winrt::no_module_lock> {}
}

namespace wil
{
// RAII COM server registration revoker
struct com_server_revoker
{
    std::vector<DWORD> registrations;
    ~com_server_revoker()
    {
        for (auto&& registration : registrations)
        {
            winrt::check_hresult(CoRevokeClassObject(registration));
        }
    }
};

template <typename T, typename... Rest>
[[nodiscard]] com_server_revoker register_com_server();
}
```

#### Usage
```
// pch.h
#pragma once
// Include the module lock. Filename TBD
#include <wil/cppwinrt_notifiable_module_lock.h>
// Enable cppwinrt custom module lock support by defining this MACRO
#define WINRT_CUSTOM_MODULE_LOCK
// Define the custom module lock
namespace winrt
{
  inline auto& get_module_lock()
  {
    static wil::notifiable_module_lock<void(*)()> lock.
    return lock;
  }
}
// Include winrt _after_ configuring module lock
#include <unkwn.h>
#include <winrt/base.h>
// Include com registration helper
#include <wil/cppwinrt_register_com_server.h>

// main.cpp
wil::unique_event _comExitEvent;

void notifier()
{
  _comExitEvent.SetEvent();
}

int main()
{
  _comExitEvent.create();
  wil::notifiable_module_lock<void(*)()>::set_notifier(notifier);
  winrt::init_apartment();
  auto revoker = wil::register_com_server<MyClass>();
  _comExitEvent.wait();
  return 0;
}
```

### Open Question

1. Should wil include a default implementation of the boilerplate of defining the `WINRT_CUSTOM_MODULE_LOCK` MACRO and actually defining the custom module lock in `winrt` namespace? I have tested this idea and found that wil needs to provide another API to configure the module lock's notifier
2. Should notifiable module lock and object registration be in the same header file? I personally prefer separating them into two headers, but I'm aware wil like to group functionality into one header file and use macro to light up functionality



Fix (patch):
diff --git a/include/wil/common.h b/include/wil/common.h
index 50080ee3..08342449 100644
--- a/include/wil/common.h
+++ b/include/wil/common.h
@@ -810,7 +810,7 @@ _Post_satisfies_(return == error) inline T verify_win32(T error) WI_NOEXCEPT
 // Implementation details for macros and helper functions... do not use directly.
 namespace details
 {
-// Use size-specific casts to avoid sign extending numbers -- avoid warning C4310: cast truncates constant value
+    // Use size-specific casts to avoid sign extending numbers -- avoid warning C4310: cast truncates constant value
 #define __WI_MAKE_UNSIGNED(val) \
     (__pragma(warning(push)) __pragma(warning(disable : 4310 4309))( \
         sizeof(val) == 1   ? static_cast<unsigned char>(val) \
diff --git a/include/wil/cppwinrt_notifiable_module_lock.h b/include/wil/cppwinrt_notifiable_module_lock.h
new file mode 100644
index 00000000..8e85f8fe
--- /dev/null
+++ b/include/wil/cppwinrt_notifiable_module_lock.h
@@ -0,0 +1,104 @@
+//*********************************************************
+//
+//    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.
+//
+//*********************************************************
+//! @file
+//! Utilities for implementing OOP COM server with cppwinrt only
+
+#ifndef __WIL_CPPWINRT_NOTIFIABLE_MODULE_LOCK_INCLUDED
+#define __WIL_CPPWINRT_NOTIFIABLE_MODULE_LOCK_INCLUDED
+
+#ifdef WINRT_BASE_H
+#error You must include this header before including any winrt header
+#endif
+
+#ifndef WINRT_CUSTOM_MODULE_LOCK
+#error You must define 'WINRT_CUSTOM_MODULE_LOCK' at the project level
+#endif
+
+#include <atomic>
+#include <cstdint>
+
+namespace wil
+{
+// Adopted from cppwinrt
+struct notifiable_module_lock_base
+{
+    notifiable_module_lock_base() = default;
+
+    notifiable_module_lock_base(uint32_t count) : m_count(count)
+    {
+    }
+
+    uint32_t operator=(uint32_t count) noexcept
+    {
+        return m_count = count;
+    }
+
+    uint32_t operator++() noexcept
+    {
+        return m_count.fetch_add(1, std::memory_order_relaxed) + 1;
+    }
+
+    uint32_t operator--() noexcept
+    {
+        auto const remaining = m_count.fetch_sub(1, std::memory_order_release) - 1;
+
+        if (remaining == 0)
+        {
+            std::atomic_thread_fence(std::memory_order_acquire);
+            notifier();
+        }
+        else if (remaining < 0)
+        {
+            abort();
+        }
+
+        return remaining;
+    }
+
+    operator uint32_t() const noexcept
+    {
+        return m_count;
+    }
+
+    template <typename Func>
+    void set_notifier(Func&& func)
+    {
+        notifier = std::forward<Func>(func);
+    }
+
+private:
+    std::atomic<int32_t> m_count{0};
+    std::function<void()> notifier{};
+};
+
+struct notifiable_module_lock final : notifiable_module_lock_base
+{
+    static notifiable_module_lock& instance() noexcept
+    {
+        static notifiable_module_lock lock;
+        return lock;
+    }
+};
+} // namespace wil
+
+#ifndef WIL_CPPWINRT_COM_SERVER_CUSTOM_MODULE_LOCK
+
+namespace winrt
+{
+auto& get_module_lock()
+{
+    return wil::notifiable_module_lock::instance();
+}
+} // namespace winrt
+
+#endif
+
+#endif
\ No newline at end of file
diff --git a/include/wil/cppwinrt_register_com_server.h b/include/wil/cppwinrt_register_com_server.h
new file mode 100644
index 00000000..7dc79eb4
--- /dev/null
+++ b/include/wil/cppwinrt_register_com_server.h
@@ -0,0 +1,76 @@
+//*********************************************************
+//
+//    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.
+//
+//*********************************************************
+//! @file
+//! Utilities for making managing COM server easier
+
+#ifndef __WIL_CPPWINRT_REGISTER_COM_SERVER_INCLUDED
+#define __WIL_CPPWINRT_REGISTER_COM_SERVER_INCLUDED
+
+#include <winrt/base.h>
+#include <wil/resource.h>
+#include <wil/cppwinrt.h>
+#include <vector>
+
+namespace wil::details
+{
+template <typename T>
+struct CppWinRTClassFactory : winrt::implements<CppWinRTClassFactory<T>, IClassFactory, winrt::no_module_lock>
+{
+    HRESULT __stdcall CreateInstance(IUnknown* outer, GUID const& iid, void** result) noexcept final
+    try
+    {
+        *result = nullptr;
+
+        if (outer)
+        {
+            return CLASS_E_NOAGGREGATION;
+        }
+        return winrt::make_self<T>().as(iid, result);
+    }
+    CATCH_RETURN()
+
+    HRESULT __stdcall LockServer(BOOL) noexcept final
+    {
+        return S_OK;
+    }
+};
+
+template <typename T = void, typename... Rest>
+void register_com_server(std::vector<wil::unique_com_class_object_cookie>& registrations)
+{
+    DWORD registration{};
+    winrt::check_hresult(CoRegisterClassObject(
+        winrt::guid_of<T>(), winrt::make<CppWinRTClassFactory<T>>().get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &registration));
+    // This emplace_back is no-throw as wil::register_com_server already reserves enough capacity
+    registrations.emplace_back(registration);
+    register_com_server<Rest...>(registrations);
+}
+
+template <>
+void register_com_server<void>(std::vector<unique_com_class_object_cookie>&)
+{
+}
+} // namespace wil::details
+
+namespace wil
+{
+template <typename T, typename... Rest>
+[[nodiscard]] std::vector<wil::unique_com_class_object_cookie> register_com_server()
+{
+    std::vector<wil::unique_com_class_object_cookie> registrations;
+    registrations.reserve(sizeof...(Rest) + 1);
+    details::register_com_server<T, Rest...>(registrations);
+    // C++17 doesn't provide guaranteed copy ellision, but the copy should be elided nonetheless.
+    return registrations;
+}
+} // namespace wil
+
+#endif
\ No newline at end of file
diff --git a/include/wil/result_macros.h b/include/wil/result_macros.h
index f802f084..6f421372 100644
--- a/include/wil/result_macros.h
+++ b/include/wil/result_macros.h
@@ -93,7 +93,7 @@ typedef _Return_type_success_(return >= 0) LONG NTSTATUS;
 #endif
 #ifndef __NTSTATUS_FROM_WIN32
 #define __NTSTATUS_FROM_WIN32(x) \
-    ((NTSTATUS)(x) <= 0 ? ((NTSTATUS)(x)) : ((NTSTATUS)(((x)&0x0000FFFF) | (FACILITY_WIN32 << 16) | ERROR_SEVERITY_ERROR)))
+    ((NTSTATUS)(x) <= 0 ? ((NTSTATUS)(x)) : ((NTSTATUS)(((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | ERROR_SEVERITY_ERROR)))
 #endif
 
 #ifndef WIL_AllocateMemory
@@ -2770,7 +2770,7 @@ namespace details
             {
                 status =
                     ((NTSTATUS)(hr) <= 0 ? ((NTSTATUS)(hr))
-                                         : ((NTSTATUS)(((hr)&0x0000FFFF) | (FACILITY_SSPI << 16) | ERROR_SEVERITY_ERROR)));
+                                         : ((NTSTATUS)(((hr) & 0x0000FFFF) | (FACILITY_SSPI << 16) | ERROR_SEVERITY_ERROR)));
             }
             else
             {
diff --git a/include/wil/wistd_type_traits.h b/include/wil/wistd_type_traits.h
index cc05b55c..ce4f95d0 100644
--- a/include/wil/wistd_type_traits.h
+++ b/include/wil/wistd_type_traits.h
@@ -1946,7 +1946,7 @@ struct __numeric_type<void>
 
 // __promote
 
-template <class _A1, class _A2 = void, class _A3 = void, bool = __numeric_type<_A1>::value&& __numeric_type<_A2>::value&& __numeric_type<_A3>::value>
+template <class _A1, class _A2 = void, class _A3 = void, bool = __numeric_type<_A1>::value && __numeric_type<_A2>::value && __numeric_type<_A3>::value>
 class __promote_imp
 {
 public: