How can I marshal an out-of-process COM reference across process boundaries?
Asked Answered
H

5

6

I have an out-of-process COM server that needs to keep an eye on things. This server runs as a service and is, internally, a singleton. For simplicity sake, I will call him BossCom.

I have another out-of-process COM server that is a worker. It is, for system stability, a single-use server (meaning that if you create 2 WorkerCom's, there are 2 WorkerCom.exe's running). For simplicity sake, I will call him WorkerCom.

WorkerCom can be started by anything, even by itself if someone runs him via the command line with the right command line arguments.

The overall goal is for BossCom to know what WorkerComs are around, know what they are doing, and be able to give them orders (pause, stop, ramp up, etc).

My initial thought at this would be that whenever WorkerCom starts, he would CoCreateInstance a BossCom and call BossCom->RegisterWorker(IUnknown me). Then when the WorkerCom is about to shutdown, he would call BossCom->UnregisterWorker(IUnknown me). BossCom could QueryInterface the IUnknown for IWorkerCom and be able to issue commands.

That would work great if all these com objects were in the same process, but they're not. I thought about using the GlobalInterfaceTable, but it is only global in the sense of a single process.

I have spent a few days researching this and am at a loss. Maybe I'm tunnel-visioned.

How can I marshal a reference to a com object from the Worker to the Boss?

Oh, and, for what it's worth, BossCom is written in C# and WorkerCom is written in ATL C++ but I'll take solutions written in VB, Scala, Lisp, or anything. I figure I can translate the core idea. :-)

Humidity answered 14/1, 2011 at 14:18 Comment(8)
Doesn't this just work? I admit I've not done anything much with COM, but the whole point of it being out-of-process is that you're already getting COM to marshal calls across process boundaries, isn't it?Chevalier
Right, for most things, but IUnknown is just an pointer, right? The "this" pointer in WorkerCom won't have any significance in BossCom right? Or will the marshalling process keep the significance. I guess I should at least try it.Humidity
It should work off-the shelf given there're proxy/stubs or typelib to facilitate marshalling. In fact marshalling is exactly for that - to give a client a fake object ("proxy") that is mirrored to the server. It works transparently from the code perspective.Langsdon
Huh... I guess I should try it then. I had talked myself out of it working so I didn't even try it.Humidity
@Langsdon is right, I think. I also had limited experience with EXE COM servers but don't found much problems except UI control and performance. In ATL, a proxy dll is generated automatically for a EXE COM server. Clients use proxy dll in-proc fashion, proxy dll handle all inter-process work.Indevout
Unfortunately, I cannot accept comments as the "answer". @sharptooth, would you write up an answer that includes the MarshalAs information that I included in my answer so that I can give you proper credit? Then I'll delete my answer. I'd like you to include the note about MarshalAs because it was important to the working solution.Humidity
@Jere.Jones: Actually I'm not familiar with MarshalAs enough to even mention it in an answer.Langsdon
@Jere.Jones: Having to accept your own answer happens once in a while, especially when you ask a question on some hardcore specific feature of some not-so-widely used technology, there's nothing wrong about that.Langsdon
H
2

As per the comments, this does indeed work out of the box. There is one additional detail though that makes it work.

Originally when I was looking at C# interfaces that dealt with copying interfaces, the argument type was IntPtr. Well, an IntPtr is just a long so it transfers the value as is and, as such, doesn't work.

The key is to set the MarshalAs attribute on the argument. So my RegisterWorker method looks like this:

    public void RegisterWorker(
        [In, MarshalAs(UnmanagedType.IUnknown)] object ptr
        )
    {
        IWorkerCom worker = (IWorkerCom) ptr;
        Workers.Add(worker);
    }

Utterly amazing.

Humidity answered 14/1, 2011 at 18:7 Comment(0)
S
2

You should take a look at Monikers, that are used to identify a COM object instance even across different machines.

Silicic answered 14/1, 2011 at 14:47 Comment(1)
This is interesting. And it will potentially make other parts of my project easier. I remember working with DirectShow, monikers were just text strings. Those should marshal extremely easy. Heck, I could even put them in an ESE table so anyone (like other workers) could find them. A quick search didn't show how I can create a moniker for my object. Any pointers?Humidity
H
2

As per the comments, this does indeed work out of the box. There is one additional detail though that makes it work.

Originally when I was looking at C# interfaces that dealt with copying interfaces, the argument type was IntPtr. Well, an IntPtr is just a long so it transfers the value as is and, as such, doesn't work.

The key is to set the MarshalAs attribute on the argument. So my RegisterWorker method looks like this:

    public void RegisterWorker(
        [In, MarshalAs(UnmanagedType.IUnknown)] object ptr
        )
    {
        IWorkerCom worker = (IWorkerCom) ptr;
        Workers.Add(worker);
    }

Utterly amazing.

Humidity answered 14/1, 2011 at 18:7 Comment(0)
T
1

I had to do something similar recently and found that using shared memory worked very well for me. The BossCom could create and own the shared memory and the workers could register by making an entry in the shared memory. Here is an MSDN link to what I am talking about. Remember to use a mutex to synchronise access to the memory...

Tonjatonjes answered 14/1, 2011 at 14:26 Comment(3)
That handles registration ok, but I'm unclear on how that helps with the command and control of the workers.Humidity
The shared memory is simply a way of communicating through a scratchpad. The BossCom could write the commands to the memory and signal an event which the workers wait on. There are many other ways of doing this and it all depends on your security requirements. I just found this approach very quick to implement and very flexible.Tonjatonjes
I appreciate the idea, but UAC makes this more difficult. My application has to run in Windows XP and above. The API's that make shared memory usable across UAC user boundaries don't exist in XP. I've worked around this in the past, but I'm not eager to do it again.Humidity
C
1

You're a bit limited in being able to create marshalable interfaces in C#. No easy way to setup the proxy. No such problem in ATL, declare a callback interface in the IDL. And pass an instance pointer with the RegisterWorker() call. The server should store it until it gets the unregister call. Use that callback interface to generate notifications.

Crematorium answered 14/1, 2011 at 14:46 Comment(0)
M
1

You should also look into the ROT (Running Object Table), it may be a different way to solve this problem.

http://msdn.microsoft.com/en-us/library/windows/desktop/ms695276(v=vs.85).aspx

http://www.codeproject.com/KB/COM/ROTStuff.aspx

Microminiaturization answered 28/10, 2011 at 16:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.