Basic Framework of MFC Programs for Window s Applications (C/C++)

Basic Framework of MFC Programs

3.1 Class Framework for MFC Projects

To build a Single document MFC application, the wizard automatically generates code for us, with five important classes to note:

  1. CAboutDlg (derived from CDialog)
  2. CMainFrame (derived from CFrameWnd)
  3. CTestAPP (derived from CWinApp): Application class
  4. CTestDoc (derived from CDocument)
  5. CTestView (derived from CView)

Test in the last three names of these five classes is the project name and changes as the project name changes.

The inheritance relationship of the base classes involved here can be seen from the following figure:

Classes that ultimately inherit from the window class (CWnd) are all related to window implementation, CAboutDlg is responsible for the help dialog, CMainFrame for the main frame window, and CTestView for the view window (above the main frame window).

3.2 Application Instances

Only one class in an MFC project is derived from the Application Class (CWinApp), whose name is composed of C+Project Name+App.

We know that in Win32 applications, the application itself is uniquely identified by an instance handle (hInstance), but in MFC it is different by identifying the application itself by a ** global object of the application class (theApp)**.

The first thing an MFC program does is create an instance of the CTestApp class (theApp):

//Test.CPP
//Create Application Instance Object
CTestApp theApp;

//CTestApp constructor
CTestApp::CTestApp()
{
}

The CTestApp constructor is called when the App is created. According to the C++ inheritance concept, the base class CWinApp constructor is called before the CTestApp constructor. The base class constructor code is as follows:

//APPCORE.CPP
CWinApp::CWinApp(LPCTSTR lpszAppName)
{
	if (lpszAppName != NULL)
		m_pszAppName = _tcsdup(lpszAppName);
	else
		m_pszAppName = NULL;

	// initialize CWinThread state
	AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
	AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
	ASSERT(AfxGetThread() == NULL);
	pThreadState->m_pCurrentWinThread = this;//this points to the App object
	ASSERT(AfxGetThread() == this);
	m_hThread = ::GetCurrentThread();
	m_nThreadID = ::GetCurrentThreadId();

	ASSERT(afxCurrentWinApp == NULL);
	pModuleState->m_pCurrentWinApp = this;//this points to the App object
	ASSERT(AfxGetApp() == this);
    ......

The base class CWinApp constructor has an lpszAppName parameter and should therefore be called explicitly in a derived class.However, since default parameters are declared in the constructor, the call is not actually displayed.The constructor of the application class completes various initialization tasks for the application.

3.3 WinMain function

When the App global object is successfully created, it will enter the WinMain section, which is coded as follows:

//APPMODUL.CPP
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	LPTSTR lpCmdLine, int nCmdShow)//Entry function
{
	return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}

This part of the code is not very different from WinMain in Win32, _tWinMain's first article described a macro compatible with both UNICODE and ASCII.AfxWinMain function is called in WinMain function. Functions in MFC that start with Afx are application framework functions. They are global functions and can be called in any class. Here AfxWinMain function is responsible for creating, registering window classes, creating, displaying, updating windows, etc.

AfxWinMain calls the AfxGetThread function and the AfxGetApp function, respectively, to get two pointers, pThread and pApp, and then through these two pointers to the App, calls three member functions to complete all the necessary steps for a Windows application, with some code as follows:

//WINMAIN.CPP
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	LPTSTR lpCmdLine, int nCmdShow)
{
	ASSERT(hPrevInstance == NULL);

	int nReturnCode = -1;
	CWinThread* pThread = AfxGetThread();//A pointer to the App
	CWinApp* pApp = AfxGetApp();//A pointer to the App

	if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
		goto InitFailure;

	if (pApp != NULL && !pApp->InitApplication())//Important function 1, completing internal management of MFC
		goto InitFailure;

	if (!pThread->InitInstance())//Important function 2, which is a virtual function, so the subclass function is called here
	{
		if (pThread->m_pMainWnd != NULL)
		{
			TRACE0("Warning: Destroying non-NULL m_pMainWnd\n");
			pThread->m_pMainWnd->DestroyWindow();
		}
		nReturnCode = pThread->ExitInstance();
		goto InitFailure;
	}
	nReturnCode = pThread->Run();//Important function 3, complete message loop

The virtual function InitInstance is called through pThread, so the subclass version is actually called, in which the creation and registration of window classes, the creation, display and update of windows are completed.Tracking the code for the function reveals the following two lines:

m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();

Obviously, these two lines display and update the windows, which will be mentioned again later in this article.

3.4 Design and Registration Window Classes

The registration window class is done in a function called CMainFrame::PreCreateWindow, which is called before the window is generated, and the code is probably as follows:

//MAINFRM.CPP
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
	if( !CFrameWnd::PreCreateWindow(cs) )//Further calls to its base class's PreCreateWindow function
		return FALSE;
	return TRUE;
}

Then enter the called function CFrameWnd::PreCreateWindow with the following code:

//MAINFRM.CPP
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
	if (cs.lpszClass == NULL)
	{
		VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));//This verifies whether the window class is registered or not
		cs.lpszClass = _afxWndFrameOrView;
	}

	if ((cs.style & FWS_ADDTOTITLE) && afxData.bWin4)
		cs.style |= FWS_PREFIXTITLE;

	if (afxData.bWin4)
		cs.dwExStyle |= WS_EX_CLIENTEDGE;

	return TRUE;
}

This function calls AfxDeferRegisterClass to register the window class. In fact, it is a macro to the AfxEndDeferRegisterClass function. AfxEndDeferRegisterClass is implemented as follows:

//WINCORE.CPP
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
{
	AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
	fToRegister &= ~pModuleState->m_fRegisteredClasses;
	if (fToRegister == 0)
		return TRUE;

	LONG fRegisteredClasses = 0;

	WNDCLASS wndcls;
	memset(&wndcls, 0, sizeof(WNDCLASS)); 
	wndcls.lpfnWndProc = DefWindowProc;//Setting window procedure functions for window classes
	wndcls.hInstance = AfxGetInstanceHandle();
	wndcls.hCursor = afxData.hcurArrow;

	INITCOMMONCONTROLSEX init;
	init.dwSize = sizeof(init);

	if (fToRegister & AFX_WND_REG)
	{
		// Child windows - no brush, no icon, safest default class styles
		wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
		wndcls.lpszClassName = _afxWnd;
		if (AfxRegisterClass(&wndcls))
			fRegisteredClasses |= AFX_WND_REG;
	}
	if (fToRegister & AFX_WNDOLECONTROL_REG)
	{
		// OLE Control windows - use parent DC for speed
		wndcls.style |= CS_PARENTDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
		wndcls.lpszClassName = _afxWndOleControl;
		if (AfxRegisterClass(&wndcls))
			fRegisteredClasses |= AFX_WNDOLECONTROL_REG;
	}
	if (fToRegister & AFX_WNDCONTROLBAR_REG)
	{
		// Control bar windows
		wndcls.style = 0;   // control bars don't handle double click
		wndcls.lpszClassName = _afxWndControlBar;
		wndcls.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
		if (AfxRegisterClass(&wndcls))
			fRegisteredClasses |= AFX_WNDCONTROLBAR_REG;
	}
	if (fToRegister & AFX_WNDMDIFRAME_REG)
	{
		// MDI Frame window (also used for splitter window)
		wndcls.style = CS_DBLCLKS;
		wndcls.hbrBackground = NULL;
		if (_AfxRegisterWithIcon(&wndcls, _afxWndMDIFrame, AFX_IDI_STD_MDIFRAME))
			fRegisteredClasses |= AFX_WNDMDIFRAME_REG;
	}
	if (fToRegister & AFX_WNDFRAMEORVIEW_REG)
	{
		// SDI Frame or MDI Child windows or views - normal colors
		wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
		wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
		if (_AfxRegisterWithIcon(&wndcls, _afxWndFrameOrView, AFX_IDI_STD_FRAME))
			fRegisteredClasses |= AFX_WNDFRAMEORVIEW_REG;
	}
	if (fToRegister & AFX_WNDCOMMCTLS_REG)
	{
		// this flag is compatible with the old InitCommonControls() API
		init.dwICC = ICC_WIN95_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WIN95CTLS_MASK);
		fToRegister &= ~AFX_WIN95CTLS_MASK;
	}
	if (fToRegister & AFX_WNDCOMMCTL_UPDOWN_REG)
	{
		init.dwICC = ICC_UPDOWN_CLASS;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_UPDOWN_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_TREEVIEW_REG)
	{
		init.dwICC = ICC_TREEVIEW_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_TREEVIEW_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_TAB_REG)
	{
		init.dwICC = ICC_TAB_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_TAB_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_PROGRESS_REG)
	{
		init.dwICC = ICC_PROGRESS_CLASS;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_PROGRESS_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_LISTVIEW_REG)
	{
		init.dwICC = ICC_LISTVIEW_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_LISTVIEW_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_HOTKEY_REG)
	{
		init.dwICC = ICC_HOTKEY_CLASS;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_HOTKEY_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_BAR_REG)
	{
		init.dwICC = ICC_BAR_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_BAR_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_ANIMATE_REG)
	{
		init.dwICC = ICC_ANIMATE_CLASS;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_ANIMATE_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_INTERNET_REG)
	{
		init.dwICC = ICC_INTERNET_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_INTERNET_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_COOL_REG)
	{
		init.dwICC = ICC_COOL_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_COOL_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_USEREX_REG)
	{
		init.dwICC = ICC_USEREX_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_USEREX_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_DATE_REG)
	{
		init.dwICC = ICC_DATE_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_DATE_REG);
	}

	// save new state of registered controls
	pModuleState->m_fRegisteredClasses |= fRegisteredClasses;

	// special case for all common controls registered, turn on AFX_WNDCOMMCTLS_REG
	if ((pModuleState->m_fRegisteredClasses & AFX_WIN95CTLS_MASK) == AFX_WIN95CTLS_MASK)
	{
		pModuleState->m_fRegisteredClasses |= AFX_WNDCOMMCTLS_REG;
		fRegisteredClasses |= AFX_WNDCOMMCTLS_REG;
	}

	// must have registered at least as mamy classes as requested
	return (fToRegister & fRegisteredClasses) == fToRegister;
}

First, the function accepts a parameter, fToRegister, which is an integer that combines many bits of flags through or operations and represents all the classes that you want to register.The first part of the code removes the flag bits of registered classes from fToRegister, then uses a large number of branching structures to determine if a predefined class is in the list of registered classes. If you call the function AfxRegisterClass to register it, the code looks long, but most of it is a simple duplication of the same function.

The AfxRegisterClass contains the following code:

//WINCORE.CPP
if (!::RegisterClass(lpWndClass))//Register window class
{
    TRACE1("Can't register window class named %s\n",
           lpWndClass->lpszClassName);
    return FALSE;
}

We are familiar with the RegisterClass function inside, which is the function we use to register window classes in Win32.

3.5 Create Window

The window should be created after the registration number window class, which is ultimately done by CWnd::CreateEx, which is called by the method CFrameWnd::Create in Cwnd's derived class CFrameWnd

//WINCORE.CPP
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
	LPCTSTR lpszWindowName, DWORD dwStyle,
	int x, int y, int nWidth, int nHeight,
	HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
	// allow modification of several common create parameters
	CREATESTRUCT cs;
	cs.dwExStyle = dwExStyle;
	cs.lpszClass = lpszClassName;
	cs.lpszName = lpszWindowName;
	cs.style = dwStyle;
	cs.x = x;
	cs.y = y;
	cs.cx = nWidth;
	cs.cy = nHeight;
	cs.hwndParent = hWndParent;
	cs.hMenu = nIDorHMenu;
	cs.hInstance = AfxGetInstanceHandle();
	cs.lpCreateParams = lpParam;

	if (!PreCreateWindow(cs))
	{
		PostNcDestroy();
		return FALSE;
	}

	AfxHookWindowCreate(this);
	HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
			cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
			cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);

#ifdef _DEBUG
	if (hWnd == NULL)
	{
		TRACE1("Warning: Window creation failed: GetLastError returns 0x%8.8X\n",
			GetLastError());
	}
#endif

	if (!AfxUnhookWindowCreate())
		PostNcDestroy();        // cleanup if CreateWindowEx fails too soon

	if (hWnd == NULL)
		return FALSE;
	ASSERT(hWnd == m_hWnd); // should have been set in send msg hook
	return TRUE;
}

This function calls the Windows API function **:: CreateWindowEx** to create a window. In fact, before calling this function, the PreCreateWindow function is called again to give the user the opportunity to modify the appearance of the window before it is created.The CreateEx function executes many times during application execution because a single-document MFC application contains many windows, and each window is created by this function.

Then there is the Display and Update window, which is done in CTestApp::InitInstance, as described above.

3.6 Message Cycles and Window Processes

Previously, when looking at the implementation of the AfxWinMain function, we mentioned that three functions are called through two pointers to complete the application initialization process. The third function is CWinThread::Run, which is defined as follows:

//THRDCORE.CPP
int CWinThread::Run()
{
	ASSERT_VALID(this);

	BOOL bIdle = TRUE;
	LONG lIdleCount = 0;

	for (;;)//Enter Dead Cycle
	{
		while (bIdle &&
			!::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))//Cancel message from message queue
		{
			if (!OnIdle(lIdleCount++))
				bIdle = FALSE; 
		}
		
        //Exit when receiving WM_QUIT
		do
		{
			if (!PumpMessage())//GetMessage(), which is very familiar with PumpMessage calls
				return ExitInstance();

			if (IsIdleMessage(&m_msgCur))
			{
				bIdle = TRUE;
				lIdleCount = 0;
			}

		} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
	}

	ASSERT(FALSE);
}

This part of the above is the code of the message loop, whose body is a for loop, which calls PumpMessage internally. This function first cancels the message by GetMessage, then translates and delivers the message by TranslateMessage and DispatchMessage. This process is no different from the implementation of Windows API.

When we designed the window class with the AfxEndDeferRegisterClass function earlier, we saw that the window class was bound to the default window process, so we will not paste the code here. It should be noted that it looks like MFC responds to messages entirely through the default window process, but actually uses a message mapping mechanism, which we will add later.

3.7 Display a button in the window

In order for a button to be displayed in the window, you should respond to the WM_CREATE event of the window by writing the onCreate() function of the window class in MFC, for example, with the following code:

private:
	CButton btn;//Add a member variable to the main frame window class to save the button

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	......//A lot of automatically generated code is omitted here
        
	//Here is the code written by the user himself
	btn.Create("Button", WS_CHILD|BS_DEFPUSHBUTTON, CRect(0,0,100,100),this,123);
	btn.ShowWindow(SW_SHOWNORMAL);
	return 0;
}

Note that the object of the button class cannot be created in the message response function, which causes the button object to be destructed after the function ends.

Note that the object of the C++ window class and the window itself are not a thing. The relationship between them is that the window handle variable defined in the window class object holds the handle of the window associated with this C++ window class.When a window is destroyed, whether or not the window class object is destroyed depends on whether or not the object's lifetime ends.When a window class object is destroyed, local variables within it are also recycled, so the window is destroyed (destructed).

Overall MFC is an obsolete technology, just because of curiosity, it should not be updated all the time.

Twelve original articles have been published. Complimented 14. Visits 5500
Private letter follow

Tags: Windows ascii

Posted on Sat, 08 Feb 2020 01:37:52 -0500 by willeadie