Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ActiveX Events and MFC State

0.00/5 (No votes)
11 Aug 2004 1  
This article describes how to ensure that the MFC state is properly maintained when sinking ActiveX events

Introduction

This article describes some of the problems that you may encounter while trying to handle ActiveX events in an MFC application. Although there are many articles available that show how to sink ActiveX events, few mention the details of properly maintaining the MFC state. Maintaining the MFC state is critical if you want to use calls like AfxGetApp() and AfxGetResourceHandle() in your event sinks.

Although the concept MFC state is an issue whenever you make calls or callbacks between different modules, this article focuses specifically on the case of ActiveX events.

Background

This article assumes that you are familiar with ActiveX events and the fundamental concepts required to connect to an event source. To review this topic, see the article "VC Clients for VB ActiveX DLLs".

In addition, this article is only relevant if you are developing MFC clients for ActiveX event sources that use MFC internally.

MFC State

MFC uses three different types of state information.

  • Module
  • Process
  • Thread

This article is only concerned with the first one, module state. Although the current module state is maintained on a per thread basis, this article will focus on the single threaded case. For an in depth discussion of all the MFC states, see the book "MFC Internals, Inside the Microsoft Foundation Class Architecture" by George Shepherd and Scot Wingo.

Crossing a module boundary occurs when code execution calls into another dll, ActiveX control, or even when an ActiveX control calls back into the application. The latter occurs when an ActiveX event is fired. Whenever an MFC module boundary is crossed, the MFC framework must be notified so that the module state is kept up to date. Otherwise, any of the state specific calls would refer to the state of the prior module. The figure below demonstrates the concept.

In this simple example, the MFC application (MainApp) makes a call into an MFC ActiveX control. When this happens, the current module state must be changed. Next, the ActiveX control fires an event that MainApp has connected to. Once again, when the transition is made back into MainApp, the current module state must be changed.

MFC uses the concept of module state to keep track of things such as the current CWinApp object and the current instance handle. In order to properly interact with MFC, you need to make sure that you keep the state correctly synchronized.

For example, a common trick that many MFC programmers use is to retrieve the current CWinApp object pointer using the AfxGetApp() function, The returned pointer is then cast to their own application object and some custom method may be called using the pointer.

//

// Example code that counts on MFC state to get the current WinApp object

//

CMainApp *pApp = (CMainApp *) AfxGetApp();
pApp->DoSomething(); 

This code depends on the MFC state. Internally, AfxGetApp retrieves the App object pointer using the current module state. If the module state is incorrect, then the pointer returned from AfxGetApp will point to the wrong App object and the call can fail, corrupt memory or even access violate.

Another case that depends on the MFC state is when you attempt to load a resource. When you load a resource by ID, you usually want to load it from the current module's resources. An example of this is the CString::LoadString function. This function will load a string (based on an ID) from the current module, as determined by the current state. If the module state is not correct, the wrong string will be loaded or the string resource might not be found at all.

How to Manage the MFC State

Whenever a call is made that crosses an MFC module boundary, you can use the AFX_MANAGE_STATE macro to update the MFC state. This information will be valid until the function returns, at which point the previous state is automatically restored (the state is popped off the stack when the function returns). This function takes a pointer to an AFX_MODULE_STATE structure. Typically, when you are writing the code in an external dll, you will use the AfxGetStaticModuleState() function to supply this pointer.

When using Visual Studio, in most cases when you create a module (dll, ActiveX control or component) that supports MFC, the AFX_MANAGE_STATE(AfxGetStaticModuleState()) call will be inserted for you automatically when you add a new method or exported function. The wizard will add the macro at the top of the function. From this point on, you don't need to change the module state as long as you continue to make calls within the same module. On the other hand, if you cross over a module boundary again, perhaps to make a call to another dll with MFC support, the MFC state must be updated.

Note: there are some exceptions to the state management rules which make this topic even more confusing. Regular dlls that statically link to MFC and MFC extension dll's should not use AFX_MANAGE_STATE. See the MSDN article "Regular DLLs Dynamically Linked to MFC" for further details.

ActiveX Events

Finally we reach the topic of interest. As mentioned in the previous section, we must make sure that the MFC state is properly maintained whenever we cross over a module boundary. When an ActiveX event is fired, this is exactly what happens. Therefore, we must be sure to maintain the MFC state in the client (the ActiveX event sink).

There are several ways to sink ActiveX events. In the article that I mentioned earlier, "VC Clients for VB ActiveX DLLs", the author presents two different approaches.

  • An MFC Client
  • An ATL Client
The sections that follow discuss the different alternatives.

MFC ActiveX Event Sink

This approach involves creating a CCmdTarget derived class with Automation support. Methods are then added to match the fired event source routines and handle the event as desired.

The beauty of this approach is that you don't need to do anything to manage the MFC state! The MFC base class CCmdTarget automatically takes care of the state change management when your code transitions between the event source and the event sink (in the COleDispatchImpl::Invoke routine). CCmdTarget knows what the correct module state based on the state that was stored when the objected was constructed.

You can use the demo program included with this article to prove this to yourself. See the CMfcEventSink class for details.

ATL ActiveX Event Sink

This approach involves creating an IDispEventSimpleImpl derived class. Once again, methods must be added to match the event source.

Unlike the MFC approach, in this case you are responsible for managing the MFC state yourself. To do this, you should make use of the AFX_MANAGE_STATE macro discussed earlier. In this case however, since the event sink object is instantiated in the main application, use the AfxGetAppModuleState() function to supply the pointer to the AFX_MODULE_STATE structure.

For example, a typical event sink function for an object constructed in the main application should start with the following code.

//

// Make sure the MFC state is correct.

//

         AFX_MANAGE_STATE(AfxGetAppModuleState())    
//

//       The rest of your code...

//

As mentioned earlier, when this function returns, the MFC state will be automatically restored.

The demo program illustrates the use of this technique in the CAtlEventSink class.

The Demo Program

A demo project is included with this article to allow you to step through the code with a debugger and understand the MFC state management. The demo program consists of two projects, a main MFC program, and an ATL ActiveX control. The MFC main program is implemented as a simple dialog application.

An example event sink class is provided for both the MFC and ATL methods described above. These classes are named CMfcEventSink and CAtlEventSink respectively.

To fire events the ActiveX control implements an interface - IEventControlEvents. The control has a method - TestMethod - which fires one event - TestEvent. The button labeled "Fire Events" makes a call to the control's TestMethod function.

The CMfcEventSink and CAtlEventSink classes both have an OnTestEvent method which is what is called when the event is fired. In this method a simple test of the MFC state is performed using the CWinApp object. A message box is then displayed to indicate whether the MFC state is correct or not.

The event sink objects are instantiated and connected in the main program's CMainDlg::OnInitDialog() routine.

You can play with the code to understand how the MFC state is changing between function calls.

To run the main program, you must first register the control (control.dll) using Regsvr32. Alternatively, if you rebuild the project the control will register itself during the build process.

In addition to the two event sinks that were manually created, I also hooked into the control's event using the control's container site code. This was accomplished using the MFC ClassWizard. I added this simply to illustrate the simplest case. In this case the MFC state management is taken care of for you by the control container site classes.

Conclusion

This article has highlighted some of the issues that you may run into when using an MFC client to sink events from other MFC controls and components.

My first experience with this issue came after releasing a 1.0 version of an MFC application that successfully connected to and handled events from several other (non-MFC) components. There were no problems with the event sink code and everything functioned exactly as expected.

Then came version 1.1. We added our first MFC component. Immediately after adding the new component, there were assertions popping up in existing code that was previously working flawlessly. After quite a bit of research I found that our event sinks were suffering from MFC state problems.

Hopefully this article will help you to avoid the same problem.

History

  • 15-May-2004 Original

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here