Introduction
The MFC class library makes handling ActiveX control events so easy that you are misled to believe that handling COM events is no big deal, until you try to do it within a console application. As you may have heard, I wrote the very simple XYDispDriver class, which can be used to create COM objects and call COM methods easily, especially in console applications. It would be nice if we can also use XYDispDriver
to handle COM events, so I started to work on its enhancement.
Basically, a COM event is the opposite of COM method. Your code calls the methods in a COM object, and if you set up this thing called "event sink" correctly, the COM object will call your code (i.e. event handlers) when something happens within the object. That's why the COM event interface is called the "outgoing interface" (depending on which side you are standing, I guess).
Here is my idea. If I have a COM object which fires events, I will be able to find out the event interface, including its class ID and function signatures, from the type library. I will build a new COM object as my event handler, whose methods will match those in the event interface of the original COM object (same dispid, same signature, etc.). The magic is, I will use the XYDispDriver
class to create both objects and somehow "connect" them together. Whenever the original object fires an event, the corresponding method in the new object will be called to handle the event. Of course, I have to make it easy to use (in console applications). You can add more methods to the event handler object, even passing function pointers to it so that you can call functions defined outside the object when handling events.
As you can see from the source code, I added an Advise
method to the XYDispDriver
class. This method takes two arguments, the first argument is the IDispatch
pointer of the new COM object (the event handler), the second one is the class ID of the event interface of the original COM object. Calling the Advise
method is all it takes to "connect" the event handler to the event interface. The Advise
method is implemented with some boring COM API calls (QueryInterface
, FindConnectionPoint
, etc.), but the code is surprisingly simple. By the way, you don't have to bother with Unadvise
(if you have heard of it), it is done automatically when the XYDispDriver
object goes out of scope.
Now we test the idea. Using the MFC Control Wizard, I first created an ActiveX control TestCon
, which has one method called Connect
, this method will fire an InvalidLoginData
event whenever it fails. Then I created a second ActiveX control TestHndlr
as the event handler for the first control. The second control has one method that matches the dispid and signature of the InvalidLoginData
event in the first control, the handler method will pop up a message box when it is invoked. Finally, I wrote a console application that uses these two controls. Here is the code of the console app.
#include <stdio.h>
#include "XYDispDriver.h"
void main()
{
XYDispDriver dispCon, dispEvent;
if(dispCon.CreateObject("TestCon.1"))
{
if(dispEvent.CreateObject("TestHndlr.1"))
{
CLSID clsidEvent = {0xe77a1f7e,0xe3ff,0x11d5,
{0x88,0x12,0x00,0xb0,0xd0,0x55,0xb5,0x23}};
dispCon.Advise(dispEvent.GetDispatch(),clsidEvent);
}
else printf("Error: %x\n",dispEvent.GetLastError());
dispCon.InvokeMethod("Connect","MyName","MyPwd");
}
else printf("Error: %x\n",dispCon.GetLastError());
::MessageBox(NULL,_T("Done"),_T("Test"),MB_OK);
}
You can use the same technique with a more complicated event interface, of course. I have tested my XYMessenger.OCX control with this method, so it can now be used even in console applications. The more complicated situations (such as multiple event sinks, etc.) will be dealt with in a separate article.
The zip file included with this article only has source code for the two ActiveX controls and the console app. You can download the source code for XYDispDriver
from my other article. You can also find other articles and software from my home page.
Disclaimer
I do not claim that I invented anything new here. I am not a COM expert, this is actually the first time I used the IConnectionPoint
interface. There could be other more "standard" ways to do the same thing. Thank you.