How to make small statically linked MFC EXEs in Visual C++ 2010

As a followup to my previous blog entry, I’ve decided to make a simple workaround for those users that want to create a statically linked MFC EXE in 2010 with a size comparable to what was possible in 2008 SP1, that is, a size around 600K instead of the 1800K that you’d get by creating a simple MFC SDI app with classic menus, native/default look and feel, and classic docking toolbars.

It turns out that there are only three source files from the “classic” MFC source code with dependencies on the feature pack source code, that is causing it all to be pulled in when linking. If we can modify this source code by including these three files in our projects, by removing references to the feature pack source code, then the generated EXE size drops dramatically to 600K.

And of course, we must make sure that we do not use any of the feature pack classes, because as soon as you pull one in, you get most of it linked in. I’m specifcally alluding to CDialogEx. CDialogEx is the “new” base class for dialog boxes generated by the MFC appwizard, including the default generated About box. CDialogEx is relatively useless, unless you want to change the background color and/or image of your dialog box, which is something I wouldn’t recommend doing anyway. So the first thing you want to do is change any instances in your project from CDialogEx to CDialog (base classes, base method calls, etc). (a simple Find in Files will suffice)

Then, follow the directions below to get your app back to a reasonable size.

1) Create a default MFC SDI app with no special features (MFC standard, windows native/default style, classic menu, classic docking toolbar, no restart manager support) (this step is not necessary if you already have a project to play with)
2) Change to MFC static linking using project properties
3) add the following MFC source files to your project (copy them from the MFC source file folder located in the place you installed Visual Studio 2010, usually your program files\microsoft visual studio 10.0\vc\atlmfc\src\mfc into your project folder and then “add existing item” for all three files)

afxctrlcontainer.cpp
afxglobals.cpp
winmdi.cpp

4) comment out (or #if 0/#endif) the following code in the MFC source files added:

in afxctrlcontainer.cpp:
all the includes after afxctrlcontainer.h
some lines in CMFCControlContainer::CreateDlgControl
some lines in PreUnsubclassControl
the call to tagManager in ReadBoolProp

in afxglobals.cpp
all the includes after comdef.h except afxglobals.h
the loop that starts with “Notify toolbars about font changing:”
the cleanup for various items ( these will be obvious based on the errors you get compiling after removing the includes above)
the references to CPaneFrameWnd
the references to feature pack classes in AfxIsExtendedFrameClass
the reference to CMFCToolbar in AfxIsMFCToolbar

in winmdi.cpp
the two includes after sfdafx.h
the block of code in UpdateClientEdge that starts with CMDIChildWndEx

5) In your project’s stdafx.h add the following lines (these are internal MFC code that some of the source files require, you can add them to the individual source files instead of you prefer):

#include "afxpriv.h"
#include "afxole.h"
#include "..\src\mfc\oleimpl2.h"
#include "..\src\mfc\afximpl.h"

6) Remove the

#include <afxcontrolbars.h>

from the stdafx.h if applicable. And remove any references to CShellManager in your generated project.

After doing the above, recompile your release build, and notice that your generated EXE should be quite a bit smaller (around 600K instead of 1800K for the above example).

About these ads

About tedwvc
On this blog you'll find some tips and tricks for dealing with Visual C++ issues.

42 Responses to How to make small statically linked MFC EXEs in Visual C++ 2010

  1. Jimmy Lenon says:

    I tried these steps and it reduced the size of my exe from 2Mb to 700Kb. Great work.

  2. Gary says:

    This worked. Thanks! I’m just wandering why MS does not have option to turn of this libraries. I always seen Borland’s GUI apps were larger than VC++ but not anymore with VC++ 2010.

    Maybe there is option that we don’t know? If there is no options than Microsoft don’t be lazy and destroy VC ++ name. Add option in settings to disable this feature packs that makes static exe’s very large files!!!

    • tedwvc says:

      I agree, there should be an option, there is definitely not one, as I’ve talked to the lead developer of MFC at Microsoft and he stated the same thing. These dependencies were mainly put in there so that the design time (resource editor) support for ribbons are working properly. With some simple restructuring it could have been done and saved us from using this workaround.

  3. Pallavi Saxena says:

    I tried the solution in a DHTMLDialog based MFC application. But I get a list of linker errors all from uafxcw.lib(afxglobals.obj) for some symbols already defined in afxglobals.obj.

    Is this approach meant for SDI/MDI applications only?

    • tedwvc says:

      It works for DHTMLDialog apps as well. I did a test, creating a dialog box based app, and checked the HTML Dialog checkbox in the wizard, added the 3 files I mentioned (edited from an older project). The only variation from my instructions, is that I had to get rid of the include afxcontrolbars.h and the references to CShellManager in my generated dialog’s InitInstance. If you need copies of my edited three cpp files let me know I can send you them via email. The resulting EXE was 443K

      • Gregory Muirhead says:

        I have also gone through the instructions listed and a new MFC simple dialog app with the changes mention still comes to 1.6 MB. I have removeds the CDialogEx references as well but still no go. If you have the source and can share them that would be great thanks.

      • tedwvc says:

        sent requested sample via email

  4. Pallavi Saxena says:

    Hi Ted,

    Thank you for replying.

    For a new project I too am able to compile and link the DHTMLDlg based app. But for my existing application, I get the linker errors.

    I also had to #define _WIN32_WINNT 0×0601 to get rid of compilation errors related to m_bComInitialized being not defined.

    Notable that I have imported a tlb (*_i.c and *_i.h) for ATL based simlpe object into my MFC app. Is that causing the problems?

    Linker Errors:
    —————–

    uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: __thiscall CMemDC::CMemDC(class CDC &,class CWnd *)” (??0CMemDC@@QAE@AAVCDC@@PAVCWnd@@@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: __thiscall CMemDC::CMemDC(class CDC &,class CRect const &)” (??0CMemDC@@QAE@AAVCDC@@ABVCRect@@@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: virtual __thiscall CMemDC::~CMemDC(void)” (??1CMemDC@@UAE@XZ) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: __thiscall AFX_GLOBAL_DATA::AFX_GLOBAL_DATA(void)” (??0AFX_GLOBAL_DATA@@QAE@XZ) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: __thiscall AFX_GLOBAL_DATA::~AFX_GLOBAL_DATA(void)” (??1AFX_GLOBAL_DATA@@QAE@XZ) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: void __thiscall AFX_GLOBAL_DATA::UpdateFonts(void)” (?UpdateFonts@AFX_GLOBAL_DATA@@QAEXXZ) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: void __thiscall AFX_GLOBAL_DATA::OnSettingChange(void)” (?OnSettingChange@AFX_GLOBAL_DATA@@QAEXXZ) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: void __thiscall AFX_GLOBAL_DATA::UpdateSysColors(void)” (?UpdateSysColors@AFX_GLOBAL_DATA@@QAEXXZ) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: int __thiscall AFX_GLOBAL_DATA::SetMenuFont(struct tagLOGFONTW *,int)” (?SetMenuFont@AFX_GLOBAL_DATA@@QAEHPAUtagLOGFONTW@@H@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “protected: void __thiscall AFX_GLOBAL_DATA::UpdateTextMetrics(void)” (?UpdateTextMetrics@AFX_GLOBAL_DATA@@IAEXXZ) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “protected: struct HBITMAP__ * __thiscall AFX_GLOBAL_DATA::CreateDitherBitmap(struct HDC__ *)” (?CreateDitherBitmap@AFX_GLOBAL_DATA@@IAEPAUHBITMAP__@@PAUHDC__@@@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: void __thiscall AFX_GLOBAL_DATA::CleanUp(void)” (?CleanUp@AFX_GLOBAL_DATA@@QAEXXZ) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “void __cdecl ControlBarCleanUp(void)” (?ControlBarCleanUp@@YAXXZ) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: int __thiscall AFX_GLOBAL_DATA::DrawParentBackground(class CWnd *,class CDC *,struct tagRECT *)” (?DrawParentBackground@AFX_GLOBAL_DATA@@QAEHPAVCWnd@@PAVCDC@@PAUtagRECT@@@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “class CFrameWnd * __cdecl AFXGetParentFrame(class CWnd const *)” (?AFXGetParentFrame@@YAPAVCFrameWnd@@PBVCWnd@@@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: unsigned long __thiscall AFX_GLOBAL_DATA::GetColor(int)” (?GetColor@AFX_GLOBAL_DATA@@QAEKH@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: int __thiscall AFX_GLOBAL_DATA::SetLayeredAttrib(struct HWND__ *,unsigned long,unsigned char,unsigned long)” (?SetLayeredAttrib@AFX_GLOBAL_DATA@@QAEHPAUHWND__@@KEK@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: void __thiscall AFX_GLOBAL_DATA::EnableAccessibilitySupport(int)” (?EnableAccessibilitySupport@AFX_GLOBAL_DATA@@QAEXH@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: class ATL::CStringT<wchar_t,class StrTraitMFC<wchar_t,class ATL::ChTraitsCRT > > __thiscall AFX_GLOBAL_DATA::RegisterWindowClass(wchar_t const *)” (?RegisterWindowClass@AFX_GLOBAL_DATA@@QAE?AV?$CStringT@_WV?$StrTraitMFC@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@PB_W@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: int __thiscall AFX_GLOBAL_DATA::ExcludeTag(class ATL::CStringT<wchar_t,class StrTraitMFC<wchar_t,class ATL::ChTraitsCRT > > &,wchar_t const *,class ATL::CStringT<wchar_t,class StrTraitMFC<wchar_t,class ATL::ChTraitsCRT > > &,int)” (?ExcludeTag@AFX_GLOBAL_DATA@@QAEHAAV?$CStringT@_WV?$StrTraitMFC@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@PB_W0H@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: int __thiscall AFX_GLOBAL_DATA::DwmExtendFrameIntoClientArea(struct HWND__ *,struct _AFX_MARGINS *)” (?DwmExtendFrameIntoClientArea@AFX_GLOBAL_DATA@@QAEHPAUHWND__@@PAU_AFX_MARGINS@@@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: long __thiscall AFX_GLOBAL_DATA::DwmDefWindowProc(struct HWND__ *,unsigned int,unsigned int,long)” (?DwmDefWindowProc@AFX_GLOBAL_DATA@@QAEJPAUHWND__@@IIJ@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: int __thiscall AFX_GLOBAL_DATA::DwmIsCompositionEnabled(void)” (?DwmIsCompositionEnabled@AFX_GLOBAL_DATA@@QAEHXZ) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: int __thiscall AFX_GLOBAL_DATA::DrawTextOnGlass(void *,class CDC *,int,int,class ATL::CStringT<wchar_t,class StrTraitMFC<wchar_t,class ATL::ChTraitsCRT > >,class CRect,unsigned long,int,unsigned long)” (?DrawTextOnGlass@AFX_GLOBAL_DATA@@QAEHPAXPAVCDC@@HHV?$CStringT@_WV?$StrTraitMFC@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@VCRect@@KHK@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: struct HICON__ * __thiscall AFX_GLOBAL_DATA::GetHandCursor(void)” (?GetHandCursor@AFX_GLOBAL_DATA@@QAEPAUHICON__@@XZ) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: int __thiscall AFX_GLOBAL_DATA::Resume(void)” (?Resume@AFX_GLOBAL_DATA@@QAEHXZ) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: int __thiscall AFX_GLOBAL_DATA::GetNonClientMetrics(struct tagNONCLIENTMETRICSW &)” (?GetNonClientMetrics@AFX_GLOBAL_DATA@@QAEHAAUtagNONCLIENTMETRICSW@@@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “int __stdcall AfxIsExtendedFrameClass(class CWnd *)” (?AfxIsExtendedFrameClass@@YGHPAVCWnd@@@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “int __stdcall AfxIsMFCToolBar(class CWnd *)” (?AfxIsMFCToolBar@@YGHPAVCWnd@@@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: long __thiscall AFX_GLOBAL_DATA::ShellCreateItemFromParsingName(wchar_t const *,struct IBindCtx *,struct _GUID const &,void * *)” (?ShellCreateItemFromParsingName@AFX_GLOBAL_DATA@@QAEJPB_WPAUIBindCtx@@ABU_GUID@@PAPAX@Z) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “public: static int CMemDC::m_bUseMemoryDC” (?m_bUseMemoryDC@CMemDC@@2HA) already defined in afxglobals.obj
    1>uafxcwd.lib(afxglobals.obj) : error LNK2005: “struct AFX_GLOBAL_DATA afxGlobalData” (?afxGlobalData@@3UAFX_GLOBAL_DATA@@A) already defined in afxglobals.obj
    ———————————-

    • tedwvc says:

      I belive that in order for my “trick” to work (that is, substituting an existing compiled obj from a library with one in my own project without causing multiple definition errors) is to make sure all compile options are the same. For example, treat WChar as built-in type (under Project settings – C++ – Language) must be set to true. There may be other compilation options that cause the multiple definition error. I would suggest going through (side by side on a monitor, or look at c++ command line options tab) and compare between your non-working project and the default DHTML project you created that works. Good luck and let me know how it goes.

  5. jb says:

    Hey Ted..

    I tried your changes in a brand new MFC application (stock generated code, removing most options).. But I only saw a file size reduction from 1.6 MB to 1.5 MB (about 100 KB change).. I don’t know if there’s a project setting I got wrong when going thru the wizard, or if its something else I’m missing??

    Email me, and I’ll send you the project, if you feel so inclined.. (assuming you have access to the supplied email addresses)..

    Thanks..

    • tedwvc says:

      For the benefit of other readers, I’ll mention that we resolved this via email, and it ended up being the fact that a reference to CDialogEx still existed in the source code (the default generated one in the About box code). Removing that reference using replace in files brought the app’s release size down to 404K.

  6. nc says:

    Hi Ted,

    I tried your solutino but don’t work.
    Can you send me via e-mail your project?

    Your idea is very good, I hope VS C++ Team resolve this problem.

  7. JnZ says:

    CDialogEx is not useless. Eg. if you want to put CMFCPopupMenu on CDialog, you can expect curious problems. Especialy if you’re using multithreaded dialgs. So difference between CDialog and CDialogEx is’nt only in setting color atc. If you’re using new CMFCxxx classes, you should use CDialogEx, you can use CDialog but expect some problems in future.

  8. shilpiboosar says:

    Hi Ted,
    I Tried your solution in MFC dialog based application but the size is not reduced. also i am using CDailog instead of CDialogEx but the size remains almost same.

    Please mail me for the same.

  9. nicholsboba says:

    This worked quite nicely reducing the size of a simple dialog from over 1,700 kb to 428 kb – Still about 150 kb greater than the same program built with VS 2008

  10. lqjingfeng says:

    Your idea is very good,in my solution but don’t work,Can you send me via e-mail your project?Thanks

  11. Mathias says:

    Anyone got this to work with VS2010 SP1 ?

    • Mathias says:

      I got it to work.

      afxglobals.cpp is updated in SP1 so just redo the “trick” with that file and it work.
      My exe grow an extra 5kb in size with SP1 compared to VS2010 RTM but that is acceptable.

      • tedwvc says:

        Thanks very much for the info. It does bring an important point to the forefront which is: whenever one takes copies of MFC source code, it’s always a good idea to check if they’ve been updated.

  12. Dave says:

    Ted,

    Could you send me your VS2010 project so that I could compare it with my own attempt?

    Thanks,
    -Dave

  13. Dave says:

    Ted,

    Thats really strange… it is my valid e-mail address. Could you try without an attachment? This reply contains an alternate address. :)

    -Dave

  14. herb says:

    i tried your solution in my vs2010sp1 project, it doesn’t work, would you please send me your project?

    Regards

  15. David says:

    Ted,
    Could you also send me your VS2010 SP1 project to compare with mine?
    I cannot get rid of the LNK2005 uafxcw.lib(afxglobals.obj) in a new MFC dialog EXE project.
    Thanks.
    -David
    (not the same Dave as the 3/25 post)

  16. Kirk says:

    Hi Ted!

    Thanks for the valuable information. I was wondering if you would mind if we reposted this information on our website? Thanks in advance, and great work! Hopefully Microsoft will listen to developers on this one.

    -Kirk

    • tedwvc says:

      Hi Kirk, not a problem – just include a link to the blog entry as well in case I make any future updates.

  17. Pingback: The MFC Balloon « Outside The Box

  18. Michael says:

    Worked perfectly with Visual Studio until about 2 weeks ago. Now (probably after installation of SP1), the EXE size is still small, but starting the EXE (Release-Build, without debugger) now lasts several seconds before the application window is shown. This has been tested with a simple dialog application created by the wizard – no additional code.

    First I thought it’s some system setting (Firewall or something) that caused the delay, but I found an old EXE from a test-project I did 2 weeks ago, which shows the dialog immediately after starting. A simple rebuild-all of the same (!) project resulted in an EXE file of the same size as the old one, but with the multiple seconds delay.

    I re-did all the afxctrlcontainer.cpp, afxglobals.cpp and winmdi.cpp changes with the current MFC sources from the Visual Studio directory, but to no avail.

    Note that the application is running perfectly and the EXE size of an empty project (statically linked with the described method) is about 337 KB – and without, it’s ~1.7 MB, but showing the application window immediately. It’s just the long delay of around 4-5 seconds, and it does not change when you launch the EXE multiple times consecutively (so it’s loaded from cache). During the delay, I recognized that the mouse pointer changes the hourglass for first few seconds, then to the pointer-with-small-hourglass, before displaying the dialog.

    So to sum up:
    Before around two weeks: small EXE, fast start
    now (same project): small EXE, slow start

    Why must MS always break good workarounds without offering any alternatives?

    Anyone with the same problem, or with a solution?

    • Michael says:

      Hmmm… that seems to have been a temporary local problem. Today, it works as before, i.e. the built EXEs do no longer have the delay on startup. I’m still wondering what caused this (as when the effect happened, the older EXEs were still without delay)…

      • tedwvc says:

        it could have been a corrupted SUO file – this is a hidden file in the solution folder(sometimes they get big – in the range of hundreds of megabytes in size where they should be only 100K or so)

  19. Andrew says:

    Hi Ted,

    Could you mail me your project file as well?
    Thanks!

  20. jim says:

    Ran into the problem as well. Any chance you can email me the updated VS10 files?

    thanks,
    jim

  21. Thierry says:

    Hi Ted,

    Thanks for mailing me your project files?

    Thierry

  22. Jack says:

    Hi Ted,

    Could you please mail me your vs 2010 project files. Thanks in advance for your great working.

    Jack.

  23. Joe says:

    Can I also get the files?
    Can’t seem to get rid of the linker errors Pallavi Saxena had.

  24. Microsoft has a fix for this now, see http://blogs.msdn.com/b/vcblog/archive/2012/02/06/10263387.aspx for details

    All you need to do is to define _AFX_NO_MFC_CONTROLS_IN_DIALOGS before you include the afx*.h files, either in stdafx or in the project/build settings.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: