What Is MFC on Windows? Foundational Knowledge for Maintaining Existing Assets
· Go Komura · Windows, MFC, Visual C++, C++, Win32, Native App, Desktop App, Legacy Code, Legacy Asset Reuse
1. What to Understand First
When maintaining older Windows desktop applications, you sometimes come across names like these.
CWinApp
CWnd
CDialog
CDialogEx
CFrameWnd
CDocument
CView
CString
CFile
CArchive
BEGIN_MESSAGE_MAP
ON_COMMAND
ON_BN_CLICKED
DoDataExchange
UpdateData
These come up constantly in MFC, a Windows application framework for C++. MFC stands for Microsoft Foundation Classes, a library that makes the Win32 API easier to work with as C++ classes.
In today’s Windows app development there are many options — WinUI, WPF, Windows Forms, Electron, Qt, web technologies — so MFC is rarely the first choice for new development. Yet it is not a dead technology either. Business applications, measurement instruments, control software, CAD/CAM, internal tools, and long-lived packaged software still leave us maintaining MFC codebases today.
Let me state up front the perspectives worth holding when trying to understand MFC.
MFC is an essential map for reading older Windows desktop apps
Think of MFC not as hiding the Win32 API, but as wrapping it in a C++-friendly way
Without knowing MFC's conventions, you will misread behavior more than the code's appearance suggests
Its value lies in maintaining, extending the life of, and incrementally migrating existing assets, not in new adoption
This article walks through an overview of MFC: application structure, message maps, Document/View, dialogs, DDX/DDV, resources, builds, and the points to watch during maintenance.
The code fragments in this article are published on GitHub as a reference code collection, organized into files by chapter.
windows-mfc-overview - komurasoft-blog-samples (GitHub)
2. What Is MFC?
MFC is a class library for building native Windows desktop applications in C++.
When using the Win32 API directly, you typically write code like this.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
// Painting
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
The Win32 API is extremely powerful, but because everything is built around C functions, handles, messages, and callbacks, large applications quickly become hard to navigate. MFC lets you work with these as C++ classes: a window is a CWnd, a dialog is a CDialog, the application as a whole is a CWinApp, a frame window is a CFrameWnd, and a view is a CView.
class CMainFrame : public CFrameWnd
{
public:
CMainFrame();
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
DECLARE_MESSAGE_MAP()
};
MFC does not replace the Win32 API with something entirely different. It is a thin but wide-ranging C++ framework built on the Win32 API’s way of thinking. So to read MFC, you need knowledge of not just the MFC classes but also areas like these.
Windows messages
Handles such as HWND
GDI/GDI+
Resource files
COM/OLE
DLLs and runtimes
Character encodings
Threads and message loops
Rather than “a magic library that lets you write Windows apps without knowing Windows,” the true nature of MFC is “the mechanics of Windows organized into C++ types and a framework.”
3. Is MFC Still Usable Today?
MFC is still available in Visual Studio. But do not misunderstand its position. While it remains supported, it is not a modern UI framework receiving active new features — Microsoft’s MFC documentation notes that MFC continues to be supported but will not receive new features or documentation updates.
So MFC’s position works out roughly like this.
Maintaining existing MFC apps -> realistically common
Adding features to existing MFC apps -> plausible
Updating an MFC app's build environment -> important
Incremental migration from MFC to another UI -> plausible
Adopting it for a brand-new general GUI app -> judge carefully
In long-lived business applications in particular, the UI, printing, file I/O, device control, proprietary protocols, and COM integration are often all bundled up inside MFC.
For such codebases, before “throwing MFC away,” what you need first is “getting to a state where you can read MFC.”
4. The Areas Where MFC Excelled
The classic domain where MFC has been used is native Windows desktop applications.
Concretely, applications like these.
Dialog-centric business tools
SDI apps that open and edit files
MDI apps that handle multiple documents
Control screens for measurement instruments and manufacturing equipment
Native CAD/CAM applications
Apps that make heavy use of printing and previews
Apps involving ActiveX or OLE integration
Apps tightly coupled to old Windows APIs and COM assets
MFC’s strength is that it operates close to native Windows components. Windows, menus, toolbars, status bars, dialogs, common controls, printing, file dialogs, the registry, and GDI drawing can all be handled as C++ classes.
Its weakness, on the other hand, is that modern UI construction, data binding, testability, asynchronous processing, modern layout, high-DPI support, localization, and accessibility cannot be expressed as naturally as in recent frameworks.
Listing its characteristics, we get this.
Close to native Windows
Direct control from C++
A wealth of existing assets
Requires Win32 knowledge
Full of old conventions
You must shape a testable structure yourself
5. Preparing to Use MFC in Visual Studio
Even if you have C++ installed in Visual Studio, MFC is not necessarily included. MFC is handled as an individual component in the Visual Studio Installer. Typically, check components in this area.
Desktop development with C++
MSVC v143 - VS 2022 C++ x64/x86 build tools
Windows SDK
C++ MFC for latest v143 build tools
C++ ATL for latest v143 build tools
Whether the Spectre Mitigations variant of MFC is needed
If MFC-related files cannot be found at build time, check not only the project settings but also whether the MFC component is installed in Visual Studio itself.
The same applies to CI environments and build servers. If a project builds in local Visual Studio but fails on CI, the cause may be the presence or absence of the MFC component, or a mismatch in the target toolset version.
6. The Basic Structure of an MFC Application
An MFC application generally has the following structure.
CWinApp-derived class
Responsible for application-wide initialization and shutdown
CFrameWnd / CMDIFrameWnd / CDialog-derived classes
Responsible for the main window and dialogs
CView-derived classes
Responsible for screen display and user interaction
CDocument-derived classes
Responsible for data and file saving
Resource files
Hold menus, dialogs, icons, strings, and so on
Message maps
Bind Windows messages and commands to handler functions
For example, a simple MFC app contains a CWinApp-derived class like this.
class CMyApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
CMyApp theApp;
BOOL CMyApp::InitInstance()
{
CWinApp::InitInstance();
CMainFrame* pFrame = new CMainFrame;
m_pMainWnd = pFrame;
pFrame->Create(nullptr, _T("My MFC Application"));
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
CWinApp is the class that represents the application as a whole.
An MFC application normally has exactly one object derived from CWinApp.
A global object like theApp may feel odd at first, but in MFC this is the standard structure.
7. What Does CWinApp Do?
CWinApp matters as the entry point of an MFC application. In a plain Win32 application you write WinMain, window class registration, and the message loop yourself; in MFC, the framework takes over most of that. Developers mainly override InitInstance and write the application-specific initialization there.
BOOL CMyApp::InitInstance()
{
CWinApp::InitInstance();
// Load settings
// Initialize COM
// Create the main window
// Register document templates
return TRUE;
}
The kinds of processing that tend to live in InitInstance include these.
Initializing common controls
Setting the registry key
Loading the most-recently-used file list
Registering document templates
Creating the main frame
Processing command-line arguments
Initializing COM/OLE
During maintenance, looking at the CWinApp-derived class first makes the application’s overall startup sequence much easier to see.
8. CWnd Is the Class at the Center of MFC
Most of MFC’s UI classes derive from CWnd, which represents a Windows window. But a CWnd object and an HWND are not the same thing.
HWND
A window handle managed by the Windows OS
CWnd
A C++ wrapper object that makes the HWND easier to work with
In MFC, the CWnd holds the HWND internally.
HWND hWnd = m_hWnd;
Or you obtain it like this.
HWND hWnd = GetSafeHwnd();
What matters during maintenance is that even when a CWnd* exists, the corresponding HWND may already have been destroyed.
So you check whether a window is valid like this.
if (pWnd != nullptr && ::IsWindow(pWnd->GetSafeHwnd()))
{
pWnd->ShowWindow(SW_SHOW);
}
When investigating MFC bugs, it is important to check whether the C++ object lifetime of the CWnd and the lifetime of the actual Windows window handle have drifted apart.
9. What Is a Message Map?
One of the mechanisms where MFC’s character shows most clearly is the message map.
A Windows application receives mouse clicks, key input, repaints, window resizes, menu selections, and so on as Windows messages.
With the Win32 API, you normally handle messages in the switch statement of WndProc.
In MFC, a message map binds them to handler functions instead.
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_BN_CLICKED(IDC_BUTTON_OK, &CMyDialog::OnClickedButtonOk)
ON_WM_CLOSE()
END_MESSAGE_MAP()
void CMyDialog::OnClickedButtonOk()
{
AfxMessageBox(_T("Clicked"));
}
The meaning of this code is as follows.
When the button IDC_BUTTON_OK is clicked
call CMyDialog::OnClickedButtonOk
If you are not used to reading MFC, it can be hard to tell where a function is being called from. When a search turns up no direct call, look at the message map.
The function is not called directly anywhere
Yet it runs when an event occurs
-> Check the BEGIN_MESSAGE_MAP / ON_... macros
In MFC code reviews, it is important to look not just at the handler functions but at the message map together with them.
10. Command Routing
In MFC, menu and toolbar interactions are also treated as commands.
The most common form is ON_COMMAND.
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_COMMAND(ID_FILE_OPEN, &CMainFrame::OnFileOpen)
END_MESSAGE_MAP()
void CMainFrame::OnFileOpen()
{
// Open the file
}
MFC has a mechanism for routing commands to the appropriate object.
For example, the same ID_EDIT_COPY may be handled by the currently active view, the document, the frame, or the application, depending on the situation.
Active view
Document
Frame window
Application
The command is passed along in this order to whichever object can handle it.
As a result, in MFC it can be hard to trace “which function runs when this menu item is clicked” with a simple text search.
The points to look at during maintenance are these.
What is the command ID?
Which class has the ON_COMMAND?
Where is the ON_UPDATE_COMMAND_UI?
Which view is currently active?
Is the app using the Document/View structure?
11. What Is ON_UPDATE_COMMAND_UI?
In MFC, ON_UPDATE_COMMAND_UI is sometimes used to update the enabled/disabled state, check state, or display text of menu items and toolbar buttons.
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_COMMAND(ID_EDIT_DELETE, &CMainFrame::OnEditDelete)
ON_UPDATE_COMMAND_UI(ID_EDIT_DELETE, &CMainFrame::OnUpdateEditDelete)
END_MESSAGE_MAP()
void CMainFrame::OnUpdateEditDelete(CCmdUI* pCmdUI)
{
pCmdUI->Enable(CanDeleteCurrentItem());
}
This lets you enable the menu item or button only when deletion is actually possible.
When investigating behavior in an MFC app — a button that is mysteriously grayed out, a menu that cannot be clicked, a check state that changes — searching for ON_UPDATE_COMMAND_UI often reveals the cause.
12. Dialog-Based MFC Applications
One of the most approachable forms of MFC is the dialog-based application.
Settings screens, simple business tools, and equipment operation panels are often built around dialogs.
Typically, you derive from CDialog or CDialogEx.
class CSettingsDialog : public CDialogEx
{
public:
CSettingsDialog(CWnd* pParent = nullptr);
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_SETTINGS_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
afx_msg void OnBnClickedOk();
DECLARE_MESSAGE_MAP()
private:
CString m_name;
int m_interval;
};
In dialog-based code, the following elements come up frequently.
IDD_... dialog resource ID
IDC_... control ID
OnInitDialog initialization
DoDataExchange associates controls with member variables
UpdateData synchronizes the screen and the variables
ON_BN_CLICKED button click handling
A dialog is not just its appearance — it works through the combination of resources, member variables, the message map, and initialization code.
13. DDX and DDV
Two things that come up constantly in MFC dialogs are DDX and DDV.
DDX = Dialog Data Exchange
DDV = Dialog Data Validation
DDX is the mechanism that associates controls on a dialog with C++ member variables; DDV is the mechanism that validates the input values.
void CSettingsDialog::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_NAME, m_name);
DDX_Text(pDX, IDC_EDIT_INTERVAL, m_interval);
DDV_MinMaxInt(pDX, m_interval, 1, 3600);
}
Calling UpdateData(TRUE) copies the values entered on screen into the member variables.
void CSettingsDialog::OnBnClickedOk()
{
if (!UpdateData(TRUE))
{
return;
}
// At this point m_name and m_interval hold the on-screen input values
SaveSettings(m_name, m_interval);
CDialogEx::OnOK();
}
Conversely, calling UpdateData(FALSE) copies the member variable values to the screen.
BOOL CSettingsDialog::OnInitDialog()
{
CDialogEx::OnInitDialog();
m_name = _T("default");
m_interval = 60;
UpdateData(FALSE);
return TRUE;
}
When input values misbehave in an MFC dialog, check these points.
Is the DDX defined in DoDataExchange?
Is UpdateData(TRUE) being called?
Is UpdateData(FALSE) being called at the right time?
Is DDV rejecting the input?
Do the control IDs match the resources?
14. The Document/View Architecture
One of MFC’s defining features is the Document/View architecture.
This is a structure for separating the data an application handles from how it is displayed.
CDocument
Holds the data
Responsible for reading and writing files
Notifies multiple views of updates
CView
Displays the data
Handles user interaction
Manages drawing and selection state
For example, in apps like text editors, shape editors, configuration file editors, or CAD, separating data from presentation pays off.
class CMyDocument : public CDocument
{
public:
std::vector<Item> m_items;
virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
virtual BOOL OnSaveDocument(LPCTSTR lpszPathName);
};
class CMyView : public CView
{
protected:
virtual void OnDraw(CDC* pDC);
CMyDocument* GetDocument() const;
};
On the view side, you fetch the document and draw it.
void CMyView::OnDraw(CDC* pDC)
{
CMyDocument* pDoc = GetDocument();
if (pDoc == nullptr)
{
return;
}
for (const auto& item : pDoc->m_items)
{
// Draw using pDC
}
}
The benefit of Document/View is that it is easy to present the same data in multiple views.
For example, the same data can be shown like this.
A table view
A chart view
A detail view
A preview
A print view
That said, applying Document/View to a simple settings screen or a small tool can make the structure feel heavier than necessary.
During maintenance, first determining whether the app uses Document/View or is dialog-centric makes the code much easier to follow.
15. SDI and MDI
In MFC, two structures often appear in combination with Document/View: SDI and MDI.
SDI = Single Document Interface
MDI = Multiple Document Interface
SDI is essentially the form where one frame handles one document.
Main window
One document
One or more views
MDI has multiple child windows inside one parent window, each handling its own document.
MDI parent frame
MDI child frame 1 -> document 1
MDI child frame 2 -> document 2
MDI child frame 3 -> document 3
Old Windows applications used MDI heavily.
During maintenance, the class names give you a good idea of the structure.
CFrameWnd SDI-style frame
CMDIFrameWnd MDI parent frame
CMDIChildWnd MDI child frame
CSingleDocTemplate document template for SDI
CMultiDocTemplate document template for MDI
In apps created by the MFC wizard, the registration of CSingleDocTemplate or CMultiDocTemplate usually appears inside InitInstance.
16. Understanding Resource Files
In MFC apps, the .rc file is extremely important.
.rc is a Windows resource file.
It defines things like these.
Dialog templates
Menus
Accelerator keys
Icons
Bitmaps
String tables
Version information
Toolbars
In addition, resource.h defines the resource IDs.
#define IDD_SETTINGS_DIALOG 101
#define IDC_EDIT_NAME 1001
#define IDC_EDIT_INTERVAL 1002
#define ID_FILE_OPEN 32771
MFC code uses these IDs to bind the resources to the C++ code.
DDX_Text(pDX, IDC_EDIT_NAME, m_name);
ON_COMMAND(ID_FILE_OPEN, &CMainFrame::OnFileOpen)
A common problem during maintenance is resource ID inconsistency.
An ID in resource.h changed
A branch merge caused an ID collision
The control ID on the dialog and the DDX ID do not match
A menu ID that should have been deleted is still there
String table IDs are duplicated
When investigating an MFC app’s behavior, you need to look at the .rc file and resource.h alongside the C++ code.
17. The Class Wizard and Hand-Written Code
MFC has a history deeply intertwined with Visual Studio’s Class Wizard.
The Class Wizard can auto-generate message handlers, DDX variables, virtual function overrides, and more.
As a result, MFC code retains a lot of tool-generated patterns.
//{{AFX_DATA(CSettingsDialog)
//}}AFX_DATA
//{{AFX_MSG(CSettingsDialog)
//}}AFX_MSG
In newer versions of Visual Studio the appearance and the generated shape may differ, but old codebases often still contain comment markers like these.
What matters during maintenance is not to carelessly destroy the boundary between generated and hand-written code.
Do not delete the message map
Do not break the DDX associations
Do not change resource IDs carelessly
Do not casually delete old Class Wizard comment markers
In MFC, merely compiling as valid C++ is not enough. You also need to preserve, to a degree, the shape that the Visual Studio resource editor and Class Wizard expect.
18. CString and Strings
The string class that appears constantly in MFC is CString.
CString name = _T("Komura");
CString message;
message.Format(_T("Hello, %s"), name.GetString());
CString is a variable-length string class widely used in MFC/ATL-style code. Modern C++ favors std::string and std::wstring in most places, but in MFC, CString is used heavily because of its affinity with the APIs and controls.
During maintenance, you have to keep character encodings in mind.
CString effectively CStringA or CStringW depending on project settings
CStringA ANSI / MBCS family
CStringW Unicode / UTF-16 family
LPCTSTR TCHAR-based string pointer
LPCSTR char family
LPCWSTR wchar_t family
std::string normally char family
std::wstring wchar_t family
Modern Windows apps are basically safer when built on the Unicode assumption, but old MFC apps often retain MBCS-based code.
CString text = _T("日本語");
std::wstring ws(text.GetString());
String conversion bugs come up frequently in MFC maintenance.
Be especially careful in cases like these.
Reading files that assume Shift_JIS
Switching to a Unicode build
An external DLL that requires char*
COM that requires BSTR
Naive conversion to std::string producing garbled text
When you see a CString, do not just think “that’s an old string class.” Check it together with the project’s character set setting, external APIs, and file formats.
19. CFile and CArchive
MFC also includes classes for file operations and serialization. The representative ones are CFile and CArchive.
CFile file;
if (file.Open(path, CFile::modeRead))
{
CArchive ar(&file, CArchive::load);
// Read from ar
}
CArchive is commonly used in MFC’s serialization machinery.
In CDocument-derived classes, Serialize is often overridden so that loading and saving live in the same function.
void CMyDocument::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
ar << m_title;
ar << static_cast<int>(m_items.size());
for (const auto& item : m_items)
{
ar << item.Name;
ar << item.Value;
}
}
else
{
int count = 0;
ar >> m_title;
ar >> count;
m_items.clear();
for (int i = 0; i < count; ++i)
{
Item item;
ar >> item.Name;
ar >> item.Value;
m_items.push_back(item);
}
}
}
MFC serialization is convenient, but it requires care over long-term operation.
Compatibility with old file formats
Managing version numbers
Recovery when loading fails
Exception handling
Character encodings
Endianness
Whether structs are being saved verbatim
In MFC apps that have used a proprietary binary format for a long time, Serialize can effectively be the file format specification.
In that case, before changing the code, you should always prepare test data that exercises loading the existing files.
20. GDI Drawing and CDC
For screen drawing in MFC, the CDC class, which wraps the Windows Device Context, is used heavily. CView::OnDraw receives a CDC* as its argument.
void CMyView::OnDraw(CDC* pDC)
{
pDC->TextOut(10, 10, _T("Hello MFC"));
pDC->Rectangle(10, 40, 200, 120);
}
When using pens and brushes, pay attention to selection and restoration.
void CMyView::OnDraw(CDC* pDC)
{
CPen pen(PS_SOLID, 1, RGB(0, 0, 0));
CPen* pOldPen = pDC->SelectObject(&pen);
pDC->MoveTo(10, 10);
pDC->LineTo(100, 100);
pDC->SelectObject(pOldPen);
}
Around GDI objects, mistakes like these become problems.
Not restoring the original object after SelectObject
Creating large numbers of GDI objects without destroying them
Confusing the roles of OnPaint and OnDraw
Flickering due to a lack of double buffering
Fixed-pixel drawing falling apart at high DPI
For MFC drawing bugs, you need to check not just the C++ logic but also Windows GDI resources, repaint timing, DPI, and font sizes.
21. Modal and Modeless Dialogs
In MFC, how a dialog is shown also requires attention.
A modal dialog is displayed with DoModal.
CSettingsDialog dlg(this);
if (dlg.DoModal() == IDOK)
{
// Handle OK
}
In this case, the caller waits until the dialog closes.
A modeless dialog, on the other hand, returns control to the caller after creation.
m_pToolDialog = new CToolDialog(this);
m_pToolDialog->Create(IDD_TOOL_DIALOG, this);
m_pToolDialog->ShowWindow(SW_SHOW);
For modeless dialogs, lifetime management is critical.
When do you delete the dialog you newed?
Could the parent window be destroyed first?
Does the dialog use PostNcDestroy?
Could it be created twice?
Is a pointer left dangling after the dialog closes?
In MFC crash investigations, modeless dialog lifetime problems are a recurring cause.
22. The Lifetimes of C++ Objects and Windows Handles
Something extremely important in MFC is the difference between the lifetimes of C++ objects and Windows handles. CWnd, for example, is a C++ object, but the actual window is managed by Windows as an HWND, and the two are not always created and destroyed together.
The CWnd object exists but the HWND does not yet
The HWND was destroyed but the CWnd object remains
A temporary CWnd wrapper has been created
The handle was swapped via Attach/Detach
For example, code like this deserves caution.
CWnd* pWnd = GetDlgItem(IDC_SOME_CONTROL);
// Storing pWnd in a member for later use
If you hold on to a pointer obtained from GetDlgItem for a long time, you risk referencing it after the window has been destroyed.
If needed, it can be safer to call GetDlgItem each time, or to manage a member variable for the control through DDX.
DDX_Control(pDX, IDC_LIST_ITEMS, m_listItems);
In MFC, a non-null pointer is not necessarily a safe pointer.
if (m_pDialog != nullptr && ::IsWindow(m_pDialog->GetSafeHwnd()))
{
m_pDialog->SetWindowText(_T("Running"));
}
This instinct is essential in MFC maintenance.
23. Threads and UI Updates
Windows UI must fundamentally be manipulated on the UI thread that created it, and the same applies to MFC apps. Manipulating UI controls directly from a worker thread causes instability and crashes.
An example to avoid.
UINT WorkerThreadProc(LPVOID pParam)
{
CMyDialog* pDlg = static_cast<CMyDialog*>(pParam);
// Avoid touching the UI directly from a worker thread
pDlg->SetDlgItemText(IDC_STATUS, _T("Done"));
return 0;
}
Generally, you notify the UI thread via something like PostMessage.
constexpr UINT WM_APP_WORK_DONE = WM_APP + 1;
UINT WorkerThreadProc(LPVOID pParam)
{
HWND hWnd = static_cast<HWND>(pParam);
// Heavy processing
::PostMessage(hWnd, WM_APP_WORK_DONE, 0, 0);
return 0;
}
The UI side receives it through the message map.
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_MESSAGE(WM_APP_WORK_DONE, &CMyDialog::OnWorkDone)
END_MESSAGE_MAP()
LRESULT CMyDialog::OnWorkDone(WPARAM, LPARAM)
{
SetDlgItemText(IDC_STATUS, _T("Done"));
return 0;
}
Around MFC threading, check the following.
Is the UI being touched directly from a worker thread?
Is PostMessage being called after the window was destroyed?
Is the UI thread blocked waiting for a thread to finish?
Is shared data locked appropriately?
Is the return value and lifetime of AfxBeginThread misunderstood?
24. MFC DLLs and Module State
When building DLLs with MFC, the concept of module state comes up.
When loading resources from an MFC DLL, showing dialogs, or building extension DLLs, the question becomes which module’s resources are consulted.
At the entry points of MFC DLL functions, you sometimes see this macro.
AFX_MANAGE_STATE(AfxGetStaticModuleState());
This exists so that MFC can use the correct module state.
Forgetting this macro leads to problems like these.
Dialog resources inside the DLL cannot be found
String resources get read from a different module
Icons and menus cannot be found
Works in debug but breaks in release
When maintaining MFC DLLs, it is important to map out the relationships among the EXE, regular DLLs, extension DLLs, and resource DLLs.
Take particular care when a non-MFC app calls an MFC DLL, or when the system has a plugin architecture.
25. Linking MFC Statically or Using the Shared DLL
MFC projects have a “Use of MFC” project setting.
The main options are these two.
Use MFC in a Shared DLL
Use MFC in a Static Library
When using the shared DLL, the target environment needs the corresponding MFC runtime and Visual C++ runtime.
With static linking, distribution may look simpler, but you have to consider executable size, updates, picking up security fixes, and licensing and redistribution terms.
Neither is always the right answer.
The decision factors are roughly these.
Can you install the Visual C++ Redistributable on target machines?
Do you want the app to approach a single exe?
How will you roll out security updates?
Will multiple apps share the same runtime?
Can you provide an installer?
Which Windows versions are you targeting?
In maintenance, the first thing is to check the current setting.
Configuration Properties
General
Use of MFC
Also look at the Runtime Library setting.
/MD Multi-threaded DLL
/MDd Multi-threaded Debug DLL
/MT Multi-threaded
/MTd Multi-threaded Debug
If MFC and CRT link settings are mixed inconsistently, memory allocation/deallocation problems can appear at library boundaries.
26. Unicode, MBCS, and TCHAR
In old MFC code, TCHAR, LPCTSTR, and the _T() macro appear constantly.
CString title = _T("設定");
SetWindowText(title);
This style exists to support both Unicode builds and MBCS builds.
Unicode build
TCHAR -> wchar_t
LPCTSTR -> const wchar_t*
_T("...") -> L"..."
MBCS build
TCHAR -> char
LPCTSTR -> const char*
_T("...") -> "..."
Unicode builds are the norm today, but old apps often retain MBCS-based processing.
In particular, for external files, communication protocols, old DLLs, database connections, and serial communication, you need to verify the character encoding assumptions.
The thing to be careful about is not treating Unicode conversion as a simple find-and-replace job.
Is the char array size a byte count or a character count?
Is strlen being used?
Is sizeof(buffer) being used as a character count?
Does the external API accept UTF-16 or Shift_JIS?
Is it acceptable for the file save format to change?
When fixing MFC string handling, check not just the screen display but file compatibility and external integrations as well.
27. MFC and COM/OLE/ActiveX
MFC has also been used in applications deeply involved with COM, OLE, and ActiveX.
Old business apps may retain elements like these.
OLE Automation
ActiveX controls
COM servers
COM clients
IDispatch
BSTR
VARIANT
COleDispatchDriver
COleVariant
Code like this sometimes appears in an MFC app’s startup path.
if (!AfxOleInit())
{
AfxMessageBox(_T("OLE initialization failed"));
return FALSE;
}
When COM/OLE is in use, what looks like an MFC problem may actually be caused by COM initialization, the threading model, reference counting, registration data, or 32-bit/64-bit differences.
Pay particular attention to 32-bit ActiveX and COM components.
A 32-bit MFC app uses 32-bit COM
A 64-bit MFC app uses 64-bit COM
32-bit and 64-bit COM registrations are separate
Old ActiveX components may not support 64-bit
When porting an MFC app to x64, always check not just the UI code but the COM/OLE dependencies as well.
28. High-DPI Support and Modern Windows
Running an old MFC app on modern Windows, the display can fall apart in high-DPI environments.
For example, problems like these.
Text gets clipped
Buttons are too small
Fixed-pixel drawing is misaligned
Layouts break when monitors have different scaling factors
Old bitmaps look blurry
Dialog layouts get cramped
In Windows desktop apps, the application needs to declare its DPI-awareness mode explicitly.
In MFC apps too, you have to check the manifest, the resources, the drawing code, the fonts, and the layout.
In particular, code like this with hard-coded fixed coordinates is prone to problems at high DPI.
pDC->TextOut(10, 10, _T("Status"));
pDC->Rectangle(10, 40, 200, 80);
Coordinates that assume fixed pixels fall apart visually when the DPI changes.
The maintenance checkpoints are these.
The DPI settings in the application manifest
The font in the dialog resources
Fixed-pixel drawing
The resolution of image resources
Behavior with multiple monitors
Display on Windows 10 / Windows 11
High-DPI support in MFC is not necessarily finished by just changing a project setting. With old UIs, actual on-screen verification and layout fixes are required.
29. Exception Handling and Error Handling
MFC has its own exception classes and error-handling conventions.
In old code you encounter macros like these.
TRY
{
// Processing
}
CATCH(CFileException, e)
{
e->ReportError();
}
END_CATCH
There is also code where these are mixed with modern C++ try / catch.
try
{
DoSomething();
}
catch (const std::exception& ex)
{
// Write to log
}
In MFC maintenance, watch for the following.
Are MFC exceptions and standard C++ exceptions mixed?
Is the lifetime of exception objects misunderstood?
Do you understand the old THROW/CATCH macros?
Are return-value errors and exceptions mixed?
Are errors ending at AfxMessageBox with no log left behind?
In business applications, it is important not only to show an error message on screen but also to record logs, operation history, input values, and external connection state.
In old MFC apps, errors sometimes end at an AfxMessageBox and nothing more.
AfxMessageBox(_T("Failed to save."));
To improve maintainability, separate UI display from log recording.
LogError(_T("Save failed"), path);
AfxMessageBox(_T("Failed to save. Please check the log."));
30. Making MFC Coexist with Modern C++
Maintaining an MFC app does not mean everything has to conform to old C++ style.
You can respect MFC’s conventions in the UI layer while organizing domain logic and computation in modern C++.
For example, divide it like this.
MFC layer
CDialog
CView
CDocument
CString
Message maps
Resource handling
Non-MFC layer
std::string / std::wstring
std::vector
std::optional
std::variant
std::filesystem
Unit-testable classes
Business logic
The bad shape is the one where all the processing is crammed into the dialog class.
void CMainDialog::OnBnClickedExecute()
{
// Get input
// Read files
// Communicate
// Compute
// Update the DB
// Update the screen
// Write logs
// Handle exceptions
}
Code like this is hard to change, hard to test, and hard to debug.
To improve it, move the logic out of the MFC class.
void CMainDialog::OnBnClickedExecute()
{
if (!UpdateData(TRUE))
{
return;
}
ExecuteRequest request;
request.Name = ToStdWString(m_name);
request.Interval = m_interval;
ExecuteResult result = m_service.Execute(request);
m_status = ToCString(result.Message);
UpdateData(FALSE);
}
With this shape, m_service.Execute can be tested without MFC.
The single most effective improvement in maintaining existing MFC assets is gradually extracting logic out of the UI classes.
31. Making MFC Code Testable
MFC apps are often hard to unit test as-is.
The reason is that the UI, Win32, files, communication, databases, and global state tend to be tightly coupled.
The way to think about making them testable is this.
Do not try to test CDialog or CView directly
First extract the non-UI logic
Convert MFC types at the boundary
Put files and communication behind interfaces
Keep UI event handlers thin
For example, move the processing into a pure C++ class.
class PriceCalculator
{
public:
int CalculateTotal(const std::vector<int>& prices) const
{
int total = 0;
for (int price : prices)
{
total += price;
}
return total;
}
};
The MFC side handles only input and output.
void CPriceDialog::OnBnClickedCalculate()
{
if (!UpdateData(TRUE))
{
return;
}
std::vector<int> prices = ParsePrices(ToStdWString(m_input));
int total = m_calculator.CalculateTotal(prices);
m_result.Format(_T("%d"), total);
UpdateData(FALSE);
}
With this structure, PriceCalculator and ParsePrices can be tested with an ordinary C++ test framework.
There is no need to rebuild the whole MFC app at once. Just moving testable processing out of the event handlers already pays off.
32. Pinning Down the Build Environment
In MFC app maintenance, pinning down the build environment is critical.
In old codebases, the build result can change based on differences like these.
Visual Studio version
MSVC toolset version
Windows SDK version
Presence of the MFC/ATL components
x86 / x64 / ARM64
Debug / Release
Unicode / MBCS
MFC static link / shared DLL
Runtime library settings
Precompiled headers
In MFC apps, many dependencies often gather in stdafx.h or pch.h.
#include "framework.h"
#include "MyApp.h"
Builds can break because one file has different compile settings, or the precompiled header settings have drifted.
In a maintenance project, writing this information down in the README pays dividends later.
Required Visual Studio version
Required workloads and individual components
Required Windows SDK
Target platforms
MFC link method
Build steps
How to run CI
How to produce the distribution
Graduating from “it builds on my PC” is the first step in MFC maintenance.
33. Building MFC on CI
MFC apps can be built on CI too.
However, the CI environment must have the MFC components installed.
When using the Visual Studio Build Tools, you need to install them with the MFC/ATL component IDs specified.
Here are the points to verify.
Is MFC included in the Build Tools?
Does the target toolset match the project?
Is the Windows SDK installed?
Are both x86 and x64 builds verified?
Does the resource compiler run?
Is there a signing step?
Is installer generation also covered by CI?
Automating UI tests for an MFC app is not easy, but automating at least the following is worthwhile.
Debug / Release builds
x86 / x64 builds
Static analysis
Unit tests
Installer creation
Saving artifact hashes
Verifying dependent DLLs
In long-term maintenance, just keeping the project buildable already has substantial value.
34. Where to Look When Debugging an MFC App
When chasing a defect in an MFC app, this order of investigation is efficient.
1. Which screen is it?
2. Is it a dialog, a View, or a Frame?
3. What resource ID corresponds to the action?
4. Which function does the message map route to?
5. Is the direction of UpdateData correct?
6. If Document/View, what is the Document's state?
7. Is command routing sending it to another class?
8. Is a worker thread touching the UI?
9. Are exceptions or errors being swallowed by a lone AfxMessageBox?
10. Are there release-build-specific uninitialized or lifetime problems?
For example, for a “nothing happens when I click the button” defect, check here.
Is the button's IDC correct?
Does an ON_BN_CLICKED exist?
Is the handler's signature correct?
Is the dialog resource actually a different one?
Is the button disabled?
Is UpdateData failing partway through?
Is an exception being swallowed?
For “the menu item cannot be clicked,” check here.
Is it disabled by ON_UPDATE_COMMAND_UI?
Are command IDs duplicated?
Is the active view what you expect?
Where is the handler: Frame, View, Document, or App?
Because MFC connects surface-level events to the actual processing through macros and routing, drawing the call paths as a diagram helps a great deal until you get used to it.
35. Common Pitfalls
Let’s organize the common pitfalls in MFC maintenance.
Searching only for function calls without looking at the message map
Assuming a non-null CWnd* is valid
Confusing HWND lifetime with C++ object lifetime
Getting the direction of UpdateData(TRUE/FALSE) wrong
Not noticing something is disabled via ON_UPDATE_COMMAND_UI
Not noticing ID collisions in resource.h
Touching the UI directly from a worker thread
Garbled text from CString/std::string conversion
Breaking MBCS-based code during Unicode conversion
Forgetting AFX_MANAGE_STATE in an MFC DLL
Breaking x86-only COM/ActiveX during an x64 port
Mishandling GDI object selection/release
Fixed-coordinate layouts breaking at high DPI
MFC defects often cannot be understood by looking at C++ syntax alone; you need to look at Windows messages, resources, handles, modules, and runtime settings as well.
36. Should You Choose MFC for New Development?
Whether to choose MFC for a completely new project deserves careful judgment.
If there are reasons to choose MFC, they look like this.
Tight integration with existing MFC code is required
You want to reuse existing MFC components and screens
You need control very close to Win32/GDI/COM
Your organization has ample MFC maintenance skill
The target is strictly Windows desktop
The long-term migration plan requires building onto MFC first
Conversely, consider other options in cases like these.
You want a modern UI
You need flexible layout and animation
Web or cloud integration is the focus
You value testability
You want technology that younger developers can join easily
You need cross-platform support
You want accessibility and high DPI as first-class concerns
MFC is neither a technology “not worth learning anymore” nor one “to choose because it’s new” — it is a technology for engaging with existing Windows native assets.
37. How to Think About Migrating Away from MFC
If you want to migrate an MFC app to another technology, aiming for a complete rewrite from the start is a recipe for failure.
First, decompose the app like this.
UI
Business logic
File formats
Communication
Database access
Device control
Printing
COM/OLE integration
Configuration management
Logging
Of these, the part most dependent on MFC is the UI.
Business logic and file handling, on the other hand, can potentially be extracted.
The realistic migration order looks like this.
1. Reproduce the build environment
2. Pin down existing behavior with test data
3. Extract logic out of UI event handlers
4. Move it into non-MFC C++ libraries
5. Add automated tests
6. Document the external specifications
7. Replace screens incrementally, starting with the ones that need it
Rather than making “getting rid of MFC” the goal itself, making “freeing the important logic trapped inside MFC” the goal is far more likely to succeed.
38. Entry Points for Reading MFC Code
When reading an existing MFC project for the first time, start with files in this area.
*.vcxproj
Check the toolset, MFC settings, character set, runtime settings
resource.h
Check the resource IDs
*.rc
Check dialogs, menus, strings, icons
*App.cpp / *App.h
Check the CWinApp-derived class and InitInstance
MainFrm.cpp / MainFrm.h
Check the main frame and the menus/toolbars
*Doc.cpp / *Doc.h
For Document/View, check the data structures and save logic
*View.cpp / *View.h
Check drawing and user interaction
*Dlg.cpp / *Dlg.h
Check dialogs, DDX, and button handling
Next, the commonly used search keywords.
BEGIN_MESSAGE_MAP
ON_COMMAND
ON_UPDATE_COMMAND_UI
ON_BN_CLICKED
DoDataExchange
UpdateData
OnInitDialog
OnDraw
Serialize
AfxMessageBox
AfxBeginThread
AFX_MANAGE_STATE
Searching for these makes the application’s behavior much easier to see.
39. Design Policies for Maintaining MFC
If you will be maintaining existing MFC assets for the long haul, policies like these are effective.
Keep UI event handlers thin
Do not let CString or CWnd leak too far into non-UI layers
Move business logic into ordinary C++ classes
Build compatibility tests for the file formats
Make the x86/x64 differences explicit
Review changes to resource IDs
Check module state in MFC DLLs
Put logging in order
Pin the build down with CI
Regularly verify the screens at high DPI and on Windows 11
Above all, it is important not to cram too much processing into CDialog and CView.
Keep MFC’s screen classes focused on input, display, and event dispatch.
Take values from the screen
Pass them to a service
Reflect the result back on the screen
If you can keep them at this level, even MFC becomes considerably easier to maintain.
40. A Practical Checklist
Here is a checklist for working with MFC apps.
Is the Visual Studio version clearly defined?
Are the MFC/ATL components installed?
Are the x86/x64 targets clearly defined?
Do you know the Unicode/MBCS settings?
Is MFC statically linked or using the shared DLL?
Which Visual C++ Redistributable is required?
Are resource.h and the .rc file part of code review?
Are you tracing event paths through the message maps?
Is the direction of UpdateData correct?
Is the Document/View structure in use?
Is modeless dialog lifetime management safe?
Is the UI being touched directly from worker threads?
Are there places in MFC DLLs that need AFX_MANAGE_STATE?
Are screens verified in high-DPI environments?
Do the COM/ActiveX dependencies support 32-bit/64-bit?
Are logs being recorded?
Can the non-UI logic be tested?
MFC looks idiosyncratic until you get used to it, but once you know where to look, it reads quite predictably.
41. Summary
MFC is a long-established framework for building native Windows desktop applications in C++. It is no longer the mainstream choice for new development, but for maintaining existing assets, updating build environments, adding features, and incremental migration, it remains an important technology today.
The most important points for understanding MFC are these.
MFC makes the Win32 API easier to work with from C++
CWinApp manages the application as a whole
CWnd and HWND do not share the same lifetime
Message maps bind events to functions
Document/View is the mechanism that separates data from display
DDX/DDV handle dialog value synchronization and validation
Resource files and resource.h are extremely important
Understand CString and TCHAR together with the character set settings
Watch out for module state in MFC DLLs
Old MFC apps are worth revisiting from the angles of high DPI, x64, CI, and testing
When working with MFC, rather than dismissing it as “old, therefore bad,” what matters first is understanding its structure. Existing MFC apps often contain years of business knowledge, customer-specific specifications, device integration, and file compatibility. To protect that value while gradually making things easier to maintain, the realistic path is to understand MFC’s conventions, extract the non-UI logic, and get builds and tests in order.
If we had to sum it up in one line, it would be this.
A foundation for working with the mechanics of native Windows apps through C++ classes and a framework.
With this perspective, MFC is not merely an old technology — it becomes a key to safely deciphering existing Windows assets.
References
- Reference code collection for this article, organized by chapter - komurasoft-blog-samples (GitHub)
- MFC Desktop Applications - Microsoft Learn
- MFC and ATL - Microsoft Learn
- Creating an MFC Application - Microsoft Learn
- CWinApp Class - Microsoft Learn
- CWinApp: The Application Class - Microsoft Learn
- Mapping Messages - Microsoft Learn
- Document-View Architecture - Microsoft Learn
- Dialog Data Exchange and Validation - Microsoft Learn
- MFC Library Versions - Microsoft Learn
- Redistribute the MFC Library - Microsoft Learn
- Visual Studio Build Tools workload and component IDs - Microsoft Learn
- High DPI Desktop Application Development on Windows - Microsoft Learn
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Handling Windows Impersonation Tokens Correctly — Borrowing Privileges per Thread and Reverting Safely
A practical guide to Windows impersonation tokens — access tokens, primary tokens, thread tokens, impersonation levels, RevertToSelf, and...
What to Do Before Disposing of a Windows PC — A Practical Checklist for Data Erasure, Account Unlinking, and Backups
What to do before disposing of, transferring, selling, or returning a leased Windows PC — covering backups, data erasure, BitLocker, Micr...
Windows App Outsourcing and Contract Development: What to Sort Out Before You Ask
Before commissioning Windows app outsourcing or contract development, here is how to sort out existing software modification, device inte...
How to Run PowerShell from C# (CSharp) and Receive the Results as Objects
How to launch PowerShell from C# and receive results as PSObject rather than strings — a practical walkthrough of the PowerShell SDK, Add...
Testing PowerShell with Pester — A Practical Approach to Making Operations Scripts Harder to Break
A practical walkthrough of testing PowerShell scripts with Pester v5 — safely covering date handling, file operations, deletion logic, mo...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Author Profile
Profile page for the article author.
Go Komura
Representative of KomuraSoft LLC
Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.
Public links