Click here to Skip to main content
15,895,779 members
Articles / Web Development / HTML

Handling VB ActiveX Events in Visual C++ Client

Rate me:
Please Sign up or sign in to vote.
4.92/5 (17 votes)
8 May 20019 min read 273.8K   2.5K   88  
This article shows how to handle custom events generated in a VB ActiveX component in a Visual C++ client.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<STYLE type=text/css>.title {
	FONT: bold 16pt Arial; COLOR: #ff6633; TEXT-ALIGN: center; TEXT-DECORATION: none
}
.newtitle {
	FONT: bold 12pt Tahoma; COLOR: #ff6633; TEXT-ALIGN: left; TEXT-DECORATION: none
}
.standard {
	FONT: 11pt Verdana; COLOR: #000000; TEXT-ALIGN: left; TEXT-DECORATION: none
}
</STYLE>

<META http-equiv=Content-Type content="text/html; charset=windows-1252">
<META content="MSHTML 5.50.4134.600" name=GENERATOR></HEAD>
<BODY>
<P class=title>Handling VB ActiveX Events in Visual C++ client</P>
<META GENERATOR="xSite - Version 1.0.0.233">
<PRE>
Author:      Amit Dey
Email:       amitdey@joymail.com
Subject:     VB ActiveX,MFC,COM,ATL,IDL
Level:       Beginners/Intermediate
</PRE>
<P class=newtitle>Introduction</P>
<P class=standard>This tutorial is an extension to my article on building VC 
clients for VB ActiveX DLLs. Having read such article, the question about 
handling events naturally comes to the reader's mind. 
<P class=standard>Here I'm going to show you how to handle custom events 
generated in VB ActiveX code components,in a Visual C++ client. First we are going to build a MFC client and then turn our attention to creating an ATL client. Doing all of this is not tough at all, as you shall see. A framework like MFC makes it very easy for the programmer to receive event notifications from an ActiveX code component. 
A word before we move on, I assume that the reader is conversant with VB ActiveX technology, 
Automation and MFC COM and IDL(Interface Definition Language). </P>
<P class=newtitle>VB ActiveX Components </P>
<P class=standard>A VB ActiveX component is a unit of code that follows the COM 
specification for providing objects. It exposes much of its functionality 
through one or more interfaces. These software components have a high 
reusability factor.<BR>As the component needs to communicate with the client, 
they can be implemented as an <B>in-process</B>(read DLL) component and an 
<B>out-process</B> (read EXE) component.</P>
<P class=standard>In an inprocess component(ActiveX DLL components), the 
communication between the server and the client is implemented in the address 
space of the client application. Though this makes them faster than ActiveX EXE 
components, who need to be loaded in their own address space, the biggest 
drawback is that a faulty DLL will crash the client, and in turn, the object. 
That tends to bring everybody down.:-) </P>
<P class=newtitle>VB Events</P>
<P class=standard>An event can be simply defined as something that occurs during 
the applications lifetime. Events allow a class to communicate to other classes 
and programs. A component <I>raises</I> an event to notify the client about the 
completion of some task. This event can be caught by the client, and the client 
can respond to the event as it sees fit.</P>
<P class=standard>Custom events provide event-handling capabilities to classes 
and components. The object that generates the event is known as the <B>event 
source</B> and the object that responds to an event is known as the <B>event 
sink</B>. First, we are going to fire a custom event from a VB ActiveX 
DLL and handle the notification in an MFC client. In other words, we have to 
build an event sink in an MFC client that responds to events generated by a VB 
ActiveX component, which acts as the event source. The code in the event sink is 
executed when the event is fired.</P>
<P class=standard>To declare a custom event called evtTaskDone in VB, use 
the <B>Event</B> keyword in the <B>General Declarations</B> section of a class 
module like: </P><PRE>Public Event evtTaskDone()</PRE>
<P class=standard>This custom event can then be fire by using the 
<B>RaiseEvent</B> keyword like:</P><PRE>RaiseEvent evtTaskDone</PRE>
<P class=standard>With all that in mind, we now roll up our sleeves and dig into 
some code. First, we are going to build a VB ActiveX DLL that is the source of 
the event.</P>
<P class=newtitle>Building the event source</P>
<P class=standard>Fire up VB and in the New Project dialog, choose <B>ActiveX 
DLL</B> and click <B>Open</B>. VB creates a new DLL component project called 
Project1, having a single class Class1. Go to Project-&gt;Properties and set the 
Project Name as <B>VBEvents</B>. In the Project Explorer View, right-click on 
Class1 and choose to remove it from the project. <b>Note: We could also have chosen to use this class, but then we wouldn't have seen the VB Class Builder.</b></P>
<P class=standard>Now right-click again on the Project Explorer View and add a 
single <B>Class Module</B> to the project. In the <B>Add Class Module</B> 
dialog, choose <B>VB Class Builder</B> and click on Open.</P>
<P class=standard>In Class Builder, go to <B>File-&gt;New-&gt;Class</B> and add 
a new class called <B>clsEventSrc</B> Accept the default values and click on OK. 
Next, go to <B>File-&gt;New-&gt;Event</B> and add a single event to this class 
called <B>evtNotify</B>. Update all the changes to the project and close the 
Class Builder window.</P>
<P class=standard>Next, click on <B>Tools-&gt;Add Procedure</B> and add a new 
procedure to the <B>clsEventSrc</B> class called <B>prcFireEvent</B> to the 
class you just created like:</P><PRE>Public Sub prcFireEvent()
RaiseEvent evtNotify
End Sub</PRE>
<P class=standard>The procedure simply fires our event. Save everything and go 
to <B>File-&gt;Make</B> to build VBEvents.dll and register the component.</P>
<P class=newtitle>Building the MFC client</P>
<P class=standard>Our MFC client, is a plain old Appwizard generated 
Dialog-based application with additional <B>Automation support</B>. As usual, 
open VC++ 6.0 and create a new MFC Appwizard EXE project called 
<B>MFCClient</B>. Hit Build to build the project, and take a break from all that 
hard work!</P>
<P class=standard>The OLE/COM Object Viewer is a nifty little tool that is 
shipped along with Visual C++ 6.0. It will help us generate the IDL file for the 
DLL component. Go to <B>Tools-&gt;OLE/COM Object Viewer</B> and open this tool. 
Next, in OLE/COM Object Viewer, click on <B>File-&gt;View Typelib</B> and 
navigate to the VBEvent.dll file that we have previously built. Ready for some 
magic? Click on Open and open up <B>ITypeLib Viewer</B> Can you can view the IDL 
file? Whoa! Save the file through <B>File-&gt;Save As</B>, as 
<B>VBEvents.IDL</B> and close the tool. We have no need for it at present. </P>
<P class=standard>Next in our VC++ project, add this IDL file to the project. In 
FileView, right click on the IDL file and choose <B>Settings</B>. In the 
<B>MIDL</B> tab, set the <B>Output header file name</B> to VBEvents.h and the 
<B>UUID filename</B> to VBEvents_i.c. Also deselect the <B>MkTyplib 
compatible</B> option.</P>
<P class=standard>Save everything and in FileView, right-click on the 
VBEvents.IDL file and choose <B>Compile</B>. This will build the typelibrary and 
generate the necessary files. </P>
<P class=standard>Examine the MIDL generated VBEvents_i.c file. It contains all 
the UUID definitions that the client can use to build a sink object. In 
VBEvents.h, notice the dual interface _clsEventSrc. The component's 
dispinterface __clsEventSrc is identified by DIID___clsEvent. This is the event 
source for out custom event.</P>
<P class=standard>The next step is to add an sink object to connect to the 
source event. Fortunately, for us, MFC makes the task of building an event sink 
as easy as 1-2-3 (and well,4-5-6) With a couple of MFC macros, you can delegate 
much of the intricacy involved behind building a sink to MFC. First, add a new 
<B>CCmdTarget</B> derived class to the project called <B>MFCSink</B>. In the 
ClassWizard choose to select the <B>Automation</B> option. This is our sink 
object with Automation support.</P>
<P class=standard>Then import the server's typelib in the client with #import. 
If you haven't read my previous article, read it here. Else, go right ahead and 
use code like: </P><PRE>#import "VBEvents.dll" rename_namespace("MFCClient")
using namespace MFCClient;
</PRE>
<P class=standard>There's nothing new to this code. While you are there in 
stdafx.h, also #include the afxctl.h file</P>
<P class=standard>Next, open MFCSink.cpp and modify the INTERFACE_PART macro so 
that the second parameter(IID) is the IID of the event source, in our case 
DIID___clsEventSrc. Your interface map should look like:</P><PRE>BEGIN_INTERFACE_MAP(MFCSink, CCmdTarget)
INTERFACE_PART(MFCSink, DIID___clsEventSrc, Dispatch)
END_INTERFACE_MAP()
</PRE>
<p class=standard>Next, in the DISPATCH Map of the MFCSink class add a DISP_FUNCTION_ID macro for each of the events defined in the source interface that you want to handle. My DISPATCH Map looks like: </p>
<pre>BEGIN_DISPATCH_MAP(MFCSink, CCmdTarget)
	//{{AFX_DISPATCH_MAP(MFCSink)
	DISP_FUNCTION_ID(MFCSink, "evtNotify",1,evtNotify, VT_EMPTY, VTS_NONE)
	//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()

</pre>

<P class=standard>In Classview, right-click on the <B>IMFCSink</B> interface and 
add a single Method evtNotify(). Notice, that as per our DISPATCH Map, this method takes no parameters and returns a void. Our implementation of this method displays a 
simple MessageBox and looks like:</P>
<PRE>void MFCSink::evtNotify()
{
// TODO: Add your dispatch handler code here
AfxMessageBox("Event notification handled in MFC client");
}
</PRE>
<P class=standard>All that remains for us to do is hook up and terminate the 
connection appropriately in the client code. MFC makes this job very easy with <b>AfxConnectionAdvise()</b> and correspondingly <b>AfxConnectionUnadvise()</b>. If you are not familiar with these functions, now'd be a good time to look up their documentation.</P>
<P class=standard>Moving on, declare three variables in the dialog class 
header as :</P><PRE>_clsEventSrc *m_pSrc;
MFCSink *m_pSink;
DWORD m_dwCookie;</PRE>
<P class=standard>The first is a pointer to the interface through which we shall 
fire the event. The second is a pointer to the sink object. Lastly, the 
m_dwCookie variable is a cookie that stores the number of connections that has been 
established. We'll need this when we want to disconnect from the event source.In our case, we set this to 1 in the dialog class constructor. 
Don't forget to #include the VBEvents_i.c file. The code to establish the 
connection in the dialog's OnInitDialog() member function looks like:</P><PRE>CoInitialize(NULL);
/*Initialize COM system*/
m_pSink=new MFCSink; /*create an instance of the sink object*/
/*create source object*/
HRESULT hr=CoCreateInstance(CLSID_clsEventSrc,NULL,CLSCTX_INPROC_SERVER,IID_IDispatch,(void**) &amp;m_pSrc);
if(SUCCEEDED(hr))
LPUNKNOWN m_pUnk=m_pSink-&gt;GetIDispatch(FALSE);
if(SUCCEEDED(hr))
{
/*establish the connection*/

if(AfxConnectionAdvise(m_pSrc,DIID___clsEventSrc,m_pUnk,FALSE,&amp;m_dwCookie))
return TRUE;
else
return FALSE;
}
else
return FALSE;
</PRE>
<P class=standard>As we have setup a connection, we also need to disconnect when 
the dialog is destroyed. We can do that in the dialog's OnDestroy(). In 
ClassView, right-click the dialog class and Add a Windows message handler to 
handle WM_DESTROY messages. In the handler, add the following code to 
successfully disconnect.</P><PRE>LPUNKNOWN m_pUnk=m_pSink-&gt;GetIDispatch(FALSE);

AfxConnectionUnadvise(m_pSrc,DIID___clsEventSrc,m_pUnk,FALSE,m_dwCookie);
if(m_pSink!=NULL)
{
delete m_pSink; /*the sink destructor must be public or compiler will complain*/
m_pSink=NULL;
m_pSrc=NULL;
}
</PRE>
<P class=standard>With everything in place, we now need to fire the event. 
Simply call: </P><PRE>m_pSrc-&gt;prcFireEvent();</PRE>
<P class=standard>anywhere in your code where you want to fire the event.</P>

<P class=newtitle>ATL Client</P>
<p class=standard>Building a pure ATL client means a little more typing than the MFC client. But it's a lot easier than creating connectable objects in raw C++. Remember, that the sink has to support IDispatch. So that means at a minimum,implementing 7 methods.( 3 for IUnknown and 4 for IDispatch). To our relief, ATL provides the <b>IDispEventSimpleImpl<> </b>and <b>IDispEventImpl<></b> template classes that helps us in quickly creating dispinterface sink objects. These is a host of information and code available for creating ATL sinks for dispinterface based source objects that you might want to lookup. Relevant Microsoft KB Articles Q:181277, Q:181845  and Q:194179</p>
<p class=standard> Back to the task at hand, to make our client very efficient, we'll use an IDispEventSimpleImpl derived class. First create a new ATL/COM AppWizard generated EXE project called <b>ATLClient</b>. To this add a dialog called <b>ATLClientDlg</b>.  The dialog has two buttons, one to setup the connection and the other to fire the event. Next import te server's typelib with #import as described in the MFC client section above. Moving on to the sink object, the declaration looks like:</p>
<pre>
#define IDC_SRCOBJ 1
static _ATL_FUNC_INFO OnEventInfo = {CC_STDCALL, VT_EMPTY, VT_NULL};

class CSinkObj : public IDispEventSimpleImpl&ltIDC_SRCOBJ, CSinkObj, &__uuidof(__clsEventSrc)&gt
{
public:
	HWND m_hWndList;
	CSinkObj(HWND hWnd = NULL) : m_hWndList(hWnd)
	{
	}

BEGIN_SINK_MAP(CSinkObj)
	//Make sure the Event Handlers have __stdcall calling convention
	SINK_ENTRY_INFO(IDC_SRCOBJ, __uuidof(__clsEventSrc), 1, evtNotify, &OnEventInfo)
END_SINK_MAP()

	// Event handler 
	HRESULT __stdcall evtNotify()
	{
		// output string to list box
		TCHAR buf[80];
		wsprintf(buf, "Sink : Notification Event Received");
		AtlTrace("\n%s",buf);
		return S_OK;
	}
};


</pre>
<P class=standard>All I have done is add a sink map to the IDispEventSimpleImpl-derived class and then add a sink entry corresponding to each event of a source interface that I would like to handle. The ATL_FUNC_INFO structure helps us pass parameters to event handlers. In our event handler however, we do nothing fancy. Just a simple debug message would do.
</P>
<p class=standard>In the dialog class, add variables:<pre>
private:
	CSinkObj* m_pSink;
	_clsEventSrc *pEvent;</pre><p class=standard>The dialog class's OnConnect() looks like:</p>
<pre>
LRESULT OnConnect(UINT,WORD,HWND hWndCtrl,BOOL& bHandled)
{
m_pSink=new CSinkObj(hWndCtrl);

HRESULT hr=CoCreateInstance(CLSID_clsEventSrc,NULL,CLSCTX_INPROC_SERVER,__uuidof(_clsEventSrc),(void**)&pEvent);
if(SUCCEEDED(hr))
{
m_pSink->DispEventAdvise(pEvent);
}
return hr;
}
</pre>

<p class=standard>As before, call:</p> 
<pre>pEvent->prcFireEvent();</pre> 
<p class=standard>when you want to fire the event. Don't forget to use <b>DispEventUnadvise()</b> to disconnect when the dialog is detroyed.</p>
<p class=standard>That's it! We have built both an MFC and an ATL client that responds to events generated by a VB ActiveX DLL code component. The code and project files were built with Visual C++ 6.0 SP3 under Win95.</p>
<p class=standard>I have included another project <b>VBTimer</b> consisting of a VB ActiveX DLL and respective ATL client project files. This project does something a little more sophisticated than our first VB DLL, which fires an event without any parameters. The ActiveX DLL implements a VB Timer that fires an event with an single parameter(timer count) after every 1 second interval. This event is caught by the ATL client that displays the timer count in the output window.
<P class=newtitle>Author</P>
<P class=standard>Amit Dey.</P>
<P class=newtitle>References</P>
<P class=standard>NIIT Technical Reference<BR>Microsoft KB Articles Q181845,Q181277 and Q194179 </P></DIV></BODY></HTML>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.


Written By
Web Developer
India India
Amit Dey is a freelance programmer from Bangalore,India. Chiefly programming VC++/MFC, ATL/COM and PocketPC and Palm platforms. Apart from programming and CP, he is a self-taught guitar and keyboard player.

He can be contacted at visualcdev@hotmail.com


Comments and Discussions