孙鑫VC++第三章 2.基于MFC的程序框架剖析

news2024/11/27 18:27:25

目录

 1. MFC向导生成类

 2. 框架流程

2.1 WinMain

2.2 全局对象:theApp

2.3 AfxWinMain函数

1.AfxWinMain:

2.AfxGetThread函数(thrdcore.cpp):

3.AfxGetApp是一个全局函数,定义于(afxwin1.inl)中:

2.4 InitInstance函数(Test.h)

2.5  窗口类的设计

2.6 窗口类的注册 

2.7 窗口创建

2.8 显示窗口和更新窗口


 1. MFC向导生成类

MFC库是开发Windows应用程序的C++接口。MFC提供了面向对象的框架,程序开发人员可以基于这一框架开发Windows应用程序。MFC采用面向对象设计,将大部分的Windows API封装到C++类中,以类成员函数的形式提供给程序开发人员调用。

下面看一下MFC应用程序向导帮助生成的这些代码:

单击Visual Studio菜单栏上的【视图】菜单,单击【类视图】菜单项,这将打开类视图子窗口,方便查看项目中的类组织结构。在“类视图”中展开Test根节点,可以看到项目中所有的五个类,如图所示:

提示: 如果要查看某个类的成员信息,可以直接选中某个类,就会在下方的窗格中显示该类拥有的函数和属性。双击跳转到定义类的头文件或源文件。

对于一个单文档应用程序(即我们在创建项目时第二步应用程序类型中选择的“单个文档”),都有一个CMainFrame类、一个以“C+项目名+App”为名字的类、一个以“C+项目名+Doc”为名字的类和一个以“C+项目名+View”为名字的类。

五个类都有一个基类,这些基类都是MFC中的类。

 2. 框架流程

创建Win32应用程序

进入 WinMain 函数、

设计窗口类、

注册窗口类、

产生窗口、

显示窗口、

更新窗口,

最后进入消息循环,将消息路由给窗口过程函数去处理。

但在当前项目在当前Test项目中查找不到WinMain函数:

MFC 源代码的具体路径(可作参考):

C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.16.27023\atlmfc\src\mfc 可以用vscode打开,查找WinMain

2.1 WinMain

在 appmodul.cpp中

extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	_In_ LPTSTR lpCmdLine, int nCmdShow)
#pragma warning(suppress: 4985)
{
	// call shared/exported WinMain
	return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}

2.2 全局对象:theApp

加上断点时,发现 CTestApp 构造在WinMain函数之前调用,是因为程序中定义了一个CTestApp类型的全局对象:theApp

提示: MFC程序的全局变量都放置在类视图窗口中的“全局函数和变量”分支下,单击该分支即可看到程序当前所有的全局函数和变量。双击某个全局变量,即可定位到该变量的定义处。

// CTestApp:
// 有关此类的实现,请参阅 Test.cpp
//
 
class CTestApp : public CWinApp
{
public:
	CTestApp() noexcept;
 
 
// 重写
public:
	virtual BOOL InitInstance();
	virtual int ExitInstance();
 
// 实现
	afx_msg void OnAppAbout();
	DECLARE_MESSAGE_MAP()
};
 
extern CTestApp theApp;
程序执行的顺序:theApp全局对象定义处、CTestApp构造函数、WinMain函数。
在程序入口main函数加载时,系统就已经为全局变量或全局对象分配了存储空间,并为它们赋了初始值。

应用程序的实例是由实例句柄(WinMain函数的参数hInstance)来标识的。而对 MFC 程序来说,通过产生一个应用程序类的对象来唯一标识应用程序的实例。每一个MFC程序有且仅有一个从应用程序类(CWinApp)派生的类。每一个MFC程序实例有且仅有一个该派生类的实例化对象,也就是theApp全局对象。该对象就表示了应用程序本身。

当一个子类在构造之前会先调用其父类的构造函数。因此 theApp 对象的构造函数 CTestApp 在调用之前,会调用其父类CWinApp的构造函数,从而就把我们程序自己创建的类与Microsoft提供的基类关联起来了。CWinApp的构造函数完成程序运行时的一些初始化工作。

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();
	ENSURE(pModuleState);
	AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
	ENSURE(pThreadState);
	ASSERT(AfxGetThread() == NULL);
	pThreadState->m_pCurrentWinThread = this;
	ASSERT(AfxGetThread() == this);
	m_hThread = ::GetCurrentThread();
	m_nThreadID = ::GetCurrentThreadId();
 
	// initialize CWinApp state
	ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please
	pModuleState->m_pCurrentWinApp = this;
	ASSERT(AfxGetApp() == this);
 
	// in non-running state until WinMain
	m_hInstance = NULL;
	m_hLangResourceDLL = NULL;
	m_pszHelpFilePath = NULL;
	m_pszProfileName = NULL;
	m_pszRegistryKey = NULL;
	m_pszExeName = NULL;
	m_pszAppID = NULL;
	m_pRecentFileList = NULL;
	m_pDocManager = NULL;
	m_atomApp = m_atomSystemTopic = NULL;
	m_lpCmdLine = NULL;
	m_pCmdInfo = NULL;
	m_pDataRecoveryHandler = NULL;
 
	// initialize wait cursor state
	m_nWaitCursorCount = 0;
	m_hcurWaitCursorRestore = NULL;
 
	// initialize current printer state
	m_hDevMode = NULL;
	m_hDevNames = NULL;
	m_nNumPreviewPages = 0;     // not specified (defaults to 1)
 
	// initialize DAO state
	m_lpfnDaoTerm = NULL;   // will be set if AfxDaoInit called
 
	// other initialization
	m_bHelpMode = FALSE;
	m_eHelpType = afxWinHelp;
	m_nSafetyPoolSize = 512;        // default size
 
	m_dwRestartManagerSupportFlags = 0;    // don't support Restart Manager by default
	m_nAutosaveInterval = 5 * 60 * 1000;   // default autosave interval is 5 minutes (only has effect if autosave flag is set)
 
	m_bTaskbarInteractionEnabled = TRUE;
 
	// Detect the kind of OS:
	OSVERSIONINFO osvi;
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
 
// Fix for warnings when building against WinBlue build 9444.0.130614-1739
// warning C4996: 'GetVersionExW': was declared deprecated
// externalapis\windows\8.1\sdk\inc\sysinfoapi.h(442)
// Deprecated. Use VerifyVersionInfo* or IsWindows* macros from VersionHelpers.
#pragma warning( disable : 4996 )
	::GetVersionEx(&osvi);
#pragma warning( default : 4996 )
 
	m_bIsWindows7 = (osvi.dwMajorVersion == 6) && (osvi.dwMinorVersion >= 1) || (osvi.dwMajorVersion > 6);
 
	// Taskbar initialization:
	m_bComInitialized = FALSE;
 
	m_pTaskbarList = NULL;
	m_pTaskbarList3 = NULL;
	m_bTaskBarInterfacesAvailable = TRUE;
}

CWinApp的构造函数中有这样两行代码:

pThreadState->m_pCurrentWinThread = this;
pModuleState->m_pCurrentWinApp = this;

m_pCurrentWinThread对象的类型是CWinThread,该类是CWinApp的父类。根据C++继承性原理,这个this对象代表的是子类CTestApp的对象,即theApp。

CWinApp的构造函数有一个LPCTSTR类型的形参:lpszAppName,但是CTestApp的构造函数没有参数。

如果基类的构造函数带有一个形参,那么子类构造函数需要显式地调用基类带参数的构造函数。

如果某个函数的参数有默认值,那么在调用该函数时可以传递该参数的值,也可以不传递,直接使用默认值即可。

在CWinApp 类名上单击鼠标右键,利用【转到定义】命令,定位到CWinApp类的定义处,代码下:CWinApp构造函数的形参确实有一个默认值(NULL)。这样,在调用CWinApp类的构造函数时,就不用显式地去传递这个参数的值。

class CWinApp : public CWinThread
{
	DECLARE_DYNAMIC(CWinApp)
public:
 
// Constructor
	explicit CWinApp(LPCTSTR lpszAppName = NULL);     // app name defaults to EXE name
...
}

2.3 AfxWinMain函数

知识点 :因为c++不是完全面向对象的函数,为了让各个类有机地结合在一起,就要定义一些全局函数。全局函数(在每个类中都可以使用),应用程序框架函数。Afx前缀的函数代表应用程序框架(Application Framework)函数。应用程序框架实际上是一套辅助我们生成应用程序的框架模型。该模型把多个类进行了一个有机的集成,可以根据该模型提供的方案来设计我们自己的应用程序。在 MFC 中,以Afx为前缀的函数都是全局函数,可以在程序的任何地方调用它们。

winmain.cpp

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	_In_ LPTSTR lpCmdLine, int nCmdShow)
{
	ASSERT(hPrevInstance == NULL);
 
	int nReturnCode = -1;
	CWinThread* pThread = AfxGetThread();
	CWinApp* pApp = AfxGetApp();
 
	// AFX internal initialization
	if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
		goto InitFailure;
 
	// App global initializations (rare)
	if (pApp != NULL && !pApp->InitApplication())
		goto InitFailure;
 
	// Perform specific initializations
	if (!pThread->InitInstance())
	{
		if (pThread->m_pMainWnd != NULL)
		{
			TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
			pThread->m_pMainWnd->DestroyWindow();
		}
		nReturnCode = pThread->ExitInstance();
		goto InitFailure;
	}
	nReturnCode = pThread->Run();
 
InitFailure:
#ifdef _DEBUG
	// Check for missing AfxLockTempMap calls
	if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
	{
		TRACE(traceAppMsg, 0, "Warning: Temp map lock count non-zero (%ld).\n",
			AfxGetModuleThreadState()->m_nTempMapLock);
	}
	AfxLockTempMaps();
	AfxUnlockTempMaps(-1);
#endif
 
	AfxWinTerm();
	return nReturnCode;
}

1.AfxWinMain:

首先调用AfxGetThread函数获得一个CWinThread类型的指针。接着调用AfxGetApp函数获得一个CWinApp类型的指针。MFC类库组织结构图中可以知道CWinApp派生于CWinThread。

    CWinThread* pThread = AfxGetThread();
    CWinApp* pApp = AfxGetApp();

2.AfxGetThread函数(thrdcore.cpp):

位置:C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.16.27023\atlmfc\src\mfc

CWinThread* AFXAPI AfxGetThread()
{
    // check for current thread in module thread state
    AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
    CWinThread* pThread = pState->m_pCurrentWinThread;
    return pThread;
}
    if (pApp != NULL && !pApp->InitApplication())

AfxGetThread函数返回的就是在CWinApp构造函数中保存的this指针(如下代码)。对Test程序来说,这个this指针实际上指向的是CTestApp的全局对象:theApp。

连接:CWinApp的构造函数中有这样两行代码:

pThreadState->m_pCurrentWinThread = this;
pModuleState->m_pCurrentWinApp = this;

3.AfxGetApp是一个全局函数,定义于(afxwin1.inl)中:

位置:C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.16.27023\atlmfc\include

_AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp()
    { return afxCurrentWinApp; }

afxCurrentWinApp,定义于(afxwin.h*)中,代码如下:

#define afxCurrentWinApp    AfxGetModuleState()->m_pCurrentWinApp

AfxGetApp函数返回的是在CWinApp构造函数中保存的this指针。对Test程序来说,这个this指针实际上指向的是CTestApp的对象:theApp。也就是说,对Test程序来说,pThread和pApp所指向的都是CTestApp类的对象,即theApp全局对象。

2.4 InitInstance函数(Test.h)

pApp首先调用InitApplication函数,该函数完成MFC内部管理方面的工作。接着,调用pThread的InitInstance函数。在Test程序中,可以发现从CWinApp派生的应用程序类CTestApp也有一个InitInstance函数,其声明代码如下:

virtual BOOL InitInstance();

从其定义(virtual)可以知道,InitInstance函数是一个虚函数。根据类的多态性原理,可以知道AfxWinMain函数在这里调用的实际上是子类CTestApp的InitInstance函数。CTestApp类的InitInstance函数定义代码如下所示:

BOOL CTestApp::InitInstance()
{
	// 如果一个运行在 Windows XP 上的应用程序清单指定要
	// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
	//则需要 InitCommonControlsEx()。  否则,将无法创建窗口。
	INITCOMMONCONTROLSEX InitCtrls;
	InitCtrls.dwSize = sizeof(InitCtrls);
	// 将它设置为包括所有要在应用程序中使用的
	// 公共控件类。
	InitCtrls.dwICC = ICC_WIN95_CLASSES;
	InitCommonControlsEx(&InitCtrls);
 
	CWinApp::InitInstance();
 
 
	// 初始化 OLE 库
	if (!AfxOleInit())
	{
		AfxMessageBox(IDP_OLE_INIT_FAILED);
		return FALSE;
	}
 
	AfxEnableControlContainer();
 
	EnableTaskbarInteraction(FALSE);
 
	// 使用 RichEdit 控件需要 AfxInitRichEdit2()
	// AfxInitRichEdit2();
 
	// 标准初始化
	// 如果未使用这些功能并希望减小
	// 最终可执行文件的大小,则应移除下列
	// 不需要的特定初始化例程
	// 更改用于存储设置的注册表项
	// TODO: 应适当修改该字符串,
	// 例如修改为公司或组织名
	SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
	LoadStdProfileSettings(4);  // 加载标准 INI 文件选项(包括 MRU)
 
 
	// 注册应用程序的文档模板。  文档模板
	// 将用作文档、框架窗口和视图之间的连接
	CSingleDocTemplate* pDocTemplate;
	pDocTemplate = new CSingleDocTemplate(
		IDR_MAINFRAME,
		RUNTIME_CLASS(CTestDoc),
		RUNTIME_CLASS(CMainFrame),       // 主 SDI 框架窗口
		RUNTIME_CLASS(CTestView));
	if (!pDocTemplate)
		return FALSE;
	AddDocTemplate(pDocTemplate);
 
 
	// 分析标准 shell 命令、DDE、打开文件操作的命令行
	CCommandLineInfo cmdInfo;
	ParseCommandLine(cmdInfo);
 
 
 
	// 调度在命令行中指定的命令。  如果
	// 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。
	if (!ProcessShellCommand(cmdInfo))
		return FALSE;
 
	// 唯一的一个窗口已初始化,因此显示它并对其进行更新
	m_pMainWnd->ShowWindow(SW_SHOW);
	m_pMainWnd->UpdateWindow();
	return TRUE;
}

2.5  窗口类的设计

窗口类的注册(wincore.cpp):AfxEndDeferRegisterClass 函数

在InitInstance中是通过ProcessShellCommand这个函数完成窗口的设计,注册,等操作
注册窗口是通过调用AfxEndDeferRegisterClass完成注册操作注册窗口时在正常情况下:是通过先调用子类PrecreateWindow调用父类PrecreateWindow再在调用AfxEndDeferRegisterClass来注册,如果是单文档(SDI)就会先执行一次AfxEndDeferRegisterClass注册,

ProcessShellCommand()——>OnCmdMsg()——>AfxDispatchCmdMsg()——>pfn_Command()——>OnFileNew()——>OpenDocumentFile()——>CreateNewFrame()——>LoadFrame()

多次调用PreCreateWindow、CreateEx、AfxEndDeferRegisterClass 注册工具栏、状态栏等.

BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
{
	// mask off all classes that are already registered
	AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
	fToRegister &= ~pModuleState->m_fRegisteredClasses;
	if (fToRegister == 0)
		return TRUE;
 
	LONG fRegisteredClasses = 0;
 
	// common initialization
	WNDCLASS wndcls;
	memset(&wndcls, 0, sizeof(WNDCLASS));   // start with NULL defaults
	wndcls.lpfnWndProc = DefWindowProc;
	wndcls.hInstance = AfxGetInstanceHandle();
	wndcls.hCursor = afxData.hcurArrow;
 
	INITCOMMONCONTROLSEX init;
	init.dwSize = sizeof(init);
 
	// work to register classes as specified by fToRegister, populate fRegisteredClasses as we go
	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);
	}
	if (fToRegister & AFX_WNDCOMMCTL_LINK_REG)
	{
		init.dwICC = ICC_LINK_CLASS;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_LINK_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_PAGER_REG)
	{
		init.dwICC = ICC_PAGESCROLLER_CLASS;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_PAGER_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 many classes as requested
	return (fToRegister & fRegisteredClasses) == fToRegister;
}

2.6 窗口类的注册 

AfxRegisterClass函数,注册窗口类:

AfxRegisterClass函数首先获得窗口类信息。如果该窗口类已经注册,则直接返回一个真值;如果尚未注册,就调用RegisterClass函数注册该窗口类。

// like RegisterClass, except will automatically call UnregisterClass
// 和RegisterClass一样,except会自动调用UnregisterClass  
BOOL AFXAPI AfxRegisterClass(WNDCLASS* lpWndClass)
{
	WNDCLASS wndcls;//获得窗口类信息
	if (GetClassInfo(lpWndClass->hInstance, lpWndClass->lpszClassName,
		&wndcls))
	{
		// class already registered
		return TRUE;//如果该窗口类已经注册,则直接返回一个真值
	}
 
	if (!RegisterClass(lpWndClass))//如果尚未注册,就调用RegisterClass函数注册该窗口类
	{
		TRACE(traceAppMsg, 0, _T("Can't register window class named %Ts\n"),
			lpWndClass->lpszClassName);
		return FALSE;
	}
 
	BOOL bRet = TRUE;
 
	if (afxContextIsDLL)
	{
		AfxLockGlobals(CRIT_REGCLASSLIST);
		TRY
		{
			// class registered successfully, add to registered list
			AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
			pModuleState->m_strUnregisterList+=lpWndClass->lpszClassName;
			pModuleState->m_strUnregisterList+='\n';
		}
		CATCH_ALL(e)
		{
			AfxUnlockGlobals(CRIT_REGCLASSLIST);
			THROW_LAST();
			// Note: DELETE_EXCEPTION not required.
		}
		END_CATCH_ALL
		AfxUnlockGlobals(CRIT_REGCLASSLIST);
	}
 
	return bRet;
}

MFC 应用程序 Test,实际上有两个窗口。其中一个是CMainFrame类(MainFrm.h)的对象所代表的应用程序框架窗口,该类有一个PreCreateWindow函数,这是在窗口产生之前被调用的。该函数的默认实现代码如下所示。

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
	if( !CFrameWnd::PreCreateWindow(cs) )
		return FALSE;
	// TODO: 在此处通过修改
	//  CREATESTRUCT cs 来修改窗口类或样式
 
	return TRUE;
}

该函数首先调用CFrameWndPreCreateWindow函数。后者的定义位于源文件winfrm.cpp中,代码下。 

// CFrameWnd second phase creation
// CFrameWnd第二阶段创建
 
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
	if (cs.lpszClass == NULL)
	{
		VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
		cs.lpszClass = _afxWndFrameOrView;  // COLOR_WINDOW background
	}
 
	if (cs.style & FWS_ADDTOTITLE)
		cs.style |= FWS_PREFIXTITLE;
 
	cs.dwExStyle |= WS_EX_CLIENTEDGE;
 
	return TRUE;
}

// 该函数中调用了AfxDeferRegisterClass函数,读者可以在afximpl.h文件中找到后者的定义,定义代码如下: AfxDeferRegisterClass函数(afximpl.h):

#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)

2.7 窗口创建

在MFC程序中,窗口的创建功能是由 CWnd 类 CreateEx 函数实现的,该函数的声明位于afxwin.h文件中,具体代码如下所示。CreateEx 函数(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)
{
	ASSERT(lpszClassName == NULL || AfxIsValidString(lpszClassName) ||
		AfxIsValidAtom(lpszClassName));
	ENSURE_ARG(lpszWindowName == NULL || AfxIsValidString(lpszWindowName));
 
	// 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)
	{
		TRACE(traceAppMsg, 0, "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;
}

在MFC底层代码中,CFrameWnd类Create函数内部调用了上述CreateEx函数。而前者又由CFrameWnd类的LoadFrame函数调用。Create函数(winfrm.cpp)定义:

BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
	LPCTSTR lpszWindowName,
	DWORD dwStyle,
	const RECT& rect,
	CWnd* pParentWnd,
	LPCTSTR lpszMenuName,
	DWORD dwExStyle,
	CCreateContext* pContext)
{
	HMENU hMenu = NULL;
	if (lpszMenuName != NULL)
	{
		// load in a menu that will get destroyed when window gets destroyed
		HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, ATL_RT_MENU);
		if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
		{
			TRACE(traceAppMsg, 0, "Warning: failed to load menu for CFrameWnd.\n");
			PostNcDestroy();            // perhaps delete the C++ object
			return FALSE;
		}
	}
 
	m_strTitle = lpszWindowName;    // save title for later
 
	if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
		rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
		pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
	{
		TRACE(traceAppMsg, 0, "Warning: failed to create CFrameWnd.\n");
		if (hMenu != NULL)
			DestroyMenu(hMenu);
		return FALSE;
	}
 
	return TRUE;
}

CFrameWnd类派生于CWnd类,根据类的继承性原理,CFrameWnd类就继承了CWnd类的CreateEx函数。因此,CFrameWnd类的Create函数内调用的实际上就是CWnd类的CreateEx函数。

2.8 显示窗口和更新窗口

Test程序的应用程序类TestApp从它的基类CWinThread继承了一个名为m_pMainWnd的成员变量(公有成员)。该变量是一个 CWnd 类型的指针,它保存了应用程序框架窗口对象的指针。也就是说,是指向 CMainFrame 对象的指针。在 CTestApp 类的 InitInstance函数实现内部有如下两行代码。

    // 调度在命令行中指定的命令。  如果
    // 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。
    if (!ProcessShellCommand(cmdInfo))
        return FALSE;//此时窗口创建完成
 
    // 唯一的一个窗口已初始化,因此显示它并对其进行更新
    m_pMainWnd->ShowWindow(SW_SHOW);
    m_pMainWnd->UpdateWindow();
    return TRUE;
    m_pMainWnd->ShowWindow(SW_SHOW);
    m_pMainWnd->UpdateWindow();

这两行代码的功能是显示应用程序框架窗口和更新这个窗口。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/524736.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

原型/原型链/构造函数/类

认识构造函数 为什么有构造函数 因为一般的创建对象的方式一次只能创建一个对象, 利用工厂模式创建的对象,对象的类型都是Object类型 什么是构造函数 构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数…

Uni-app项目应用总结(一)

目录 一.新建uniapp项目 第一步:下载HBuilder 第二步:创建uni-app项目 第三步:运行uni-app 二.uni-app组件使用 三.uni-app路由跳转  1.页面路由配置    (1)在pages.json中配置页面路由    (2)在pages.json中配置底部导航栏 2.路由跳转方法…

【输配电路 DZY-104端子排中间继电器 接通、信号转换 JOSEF约瑟】

DZY-104端子排中间继电器品牌:JOSEF约瑟型号:DZY-104名称:端子排式中间继电器触点容量:5A/250V功率消耗:≤1.5W/≤3W/≤7W/≤3VA/≤7VA/≥5W绝缘电阻:≥10MΩ 系列型号: DZY-101端子排中间继电器; DZY-104端子排中间继电器; DZY-105端子排…

华南农业大学|图像处理与分析技术综合测试|题目解答:求芒果单层坏损率

设计任务 对于一幅芒果果实内部的 CT 断层图像,试采用图像处理与分析技术,设计适当的算法和程序,首先分割出其中的坏损区域,然后计算其像素面积占整个果肉区域的百分比(单层坏损率)。请按统一要求写出算法…

nuc980 uboot 2017.11 移植:env 保存位置选择问题

开发环境 Win10 64位 ubuntu 20.04 虚拟机 VMware Workstation 16 Pro 开发板:NK-980IOT(NUC980DK61Y) gcc 交叉编译工具链: ARM 官方 gcc version 11.2.1 20220111 NUC980 uboot 版本 :尝试移植到 u-boot-2017.1…

科普 “平均工资又涨了”

周四晚上做了一个图,发了一则朋友圈,科普了一下为什么平均工资一直在涨: 曲线是 drawio 画的,不是类似 geogebra 画的精确数学函数,误差比较大,但大概就是这个意思。 收入应该是无标度分形的幂律分布&am…

孙鑫VC++第三章 4.窗口类、窗口类对象与窗口三者之间关系

目录 1. 创建CWnd 2. WinMain 3. 创建CButton 1. 创建CWnd 模拟CWnd类的封装过程。在解决方案ch04下添加一个新的空项目,项目名称为:WinMain,在项目创建完成后,将WinMain项目设为启动项目。 接下来在WinMain项目中添加一个名…

【C++ 学习 ④】- 类和对象(下)

目录 一、初始化列表 1.1 - 定义 1.2 - 使用初始化列表的原因 1.3 - 成员变量的初始化顺序 二、静态成员 2.1 - 静态成员变量 2.2 - 静态成员函数 三、友元 3.1 - 友元函数 3.2 - 友元类 四、内部类 五、匿名对象 5.1 - 匿名对象的特性 5.2 - 匿名对象的使用场景…

3.View的绘制流程

View是在什么时候显示在屏幕上面的?(如:MainActivity的布局文件activity_main.xml) setContentView最终的结果是将解析的xml文件中的View添加到DecorView中. 那么这个DecorView是什么时候添加到Window(PhoneWindow)的呢? DecorView是在ActivityThread.java的handleResumeA…

2-Zookeeper单机版安装

2-Zookeeper单机版安装 本文介绍的是 Linux 系统下 Zookeeper 安装方式 ① 下载 进入官网 https://zookeeper.apache.org/ 点击下载按钮 进入下载页 https://zookeeper.apache.org/releases.html 后选择 最新的稳定版本,如下: 3.7.1 为最新的稳定版本…

号称分割一切的图片分割模型开源了——Segment Anything Meta SAM

头条号:人工智能研究所 微信号:启示AI科技 微信小程序:AI人工智能工具 以前,要解决任何类型的分割问题,有两类方法。第一种是交互式分割,允许分割任何类别的对象,但需要人通过迭代细化掩码来指导。第二种,自动分割,允许分割提前定义的特定对象类别(例如,猫或椅子),…

【计算机系统】指令

leaq指令 一元指令 二元指令 例子 指令addq 指令subq 指令incq 指令subq 移位指令 移位指令用途 特殊运算指令

LitCTF2023 郑州轻工业大学首届网络安全赛 WP 部分

LitCTF2023 郑州轻工业大学首届网络安全赛 WP 部分 前言:Web:我Flag呢?导弹迷踪:Follow me and hack me:PHP是世界上最好的语言!!作业管理系统:Vim yyds:Ping&#xff1a…

Java基础-面向对象总结(2)

这篇文章主要讲解 Java中的 变量方法代码块访问修饰限定符Java 是值传递,还是引用传递?类和对象的生命周期..... 希望给您带来帮助 目录 变量 成员变量与局部变量的区别 静态变量和实例变量的区别?静态方法、实例方法呢? 可以…

数据分析06——Pandas中的数据抽取

1、前言: 在Pandas中进行数据抽取主要有两种方法,一种是loc方法,一种是iloc方法;在获取数据时可以获取的数据有三种形式,一种是Series类型,一种是DataFrame类型,还有一种是直接获取数据值&…

Nginx make报错处理

文章目录 make报错:fatal error:sys/sysctl.h:No such file or directory问题处理 make 报错:error: this statement may fall through [-Werrorimplicit-fallthrough]问题处理 make报错:error: struct crypt_data has no member named curre…

DCGAN--Keras实现

文章目录 一、Keras与tf.keras?二、keras中Model的使用三、使用Keras来实现DCGan1、导入必要的包2.指定模型输入维度:图像尺寸和噪声向量 的长度3、构建生成器4、构造鉴别器5、构建并编译DCGan6、对模型进行训练7、显示生成图像8、运行模型 总结 一、Ker…

力扣sql中等篇练习(二十)

力扣sql中等篇练习(二十) 1 寻找面试候选人 1.1 题目内容 1.1.1 基本题目信息1 1.1.2 基本题目信息2 1.1.3 示例输入输出 a 示例输入 b 示例输出 1.2 示例sql语句 # 分为以下两者情况,分别考虑,然后union进行处理(有可能同时满足,需要去进行去重) # ①该用户在 三场及更多…

软件测试八股文,软件测试常见面试合集【附答案】

PS:加上参考答案有几十万字,答案就没有全部放上来了,高清打印版本超过400多页,评论区留言直接获取 1、你的测试职业发展是什么? 2、你认为测试人员需要具备哪些素质 3、你为什么能够做测试这一行 4、测试的目的是什么? 5、测…