I have a (long running) console application, written in C#, which I want to be able to manipulate through COM (so no InProc DLLs and regasm.exe). IDispatch
is all I need - so a classic OLE Automation object.
Here I'll present a minimal version of what I try to do. I've defined a COM class like this:
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("9009311a-c0b2-42a4-8e7c-f42091d71594")]
public interface ITestEvents {
void OnEvent();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
[ComSourceInterfaces(typeof(ITestEvents))]
public class ComClass {
public event Action OnEvent;
public int Test() {
return 100;
}
}
In Main()
I simply register the object in the Running Object Table (ROT) with "comTestApp"
Moniker and sleep the application.
You can see the full source here.
This works just fine when I try to invoke the object's methods. For example, this VBScript works OK:
Set obj = GetObject("comTestApp")
WScript.Echo obj.Test() Rem prints 100
But when I try to connect the events:
Set obj = GetObject("comTestApp")
WScript.Echo obj.Test() Rem Works
WScript.ConnectObject obj, "obj_" Rem Fails
Sub obj_OnEvent
WScript.Echo "Wish it worked"
End Sub
I get error on calling ConnectObject
(error 0x80020009 "Could not connect object").
The same code (only the class GUID/ProgID have to be added) works if I register the assembly as InProc object with regasm.exe but I don't need that. I need access to the running application and that's why I use the ROT.
I created a simple C++ test to see if I can find out more about the problem. The source is here. I have written a minimal COM object implementing the IDispatch
interface which should act as the Event Sink. First I get the object from the ROT, query for the IConnectionPointContainer
, then get IConnectionPoint
for ITestEvents
's IID and finally call its Advise()
method. As with VBScript, it fails (although I get another error - 0x80040202). I placed a breakpoint on the QueryInterface
method of the event sink to see what happens when Advise()
is called. I can see that QueryInterface
is called for various interfaces and finally it requests my ITestEvents
, which I return and set status S_OK
. But still, the Advise()
method returns the above error.
I have also tried another thing: I've set the GUID of the ITestEvents
to {00020400-0000-0000-C000-000000000046}
which is the IID of IDispatch
. And now Advise()
returns S_OK
! I have even simulated an event and the Invoke()
method of the event sink gets called! Alas this does not solve the problem in general. If you request it directly from IConnectionPointContainer
by IID - you get it, but it seems it is not properly enumerated by ITypeInfo
and VBScript still does not work.
I have almost no experience with COM so I'm not sure where to go from here. The fact that if I use the IDispatch
IID makes it work, makes me wonder if some custom Marshaling is needed for the ITestEvents
interface, although it is pure IDispatch
so I think it should be handled well by the runtime.
Thank you!
My conclusion is that it is not possible to supply connection sink (to Out of Process object) whose interface IID is not registered in the registry.
It seems that marshaling information is mandatory, in order to send the sink interface to the server. I was hoping that since the C# classes supply full ITypeInfo
, the COM runtime will figure out that the interface is IDispatch
type and use a default proxy. Alas, this does not seem to be the case so the only option remains - the registry.
As a bare minimum I found that this is needed:
[HKEY_CLASSES_ROOT\Interface\{9009311a-c0b2-42a4-8e7c-f42091d71594}\ProxyStubClsid32]
@="{00020424-0000-0000-C000-000000000046}"
This defines our events interface and says that we use for proxy the standard IDispatch proxy.
I've tested this on Windows 7 and 10 and it works! - VBScript can ConnectObject
and everything works fine.
Accidentally I found a Win7 installation where this did not work (win ver 6.1 build 7601 sp1). It worked only after registering the Type Library (regasm.exe /tlb
). The installation in question has Windows Updates disabled (my other Win7 is fully updated) so I guess at some point something has been changed, which enables the interface to be marshaled even without type library, based on the fact that we specify the default IDispatch proxy.
Finally, since I wanted to keep my app portable and independent of the registry, I resorted on manual events implementation. Something similar can be seen here.
User contributions licensed under CC BY-SA 3.0