Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
roxk opened this issue Feb 20, 2024 · 0 comments · Fixed by #440
Closed

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

roxk opened this issue Feb 20, 2024 · 0 comments · Fixed by #440
Labels
feature-request New feature or request

Comments

@roxk
Copy link
Contributor

roxk commented Feb 20, 2024

Context:
microsoft/cppwinrt#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
roxk added a commit to roxk/wil that referenced this issue Feb 20, 2024
@dunhor dunhor added the feature-request New feature or request label Feb 22, 2024
roxk added a commit to roxk/wil that referenced this issue Mar 9, 2024
dunhor added a commit that referenced this issue Mar 28, 2024
…y (without WRL) (#440)

* Fix tests cmake not working with msvc + msbuild

* Initial impl of #439

* Make com server test a separate executable

* Fix not able to create non-projected implementation class

* Add tests

* Fix format

* Fix cannot set notifier directly

* Add test for defining module lock manually

* Rename default class factory

Co-authored-by: Duncan Horn <40036384+dunhor@users.noreply.github.com>

* Fix not using CppWinRTClassFactory elsewhere

* Clear tokens after revoking

* Make com_server_revoker non-copyable

* Do not record defaulted registration token

* Fix revoker not constructible

* Remove C++17 guard

* Handle server registration failure

Align behavior with WRL. Specifically, failing any server registration undo all registrations.

Fix revoke can throw, making dtor of com_server_revoker throw (which is bad)

* Remove C++/17 guard in tests

* De-templatize notifiable_module_lock

Make CustomModuleLockTest test user using another lock type

* Require user to define WINRT_CUSTOM_MODULE_LOCK

Detect and warn users to include notifiable_module_lock _before_ including winrt headers

* Fix format

* Update include/wil/cppwinrt_notifiable_module_lock.h

* Update include/wil/cppwinrt_register_com_server.h

* Add cppwinrt-com-server* tests to runtests.cmd

* Explain why push_back is safe

* Use wil::unique_com_class_object_cookie as revoker

* Ensure module lock's count is initialized

Co-authored-by: Duncan Horn <40036384+dunhor@users.noreply.github.com>

* Remove redundant try-catch

It was necessary, but now that we use wil's unique_com_class_cookie we
no longer need to catch, clean up and re-throw

* Try-catch CreateInstance

winrt::hresult doesn't seem to be recognized so it's handled separately

* Let wil handle winrt::hresult_error

* Add test for register_com_server failure

* Fix format

* Make notifiable_module_lock singleton #439

* Update activation after module starts waiting test

* Add notifiable_module_lock_base

---------

Co-authored-by: Duncan Horn <40036384+dunhor@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants