当前位置: 首页 > 科技观察

网络安全编程:Windows消息机制实例

时间:2023-03-20 01:15:30 科技观察

SendMessage()向指定窗口发送指定消息,窗口接收消息并发生相应行为。那么窗口收到消息后的一系列行为是如何发生的呢?让我们通过熟悉Windows消息机制来了解消息处理背后的秘密。01DOS程序与Windows程序执行过程比较Windows下的窗口应用程序是基于消息机制的,操作系统与应用程序、应用程序与应用程序之间的通信与交互大多是通过消息机制。要真正掌握Windows应用程序内部如何处理消息,必须分析实际的源代码。在编写基于消息的Windows应用程序之前,让我们比较一下DOS程序和Windows程序的执行流程。一、DOS程序执行流程编写的程序是在DOS下执行的,执行过程中有比较清晰的流程。例如,用C语言编写程序后,程序执行的大致流程如图1所示。图1传统DOS程序的执行流程从图1可以看出,DOS程序的流程是按照代码顺序(这里的顺序不是指程序控制结构中的顺序、分支和循环,而是指程序运行逻辑有明显的流程),流程是按顺序执行的。大致步骤是:DOS程序从main()主函数开始执行(其实程序真正的入口并不是main()函数);在执行过程中,按照代码编写过程依次调用各个子程序;在执行过程中,会等待用户输入等操作;当每个子程序执行完毕,最终都会返回到main()主函数,执行完main()主函数的return语句后,程序退出(其实程序真正的退出并不是main()函数语句的返回)。2、Windows程序执行流程DOS程序的执行流程比较简单,而Windows应用程序的执行流程则比较复杂。DOS是一个单任务操作系统。在DOS中,通过输入命令,DOS操作系统会将控制权从Command.com转移到DOS程序中执行。然而,Windows是一个多任务操作系统。在Windows下,可以同时运行多个应用程序,因此Windows不能将控制权完全交给一个应用程序。应用程序如何在Windows下运行?首先看Windows应用程序的总体结构图,如图2所示。图2Windows应用程序执行示意图图2看似复杂,但实际上Windows应用程序的内部结构比这张示意图要复杂。在实际开发Windows应用程序时,需要注意的主要部分是“主程序”和“窗口进程”。但是从图2来看,主程序和窗口过程之间并没有直接的调用关系,而是在主程序和窗口过程之间有一个“系统程序模块”。“主程序”的作用是注册窗口类、获取消息和分发消息。“窗口过程”定义了需要处理的消息,“窗口过程”会根据不同的消息执行不同的动作,不需要程序处理的消息会交给默认的系统处理过程。在“主程序”中,RegisterClassEx()函数会注册一个窗口类,窗口类中的字段包含“窗口过程”的地址信息,即“窗口类”的信息(包括“窗口过程”的地址信息))告诉操作系统。然后“主程序”通过调用GetMessage()函数不断获取消息,再通过DispatchMessge()函数分发消息。消息分发后,并不是直接调用“窗口过程”处理消息,而是系统模块查找窗口指定的窗口类,通过窗口类找到窗口过程的地址,最后发送给窗口过程的消息。进程处理消息。02一个简单的Windows应用程序比一个简单的DOS程序要长得多。下面的例子只实现了一个特别简单的Windows程序。该程序在桌面上显示一个简单的窗口。它没有菜单栏、工具栏或状态栏。它只是在窗口中输出一个简单的字符串。程序虽然如此简单,但编写起来大约需要100行代码。考虑到初学者的朋友,这里会逐步介绍代码的细节,减少代码的篇幅,方便初学者的学习。一、Windows窗口应用程序的主要函数——WinMain()在DOS时代,或者在Windows下编写命令行程序,用C语言编写代码时,都是从main()函数开始的。在Windows下编写带窗口的程序时,如果要用C语言编写窗口程序,不再从main()函数入手,取而代之的是WinMain()函数。由于Windows应用程序的主要函数是WinMain(),所以从了解WinMain()函数的定义开始学习Windows应用程序的开发。WinMain()函数的定义如下:intWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow);该函数的定义取自MSDN。看到WinMain()函数的定义后,直观的发现WinMain函数的参数比()函数的参数发生了变化。在参数数量方面,WinMain()函数接收到的信息更多。我们来看看每个参数的含义。hInstance是应用程序的实例句柄。保存在磁盘上的程序文件是静态的。当加载到内存中时,在分配了进程所需的CPU、内存等资源后,一个静态程序被实例化为一个拥有各种执行资源的进程。向上。句柄的概念因上下文而异,句柄是操作某种资源的“句柄”。当需要对实例化进程进行操作时,需要使用实例句柄进行操作。这里的实例句柄是程序加载到内存后的起始地址。实例句柄的值也可以通过GetModuleHandle()参数获取(注意系统中没有GetInstanceHandle()函数,不要误以为hInstance会有GetInstance×××()类函数)。在开发Windows程序时,句柄这个词是一个非常常用的词。术语“句柄”的含义因上下文而异。例如,磁盘上的程序文件加载到内存后,会创建一个实例句柄。这个实例句柄就是程序加载到内存后的“起始地址”,或者说是“模块的起始地址”。以SendMessage()函数为例,句柄相当于一个操作面板,发送给句柄的消息相当于面板上的各个开关按钮,消息的附加数据相当于发送给操作面板的各种参数开关按钮,这些参数根据Keys而有所不同。hPrevInstance是由同一文件创建的前一个实例的实例句柄。该参数在Win16平台下是遗留的,在Win32下不再使用。lpCmdLine是main函数的一个参数,用来在程序启动时给进程传递参数。例如输入“notepadc:\boot。C:\Boot.ini文件是通过WinMain()函数的lpCmdLine参数传递给notepad.exe程序的。nCmdShow是显示进程的方式,可以是最大化、最小化、隐藏(如果是启动木马程序,启动方式当然要自己掌握),main函数的参数介绍,写一个Windows窗口程序,应该完成哪些操作2.WinMain()函数中的过程是编写Windows下的窗口程序WinMain()主函数中的主要任务是注册一个窗口类,创建一个窗口并显示窗口创建窗口,然后不断获取自己的消息,分发给自己的窗口过程,直到收到WM_QUIT消息,退出消息循环结束进程,这就是程序在main函数中的执行上下文,在程序中,操作of注册窗口类和创建窗口被封装为自定义函数。代码如下:intWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow){MSGMsg;BOOLbRet;//注册窗口类MyRegisterClass(hInstance);//创建窗口和显示窗口if(!InitInstance(hInstance,SW_SHOWNORMAL)){returnFALSE;}//消息循环//获取自己的消息并分发while((bRet=GetMessage(&Msg,NULL,0,0))!=0){if(bRet==-1){//handletheerrorandpossiblyexitbreak;}else{TranslateMessage(&Msg);DispatchMessage(&Msg);}}returnMsg.wParam;}代码中MyRegisterClass()和InitInstance()是两个自定义函数,分别用来注册窗口类,创建窗口,显示窗口创建更新窗口。消息循环的后半部分用于获取消息并进行分发。其流程如图2的“主程序”部分所示,代码中主要有三个函数,分别是GetMessage()、TranslateMessage()和DispatchMessage()。这3个函数是Windows提供的API函数。GetMessage()的定义如下:BOOLGetMessage(LPMSGlpMsg,HWNDhWnd,UINTwMsgFilterMin,UINTwMsgFilterMax);该函数用于获取自己的消息,填充MSG结构。与GetMessage()类似的一个函数是PeekMessage(),它可以判断消息队列中是否有消息。如果没有消息,它可以主动让出CPU时间给其他进程。PeekMessage()函数的使用请参考MSDN:BOOLTranslateMessage(CONSTMSG*lpMsg);该函数用于处理键盘消息。它将虚拟代码消息转换为字符消息,即将WM_KEYDOWN消息和WM_KEYUP消息转换为WM_CHAR消息,将WM_SYSKEYDOWN消息和WM_SYSKEYUP消息转换为WM_SYSCHAR消息:LRESULTDispatchMessage(CONSTMSG*lpmsg);该函数将消息分发到窗口过程中。3、注册窗口类的自定义函数在WinMain()函数中,首先调用自定义函数MyRegisterClass(),需要将进程的实例句柄hInstance作为参数传入。该函数完成窗口类的注册,分为两步:第一步填充WNDCLASSEX结构,第二步调用RegisterClassEx()函数进行注册。这个函数比较简单,但是这个函数稍微复杂一点的是WNDCLASSEX结构体中有很多成员。代码如下:ATOMMyRegisterClass(HINSTANCEhInstance){WNDCLASSEXWndCls;//用0ZeroMemory(&W??ndCls,sizeof(WNDCLASSEX));//cbSize为结构的大小WndCls.cbSize=sizeof(WNDCLASSEX);//lpfnWndProc为窗口过程地址WndCls.lpfnWndProc=WindowProc;//hInstance为实例句柄WndCls.hInstance=hInstance;//lpszClassName为窗口类名WndCls.lpszClassName=CLASSNAME;//style为窗口类样式WndCls.style=CS_HREDRAW|CS_VREDRAW;//hbrBackground为Window类背景色WndCls.hbrBackground=(HBRUSH)COLOR_WINDOWFRAME+1;//hCursor为鼠标句柄WndCls.hCursor=LoadCursor(NULL,IDC_ARROW);//hIcon为图标句柄WndCls.hIcon=LoadIcon(NULL,IDI_QUESTION);//otherWndCls.cbClsExtra=0;WndCls.cbWndExtra=0;returnRegisterClassEx(&WndCls);}代码中引入了WNDCLASSEX结构体的成员。WNDCLASSEX中最重要的字段是lpfnWndProc,它保存着窗口过程的地址。窗口过程是处理各种消息过程的“集合地”,也是编写Windows应用程序的关键部分。代码中的函数比较简单,主要涉及LoadCursor()、LoadIcon()和RegisterClassEx()三个函数。由于这三个功能简单易用,可以通过代码理解,这里就不做过多介绍了。注册窗口类(指的是窗口类,大家有没有想到FindWindow()函数的第一个参数?)的要点就是在下面的代码中根据窗口类创建一个该类型的窗口。在代码中,如果在定义窗口类时指定了背景颜色、鼠标指针、窗口图标等,那么使用该窗口类创建的窗口都将具有相同的窗口类型。4.创建主窗口并显示更新注册的窗口类,根据窗口类创建具体的主窗口并显示更新窗口。代码如下:BOOLInitInstance(HINSTANCEhInstance,intnCmdShow){HWNDhWnd=NULL;//创建窗口口hWnd=CreateWindowEx(WS_EX_CLIENTEDGE,CLASSNAME,"MyFirstWindow",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL);if(NULL==hWnd){returnFALSE;}//显示窗口ShowWindow(hWnd,nCmdShow);//更新窗口UpdateWindow(hWnd);returnTRUE;}调用该函数时,需要将实例句柄和窗口显示模式传递给函数两个参数。这两个参数的第一个参数由WinMain()函数的参数hInstance指定,第二个参数可以由WinMain()函数的第三个参数指定,也可以自定义。程序中的调用代码如下:InitInstance(hInstance,SW_SHOWNORMAL);CreateWindowEx()函数在创建主窗口时调用,先看其函数原型:HWNDCreateWindowEx(DWORDdwExStyle,LPCTSTRlpClassName,LPCTSTRlpWindowName,DWORDdwStyle,intx,inty,intnWidth,intnHeight,HWNDhWndParent,HMENUhMenu,HINSTANCEhInstance,LPVOIDlpParam);CreateWindowEx()中的第二个参数是lpClassName,从注释中可以知道是注册的类名。注册的类名是WNDCLASSEX结构的lpszClassName字段。5、处理消息的窗口流程如图2所示流程,WinMain()的main函数部分已经完成。接下来看程序的关键部分——窗口过程。从WinMain()主函数可以看出,WinMain()主函数中没有直接调用窗口过程的地方,而是在注册窗口类时指定了窗口过程的地址。那么谁在调用窗口类呢?答案由操作系统调用。有两个原因。首先,窗口过程的地址由系统维护。在注册窗口类时,“窗口过程的地址”是向操作系统注册的。其次,除了应用程序本身调用自己的窗口过程外,其他应用程序也会调用自己的窗口过程。例如,在前面的例子中,调用SendMessage()函数发送消息后,系统需要调用目标程序的窗口过程来完成相应的过程。动作。如果窗口过程是自己调用的,那么窗口就得自己维护窗口类的信息,进程间的消息通信会非常繁琐,无形中也会增加系统的开销。窗口过程的代码如下:&ps);GetClientRect(hwnd,&rt);DrawTextA(hDC,pszDrawText,strlen(pszDrawText),&rt,DT_CENTER|DT_VCENTER|DT_SINGLELINE);EndPaint(hwnd,&ps);break;}caseWM_CLOSE:{if(IDYES==MessageBox(hwnd,"是否退出程序","MyFirstWin",MB_YESNO)){DestroyWindow(hwnd);PostQuitMessage(0);}break;}default:{returnDefWindowProc(hwnd,uMsg,wParam,lParam);}}return0;}inWinMain()函数,调用RegisterClassEx()函数注册窗口类,调用CreateWindowEx()函数创建窗口,GetMessage()函数不断获取消息,但创建的窗口在main中没有处理功能。那是因为真正对窗口行为的处理都放在了窗口进程中。当WinMain()函数中的消息循环拿到消息后,通过调用DispatchMessage()函数将消息派发给窗口过程(实际上不是DispatchMessage()函数直接派发),让窗口过程处理消息.窗口过程的定义是按照MSDN上给出的表格来定义的。MSDN上的定义形式如下:LRESULTCALLBACKWindowProc(HWNDhwnd,UINTuMsg,WPARAMwParam,LPARAMlParam);WindowProc是窗口过程的函数名,可以随意更改,但是窗口过程的函数名必须和WNDCLASSEX结构中的lpfnWndProc成员变量的值保持一致。函数的第一个参数hwnd是窗口的句柄,第二个参数uMsg是消息值,第三和第四个参数是消息值的附加参数。这四个参数的类型对应于SendMessage()函数的参数。上面的WindowProc()窗口进程只处理了两条消息,即WM_PAINT和WM_CLOSE。为了演示,这里只简单处理了两条消息。Windows中有成千上万的消息,很多消息是程序员自己无法处理的。程序员只处理部分程序中需要的消息,其余的消息交给DefWindowProc()函数处理。DefWindowProc()函数实际上是将消息传递给操作系统,操作系统处理程序中未处理的消息。例如调用CreateWindow()函数时,系统会向窗口过程发送消息WM_CREATE,但是这个消息可能不需要程序的函数进行特殊处理,所以直接交给DefWindowProc()函数进行处理系统来处理。DefWindowProc()函数的定义如下:LRESULTDefWindowProc(HWNDhWnd,UINTMsg,WPARAMwParam,LPARAMlParam);该函数的四个参数与窗口过程的参数相同,只要将窗口过程的参数依次传递给DefWindowProc()函数即可完成函数调用。直接在switch分支结构的默认位置调用DefWindowProc()函数即可。WM_CLOSE消息是窗口关闭时发送的消息。在这个消息中,需要调用DestoryWindow()函数销毁窗口,调用PostQuitMessage()退出消息循环,从而使程序退出。对于WM_PAINT消息,这里不再介绍,涉及到的几个API函数可以参考MSDN了解。有些资料在介绍消息循环的时候会给出一个建议,就是把需要经常处理的消息放在程序的上部,把不经常处理的消息放在程序的下部,所以以提高程序的效率。其实在window进程中,switch结构经常用来判断消息(如果用if和else结构来判断消息,那么常用的消息应该放在前面),switch结构会是编译器编译后优化。处理,从而大大提高程序的运行效率。