本文以孙鑫老师VC++教程中的程序为基础,学习了Windows程序内部运行机制,理解了Windows程序运行原理及相应的VC++程序设计。程序所实现的功能如下
创建一个Win32应用程序步骤:
1、编写WinMain函数;
2、创建窗口(步骤如下):
a、设计(一个)窗口类(WNDCLASS)
b、注册(该)窗口类。
c、创建窗口。
d、显示并更新窗口。
3、编写消息循环。
4、编写窗口过程函数。
#include "stdafx.h" #include "Win32ProjTest.h" #include "string" #define MAX_LOADSTRING 100 // 全局变量: HINSTANCE hInst; // 当前实例 TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本 TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名 // 此代码模块中包含的函数的前向声明: ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK EbowWndProc(HWND, UINT, WPARAM, LPARAM);//回调函数,主窗口过程函数 INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);//回调函数,关于窗口过程函数 //主函数 int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // TODO: 在此放置代码。 MSG msg; HACCEL hAccelTable; // 初始化全局字符串 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_WIN32PROJTEST, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); // 执行应用程序初始化: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32PROJTEST)); // 主消息循环: while (GetMessage(&msg, NULL, 0, 0))//应用程序从他的消息队列中获取消息 { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { //操作系统感知用户的任何操作并封装成消息,投递到消息队列中 TranslateMessage(&msg);//翻译成新的消息(比如字符消息),并投递到消息队列中 DispatchMessage(&msg);//应用程序调用此函数将窗口消息发送给系统,系统将调用回调函数处理消息msg } } return (int) msg.wParam; } // // 函数: MyRegisterClass() // // 目的: 定义窗口类并向系统注册窗口类。 // ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; //定义窗口样式 wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = EbowWndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32PROJTEST)); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WIN32PROJTEST); wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassEx(&wcex);//注册窗口 } // // 函数: InitInstance(HINSTANCE, int) // // 目的: 保存实例句柄并创建主窗口 // // 注释: // // 在此函数中,我们在全局变量中保存实例句柄并 // 创建和显示主程序窗口。 // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; // 将实例句柄存储在全局变量中 hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow);//显示窗口 UpdateWindow(hWnd); return TRUE; } // // 函数: EbowWndProc(HWND, UINT, WPARAM, LPARAM) // // 目的: 处理主窗口的消息。 // // WM_COMMAND - 处理应用程序菜单 // WM_PAINT - 绘制主窗口 // WM_DESTROY - 发送退出消息并返回 // // 说明: CALLBACK,表明这个函数是主窗口的过程函数,注意这个回调函数是系统来调用的,虽然是程序声明和定义的 LRESULT CALLBACK EbowWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; switch (message) { //当我们按下一个菜单选项,或者一个控件需要通知父窗口一个事件发生(如鼠标单击、双击等), //或者快捷键被按下时,Windows将会发送一个 WM_COMMAND 消息给父窗口。 case WM_COMMAND: wmId = LOWORD(wParam);//哪一个菜单项 wmEvent = HIWORD(wParam); // 分析菜单选择: switch (wmId) {//菜单选项,关于 case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; //菜单选项,退出 case IDM_EXIT: if (IDYES == MessageBox(hWnd, _T("是否真的退出?"), _T("Test By EbowTang"), MB_YESNO)) { DestroyWindow(hWnd); } break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // TODO: 在此添加任意绘图代码... EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // “关于”框的消息处理程序。 INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: return (INT_PTR)TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; } break; } return (INT_PTR)FALSE; }
1,简介MFC
MFC是Windows下程序设计的一个类库,但是该类库比较庞杂,尤其是它的消息映射机制,更是涉及到很多低层的东西。VC++的MFC类库实际上是Windows下C++编程的一套最为流行的类库。MFC的框架结构大大方便了程序员的编程工作,但是为了更加有效、灵活的使用MFC编程,了解MFC的体系结构往往可以使编程工作事半功倍。它合理的封装了WIN32 API函数,并设计了一套方便的消息映射机制。但这套机制本身比较庞大和复杂,对它的分析和了解无疑有助于我们写出更为合理的高效的程序。这里我们简单的分析MFC的消息响应机制,以了解MFC是如何对Windows的消息加以封装,方便用户的开发。
所有能够进行消息处理的类都是基于CCmdTarget类的,也就是说CCmdTarget类是所有可以进行消息处理类的父类。CCmdTarget类是MFC处理命令消息的基础和核心。
比如在一个单文档的例子中,视类窗口始终覆盖在框架类窗口之上,因此所有操作,包括鼠标单击、鼠标移动等操作都只能由视类窗口捕获。一个MFC消息响应函数在程序中有三处相关信息:函数原型、函数实现和以及用来关联消息和消息响应函数的宏。
1,消息响应函数的声明,在消息响应函数的原型代码中,函数声明的前部有一个afx_msg限定符,也是一个宏,该宏表明这个函数是一个消息响应函数的声明。例如我在实例CMFCAppTest中的视类CMFCAppTestView中添加一个WM_LBUTTONDOWN的消息相应,类向导将在CMFCAppTestView的头文件中添加该消息的声明
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
2,消息映射宏:
在视图类的源文件中,BEGIN_MESSAGE_MAP()和END_MASSAGE_MAP()这两个宏之间定义了消息映射表,如下图所示:
例如对于画线,其中有一个ON_WM_LBUTTONDOWN()消息映射宏,这个宏的作用就是把鼠标左键按下消息(WM_LBUTTONDOWN)与一个消息响应函数关联起来,通过这种机制,一旦有消息产生,程序就会调用相应的消息响应函数来进行处理。
3,消息响应函数的定义:在视图类的源文件中,可以看到OnLButtonDown函数的定义。头文件中在两个AFX_MSG注释宏之间是消息响应函数原型的声明。源文件中有两处:一处是在两个AFX_MSG_MAP注释宏之间的消息映射宏,通过这个宏把消息与消息响应函数关联起来;另一处是源文件中的消息响应函数的实现代码。
void CMFCAppTestView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 CView::OnLButtonDown(nFlags, point); }
在Win32应用程序中,当有消息产生时,操作系统会把这条消息放到应用程序的消息队列中,应用程序通过GetMessage函数从这个队列中取出一条具体的消息,并通过DispatchMessage 函数把消息交给操作系统,调用的是应用程序的窗口过程,即窗口过程函数WndProc进行处理,然而在MFC程序中,并不是按这种途径进行处理的, 只要定义了与消息有关的三处信息后,便可实现消息的响应处理.MFC中采用的这种消息处理机制称为MFC消息映射机制.
MFC消息映射机制的具体实现方法是:在每个能接收和处理消息的类中,定义一个消息和消息函数静态对照表,即消息映射表.在消息映射表中,消息与对应的消息处理函数指针是成对出现的.某个类能处理的所有消息及其对应的消息处理函数的地址都列在这个类所对应的静态表中.当有消息需要处理时,程序只要搜索该消息静态表,查看表中是否含有该消息,就可知道该类能否处理此消息.如果能处理此消息,则同样依照静态表能很容易找到并调用对应的消息处理函数.
MFC消息映射机制的实际实现过程:MFC在后台维护了一个窗口句柄与对应的C++对象指针的对照表,以例中的CDrawView类为例,与CDrawView对象相关的有一个窗口,窗口当然有它的窗口句柄,该句柄与CDrawView对象的一个指针(即CDrawView*)存在一一对应关系,在窗口句柄与C++对象对照表中就维护了这种对应关系.当收到某一个消息时,消息的第一个参数就指明了该消息与哪个窗口句柄相关,通过对照表,就可以找到与之相关的C++对象指针.然后把这个指针传递给应用程序框架窗口类的基类,后者会调用一个名为WindowProc函数,该函数定义位于WinCore.cpp中.CWnd::WindowProc函数内部调用了一个OnWndMsg函数,真正的消息路由,也就是消息映射是由此函数完成的.OnWndMsg函数的处理过程:首先判断消息是否有消息响应函数.判断方法是在相应窗口类中查找所需的消息响应函数.因为传递给WindowProc的是窗口子类指针,OnWndMsg会到相应的子类头文件中查找,看看DECLARE_MESSAGE_MAP()宏之上,两个AFX_MSG注释宏之间是否有相应的消息响应函数原型的声明;再到子类的源文件中,看看BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()两个宏之间是否有相应的消息映射宏.如果通过上述步骤,找到了消息响应函数,接着就调用该响应函数,对消息进行处理.如果在子类中没有找到,那么就交由基类进行处理.通过以上步骤,MFC就实现了具体的消息映射,从而完成对消息的响应,如下图所示便是一个完整的过程: