Code for this article: Dec99VP.exe (295KB)
George Shepherd is an instructor with DevelopMentor and a Software Engineer at RogueWave Software. George is coauthor of MFC Internals (Addison-Wesley, 1996) and Programming Visual C++ (Microsoft Press, 1998).|
Q I am developing an ActiveX® control using MFC. The control lives in a DLL. Once the control is invoked from Visual Basic®, I want to be able to step into the control's source code to debug. Is this possible? How do I do it?
Harry Feng via the Internet
A Debugging ActiveX controls residing in a DLL involves a little trick surprisingly few folks know. To debug any COM object residing in a DLL, simply load the DLL into Visual Studio®, select Settings from the Project menu, then point the "Executable for debug session" field in the Debug tab to the Visual Basic executable, as shown in Figure 1.
Figure 1 Debugging an ActiveX Control from within Visual Basic
Set your breakpoints in the object's source code, press F5 to start the debugger, and sit back to watch the fireworks.
Q I am trying to use a C++ ActiveX-based DLL that contains two source interfaces. I can get the default interface without any problem. Is there any way of getting the events from the nondefault source interface?
Pieter Oosthuizen via the Internet
A There are a few answers, depending upon the client environment, so I'll cover what it takes to connect to an object using nondefault source interfaces from ATL and MFC-based clients.
The sample is an ActiveX control created using ATL and living in a DLL named AnATLDLLSvr. There's nothing really special about this control except that it describes two outgoing interfaces: _IControlWithMultipleEventsEvents and _IControlWithMultipleEventsMoreEvents. Figure 2 shows the control's IDL code and how the control exposes the two outgoing interfaces.
Notice how the interfaces are described as plain old IDispatch interfaces (via the dispinterface keyword) and that they're labeled as source interfaces. The source keyword tells the world that they are outgoing interfacesthat is, they are interfaces to be implemented by the client. The default keyword tells interested clients that _IControlWithMultipleEventsEvents is the default source interface. For example, this is the interface Visual Basic uses to connect via the WithEvents keyword and that Visual Studio uses when manufacturing event handlers for your MFC-based client app. Let's start with setting up multiple event sinks with ATL.
The ATL Answer
In some ways, it's easier to set up multiple event sinks with ATL because ATL is so close to COM; it's really no problem managing the interfaces involved in setting up multiple event sinks. In fact, ATL has a very useful wrapper class for managing IDispatch-based event sinks, IDispEventImpl, which is defined as:
template <UINT nID, class T,
const IID* pdiid = &IID_NULL,
const GUID* plibid = &GUID_NULL,
WORD wMajor = 0, WORD wMinor = 0,
class tihclass = CComTypeInfoHolder>
class ATL_NO_VTABLE IDispEventImpl :
public IDispEventSimpleImpl<nID, T, pdiid>
// Yadda, yadda, yadda...
To use the IDispEventImpl class, simply derive your own event sink class from IDispEventImpl and then implement the dispatch member functions. Notice that IDispEventImpl is a template class that takes several parameters. These parameters include the ID for your event sink, the GUID of the source interface, the GUID for the type library, the major and minor versions of the type library, and a class for managing type information (which defaults to CComTypeInfoHolder). In addition to deriving from IDispEventImpl, your class needs to include an event sink map and implement the event sink functions. Figure 3 shows how to implement the two event sinks.
Remember that all you're really doing here is setting up implementations of IDispatch. IDispEventImpl creates implementations of IDispatch for you. To make the event sinks work in ATL, you simply derive a class from IDispEventImpl and apply the correct template parameters. In this case, the parameters include the callback interface IDs and the type library ID for the control.
In addition, notice the event sink map in the IDispEventImpl-based class. The event sink map contains a list of _ATL_EVENT_ENTRY structures, each containing information about the various member functions in the dispatch interface. This includes the control identifier, the GUID representing the dispatch interface, the method or property to be invoked, and a pointer to the function to invoke. The event sink map is filled by using the SINK_
IDispEventImpl implements IDispatch::Invoke by searching the event sink map for the DISPID, retrieving information about the function from the type library, and invoking the function through a class named CComStdCallThunk, which maps the dispatch method to the C++ method provided within the SINK_ENTRY_EX macro.
Notice that the EventSink2 class includes the function implementations of the source interfaces. The signatures for these functions came from the control's IDLor you can figure out the type information by using a program like OLEView to learn about the control's event signatures.
Setting up the sinks on the client side is a matter of creating a window to host the control, instantiating a couple of event sinks, and connecting the sinks once the window has been created. Figure 4 shows the use of event sinks within an ATL-based application.
The structure of the sample program is straightforward. It's a regular ATL-based app generated by the ATL AppWizard. There is a main window type named CMainWindow based on the ATL CWindowImpl class. CMainWindow includes the two event sinks as data members. Also notice that CMainWindow includes a data member of type CAxWindow for hosting the ActiveX control.
When WinMain kicks up, the application calls AtlAxWinInit, which enables control containment within ATL-based applications. Then WinMain creates an instance of CMainWindow, which creates the control during WM_
CREATE. After creating the control, CMainWindow gets the control's IUnknown pointer and sets up the event sink by calling DispEventAdvise through each event sink. DispEventAdvise is a handy function that manages the connection point protocol for you. This function asks the control for its IConnectionPointContainer interface, tries to find the outgoing connection point through IConnectionPointContainer::FindConnectionPoint, and then calls IConnectionPoint::Advise for you. DispEventAdvise saves you a bunch of typing.
As you can see, using ATL to create event sinks is fairly straightforward. Now let's see how to do this in MFC.
The MFC Answer
Setting up multiple event sinks within MFC is a bit less straightforward. Getting the main default sink hooked up is a no-brainer because MFC and the Class Wizard will do all the work for you. All you need to do is add select Project | Add To Project | Components and Controls and bring the control into your project. If you add the control to a form view or a dialog box, you can easily use Class Wizard to create your event handlers for the default source interface.
When you bring the control into your project, Visual Studio reads the control's type information, which has the description of the control's default source interface. That way, Class Wizard understands the names and signatures of the functions in the source interface. As soon as you select the Class Wizard ActiveX Events tab and start adding entries, Visual Studio starts building up an event sink map in your dialog box. However, Visual Studio understands only the default source interface, and if you want to start listening to a nondefault interface, there's a little more work to do.
As with ATL, when you're setting up an event sink in MFC you're simply implementing IDispatch. It would be nice if you could just create a new MFC event sink map by hand. Unfortunately, you can't because the MFC event sink macros generate function calls that create name collisions. Failing that, the easiest way to implement IDispatch in MFC is to have Class Wizard create the class (derived from CCmdTarget) for you, turn on the Automation option, and then use Class Wizard to implement the IDispatch properties and methodsthat is, use the Class Wizard Automation tab to add handlers for Event3 and Event4 from the secondary source interface. (Of course, there is a caveat, which you'll see in a minute.) Again, you'll need to look at the control's IDL or otherwise figure out the control's type library to get the names and signatures.
The last step in creating the event sink class is to make sure the secondary source interface is mapped to your event sink's IDispatch implementation. This means putting an entry in your class's interface map:
// The Event Sink's .CPP file...
// from the files generated by Class Wizard
After the MFC client hands the event sink interface over to the object, the object calls QueryInterface through the event sink using DIID__IControlWithMultipleEventsMoreEvents. Adding the entry to the event sink's interface map simply maps a QueryInterface request for DIID__IControlWithMultipleEventsMoreEvents to the event sink's implementation of IDispatch.
Once you've created the event class with an implementation of IDispatch, you'll want to create an instance of the event sink within your client:
class CMFCUsemultipleeventsetsDlg : public CDialog
// Regular MFC stuff...
This is a simple example where the control is hosted in a dialog box and the event sink is just becoming a data member of the dialog box. In addition to storing an instance of the event sink itself, the client needs to hold onto the event sink's IUnknown pointer as well as a cookie for "un-advising" the events.
You'll need the event sink's IUnknown pointer when setting up events. The following code shows the dialog box's constructor initializing the unknown pointer (you can always get this via GetIDispatch) by calling the event sink's GetIDispatch function:
// Regular MFC initialization...
m_dwCookie = 0;
Once you have the event sink's IUnknown pointer, you can easily connect the event sink using AfxConnectionAdvise, which manages the connection point protocol for you. Here's how to connect the event sink to the control via AfxConnectionAdvise:
// Normal init goes above here...
IUnknown* pUnk = NULL;
pUnk = m_ctl.GetControlUnknown();
Of course, you also need to tear down the connection when the dialog box goes away. Figure 5 shows how AfxConnectionUnadvise disconnects the event sink from the control. |
While it's straightforward to create an event sink using MFC and the Visual Studio Class Wizard, there is a caveat: MFC implements IDispatch through a table-driven mechanism. When looking up dispatch members, MFC uses the dispatch ID as an index into the table when invoking members. So if the control to which you're connecting an event sink uses unorthodox DISPIDs (ones that don't run sequentially starting at 1), you'll have problems with the event sink. This is because the DISPIDs that the control throws back to your sink won't be the same as the DISPIDs your sink expects. If you have an issue with DISPIDs, you may use alternatives like implementing IDispatch using the control's type information.
I showed you how to hook up event sinks if the event isn't the default source interface. The source keyword in IDL specifies an outgoing interface. The default keyword enables environments like Visual Studio and Visual Basic to generate event handlers for the outgoing interface without batting an eyelash. Unfortunately, making the tools so easy to use this way also makes it hard to get to source interfaces that aren't the default. But you can do it as long as you understand connection points and are willing to do just a bit more typing.
Have a question about programming in
Visual Basic, Visual FoxPro, Microsoft
Access, Office, or stuff like that? Send
your questions via email to George Shepherd at firstname.lastname@example.org.
From the December 1999 issue of Microsoft Systems Journal.
Get it at your local newsstand, or better yet, subscribe.