图形设备接口(GDI:Graphics Device Interface)是Windows的子系统,它负责在视讯显示器和打印机上显示图形。正如您所认为的那样,GDI是Windows非常重要的部分。不只您为Windows编写的应用系统在显示视觉信息时使用GDI,就连Windows本身也使用GDI来显示使用者接口对象,诸如菜单、滚动条、图标和鼠标光标。
GDI原理
Windows 98和Microsoft Windows NT中的图形主要由GDI32.DLL动态链接库输出的函数来处理。在Windows 98中,这个GDI32.DLL实际是利用16位GDI.EXE动态链接库来执行许多函数。在Windows NT中,GDI.EXE只用于16位的程序。
这些动态链接库呼叫您安装的视讯显示器和任何打印机呼叫驱动程序中的例程。视讯驱动程序存取视讯显示器的硬件,打印机驱动程序将GDI命令转换为各种打印机能够理解的代码或者命令。显然,不同的视讯显示卡和打印机要求不同的设备驱动程序。
因为PC兼容机种上可以连接许多种不同的视讯设备,所以,GDI的主要目的之一是支持与设备无关的图形。Windows程序应该能够毫无困难地在Windows支持的任意一种图形输出设备上执行,GDI通过将您的程序和不同输出设备的特性隔离开来的方法来达到这一目的。
图形输出设备分为两大类:位映像设备和向量设备。大多数PC的输出设备是位映像设备,这意味着它们以图点构成的数组来表示图像,这类设备包括视讯显示卡、点阵打印机和激光打印机。向量设备使用线来绘制图像,通常局限于绘图机。
许多传统的计算机图形程序设计方式都是完全以向量为主的,这意味着使用向量图形系统的程序与硬件有着一定层次的隔离。输出设备用图素表示图形,但是程序与程序接口之间并不是用图素进行沟通的。您当然可以使用Windows GDI作为一个高阶的向量绘制系统,同时也可以将它用于比较低阶的图素操作。
从这方面来看,Windows GDI和传统的图形接口语言之间的关系,就如同C和其它程序设计语言之间的关系一样。C以它在不同操作系统和环境之间的高度可移植性而闻名,然而C也以允许程序写作者进行低阶系统呼叫而闻名,这些呼叫在其它高级语言中通常是不可能的。正如C有时被认为是一种「高级汇编语言」一样,您可以认为GDI是图形设备硬件之间的一种高阶界面。
您已经看到,Windows内定使用图素坐标系统。大多数传统的图形语言使用「虚拟」坐标系,其水平和垂直轴的范围在0到32,767之间。虽然有些图形语言不让您使用图素坐标,但是Windows GDI允许您使用两种坐标系统之一(甚至依据实际度量衡的坐标系)。您可以使用虚拟坐标系以便让程序独立于硬件之外,或者也可以使用设备坐标系而完全迎合硬设备提供的环境。
某些程序写作者认为一旦开始使用操作图素的程序设计方式,就放弃了设备无关性。我们在 上一章看到,这不完全是正确的,其中的诀窍是在与设备无关的方式中使用图素。这要求图形接口语言为程序提供一些方法来确定设备的硬件特征,并进行适当的调节。例如,在SYSMETS程序中,我们根据标准系统字体字符的图素大小来确定屏幕上的文字间距,这种方法允许程序针对分辨率、文字大小和方向比例各不相同的显示卡进行相应的调节。您将在本章看到一些用于确定显示尺寸的其它方法。
早期,许多使用者在单色显示器上执行Windows。即使是几年前,笔记本计算机也还只有灰阶显示。为此,GDI的设计保证了您可以在编写一个程序时不必太担心色彩问题-也就是说,Windows可以将色彩转换为灰阶显示。甚至在今天,Windows 98使用的视讯显示已经具有了不同的色彩能力(16色、256色、「high-Color」以及「true-color」)。虽然,彩色喷墨打印机的成本已经很低了,但是大多数使用者仍然坚持使用黑白打印机。盲目地使用这些设备是可以的,但是您的程序也应该能决定在某种显示设备上有多少色彩可以使用,从而最佳利用硬件功能。
当然,就如同您编写C程序时,为了使它在其它计算机上执行而遇到一些微妙的移植性问题一样,您也可能不小心让设备依赖性溜进您的Windows程序,这就是不与硬件完全隔离的代价。您还应该知道Windows GDI的局限。虽然可以在显示器上到处移动图形对象,但GDI通常是一个静态的显示系统,只有有限的动画支持。如果需要为游戏编写复杂的动画,就应该研究一下Microsoft DirectX,它提供了您需要的支持。
取得设备内容句柄
Windows提供了几种取得设备内容句柄的方法。如果在处理一个消息时取得了设备内容句柄,应该在退出窗口函数之前释放它(或者删除它)。一旦释放了句柄,它就不再有效了。对于打印机设备内容句柄,规则就没有这么严格。
最常用的取得并释放设备内容句柄的方法是,在处理WM_PAINT消息时,使用BeginPaint和EndPaint呼叫:
hdc = BeginPaint (hwnd, &ps) ;
其它行程序
EndPaint (hwnd, &ps) ;
变量ps是型态为PAINTSTRUCT的结构,该结构的hdc字段是BeginPaint传回的设备内容句柄。 PAINTSTRUCT结构又包含一个名为rcPaint的RECT(矩形)结构,rcPaint定义一个包围窗口显示区域无效范围的矩形。使用从BeginPaint获得的设备内容句柄,只能在这个区域内绘图。BeginPaint呼叫使该区域有效。
Windows程序还可以在处理非WM_PAINT消息时取得设备内容句柄:
hdc = GetDC (hwnd) ;
其它行程序
ReleaseDC (hwnd, hdc) ;
这个设备内容适用于窗口句柄为hwnd的显示区域。这些呼叫与BeginPaint和EndPaint的组合之间的基本区别是,利用从GetDC传回的句柄可以在整个显示区域上绘图。当然, GetDC和ReleaseDC不使显示区域中任何可能的无效区域变成有效。
Windows程序还可以取得适用于整个窗口(而不仅限于窗口的显示区域)的设备内容句柄:
hdc = GetWindowDC (hwnd) ;
其它行程序
ReleaseDC (hwnd, hdc) ;
这个设备内容除了显示区域之外,还包括窗口的标题列、菜单、滚动条和框架(frame)。GetWindowDC函数很少使用,如果想尝试用一用它,则必须拦截处理WM_NCPAINT消息,Windows使用该消息在窗口的非显示区域上绘图。
BeginPaint、GetDC和GetWindowDC获得的设备内容都与视讯显示器上的某个特定窗口相关。取得设备内容句柄的另一个更通用的函数是CreateDC:
hdc = CreateDC (pszDriver, pszDevice, pszOutput, pData) ;
其它行程序
DeleteDC (hdc) ;
例如,您可以通过下面的呼叫来取得整个屏幕的设备内容句柄:
hdc = CreateDC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
在窗口之外写入画面一般是不恰当的,但对于一些不同寻常的应用程序来说,这样做很方便(您还可通过在呼叫GetDC时使用一个NULL参数,从而取得整个屏幕的设备内容句柄,不过这在文件中已经提到了)。在 第十三章中,我们将使用CreateDC函数来取得一个打印机设备内容句柄。
有时您只是需要取得关于某设备内容的一些信息而并不进行任何绘画,在这种情况下,您可以使用CreateIC来取得一个「信息内容」的句柄,其参数与CreateDC函数相同,例如:
hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
您不能用这个信息内容句柄往设备上写东西。
使用位图时,取得一个「内存设备内容」有时是有用的:
hdcMem = CreateCompatibleDC (hdc) ;
其它行程序
DeleteDC (hdcMem) ;
您可以将位图选进内存设备内容,然后使用GDI函数在位图上绘画。