*
MSDN*
Results by Bing
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > December 1998
December 1998

Microsoft Systems Journal Homepage

The Visual Programmer

George Shepherd is a Senior Software Engineer at Stingray Software where he develops Visual CASE. Scot Wingo is cofounder of Stingray Software (www.stingsoft.com), an MFC/Java class library company. George and Scot co-wrote MFC Internals (Addison-Wesley, 1996).

Q I've developed an invisible in-process ActiveX® control using ATL. Now that the control is debugged and working correctly, I want to write a container application for it using MFC. I'm having several problems.
First, when I add my control to the container project, the Visual C++
® Gallery adds a wrapper class that treats the control as a CWnd. Although the control's type library states that all methods return HRESULTs, the wrapper class functions nevertheless return void, hiding useful information from the client. Furthermore, my control supports a dual interface, yet the wrapper class in Visual C++ accesses methods through IDispatch::Invoke (via CWnd::InvokeHelper). Why does Visual C++ eat my error codes, and why get IDispatch involved?
Second, to avoid method calls through IDispatch, I have set aside the wrapper class and instead instantiate the control like this:
 CmyApp::InitInstance() {
     AfxOleInit();
     CoCreateInstance( CLSID_MyCtrl, 
                       NULL, 
                       CLSCTX_INPROC_SERVER, 
                       IID_IMyCtrl, 
                       (void**) &pMyCtrl );
 .
 .
 .
 }
This allows the container to make direct calls to the control's methods and to receive return values correctly. However, now my event sink isn't hooked up. What's the secret to making this work?
Christine Winsor

A You've got to admit that the wizard-generated Visual C++ wrapper classes are great for the most part. Just point the wizard towards an ActiveX control and it reads the type library and generates a wrapper class around the control. That way you don't have the trouble of dealing with the raw COM interfaces. But while the wrapper class generated by the wizard hides the control's complexity, it also hides lots of useful information.
This bothers you because you are apparently a deep-down COM developer who lives for QueryInterface and the HRESULTs returned by the COM API and member functions. Yes, HRESULTs tell you not only that an operation failed, but exactly why the operation failed.
Unfortunately, we can't tell you exactly why the wizard-based wrapper classes work the way they do (and as a result, hide some of that important HRESULT information). We do know they were originally built by the developers of Visual C++ a long time ago, before dual interfaces became so popular. Our best guess is that they were never updated to reflect the state of the COM art as the technology evolved.
You're taking the correct steps to get around this difficulty. If you really must see the HRESULTs and you despise pushing your method calls through the sausage grinder that is IDispatch::Invoke, you need to deal with the COM interfaces head-on. You may use the interfaces in the raw (or you may use the Direct to COM support in Visual C++ and its mapping of COM error handling to C++ exceptions). That way you get around the overhead of using IDispatch and at the same time you get to manage your own HRESULT handling.
This brings us to the second question you asked. Your event sink isn't hooked up correctly because once you assume responsibility for creating and activating the control, you also have to assume responsibility for managing the hookup between the ActiveX control and the container. There's a lot of grungy code that MFC handles for you when you simply take a control off the palette and place it in a dialog box. Part of hooking up the control and container involves getting the client's event sink over to the control so the control can call it.
There are actually two ways to handle this issue. The first one is really easy. The second method will give you some insight into how ActiveX control events work. Let's first examine what MFC does for you when you drop a control on a dialog box. Then we'll cover the solutions to your problem.

ActiveX Controls and their Clients

Keep in mind that ActiveX controls are really just COM objects. They implement interfaces so clients can communicate with them. Most of the time COM objects materialize as a result of the client calling CoGetClassObject. Actually getting the control and the container to do meaningful work requires the client and the control to engage in a much deeper conversation by exchanging a number of other interfaces. Once the client has called CoCreateInstance, the client uses the newly acquired interface to garner further interfaces and to begin an ActiveX control-compliant dialogue with the control. Much of this conversation involves negotiating all the OLE document-ness of the relationship. After all, ActiveX controls rely on the OLE document embedding protocol to handle their visual presentations. In addition, ActiveX controls and their clients negotiate event handling during creation.
The most common way to use an ActiveX control in an MFC-based application is to add it to a dialog box template. Let's see just how this works.

Control Activation

Microsoft extended Windows
® dialog box resources to include ActiveX controls some time ago. If you watch the ActiveX control creation code for dialog boxes, you'll see that the dialog template information includes information necessary to create controls.
If a dialog box template happens to include an ActiveX control, MFC picks this up during the dialog's OnInitDialog function. OnInitDialog uses CDialog::HandleInitDialog to respond to the handle creating the dialog. HandleInitDialog retrieves the framework's control container manager (named afxOccManager) and then calls afxOccManager's CreateDlgControls function using the dialog box template.
COccManager::CreateDlgControls cycles through each control in the template. For each ActiveX control in the dialog box, COccManager::CreateDlgControls calls COccManager::CreateDlgControl to actually create the control. CreateDlgControl sets up a site for the new control and effectively calls CoCreateInstance to create the control. The control container asks for the control's IOleObject interface to be returned. The control container will need this interface to talk to the control intelligently.
Once the control comes into existence, the container still has to set up a few more things. Because ActiveX controls are in-place objects, the container needs to set up that part of the communication. We won't go through the entire protocol, but it involves the client and the object exchanging their OLE document interfaces, negotiating presentation spaces, and serializing the control's properties.
If all this succeeds, then the control is alive and breathing and almost ready to do its stuff. The container still needs to connect to the control so it can fire events to the container. What follows is an explanation of how "classic" ActiveX controls hook up to a client's event sink. We're covering it here because one of the solutions we give later involves hooking up your own event sink manually.

COM Connections

Most interfaces that you see are incoming interfaces; that is, the client calls inbound methods on the interface. An outgoing interface is an interface going the other way; it's an interface implemented by the client and called by the control. In ActiveX controls, these outgoing interfaces are hooked up using COM connections.
A COM connection consists of two parts: the source (the object calling the connection interface) and the sink (the object implementing the connection interface). In the case of ActiveX control events, the control is the source and the control container implements the sink. By exposing a connection point, an ActiveX control allows a control container to establish connections to the control. Using the connection point mechanism, an ActiveX control obtains a pointer to a set of functions implemented by the control container.
COM defines two interfaces for supporting connections: IConnectionPoint and IConnectionPointContainer. IConnectionPoint defines a single connection between an ActiveX control and its container, while IConnectionPointContainer "holds" connection points. Here's the interface definition for IConnectionPointContainer:
 interface IConnectionPointContainer {
 public: 
     HRESULT EnumConnectionPoints(
                        LPENUMCONNECTIONPOINTS* ppEnum);
     HRESULT FindConnectionPoint(REFIID iid,  
                        LPCONNECTIONPOINT* ppCP);
 };
ActiveX controls maintain two connection points by default: default sink and property notifications. Each connection point is represented by a separate object implementing IConnectionPoint.
The other connection interface is IConnectionPoint, which defines a single connection from one COM object to another.
 interface IConnectionPoint {
 public:
     HRESULT GetConnectionInterface(IID* pIID);
     HRESULT GetConnectionPointContainer(
             IConnectionPointContainer** ppCPC);
     HRESULT Advise(LPUNKNOWN pUnkSink, 
                    DWORD* pdwCookie);
     HRESULT Unadvise(DWORD dwCookie);
     HRESULT EnumConnections(LPENUMCONNECTIONS* ppEnum);
 };
IConnectionPointContainer and IConnectonPoint have the functions necessary to implement connectable objects. Let's examine how connections are established.

Establishing a Connection

An ActiveX control container can QueryInterface the control for IConnectionPointContainer to find out if the control supports connections at all. If the control returns a valid IConnectionPointContainer pointer, the control container can then query the control for actual connections using IConnectionPointContainer::FindConnectionPoint, asking for the interface by GUID. For example, if the container wants to set up a connection with the control's events interface, then the container would call FindConnectionPoint using the GUID representing the control's events interface.
Remember, the container implements the events interface that the control expects to see. Once the container has found the correct connection, the container can plug its interface pointer into the control's event set by calling IConnectionPoint::Advise. IConnectionPoint::Advise stores the interface so the control can use it later.
The control's FindConnectionPoint searches for the appropriate connection. If the container asks for the events connection, then FindConnectionPoint takes a shortcut and immediately returns the event's connection point. Otherwise, FindConnectionPoint searches the connection map for a connection that has a matching interface GUID. If the container finds the correct connection, then the container plugs its event sink into the control by calling the connection point's Advise function. The control's connection point then adds the interface (the events IDispatch or the property notification sink, for example) to a list of the clients it maintains.

Quick Activation

As you'll see in a minute, connecting a control to its container is a rather involved process requiring many round-trips. In an effort to curtail the number of round-trips and complexity involved in setting up the connection between a control and its client, modern ActiveX controls and containers support quick activation.
When a client tries to quick activate a control, the client calls QueryInterface to find out if the control supports an interface named IQuickActivate. If the QueryInterface succeeds, then the client can establish the connection by simply filling a QACONTAINER structure with the interface pointers required by the control. This includes setting up the event sink; the container hands its event sink interface over through the QACONTAINER structure. Quick activation also requires the client to hand over information about its ambient properties. This information goes into a QACONTROL structure.
Regardless of how the container activated the control, the next thing the container does is call COleControlSite::DoVerb to in-place activate the control. DoVerb simply calls the control's IOleObject::DoVerb function. At this point, the control is up and running, talking to the container, and ready to fire events to its container.
Now let's see how those ActiveX control events work.

ActiveX Control Events

Recall that an ActiveX control event is the notification a control's host receives when interesting things happen within a control. Imagine you have an ActiveX control that collects data from a real-time data source (like the New York Stock Exchange, for example). You could use the control inside your application to monitor changes in the market. The control would use an event to notify your program about stock price changes.
Adding events to an ActiveX control using ClassWizard is almost trivial. Just push the Add Event button, decide on a name for your event, specify any necessary parameters, and ClassWizard adds a function to your control. Whenever you want to notify the container of the event, just call the function inside your control.
For example, consider the stock market control again. One interesting event might be when the price of a certain stock changes. To add that event to the stock control, you'd first pull up ClassWizard and push the Add Event button. Typing CurrentPriceChanged in the Event Name field causes ClassWizard to add the function FireCurrentPriceChanged to your control. In addition, you might want to tell the container both the old price and the new price. To do that, just add parameters to the event.
Here's a good way to think about events: ActiveX control containers implement functions to handle events. To invoke an event, an ActiveX control calls the event handler implemented by the container. Using the terminology of the regular Windows architecture, the message handler is really a big event sink that is called continually by an event source (in this case, Windows itself).
ActiveX control containers implement something similar to a callback function. In COM land, it's not a function, it's an interface. The control container implements an Automation interface and makes it available to the control. Then the control can notify the container of any events through the interface.
The control should mark that its "Event source" is a DispInterface. Many control containers will not support sinks that are not DispInterfaces. The reason for this is that it's much easier to build the support up at runtime. Even those containers that build the sink at compile time may only want to support five of the 50 events and do not want to incur the overhead of a vtable containing 45 methods returning E_NOTIMPL. Let's take a look at how MFC does this.

Firing Events

Every time you add an event, ClassWizard deposits a function in your code. In our stock example, ClassWizard added the function FireCurrentPriceChanged:
 void FireCurrentPriceChanged(double dOldPrice, 
                              double dNewPrice);
FireCurrentPriceChanged is really just a friendly wrapper around COleControl::FireEvent. FireEvent takes a dispatch ID and a pointer to an array of bytes representing the event parameters. FireEvent uses va_start to set up a variable argument list, then calls the variable argument list-enabled function for firing events: FireEventV. COleControl::FireEventV gets a dispatch interface representing the control's events from the control.
The control knows about the container's events dispatch interface because the connection was set up when the control was created. How does MFC handle ActiveX control events? OLE controls fire events by calling functions in an interface implemented by the client. FireEventV has to retrieve the container's interface. This is where the COM connections become important to an MFC-based ActiveX control.
MFC's COleControl class has a member class of type CConnectionPoint called m_xEventConnPt. To invoke the event, FireEventV first needs to get the event handler function from the container. FireEventV uses m_xEventConnPt's GetConnections function to get the connections. CConnectionPoint maintains arrays of interface pointers and connection interfaces. When the container created the control, it asked for the connection point representing the events interface. Then the container plugged in the events interface using IConnectionPoint::Advise.
In MFC, the standard event connections happen to be dispatch interfaces (or IDispatch pointers). To execute the container's event, FireEventV declares a COleDispatchDriver object on the stack. COleDispatchDriver is a handy MFC class that wraps an IDispatch pointer, making it easier to access IDispatch methods and properties.
Next, for each of the connections represented by the control's m_xEventConnPt object, FireEventV retrieves the container's IDispatch pointer from m_xEventConnPt. The connections are represented by an array of IDispatch pointers. FireEventV attaches the IDispatch interface to the COleDispatchDriver object using COleDispatchDriver::Attach. Once the dispatch driver is attached to the IDispatch pointer, FireEventV can easily call the container's IDispatch using COleDispatchDriver::InvokeHelper, passing the event's dispatch ID and parameter list. After the control finishes invoking the control container's method, FireEventV cleans up by detaching the COleDispatchDriver object from the container's IDispatch pointer.
So that's how ActiveX controls fire events. It's a symbiotic relationship between the control and the control container. The control can only fire events as long as the container has functions that represent the events. Obviously the container is going to have some responsibilities. Let's see what those are.

How MFC Handles Events

Recall that a control's events dispatch interface is inside the COleControlSite class. COleControlSite calls its IDispatch for events using an event sink. The event sink is simply a version of IDispatch implemented the MFC way. That is, COleControlSite uses the nested class/interface map approach. That means COleControlSite maintains an IDispatch vtable within the nested class called COleControlSite::XEventSink, and COleControlSite maintains an event sink member called m_xEventSink.
When an ActiveX control container creates a control, the container asks the control for its IConnectionPointContainer interface. If the ActiveX control container gets back a valid IConnectionPointContainer interface, the container knows the control supports connections. Next, the container asks the control for the GUID of its primary event set (using another interface implemented by the control, IProvideClassInfo2). Once the control container has the events interface's GUID, the control can get the IConnectionPoint pointer that represents the events. Then the container can register its event handler (the event sink COleControlSite::m_xEventSink) with the control using IConnectionPoint::Advise.
Remember, the event sink is really just another version of IDispatch. MFC implements the event sink through some macros that generate an event sink map. (More maps and macros? Pretty unbelievable.)
Any CCmdTarget-derived class can have an event sink, just like every CCmdTarget can have a message map. A CCmdTarget's event sink map relates an event set's dispatch member to a member function inside the CCmdTarget-derived class. When you track an event through the container's event sink, you'll see this happen.
To fire an event, the control just calls IDispatch::Invoke (somehow implemented by the container). If the container is written using MFC, the call ends up in COleControlSite::m_xEventSink::Invoke. MFC's event sink m_xEventSink::Invoke calls COleControlSite::OnEvent, calling the control's parent window's OnCmdMsg, which calls COccManager::OnEvent (an undocumented MFC class that manages OLE controls), finally ending up in CCmdTarget::OnEvent.
MFC implements a lookup mechanism to match OLE control events to C++ member functions for handling the events. A CCmdTarget's event sink map is like the other lookup mechanisms in MFC. Similar to the message map syntax, MFC includes the DECLARE_EVENTSINK_MAP and the BEGIN_EVENTSINK_MAP/END_EVENTSINK_MAP macros to set up the event sink map. The event sink map really just boils down to an array of event sink entry structures:
 struct AFX_EVENTSINKMAP_ENTRY
 {
     AFX_DISPMAP_ENTRY dispEntry;
     UINT nCtrlIDFirst;
     UINT nCtrlIDLast;
 };
Notice that AFX_EVENTSINKMAP_ENTRY includes an AFX_DISPMAP_ENTRY. That's a clue that MFC is treating the event as another automation method. The AFX_DISPMAP_ENTRY represents the method that is called as a result of firing the event.
CCmdTarget::OnEvent uses CCmdTarget::GetEventSinkEntry to look in the command target-derived object's event sink map to find an event map entry. If OnEvent finds an event sink entry, OnEvent packages the parameters (remember, they're still represented by an array of VARIANTs) and uses CCmdTarget::CallMemberFunction to invoke the event. While this is a fairly elaborate scheme, it works pretty well as long as everyone's in the same apartment—and it merges right in with the MFC command routing mechanism.
But let's get back to the problem: you can't get your event sink hooked up if you call CoCreateInstance yourself. Here are two ways to resolve your issue.

Solution 1

The first solution is the easiest. Your original issue was that you wanted to skip past all that IDispatch nonsense and get to the dual interface implemented by your control. Go ahead and use the standard Visual C++ mechanism for adding a control to your project. When you want to make calls through the dual interface, just ask the control for a dual interface as follows. Imagine a simple control that implements an interface named IInvisCtl. IInvisCtl has a function for calculating the square root of some number. You can ask the control for its IUnknown pointer by calling CWnd's GetControlUnknown. Figure 1 shows how to get a control's IUnknown.
Because you let MFC take responsibility for setting up the control, your regular ClassWizard-generated event handlers are all wired up. And because you have access to the control's IUnknown pointer, you have a way to get to any other interfaces implemented by the control.
Let's imagine that for some reason you really must create your control using CoCreateInstance and bypass all the standard ActiveX setup protocol. You can still set up your own event sink. Here's the second technique.

Solution 2

If all you're interested in is the events at runtime, then it doesn't make sense to connect all those extra interfaces—just connect your own event sink. Remember that COM is all about separating an object's interface from its implementation. When an object wants to fire events, the client's biggest responsibility is to provide an implementation of the callback interface. Most of the time, an MFC-based client is going to implement the callback using the event sink map that we described earlier. The client may choose to implement the callback interface another way. That is, you may want to implement the callback interface and hook it up manually.
One way is to write an MFC-based class supporting automation. You can then use ClassWizard to implement the interface needed by the object. First use ClassWizard to write the class. Make sure to select the Automation checkbox to turn on IDispatch support for the class. Then use the ClassWizard Automation tab to create an implementation of your control's callback interface. That is, for each method in the object's main outbound interface, enter a method using the ClassWizard. For example, given the control's outbound interface description below, you'd want to create two methods using the Automation tab.
 dispinterface _IInvisCtlEvents
 {
     properties:
     methods:
         [id(1)]void CalculatedSquareRoot(double d);
         [id(2)]void HadAGoodTime();
 };
Once you've created the implementation of the callback interface, be sure to copy the interface's GUID to the MFC project's ODL file as well as to the callback class's CPP file. Figure 2 shows the client application's ODL file. Be sure the interface name and GUIDs agree between the client and the object. Here's the callback interface's GUID:
 // {93BC134B-065C-11D2-ABCD-A5273A388638}
 static const IID IID_IInvisCtlEvents =
 { 0x93BC134B, 0x065C, 0x11D2, 
     { 0xAB, 0xCD, 0xA5, 0x27, 0x3a, 0x38, 0x86, 0x38 }
 };
Next, declare an instance of the callback in your project wherever you need it. This will serve as the callback implementation that the object needs. Then, whenever you want to receive events from the object, hook up the event sink as shown in Figure 3. Once this is done, your custom sink will start receiving events from the control.

Conclusion

As you can tell, the design philosophies behind ATL and MFC are somewhat divergent. MFC lets you build useful controls centered around the MFC framework. The wizard support is good and the framework hides most of the ugly details, but also hides some of the information as well—information like HRESULTs. You can get around that by working with pure or dual COM interfaces. ATL makes implementing pure COM and dual interfaces pretty painless.
As a control container development tool, MFC doesn't cut it all the time—the wrapper classes generated by the wizard don't understand dual interfaces. If you want to get to the vtable interface from within MFC, you have two choices. First, you can simply plant the control on your dialog box and then query for the pure COM interfaces. Second, you can create the COM object using CoCreateInstance and ask for the pure COM or dual interface. If you follow this path, you miss out on having your container's event sink hooked up automatically. The easiest way around this is to set up your own event sink and hook it up manually.

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 70023.1000@compuserve.com.

From the December 1998 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1998 Microsoft Corporation. All rights reserved. Legal Notices.

© 2015 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft