microsoft / wil

Windows Implementation Library
MIT License
2.57k stars 234 forks source link

Helpers for implementing Out Of Proc COM server in cppwinrt, without WRL #439

Closed roxk closed 5 months ago

roxk commented 6 months ago

Context: https://github.com/microsoft/cppwinrt/issues/601

This comment summarize the request in a succinct way: image

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 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