The goal of this chapter was to explain how to handle events raised by COM components (including ActiveX controls) when they use the connection points protocol. The complement to this chapter is Chapter 13, which covers the opposite direction, in which a .NET component is the event source and a COM component is the event sink.
In either case, this type of bi-directional communication is sometimes called tightly coupled events. Although the event source has no knowledge of the event sinks that are listening, every event sink must have prior knowledge about the event source. In a loosely coupled events system, as in COM+, an event sink can receive event notifications from event sources that it's not even aware of.
FAQ: I've hooked up a .NET event handler to a COM event but it never gets raised when it's supposed to! What's going on?
The likely cause of this is that an exception occurs when the COM source attempts to call a method on the source interface, but the COM object "swallows" the failure HRESULT returned rather than reporting it. This can actually be a fairly common occurrence for two reasons:
Most source interfaces are dispinterfaces.
By default, value types (UDTs) cannot be passed as parameters when late binding across the managed/unmanaged boundary.
The significance of the source interface being a dispinterface is that when the COM event source calls back upon any event sink, it is forced to perform late binding through the IDispatch interface. The Interop Marshaler does not support marshaling VARIANTs with the VT_RECORD type, discussed further in the next chapter, which is exactly what value type parameters become when passed to a method invoked through IDispatch. The Interop Marshaler throws an exception when this is attempted. One way to fix such a problem is to mark the importer-generated sink helper class with the IDispatchImplAttribute custom attribute and the value IDispatchImplType.CompatibleImpl. This custom attribute is described in Chapter 14, "Implementing COM Interfaces for Binary Compatibility," and modifying importer-generated classes is the topic of Chapter 7, "Modifying Interop Assemblies." If you own the COM component, you could instead change the source interface to a dual interface, although this prevents Visual Basic 6 clients from working properly.
Another cause of events not being raised to managed code is a COM object that calls QueryInterface for IDispatch rather than the specific source interface during Step 4 of Figure 1. Because the importer-generated sink helper classes are non-public, they do not successfully respond to such a query. For example, various events from Microsoft Excel, Microsoft Outlook, and Microsoft PowerPoint cannot be sinked in .NET applications without changing the sink helper classes generated by the type library importer to be public instead of private using the techniques introduced in Chapter 7 (or using a PIA that contains such customizations).
In addition to this issue, Microsoft PowerPoint 2002 has a dual source interfaceEApplicationwhose members are not marked with DISPIDs. This causes event handling in .NET applications to fail because the CLR cannot properly handle incoming invocations via DISPIDs without the proper DISPIDs present in the metadata description of the dual source interface. Although not available at the time of writing, a PIA for Microsoft PowerPoint should solve this problem by containing an updated interface definition with DISPIDs.
Occasionally, failure to source events can occur when the COM event source passes an invalid VARIANT. This causes the invocation on the .NET sink helper to throw an InvalidOleVariantTypeException. The BeforeNavigate2 event, used in the examples throughout this chapter, is one such event that runs into this problem!