Windows CE 使用者接口与图形子系统.docx
- 文档编号:6555926
- 上传时间:2023-01-07
- 格式:DOCX
- 页数:28
- 大小:221.72KB
Windows CE 使用者接口与图形子系统.docx
《Windows CE 使用者接口与图形子系统.docx》由会员分享,可在线阅读,更多相关《Windows CE 使用者接口与图形子系统.docx(28页珍藏版)》请在冰豆网上搜索。
WindowsCE使用者接口与图形子系统
第七章使用者接口与图形子系统
与MicrosoftXP操作系统不同,WindowsCE将Win32API的使用者界面(User32)和图形设备接口(GDI32)合并成一个新的模块gwes.exe,称为GWE子系统。
GWE是一个缩写词,其中G代表Graphics(图形),W代表WindowManager(窗口管理器),E代表EventManager(事件管理器)。
GWE子系统是使用者、应用程序和操作系统之间的图形使用者接口。
GWE支援组成WindowsCE图形使用者接口的所有窗口、对话框、控件、菜单和资源,使使用者能够通过执行菜单命令、单击按钮等操作来控制应用程序。
GWE还以位图、光标、文字以及图标等形式为使用者提供信息。
即使不具备图形使用者接口的基于WindowsCE的平台也使用了GWE的基本窗口和讯息功能,这些功能提供了在使用者、应用程序和操作系统之间进行通讯的方法。
本章主要分析GWE子系统的体系结构以及相关的实作程序代码,主要涉及的源程序位于WindowsCE.NET原始程序代码树中的[CEROOT]\Private\Winceos\Coreos\GWE目录下。
需要说明的是,受MicrosoftSharedSourceLicense的限制,GWE子系统中只有GDI部分公开了少量的原始程序代码,User部分的原始程序代码均未公布,因此本章内容主要着重于GWE子系统体系结构的分析。
7.1GWE概述
对应于桌面Windows操作系统中的User32,WindowsCEGWE子系统中的USER部分包含了使用者输入系统(UserInputSystem)、事件管理器(EventManager)和窗口管理器(WindowManager)三个组件。
其中使用者输入系统接收来自键盘、鼠标和手写笔等设备的讯息,事件管理器管理讯息和讯息队列,而窗口管理器将讯息响应发送到对应的窗口以实作特定的显示。
GWE子系统中的GDI(GraphicsDeviceInterface,图形设备接口)部分依靠二维图形包中的API函式将使用者的绘图操作通过直线、曲线、填充区域、位图和文字等GDI基本操作来实作,此外GDI还支持点阵字体和TrueType字体。
基于WindowsCE的程序设计要通过讯息循环。
讯息循环是在Windows应用程序中的一种循环,它负责接收系统传送过来的讯息,并且把它们发送到相对应的窗口,直到系统表明所有的讯息都发送完毕,讯息循环才结束。
它包含在WinMain函式中。
处理讯息循环的函式是WndProc。
下面是GWE的一些特殊功能:
GWE实时追踪执行系统的工作情况,所以如果没有在三分钟内给设备一些指令的话,GWE将关闭这个设备。
GWE还添加了储存空间不足时的提示和解决方案。
虽然在技术实作上储存空间不够的提示和解决没有必要一定成为GWE的一部分,但是在这里加上一些程序代码实作这种功能是最方便的。
因为GWE和输入相关。
如果储存空间不足,将出现内存不足的对话框。
同时会弹出一个窗口,给出正在执行的程序代码,让使用者选择关掉其中的一个或几个。
WindowsCE添加储存空间不足时的解决程序是基于执行设备的考虑。
因为执行WindowsCE的设备一般没有硬盘,同时也没有足够的内存,一旦在执行时超过了内存的容量,将没有硬盘保存,所以必须在有可能超过储存容量的地方结束一些程序以节省空间。
当然这在执行桌面Windows操作系统的设备上不常见到。
在使用者输入系统的设计上,WindowsCE也尽量减少执行绪数。
它将键盘设备和触控设备的处理直接交给了GWE,并且将这些设备所传送的讯息直接放在全域(Global)设备队列中。
GWE在设计之初有以下的一些目标:
•在小屏幕设备上执行比较稳定:
GWE设计的时候提供了足够的函式以便使用者写出的程序代码在小屏幕上能够执行的很好。
•和Win32的兼容性:
和Win32API的兼容性是GWE设计时最重要的目标,因为这可以让那些会Windows程序设计的人能够在WindowsCE上程序设计。
•支持广泛的颜色位深度和色彩模式,如1、2、4、8、16、24和32位颜色。
这些在桌面Windows操作系统中是没有提供的。
•加上了调节低电压和对电源的管理的功能设计:
这使得系统在判断使用者没有使用设备后关掉设备。
在上述目标之中,WindowsCE最基本的设计目标是和Win32API兼容,它的大部分程序代码也没有重新写。
和桌面Windows操作系统一样,WindowsCE的窗口管理器也包括对话框管理器、Splash类别和控件等。
具体的结构如图7.1所示。
WindowsCE中的非使用者输入区和桌面Windows操作系统中的有一些区别。
它和窗口管理器结合在一起。
在WindowsCE中,一个独立的菜单列和工具列将占据太多的空间,所以WindowsCE将菜单列和工具列结合成一个新的控件,称为命令列。
以前的菜单被放在非使用者区而工具列放在使用者区,现在菜单和别的控件一样被放在命令列中,并且由命令列实作菜单控件的功能。
对话框管理器位于窗口管理器的上层。
当实作一个对话框管理器时,同时会有一个消息框产生。
消息框位于对话框管理器的上层,并且所有的控件(比如编辑框、列表框、组合框等)都位于对话框管理器的上层。
菜单与以前有一些不同,现在也作为控件处理。
GWE还包括内存不足(OOM,outofmemory)对话框和内存不足句柄。
内存不足对话框在使用者内存空间不够时弹出,并将正在执行的程序行出来,让使用者选择关闭其中的一个或几个。
同样,内存不足句柄也是由于储存空间比较小才被加到WindowsCE系统中去的。
图7.1WindowsCE图形子系统的结构
7.2使用者输入系统
GWE的USER部分包括讯息队列、事件管理器和使用者输入系统三个核心组件。
其中最重要的是使用者输入系统,它负责接收从键盘、鼠标以及触控板发出的讯息。
使用者输入系统将使用者从键盘、鼠标等设备输入的讯息通过讯息传送机制送到相对应的窗口中。
USER利用产生窗口和讯息传送这两部分功能来实作使用者输入系统。
使用者输入系统的结构如图7.2所示,主要组件包括:
•Msgque:
讯息队列,这是任何需要讯息传递的地方所必须的部分,因为要实作使用者输入就必须能够把输入的信息传给所需要的窗口。
•Wmbase:
这部分组件的作用是建立窗口,为窗口提供窗口处理函式WndProc,并且给它发送讯息。
•Winmgr:
窗口管理器,它负责把绘图操作的结果在屏幕上显示出来。
下面分两部分对以上内容进行详细阐述,其中将窗口的产生和管理合并成输入系统。
图7.2GWE的USER部分的主要结构
7.2.1讯息队列
讯息队列有两个功能:
它不仅负责接收讯息并将讯息发送到相对应的窗口,而且它还负责保存输入状态信息,比如光标的大小、提示符闪烁率等。
在讯息传送时,有两个最基本的函式:
SendMessage和PostMessage。
其中SendMessage函式采用的是同步讯息传送机制:
发送者发出讯息,接收者接收讯息,而发送者则等待讯息被处理完成。
讯息队列和执行绪存在一一对应的关系。
通过深入了解API函式可以发现,当把讯息传送到对应的窗口时,每一个窗口对应一个执行绪,SendMessage函式先将讯息发送到相对应的窗口,在后台可以发现和这个窗口联系的执行绪在同步的回应。
如果呼叫SendMessage函式的执行绪和窗口所在的执行绪是同一个执行绪,这次呼叫就会退化为呼叫WndProc函式的一个子程序(因为WndProc是这个窗口的预设处理函式)。
函式PostMessage的工作和SendMessage有所不同。
PostMessage采用的是异步讯息传送机制:
它仅仅将讯息封装以后送进讯息队列中,讯息的发送者继续执行,并不管讯息在什么时候被处理。
一段时间后,讯息从讯息队列中被取出,送到相对应的地方等待处理。
所以,每一个窗口都和一个与特定执行绪相关的讯息队列联系在一起,窗口成为讯息传送的目的地。
执行绪、讯息队列和窗口以及窗口处理函式紧密联系在一起,它们之间的关系是一个窗口拥有它自己的执行绪、自己的讯息队列和相对应的窗口处理函式。
在WinMain()函式中经常看见这样的讯息循环:
while(GetMessage((&msg...)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
当一个执行绪呼叫GetMessage函式时,相对应执行绪的讯息队列会发生下述变化。
在讯息处理过程中最简单的部分是已发送的讯息(将讯息放到别的执行绪的讯息队列中,但是对本执行绪来讲是发送讯息)。
在执行程序代码过程中,讯息队列中的一部分指针指向即将被响应的讯息队列,当函式GetMessage被呼叫时,它查看将被响应的讯息队列。
如果所需要的讯息在队列中,函式GetMessage将这个讯息送到特定的地方然后返回。
接着主循环将呼叫函式DispatchMessage。
函式DispatchMessage首先查看送到的讯息所包含的信息,找到和这条讯息相关的窗口,以及和窗口相关的处理程序,将讯息和相对应的参数发送给处理程序,让相对应的处理程序做出响应。
对讯息的处理就是这样,将讯息从队列中取出,送到相对应的位置等待,当轮到处理这条讯息时,找到相对应的窗口和窗口处理程序,呼叫处理程序做出讯息响应。
从讯息发送方面看,呼叫函式PostMessage作用就是将讯息封装以后送到相对应的讯息队列中去,不管讯息是通过和函式PostMessage同一个执行绪处理还是不同的执行绪处理,结果都一样,就是讯息被从队列中取出然后送到了相对应的窗口处理函式等待响应。
同样,如果讯息处理的执行绪正确执行,将讯息从队列中取出然后发送出去,从外面看不出有什么变化,如图7.3所示。
图7.3PostMessage的讯息处理流程
函式SendMessage和函式PostMessage相比有一点区别,它和讯息处理是同步的,所以当发现函式SendMessage返回时,这说明讯息已经被处理完了。
讯息有两种途径被发送到处理程序,这主要取决于讯息发送到的窗口是在函式SendMessage的执行绪还是别的执行绪。
最简单的情况是将讯息传送到和函式SendMessage在同一个执行绪的窗口。
函式SendMessage发现产生窗口的执行绪和自己所在的执行绪是同一个执行绪,就直接呼叫窗口处理程序(而不是将它放到讯息队列中),然后返回,这里的讯息处理仅仅是呼叫一个子函式。
SendMessage的处理流程如图7.4所示。
图7.4SendMessage的同执行绪讯息处理流程
复杂一点的情况是将讯息传送到属于另外一个执行绪的窗口。
此时,讯息队列有一个子队列负责处理传送到特定窗口和执行绪的讯息。
函式SendMessage发现这个讯息将要传送到另一个执行绪的讯息队列,就将它封装然后放到这个执行绪的讯息队列中去,然后等待。
所以现在就有一个正在呼叫的执行绪等待一个内部Win32事件对象的响应。
这时,执行绪的等待并不影响它接收讯息,执行绪可以接收讯息并且对它做出处理。
这里的关键点是当讯息传送到属于别的执行绪的窗口时,实际上是窗口所属的主执行绪在执行程序代码。
发送讯息的执行绪呼叫函式SendMessage,但是是别的执行绪的WndProc函式在接收讯息。
如图7.5所示。
说的更详细一点,当送出讯息的执行绪被挂起(Hook)等待响应时,它还必须实时检查讯息是否被送回来。
接收到送出执行绪送出讯息的窗口可以立即回发一个讯息,所以当执行绪被挂起等待响应时,它依然可以接收送过来的讯息。
在发送讯息的区别上,除了函式SendMessage发出讯息后必须等待讯息被处理而函式PostMessage不需要外,函式SendMessage还被函式GetMessage所操作。
应用程序的主循环从不检查函式GetMessage的呼叫是否返回。
函式GetMessage执行时仅仅按优先级处理讯息,并将讯息分别发送到相对应的地方。
表7.1列举了一些讯息的等级。
图7.5SendMessage的异执行绪讯息处理流程
表7.1讯息的级别
级别
讯息类型
1
发送讯息—呼叫SendMessage发送的讯息,有最高级的优先级
2
发送讯息—呼叫PostMessage发送的讯息,有次高级的优先级
3
WM_QUIT讯息
4
WM_PAINT讯息
5
WM_TIMER讯息
所以,如果一个窗口或者一个执行绪被挂起,最常见的原因是一些执行绪在等待Win32事件对象的响应而不是在自己的讯息循环中。
同时如果这个执行绪被挂起,其中发送讯息的程序一样被挂起等待响应的返回。
那样将会呼叫MsgWaitForMultipleObjects,它是一个API函式,作用是等待一个Win32事件对象,同时如果有讯息送来将立即处理。
这种方式将是最好的混合模式,因为它既可以等待Win32事件对象,还能处理讯息。
如果不用混合模式,那么必须让执行绪挂起等待Win32事件对象或者让执行绪仅仅处理讯息。
下面是本小节最重要的几点:
•函式SendMessage和讯息处理是同步的。
•函式PostMessage不等待讯息被处理,和讯息处理是不同步的。
•所有的讯息,从呼叫函式GetMessage进行分配到发送到后台,会被不同的窗口程序处理。
•当用讯息传送机制时,执行绪产生一个窗口,并且相对应的窗口处理程序会执行相对应的程序代码来处理讯息。
不会有一个执行绪呼叫函式SendMessage并且这个执行绪还执行另外一个窗口的处理程序。
不必要在窗口处理程序中自己将讯息进行序列化,因为一旦讯息通过SendMessage传送,它会自动被讯息队列序列化。
7.2.2输入管理
输入管理由一整套子系统来完成,在WindowsCE中,该子系统负责处理前台窗口、活动窗口和焦点窗口。
每一个执行绪有一个特定的窗口称为活动窗口。
这是被特定执行绪拥有和启动的最高等级的窗口。
活动窗口和它的子窗口可以是焦点窗口(具有输入焦点的窗口)。
焦点窗口能够接收来自键盘的讯息。
系统中一个特定的执行绪或者讯息队列称为前台执行绪,前台执行绪中的活动窗口是前台窗口。
这三个窗口是相互关联的,设定输入焦点可以改变活动窗口。
同样,改变活动窗口也可以改变输入焦点。
设定前台窗口或者活动窗口可以改变窗口的坐标原点位置,同样,改变窗口的位置可以改变前台窗口。
所以,三个窗口有类似“石头、剪、布”之间的关系,它们之间的任何一个改变,其它的都会受到影响,如图7.6所示。
图7.6三个窗口相互关联
活动窗口和焦点窗口的信息都保存在讯息队列的结构中,所以它们都有一个基本的执行绪。
当呼叫函式SetActiveWindow时,一个执行绪将把和它同一个执行绪的窗口启动。
函式SetFocus将改变其所在执行绪中任意窗口的输入焦点(这个窗口可以是优先级最高的窗口也可以是一个子窗口)。
当输入焦点改变或者别的窗口被启动时,WM_SETFOCUS,WM_KILLFOCUS会被发送。
因为在输入系统中有一个执行绪负责输入事件,所以上面所说的工作在系统这个大的框架下面完成,而不是在更低一级的函式中处理。
这个执行绪会通过观察它的讯息队列的情况实时追踪前台执行绪的执行情况。
它从系统中产生一个执行绪作为前台执行绪,当使用者按下某个键,键盘的输入会被传送到前台执行绪的输入焦点处。
在旧的Windows版本中,可以呼叫SetActiveWindow函式来使不同的窗口启动。
但是呼叫这个函式并不是像前面所说的那样工作。
WindowsCE在输入方面和桌面Windows操作系统一样,由一个基本执行绪管理。
现在可以呼叫函式SetForegroundWindow来负责改变输入系统中前台窗口的信息,键盘输入也可以顺利地发送出去。
使用者可能会疑惑,因为他执行的应用程序可能并不在前台执行绪中。
其实,他的应用程序已经内部呼叫了SetActiveWindow函式和SetFocus函式,但是使用者并没有看见应用程序所属的窗口被启动。
整个系统好像没有变化。
但是函式GetActiveWindow会表明上面所说的窗口是活动窗口。
函式GetFocus说明那个应用程序的窗口已经获得了输入焦点。
窗口还没有放到前台的原因是它所在的执行绪还没有成为前台执行绪。
所以,这就是函式SetForegroundWindow(设置前台窗口)被加上的原因。
它会告诉系统“这就是你所要的接收使用者输入的前台窗口”。
这个呼叫会产生一系列的反应:
系统希望的窗口将被置于前台,它可以从内部将另一个执行绪启动,将输入焦点改变从而使合适的窗口接收到键盘输入。
类似地,如果现在的执行绪是前台执行绪,并且将别的具有最高等级的窗口置于顶层(比如呼叫函式SetWindowPos将它的位置改变),那么这个窗口将变成前台窗口。
并且如果你将输入焦点从一个窗口转向另一个窗口,活动窗口也随之改变。
这中间没有改变的是具有输入焦点的窗口总是活动窗口或者是活动窗口的子窗口,当然,它也可能为空。
因此总体的结构是这样的:
在所有储存结构中,一个处理程序有一个端口。
GWE就是其中一个处理程序。
GWE内部是一个执行绪等待着输入事件。
键盘或者别的触控式设备将一个事件放入主输入队列,主输入队列相当于系统输入队列,但是它没有桌面Windows操作系统中系统队列描述的那么细致。
执行绪在那儿得到相对应的事件。
如果得到的事件是一个触控输入事件,事件管理器会呼叫窗口管理器来找出是哪一个窗口被点选,接着使用函式PostMessage将讯息封装,然后放到所属窗口合适的讯息队列中去。
如果事件是一个键盘输入事件,事件管理器会将与信息相关的窗口和讯息队列交给前台执行绪处理后返回。
相对应的信息会表现为函式PostMessage所发送的讯息。
所以,这种发送的机制会同步执行,获得所有的输入情况并且将它发送到相对应的执行绪。
当别的执行绪执行时,它们将相对应的讯息从讯息队列中取出并且处理它。
如图7.7所示。
图7.7触控输入事件和键盘输入事件的处理
7.3图形设备接口
在别的图形包(Package)中,可以使用所有的画笔、画刷和字体等来完成每一个绘图操作。
在Win32GDI(图形设备接口)中,设备描述表(DeviceContext,DC)描述了图形的输出模版。
通过将使用的绘图工具(画笔、画刷等)对象选入设备描述表中来完成对绘图工具的选择。
设备描述表是所有绘图工具的集合。
绘图操作使用所有被选入设备描述表的工具对象。
当通过呼叫相对应的API函式来使用画笔和画刷时,画笔和画刷必须转化成和接口目标一致的形式。
所以,如果你呼叫的API函式和接口与画笔都有关时,必须明白画笔和接口要一致。
在WindowsCE中,将画笔选入设备描述表,同时和画笔一致的接口对象也被选入了设备描述表中。
画笔只需要实作一次,但是能画出一百万条线和矩形。
对所有的GDI图形对象来说,实作是很模糊的概念,并且它对不同的对象也代表了不同的操作。
你想用想象中的颜色的画笔和画刷,也许这种颜色在系统中并不存在,但是系统会尽量选择一个最接近的颜色。
从某种意义上说这就是一种实作。
字体的实作是这样的:
指定一种理想化的字体并且将它选入设备描述表,它和现实中的某种物理字体是匹配的。
调色板也是这样实作的,通过函式RealizePalette将一个理想中的调色板选入物理设备中。
当将某种式样的画刷选入设备描述表中(它可以是一个位图或者更大规格),必须建立一个和所需要的位图格式相匹配的画刷。
这种概念上的实作和别的可以选择的图形对象的实作是一样的。
一般情况下,当绘图时,仅仅是选择相对应的资源并且把它们拷贝到对应的地方。
但是别的逻辑操作会把它们连接起来。
映像模式的操作是选择逻辑操作的方法。
当用画笔在接口上绘图时,可以使用的一种可能的映像模式是将和画笔规格具有同样像素的线条复制到指定的区域。
当然,也可以通过一些别的操作组合来实作别的。
资源有16种组合方式,这些组合方式产生的基本区域被记作第二类映像模式。
还有第三类映像模式,它将组合出3个像素宽度的区域作为画刷的宽度。
7.3.1基本GDI对象
在WindowsCE的图形设备接口(GDI)中,所有的东西都是一个C++对象。
基础类别是一个被称作GDIOBJ的类别,其定义如程序代码7.1所示。
程序代码7.1GDIOBJ类别
//摘自[CEROOT]\Private\Winceos\Coreos\GWE\MGDI\inc\GDIOBJbase.hpp
classGDIOBJ
{
public:
staticHTABLE*m_pHTable;//句柄表
INT16m_nCount;//引用计数
UINT16m_nIndex;//句柄表索引
GDIOBJ(void);
~GDIOBJ(void);
ULONGIncrement(void);
ULONGDecrement(void);
voidRemoveFromHandleTable(void);
BOOLIsStockObject(void);
virtualBOOLDeleteObject(void);
virtualintGetObject(intCntBytesBuffer,void*pObject)=0;
virtualDWORDGetObjectType(void)=0;
virtualGDIOBJ*SelectObject(DC*)=0;
};
由GDIOBJ类别的定义可知所有的GDI对象都拥有一个16位的引用计数m_nCount。
但是GDI对象没有组件对象模型(COM)对象那么强的引用计数,当COM对象的引用计数为0时,对象可以自我删除。
对于GDI对象来说,由程序员负责删除资源:
当引用计数为0时,应该呼叫DeleteObject函式。
基本GDI对象定义了一些抽象函式,规定了实际的GDI对象需要完成的任务,例如删除对象自身(DeleteObject)、将对象自身选入设备描述表(SelectObject)等。
句柄表(m_pHTable)是对象句柄的一个列表,它保存着交给使用者程序处理的句柄。
m_nIndex是一个16位的索引,指向句柄表。
应用程序在失效的句柄上有很多问题。
比如,一个应用程序呼叫函式CreatePen建立一个画笔对象,同时得到对象相对应的句柄(HPEN)。
其后,程序删除了这个画笔对象,建立一个画刷对象。
但是,画笔句柄依然存在着。
当应用程序呼叫函式SelectObject将画刷句柄选入设备描述表中时,画笔句柄才会转化成一个画刷句柄。
有时候程序员以为选择的是一个画笔,而实际上选择的却是画刷,应用程序可能将这一切搞得很混乱。
这是Win32API存在的问题,一个多态性的问题。
因此,当选择一个句柄时,必须判断准确这个句柄是什么类型的句柄。
当应用程序退出而没有释放所占用的内存空间时,操作系统负责将多余对象所占的内存空间释放。
比如一个应用程序建立了一个画笔的句柄(HPEN)并且在程序结束之前忘记把它删除。
程序GWEs.exe接收到应用程序终止的讯息以后,从句柄表里面可以查明哪些处理程序建立了GDI对象。
从而,系统就可以在句柄表中寻找已经结束处理程序的对象,然后删除它们。
所以,尽管应用程序没有将多余的空间释放,但是操作系统可以确保不让多余的对象占据内存空间。
当然,正确的做法是让应用程序要尽力使它在退出时不会留下一些占据内存空间的无用数据。
在编译应用程序的侦错版本时,系统会指出错误信息,告诉应用程序哪些对象句柄应该被释放
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Windows CE 使用者接口与图形子系统 使用者 接口 图形 子系统