第七章 使用者接口与图形子系统
与Microsoft XP操作系统不同, Windows CE将Win32 API的使用者界面(User32)和图形设备接口(GDI32)合并成一个新的模块gwes.exe,称为GWE子系统。GWE是一个缩写词,其中G代表Graphics(图形),W代表Window Manager(窗口管理器),E代表Event Manager(事件管理器)。GWE子系统是使用者、应用程序和操作系统之间的图形使用者接口。
GWE支援组成Windows CE图形使用者接口的所有窗口、对话框、控件、菜单和资源,使使用者能够通过执行菜单命令、单击按钮等操作来控制应用程序。GWE还以位图、光标、文字以及图标等形式为使用者提供信息。即使不具备图形使用者接口的基于Windows CE的平台也使用了GWE的基本窗口和讯息功能,这些功能提供了在使用者、应用程序和操作系统之间进行通讯的方法。
本章主要分析GWE子系统的体系结构以及相关的实作程序代码,主要涉及的源程序位于Windows CE .NET原始程序代码树中的[CEROOT]/Private/Winceos/Coreos/GWE目录下。需要说明的是,受Microsoft Shared Source License的限制, GWE子系统中只有GDI部分公开了少量的原始程序代码,User部分的原始程序代码均未公布,因此本章内容主要着重于GWE子系统体系结构的分析。
7.1 GWE概述
对应于桌面Windows操作系统中的User32,Windows CE GWE子系统中的USER部分包含了使用者输入系统(User Input System)、事件管理器(Event Manager)和窗口管理器(Window Manager)三个组件。其中使用者输入系统接收来自键盘、鼠标和手写笔等设备的讯息,事件管理器管理讯息和讯息队列,而窗口管理器将讯息响应发送到对应的窗口以实作特定的显示。
GWE子系统中的GDI(Graphics Device Interface,图形设备接口)部分依靠二维图形包中的API函式将使用者的绘图操作通过直线、曲线、填充区域、位图和文字等GDI基本操作来实作,此外GDI还支持点阵字体和True Type字体。
基于Windows CE的程序设计要通过讯息循环。讯息循环是在Windows应用程序中的一种循环,它负责接收系统传送过来的讯息,并且把它们发送到相对应的窗口,直到系统表明所有的讯息都发送完毕,讯息循环才结束。它包含在WinMain函式中。处理讯息循环的函式是WndProc。
下面是GWE的一些特殊功能:
GWE实时追踪执行系统的工作情况,所以如果没有在三分钟内给设备一些指令的话, GWE将关闭这个设备。
GWE还添加了储存空间不足时的提示和解决方案。虽然在技术实作上储存空间不够的提示和解决没有必要一定成为GWE的一部分,但是在这里加上一些程序代码实作这种功能是最方便的。因为GWE和输入相关。如果储存空间不足,将出现内存不足的对话框。同时会弹出一个窗口,给出正在执行的程序代码,让使用者选择关掉其中的一个或几个。Windows CE添加储存空间不足时的解决程序是基于执行设备的考虑。因为执行Windows CE的设备一般没有硬盘,同时也没有足够的内存,一旦在执行时超过了内存的容量,将没有硬盘保存,所以必须在有可能超过储存容量的地方结束一些程序以节省空间。当然这在执行桌面Windows操作系统的设备上不常见到。
在使用者输入系统的设计上, Windows CE也尽量减少执行绪数。它将键盘设备和触控设备的处理直接交给了GWE,并且将这些设备所传送的讯息直接放在全域(Global)设备队列中。
GWE在设计之初有以下的一些目标:
• 在小屏幕设备上执行比较稳定: GWE设计的时候提供了足够的函式以便使用者写出的程序代码在小屏幕上能够执行的很好。
• 和Win32的兼容性:和Win32 API的兼容性是GWE设计时最重要的目标,因为这可以让那些会Windows程序设计的人能够在Windows CE上程序设计。
• 支持广泛的颜色位深度和色彩模式,如1、2、4、8、16、24和32位颜色。这些在桌面Windows操作系统中是没有提供的。
• 加上了调节低电压和对电源的管理的功能设计:这使得系统在判断使用者没有使用设备后关掉设备。
在上述目标之中,Windows CE最基本的设计目标是和Win32 API兼容,它的大部分程序代码也没有重新写。和桌面Windows操作系统一样,Windows CE的窗口管理器也包括对话框管理器、Splash类别和控件等。具体的结构如图7.1所示。
Windows CE中的非使用者输入区和桌面Windows操作系统中的有一些区别。它和窗口管理器结合在一起。在Windows CE中,一个独立的菜单列和工具列将占据太多的空间,所以Windows CE将菜单列和工具列结合成一个新的控件,称为命令列。以前的菜单被放在非使用者区而工具列放在使用者区,现在菜单和别的控件一样被放在命令列中,并且由命令列实作菜单控件的功能。对话框管理器位于窗口管理器的上层。当实作一个对话框管理器时,同时会有一个消息框产生。消息框位于对话框管理器的上层,并且所有的控件(比如编辑框、列表框、组合框等)都位于对话框管理器的上层。菜单与以前有一些不同,现在也作为控件处理。
GWE还包括内存不足(OOM,out of memory)对话框和内存不足句柄。内存不足对话框在使用者内存空间不够时弹出,并将正在执行的程序行出来,让使用者选择关闭其中的一个或几个。同样,内存不足句柄也是由于储存空间比较小才被加到Windows CE系统中去的。
图7.1 Windows CE图形子系统的结构
7.2 使用者输入系统
GWE的USER部分包括讯息队列、事件管理器和使用者输入系统三个核心组件。其中最重要的是使用者输入系统,它负责接收从键盘、鼠标以及触控板发出的讯息。使用者输入系统将使用者从键盘、鼠标等设备输入的讯息通过讯息传送机制送到相对应的窗口中。USER利用产生窗口和讯息传送这两部分功能来实作使用者输入系统。
使用者输入系统的结构如图7.2所示,主要组件包括:
• Msgque:讯息队列,这是任何需要讯息传递的地方所必须的部分,因为要实作使用者输入就必须能够把输入的信息传给所需要的窗口。
• Wmbase:这部分组件的作用是建立窗口,为窗口提供窗口处理函式WndProc,并且给它发送讯息。
• Winmgr:窗口管理器,它负责把绘图操作的结果在屏幕上显示出来。
下面分两部分对以上内容进行详细阐述,其中将窗口的产生和管理合并成输入系统。
图7.2 GWE的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.3 PostMessage的讯息处理流程
函式SendMessage和函式PostMessage相比有一点区别,它和讯息处理是同步的,所以当发现函式SendMessage返回时,这说明讯息已经被处理完了。讯息有两种途径被发送到处理程序,这主要取决于讯息发送到的窗口是在函式SendMessage的执行绪还是别的执行绪。最简单的情况是将讯息传送到和函式SendMessage在同一个执行绪的窗口。函式SendMessage发现产生窗口的执行绪和自己所在的执行绪是同一个执行绪,就直接呼叫窗口处理程序(而不是将它放到讯息队列中),然后返回,这里的讯息处理仅仅是呼叫一个子函式。SendMessage的处理流程如图7.4所示。
图7.4 SendMessage的同执行绪讯息处理流程
复杂一点的情况是将讯息传送到属于另外一个执行绪的窗口。此时,讯息队列有一个子队列负责处理传送到特定窗口和执行绪的讯息。函式SendMessage发现这个讯息将要传送到另一个执行绪的讯息队列,就将它封装然后放到这个执行绪的讯息队列中去,然后等待。所以现在就有一个正在呼叫的执行绪等待一个内部Win32事件对象的响应。这时,执行绪的等待并不影响它接收讯息,执行绪可以接收讯息并且对它做出处理。
这里的关键点是当讯息传送到属于别的执行绪的窗口时,实际上是窗口所属的主执行绪在执行程序代码。发送讯息的执行绪呼叫函式SendMessage,但是是别的执行绪的WndProc函式在接收讯息。如图7.5所示。
说的更详细一点,当送出讯息的执行绪被挂起(Hook)等待响应时,它还必须实时检查讯息是否被送回来。接收到送出执行绪送出讯息的窗口可以立即回发一个讯息,所以当执行绪被挂起等待响应时,它依然可以接收送过来的讯息。
在发送讯息的区别上,除了函式SendMessage发出讯息后必须等待讯息被处理而函式PostMessage不需要外,函式SendMessage还被函式GetMessage所操作。
应用程序的主循环从不检查函式GetMessage的呼叫是否返回。函式GetMessage执行时仅仅按优先级处理讯息,并将讯息分别发送到相对应的地方。
表7.1列举了一些讯息的等级。
图7.5 SendMessage的异执行绪讯息处理流程
表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 输入管理
输入管理由一整套子系统来完成,在Windows CE中,该子系统负责处理前台窗口、活动窗口和焦点窗口。
每一个执行绪有一个特定的窗口称为活动窗口。这是被特定执行绪拥有和启动的最高等级的窗口。活动窗口和它的子窗口可以是焦点窗口(具有输入焦点的窗口)。焦点窗口能够接收来自键盘的讯息。系统中一个特定的执行绪或者讯息队列称为前台执行绪,前台执行绪中的活动窗口是前台窗口。
这三个窗口是相互关联的,设定输入焦点可以改变活动窗口。同样,改变活动窗口也可以改变输入焦点。设定前台窗口或者活动窗口可以改变窗口的坐标原点位置,同样,改变窗口的位置可以改变前台窗口。所以,三个窗口有类似“石头、剪、布”之间的关系,它们之间的任何一个改变,其它的都会受到影响,如图7.6所示。
图7.6 三个窗口相互关联
活动窗口和焦点窗口的信息都保存在讯息队列的结构中,所以它们都有一个基本的执行绪。当呼叫函式SetActiveWindow时,一个执行绪将把和它同一个执行绪的窗口启动。函式SetFocus将改变其所在执行绪中任意窗口的输入焦点(这个窗口可以是优先级最高的窗口也可以是一个子窗口)。当输入焦点改变或者别的窗口被启动时,WM_SETFOCUS,WM_KILLFOCUS会被发送。
因为在输入系统中有一个执行绪负责输入事件,所以上面所说的工作在系统这个大的框架下面完成,而不是在更低一级的函式中处理。这个执行绪会通过观察它的讯息队列的情况实时追踪前台执行绪的执行情况。它从系统中产生一个执行绪作为前台执行绪,当使用者按下某个键,键盘的输入会被传送到前台执行绪的输入焦点处。
在旧的Windows版本中,可以呼叫SetActiveWindow函式来使不同的窗口启动。但是呼叫这个函式并不是像前面所说的那样工作。Windows CE在输入方面和桌面Windows操作系统一样,由一个基本执行绪管理。现在可以呼叫函式SetForegroundWindow来负责改变输入系统中前台窗口的信息,键盘输入也可以顺利地发送出去。
使用者可能会疑惑,因为他执行的应用程序可能并不在前台执行绪中。其实,他的应用程序已经内部呼叫了SetActiveWindow函式和SetFocus函式,但是使用者并没有看见应用程序所属的窗口被启动。整个系统好像没有变化。但是函式GetActiveWindow会表明上面所说的窗口是活动窗口。函式GetFocus说明那个应用程序的窗口已经获得了输入焦点。窗口还没有放到前台的原因是它所在的执行绪还没有成为前台执行绪。
所以,这就是函式SetForegroundWindow(设置前台窗口)被加上的原因。它会告诉系统“这就是你所要的接收使用者输入的前台窗口”。这个呼叫会产生一系列的反应:系统希望的窗口将被置于前台,它可以从内部将另一个执行绪启动,将输入焦点改变从而使合适的窗口接收到键盘输入。
类似地,如果现在的执行绪是前台执行绪,并且将别的具有最高等级的窗口置于顶层(比如呼叫函式SetWindowPos将它的位置改变),那么这个窗口将变成前台窗口。并且如果你将输入焦点从一个窗口转向另一个窗口,活动窗口也随之改变。这中间没有改变的是具有输入焦点的窗口总是活动窗口或者是活动窗口的子窗口,当然,它也可能为空。
因此总体的结构是这样的:在所有储存结构中,一个处理程序有一个端口。GWE就是其中一个处理程序。GWE内部是一个执行绪等待着输入事件。键盘或者别的触控式设备将一个事件放入主输入队列,主输入队列相当于系统输入队列,但是它没有桌面Windows操作系统中系统队列描述的那么细致。执行绪在那儿得到相对应的事件。
如果得到的事件是一个触控输入事件,事件管理器会呼叫窗口管理器来找出是哪一个窗口被点选,接着使用函式PostMessage将讯息封装,然后放到所属窗口合适的讯息队列中去。
如果事件是一个键盘输入事件,事件管理器会将与信息相关的窗口和讯息队列交给前台执行绪处理后返回。相对应的信息会表现为函式PostMessage所发送的讯息。所以,这种发送的机制会同步执行,获得所有的输入情况并且将它发送到相对应的执行绪。当别的执行绪执行时,它们将相对应的讯息从讯息队列中取出并且处理它。如图7.7所示。
图7.7 触控输入事件和键盘输入事件的处理
7.3 图形设备接口
在别的图形包(Package)中,可以使用所有的画笔、画刷和字体等来完成每一个绘图操作。在Win32 GDI(图形设备接口)中,设备描述表(Device Context,DC)描述了图形的输出模版。通过将使用的绘图工具(画笔、画刷等)对象选入设备描述表中来完成对绘图工具的选择。设备描述表是所有绘图工具的集合。绘图操作使用所有被选入设备描述表的工具对象。
当通过呼叫相对应的API函式来使用画笔和画刷时,画笔和画刷必须转化成和接口目标一致的形式。所以,如果你呼叫的API函式和接口与画笔都有关时,必须明白画笔和接口要一致。在Windows CE中,将画笔选入设备描述表,同时和画笔一致的接口对象也被选入了设备描述表中。画笔只需要实作一次,但是能画出一百万条线和矩形。
对所有的GDI图形对象来说,实作是很模糊的概念,并且它对不同的对象也代表了不同的操作。你想用想象中的颜色的画笔和画刷,也许这种颜色在系统中并不存在,但是系统会尽量选择一个最接近的颜色。从某种意义上说这就是一种实作。字体的实作是这样的:指定一种理想化的字体并且将它选入设备描述表,它和现实中的某种物理字体是匹配的。调色板也是这样实作的,通过函式RealizePalette将一个理想中的调色板选入物理设备中。当将某种式样的画刷选入设备描述表中(它可以是一个位图或者更大规格),必须建立一个和所需要的位图格式相匹配的画刷。这种概念上的实作和别的可以选择的图形对象的实作是一样的。
一般情况下,当绘图时,仅仅是选择相对应的资源并且把它们拷贝到对应的地方。但是别的逻辑操作会把它们连接起来。映像模式的操作是选择逻辑操作的方法。
当用画笔在接口上绘图时,可以使用的一种可能的映像模式是将和画笔规格具有同样像素的线条复制到指定的区域。当然,也可以通过一些别的操作组合来实作别的。资源有16种组合方式,这些组合方式产生的基本区域被记作第二类映像模式。还有第三类映像模式,它将组合出3个像素宽度的区域作为画刷的宽度。
7.3.1 基本GDI对象
在Windows CE的图形设备接口(GDI)中,所有的东西都是一个C + +对象。基础类别是一个被称作GDIOBJ的类别,其定义如程序代码7.1所示。
程序代码7.1 GDIOBJ类别
/ /摘自[CEROOT]/Private/Winceos/Coreos/GWE/MGDI/inc/GDIOBJbase.hpp class GDIOBJ { public: static HTABLE* m_pHTable; // 句柄表 INT 16 m_nCount; // 引用计数 UINT 16 m_nIndex; // 句柄表索引 GDIOBJ ( void ) ; ~ GDIOBJ ( void ) ; ULONG Increment(void); ULONG Decrement(void); void R e m o v e F r o m H a n d l e T a b l e ( void ) ; BOOL IsStockObject(void); virtual BOOL DeleteObject(void); virtual int GetObject(int CntBytesBuffer, void* pObject) = 0; virtual DWORD GetObjectType(void ) = 0; virtual GDIOBJ* 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将画刷句柄选入设备描述表中时,画笔句柄才会转化成一个画刷句柄。有时候程序员以为选择的是一个画笔,而实际上选择的却是画刷,应用程序可能将这一切搞得很混乱。这是Win32 API存在的问题,一个多态性的问题。因此,当选择一个句柄时,必须判断准确这个句柄是什么类型的句柄。
当应用程序退出而没有释放所占用的内存空间时,操作系统负责将多余对象所占的内存空间释放。比如一个应用程序建立了一个画笔的句柄(HPEN)并且在程序结束之前忘记把它删除。程序GWEs.exe接收到应用程序终止的讯息以后,从句柄表里面可以查明哪些处理程序建立了GDI对象。从而,系统就可以在句柄表中寻找已经结束处理程序的对象,然后删除它们。所以,尽管应用程序没有将多余的空间释放,但是操作系统可以确保不让多余的对象占据内存空间。
当然,正确的做法是让应用程序要尽力使它在退出时不会留下一些占据内存空间的无用数据。在编译应用程序的侦错版本时,系统会指出错误信息,告诉应用程序哪些对象句柄应该被释放。
7.3.2 图形基本操作
Windows CE GDI一个非常重要的结构特点是它不直接接触像素。所有的信息都将送至装置驱动程序,并由装置驱动程序最终完成像素点的输出。
在桌面Windows操作系统中,装置驱动程序将实作不了的操作交还给GDI,其GDI可以直接进行像素点的输出。但是Windows CE GDI没有这种能力。为了降低内存占用,系统必须让装置驱动程序能够支持所有和像素有关的信息。
Windows CE GDI(绘图部分)的基本操作有:矩形、折线、多边形、椭圆和圆角矩形。而装置驱动程序只知道如何画线和填充小区域,问题在于如何将GDI绘图的基本操作分解为一些线和小区域。为解决这个问题,首先应该弄清楚Windows CE GDI的画笔(用来绘制直线的工具)和画刷(用来添充区域内部的工具)。
• 画笔
BLACK-PEN 和WHITE-PEN分别用黑色和白色绘制1个像素宽的直线,这些画笔和NULL-PEN(空画笔)都是GDI的普通画笔。GetStockObject函式可用来选择这些普通画笔。
CreatePen和CreatePenIndirect 函式用来设计与普通画笔不同属性的画笔。它们允许使用者定义画笔的线宽度、颜色和画笔类型。表7.2列出了画笔类型。
表7.2 画笔类型
画笔 | 类型 |
PS_SOLID | 画实线 |
PS_DASH | 画点划线 |
PS_NULL | 不画线 |
• 画刷
普通画刷和普通画笔一样,可通过SelectObject函式选择。CreateDIBPatternBrushpt 函式能够用来设计任何尺寸、颜色和模式的画刷。它可以设置某种单一颜色或者混合色。
画笔和画刷的定义如程序代码7.2所示。
程序代码7.2 画笔和画刷的定义
/ /摘自[CEROOT]/Private/Winceos/Coreos/GWE/MGDI/inc/GDIOBJ.h struct PEN : public GDIOBJ { LOGPEN m_LogPen; PEN(CONST LOGPEN *); virtual int GetObject(int,PVOID); virtual DWORD GetObjectType(); virtual GDIOBJ *SelectObject(DC *); #if DEBUG virtual int Dump (void); #endif } ; struct BRUSH : public GDIOBJ { LOGBRUSH m_LogBrush; MBITMAP *m_pBitmap; // bitmap containing pattern BRUSH( ); ~BRUSH(); BOOL Create(CONST LOGBRUSH *); virtual int GetObject(int,PVOID); virtual DWORD GetObjectType(); virtual GDIOBJ *SelectObject(DC *); #if DEBUG virtual int Dump (void); #endif } ;
|
有了画笔和画刷的概念,下面看看如何具体将GDI绘图的基本元素分解为一些线和小区域。
函式Setpixel和Getpixel处理的对象—像素本身就是一个小区域(就是通常的点),可以看作一个1像素* 1像素的矩形。
函式Rectangle的功能是画出及填充一个矩形。由于矩形只是一系列区域的组合,实作起来非常容易。呼叫装置驱动程序时,对于矩形区域内部使用一个画刷工具,而对于矩形外边缘,使用4个小区域。首先使用选入设备描述表中的画刷填充矩形区域内部,然后使用选入设备描述表中的画笔描绘矩形外边缘。如果只想填充矩形区域内部,则可以在设备描述表中选入NULL-PEN后呼叫函式Rectangle,这样会画出没有边框的矩形。
函式Polyline的功能是画出一个连续的折线,能够逼近各种形状。如果画笔的宽度多于一个像素,则将节点间联机转变为对区域的填充,GDI将每一段折线变成一个填充区域,然后将这些区域送至装置驱动程序。
函式Polygon的功能是画出及填充一个多边形。它假定所有节点组成的是一个封闭图形,如果不是这样,Windows CE会自动将其封闭。和函式Rectangle一样,函式Polygon将进行多边形区域内部的填充和多边形外边缘的描绘。同样如果画笔的宽度多于一个像素,将会呼叫一个新的Polygon函式,产生一个在原多边形外侧的带状多边形。
函式Ellipse的功能是画出及填充一个椭圆。它和RoundRect是惟独两个能够处理曲线对象的函式。可以对这些曲线进行贝塞尔级数展开从而很好的对曲线的每一区域进行近似。然后通过贝塞尔近似将其转变为一系列直线段,这样装置驱动程序便可以对其进行处理了。
函式RoundRect的功能是画出及填充一个有圆角的矩形,其实就是将椭圆用两组分别平行于其长短轴的直线组切割后剩下的部分。因此它的实作只须将函式Ellipse进行一下简单地扩充。
桌面Windows操作系统预设具有几何宽度的画笔,并可以将线的末端设置为方角形或斜角形。Windows CE中没有支持这种几何型的画笔,它只支持最简单的画笔,和一般画笔比较相近。
其实在Windows CE中本可以增加一些图形基本元素,例如粗画笔、粗线椭圆或者一些特殊形状工具。但是这样无非是用空间换取了时间,这违背了Windows CE设计的基本原则,是不可取的。Windows CE所需要的只是一些最基本的元素,而对于复杂的图形,宁可通过增加时间复杂度的方法来实作。
7.3.3 调色板
可能有许多人都会问一个问题,“Windows CE的调色板是什么?”。回答是Windows CE中根本就没有自己的调色板。装置驱动程序在启动时会提供一个首选调色板并说明它是否可以变动。某些设备会拥有一个硬编码调色板,在这些设备上,它就是Windows CE的调色板。
虽然没有一个通用的系统调色板,但是当需要它的时候,会有一个装置驱动程序提供一个调色板。系统开发人员推荐原始设备制造商(OEM)将半色调调色板(Halftone Palette)植入设备中并将其加载为原始编码。可以将你的资源建立在半色调调色板上,同时在执行的时候不能存在色彩转换。
如果问程序开发者们他们觉得在台式机系统的GDI中最头疼的部分是什么,他们一定会说是调色板的模式。在MSDN上有一篇著名的关于调色板的文章,叫作“ The Palette Manager—How and Why It Does What It Does”,此文对调色板进行了详细阐述。对此有兴趣的读者可以认真阅读一下此文章,相信一定会受益非浅。
在台式机系统中,调色板管理器合并了多个应用程序的请求从而使各方面都得到尽可能的满足。当试图在台式机系统上建立一个调色板时,可以在每个PALETTEENTRY类别的对象处插入一些标记参数peFlags。它告诉调色板管理器你打算对这个调色板设置的入口参数,例如不允许映像、不允许别的使用者使用这个调色板入口、将对这个调色板入口进行大面积修改等。开始10个和最后10个颜色叫作系统颜色(Windows colors),他们很难修改。即便向Windows发出一个请求,台式机系统也不会允许你修改第一个和最后一个入口参数,它们分别代表黑和白。
但在Windows CE中,系统开发人员忽略了所有这些。只须将一个调色板选入设备描述表,然后在设备上颜色就会在逻辑调色板和物理调色板之间进行一一映射。所有Windows CE的调色板都是一致性调色板。Windows CE没有一个调色板管理器。系统开发人员将Windows CE的这种调色板设计比做调色板的公民自由模式。你可以对系统调色板做你愿意做的任何事。系统开发人员信任你会是个好的调色板公民。
这里系统没有任何限制。如果希望选择一个256个入口参数的调色板并且都为红色,那就这样去做。你的显示器将变成红色。任务列、系统对话框…一切都会变成红色。
在设计中,系统开发人员进行了一些简化工作从而使其能很好地支持Windows CE中的调色板。同时系统开发人员很有希望能建立起一个可以被人们所理解和认同的模型。他们在简化系统以及保留合理功能这一方面做的很好。
Windows CE的调色板可以让我们做一些在台式机系统中做不到的事情。例如在台式机系统中,如果你将一个设备描述表中的区域映像到另一个设备描述表中,不会得到正确的颜色。而在Windows CE 中没有这种现象,你可以得到最好的色彩转换。这是因为Windows CE中的函式StretchBlt在实作色彩转换上比台式机系统中的函式BitBlt要精巧的多。
如果必须进行色彩转换,系统实际上会将色彩转换的对象存入图像高速缓冲存储器。色彩转换是一个非常复杂的问题。设想现在要进行从一个某种8位色彩模式图像到另一个不同的8位色彩模式图像的转换。由于两个调色板并不匹配,你需要在目标处找到一个与信息源处第一个入口参数最接近的入口,然后重复这种操作。要知道这是一个256色到256色的转换问题,它会相当耗费资源。而将其运算结果存入图像高速缓冲存储器也会占据很大的空间。
实际上程序开发者们在实际应用时并没有使用这么多不同的调色板。调色板越多,信息源与目标的组合就越多,这对图像高速缓冲存储器无疑是一个重大的考验。如果是这样,你一定无法忍受程序执行所耗费的足够多的时间。即便色彩转换全部在高速缓冲存储器里完成,如果你很在乎系统的性能,也应该在同一模式和同一调色板下进行信息源和目标的色彩转换。因此无论是否在高速缓冲存储器里,在同一模式和调色板下的色彩转换都会大大提高速度。
习惯了台式机系统调色板的人们可能会在使用Windows CE调色板的时候遇到麻烦。他们可能会认为调色板管理器还在帮助自己完成一些基本的工作,然而实际上它已经不存在了。如果你在Windows的最上层打开了一个窗口(在它的下面还有很多其它的窗口),而这时你想改变调色板,于是呼叫函式RealizePalette。在台式机系统上,执行着的调色板管理器会帮你把整个背景整理的井井有条,但是在Windows CE上,背景上的东西看起来很糟糕,这是因为他们都被映像到了一个不同的调色板而且没有一个调色板管理器。因此我们要说的是在Windows CE上不要去碰调色板。如果一定要改变系统调色板,要确定打开的窗口已经被最大化,它已经覆盖住了所有下层的其它窗口。否则的话它们会看上去很糟。当然对于程序开发者们他们可以做任何他们想做的事情。但是如果他们想改变调色板的话,应该建议他们不要试图改变所谓的系统颜色(Windows colors),也就是前面提过的开始10个和最后10个颜色。对于游戏程序和某些应用程序,拥有完整的调色板是十分有意义的,但是程序开发者们必须足够努力工作才能做到这一点。
然而在调色板模式问题上还是有一些小的问题。如果通过呼叫函式CreatDIBSection建立一个位图,你需要操作一个色彩表,它是一个附加在这个位图上的调色板,定义了位图上像素的颜色与其值的对应关系。但是其它建立位图的API函式并不接受色彩表。在这个意义上色彩处理会有一点混乱。
如果通过呼叫函式CreateBitmap建立一个非8位的位图,这个位图会和这个位深度的预设色彩表关联起来。例如,对于1位位图,当然会采用黑白两色的预设色彩表。对于2位位图,H/PC为系统开发人员提供了先例,即预设为黑、深灰、浅灰和白四色色彩表。对于4位位图,系统开发人员选择了台式机系统上的16位EGA调色板。对于比8位高的位图,如16、24和32位位图,系统开发人员使用了台式机系统上的RGB预设色彩表。这样便设置了除8位外所有位深度的位图预设色彩表。
为了允许动态调色板工作,系统开发人员对8位位图做了一些不同的设计。对于8位位图,函式CreateBitmap返回一个没有色彩表与之关联的位图,如果这样颜色是如何定义的呢?“ 17”这个像素值又是代表什么颜色呢?这时候被选入设备描述表中的调色板就开始起作用了。如果有一个没有色彩表的8位位图,那么这个位图的颜色定义便由在设备描述表中选择的调色板决定。这就使诸如动态调色板之类的功能成为可能:你可以建立一个位图,选择一个调色板,将其送到显示器,然后再将另一个调色板选入设备描述表并不断改变这个调色板,如此进行重复操作。这或许是在调色板模式问题上的一个更为混乱的问题。当你将一个没有色彩表的8位位图传给一个2位设备时,系统会试图将这个8位位图和设备描述表中4个入口参数的色彩表关联起来,于是错误发生了。人们可能经常抱怨将一个本以为是8位色彩的彩色位图输入后得到的却是一个灰度图,这很可能是因为他们忘记在设备描述表中选择一个与输入模式相对应的调色板了。
调色板的相关程序代码如程序代码7.3所示,色彩表的相关程序代码如程序代码7.4所示。
程序代码7.3 调色板
/ /摘自[CEROOT]/Private/Winceos/Coreos/GWE/MGDI/inc/GDIOBJ.h struct PALETTE : public GDIOBJ { public: PALETTE() { m_pcolortbl = NULL; } ~PALETTE();
// If constructor succeeds (ppalette, handle are not NULL), // FInitPal() has to be called to init newly created object BOOL FInitPalette( CONST PALETTEENTRY *ppe, ULONG cLogPalEntries, BOOL fStock);
virtual int GetObject(int,PVOID); virtual DWORD GetObjectType(); virtual GDIOBJ *SelectObject(DC *); PALETTE *SelectPalette(DC *);
// Getting/Setting palette entries, all params are expected to be validated // by the caller void GetEntries( UINT 32 uiStart, UINT 32 uiNum , PALETTEENTRY* pPe ) ;
void SetEntries( UINT 32 uiStart, UINT 32 uiNum, CONST PALETTEENTRY* pPe ) ;
ULONG xxxSetSysPal(Driver_t *pdriver);
#ifdef DEBUG virtual int Dump (void) { int cThis; |
DEBUGMSG (1, (TEXT("PALETTE size=%d/r/n"), cThis = LocalSize(this))); return cThis; } #endif
// // Trivial getters // ColorTable_t* Pcolortbl( void ) const;
PALETTEENTRY* PPalentries( void ) const;
USHORT CEntries( void ) const;
private: ColorTable_t *m_pcolortbl; } ;
|
程序代码7.4 色彩表
/ /摘自[CEROOT]/Private/Winceos/Coreos/GWE/MGDI/inc/colortable.hpp class ColorTable_t { public: static void I n i t S t o c k ColorTable s ( void ) ; ColorTable_t( unsigned int PaletteType , USHORT Mutable , USHORT C n t PaletteEntries , PALETTEENTRY* p PaletteEntries ) ; |
static ColorTable_t* Stock( unsigned long BitmapFormat ) ; static BOOL ColorTableFromBitmAPInfo( const BITMAPINFO* pbitmAPInfo , UINT iUsage , BOOL Mutable , ColorTable_t** ppColorTable ) ; void IncRefcnt( void ) ; void DecRefcnt( void ) ; void UpdateIuniq( void ) ; USHORT CEntries ( void ) const; PALETTEENTRY* PPalentries( void ) const; IUNIQ Iuniq( void ) const; unsigned int Paltype( void ) const; BOOL Mutable( void ) const; bool Indexed( void ) ;
private: ~ ColorTable_t( void ) ; |
static ColorTable_t* ColorTableFromPalentries( INT CntPaletteEntries , PALETTEENTRY* p PaletteEntries , unsigned int PaletteType , BOOL Mutable ) ; void AddToPool( void ) ; void RemoveFromPool( void ) ; IUNIQ IuniqNext( void ) ; unsigned int m _PaletteType; //PAL_INDEXED, PAL_BITFIELDS, PAL_RGB, PAL_BGR IUNIQ m_IUNIQ ; / /本颜色表的惟一标志 USHORT m_Mutable ; / /如果调色板项可以修改为TRUE,实际为BOOL类型 USHORT m_RefCnt ; USHORT m_CntPaletteEntries ; PALETTEENTRY* m_pPaletteEntries ; ColorTable_t* m_pNext ; } ; |
7.3.4 位图
位图有许多不同的种类,可以通过呼叫函式CreateDIBSection、CreateBitmap或CreateCOMpatibleBitmap来建立位图,从而得到相对应的位图句柄。前两个函式是分配在系统内存中的。而CreateCOMpatibleBitmap根据装置驱动程序和硬设备的不同由装置驱动程序分配内存,同时装置驱动程序可以分配影像内存。影像内存是一个很有价值的资源,这是因为在它之中的小区域操作速度会大大增加。如果十分在意速度并希望能够快速地对位图进行操作,那么呼叫函式CreateCOMpatibleBitmap或许是最佳的选择。
在某些系统平台上,只有当信息源和目标文件同时存在于影像内存中时硬件加速卡才能起作用。因此将信息源和目标文件同时放在影像内存中会大大提高系统的影像性能。
7.3.5 字体
Windows CE拥有一个完整的True Type字体(一种由Microsoft和Apple公司共同研制的字型标准)处理体系。显示一个字形需要花费很长的时间,当呼叫某种商标字体中的一个字母时,系统需要从这种字体的字体文件中取出它的字形,缩放到需要的大小,最后将其显示出来。为了减少这个过程的时间,系统使用了一个“字形高速缓冲存储器”。当要建立一个特殊尺寸的字形时,系统需要权衡处理速度与占用高速缓存大小之间的矛盾以决定给这个操作分配多少高速缓存和分配的时机。
作为一个程序开发者,可以用两种方法对其进行控制:可以在建立Windows CE系统时就设置好字形高速缓冲存储器的大小,或者当字体被丢弃时由在Windows CE系统上执行独立的应用程序进行控制。字形高速缓冲存储器是和字体句柄相联系的。因此当在呼叫函式DeleteObject时,会删除相对应的字体句柄,这样字形高速缓冲存储器也就不存在了。如果需要进行高速的文字显示,应该使显示文字的字体句柄始终存在,这时字形高速缓冲存储器便时刻处于工作状态。试想如果要反复呼叫同一段文字,若每次都是先建立字体,而后显示这些文字,最后删除相对应的字体句柄的话,系统一定会变得非常慢。
系统预设字形高速缓冲存储器的容量为4KB,这个容量对于容纳一般常用的英文字母表很合适。几乎所有英文字母表的字母都可以放在这个4KB的高速缓存中。而对于尺寸比较大的字母会超出这个4KB的容量。而对于像中文这样的字体,所面对的不再是52个大小写字母而是或许超过10000个的字符,试图将所有这些字符都放到一个4KB的高速缓存中显然是不可能的。如果还是非常在意速度,可以改变字形高速缓冲存储器的大小。你会惊喜地发现基于中文字体的应用程序的执行速度会随着字形高速缓冲存储器容量的增加而显著地提高,这是因为当字体工作在高速缓存中时,系统的性能会大大提高。
系统支持在任何设备上使用anti-aliased(平滑处理)字体,但是这需要此设备显示驱动程序的支持。如果一个设备的显示驱动程序支持anti-aliased字体,它需要在系统启动时通知系统它有此种能力。这一过程是函式GetGraphicsCaps通过返回GCAPS_GRAY16完成的。如果一个设备的显示驱动程序不支持这种功能,系统便取消对anti-aliased(平滑处理)字体的支持。
字体的定义如程序代码7.5所示。
程序代码7.5 字体的定义
/ /摘自[CEROOT]/Private/Winceos/Coreos/GWE/MGDI/inc/GDIOBJ.h struct LOGFONTWITHATOM { LONG lfHeight; LONG lfWidth; LONG lfEscapement; LONG lfOrientation; LONG lfWeight; BYTE lfItalic; BYTE lfUnderline; BYTE lfStrikeOut;
|
BYTE lfCharSet; BYTE lfOutPrecision; BYTE lfClipPrecision; BYTE lfQuality; BYTE lfPitchAndFamily; ATOM lfAtomFaceName; } ;
struct FONT : public GDIOBJ { LOGFONTWITHATOM m_LogFont; // TrueType font realizations (RFONTOBJ); unused in the raster font engine. void *m_prfont; // Monochrome realization void *m_prfontAA; // Anti-Aliased version of this realization void *m_prfontCT; UINT32 m_uEffxDisplay; // Display-time font effects, eg, Bold, underline, strikeout
FONT(CONST LOGFONT *); ~FONT() ;
virtual int GetObject(int,PVOID); virtual DWORD GetObjectType(); virtual GDIOBJ *SelectObject(DC *); #if DEBUG virtual int Dump (void); #endif } ; |
7.3.6 GDI的组件
GDI组件如图7.8所示,其中包括GDI的核心组件MGBase,除此之外的绝大部分都是使用者可以选择使用的组件。一般的配置是包括所有的组件,它们使用点阵字体或是True Type字体。
系统开发人员在开发时还建立了一个“游戏平台”,它的图形输出全部由DirectX完成。支持它的只是GDI程序的一个很小子集。最基本的可以使用这个平台加载一个位图并将它分区域在显示器上显示。还可以通过它建立一个屏幕保护位图,对它任意修改并最后显示出来。甚至也可以通过它将点阵字体显示在显示器上面。所有在这个平台上的操作都是通过DirectX完成的。
图7.8 GDI的组件
7.4 显示驱动程序接口
Windows CE的显示驱动程序接口DDI(Display Driver Interface)是Microsoft WindowsNT/2000/XP DDI的子集,Windows CE仅使用Windows NT DDI中最基本的图形引擎函式和驱动程序函式。Windows CE和Windows NT/2000/XP在显示驱动程序方面主要有下述三点细节上的差别:
• Windows CE的显示驱动程序都有相同的一套功能,GDI并不查询驱动程序的参数及性能信息。
• Windows CE的显示驱动程序从不拒绝复杂的操作。当遇到复杂情况时,显示驱动程序调其回GDI,使操作分成简单几步。因为所有Windows CE的显示驱动程序均有相同的一套功能,GDI需要在首次呼叫显示驱动程序前分解复杂操作。
• Windows CE的显示驱动程序被编译成动态链接库(.DLL檔)而不是函式库(.LIB檔)。
所有Windows CE的显示驱动程序必须执行一套由GDI呼叫的DDI函式来完成初始化和将图像显示在显示器上的工作。除了DDI函式,还须有一套C + +的图形基本操作引擎GPE(Graphics Primitive Engine)类别。它使得显示驱动程序对硬件加速更容易。标准的显示驱动程序通过呼叫GPE对硬件加速是基于S3Trio64的显示硬件,如果显示硬件使用不同的影像芯片则需要改变GPE的呼叫方式以兼容硬设备。
使用GPE类别是可选择的,在写入一个显示驱动程序时它并不是必须的,不过没有它DDI函式执行起来会更加复杂。需要注意的是,由Microsoft提供的GPE类别需要显示硬件有一个flat frame缓冲区。如果显示硬件没有这个缓冲区(例如,它用一个固定大小的可移动窗口连接整个显示卡内存)则可能不能够使用GPE类别。
GPE类别的实作程序代码位于[CEROOT]/Private/Winceos/Coreos/GWE/MGDI/
gpe/gpe.cpp中。
表7.3列出了Windows CE显示驱动程序和打印驱动程序中用到的DDI函式。显示驱动程序必须实作表中所有的DDI函式而打印驱动程序只需实作表中列出的打印DDI函式。其中只有函式DrvEnableDriver必须由显示驱动程序的动态链接库(DLL)导出,所以只有DrvEnableDriver的函式名不能改变。而其它函式可以随意起名,因为它们均是通过由函式DrvEnableDriver返回的函式指标连接到GDI。不过要特别注意的是,无论它们叫什么名字,总要和定义在Winddi.h檔中的名称保持一致。
表7.3 DDI函式
函式名称 | 用途 |
DrvAnyBlt | 传送字节(支持缩放及透明化) |
DrvBitBlt | 传送一般字节(支持剪切及屏蔽) |
DrvContrastControl | 允许软件对显示硬件的对比度进行调整 |
DrvCopyBits | 将由GDI产生的打印段发送到打印驱动程序 |
DrvCreateDeviceBitmap | 建立和处理位图 |
DrvDeleteDeviceBitmap | 删除位图 |
DrvDisableDriver | 通知驱动程序GDI不再需要它,并准备卸载它 |
DrvDisablePDEV | 通知驱动程序GDI不再需要一个特殊的打印设备或显示设备 |
DrvDisableSurface | 通知驱动程序GDI不再需要一个特殊的打印设备或显示设备 |
DrvEnableDriver | 为其它DDI函式返回函式指标并连接到DDI |
DrvEnablePDEV | 为GDI返回一个描述物理显示设备的PDEV |
DrvEnableSurface | 为GDI返回一个描述物理显示设备的PDEV |
DrvEndDoc(NULL) | 发送结束一个打印工作的讯息 |
DrvEscape | 获得一个在装置驱动程序接口中无效的设备的信息 |
DrvFillPath | 用画刷填充路径 |
DrvGetMasks | 为显示设备的目前模式获取彩色屏蔽 |
DrvGetModes | 列出显示设备支持的色彩模式 |
DrvMovePointer | 移动光标以保证不被GDI干扰 |
DrvPaint | 用画刷绘出一个指定区 |
DrvPowerHandler | 发出通、断电讯息 |
DrvRealizeBrush | 建立由GDI指定参数的画刷 |
DrvRealizeColor | 把一个RGB模式颜色映像成设备支持的最接近的色彩模式值 |
DrvSetPalette | 设置显示设备的调色板 |
DrvSetPointerShape | 设置新的光标形状并更新显示 |
DrvStartDoc(NULL) | 发送开始一个打印工作的讯息 |
DrvStartPage(NULL) | 发送开始打印新的一页的讯息 |
DrvStrokePath | 描绘路径 |
DrvTransparentBlt | 传送字节(支持透明化) |
DrvUnrealizeColor | 把一个显示设备色彩模式的颜色值映像成RGB色彩模式值 |
Windows CE GDI通过定义的结构体以及一些独立的C函式为显示驱动程序提供服务。定义的结构体为画刷、剪切区、调色板、描绘和填充路径提供支持。而其中独立的C函式提供设备位图和接口的支持。具体结构体、函式名称及其用途在表7.4中列出。
表7.4 GDI结构体和函式
结构体或函式名称 | 用途 |
BRUSHOBJ | 描述一个进行实线(或图案)描绘和填充操作的画刷对象的结构体 |
BRUSHOBJ_pvAllocRbrush | 为一个画刷分配内存的函式 |
BRUSHOBJ_pvGetRbrush | 获得一个指定画刷的函式 |
CLIPOBJ | 描述一个剪切区的结构体 |
CLIPOBJ_bEnum | 列出指定剪切区中剪切矩形的函式 |
CLIPOBJ_cEnumStart | 为列出指定剪切区中剪切矩形设置参数的函式 |
EngCreateDeviceBitmap | 由GDI建立一个设备位图句柄的函式 |
EngCreateDeviceSurface | 由GDI建立一个显示驱动程序可以管理的设备接口的函式 |
EngDeleteSurface | 通知GDI显示驱动程序不再需要一个设备接口的函式 |
PALOBJ_cGetColors | 将颜色复制到一个调色板上的函式 |
PATHDATA | 储存部分绘制路径的结构体 |
PATHOBJ_bEnum | 从绘制路径中列出PATHDATA记录的函式 |
PATHOBJ_vEnumStart | 通知PATHOBJ结构体驱动程序要呼叫函式并从绘制路径中列出直线或曲线的函式 |
PATHOBJ_vGetBounds | 为指定绘制路径获得边缘矩形的函式 |
XLATEOBJ | 在调色板之间转换和传递色彩的结构体 |
XLATEOBJ_cGetPalette | 从一个索引调色板中获得颜色的函式 |
Windows CE的显示驱动程序在许多方面与通常的装置驱动程序不同。最主要不同是他们不暴露I / O接口。因此,他们不被设备管理器管理,这样一来函式RegisterDevice便不能被他们呼叫。结果是没有特别的设备文件或其它文件系统入口来跟随记录动态的显示驱动程序。当应用程序呼叫带有显示驱动程序名的CreateDC函式时,显示驱动程序被加载并被初始化。对于系统预设的显示驱动程序,系统会自动对其加载。