什么是窗口
想要深入了解Windows机制就必须从我们随处可见的Window(窗口)说起。窗口就是我们在使用图形程序时,屏幕上显示的矩形区域。不同的窗口可能包含一些相同组成部分,如:窗口外沿,就是我们用于拖拽窗口移动位置的外边框。标题栏,也就是窗口程序最上方显示标题的地方,一般用于显示应用程序名字。一下图片是窗口的组成部分,各个组成部分都有自己的行为特征。大部分窗口都和以下图片类似,但并不是每个窗口都有标题栏,状态栏等,举个典型的例子,瑞星杀毒软件的小狮子就是一个窗口。
窗口的运作方式
首先窗口程序是由事件驱动的,用户可能随时发出各种消息,如:操作过程中绝得窗口不够大了,马上拖动边框,程序就必须马上调整客户区的内容以适应新窗口大小;用户可能突然想要做其他事情,可能随时会把窗口最小化甚至关闭,窗口程序也必须立即响应缩小和退出的消息请求。如果窗口没有接收特定的请求,它们就会一动不动的停在桌面上什么也不做。
它与过程驱动方式不同,顺序驱动方式只是单纯的按照代码逻辑一行一行处理数据,当第一遍流程处理完毕后就直接退出程序了,无法再次重头处理一遍,这就是事件驱动和顺序驱动的区别。
说白了就是窗口程序循环接受用户或者系统其他程序发来的消息持续处理,接收到退出事件才会退出;而顺序驱动的程序只处理一遍代码逻辑直接退出了。
以下是实现一个窗口的完整过程:
创建一个或多个窗口回调函数,因为一个Win32程序有可能不止一个窗口,而每个窗口必须有一个回调函数,所以一个win32程序可能有多个窗口回调。
创建一个WNDCLASSEX窗口对象填写对应窗口的信息,并将窗口类通过RegisterClassEx()函数注册进入Windows消息机制中。注册后需要调用显示创建的窗口调用ShowWindow()和UpdataWindow()函数显示窗口在屏幕上。接下来需要调用user32.dll的GetMessage()函数,从对应程序的消息队列中获取对应程序的消息。然后调用user32.dll中的DispatchMessage()函数将消息分发给正确的处理该消息的窗口即可。
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; FirstWindow.asm
; 窗口程序的模板代码
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff FirstWindow.asm
; Link /subsystem:windows FirstWindow.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include gdi32.inc
includelib gdi32.lib
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?
.const
szClassName db 'MyClass',0
szCaptionMain db 'My first Window !',0
szText db 'Win32 Assembly, Simple and powerful !',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 窗口过程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcWinMain proc uses ebx edi esi hWnd,uMsg,wParam,lParam
local @stPs:PAINTSTRUCT
local @stRect:RECT
local @hDc
mov eax,uMsg
;********************************************************************
.if eax == WM_PAINT
invoke BeginPaint,hWnd,addr @stPs
mov @hDc,eax
invoke GetClientRect,hWnd,addr @stRect
invoke DrawText,@hDc,addr szText,-1,\
addr @stRect,\
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd,addr @stPs
;********************************************************************
.elseif eax == WM_CLOSE
invoke DestroyWindow,hWinMain
invoke PostQuitMessage,NULL
;********************************************************************
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
;********************************************************************
xor eax,eax
ret
_ProcWinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_WinMain proc
local @stWndClass:WNDCLASSEX
local @stMsg:MSG
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass
;********************************************************************
; 注册窗口类
;********************************************************************
invoke LoadCursor,0,IDC_ARROW
mov @stWndClass.hCursor,eax
push hInstance
pop @stWndClass.hInstance
mov @stWndClass.cbSize,sizeof WNDCLASSEX
mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW
mov @stWndClass.lpfnWndProc,offset _ProcWinMain
mov @stWndClass.hbrBackground,COLOR_WINDOW + 1
mov @stWndClass.lpszClassName,offset szClassName
invoke RegisterClassEx,addr @stWndClass
;********************************************************************
; 建立并显示窗口
;********************************************************************
invoke CreateWindowEx,WS_EX_CLIENTEDGE,offset szClassName,offset szCaptionMain,\
WS_OVERLAPPEDWINDOW,\
100,100,600,400,\
NULL,NULL,hInstance,NULL
mov hWinMain,eax
invoke ShowWindow,hWinMain,SW_SHOWNORMAL
invoke UpdateWindow,hWinMain
;********************************************************************
; 消息循环
;********************************************************************
.while TRUE
invoke GetMessage,addr @stMsg,NULL,0,0
.break .if eax == 0
invoke TranslateMessage,addr @stMsg
invoke DispatchMessage,addr @stMsg
.endw
ret
_WinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
call _WinMain
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
程序显示结果
Windows的消息驱动机制
在这里详细描述完整的Windows消息驱动机制。看下图
Windows下的窗口程序不止1个,往往有很多程序在同时运行。当用户点击其中一个程序的窗口或者进行其他操作时,Windows系统对应被点击程序就会判断点击的坐标位置是否是当前程序的窗口坐标或者窗口范围内。如果是,则该程序就会产生一个消息结构体,记录下消息产生的原因送入Windows系统的总消息队列里。然后系统再根据产生的消息分配到每个应用程序的消息队列中。(从总的消息队列中再次分类,将消息挨个“落户”到每个程序对应的消息队列中)。当应用程序调用GetMessage()函数时,系统会从调用GetMessage()函数中的应用程序对应消息队列中取出一条消息交给应用程序,得到消息后还需要知道这条消息具体应该交给该应用程下的哪个窗口去处理,这时候就再次调用DespatchMessage()函数,将消息交给对应窗口的消息回调函数,执行该窗口的条件分支来处理这个消息。
程序之间是允许相互传递消息的,PostMessage()函数允许一个程序将消息发送到其他程序的消息队列中。SendMessage()更加极端,直接将消息交给其他应用程序的具体某个窗口,让该窗口的窗口回调消息直接处理要发送的消息。