一、回到从前:
1.1、多任务操作系统是如何设计和实现的?1.1.1、系统——多个应用程序
方案1:(系统不断读取应用程序状态)
系统通过大型循环(Loop)不断坚持么一个恶用用程序是否触发了特定的事件。
方案2:(事件驱动模型——事件/消息处理模型)
执行环境将事件转换成代表事件的消息,然后发送给对应的应用程序。
//消息大概格式
TMyMessage = packed record //用来存储事件信息
Message : Longint;
wParam : Longint;
lParam : Longint;
time : Longint;
pt : TPoint;
end;
1、应用程序可能拥有多个窗口,如何把触发的消息分派给正确地窗口去处理?
2、把消息分派给应用程序或者窗口是如何办到的?
3、当分派了消息给窗口之后,窗口要如何处理此消息?
1.2.1、窗口运作模型:(问题1)
执行环境中可能有多个应用程序,一个应用程序可能建立多个窗口。如何保证执行环境的事件能分配到正确应用程序的正确窗口?
因此,我们可以在MyMessage中再加入一个代表窗口的识别值来分辨应用程序的不同窗口:
TMyMessage = packed record
hwnd : HWND;
Message : Longint;
wParam : Longint;
lParam : Longint;
time : Longint;
pt : TPoint;
end;
hwnd: HWND;//代表窗口的标识。消息中有唯一代表窗体的hWnd,通过hwnd可以准确的将消息发送到对应的窗口。
2、高速缓存(caching)执行环境为每个应用程序建立一个消息队列(Message Queue)。当事件发生时执行环境酒吧代表他的消息分派到此消息队列,等待应用程序从中取出并且处理。
如此,问题1解决。
1.2.2、执行系统、事件、消息和触发应用程序代码
1.2.2.1、执行环境需要哪些信息才能正确地为应用程序创建窗口呢?
窗口的位置和大小;
窗口的格式、使用的颜色以及使用的光标种类;
窗口使用的菜单以及其他资源;
当窗口发生事件时,能够处理窗口消息的函数地址。
执行环境提供了一个标准窗口的类别数据结构,在这个标准数据结构中即可定义上述信息的字段,应用程序只要在每个字段中填入正确地信息已表达想要创建的窗口格式即可。
MyWindowClassInfo = packed record
style: UINT; //代表窗口的格式
iWidth: Integer;
iHeight: Integer;
lpfnWndProc: Pointer; //处理窗口消息的函数的地址
hIcon: HICON;
hCursor: HCURSOR;
hbrBackground: HBRUSH;
lpszMenuName: PAnsiChar;
lpszClassName: PAnsiChar; //窗口的类名称
hIconSm: HICON;
end;
应用程序为每个窗体向系统提供MyWindowsClassInfo数据结构,再分别向执行环境注册每一个窗体的MyWindowsClassInfo信息。当创建特定窗口种类时,只需要提供要创建窗口的类名称,执行环境再根据窗口的类名称找到相应的MyWindowsClassInfo数据结构,最后根据MyWindowsClassInfo中的信息来创建窗口。
解决了执行环境如何创建窗口的问题后,那么,窗体如何来处理执行环境发生过来的事件消息?
让执行环境调用窗口提供的函数并且传递消息给此函数来处理,在上面部分MyWindowClassInfo的lpfnWndProc字段就指向了处理该窗口消息的函数(回调函数)。执行环境通过调用该函数,将消息分派给指定的窗体。
执行环境需定义回调函数的原型:
function WindowProced(Window: HWnd; AMessage: UINT; WParam: WPARAM; LParam: LPARAM):LRESULT; stdcall; export;
在上面的回调函数原型中参数Window、WPrarm和LPrarm字段,代表发生事件的窗口ID以及窗口消息的辅助信息等。而参数AMessage则代表事件的窗口消息值。因此当英语程序遵照回调函数的原型提供了适当的处理窗口消息的函数并且指定给MyWindowClassInfo中的lpfnWndProc字段,那么应用程序就可以等待这个回调函数在窗口事件发生后自动由执行环境调用了。(至此,问题2解决)
最后,当执行环境调用了回调函数后,其中的AMessage就代表了发生事件的ID,因此在回调函数中就可以通过判断AMessage的数值来得知发生的时间,再格恩局不同的时间使用相应的程序代码来处理:
case AMessage of
WM_PAINT://重画窗体的消息
...
WM_DESRROY://窗体消灭的消息
...
end;
由于应用程序要判断回调函数中发生的消息ID值,因此执行环境当然必须为触发时间定义代表消息ID及每个消息的意义了:
unti Messages;
...
const
WM_NULL = $0000;
WM_Create = $0001;
WM_DESTROY = $0002;
WM_MOVE = $0003;
...
WM_PAINT = $000F;
...
应用程序只需要uses这个消息单元就可以根据其中的消息ID值来判断AMessage代表的事件了。(至此,问题3解决)现在我们几乎已经完整地定义了执行环境如何使用 事件/消息/回调函数 的机制在多任务环境中提供应用程序处理事件的机制。首先,应用程序经由填入MyWindowClassInfo数据结构来注册窗口信息,之后应用程序在调用创建窗口并且提供窗口回调函数。
当应用程序欲创建窗口时只要提供窗口类名称,执行环境便会在所有已经注册的窗口类中搜寻MyWindowClassInfo中lpszClassName字段值与提供的窗口类名称一致的MyWindowClassInfo数据结构。找到之后根据其中的窗口特性值来创建按窗口。
当执行环境产生消息后,会根据窗口的ID值,即MyMessage中的hwnd字段,找到此窗口的MyWindowClassInfo数据结构,再调用其中的回调函数字段lpfnWndProc。如此,执行环境便可以正确调用到应用程序提供的窗口消息处理函数了。