Reducing the Size of Statically-linked MFC Applications in VC11
Hello, I’m Pat Brenner, a developer on the Visual C++ Libraries team, and I am the primary developer working on the Microsoft Foundation Classes (MFC).
In Visual Studio 2010, the size of statically-linked MFC applications grew substantially. We’ve gotten a number of comments about this issue, so I wanted to post an article about the cause and the solution that we have come up with.
In Visual Studio 2010, we added a feature to the resource editor which allows you to add MFC controls to your dialogs. The MFC control types appear in the toolbox along with the standard Windows controls. Properties specific to the MFC controls can be set on them, so they behave as desired when the dialog is created.
In order for this to work properly, a DLGINIT block has to be written in the RC file for the project, which contains the properties information in binary format. The DLGINIT block has to be parsed when the dialog is being initialized, so the controls can be initialized using the information in the DLGINIT block. The code to do this parsing lives in CWnd::ExecuteDlgInit. ExecuteDlgInit method lives in WINCORE.CPP, whose object is always included in every statically-linked MFC application (because it contains the CWnd constructors and the AfxWndProc method).
The code that performs the MFC control initialization, of course, needs to know about all of the MFC controls. Those controls, in turn, may need to know about various visual managers in order to know how to draw themselves. And the visual managers, in turn, have dependencies on other MFC classes.
The result of these dependencies is that much more of MFC needs to be pulled into a statically-linked MFC application, because the linker cannot determine at build time that none of those methods will need to be called, since it all depends on the content of the RC file and DLGINIT structures inside it.
We were alerted to this size increase in statically-linked MFC applications shortly before the release of Visual Studio 2010 RTM, but we were not able to definitively establish the cause before Visual Studio 2010 shipped. Even if we had, we most likely would not have been able to put the finishing touches on a solution before the release date, because we had to try several different approaches before arriving at a working solution that puts a very small requirement on the MFC developer.
To fix the problem, we eliminated a number of dependencies between MFC classes (further details are below). We also moved several methods that have an effect on the MFC control initialization:
- CWnd::ExecuteDlgInit, DDX_Control, AfxRegisterMFCCtrlClasses
- CMFCControlContainer::SubclassDlgControls and CMFCControlContainer::PreUnsubclassControl
into separate source modules.
These separate source modules are then compiled in two different ways:
- With _AFX_NO_MFC_CONTROLS_IN_DIALOGS not #defined, they are built into the standard static MFC libraries, NAFXCW[d].LIB and UAFXCW[d].LIB, with the standard behavior enabled.
- With _AFX_NO_MFC_CONTROLS_IN_DIALOGS #defined, they are built into a new small static MFC library, AFXNMCD[d].LIB, without the ability to initialize MFC controls on dialogs. (The NMCD in the library name is an acronym for “No MFC Controls on Dialogs”.)
The new smaller library has the same methods (same names, but different implementations) as the larger standard MFC libraries, so we must make sure to link it in first. This ensures that the functions that don’t have any dependency on MFC control initialization are used and the dependencies are eliminated. This is accomplished via symbols that are defined in the new source modules, and force-included via #pragma statements in AFX.H based on #defines set.
The result of this work is that you can simply #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS in your MFC application’s stdafx.h file, and all the code that performs MFC control initialization on dialogs will be left out of your application. In a simple dialog-based application, this will reduce the size of the application by approximately 80%. [Note that if you do use MFC controls on a dialog, and build with _AFX_NO_MFC_CONTROLS_IN_DIALOGS #defined, your application may not run at all (or dialogs will not appear) because a dialog containing a nonexistent window class cannot be created. We added TRACE statements to MFC to this effect to help point out this issue.]
In addition, we have made changes in the code generated by the MFC application wizard. It will generate code that contains #ifdefs for the _AFX_NO_MFC_CONTROLS_IN_DIALOGS, so:
- Dialogs will be derived from CDialog instead of CDialogEx if the #define is set.
- No CShellManager will be created in the application’s InitInstance method if the #define is set.
We have implemented these changes in MFC for the next major release of Visual Studio. Now that we understand the cause and the best solution, we looked at the possibility of porting the changes back to Visual Studio 2010 in order to benefit applications built with that version. Unfortunately, the changes we made to reduce the dependencies between MFC classes included:
- Moving D2D-related member functions/data out of the _AFX_GLOBAL_DATA class to a separate class
- Adding a new virtual method to both CMDIChildWnd and CMDIChildWndEx
- Adding a new method to the CWinApp class
Because these changes introduce binary incompatibilities, we are not able to port the changes back to Visual Studio 2010 without breaking existing MFC applications.
I hope you find this information helpful!
Pat BrennerVisual C++ Libraries Development