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

C++/WinRT support for implementing out-of-proc WinRT servers #601

Closed
vidager opened this issue Apr 22, 2020 · 30 comments
Closed

C++/WinRT support for implementing out-of-proc WinRT servers #601

vidager opened this issue Apr 22, 2020 · 30 comments

Comments

@vidager
Copy link

vidager commented Apr 22, 2020

Currently, you cannot implement an OOP server without utilizing a hybrid approach with WRL or COM APIs.

It would be helpful if C++/WinRT provided a mechanism similar to WRL::Module that all implemented WinRT objects in the module would integrate with.

Like WRL, covers:

  1. Simple object registration
  2. Automatic outstanding object ref counting
  3. Synchronized COM shutdown and event when all objects have disconnected.

Not supported by WRL (I don't think), but nice to have:

  1. A delayed shutdown based on requested "de-bounce" time delay. i.e. trigger shutdown 5min after no objects are connected. Without this, a server can't easily create a shutdown delay. They must take a self-reference and have a way to monitor the module ref count to start the timer when ref hits 1 and reset timer when ref hits 2 or more.
@kennykerr kennykerr removed their assignment May 12, 2020
@davidmatson
Copy link
Contributor

We'd love to have this support as well.

Any thoughts on how hard this would be to implement? I'm less familiar with the plumbing for out-of-proc - any pointers to the underlying APIs involved?

@DrusTheAxe
Copy link
Member

Any update on this front?

@miniksa
Copy link
Member

miniksa commented Sep 11, 2020

Oddly enough, this just bit me in the butt today too. It's hard to be hearing "Use CppWinrt and leave WRL behind" when we're missing things like this.

@kennykerr
Copy link
Collaborator

kennykerr commented Sep 11, 2020

It would be great to have a bit more info on what exactly folks are looking for here. Here's a simple client/server that works well enough. This could easily be hosted inside an NT service or some other host but the precise details are unique to each host scenario.

Regarding Ben's points above:

For 1: C++/WinRT has never provided this and instead generates the list from metadata. I doubt we'd start providing this today just for COM. Generally, the list of COM classes should be small. For WinRT classes this isn't an issue.

For 2: This already exists and is extensible for different hosting/lifetime models.

For 3 and 4: This could be built on top of 2.

So, I'm not apposed but it would help if folks can provide more information in terms of requirements. Also, PRs are welcome. This isn't something we've got committed time to work on so if its something that matters to your team feel free to consider a PR. We are of course more than happy to review and provide expert feedback.

@miniksa
Copy link
Member

miniksa commented Sep 11, 2020

I'm looking for this. But in cppwinrt-speak.

wil::unique_event _comExitEvent;

void _releaseNotifier() {
	_comExitEvent.SetEvent();
}

int CALLBACK wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) {
	_comExitEvent.create();

	auto& module = Microsoft::WRL::Module<OutOfProc>::Create(&_releaseNotifier);

	auto comScope { wil::CoInitializeEx(COINIT_MULTITHREADED)};

	RETURN_IF_FAILED(module.RegisterObjects());
	
	_comExitEvent.wait();

	RETURN_IF_FAILED(module.UnregisterObjects());

	return S_OK;
}

@DrusTheAxe
Copy link
Member

DrusTheAxe commented Sep 11, 2020

I'll go 1 step further: I need Packaged COM OOP Server. Here's the closest docs I could find about it. Or better, follow this for a closer answer.

Not just a .exe provided an OOP server. Yes, a Classic COM OOP Server aka exe. Not a COM Service.
Also requires a Proxy/Stub dll, which means MIDL compiling idl?
And clear docs re what to add to appxmanifest.xml
And ideally, a real world VS solution with 3 projects - (1) OOP exe, (2) Proxy/Stub dll and (3) Make .msix given 1+2+appxmanifest.xml

@ChrisGuzak
Copy link
Member

FYI for OS.2020 devs, see this addition to wil that simplifies C++ WinRT WRL integration.

@sylveon
Copy link
Contributor

sylveon commented Sep 18, 2020

I'm also personally interested in this - I would like to provide an out of proc WinRT API that allows other programs to send commands to my program. Being in WinRT rather than COM allows easier consumption by managed languages, but being restricted to WRL only for the server implementation makes it harder for me to implement.

Kenny's snippet is interesting, but how would consumers from C# or Rust do to create an instance of the remote object?

@kennykerr
Copy link
Collaborator

Feel free to reopen if there's interest in exploring this further.

@DHowett
Copy link
Member

DHowett commented Dec 9, 2020

Terminal’s interested in doing this, but we don’t have reopen permission here. /cc @zadjii-msft

@kennykerr kennykerr reopened this Dec 9, 2020
@miniksa
Copy link
Member

miniksa commented Dec 9, 2020

Terminal’s interested in doing this, but we don’t have reopen permission here. /cc @zadjii-msft

Agree. I'm using WRL despite a spirit of @ChrisGuzak in my head telling me that new code based around WRL is a big no-no in 2020 and I should be using full C++/WinRT. I'm very interested in appeasing that projection I have of Chris and doing it The Right Way (TM).

@kennykerr
Copy link
Collaborator

Just to set expectations, this is not a wish list. 😉 Someone needs to offer a solution that everyone can review and rally around. I don't have one, partly because there are just too many ways to do OOP and I'm not confident any one solution would satisfy everyone's needs. I also don't have the time to invest in this at the moment.

@DrusTheAxe
Copy link
Member

FYI I'm still interested in this despite working around it as that won't be the last time I (or anyone else) need go there.

@kennykerr there are just too many ways to do OOP
Sounds like you've given it some thought. Care to share the ideas you've had? Even if you lack to the time to take it further it would help jumpstart anyone else interested in pursuing this.

@kennykerr
Copy link
Collaborator

I don't know that much about it, but I've played with enough wildly different approaches to know that I don't know enough. 😉

First there are hosting differences from NT services, dedicated or shared, and standalone server executables that may register factories at startup, or not. This may or may not impact how the code is written. For example, some registration APIs will block while others will return. Then there are both registry-based and API-based registration for both WinRT-based and COM-based activation. Then you have to figure out all the ways these can be combined and in what context some are valid while others are not.

Developers are combining these in a variety of ways, for example to get the magic of WinRT bindings in places where WinRT activation isn't supported. You just have to get Hart/Brian/Kevin going (from COM) and I'm sure they'll give you quite a few more options that I haven't thought of here.

@kennykerr
Copy link
Collaborator

Closing this issue as there has been no activity. Feel free to keep the conversation going.

@hez2010
Copy link

hez2010 commented Apr 20, 2022

Hello, is there any plan to commit on it? I want to author an WinRT out-of-proc exe server to serve as an IPC server without needs of writting WRL manually.

@vidager
Copy link
Author

vidager commented Apr 20, 2022

There is a helper to at least integrate C++/WinRT Implementation objects into the WRL module count.
https://github.com/microsoft/wil/blob/master/include/wil/cppwinrt_wrl.h

See the attached file for an example of how you can implement the Exe logic with a couple lines of WRL. The rest is then your C++/WinRT objects regstered with WIL via CoCreatableCppWinRtClass. You can also reuse module_count_wrapper if you want to do other object types such as singletons.
WinMain.zip

@sylveon
Copy link
Contributor

sylveon commented Apr 20, 2022

Isn't this for COM out of process classes? E.g. clients can't just construct the corresponding WinRT class and have to call CoCreateInstance then can cast to the WinRT class

@vidager
Copy link
Author

vidager commented Apr 20, 2022

Correct. Due to Windows limitations, not C++/WinRT, you cannot register activatable (WinRT) classes. That requires TrustedInstaller privileges. COM only requires Admin. Registration Free WinRT exists, but it only supports inproc activation from a dll AFAIK.

With that said, doing an initial CoCreateInstance and then casting to a WinRT RuntimeClass (or interface) isn't too bad, as long as you can CoCreateInstance from the client language you're using. C++ is easy, for C# use Interop (for the initial bootstrap), and Rust I'm not sure.

C++ usage example
winrt::com_ptr<Company::Area::Product::IMyWidget> objectPtr;
winrt::check_hresult(CoCreateInstance( __uuidof(MyWidget), nullptr, CLSCTX_LOCAL_SERVER, IID_IInspectable, objectPtr.put_void()));
auto obj = objectPtr.try_as<Company::Area::Product::IMyWidget>();

@kennykerr
Copy link
Collaborator

Yep, you can do the same in Rust.

@DrusTheAxe
Copy link
Member

DrusTheAxe commented Apr 21, 2022

Due to Windows limitations, not C++/WinRT, you cannot register activatable (WinRT) classes. That requires TrustedInstaller privileges.

You can make a packaged OOP WinRT server and access it via Dynamic Dependencies

WinRT registration requires the OOP server be a Windows component (and thus TrustedInstaller privilege) or an MSIX package authored by anyone. Dynamic Dependencies let's anyone consume the latter (it's not just for packaged apps anymore :-)

@sylveon
Copy link
Contributor

sylveon commented Apr 21, 2022

My app is packaged so that's an option - though can I implement the server in my main app process instead of as a dedicated server process? Since my idea is to provide third party apps an API to interact with my app, so a dedicated server process means I'm doing double RPC. Can apps create a dependency on a non-framework package as well? Documentation seems to imply it's not doable.

Also - dynamic dependencies is Windows 11 only. I can't ask apps to import WASDK's polyfill since WASDK brings in all of WinUI 3 (which, notably, results in conflicts if you're using WinUI 2). I don't have a lifetime manager for my app so that the polyfilled dynamic dependencies keeps it installed while being used and I don't know how I would implement one (this doesn't seem to be documented).


The expected use case is that apps import the WinMD, either manually or via NuGet, then the classes "just work", so C# code can eg do new MyAppController(). Some amount of setup like configuring dyn deps can work, but I guess at this point it's easier to just ask people to call CoCreateInstance. Are HSTRINGs and other WinRT constructs marshalled correctly over COM RPC?

@vidager
Copy link
Author

vidager commented Apr 21, 2022

For marshaling, you'd either need to register a proxy/stub dll with COM or both client and server each need the winmd file (to leverage MBM). I would recommend MBM as you don't have to deal with the fuss of proxy/stub dlls. To leverage MBM, you would ensure

  1. Client and server both ship with the winmd file. Winmd should sit next to the executables. I don't recall the full search paradigm COM goes through for the winmd, but we've always just dropped it next to the exe.
  2. winmd name must match the name of your namespace. If the object needing to be marshaled is in A.B.C namespace, you'd need your winmd named A.B.C.winmd (or I think a subset of the name works too. e.g. A.winmd or A.B.winmd).
  3. Keep all interfaces implemented on the RTCs public. i.e. if you have a private interface implemented on the object, but not declared on the public RTC in the winmd, you will create a much more complex marshaling scenario (for MBM). I can share some details on how you can do this, but I would not recommend it if you can avoid it.

@riverar
Copy link

riverar commented Apr 21, 2022

Meta: For those reading along, the acronym MBM refers to Metadata Based Marshaling.

@sylveon
Copy link
Contributor

sylveon commented Apr 21, 2022

MBM sounds really interesting! I'll give this a try when I can.

I suppose I don't need to mess with packaged COM stuff because I only make the class available while running, but packaged COM is to make it start up a server at will (this is for interop between other apps and mine, so I want users to still be in control of when my app runs).

@vidager
Copy link
Author

vidager commented Apr 21, 2022

Packaged COM allows a packaged app to register classic COM objects. I believe it only works for out-of-proc. So, if it's your app, you could have a MyApp.exe and a MyComServer.exe. Your MyComServer.exe would host the Packaged COM objects and your MyApp.exe would run your foreground app. When a client CoCreates one of your objects, COM will take care of starting your MyComServer.exe if it's not running, and you would own the logic for when it shuts down. Any client (including your own MyApp.exe) can CoCreate your packaged COM objects (assuming you set the security appropriately).

Your approach will depend on who you want to be the client and server. This likely depends on the user experience flow you want.

If you want your app to be the client and the partner apps' to be the servers: if they're packaged they could register via Packaged COM, if they're not-packaged, they'd register the classic style (requiring admin privileges and CLSID reg, etc). You also need a way for that partner to tell you "they exist" and which CLSID you should go CoCreate. i.e. they'd need to register with COM and register with your app (.e.g registry if non-packaged, or AppExtensions if packaged).

If you want your app to be the server and the partner apps' to be the clients, then you could leverage Packaged COM. Partner apps can simply make themselves known to your app by CoCreating your packaged COM object(s). This doesn't spin up your MyApp.exe, only your MyComServer.exe. The drawback here is the partner apps have to have something start them for your app to be aware.

Regardless, you should still switch from Classic COM to WinRT immediately after activation if you want to leverage WinRT projections and marshaling via MBM.

@wherewhere
Copy link

I try to use oop to make a Loopback Exemption Manager. The server is a win32 app written in cpp/winrt and the client is a UWP written in .NET. I tried to find out the way to using wrl to manage server by looking the winget. But it is too difficult to find out how to use it. So I use the destructor to watching for disposing...

@roxk
Copy link

roxk commented Mar 29, 2024

It's been 4 years since this ask so I might be too late, but my PR that implements (1) and (3) has been merged in WIL: microsoft/wil#440

We can now implement COM server with cppwinrt-only. No more WRL.

Usage example:

#include <wil/cppwinrt_notifiable_module_lock.h>
#include <wil/cppwinrt_register_com_server.h>

int CALLBACK wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) {
    wil::unique_event moduleEvent(wil::EventOptions::None);

    wil::notifiable_module_lock::instance().set_notifier([&]() {
        moduleEvent.SetEvent();
    });

    winrt::init_apartment();

    auto revoker = wil::register_com_server<MyServer>();

    moduleEvent.wait();
    return 0;
}

@wherewhere
Copy link

Wow, does it have any example?

@roxk
Copy link

roxk commented Mar 29, 2024

@wherewhere None for now but updating existing docs to migrate from WRL to cppwinrt+WIL is my next long term task. In the mean time, you can checkout WIL's test. For simplicity the tests use winrt::implements, but you should be able to write MyServer in idl and author with the usual MyServerT way.

This is my proof-of-concept repository before the PR. The helper class isn't in wil obviously and there are some minor API difference, but the gist is basically the same.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests