Windows编程与MFC编程基础
一、Windows编程
1、Windows应用程序编程接口API
Windows API 是指 Windows 操作系统应用程序编程接口 (Application Programming Interface, API),支持操作系统中的函数定义、参数定义、结构定义、消息格式、宏和接口的实现。主要学习 Win32 API ,此版本的 API 与其他版本的 API 不同在于,Win32 API 中有关安全方面的函数只能在 Windows NT 操作系统上使用。
-
Win32 API 主要包括以下方面:
方面 描述 Windows 管理 完成 Windows 管理中的各方面的功能。 Windows 控件 完成标准 Windows 控件的功能。 系统内核 完成 Windows 操作系统的一些核心操作。 GDI 图形设备接口 完成 Windows 操作系统有关图形绘制的功能。 系统服务 提供对 Windows 操作系统底层服务的支持。 国际化支持 提供多语言的支持。 网络服务 完成 Windows 操作系统有关网络的功能。
2、使用句柄表示窗口
- 窗口是应用程序显示在输出屏幕上的一个矩形区域,用于接收用户的输入或程序的数据处理结果的显示;多个应用程序可以共用同一个屏幕,但是同一时间只有一个窗口可以通过鼠标和键盘等数据输入设备接受用户数据的输入,因此每个窗口都需要一个标识来区别。Windows API 中使用 HWND (窗口句柄类型)来标识窗口;Win32 API 中 HWND 是一个32位的无符号整型值;不同位数的操作系统下,HWND 的储存空间大小不一样,可通过 sizeof(HWND) 来计算。
3、输入事件产生的消息
Windows 应用程序是事件驱动的,通常情况下不会显式地调用函数来获取输入,而是有系统将接收到的输入传递给应用程序的不同窗口,再由相应的窗口的窗口函数(每个窗口都有一个对应的函数,称为窗口函数)对输入进行处理并执行对系统的控制。
系统使用消息的形式将输入传递给窗口函数;系统和应用都能创建消息。应用程序可以产生指向其所属窗口的消息完成任务,也可以与其他应用程序通过消息进行通信。
-
系统向窗口函数发送消息,需要4个参数:
参数 描述 窗口句柄 用于表示向哪个窗口发送消息,操作系统通过该参数判断哪个窗口函数接收这条消息。 消息标识 一个常数,用于表示消息的种类。当消息对应的窗口函数接收到消息时,会通过消息标识来确定如何处理消息。 两个消息参数 用于表示消息所附带的参数,其含义和取值根据消息的不同而不同;当不使用消息参数时,通常设置为NULL。 Windows 操作系统使用以下两种方式将消息传递给窗口函数。
(1)发送消息到先进先出的消息队列中
- 用于储存通过鼠标或键盘输入的用户输入,如 WM_MOUSEMOVE、WM_LBUTTONDWN、WM_KEYDOWN 和 WM_CHAR 等消息;也用于储存定时器消息(WM_TIMER)、重绘消息(WM_PAINT)和推出消息(WM_QUIT)等。通过消息队列发送的消息称为队列消息,使用 PostMessage() 函数发送的消息会发送打消息队列中。
(2)使用系统定义的内存对象临时储存消息
- 除了上述的队列消息,其他消息一般都会使用系统定义的内存对象临时储存消息,并发送给窗口函数。使用 SendMessage() 函数发出的消息会直接发送给窗口函数。
4、Windows 句柄的数据类型
-
常用 Windows 句柄数据类型
类型 定义 类型 定义 HACCEL 加速键句柄 HHOOK 钩子句柄 HANDLE 对象句柄 HICON 图标句柄 HBITMAP 位图句柄 HIMAGELIST 图像列表句柄 HBRUSH 画刷句柄 HINSTANCE 实例句柄 HCURSOR 光标句柄 HKEY 注册表句柄 HDC 设备上下文句柄 HKL 键盘布局句柄 HDDEDATA DDE数据句柄 HLOCAL 本地内存块句柄 HDESK 桌面句柄 HMENU 菜单句柄 HDROP 内部下拉结构句柄 HMETAFILE 元文件句柄 HDWP 窗口位置结构句柄 HMODULE 模块句柄 HENHMETAFILE 增强型图元句柄 HMONITOR 显示器句柄 HFILE 文件句柄 HPEN 铅笔句柄 HFONT 字体句柄 HRGN 区域句柄 HGDIOBJ GDI对象句柄 HRSRC 资源句柄 HGLOBAL 全局内存块句柄 HWND 窗口句柄
二、Windows 程序执行流程
- 核心要点:程序入口函数、窗口菜单窗口函数以及关于对话框。
1、入口函数 WinMain()
-
Win32 平台默认 WinMain()函数声明如下:
int APIENTRY WinMain( HINSTANCE hInstance, //指定应用程序的当前实例句柄 HINSTANCE hPrevInstance, //指定应用程序的前一个实例的句柄,默认为NULL LPSTR lPCmdLine, //以非NULL结束的字符串用于指定执行程序的应用程序命令行 int nCmdShow //应用程序的主对话框如何显示 )
-
nCmdShow 参数用于指定窗体的显示形式,其有效取值如下表
取值 显示方式 SW_HIDE 隐藏对话框并激活其他对话框 SW_MINIMIZE 最小化指定对话框,并激活系统列表中最顶层的对话框 SW_RESTORE 激活并显示对话框。若果对话框是在最大化或最小化状态,则系统恢复原始大小和位置,与 SW_SHOWNORMAL 参数的作用一样 SW_SHOW 激活对话框,并显示当前大小和位置 SW_SHOWMAXIMIZED 激活对话框,并将对话框最大化显示 SW_SHOWMINIMIZED 激活对话框,并将对话框以图标形式显示 SW_SHOWMINNOCTIVE 显示对话框图标,并且激活的对话框仍然保持激活状态 SW_SHOWNA 以当前的状态显示对话框,激活的对话框仍然保持激活状态 SW_SHOWNOACTIVATE 以最近的大小和位置显示对话框,激活的对话框仍然保持激活状态 SW_SHOWNORMAL 同 SW_RESTORE WinMain()函数初始化应用程序,显示程序的主对话框、进入消息循环和调度循环,直到收到 WM_QUIT 消息。当收到 WM_QUIT 消息程序会终止并将消息传入的 wParam 参数包含的退出代码值返回;如果在进入消息循环前终止,则返回零。WinMain() 函数主要完成窗体类注册、窗口创建和启动消息循环的工作。
2、窗体类注册函数
每个窗体都必须有一个与其对应的窗体类,窗体类定义了窗体的属性。入口函数的第一步就是注册主窗体类,先使用类信息初始化 WNDCLASS 对象,指定窗体属性;然后将结构传入 RegisterClassEx() 函数。
-
RegisterClassEx() 函数:
ATOM MyRegisterClass(HINSTANCE hInstance)//该函数在主函数内调用,接受的实参是本程序的HINSTANCE { WNDCLASSEXW wcex; //WNDCLASSEXW是个结构体,即我们所用的窗口类 wcex.cbSize = sizeof(WNDCLASSEX); //cbSize代表窗口类结构体所占大小 wcex.style = CS_HREDRAW | CS_VREDRAW; //style窗口样式,CS_HREDRAW | CS_VREDRAW代表的是窗口在水平和竖直方向运动时,窗口画面的重绘 wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; //cbClsExtra代表窗口类后添加的字节数,常置零 wcex.cbWndExtra = 0; //cbWndExtra代表窗口句柄后添加的字节数,常置零 wcex.hInstance = hInstance; //hInstance代表本应用程序的实例 wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));//hIcon代表程序图标,MAKEINTRESOURCE将资源菜单中相应ID的项作为资源 wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);//hCursor代表鼠标图标 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);//hbrBackground代表背景色 wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);//lpszMenuName代表菜单名称 wcex.lpszClassName = szWindowClass;//lpszClassName代表窗口类的名称 wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));//hIconSm代表窗口的小图标 return RegisterClassExW(&wcex);//使用RegisterClassExW注册窗口类,该函数是一个Windows API函数,是微软提供Windows应用程序开发者的接口函数 }
3、使用 CreateWindow 创建窗口
-
CreateWindow() 函数用于创建已经注册了的窗体类的窗口;第一参数是注册窗口类的名称,其余参数指定窗口的其他属性;调用完函数后使用 if 判断是否创建成功,是则使用 ShowWindow() 函数显示窗口。
// 函数: InitInstance(HINSTANCE, int) // // 目标: 保存实例句柄并创建主窗口 // // 注释: // // 在此函数中,我们在全局变量中保存实例句柄并 // 创建和显示主程序窗口。 // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { hInst = hInstance; // 将实例句柄存储在全局变量中 //HWND是窗口句柄的缩写,是系统管理窗口的一个标识 HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); //CreateWindow是Windows API函数,用于创建窗口句柄,该函数的参数的具体意义如下 /* szWindowClass是本文件定义的一个TCHAR类型的数组,用来储存类名 szTitle储存窗体的标题 WS_OVERLAPPEDWINDOW代表窗口的风格 第四、五个参数表示窗口左上角在屏幕中的位置,使用CW_USEDEFAULT是使x坐标用默认值,该情况下y坐标无须设置,可置0 第六、七个参数代表窗口所属横向、纵向大小,同样默认 第八个参数代表这个窗口所属的父类窗口句柄,当无需使用时该参数可设置为NULL 第九个参数代表使用的菜单句柄 第十个参数hInstance是本程序的标识,代表创建的窗口属于哪个应用程序 最后一个参数代表Windows参数 */ if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }
4、使用主消息循环响应用户输入
-
用于响应用户发出的命令。
// 主消息循环: while (GetMessage(&msg, nullptr, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); //转换消息,翻译快捷键消息,若消息按键在快捷键列表中存在,则执行快捷键相应的命令;此函数将按键消息转换成字符消息。 DispatchMessage(&msg); //调度消息, 将消息发送给相应的窗口函数。 } } /* GetMessage函数获取消息后通过TranslateAccelerator函数将消息与热键表比对,若不是热键发出的菜单指令, 则会将这些消息传递给TranslateMessage函数翻译出相应的形式之后,通过DispatchMessage函数传递给消息的处理者 */
5、主窗体函数 WinProc()
-
主窗体函数如下:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) // CALLBACK 修饰符用于指定函数使用标准函数调用转换 { switch (message) // 根据消息类型执行相应的操作 { case WM_COMMAND: // 如果是WM_COMMAND类型的 { int wmId = LOWORD(wParam); // 获取发送命令对象的ID int wmEvent = HIWORD(wParam); //获取发送的事件 // 分析菜单选择: switch (wmId) { case IDM_ABOUT: // 如果是“关于”命令,则显示关于对话框 DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: // 如果是“退出”命令,则退出窗体 DestroyWindow(hWnd); break; default: // 其他命令,则做相应处理 return DefWindowProc(hWnd, message, wParam, lParam); } } break; case WM_PAINT: // 如果是重绘命令 { PAINTSTRUCT ps; //声明绘制窗体结构变量 HDC hdc = BeginPaint(hWnd, &ps); //开始重绘 // TODO: 在此处添加使用 hdc 的任何绘图代码... RECT rt; //定义区域变量 GetClientRect(hWnd,&rt); //获取客户去范围 DrawText(hdc,szHello,strlen(szHello),&rt,DT_CENTER); //绘制欢迎文本 EndPaint(hWnd, &ps); //结束重绘 } break; case WM_DESTROY: //销毁窗体 PostQuitMessage(0); //发出退出消息 break; default: return DefWindowProc(hWnd, message, wParam, lParam); //默认情况,处理窗体消息。 } return 0; }
三、MFC基础
1、微软基础类库 MFC
微软基础类库(Microsoft Foundation Class Library,MFC)是一个编写 Windows 应用程序的框架类库。
-
MFC全局函数
全局函数 功能 AfxAbort() MFC提供的默认终止函数 AfxBeginThread() 创建新线程 AfxCheckError() 检测代码是否为错误代码 AfxCheckMemory() 检测是否发生有关内存的错误 AfxDaoInit() 初始化DAO数据库引擎 AfxDaoTerm() 终止DAO数据库引擎 AfxDbInitMoudle() 初始化MFC数据库DLL AfxDoForAllClasses() 在应用程序内存空间中,枚举所有序列化派生类 AfxDump() 调试程序时,列出对象的所有状态 AfxDumpStack() 列出当前堆栈的情况 AfxEnableControlContainer() 支持对OLE控件的支持 AfxEnableMemoryTracking() 打开内存跟踪 AfxEndThread() 结束线程 AfxFreeLibrary() 释放对DLL的引用 AfxGetApp() 获取应用程序对象 AfxGetAppName() 获取应用程序名称 AfxGetHENV() 获取当前使用的ODBC句柄 AfxGetInstanceHandle() 获取当前应用程序的实例句柄 AfxGetInternetHandleType() 获取Internet句柄类型 AfxGetMainWnd() 获取应用程序主对话框 AfxGetResourceHandle() 获取资源句柄 AfxGetStaticModuleState() 获取静态模块状态 AfxGetThread() 获取当前执行的线程 AfxInitExtensionModule() 初始化DLL AfxInitRichEdit() 初始化应用程序的编辑框 AfxIsMemoryBlock() 判断指定的内存块是否是有效的内存空间 AfxIsValidAddress() 判断是否是有效的内存地址 AfxIsValidString() 判断是否是有效的字符串 AfxLoadLibrary() 装载DLL AfxMessageBox() 调用消息对话框 AfxNetInitNodule() 初始化MFC的Socket DLL AfxOleCanExitApp() 判断OLE是否可以退出 AfxParseURL() 解析URL地址 AfxRegisterClass() 在DLL中注册对话框类 AfxRegisterWndClass() MFC自动注册几个有用的对话框类 AfxSetAllocHook() 在每次分配内存时,允许使用钩子函数 AfxSetResourceHandle() 设置资源句柄 AfxSocketInit() 初始化对Windows Socket的支持 AfxThrowDaoException() 抛出DAO异常 AfxThrowDBException() 抛出CDBException类型异常 AfxThrowFileException() 抛出文件异常 AfxThrowInternetException() 抛出网络异常 AfxThrowMemoryException() 抛出内存异常 AfxNotSupportException() 抛出不支持异常 AfxThrowOleDispatchException() 抛出OLE调度异常 AfxThrowOleException() 抛出OLE异常 AfxThrowResourceException() 抛出资源异常 AfxThrowUserException() 抛出用户异常 AfxWinInit() 初始对话框应用程序
2、MFC应用程序框架分析
MFC 中主应用程序类封装了初始化、运行和终止 Windows 应用程序的功能。
-
初始化应用程序的 Initstance()函数
BOOL CMFCApplication1App::InitInstance() { AfxEnableControlContainer(); #ifdef _AFXDLL //判断是否定义了AFXDLL Enable3dControls(); #else Enable3dControlsStatic(); #endif //设置注册表键 SetRegistryKey(_T("应用程序向导生成的本地应用程序")); LoadStdProfileSettings(4); // 加载标准 INI 文件选项(包括 MRU),即加载标准配置设置 // 注册应用程序的文档模板。 文档模板 // 将用作文档、框架窗口和视图之间的连接 CMultiDocTemplate* pDocTemplate; //定义多文档变量 pDocTemplate = new CMultiDocTemplate(IDR_MFCApplication1TYPE, RUNTIME_CLASS(CMFCApplication1Doc), RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 RUNTIME_CLASS(CMFCApplication1View)); if (!pDocTemplate) return FALSE; AddDocTemplate(pDocTemplate); //增加到文档模板集合 // 创建主 MDI 框架窗口 CMainFrame* pMainFrame = new CMainFrame; //定义 CMainFrame 类 //装载框架 if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME)) { delete pMainFrame; return FALSE; } m_pMainWnd = pMainFrame; //为主窗口类赋值 // 分析标准 shell 命令、DDE、打开文件操作的命令行 CCommandLineInfo cmdInfo; //定义命令行信息变量 ParseCommandLine(cmdInfo); //解析命令行 // 调度在命令行中指定的命令。 如果 // 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。 //处理命令 if (!ProcessShellCommand(cmdInfo)) return FALSE; // 主窗口已初始化,因此显示它并对其进行更新 pMainFrame->ShowWindow(m_nCmdShow); //显示主窗口 pMainFrame->UpdateWindow(); //刷新主窗口 return TRUE; }
3、框架程序的运行核心 Run() 函数
Run() 函数通过消息循环,检查消息队列中的有效消息。如果消息有效,Run 函数会根据消息类型采取不同的处理方式。如果没有消息可用,Run 函数则会调用 OnIdle()函数完成空闲时程序或框架需要执行的操作。当应用程序结束时,Run 函数会调用 ExitInstance()函数。
-
Run 函数的封装情况如下:
int CWinThread::Run() { ASSERT_VALID(this); _AFX_THREAD_STATE* pState = AfxGetThreadState(); // for tracking the idle time state BOOL bIdle = TRUE; LONG lIdleCount = 0; // acquire and dispatch messages until a WM_QUIT message is received. for (;;) { // phase1: check to see if we can do idle work while (bIdle && !::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE)) { // call OnIdle while in bIdle state if (!OnIdle(lIdleCount++)) bIdle = FALSE; // assume "no idle" state } // phase2: pump messages while available do { // pump message, but quit on WM_QUIT if (!PumpMessage()) return ExitInstance(); // reset "no idle" state after pumping "normal" message //if (IsIdleMessage(&m_msgCur)) if (IsIdleMessage(&(pState->m_msgCur))) { bIdle = TRUE; lIdleCount = 0; } } while (::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE)); } }
4、MFC的消息映射
在 Windows 系统中,消息一般由从CWnd 派生而来的对象处理,包括 CFrameWnd、CMDIFrameWnd、CMDIChildWnd、CView、CDialog 和其他从这些类派生而来的对象。
-
VC 中使用消息映射需要执行以下几个个步骤
(1)在头文件中使用 DECLARE_MESSAGE_MAP 宏声明消息映射,放在类声明结束部分。
(2)在源文件中,使用 BEGIN_MESSAGE_MAP 宏和 END_MESSAGE_MAP 宏定义消息映射,消息映射必须定义在函数和类定义外的地方。
(3)在头文件中使用 AFX_MSG 宏声明消息函数。
(4)在源文件中重载或新定义消息函数的实现代码。
4.1、标准 Windows 消息
-
为简化工作,Windows 系统提供了标准 Windows 消息,一般由对话框类和视图类根据参数进行处理。这些标准 Windows 消息以 WM 开头的消息 ID 和对应的宏,格式是 ON_WM_xxx ;标准 Windows 消息对应的处理函数名根据消息宏派生而来,格式 OnXxx;消息处理函数的参数顺序依次是 wParam 和 IParam。
// .h文件中 class CMainFrame : public CMDIFrameWndEx { afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); DECLARE_MESSAGE_MAP() }; //.cpp 文件中 BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWndEx) ON_WM_CREATE() END_MESSAGE_MAP()
4.2、触发菜单/快捷键产生的命令消息
-
命令消息是用户触发菜单或者快捷键时发出的消息。使用 ON_COMMAND 宏可以在消息映射表中指定命令消息的处理函数;使用 ON_UPDATE_COMMAND_UI 宏可以在消息映射表中指定命令更新消息对应的处理函数。宏的第一个参数是命令 ID ,第二个参数是命令消息的处理函数。命令处理函数没有参数和返回值,命令更新处理函数只有一个 CCmdUI 类型的参数并且没有返回值。定义方式如下:
ON_COMMAND(id,memberFxn) //命令消息宏 ON_UPDATE_COMMANDA_UI(id,memberFxn) //命令更新消息定义 例: // .h文件中 #define ID_MYCMD 100 //自定义消息值 afx_msg void OnMyCommand(); //消息处理函数 afx_msg void OnUpdateMyCommand(CCmdUI* pCmdUI); DECLARE_MESSAGE_MAP() // .cpp 文件中 BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWndEx) ON_COMMAND(ID_MYCMD, OnMyCommand) //消息映射 ON_UPDATE_COMMAND_UI(ID_MYCMD, OnUpdateMyCommand) END_MESSAGE_MAP() void CMainFrame::OnMyCommand() //消息函数实现 { system("pass"); } void CMainFrame::OnUpdateMyCommand(CCmdUI* pCmdUI) { system("pass"); }
4.3、使用 ON_MESSAGE 宏自定义消息
-
使用 ON_MESSAGE 宏可以在消息映射表中指定消息对应的处理函数。
//.h #define WM_MYMESSAGE WM_USER+100 afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() //.cpp BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWndEx) ON_MESSAGE(WM_MYMESSAGE,OnMyMessage) END_MESSAGE_MAP() *CWnd* pWnd = new CWnd; pWnd->SendMessage(WM_MYMESSAGE);
- 用户自定义的消息 ID 值的范围在 WM_USER~0x7fff.
4.4、注册系统消息
-
要在系统中定义一个独立于窗口的唯一的消息处理,可以使用 Windows 注册消息,使用 RegisterWindowMessage()函数可以创建在系统中唯一的消息 ID, 使用 ON_REGISTERED_MESSAGE 宏可以在消息映射表中指定 Windows 注册消息对应的处理函数,宏的参数为使用 RegisterWindowMessage()函数返回的UINT 类型的消息 ID 。
//.h afx_msg LRESULT OnParse(WPARAM wParam, LPARAM lParam); //.cpp //注册 Windows 消息 static UINT NEAR WM_PARSE = RegisterWindowMessage((LPCWSTR)"COMMDLG_PARSE"); BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWndEx) ON_REGISTERED_MESSAGE(WM_PARSE,OnParse) END_MESSAGE_MAP() LRESULT CMainFrame::OnParse(WPARAM wParam, LPARAM lParam) { system("pass"); return 0; }