I am working in C# .NET 7 where I can create a COM object of a type that is unknown at compile time.
var comTypeName = "Word.Application";//Assume this is passed in by the user and is unknown at compile time.
var comType = Type.GetTypeFromProgID(comTypeName);
var comObj = Activator.CreateInstance(comType);
I would like to be notified of events that happen on the COM object. I have researched extensively about IConnectionPoint/IConnectionPointContainer, event sinks, IConnectionPoint.Advise() and nothing I've found can solve my problem. So I suspect that either the problem is not doable in C#, or is so obvious and axiomatic, that nobody felt the need to explain it in any documentation. I am hoping it's the latter.
The crux of the issue is that every example I've found works with a COM object whose type is known ahead of time. Thus, the code knows which events to listen for, and defines an interface which implements those events, and passes it to IConnectionPoint.Advise():
icpt = (IConnectionPoint)someobject;
icpt.Advise(someinterfacethatimplementsallevents, out var _cookie);
From my research, the first parameter to Advise() is an object which implements the interface the source object is looking for. So how can I know what that interface should be when I don't even know what the source object is at compile time?
Some research seems to say that the sink object should implement IDispatch. But what method would be called on that? Invoke()?
This sounds somewhat plausible, but anything forward of .NET Core (I am on .NET 7) has stripped out IDispatch and much other COM functionality. So they are saying we should use an interface that no longer exists in .NET?
To begin to work around this, I have unearthed implementations of IDispatch from various sources online:
[Guid("00020400-0000-0000-c000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
public interface IDispatch
{
//
// Omitting type info functions for brevity.
//
//Invoke seems to be what we care about.
void Invoke(int dispIdMember,
[MarshalAs(UnmanagedType.LPStruct)] Guid iid,
int lcid,
System.Runtime.InteropServices.ComTypes.INVOKEKIND wFlags,
[In, Out][MarshalAs(UnmanagedType.LPArray)]
System.Runtime.InteropServices.ComTypes.DISPPARAMS[] paramArray,
out object? pVarResult,
out System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo,
out uint puArgErr);
}
What I am unsure of is if I should do something like this:
public class MyDispatch : IDispatch
{
void Invoke(int dispIdMember,
[MarshalAs(UnmanagedType.LPStruct)] Guid iid,
int lcid,
System.Runtime.InteropServices.ComTypes.INVOKEKIND wFlags,
[In, Out][MarshalAs(UnmanagedType.LPArray)]
System.Runtime.InteropServices.ComTypes.DISPPARAMS[] paramArray,
out object? pVarResult,
out System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo,
out uint puArgErr)
{
//Do something with the event info and dispatch to the appropriate place in my code.
//What I *really* need here is the string name of the event so that I can figure out where to properly dispatch it to.
/*
if (nameofevent == "Changed")
ChangeHandler();
else if (nameofevent == "Closed")
ClosedHandler();
*/
}
}
At this point, I've reached the end of the available information online for solving this problem and am unsure how to proceed further.
When the instance is dynamic, it's not easy to get events. But as you found out, you can get them using raw COM interfaces (IConnectionPoint, IConnectionPointContainer and IDispatch)
Here is a C# utility class that wraps
IDispatchand connects to the requested "dispinterface" events interfaces.The first thing to do is determine:
For that you can use the OleView tool from the Windows SDK, open a type library file which describes public interfaces that a COM object supports. It's often a .TLB file (or embedded in a .dll) but in the case of Office it's an .OLB. For Word, it's located in
C:\Program Files\Microsoft Office\root\Office16\MSWORD.OLB(or a similar path).In this sample I want to get Application.DocumentOpen event. This is what OleView shows me:
So here is how to get the event:
And the Dispatcher utility class: