第4卷 图形驱动程序设计指南 第1部分 图形驱动程序 第2部分 显示器及视频微端口驱动程序 第3部分 打印机驱动程序及假脱机打印部件 第1部分 图形驱动程序 第1章 图形系统概述 第2章 GDI支持的图形驱动程序 第3章 对DDI的支持 第1章 图形系统概述 Microsoft Windows NT(/Windows 2000提供了一个健壮的图形体系结构,使第三方图形硬件公司能很容易地集成其视频显示器和打印设备。本书为编写有效的图形驱动程序提供了设计指南。可分为以下几个部分: Part 1:图形驱动程序 Part 1描述了图形驱动程序接口(GDI)和设备驱动程序接口(DDI),讨论了显示器和打印机驱动程序公共的设计和实现细节。 Part 2:显示器和视频微端口驱动程序 Part 2描述了Windows NT/Windows 2000的视频显示器环境,为显示器和视频微端口驱动程序编写者提供了设计和实现细节。 Part 3:打印机驱动程序和假脱机打印部件 Part 3描述了构成Windows NT/Windows 2000打印环境的驱动程序和假脱机打印部件,解释了如何提供驱动程序和假脱机打印部件的定制,以提供对新的打印机硬件和网络配置的支持。 Part 4:静态映像驱动程序 Part 4可以在在线DDK上找到,描述了用Microsoft静态映像体系结构(Microsoft STI)定义的COM接口,这对提供平板扫描仪和数字静态映像相机这些静态映像硬件的供应商是有用的。 图形驱动程序的术语表,在在线DDK上Design Guide的结尾可以找到,对图形子系统和驱动程序设计定义了详细的术语和缩略语。 图形驱动程序函数参考可以在在线DDK的Graphics Driver Reference中找到。 1.1 文档约定 本书使用的字体约定和所有其他的Microsoft Windows( 2000 DDK书中的约定是一样的。这些约定在驱动程序编写者指南中描述。 许多DDK视频和打印机代码例子中使用了匈牙利命名规则。匈牙利命名规则在平台SDK文档中描述。 第2章 对图形驱动程序的GDI支持 本章描述了Microsoft Windows NT(/Windows 2000图形设备接口(GDI),详细说明了GDI提供的对图形驱动程序的支持。 本书中术语“GDI”指的是核心模式GDI(也称作图形引擎);对Microsoft( Win32( DDI的引用是显式的。核心模式GDI也称作图形引擎。 在线Windows 2000 DDK Graphics Driver Reference中记录了GDI函数和结构参考。大多数GDI函数声明和结构定义在winddi.h中。对于显示器驱动程序,DirectDraw堆管理器函数在dmemmgr.h中声明。这两个文件都和Windows 2000 DDK一起发布。 2.1 从驱动程序的观点看GDI GDI是Windows NT(/Windows( 2000图形驱动程序和应用之间的中介支持。应用程序调用Win32( GDI函数进行图形输出请求,这个请求通过核心模式GDI发送。然后核心模式GDI把这些请求发送到相应的图形驱动程序,如显示器驱动程序或打印机驱动程序。核心模式GDI是一个不能被替代的系统提供的模块。 GDI通过一系列设备驱动程序接口(DDI)函数和图形驱动程序通信。这些函数用其前缀Drv标识。信息通过这些入口点的输入/输出参数在GDI和驱动程序之间传递。驱动程序必须支持某些DrvXxx函数用于GDI调用。在返回GDI之前,驱动程序通过执行在其相关硬件上相应的操作来支持GDI请求。 GDI本身包括许多图形输出能力,去除驱动程序中支持这些能力的需求就能减小驱动程序的大小。GDI也输出驱动程序能够调用的服务函数,进一步减小了驱动程序必须提供支持的图形输出能力。GDI服务函数用其Eng前缀标识,而提供访问GDI维护的结构的函数用XxxOBJ_Xxx的形式命名。 图2.1显示了这个通信流。 图2.1 图形驱动程序和GDI的相互作用 2.1.1 作为应用图形语言的GDI Win32 GDI和图形引擎都是完全与设备无关的。因此,应用不需要直接访问硬件。基于一个应用图形请求,GDI与设备无关的驱动程序一起工作,为一组图形设备提供高品质的图形输出。打印和显示设备使用相同的GDI代码路径。 2.1.2 作为绘制引擎的GDI 对于绘图操作,驱动程序首先必须对每个已经有效的PDEV结构启用一个表面。PDEV是一个物理设备的逻辑表示。如果硬件能够用GDI标准格式的位图建立,GDI就能用来进行一些或所有的位图表面的绘制。GDI也能处理高级的过渡调色技术。 对于启用PDEV和表面的信息,参考图形驱动程序参考中的DrvEnablePDEV和DrvEnableSurface函数。 2.1.2.1 GDI管理的位图 GDI全部用设备无关位图(DIB)格式管理位图,包括每个像素1位、4位、8位、16位、24位和32位。在这些位图上,GDI能进行所有的绘制直线、填充、文本输出以及位块传输(bitblt)操作。这使得驱动程序用GDI进行所有的图形绘制,或者使用实现函数都是可能的,因为硬件提供了特别支持。 如果设备在DIB格式中有帧缓冲区,GDI能够直接把一些或所有的图形输出到帧缓冲区,因而减小了驱动程序的大小。如果设备使用了非标准格式的帧缓冲区,驱动程序就必须实现所有要求的绘图函数。GDI还能模拟大多数绘图函数,尽管提高了性能上的代价:像素在被GDI操作之前必须拷贝到一个标准格式的位图中,并且在绘图完成后必须拷贝回原来的格式。 2.1.2.2 GDI管理的直线和曲线 GDI提供了改进的直线和曲线的定义。在DEVICE坐标中直线端点的坐标不要求是整数,就像Windows 3.x一样。这允许驱动程序不进行大致的取舍就传送图形对象。在GDI中基本的曲线是贝塞尔曲线(立方体锯齿)而不是一个椭圆。所有的GDI内部操作是用贝塞尔曲线处理的,它们被大多数高端的设备支持。对那些不处理贝塞尔曲线的设备,GDI在调用驱动程序绘制它们之前把曲线分割成直线段。 GDI能够下载用路径形式填充的区域,还有矩形的形式。驱动程序能够把路径分解成梯形或区间进行填充。 2.1.2.3 GDI管理的属性:画刷 GDI也管理所有的属性。GDI把属性作为画刷传递给驱动程序:驱动程序通过把画刷转换成有用的内部格式来实现它们。GDI为驱动程序维护这些转换信息。GDI还维护画刷所有的状态:包括范围、相关性、当前位置和线型。驱动程序能够缓存信息但不用来维护任何状态。除了初始化和画刷实现,GDI仅调用驱动程序在设备上绘图。在调用驱动程序之前,GDI关心转换、区域锁定以及指针与非操作。 当驱动程序要求使用还未实现的画刷,就需要回调GDI。GDI为画刷分配存储器并调用驱动程序来实现它,如果需要,可以进行抖动处理。 2.1.2.4 GDI过渡调色功能 GDI的过渡调色功能产生了高品质的抖动或颜色过渡调色映像,提供给没有内置这些功能的打印设备和显示设备。颜色过渡调色能提供: 在给定的设备上提供最高品质的可再现的彩色和灰度级别 在一组有限的强度级别上提高可视分辨率 在不同的输出设备之间改进彩色相关性 传统的模拟过渡调色是一个使用过渡调色屏幕的单元过程。这个屏幕由相等大小的单元组成,中心到中心是固定的单元间隔。固定单元间隔调节墨水的浓度,而点的尺寸可以改变,用来产生连续色调的印记。 在计算机上大多数打印或屏幕阴影也使用固定单元的像素尺寸。为模拟点大小的变化,用一束像素的组合来模拟过渡调色屏幕。在Windows NT(/Windows( 2000中,GDI包括提供一个好的第一近似的过渡调色缺省参数。额外的设备指定信息能加进系统以改进输出。 2.2 GDI/驱动程序的分工 为了理解图形驱动程序设计,弄懂GDI和驱动程序的角色以及它们如何协调是重要的。具有增强能力的GDI能够处理很多图形驱动程序预先要求的操作。GDI对于管理一些对图形操作很关键的数据结构,例如表面,也是有效的,尽管每个图形驱动程序必须访问它们。 2.2.1 GDI和驱动程序的通信 驱动程序对于GDI仅输出一个函数:DrvEnableDriver。所有其他的驱动程序支持的函数,包括DrvDisableDriver函数,是GDI通过一个指针数组来输出的。GDI调用DrvEnableDriver初始化驱动程序并返回驱动程序支持的DDI函数列表。因为有一些驱动程序必须支持的函数,所以GDI处理操作不包括在驱动程序的DrvEnableDriver例程的函数表中。当驱动程序被卸载时,GDI调用DrvDisableDriver。DDI函数在第3章“支持DDI”中深入讨论。 GDI使大量的服务对于驱动程序是可用的。这些服务分为两类:用户对象和服务例程。 2.2.1.1 GDI用户对象 GDI保护重要的内部数据结构,但通过把它们作为用户对象传递使驱动程序能访问这些结构的公共域。用户对象是中间数据结构,在GDI数据结构和需要访问这些结构内的信息的驱动程序之间提供了接口。驱动程序能把指向用户对象的指针传递回GDI,用以询问消息信息或请求各种服务。带有公共域的用户对象有下列优点: 排除了直接访问内部GDI数据结构相关的问题。 为驱动程序提供了一个空间保存GDI数据。例如,PATHOBJ结构能保存计算一个复杂对象(如路径)要求的所有额外数据。 下列用户对象是可用的: 对象 描述  BRUSHOBJ 为图形函数定义了画刷对象,输出直线、文本或填充。 驱动程序能调用BRUSHOBJ服务来实现画刷或找到GDI预先缓存的实现方法。  CLIPOBJ 为绘图或填充提供能访问裁剪区的驱动程序。这个区域能用一系列矩形计算。  FLOATOBJ 允许图形驱动程序模拟浮点操作。浮点操作不适用所有其他的核心模式驱动程序。  FONTOBJ 使驱动程序访问字体的一个特别的实例(或实现)的信息。  PALOBJ 包含RGB调色板颜色的结构;通过PALOBJ_cGetColors和DrvSetPalette函数可以访问。  PATHOBJ 定义路径指定要绘制什么(直线或贝塞尔曲线)。PATHOBJ结构传递给驱动程序用以描述要绘制或填充的一系列直线和贝塞尔曲线。  STROBJ 为驱动程序计算轮廓处理和位置的列表,描述如何绘制文本字符串。  SURFOBJ 识别一个表面,它可以是GDI位图、设备相关位图或设备管理的表面。更多的信息参见表面类型。  XFORMOBJ 描述一个任意的线性二维变换,如几何宽度直线(geometric wide line)。  XLATEOBJ 定义从源表面格式到目的表面格式转换像素所需要的变换。   2.2.1.2 GDI服务例程 GDI输出许多服务例程,其名字的形式是EngXxx。驱动程序动态地连接到win32k.sys来直接访问这些例程。GDI服务例程包括表面管理、绘图模拟以及路径、调色板、字体和文本服务。这些服务在GDI支持的服务中详细讨论。 2.2.2 PDEV协商 任何图形驱动程序的首要任务之一是在驱动程序初始化期间使PDEV有效。PDEV是物理设备的逻辑表现。这个表现由驱动程序定义,一般是私有的数据结构。启用PDEV的更多的信息参考DrvEnablePDEV。 通过DrvEnablePDEV函数,驱动程序必须给GDI提供信息,描述请求的设备及其能力。驱动程序给GDI的一条重要信息是DEVINFO结构的flGraphicsCaps和flGraphicsCaps2成员中的一组图形能力标志(GCAPS_Xxx和GCAPS2_Xxx标志)。 能力标志允许GDI确定那些操作是PDEV支持的。例如,GDI测试能力标志,指示在GDI尝试用这些基本的类型调用DrvStrokePath函数来绘制路径之前,PDEV是否能处理贝塞尔曲线和几何宽度直线。如果能力标志指示PDEV不能处理这些基本类型,GDI断开直线或曲线,使GDI能简化对驱动程序的调用。 从驱动程序一侧来看,无论何时驱动程序从GDI获得一个高级的路径相关的调用,如果路径或裁剪区对设备进行的处理过于复杂,它可返回 FALSE。 当驱动程序处理一条装饰线(cosmetic line)时它不能从DrvStrokePath返回FALSE,因为它必须为装饰线处理任意复杂的裁剪区或造型。然而,如果路径是贝塞尔曲线或几何直线,DrvStrokePath能够返回FALSE。当这种情况出现时,GDI把调用分割成简单的调用。就像能力标志位没有置1一样。例如,如果当DrvStrokePath发送一个几何直线时返回FALSE,GDI简化直线并调用DrvFillPath函数。 如果DrvStrokePath被报告一个错误,它必须返回DDI_ERROR。 在GDI和驱动程序之间的这种协商,对依赖于PDEV的函数,允许GDI和驱动程序产生高质量的输出而无须过多的通信。 2.2.3 表面协商 绘图和文本输出要求一个绘制的表面。这个表面由DrvEnableSurface函数创建并称作主表面(primary surface)。它也被称作屏幕上的表面(on-screen surface),因为它出现在视频显示器上。每个PDEV只能启用一个主表面,尽管一个驱动程序能够支持几个PDEV。支持DrvCreateDeviceBitmap函数的驱动程序能够创建和使用其他的表面。这些位图表面称作次要表面(secondary surface)或屏幕外的表面(off-screen surface)。对任一种类型的表面,驱动程序负责确定它支持的绘图操作的类型。 2.2.3.1 表面坐标 设备表面是228*228像素数组的子集。这些像素是由一对28位的带符号数寻址的。设备表面左上角的像素坐标是(0,0)。设备表面位于这个坐标空间的右下象限,两个坐标都是非负的。 2.2.3.2 DC原点 应用程序要求在227*227像素的数组范围内保存其图形。设备空间在DDI级有其他的尺寸,因为窗口管理器可能用一个带符号的27位的坐标即DC原点偏移应用程序的坐标。DC原点对驱动程序是不可见的,驱动程序在偏移执行之后才识别图形坐标。 2.2.3.3 FIX坐标 DDI使用分数坐标,能够在设备表面上1/16像素的范围内表示一个位置。(在矢量设备上,分数坐标比设备分辨率精确16倍。)分数坐标用32位数字表示,是带符号的28.4位FIX表示法。在这种表示法中,高28位表示坐标的整数部分,最低的4位表示分数部分。例如,0x0000003C等于+3.7500,0xFFFFFFE8等于-1.5000。 FIX坐标表示直线和贝塞尔曲线的控制点。对某一对象,如矩形裁剪区,GDI用带符号的32位整数表示坐标。因为坐标是28位数,整数坐标最高的5位或者全清0或者全置1。 2.2.3.4 表面类型 表面类型在如何处理它们的上下文中可以看到。这些类型如下: 引擎管理的表面 设备管理的表面(标准格式位图) 设备管理的表面(非标准表面) 引擎管理的表面 引擎管理的表面有以下特征: 由GDI创建和管理。 用一种标准的DIB格式作为DIB创建:从上至下,原点位于左上角,或者从底向上,原点位于左下角。 类型为STYPE_BITMAP。 表面没有相应的设备句柄。 标准格式位图是单平面、压缩像素(每个像素的数据用连续的方式存储)格式的位图。位图的每条扫描线在4字节的边界排列。 在EngCreateBitmap函数中创建的位图是DIB格式。为了使引擎能管理位图,必须是DIB格式。 设备管理的表面(标准格式位图) 设备管理的表面有以下特征: 通过对设备驱动程序的DrvCreateDeviceBitmap函数的调用创建。 有一个表面的相关设备句柄(DHSURF;参见在线DDK Graphics Driver Reference中的SURFOBJ。) 可以是透明的或不透明的。 不透明的设备管理表面是一种GDI既没有任何有关位图格式的信息,也没有位图中位的参考信息的表面。因为这些原因,驱动程序最少必须支持DrvBitBlt、DrvTextOut和DrvStockePath函数。这样的表面的类型是STYPE_DEVICE。 透明的设备管理表面是一种GDI含有有关位图格式的信息并知道位图中位的位置的表面。因为这个原因,驱动程序不需要实现任何绘图操作,并使它们都服从GDI。这样的表面的类型是STYPE_BITMAP。 驱动程序为了转换不透明的位图到透明的位图,它必须调用EngModifySurface函数。通过这一调用,驱动程序通知GDI位图格式和在存储器中位图的位置。 设备管理的DIB表面允许驱动程序回调GDI使GDI在表面绘图。管理其自身表面的驱动程序,也能通过在其表面周围封装DIB(用EngCreateBitmap创建)而引用到GDI的回调,使用DIB除外。 设备管理的表面(非标准格式位图) 通过调用EngCreateDeviceSurface函数使GDI创建表面并返回一个句柄,驱动程序可以启用一个设备管理的非DIB表面。GDI依赖驱动程序访问和控制绘制到何处,并从设备管理的表面读出。 设备相关位图(DDB),有时称作设备格式位图,是另一种类型的非DIB、设备管理的表面。DDB支持某些驱动程序,如VGA驱动程序,实现快速的位图到屏幕的块传送。DDB也允许驱动程序在屏幕外的显示存储器绘制非DIB位图。如果请求了DDB,驱动程序能支持DrvCreateDeviceBitmap函数并调用EngCreateDeviceBitmap函数使引擎返回一个到位图的句柄。 2.2.3.5 GDI彩色空间转换 GDI使用三个RGB颜色空间描述它的位图。在每个颜色空间中,三个位域,或颜色通道,在给定的颜色中分别用来指定红、绿、蓝使用的位的数量。为了能匹配GDI的位图能力,显示器驱动程序必须能从一个RGB颜色空间转换到另一个。 GDI能识别下列的颜色空间。 5,5,5RGB:红、绿、蓝都是5位颜色通道。 5,6,5RGB:红色是5位颜色通道,绿色是6位颜色通道,蓝色是5位颜色通道。 8,8,8RGB:红、绿、蓝都是8位颜色通道。 通常,当从一个多位的颜色通道向少位的颜色通道转换时,GDI丢弃掉低位。当从一个少位的颜色通道向多位的通道转换时,较小通道的所有位全部拷贝到较大的通道。为了填充较大通道的剩余位,较小通道的某些位将再次拷贝到较大的通道。下表概述了GDI从一个RGB颜色空间转换到另一个所使用的规则。在这个表中,转换过程中值发生改变的颜色通道用黑体表示。 GDI颜色空间转换规则 From To 规则 例子  5,5,5 5,6,5 源的绿色通道的最高有效位(MSB)加到目标的绿色通道的低位最后。 (0x15,0x19,0x1D)变成(0x15,0x33,0x1D)。 注意只有绿色通道改变。 源的5位通道的值是二进制11001,转换成6位值,110011。  5,5,5 8,8,8 对每个通道,源通道的3个MSB加到目标通道的最低位的最后。 (0x15,0x19,0x1D)变成(0xAD,0xCE,0xEF)。 在红色通道中,10101变成10101101。类似的变化也出现在绿色和蓝色通道。  5,6,5 5,5,5 丢弃源绿色通道的最低有效位(LSB)。 (0x15,0x33,0x1D)变成(0x15,0x19, 0x1D)。 注意只有绿色通道改变。 丢弃110011的最低位,得到11001。  5,6,5 8,8,8 对源的5位通道(红色和蓝色),从源通道拷贝3个MSB加到目标通道的最低位的最后。对6位的绿色通道,从源通道拷贝2个MSB加到目标通道的最低位的最后。 (0x15,0x33,0x1D)变成(0xAD,0xCE,0xEF)。 在红色通道中,10101变成10101101。在绿色通道中,110011变成11001111。蓝色通道的变化和红色通道类似。  8,8,8 5,5,5 丢弃源通道的3个最低有效位(LSB)。 (0xAB,0xCD,0xEF)变成(0x15,0x19,0x1D)。 在红色通道中,10101101变成10101。类似的变化也出现在其他两个通道。  8,8,8 5,6,5 丢弃红色和蓝色通道的3个最低有效位(LSB)。丢弃绿色通道的2个最低有效位(LSB)。 (0xAB,0xCD,0xEF)变成(0x15,0x33,0x1D)。 在绿色通道中,11001101变成110011。红色和蓝色通道的变化与前面列出的变换相同。   2.2.3.6 Hooking和Punting 术语Hooking和Punting指的是驱动程序决定是否提供标准的位图绘画操作,或依赖GDI提供这些操作。如果驱动程序实现了引擎管理的表面,GDI能处理所有的绘图操作。然而,如果硬件能加速这些操作,驱动程序能提供一个或更多的绘图函数。通过实现,或Hooking DrvXxx函数来做到这一点。 也许只想实现绘图操作的一个子集,实现一个特殊的DDI接口点。对它不支持的任何操作,驱动程序能调用相应的GDI函数来实现。这称作Punting到GDI。有一些情形是操作必须在驱动程序内实现。例如,如果驱动程序实现了一个设备管理的表面,某些绘图函数必须在显示器驱动程序内完成。 Hooking 缺省情况下,当绘图表面是引擎管理的表面时,GDI处理绘画(绘图)操作。驱动程序利用硬件对一给定的表面,为一些或所有的绘图函数提供了加速,或者使用了特别的块传送硬件,能够hook这些函数。对于hook调用,驱动程序把hook指定为EngAssociateSurface和EngModifySurface函数flHook参数的标记。 如果驱动程序指定了一个函数的hook标记,它必须提供在其支持DDI入口点的列表中的函数。驱动程序能够优化具有硬件支持的操作。这样的驱动程序在一个hook调用中可能只能处理某一种情况。例如,如果在一个hook调用中请求了复杂的图形,它可能还要更有效地直接回调GDI,允许GDI处理操作。 这里还有另一个例子,驱动程序选择是否处理hook调用。考虑应该支持硬件的驱动程序,有能力用某些ROP处理位块传输调用。即使驱动程序能够独立地实现许多操作,其他方面仅是一个缓冲区。这样的驱动程序将为帧缓冲区给位图表面返回一个句柄,就像为其PDEV表面一样,但它将为自己hook DrvBitBlt调用。当GDI调用DrvBitBlt时,驱动程序能够检查ROP来看它是否是由硬件支持的一个函数。如果不是,驱动程序能通过对EngBitBlt的一个调用把操作传递GDI。 支持设备管理表面的驱动程序必须向外hook一些绘图函数,名字是DrvCopyBits、DrvTextOut和DrvStrokePath。虽然GDI模拟能够处理其他绘图函数,因为性能上的原因推荐这种类型的驱动程序hook其他函数,就像DrvBitBlt和DrvRealizeBrush函数,因为模拟需要从或向表面绘图。 Punting Punting回调到GDI的意思是提交一个调用到相应的GDI模拟。通常,对每个DrvXxx调用都有一个相应的GDI EngXxx模拟调用,带有相同的参数。在驱动程序制作透明的位图时,所有的参数都没有改变地传递到GDI模拟。对每个调用驱动程序punt回GDI,驱动程序的大小缩减了(因为功能上的代码忽略了)。然而,因为引擎拥有调用,驱动程序在执行速度上没有控制。对一些复杂的情况,在驱动程序中提供支持也许没有实际的优势。 可以hook的GDI图形输出函数 驱动程序能够hook的图形输出函数和相应的GDI模拟在下表中列出。 驱动程序图形输出函数 相应的GDI模拟  DrvBitBlt EngBitBlt  DrvPlgBlt EngPlgBlt  DrvStretchBlt EngStretchBlt  DrvStretchBltROP EngStretchBltROP  DrvTextOut EngTextOut  DrvStrokePath EngStrokePath  DrvFillPath EngFillPath  DrvStrokeAndFillPath EngStrokeAndFillPath  DrvLineTo EngLineTo  DrvCopyBits EngCopyBits  DrvAlphaBlend EngAlphaBlend  DrvGradientFill EngGradientFill  DrvTransparentBlt EngTransparentBlt   2.3 GDI支持的服务 GDI输出许多服务例程,能简化驱动程序的设计。驱动程序能够直接调用这些例程。通常图形引擎服务例程的名字以Eng开头。与特殊的对象相关的服务例程总是用对象的名字开头;例如,CLIPOBJ_cEnumStart是一个CLIPOBJ服务。 注意 第一个参数是用户对象的指针的任务例程,是用户对象的方法和使用通常的C++惯例调用。因此,用C++写的驱动程序能作为方法访问服务例程。 这些服务例程有以下几类: 表面管理 调色板服务 路径服务 绘图服务 字体和文本服务 第3章“支持DDI”描述了DDI的入口点,并解释许多这些服务例程能用来帮助驱动程序实现入口点。每个服务函数的详细描述参见在线DDK。 2.3.1 GDI对表面的支持 对每个PDEV,驱动程序必须支持DrvEnableSurface函数。DrvEnableSurface建立要绘制的表面并将其和PDEV相关。驱动程序也必须支持DrvDisableSurface函数停止创建的表面。因为GDI创建和维护表面,驱动程序依赖几个GDI服务函数(在下页的表中列出)实现启用和禁止表面。 函数名 用途  EngAssociateSurface 用PDEV相关一个表面并定义驱动程序编写者要hook的那个表面的绘图操作。它使用PDEV缺省的调色板和方式步骤。驱动程序在DrvEnableSurface的执行过程中必须对主表面进行调用。当驱动程序在锁定表面进行写之前启用第二个表面时,也必须进行这个调用。  EngCheckAbort (仅针对打印机)使打印机驱动程序能够测定打印机的工作是否已经结束。  EngCreateBitmap 创建一个标准格式DIB。在这种类型表面上GDI能够执行所有绘图操作。  EngCreateDeviceBitmap 创建一个DDB,驱动程序在上面绘图是可靠的(虽然也可创建一个DIB,在这种情况下驱动程序能够回调以使GDI在上面绘图。  EngCreateDeviceSurface 创建一个设备管理表面。驱动程序为这个表面管理某些绘图操作是可靠的。此函数又返回一个驱动程序管理句柄。  EngDeleteSurface 删除一个表面(DIB,DDB,或设备管理表面)  EngEraseSurface 在表面上用给定的颜色填充指定的矩形,有效地清除表面。调用这个函数仅可清除一个GDI位图表面。  EngLockSurface 通过创建表面的一个用户对象(SURFOBJ)使驱动程序访问已创建的表面。(源表面是不锁定的。)  EngMarkBandingSurface (仅针对打印机)标记表面为相关的表面。  EngUnlockSurface 当驱动程序已完成一项绘图操作时释放一个表面(禁止第二个表面时调用此函数)   这些函数更详细的内容,请参考在线DDK。 2.3.2 GDI支持的调色板 GDI可以做大多数有关调色板管理的工作。当GDI调用DrvEnablePDEV函数,驱动程序把缺省的调色板作为DEVINFO结构的一部分返回到GDI。驱动程序必须用EngCreatePalette函数创建这个调色板。 调色板有效地把32位颜色索引映射到24位RGB颜色值,这是GDI使用调色板的方法。驱动程序指定其调色板,这样GDI能确定不同的颜色索引如何在设备上出现。 驱动程序只要使用GDI提供的XLATEOBJ,就不需要处理大多数调色板操作和计算。 如果设备支持可改变的调色板,必须实现DrvSetPalette函数。当应用为设备改变调色板并把作为结果的新调色板传递给驱动程序时,GDI调用DrvSetPalette。驱动程序要设置内部的硬件调色板,尽可能地匹配新调色板。 可以用下表列出的两种格式为GDI定义调色板。 调色板格式 描述  Indexed 一个颜色索引是RGB值的数组的一个索引。这一数组可以是小的,例如,包括16个颜色索引;或者是大的,例如,包括4096个颜色索引,甚至更多。  Bit Fields 颜色索引里的位域依据每一种颜色里R、G和B的数量指定颜色。例如,5位能够用来为每一种颜色提供0和31之间的一个值。当转换为RGB时,每个部分的5位的值将放大到包括0~255的范围。(通常RGB代表自己是由位域定义的)   相反,GDI一般使用调色板映射。也就是说,应用指定一个RGB颜色进行绘图,并且GDI必须指定设备显示的颜色索引的地址。就像下表指出的,GDI提供两种主要的调色板服务函数用来创建和删除调色板,以及一些与PALOBJ和XLATEOBJ有关的服务函数用于在两个调色板之间转换颜色索引。 函数 描述  EngCreatePalette 创建一个调色板。驱动程序通过给DEVINFO结构里的调色板返回一个句柄使设备和调色板相联系。  EngDeletePalette 删除给定的调色板。  PALOBJ_cGetColors 允许驱动程序从从一个索引的调色板下载RBG颜色。它由显示驱动程序在DrvSetPalette函数中调用。  XLATEOBJ_iXlate 从一个源颜色索引转换到目的颜色索引  XLATEOBJ_piVector 从一个索引的源调色板中检索转换向量。驱动程序能使用这个向量完成其自身从源索引到目的索引的转换。  XLATEOBJ_cGetPalette 在一个索引的源调色板中检索24位RGB颜色或颜色的位域格式驱动程序能使用这个函数从调色板获得信息执行颜色混合。   这些函数更详细的内容,请参考在线DDK。 2.3.3 路径的GDI服务 为帮助矢量设备填充复杂的区域,驱动程序能调用创建、修改和计算路径的引擎函数;这些函数在下表列出。驱动程序通过PATHOBJ结构访问路径。 GDI路径服务函数 描述  EngCreatePath 为驱动程序的临时使用分配一个路径。驱动程序在从当前的绘图调用返回GDI之前要删除这个路径。  EngDeletePath 删除通过EngCreatePath函数创建的路径。  PATHOBJ_vGetBounds 返回路径的一个混合矩形。  PATHOBJ_vEnumStart 通知PATHOBJ驱动程序将调用PATHOBJ_bEnum用指定的路径计算曲线。这个函数假设计算重新开始时必须要调用。  PATHOBJ_bEnum 从路径检索下一个PATHDATA记录。每个记录描述了一个子路径的全部或部分。  PATHOBJ_vEnumStartClipLines 允许驱动程序请求相对一个PATHOBJ要裁剪的直线。当裁剪区比一个矩形更复杂时是有用的。  PATHOBJ_bEnumClipLines 从路径计算已裁剪的直线段  PATHOBJ_bMoveTo 在一个已定义的PATHOBJ路径里改变现有的位置  PATHOBJ_bPloyLineTo 在一个已定义的PATHOBJ路径里绘制直线  PATHOBJ_bPloyBezierTo 在一个已定义的PATHOBJ路径里绘制贝塞尔曲线  PATHOBJ_bCloseFigure 通过正绘制的一条直线回至起始点关闭路径  这些函数更详细的内容,请参考在线DDK。 2.3.4 GDI绘图服务及其他 为了支持CLIPOBJ,BRUSHOBJ,和XFORMOBJ结构,GDI提供几种绘图服务(在下页的表中列出)此外GDI模拟在前面hooking对punting中描述。 GDI绘图服务函数 描述  EngCreateClip 为驱动程序的临时使用分配一个CLIPOBJ。当不再需要它时驱动程序调用EngDeleteClip函数删除它。  EngDeleteClip 用 EngCreateClip函数删除已分配的CLIPOBJ  EngBitBlt DrvBitBlt函数的GDI模拟  EngCopyBits DrvCopyBits函数的GDI模拟  EngStretchBlt DrvStretchBlt函数的GDI模拟  EngFillPath DrvFillPath函数的GDI模拟  EngStrokeAndFillPath DrvStrokeAndFillPath函数的GDI模拟  EngStrokePath DrvStrokePath函数的GDI模拟  EngLineTo DrvLineTot函数的GDI模拟  BRUSHOBJ_pvAllocRbrush 为实现驱动程序的画刷分配存储器。  BRUSHOBJ_pvGetRbrush 为实现驱动程序的画刷返回一个指针;如果还没有实现画刷就实现画刷。  CLIPOBJ_cEnumStart 在已裁剪的全部或部分区域里为矩形的计算设定参数。(没有调用这个函数时能够计算一次这个区域,但是随后的计算需要使用这个函数。)  CLIPOBJ_bEnum 从裁剪区域恢复大量矩形。  CLIPOBJ_ppoGetPath 作为路径用来恢复复杂的区域。  这些函数更详细的内容,请参考在线DDK。 2.3.5 GDI字体和文本服务 GDI提供对字体管理和文本输出的支持。FONTOBJ结构和相关函数允许驱动程序访问一个字体的特定实例。为了支持文本输出,驱动程序访问STROBJ结构和相关函数。下表列出了FONTOBJ和STROBJ相关的函数。 函数 描述  EngT ExtOut GDI模拟DrvTextOut函数  FONTOBJ_cGetAllGlyphHandles 允许驱动程序恢复GDI字体的每一个字形句柄。驱动程序使用这个服务下载全部字体。  FONTOBJ_vGetInfo 返回描述已分配字体的信息。  FONTOBJ_cGetGlyphs 为字体使用者把字形句柄翻译成指针给已分配的字形数据。到下一次调用FONTOBJ_cGetGlyphs之前这些指针均有效。  FONTOBJ_pQueryGlyphAttrs 返回有关字体字形的信息。  FONTOBJ_pxoGetXform 为相关的字体恢复从抽象到设备的转换。这个转换要求驱动程序实现一个驱动程序提供的字体。  FONTOBJ_pifi 取得一个到描述相关字体的IFIMETRICS结构的指针。  FONTOBJ_pvTrueTypeFontFile 取得一个到与TrueType字体相关的ROM映射TrueType文件的指针。  STROBJ_vEnumStart 为指定的STROBJ结构重新计算GLYPHPOS数组。驱动程序要在随后的计算之前调用这个函数。  STROBJ_bEnum 在指定的STROBJ中计算字体轮廓一致性和位置。  STROBJ_dwGetCodePage 返回与指定的STROBJ相关的代码页。  这些函数更详细的内容,请参考在线DDK。 2.3.6 GDI支持的服务及其他 GDI给驱动程序编写者提供了多种一般的支持服务,包括装载、读取、操作数据文件的能力,以及分配内核模式和用户模式的存储器。 2.4 浮点数支持 内核模式图形驱动程序在调用GDI提供的EngSaveFloatingPointState和EngRestoreFloatingPointState 例程之间必须进行所有的浮点操作。 如果硬件有一个浮点处理器,驱动程序能够直接做浮点操作。另外,驱动程序能使用GDI的FLOATOBJ服务来计算浮点操作。不考虑处理器类型,当声明浮点值时,驱动程序要使用FLOAT数据类型。 2.5 数据类型 下表中定义的数据类型在设备驱动程序接口里出现。这些数据类型中的几个在前面GDI用户对象里描述过。指针型数据类型用星号(*)标记。 DDI数据类型 变量前缀名 定义  BOOL b 可以是TRUE或FALSE的32位值  BYTE j 8位无符号整数  BRUSHOBJ* pbo 指向画刷对象的指针  CLIPLINE cl 裁剪线对象  CLIPOBJ pco 裁剪对象的指针  DHPDEV dhpdev 由设备驱动程序定义的32位句柄。它识别一个物理设备。  DHSURF dhsurf 由设备驱动程序定义的32位句柄。它识别一个设备管理表面。  FIX fix 定点数  FLOATL e 浮点数  FLOAT-LONG el 32位溢出值,依据上下文解释成LONG或FLOAT  FLONG fl 32位标志的设定  FONTOBJ* pfo 字体对象的指针  FSHORT fs 16位标志的设定  HBM hbm 由GDI定义能识别位图的32位句柄  HPAL hpal 由GDI定义能识别调色板的32位句柄  HSURF hsurf 由GDI定义能识别表面的32位句柄  LONG l 32位有符号整数  MIX mix 32位数量,它的低16位定义前景和背景的混合模式  PALOBJ* ppalo 调色板对象指针  PATHOBJ* ppo 路径对象指针  POINTE pte 由{FLOAT x,y;}组成的小数点结构  POINTFX ptfx 由{FIX x,y;}组成的小数点结构  POINTL ptl 由{LONG x,y;}组成的小数点结构  PWSZ pwsz 以0结束的Unicode字符串的指针  PVOID pv 没有定义数据类型的VOID指针  RECTL rcl 由{LONG xLeft,yTop,xRight,yBottom;}组成的矩形结构  RECTFX rcfx 由{FIX xLeft,yTop,xRight,yBottom;}组成的矩形结构  ROP4 rop4 指定如何混合源、目标、图形以及屏蔽像素的32位值  SHORT s 16位有符号整数  SIZEL sizl 由{ULONG Cx,Cy;}组成的二维结构  STROBJ* pstro 文本字符串对象指针  SURFOBJ* pso 表面对象指针  ULONG ul 32位无符号整数  USHORT us 16位无符号整数  XFORMOBJ* pxo 坐标转换对象指针  XLATEOBJ* pxlo 颜色转换对象指针   下表中列出的参数前缀用于修改它们使用的变量名前缀。 前缀 参数用途  i 已计算的索引  c 计数  p 指针   第3章 支持DDI 为响应通过图形设备接口(GDI)传送的设备无关应用调用,图形驱动程序必须确认其图形设备产生请求的输出。图形驱动程序通过实现必须的图形驱动程序接口(DDI)控制图形输出。 DDI函数用DrvXxx的形式命名。GDI调用这些DrvXxx函数传递数据到驱动程序。当应用进行GDI请求并且DDI确定驱动程序支持相应的函数,GDI调用这个函数。驱动程序对提供函数是可靠的并且在函数的实现中返回到GDI。 这一章描述显示器和打印机驱动程序编写者必须明白的DDI函数。DDI函数声明、结构定义和常量在winddi.h中可以找到。这个头文件和Microsoft( Windows( 2000 DDK一起发布 。每个DDI入口点函数、GDI服务函数以及DDI结构和类型的详细信息参见在线DDK的Graphics Driver Reference。 3.1 图形驱动程序函数支持 这一节描述驱动程序入口点函数,把它们归类为必需的、有条件必需的和可选的,后一节提供实现信息,对一般的图形驱动程序入口点从功能类别上组织。这个指南的第二和第三部分分别给出了显示器驱动程序和打印机驱动程序详细的设计信息。 当设备驱动程序返回一个错误,一般调用GDI的EngSetLastError函数报告扩展的错误代码。应用程序可以取得这个错误代码。 3.1.1 必需的图形驱动程序函数 所有的图形驱动程序必须支持GDI调用的入口点,启用或禁止驱动程序、PDEV结构和表面与每个PDEV相关。下表以一般调用使用的顺序列出了需要的函数。 入口点 描述  DrvEnableDriver 作为初始化驱动程序入口点,这个函数把驱动程序版本号以及所支持的任意函数的入口点提供给GDI。  DrvGetModes 由指定的视频硬件设备列出所支持的模式。(只有显示器驱动程序需要这个函数)  DrvEnablePDEV 启用PDEV。  DrvCompletePDEV 通知驱动程序设备安装已完成。  DrvEnableSurface 为指定的硬件设备创建一个表面。  DrvDisableSurface 通知驱动程序不再需要为当前设备已创建的表面。  DrvDisablePDEV 当不再需要硬件时,释放已创建但还没有删除、由设备和一些表面使用的存储器和资源。  DrvDisableDriver 为驱动程序释放所有已分配的资源并把它的初始化状态返回硬件。  DrvAssertMode 为指定的硬件设备重新设置视频模式。(只有显示器驱动程序需要这个函数)   3.1.2 有条件必需图形驱动程序函数 除了经常请求的函数,还有其他一些函数可能请求,这依赖于驱动程序如何实现。条件请求函数在下表中列出。如果驱动程序管理其自身的主表面(使用EngCreateDeviceSurface函数取得表面的句柄)或者其自身的屏幕外位图,它也必须支持几个绘图函数。驱动程序向标准格式的DIB写时通常允许GDI管理大多数或全部的这些操作。支持可设置调色板的显示器必须也支持DrvSetPallete函数。 对于打印机驱动程序定义或绘制字体比显示器更加普通。显示器驱动程序不要求处理字体。如果硬件有驻留的字体,驱动程序必须对GDI支持有关这个字体的信息。这个信息包括规格、从Unicode到个体字体轮廓识别、个体字体轮廓属性和字距调整表的映射。 入口点 请求 描述  DrvCopyBits 设备管理表面 源到目标的拷贝  DrvDescribePixelFormat 在单一表面上具有不同像素格式、支持windows的显示器 描述PDEV的像素格式  DrvGetTrueTypeFile TrueType字体驱动程序 使GDI访问映射存储器TrueType字体文件  DrvLoadFontFile 字体驱动程序 为实现字体指定使用的文件  DrvQueryFont 打印机驱动程序 为已给字体检索GDI结构  DrvQueryFontCaps 字体驱动程序 询问驱动程序字体驱动程序的容量  DrvQueryFontData 打印机驱动程序 检索有关已实现字体的信息  DrvQueryFontFile 字体驱动程序 询问驱动程序字体文件信息  DrvQueryFontTree 打印机驱动程序 查询子目录结构定义的字体映射的三种类型之一  DrvQueryTrueTypeOutline TrueType字体驱动程序 返回TrueType字形句柄给GDI  DrvQueryTrueTypeTable TrueType字体驱动程序 使GDI访问TrueType字体文件  DrvResetPDEV 在文档里允许模式改变的设备 从旧PDEV到新PDEV传送驱动程序状态  DrvSetPalette 支持可设置调色板的显示器 为指定设备实现调色板  DrvSetPixelFormat 在单一表面上具有不同像素格式、支持windows的显示器 设定窗口的像素格式  DrvStrokePath 设备管理表面 在显示器上实现路径  DrvSwapBuffers 具有成对缓冲器、支持像素格式的驱动程序 显示表面隐藏的缓冲器的目录  DrvTextOut 设备管理表面或定义字体的驱动程序 在指定位置实现一组字符映像(图示符)  DrvUnloadFontFile 字体驱动程序 通知驱动程序不需要字体文件   3.1.3 可选的图形驱动程序函数 为了减小驱动程序的大小,驱动程序编写者通常只加进硬件支持最好的几个可选的函数。例如,支持图象颜色管理(ICM)的硬件驱动程序能实现DrvIcmXxx函数。下表列出了图形驱动程序能选择实现的函数。 显示器和打印机驱动程序函数 入口点 描述  DrvAlphaBlend 提供具有(混合能力的位块传送。  DrvBitBlt 执行一般的从和向表面的位块传送  DrvCreateDeviceBitmap 用驱动程序定义的格式创建和管理位图。  DrvDeleteDeviceBitmap 删除设备管理位图  DrvDitherColor 请求设备创建一个相对设备调色板抖动的画刷。  DrvFillPath 为设备管理的表面绘制一个闭合的路径。  DrvGradientFill 屏蔽已指定的原语  DrvIcmCheckBitmapBits 检查在指定位图里的像素是否位于指定转换的设备范围内  DrvIcmCreateColorTransform 创建ICM颜色的变换  DrvIcmDeleteColorTransform 删除已指定的ICM颜色的变换  DrvIcmSetDeviceGammaRamp 设置指定的显示器设备的硬件(斜面  DrvLineTo 绘制单一的实心装饰线。  DrvPlgBlt 在设备管理和GDI管理表面的结合之间提供旋转的位块传送能力。  DrvRealizeBrush 为已定义的表面实现指定的画刷  DrvStretchBlt 在设备管理和GDI管理表面之间允许加长的位块传送。  DrvStretchBltROP 使用ROP实现加长的位块传送。  DrvStrokeAndFillPath 同时填充和识别路径。  DrvSynchronize 在GDI和显示器驱动程序支持的协处理器设备之间协调绘图操作;这仅针对引擎管理表面。  DrvSynchronizeSurface 在GDI和显示器驱动程序支持的协处理器设备之间协调绘图操作;这仅针对引擎管理表面。如果驱动程序提供DrvSynchronize和DrvSynchronizeSurface,GDI将仅能调用DrvSynchronizeSurface.  DrvTransparentBlt 提供透明的位块传送能力。   仅由显示器驱动程序使用的函数 入口点 描述  DrvMovePointer 把指针移动至新位置并刷新它。  DrvSaveScreenBits 存储或恢复屏幕上指定的矩形。(仅针对显示器驱动程序)  DrvSetPointerShape 如果驱动程序已完成绘图,从屏幕上删除指针,然后设置一个新的指针形式。   主要由打印机驱动程序使用的函数 入口点 描述  DrvDestroyFont 通知驱动程序不再需要认识字体;驱动程序释放已分配的数据结构。  DrvDrawEscape 完成绘图类型退出的函数。  DrvEscape 在独立设备DDI里从无效设备查询信息。  DrvFree 释放同指示数据结构相关联的字体存储器。   仅由打印机驱动程序使用的函数 入口点 描述  DrvEndDoc 发送文档结束的信息。  DrvFontManagement 允许访问不能直接通过GDI可用的打印机功能。  DrvGetGlyphMode 返回作为特殊字体存储的字体类型信息。  DrvNextBand 了解表面刚刚绘图的区域的内容。  DrvQueryPerBandInfo 为指定相关的打印机表面返回相关信息。  DrvSendPage 发送从表面至打印机的写后读位。  DrvStartBanding 为驱动程序准备分区。  DrvStartDoc 发送文档开始控制的信息。  DrvStartPage 发送页首控制的信息。   字体驱动程序函数 入口点 描述  DrvQueryAdvanceWidths 为指定的一组字形提供增加宽度的字符。   3.2 支持初始化和终止函数 图形驱动程序能支持多个设备和每个设备的多个并发使用。因此,初始化和终止发生在3个不同的层次,每个层次都有其自己的定时。初始化以下列顺序发生: 驱动程序初始化 PDEV初始化 表面初始化 终止以相反的顺序发生。 3.2.1 驱动程序初始化和清除 当设备驱动程序要实现几个或多个函数,它只向GDI输出DrvEnableDriver。驱动程序通过一个函数表输出其他的支持的函数。GDI向设备驱动程序调用的第一个函数是DrvEnableDriver。在这个函数中,驱动程序在传递进的DRVENABLEDATA结构内填充,这样GDI能确定支持其他的哪个DrvXxx函数以及它们的位置在哪里。在DRVENABLEDATA结构中驱动程序提供下列信息: GDI的DDI版本号。这个常量在winddi.h中定义为DDI_DRIVER_VERSION。 一个指向DRVFN结构的数组的指针,这个结构列出了支持的函数和它们的索引。 在数组中DRVFN结构的成员。 为了使GDI调用一个函数和驱动程序的启用和禁止函数不同,驱动程序必须使函数名和位置对GDI可用。 虽然DrvEnableDriver也能完成一次初始化,如信号的分配,驱动程序在DrvEnableDriver期间不能实际启用硬件。硬件初始化要在驱动程序的DrvEnablePDEV函数中发生。同样地,驱动程序在DrvEnableSurface函数中使表面有效。 GDI调用DrvDisableDriver函数通知驱动程序它将要卸载。为响应这个调用,驱动程序要释放在这一点它分配的所有资源和存储器。 如果硬件需要重新设置,GDI调用驱动程序的DrvAssertMode函数。 3.2.2 PDEV初始化和清除 每个核心模式图形驱动程序表示一个GDI管理的逻辑设备。反过来,一个驱动程序能管理一个或多个PDEV结构。PDEV是物理设备的逻辑表现。它由硬件的类型、逻辑地址和能支持的表面来描述: 硬件的类型─驱动程序支持由硬件的类型描述的PDEV的一个例子是支持假设的LaserWhiz、LaserWhizⅡ和LaserWhiz超级打印机。由GDI传递的设备名指定在驱动程序支持的全部设备集中哪一个设备被请求。 逻辑地址—例如,一个驱动程序能支持连在LPT1、COM2和名为\\SERVER1\PSLASER的服务器上的多个打印机。另外,一个能同时支持超过一个VGA显示器的显示器驱动程序可能通过端口号进行区分,如0x3CE,0x2CE,等等。打印机和其他硬拷贝输出设备的逻辑地址由GDI决定;EngWritePrinter函数向相应的目标直接输出。显示器既可以隐含地确定其逻辑地址,也可以从DEVMODEW的私有部分取到地址。 DEVMODEW结构给驱动程序提供了请求的环境设置,如设备名和指定打印机或显示器驱动程序的其他信息。 表面—每个PDEV请求一个唯一的表面。例如,如果一个打印机驱动程序同时有两个打印作业在工作,每个作业请求不同的页面格式,如风景和肖像格式,每个打印作业请求不同的PDEV。类似地,显示器驱动程序可能在同一显示器上支持两个桌面,每个桌面请求不同的PDEV和表面。对每个表面请求,有一个对DrvEnablePDEV函数的调用为表面创建一个不同的PDEV。 为响应对DrvEnablePDEV的调用,驱动程序通过几个结构返回有关硬件设备能力的信息。 在GDI调用DrvEnablePDEV之前,GDIINFO结构填充着0。驱动程序在GDIINFO中填充下列信息与GDI通信: 驱动程序版本号 基本的设备技术(光栅和矢量) 可打印页的大小和分辨率 彩色调色板和灰度比例信息 字体和文本性能 过渡调色支持 样式步号 驱动程序只填充它支持的域而忽略其余部分。 驱动程序用标志在GDIINFO结构中填充,描述这个PDEV的图形性能。在几乎所有的情况下,GDIINFO的信息告诉GDI驱动程序能提供的图形支持的级别。例如,如果需要绘制一个三角记号,GDIINFO中的信息告诉GDI是否驱动程序能处理贝塞尔曲线或者是否GDI必须发送多条直线线段。驱动程序要填充它支持的所有域,而不涉及其他的域。 驱动程序必须提供的另一条重要的信息是一个指针(phsurfPatterns),指向用表面的句柄填充的缓冲区,表示标准的填充模式。除了标准的填充模式以外,phsurfPatterns能包括空(null),这将导致GDI根据设备分辨率和像素大小自动创建模式表面。当GDI被调用,用标准模式实现一个画刷,它调用DrvRealizeBrush函数实现为请求的模式定义的画刷。 GDI为支持设备的核心驱动程序传递给DrvEnablePDEV一个句柄,hDriver。对于一个打印机驱动程序,hDriver提供句柄给打印机并在假脱机的调用中使用,如EngWritePrinter。 无论何时GDI调用DrvEnablePDEV,驱动程序必须分配存储器,用以支持创建的PDEV,即使调用DrvEnablePDEV用来为不同的模式创建其他的PDEV。(一个驱动程序可能有几个激活的PDEV,虽然在一个时刻只有一个能被启用。)然而,直到GDI调用DrvEnableSurface,不支持一个实际的表面。 如果设备表面请求位图的分配,直到表面启用为止分配是不必要的(通常在DrvEnableSurface函数中)。虽然在实际写到设备之前应用常请求设备信息,等待分配一个大的位图能保存有价值的资源,并在系统初始化时改进驱动程序的性能。 PDEV的初始化完成时,GDI调用DrvCompletePDEV函数通知驱动程序物理设备的安装已经完成。这个函数也给驱动程序提供到PDEV的GDI的逻辑句柄,驱动程序在调用GDI函数中使用。 对驱动程序的DrvDisablePDEV函数的调用指示给定的物理设备不再需要。在这个函数中,驱动程序要释放物理设备使用的所有存储器和资源。 3.2.3 启用和禁止表面 在初始化阶段的最后,GDI调用DrvEnableSurface使驱动程序为一个存在的PDEV启用一个表面。DrvEnableSurface必须通过调用相应的GDI服务指定表面的类型来创建它。就像第2章GDI对表面的支持中描述的,以及依据设备和环境,驱动程序能从DrvEnableSurface中调用合适的GDI服务创建表面: 对设备管理的表面,驱动程序调用EngCreateDeviceSurface函数得到表面的句柄。 为创建一个GDI能完全管理的标准格式(DIB)位图,包括所有绘图操作的性能,驱动程序要调用EnfCreateBitmap函数。驱动程序能钩住它能优化的所有的绘图操作。驱动程序也能使GDI为每个像素分配空间或为自身提供空间,虽然后一个选项通常只由打印机或帧缓冲区使用。 DrvEnableSurface返回一个有效的表面句柄作为返回值。 随着表面的创建,驱动程序必须通过调用GDI的EngAssociateSurface服务使表面与一个PDEV相关。这个调用也告诉GDI驱动程序为表面挂上了哪个绘图函数。 GDI调用DrvDisableSurface函数通知驱动程序不再请求由DrvEnableSurface为PDEV创建的当前的表面。驱动程序必须释放在执行DrvEnableSurface期间分配的所有的存储器和资源。如果PDEV有一个启用的表面,DrvDisableSurface总是在DrvDisablePDEV之前调用。 表面一旦创建,当它不再使用时必须删除。不能正确匹配表面创建的删除将导致无用对象的累积并使系统性能下降。 3.3 创建设备相关位图 当应用请求位图的创建,驱动程序通过支持DrvCreateDeviceBitmap函数能创建和管理DDB。当这样一个驱动程序创建了位图,它能用任何格式存储位图。驱动程序检查传递的参数,并用至少是请求的每个像素的位数提供位图。 注意 图形驱动程序通过在屏幕外的存储器支持位图和使用硬件绘制位图能提高性能。这方面的一个例子是Permedia显示器驱动程序的例子。 在DrvCreateDeviceBitmap中,驱动程序调用GDI的EngCreateDeviceBitmap服务使GDI为设备位图创建一个句柄。 如果驱动程序支持DrvCreateDeviceBitmap,它创建一个DDB,定义其格式,并返回一个它的句柄。驱动程序控制位图用什么格式在哪里存储。驱动程序要支持最接近匹配其设备表面的颜色格式。 位图的内容在创建之后不能定义。如果驱动程序返回NULL,它不能创建和管理位图;而由GDI替代执行这些任务。 如果驱动程序创建了位图,也必须能通过执行DrvDeleteDeviceBitmap函数删除它。 3.4 支持图形输出 驱动程序处理的特殊图形操作依赖于绘图表面和硬件的能力。如果表面是一个标准格式的DIB,GDI处理所有驱动程序不支持的绘图操作。驱动程序能hook任意的绘图函数并实现它们,得到硬件支持的优势。 对于设备管理的表面,驱动程序最少必须支持图形输出函数DrvCopyBits、DrvTextOut和DrvStrokePath。它也能可选支持任何其他的图形输出函数,例如支持DrvBitBlt,能提高性能。有些函数要求确定的能力级别,而其他的允许设备通过在DEVINFO结构中设置相应的GCAPS标志指示其性能。 对驱动程序的所有的绘图调用都是单线程的,而不关心表面的类型。 下面的部分描述驱动程序如何实现下列操作: 绘制直线和曲线 绘制和填充路径 拷贝位图 过渡调色技术 图象颜色管理 3.4.1 绘制直线和曲线 包括在图象输出中的直线和曲线类型是几何直线、装饰直线和贝塞尔曲线。 对于直线和曲线输出,驱动程序能支持DrvStrokePath、DrvFillPath和DrvStrokeAndFillPath函数。如果表面是设备管理的,驱动程序必须支持DrvStrokePath绘制直线;驱动程序不要求支持曲线。 当GDI用任意的属性集绘制直线或曲线,GDI调用DrvStrokePath。至少,DrvStrokePath函数必须支持用实色画刷和任意的裁剪绘制实线和样式装饰线。为实现这一点,GDI的PATHOBJ_Xxx和CLIPOBJ_Xxx服务函数通过预先计算的裁剪把这些直线分割成一个像素宽的线段的集合。DrvStrokePath提供一个指针,plineattrs,指向定义不同直线属性的LINEATTRS结构。 当路径或裁剪对于驱动程序在设备上的过程过于复杂,驱动程序能通过调用EngStrokePath函数把回调函数punt到GDI。对这种情况,GDI能把对DrvStrokePath的调用分割成用预先计算的裁剪得到的一个像素宽的线段的集合。 通过调用GDI的CLIPOBJ_Xxx服务,驱动程序能使GDI计算在路径中的所有的直线,并执行所有的直线裁剪计算。另外,驱动程序可以使用PATHOBJ_Xxx、CLIPOBJ_Xxx或XFORMOBJ_Xxx服务简化图形操作。例如,驱动程序能使用CLIPOBJ_cEnumStart和CLIPOBJ_cEnum计算在一个裁剪区域内的矩形,发送这个区域到打印机,以及裁剪它。驱动程序也能使用PATHOBJ_cEnumStart和PATHOBJ_cEnum计算路径中的直线或曲线。然后它能把路径发送到设备并绘制它。 3.4.1.1 装饰线 装饰线通常是一个像素宽并使用实色画刷绘制。它按照网格交叉点量化(GIQ)菱形约定绘制,这种方法确定装饰线上哪个像素要被绘制。 图3.1显示了一条直线置于一个矩形网格上,像素定位在网格交叉点上。为确定哪个像素将被点亮,想象一个菱形在直线中间并沿直线滑动。菱形的宽度和高度恰好等于两个相邻像素中心的距离。当菱形沿着直线移动,中心完全被菱形覆盖的像素被选中。如果直线穿过的一个点在两个相邻像素的中间,被点亮的像素取决直线的斜度和相邻像素的走向:水平的或是垂直的。 下表概括了这些情况。 直线的斜率(绝对值) 相邻像素的走向 结果  斜率<1或斜率>1 水平的 点亮菱形左顶点的像素  斜率<1或斜率>1 垂直的 点亮菱形上顶点的像素  斜率=1 水平的 点亮菱形上顶点的像素  斜率=1 垂直的 点亮菱形右顶点的像素   菱形协议使斜率在-1和1之间的直线上每一列有一个像素点亮,使斜率的绝对值比1大的直线上每一行里有一个像素点亮。用这种方式,装饰线能没有间隙地绘制。 装饰线的首尾像素也由菱形协议确定。装饰线是首像素包含而末像素不包含的;即如果直线起始于菱形内的一个像素,这个像素变亮。类似地,如果直线结束于菱形内的一个像素,这个像素不变亮,如图3.1显示。 图3.1 装饰线的菱形协议 为了着色装饰线,DrvStrokePath函数遵循GIQ菱形协议。DrvLineTo函数是一个可选入口点,驱动程序能够为应用调用Microsoft? Win32 LineTo函数提供优化。DrvLineTo比DrvStrokePath简单,因为它仅支持整数端点和实心装饰线。 对于支持R2_NOT混合模式的光栅设备,二进制光栅操作改变目标颜色为相反色,驱动程序必须使用精确的着色。对于要求GDI和驱动程序两者绘图的设备,着色也必须是精确的。这包括GDI在一些位图上绘图的设备以及驱动程序在其他表面上绘图的设备(除非像素太小不能产生可视的差异)。这也包括请求GDI 处理复杂裁剪的设备。 3.4.1.2 几何宽度直线(Geometric Wide Line) 几何直线的形状是由宽度、连接类型、画刷端点类型以及在XFORMOBJ结构里当前区域至设备转换来确定的。能够使用实心的或非实心的画刷绘制直线。 更高级的硬件设备驱动程序可以支持DrvStrokePath函数中的几何宽度直线。在调用DrvEnablePDEV返回的DEVINFO结构里测试GCAPS_GEOMETRICWIDE性能标志位,GDI确定驱动程序是否能够绘制包括几何直线的路径。如果驱动程序没有这个能力,或者因为路径或裁剪对设备过于复杂,而使函数处理此操作失败,GDI自动转换调用较简单的DrvFillPath函数。 几何宽度直线对显示器驱动程序图形函数有特殊的含义。包含设备坐标的路径使用当前转换的逆转换变成世界坐标。具有指定宽度的几何结构获得加宽的路径,考虑连接和端点。这个路径又转换成设备坐标并用指定的画刷填充。 几何宽度直线的线型是由浮点数数组确定的。数组是定长的,但好象能无限重复使用。在世界坐标里,第一个数组条目指定第一个短线的长度;下一个条目指定第一个间隙的长度。在这以后,短线和间隙的长度交替。例如,线型数组(3.0,1.0,1.0,1.0(可绘制长短线交替的直线。 当驱动程序沿着加宽前的路径移动,可以考虑线型,“擦除”路径对应于间隙的部分。这将路径分割成许多子路径。然后分割开的路径被加宽,就像它没有线型,照常应用端点盖住和连接。线型数组可以是不定长的。例如,线型数组{1,0}使驱动程序用交替的短线绘制一条直线。线型状态(定义当前的距离到线型数组)在路径中第一个子路径的开头提供。在每个后来的子路径的开始考虑重置为0.0,这在任何Win32 MoveToEx操作后出现。 3.4.1.3 花式装饰直线(Styled Cosmetic Line) DrvStrokePath函数必须支持用实色画刷绘制的带任意裁剪的装饰线。驱动程序能够进行一个到GDI的PATHOBJ_vEnumStartClipLines的调用来预先计算裁剪。 装饰线的线型类似于几何宽度直线,因为它由一个重复的数组指定。对于一条造型的装饰线,数组条目是LONG类型的值,包含类型步的长度。类型步和像素的关系由DrvEnablePDEV函数返回的GDIINFO结构的xStyleStep、yStyleStep和DenStyleStep字段定义。 当驱动程序通过复杂的裁剪调用PATHOBJ_vEnumStartClipLines处理装饰线,GDI修改CLIPLINE结构的iStyleState成员来表示线型状态。线型状态是回到线段的第一个像素的偏移,即如果直线没有被裁剪,将绘制第一个像素。线型状态由压缩到一个ULONG值中的两个16位值组成。如果HIGH和LOW是线型状态的高序(high-order)和低序(low-order)的16位值,线型状态的分数版,称作线型位置,可计算为: style position = HIGH + LOW/denStyleStep 例如,如果在iStyleState中的值是1和2,denStyleStep是3,style position是5/3。为精确确定在线型数组中线型从哪里开始绘制,计算乘积: style position * denStyleStep 在这个例子中,denStyleStep的值是3,绘图位置计算不包括线型数组的前5个像素(5/3*3)。即这条裁剪线从线型数组中的第6个像素开始绘制。 有y线型的装饰线和x线型的装饰线。如果一条线在x方向扩展dx个设备单元,在y方向扩展dy个设备单元,当下列条件为真直线是y型的: (dy * yStyleStep) ≥ (dx * xStyleStep) 在这种情况下,对每个像素在y方向的前进,线型位置的前进由yStyleStep/ denStyleStep计算。 反过来,直线是x线型的,当下式为真时,对每个像素在x方向的前进,线型位置的前进由xStyleStep/ denStyleStep计算: (dx * yStyleStep) > (dy * xStyleStep) 当线型位置前进到一个新的整数时,在线型数组中线型步前进一个单元。 图3.2显示不同斜率的几条装饰线。像素网格显示不是方的;它就像在EGA显示器中显示的那样,在x方向的4个像素与y方向的3个像素表示相同的距离。在这个图示中,线型数组(由LINEATTRS结构的pstyle成员定义)是{1,1},它把直线分割成相等大小的点和间隙。驱动程序的xStyleStep的值是3,yStyleStep的值是4,denStyleStep是12。 为进一步说明,假设一个点阵打印机有144dpi的水平分辨率和72 dpi的垂直分辨率。另外,假设最小点的长度是1/24英寸。为支持这个打印机,要选择能补偿打印机的方式比的最小的xStyleStep和yStyleStep值,如xStyleStep为1,yStyleStep为2(144/72),denStyleStep为6(144/24)。 图3.2 花式装饰线的例子 如果LINEATTRS结构里标志的LA_ALTERNATE位置1,装饰线使用了指定的线型。在这种情况下,其他每个像素都选中,而不管方向或方式比。线型状态返回,好像线型数组是{1,1},xStyleStep、yStyleStep和denStyleStep都是一个。换句话说,如果IstyleState是0,第一个像素点亮;IstyleState是1,第一个像素不亮。 如果LINEATTRS结构里标志的LA_STAARTGAP位置1,在线型数组里元素的检测是相反的。第一个数组条目指定第一个间隙的长度,第二个条目指定第一个短线的长度,如此下去。 3.4.1.4 贝塞尔曲线 一些高级硬件设备能够绘制包含贝塞尔曲线的路径,是一般作用的曲线图元。如果这样,在DrvStrokePath函数中驱动程序包含对这些曲线的支持。 当GDI必须在设备管理的表面绘制贝塞尔曲线路径,它测试GCAPS_BEZIERS标志(在DEVINFO结构中)确定是否调用DrvStrokePath。如果调用,这个函数执行执行请求操作或决定不处理它,就像在几何宽度直线中做的那样。在后一种情况中,GDI把请求分割成简单的操作,例如,把曲线转换成近似的直线。 3.4.2 绘制和填充路径 图形驱动程序把路径考虑成通过一个路径对象(PATHOBJ结构)定义的直线和/或曲线序列。为处理闭合路径的填充,驱动程序支持DrvFillPath。 GDI能在设备管理的表面上调用DrvFillPath填充一个路径。GDI比较用DEVINFO结构中的GCAPS_BEZIERS、GCAPS_ALTERNATEFILL和GCAPS_WINDINGFILL标志填充的请求,决定是否调用驱动程序。如果GDI调用驱动程序,驱动程序或者执行操作或者返回,通知GDI路径或裁剪请求对由设备处理过于复杂。对后一种情况,GDI把请求分割成几个简单的操作。 驱动程序也能支持可选的DrvStrokeAndFillPath函数完成路径填充的请求。这个函数在同一时间内填充和绘制路径。许多GDI图元请求这个函数。如果绘制使用了宽线,填充区域必须减小,以补偿路径边界宽度的增加。 当驱动程序从DrvFillPath或DrvStrokeAndFillPath函数返回FALSE,GDI把填充路径请求转换为一系列简单的操作并再次调用驱动程序函数。如果设备在第二次调用DrvFillPath时再返回FALSE,GDI把路径转换成裁剪对象并调用EngFillPath。对再次调用DrvStrokeAndFillPath返回的FALSE,GDI把调用转换成对DrvStrokePath和DrvFillPath的独立的调用。 3.4.2.1填充路径模式 路径定义的两种填充模式是交替(alternate)和旋转(winding)。两种填充模式使用一个奇偶规则决定如何填充闭合路径。 FP_ALTERNATEMODE如下应用奇偶均等规则:从闭合路径中的任意一个起始点向闭合路径外的某点画一条直线。如果直线穿过奇数个路径段,起始点在闭合区域内,是填充区的一部分。穿过偶数个路径段则意味着起始点不在填充区内。 FP_WINDINGMODE不仅考虑矢量穿过路径段的次数,还考虑每个路径段的方向。路径看作从起点向终点绘制,每个路径段的方向由指定点的顺序包含:线段的第一个顶点是“起始”点,第二个顶点是“终”点。现在用交替模式绘制同一个任意线。从0开始,为每个直线穿过“向前”方向的线段加1,对每个“相反的”方向的线段减1。(向前和相反取决于线段产生的点和任意的直线。)如果计数的结果是非0,起始点在填充区内;计数值是0则意味着点在填充区以外。 图3.3显示了如何把两种规则应用到更复杂的自交叉路径的情况。在交替填充模式中,点A在填充区中因为RAY1经过线段奇数次,而点B和C不在,因为RAY2和RAY3经过线段偶数次。在旋转填充模式中,点A和C在填充区中,因为RAY1和RAY3穿过的全部正向(正的)和反向(负的)线段的计数值加起来都不为0,而B在填充区外,因为RAY2穿过的正向(正的)和反向(负的)线段的计数值加起来都为0。 图3.3 交替和旋转模式填充 3.4.2.2 填充区(闭合路径) 像在直线绘制中那样,填充的像素考虑成整数坐标。区域中每条扫描线由路径线段确定左右边界。落在左右边界之间的像素认为是在填充区域内。恰好在左边界上的像素也在区域内,而右边界上的像素则在区域之外。如果顶边界恰好是水平线,边界上的像素都在区域内,而底边界上的像都在区域之外。 图3.4显示了相对于区域的左右边界如何确定包括在填充区域内的像素。从数学的角度来看,这个区域在左边界和上边界是“闭合的”,而在右边界和底边界是“开放的”。 图3.4 确定像素包括在填充区内 上面有关填充区x轴的描述约定也应用于y轴,用顶边界代替左边界,用底边界代替右边界。 3.4.3 拷贝位图 驱动程序实现的位块传输(BitBlt)函数必须从一个表面向另一个拷贝位块。这些函数包括: DrvBitBlt DrvStretchBlt DrvCopyBits 还有一个显示器驱动程序指定的BitBlt函数,名字为DrvSaveScreenBits。 如果绘制的表面是在设备管理的表面或位图上,驱动程序必须支持最小级别的位块传输函数。如果表面是GDI管理的标准格式位图,GDI只处理没有被驱动程序挂起的操作。 DrvBitBlt函数提供了一般的位块传输能力。如果驱动程序支持设备管理的表面或位图,它必须实现DrvCopyBits函数。至少,当调用DrvCopyBits时,驱动程序必须进行以下操作: 和位图之间进行块传输,用驱动程序和设备表面推荐的格式。 用SRCCOPY(0xCCCC)ROP执行传输。 允许任意的裁剪。 驱动程序能使用GDI CLIPOBJ计算服务,减少对一系列裁剪矩形的裁剪。GDI向下传递一个转换向量,XLATEOBJ结构,帮助源和目标表面的颜色索引转换。 如果设备的表面由标准格式的DIB组成,驱动程序可以仅支持简单传输。如果调用用一个复杂的ROP进来,驱动程序通过调用EngCopyBits函数把块传输请求直接传回到GDI。这允许GDI把调用分割成驱动程序能够执行的简单函数。 如果使用了源,DrvBitBlt映像一个几何源矩形到几何目标矩形,如果没有源,DrvBitBlt忽略了pptsrc参数。目标矩形是要修改的表面,它由两个整数端点左上角和右下角定义,矩形是右下排斥的;矩形的右边界和下边界不是块传输的一部分。DrvBitBlt不能由一个空的目标矩形调用。矩形的两个点总是有顺序的,即右下点的两个坐标都大于对角左上角点的坐标。 DrvBitBlt处理不同的ROP并能依赖设备进行一些优化。在一些情况下,如果ROP是实色,宁可进行填充而不执行BitBlt。对不支持所有ROP的设备,真正的所见即所得是不可能的。例如,PostScript驱动程序不支持ROP。 可选地,DrvBitBlt处理的位块传输能被屏蔽,包括颜色索引转换。转换矢量帮助调色板颜色索引转换。传输可能会被显示器驱动程序任意地裁剪,使用一系列的裁剪矩形。请求区域和信息由GDI提供。 实现DrvBitBlt描述了编写光栅显示器驱动程序的一个重要的部分,它没有标准格式的帧缓冲区。随Windows( 2000 DDK供应的Microsoft VGA驱动程序提供了例子代码,完全支持平面设备的基本函数。为其他设备实现DrvBitBlt的复杂性更小。 驱动程序能可选地提供DrvStretchBlt函数,即使驱动程序支持设备管理的表面。这个函数提供在设备管理的表面和GDI管理的表面之间延伸块传输能力。DrvStretchBlt仅支持某些类型的延伸,如通过整数乘延伸。 DrvStretchBlt也允许驱动程序在GDI位图上写,特别当驱动程序能进行中间色调处理时。这个函数也允许同一个中间色调算法应用于GDI位图和设备表面。 DrvStretchBlt精确映像一个几何源矩形到一个几何目标矩形。源矩形角的坐标由给定的整数坐标替换为(-0.5,-0.5)。函数参数中指定的点位于相应的像素中心的整数坐标。由两个这样的点定义的矩形认为是几何的,两个顶点是坐标给定的点,但每个坐标都用0.5代替。(GDI POINTL结构使用一个速记符指定这些分数坐标顶点。)注意如何这样的矩形的边不会有像素交叉,但提供一系列像素。矩形内的像素是通常的底边和右边排除的矩形的像素。 源矩形的点是有序的。不能给定一个空的源矩形到DrvStretchBlt。与DrvBitBlt不同,DrvStretchBlt能用一个简单的裁剪矩形调用,防止在裁剪输出中的舍入错。 目标矩形由两个整数点定义。这些点不是有序的,这意味着第二个点的坐标不必须比第一个大。这些点描述的源矩形不包括底边和右边。因为矩形不是有序的,DrvStretchBlt必须不时执行两个x坐标和/或两个y坐标的倒置。(驱动程序必须尝试读出不在源表面上的像素。)DrvStretchBlt不能用一个空的目标矩形调用。 对于颜色转换,DrvStretchBlt提供一个指针,pxlo,指向XLATEOBJ结构,用于在源和目标表面之间转换。XLATEOBJ结构可用来查询,找到任何源索引的目标索引。对高品质的延伸块传送,DrvStretchBlt在某些情况下要求加入颜色。DrvStretchBlt也使用COLORADJUSTMENT结构定义颜色调整值,在位被延伸之前用于源位图。 DrvStretchBlt使用iMode参数定义源像素如何组成输出。特别地,iMode提供HALFTONE选项,允许驱动程序在输出表面使用像素组到最接近的颜色或灰度级。在下一次用HALFTONE的iMode调用DrvStretchBlt后,COLORADJUSTMENT结构的改变传递到驱动程序。另外,如果驱动程序为GDI位图请求GDI处理中间色调,驱动程序可以挂起DrvStretchBlt,iMode设置参数到HALFTONE,并在EngStretchBlt返回。 如果DrvStretchBlt挂起一个对EngStretchBlt函数的调用,并请求了它不支持的某些操作,它把请求返回到GDI,由相应的函数进行处理。 DrvCopyBits函数由GDI从其模拟操作调用,在设备管理的光栅表面和GDI标准格式位图之间转换。DrvCopyBits为SRCCOPY(0xCCCC)ROP位块传输提供了一个快速路径。这个函数为具有设备管理的位图或光栅表面的图形驱动程序请求,必须转换驱动程序表面到(或从)任何标准格式位图。DrvCopyBits不会用一个空的目标举行调用,目标举行的两个端点是有序的。这个调用和DrvBitBlt有相同的请求。 DrvCopyBits也用RLE位图(参见平台DDK文档)和DDB调用。位图作为应用程序调用几个Win32 GDI例程的结果提供给这个函数。可选的DDB仅由少数几个指定的驱动程序支持。 3.4.4 过渡调色技术 传统的模拟中间色调使用相等大小的单元组成的中间色调屏幕,固定单元的距离是从中心到中心。固定单元的距离调节墨水的浓度,当每个单元中点的大小变化时产生连续色调的印象。 在计算机中,大多数打印或屏幕阴影也使用固定单元像素大小。为模拟点大小的变化,一组像素的组合模拟中间色调屏幕。GDI包括过渡调色技术的默认参数,提供一个好的首次近似。附加的设备指定的信息能加入到系统中改进输出。 驱动程序发送GDI设备相关的规范,GDI需要通过DrvEnablePDEV函数返回的GDIINFO结构进行过渡调色处理。驱动程序用GDIINFO的ulHTPatternSize成员指定模型的大小,它定义过渡调色技术推荐的输出格式。对指定的设备,过渡调色技术与调色模型大小有关。GDI提供许多预定义的模型尺寸,从2(2到16(16。 对每个标准的模型尺寸,还有一个修改版。它通过在标准模型名上加上后缀“_M”来区分。例如,标准的6乘6的模型定义的名字是HT_PATSIZE_6(6,修改的6乘6的模型定义的名字是HT_PATSIZE_6(6_M。修改版给定了更多的颜色分辨率,但产生一个副作用,即水平或垂直噪声。另外,因为每个模型的尺寸是设备分辨率相关的,相应的模型尺寸依赖于指定的设备。 在模型尺寸(空间分辨率)和颜色分辨率之间的折中是由模型尺寸决定。一个大的过渡调色模型产生更好的颜色分辨率,而小的模型得到更好的空间分辨率。确定最好的模型尺寸是进行频繁地尝试和出错。更多的信息参考在线DDK中 Graphic Driver Reference的GDIINFO。 GDIINFO结构中影响过渡调色技术的另一个成员是flHTFlags,它包含描述过渡调色需要的设备分辨率。 GDI处理应用的颜色调整请求,并通过DDI把信息传递到驱动程序函数。如果应用选择过渡调色,而且表面是标准格式的DIB,在位图发送到设备之后,GDI使用其过渡调色能力处理位图。在PostScript驱动程序中,EngStretchBlt函数能使用DrvCopyBits或DrvBitBlt(在SRCCOPY模式中)函数发送位图到打印机。 例如,让GDI代替PostScript打印机执行过渡调色处理,提供了更快的输出,更好的所见即所得品质。如果打印机内嵌的过渡调色能力更好,PostScript驱动程序的接口允许用户调整过渡色调并提供一个检查框关闭GDI的过渡调色。 DrvDitherColor函数能返回DCR_HALFTONE值,请求GDI使用存在的设备(过渡调色)调色板近似一个颜色。仅当设备包含一个设备(过渡调色)调色板,DCR_HALFTONE能够用于显示器驱动程序,如VGA-16适配器卡,因为它有一个标准的固定调色板。单色驱动程序,包括大多数光栅打印机,能使用DrvDitherColor中iMode的参数获得一个好的灰度效果。 注意:Windows 2000在24位(或更高)的设备上不支持过渡调色技术。 3.4.5 图像颜色管理 在计算机监视器上显示的图像在彩色打印机上打印时经常出现不同。为解决这个问题,Windows 2000把图像颜色管理(ICM)并入到执行图像的颜色修正,这样在不同的输出设备上颜色的出现是一致的。 有关图像颜色管理和输出设备的特别类的更多的信息,参见:第二部分第2章,显示器的颜色管理;第三部分第12章,打印机的颜色管理;或者在线DDK上Color Management for Still Image Devices。 有关ICM的一般讨论,参见平台DDK文档。 3.5 支持DDI颜色和调色板函数 DDI颜色和调色板函数包括调色板管理和画刷实现函数。 3.5.1 管理调色板 就像第2章对图形驱动程序的GDI支持描述的,GDI处理了许多调色板管理工作。当GDI调用DrvEnablePDEV函数时,驱动程序在DEVINFO结构中必须提供其确省的调色板到GDI。这时,驱动程序必须调用GDI的服务函数EngCreatePalette创建确省的 调色板。 支持建立调色板的驱动程序也必须支持DrvSetPalette函数。这个函数是显示器驱动程序专有的。 3.5.2 实现画刷 输出直线、文本或者进行填充的图形函数至少接受一个画刷作为参数。画刷定义在指定的表面用来绘制图形对象的模式。每个接受画刷的输出函数请求一个画刷原点。画刷原点提供了像素在设备表面的坐标,与画刷模型左上角的像素排列在一起。画刷模型重复(平铺)覆盖整个设备表面。 驱动程序支持下列函数定义画刷: DrvRealizeBrush DrvDitherColor 画刷总是使用混合模式,定义模型如何与设备表面上已经存在的数据混合。MIX数据类型包括压缩到一个ULONG值中的两个ROP2值。前景ROP在低字节。下一个字节包含背景ROP。更多的信息参见平台DDK文档。 GDI保存所有的应用请求使用过的逻辑画刷。在请求驱动程序绘制之前,GDI首先发出请求调用驱动程序的DrvRealizeBrush函数。这允许驱动程序为其自身的绘图代码计算所请求模型的最优表示。 调用DrvRealizeBrush来实现由psoPattern(画刷的模型)和psoTarget(实现画刷的表面)定义的画刷。实现的画刷包含驱动程序为了用模型填充一个区域而需要的信息和加速器。这个信息只由驱动程序定义和使用。画刷的驱动程序实现写进一个缓冲区,驱动程序通过从DrvRealizeBrush中调用GDI服务函数BRUSHOBJ_pvAllocRBrush分配这个缓冲区。GDI缓存所有已实现的画刷;这样,它们很少需要重新计算。 在DrvRealizeBrush中,BRUSHOBJ使用对象表示画刷。画刷实现的表面可以是设备的物理表面、DDB或标准格式的位图。对于一个光栅设备,表面描述画刷模型表示一个位图;对于矢量设备,它总是由DrvEnablePDEV函数返回的模型表面之一。画刷使用的透明掩码是每像素一位的位图,与模型的长度相同。掩码位为0表示像素是画刷的背景像素。DrvRealizeBrush使用一个XLATEOBJ结构把画刷模型中的颜色转换成设备颜色索引。 当BRUSHOBJ结构的iSolidColor成员的值是0xFFFFFFFF并且pvRBrush成员是NULL时,驱动程序要调用GDI的BRUSHOBJ_pvGetRBrush函数。BRUSHOBJ_pvGetRBrush取出一个驱动程序的指定画刷实现的指针。当驱动程序调用这个函数时,如果画刷还没有实现,GDI自动调用DrvRealizeBrush进行这个画刷的驱动程序实现。 抖动 如果需要,当GDI试图使用硬件不能精确表示的实色创建画刷时,它会请求驱动程序的帮助。GDI调用驱动程序的DrvDitherColor函数,请求驱动程序相对于设备调色板的保留部分抖动画刷。 抖动使用几个颜色的模型近似选择的颜色,其结构是一个设备颜色索引数组。使用这些颜色为其模型创建的画刷通常对于给定的颜色是很好的近似。DrvDitherColor也能表现设备不能精确指定的颜色。为实现这一点,DrvDitherColor请求一个几种颜色的模型,并创建一个画刷近似给定的实色。 函数DrvDitherColor是可选的,并且只在DEVINFO结构的flGraphicsCaps成员中GCAPS_COLOR_DITHER或GCAPS_MONO_DITHER能力标志置1时才调用。DrvDitherColor可返回的值在下表中列出。 值 含义  DCR_DRIVER 指示抖动值已经由驱动程序计算。在这个情况下,设备颜色索引数组的cxDither乘cyDither的句柄传递回去。  DCR_HALFTONE 指示GDI使用存在的设备(过渡色调)调色板近似一个颜色。例如,GDI可以使用打印机的典型调色板,只有3种或4种颜色。DCR_HALFTONE能被显示器驱动程序使用,仅当设备包含一个设备(过渡色调)调色板,如VGA-16适配器卡,它有一个标准的固定调色板。  DCR_SOLID 指示GDI要用存在的设备调色板把请求的颜色映像到最接近的颜色值(多对一)。   为使GDI获得好的灰度模型,单色显示器也支持DrvDitherColor。 3.6 支持DDI字体和文本函数 对许多设备,GDI能处理所有的字体函数。然而,某些驱动程序只能在设备表面上画其自己的字体,或者其设备的字体。其他驱动程序是字体驱动程序,能提供字形位图和/或轮廓和字形规格给GDI。对这些情况,驱动程序必须支持有些有用的字体函数。 文本输出是更一般的函数。如果表面是标准格式位图,GDI能处理所有的文本输出,除非驱动程序挂起调用以提高性能。对设备管理的表面,驱动程序必须支持文本输出。 3.6.1 管理和优化字体 生产者(producer)是能生成字体的驱动程序。在输出时它产生字形信息作为输出,包括字形规格、位图和轮廓。消费者(consumer)是使用字体的驱动程序。为产生文本输出,它接受字形信息作为输入,必须绘制其自身的字体或在设备管理表面上硬件的字体。驱动程序可以既是生产者又是消费者。例如,打印机驱动程序在调用DrvQueryFontData服务提供字形规格时是生产者,处理DrvTextOut调用时是消费者。 驱动程序只有在它是字体生产者或字体消费者时才请求处理字体。如果硬件有驻留的字体,驱动程序必须给GDI提供有关这个字体的信息,包括在IFIMETRICS结构中的字体规格,从Unicode到个体字形标识的映像,独立字形属性以及字距调整表。还有一些驱动程序必须支持的函数。一些函数被字体驱动程序以及使用驱动程序指定或设备指定的字体的驱动程序两者请求。其他的只被字体驱动程序请求。 字体函数的支持依赖于驱动程序的能力。通常的类型有: 规格函数 字形函数 TrueType函数 3.6.1.1字体规格函数 当驱动程序必须支持字体时,它必须通过IFIMETRICS结构给GDI提供字体信息。对每个字体有独立的FIMETRICS结构。大多数字段用术语FWORD表达,每个字段在设计空间中是带符号的16位数。如果字体是光栅字体,设计空间和设备空间是相同的,字体单元相当于像素间的距离。 基本上,IFIMETRICS结构是文本规格结构的DDI版本。所有的距离依赖于字体设计者的抽象坐标系统。抽象空间坐标系统是右手笛卡儿坐标系统,y坐标向上增长,x坐标向右增长。 IFIMETRICS结构设计为变长的。与字体联合的字符串长度没有限制。一般的习惯是紧跟着IFIMETRICS结构的最后一个字段立即存储字符串。 任何提供字体的驱动程序必须支持DrvQueryFont函数。驱动程序也包括函数DrvQueryFontData,取出有关实现字体的信息。在对DrvQueryFontData的调用中,GDI提供一个指针指向字形或字距调整句柄数组。驱动程序在GDI GLYPGDATA结构中返回相关字形的信息。如果DrvQueryFontData给定了字距调整句柄,它在Win32 POINTL表中返回有关字距调整对的信息。下表列出了字体规格函数。 函数 描述  DrvDestroyFont 通知驱动程序不再需要字体实现,驱动程序可以释放任何分配的数据结构。GDI调用这个函数一次为字体生产者,一次为字体消费者。这个函数是可选的,只在驱动程序必须释放所分配的资源时才支持。  DrvFree 通知驱动程序不再需要指定的数据结构。这个函数是可选的,只在驱动程序的存储器管理请求这个信息时才被实现。  DrvQueryFont 为字体返回指向IFIMETRICS结构的指针。这个函数被所有处理字体的驱动程序请求。  DrvQueryFontData 返回有关实现的字体的信息。这个函数被所有处理字体的驱动程序请求(为选择的iMode值)。  DrvQueryFontTree 返回指向结构的指针,定义从Unicode到字形句柄的映像或字距调整对到字距调整句柄的映像。这个函数被所有处理字体的驱动程序请求。   DrvQueryFontTree函数允许GDI获得指向由下列之一定义的树结构的指针: 从Unicode到字形句柄的映像,包括字形变量(GDI FD_GLYPHSET结构) 字距调整对到字距调整句柄的映像(FD_KERNINGPAIRS结构) DrvQueryFontTree请求尽力产生需要的结构,如果可能驱动程序要预先计算这些文件。结构可以存储在资源或文件中。如果结构存储在文件中,装载或读取它们的理想模式是调用EngMapFontFile函数,把文件映像到存储器。因为这个文件没有加进交换文件,如果需要存储器可置为可用的,这比打开和读取文件更有效。 特别地,驱动程序在pid参数中返回一个标识。当FD_GLYPHSET结构或FD_KERNINGPAIRS结构的数组不再需要时,GDI把它传递到DrvFree函数,带一个返回指针。依靠在驱动程序中存储器如何管理,pid能确定这个结构,确定结构如何分配,或什么都不做。 DrvFree和DrvDestroyFont都是可选函数。GDI调用DrvFree通知驱动程序指定的数据结构不再需要。驱动程序不需要实现它,除非它为结构分配了存储器并且当相应的数据结构被释放时需要通知。例如,如果数据与FONTOBJ结构关联,删除要被推迟直到调用DrvDestroyFont,因此不必要实现DrvFree。 DrvDestroyFont通知驱动程序字体实现不再需要,驱动程序可以释放它分配的任何数据结构。GDI调用这个函数一次为字体生产者,一次为字体消费者。只有当字体实例被摧毁时,驱动程序必须释放分配的资源,才需要实现它。 3.6.1.2 字体驱动程序函数 下表列出了字体驱动程序要支持的其他几个函数。 函数 描述  DrvLoadFontFile 指定用于创建字体实现的文件;驱动程序必须准备这些要使用的文件。字体驱动程序请求这个函数。  DrvQueryAdvanceWidths 请求驱动程序为指定的字形集发送GDI符号增长宽度。  DrvQueryFontCaps 向指定的缓冲区拷贝定义字体驱动程序能力的位的数组。  DrvQueryFontFile 依靠查询模式,返回字体文件或描述串中字体表面的数量。字体驱动程序请求这个函数。  DrvQueryGlyphAttrs 请求关于字体字形的信息。  DrvUnloadFontFile 通知驱动程序不再需要字体文件,驱动程序可以进行必要的清除操作。字体驱动程序请求这个函数。   GDI用一个特别的用来创建字体实现的文件调用DrvLoadFontFile函数。这个函数只由字体驱动程序请求。当调用DrvLoadFontFile函数时,驱动程序执行准备要使用的文件的转换。 DrvLoadFontFile返回一个唯一的标识,允许GDI使用一个GDI维护的字体使用表请求正确的字体。一旦装入了字体,GDI不再调用装入相同的字体。 当指定的字体文件不再需要时GDI调用DrvUnloadFontFile。DrvUnloadFontFile函数只由字体驱动程序请求。DrvUnloadFontFile引起删除所有的临时文件,分配的所有的系统资源被释放。 GDI调用DrvQueryFontFile函数返回驱动程序装入的字体文件的信息。DrvQueryFontFile只由字体驱动程序请求。要返回的信息的类型由iMode指定。如果iMode是QFF_DESCRIPTION,函数返回一个Microsoft( Windows NT(/Windows( 2000用来描述字体文件的字符串。如果iMode是QFF_NUMFACES,函数返回字体文件中表面的数量。表面由一个范围是1到表面数量的索引标识。 3.6.1.3 TrueType字体驱动程序函数 TrueType字体驱动程序必须支持下表列出的函数。 函数 描述  DrvGetTrueTypeFile 给定GDI有效访问存储器映像的TrueType字体文件。  DrvQueryTrueTypeOutline 用本地的TrueType格式返回字形句柄。  DrvQueryTrueTypeTable 给定GDI用TrueType字体文件格式访问指定的文件。   所有这些函数给GDI提供有关TrueType字体文件的信息。DrvQueryTrueTypeTable给定GDI用TrueType字体文件格式访问指定的表。DrvQueryTrueTypeOutline必须用本地的TrueType格式给GDI发送字形轮廓。DrvGetTrueTypeFile给GDI返回TrueType驱动程序的私有入口点,允许GDI有效访问存储器映像的TrueType字体文件。 3.6.2 绘制文本 如果驱动程序在EngAssociateSurface函数中挂起调用,文本输出函数只为设备管理的表面(设备位图或表面)或GDI管理的表面调用。主要为文本的图形输出函数是DrvTextOut和DrvGetGlyphMode。 GDI调用DrvTextOut为文本输出在指定的位置绘制一系列字形的像素。DrvTextOut的许多功能用DEVINFO结构的GXAPS位定义,它由DrvEnablePDEV函数返回。 DrvTextOut的输入参数定义两个像素集,foreground和opaque。 驱动程序绘制表面提供下列结果: 首先使用不透明的画刷绘制不透明的像素。 然后用前景画刷绘制前景像素。 每个绘图操作在裁减区内进行。裁减区外的像素不受影响。 驱动程序必须绘制表面,要首先使用不透明画刷在表面上计算和绘制不透明像素。然后用前景画刷计算和绘制前景像素。每个操作都由裁减进行限制。 前景和不透明像素通过画在表面上的颜色构造一个掩码。字体的字形本身没有颜色。前景像素集定义为字形的像素和某些额外的矩形的像素,用来模拟删除线和下划线。不透明像素由不透明矩形定义。 DrvTextOut使用一个指针pfo选择指定的字体,查询当前的FONTOBJ结构。这个过程包括下载一个软字体或字体替换,或者任何其他的对设备需要的字体优化。 如果驱动程序有可升级的字体,它要为当前的FONTOBJ结构调用FONTOBJ_pxoGetXform函数,为相关的字体返回抽象到设备的转换。它为驱动程序支持的字体请求的。抽象空间是设备字体的设计空间。例如,PostScript字体用1000乘1000单位字符单元定义。在IFIMETRICS结构里返回的大多数规格转换到抽象空间,这说明为什么抽象到设备的转换是必要的。 图形引擎通过调用DrvGetGlyphMode函数查询驱动程序,寻找在内部如何缓存字体信息。独立的字形可以缓存为位图、轮廓或其他(设备字体合适的选择)。 第2部分 显示器和视频微端口驱动程序 显示器介绍 显示器驱动程序 DirectDraw DDI Direct3D DDI Mini客户端驱动程序 视频微端口驱动程序 第1章 显示器介绍 运行在Windows NT(/Windows( 2000上的图像适配器要求一对显示器驱动程序和视频微端口驱动程序。这一章介绍这些驱动程序并提供有关这两方面的信息。 Microsoft( Windows( 2000 DDK提供了支持标准图形卡的显示器和视频微端口驱动程序的例子。 1.1 显示器体系结构 图1.1显示了Windows 2000上显示器要求的部件。 图1.1 Windows 2000显示器子系统 图1.1中带阴影的元素是Windows 2000提供的服务。未带阴影的元素指示请求了第三方显示器驱动程序和视频微端口驱动程序,使图形适配器在Windows 2000系统中显示。 对每种可以和Windows NT(/Windows( 2000一起使用的图形卡,必须具有显示器驱动程序和相应的视频微端口驱动程序。微端口驱动程序是特别为一种图形适配器(或适配器家族)编写的。显示器驱动程序可以为共享一个公共的绘图接口的任意多个适配器编写;例如,VGA显示器驱动程序可以与VGA或ET4000微端口驱动程序一起使用。这是因为显示器驱动程序进行绘制,而微端口驱动程序执行诸如模式设置的操作并给驱动程序提供有关硬件的信息。也可能有多个显示器驱动程序和一个特别的微端口驱动程序一起工作;例如,16和256色的SVGA显示器驱动程序可以使用同一个微端口驱动程序。 下面几节描述显示器和视频微端口驱动程序的主要职责。职责的分解是不严格的;主要在模块性和性能之间平衡。例如,VGA驱动程序的硬件指针代码驻留在微端口驱动程序中。这促进了模块化,同一个显示器驱动程序能够处理具有硬件指针的Vedio Seven VRAM以及没有硬件指针的ET4000。 1.1.1 显示器驱动程序职责 显示器驱动程序是一个主要职责为绘制的DLL。当一个应用通过设备无关图形请求调用Win32函数时,图形设备接口(GDI)解释这些指令并调用显示器驱动程序。显示器驱动程序再把这些请求转换成命令,视频硬件在屏幕上绘制图形。 显示器驱动程序能够直接访问硬件,因为图形硬件的能力很广泛,而且在任何系统中显示是对时间最苛求的部分。GDI的这种可访问性和广泛的能力在实现一个显示器驱动程序时提供了相当的灵活性。 缺省情况下,GDI在标准格式位图上处理绘图操作,如在包含帧缓冲区的硬件上。显示器驱动程序能hook和实现任何硬件提供特别支持的绘图函数。对时间要求较低的操作和更复杂的操作,图形适配器不支持,驱动程序能punt函数到GDI并允许GDI工作。细节参见第一部分第2章,Hooking和Punting。 对时间要求特别严格的操作,显示器驱动程序能直接访问视频硬件寄存器。例如,x86系统的VGA显示器驱动程序对一些绘图和文本操作使用优化的汇编代码实现对硬件寄存器的直接访问。 显示器驱动程序的细节在第2章显示器驱动程序中讨论。 1.1.2 视频微端口驱动程序职责 视频微端口驱动程序通常处理必须和其他NT核心部件交互的操作。例如,硬件初始化这样的操作和由Windows NT I/O子系统操作的存储器映像请求。视频微端口驱动程序职责包括资源管理,如硬件的配置,以及物理设备存储器映像。视频微端口驱动程序必须指定视频硬件。 显示器驱动程序使用视频微端口驱动程序进行操作不需要频繁请求,例如,管理资源,执行物理设备存储器映像,确保寄存器的输出出现在最近的地方,或者响应中断。 视频微端口驱动程序还能处理: 与图形卡的模式设置交互作用。 多种硬件类型,显示器驱动程序中最小地依赖硬件类型。 把视频寄存器映像到显示器驱动程序的地址空间。I/O端口直接寻址。 视频微端口驱动程序的细节在视频微端口驱动程序中讨论。 1.2 图形适配器的类型 图形适配器一般有4种类型: 帧缓冲区 加速器 协处理器 VGA 1.2.1 帧缓冲区 帧缓冲区是图形适配器上的专用存储器。简单的图形适配器有帧缓冲区而没有加速器。如果硬件是线性寻址的并且支持标准格式位图(DIB格式),GDI能处理绘图操作并直接写入帧缓冲区。这能减小显示器驱动程序的大小,因为驱动程序能让GDI进行全部或大多数的绘图。大多数帧缓冲区是线性寻址的,或者说是“平面的”。 在例子中显示器驱动程序是framebuf目录中的帧缓冲区驱动程序。因为这个驱动程序支持线性寻址帧缓冲区硬件,它允许GDI进行大多数工作,并且很小也相对简单。 1.2.2 加速器 加速器包括显示卡上带的电路,对于指定的绘图操作,如直线的绘制和填充,或位块传输,能提高图形操作的速度。硬件必须提供对一个指针的支持,驱动程序使用它通常比由GDI提供支持的指针速度要块得多。 与Windows 2000 DDK一起发布 的3Dlabs Permedia2例子驱动程序是一个复杂和相当难以处理的加速器。显示器和微端口驱动程序能在displays\3dlabs和miniport\3dlabs源程序例子文件夹中找到。3Dlabs Permedia2驱动程序完全用C语言编码,因此在整个系统中可移植。 1.2.3 协处理器 包括协处理器的图形适配器提供了加速图形显示函数的另一种方法。协处理器本质上是带有附加的可编程命令逻辑的加速器。因为协处理器是可编程的,它们支持多进程,允许协处理器处理一些费时的操作,而其他操作在别处进行。 1.2.4 VGA 基于x86的计算机的标准图形卡是VGA。这个硬件可以在文本和图形模式下运行。在VGA兼容的硬件上建立了一系列加速器。 1.3 一般的设计和实现策略 为了设计有效的Windows 2000显示器驱动程序和视频微端口驱动程序,考虑以下的策略: 修改一个已存在的为类似的图形适配器类型设计的DDK例子,减少驱动程序的设计时间。 为保证最大的可移植性,尽可能多地使用C语言编写驱动程序,仅当编写硬件没有很好支持的对时间要求严格的的程序才使用汇编语言。虽然汇编语言代码有优化的潜力,时间和可移植性在价值上超过了其代价。 操作中使用视频微端口驱动程序管理资源、执行物理设备存储器映像、确保寄存器以最近似的值输出,或响应中断。微端口驱动程序主要用于处理硬件家族中的变化,以及使显示器驱动程序对硬件类型的依赖最小。 还必须考虑以下事实: 显示器驱动程序和视频微端口驱动程序在相同的特权核心模式地址空间中操作,就像是Windows NT Executive的一部分。任何一个驱动程序中的错误都将引起系统剩余部分的故障。 显示器驱动程序和视频微端口驱动程序在任何时刻都是抢先的。 显示器驱动程序的代码和数据段都是完全分页的。 输出函数在入口处必须执行标准的Windows NT/ Windows 2000 prolog,退出时执行epilog。更多的信息参见平台SDK文档。 1.4 访问图形适配器 为确保显示器的性能,显示器驱动程序能通过下列方法访问图形卡: 通过发送IOCTL到图形适配器的视频微端口驱动程序直接访问。参见通过IOCTL和视频微端口驱动程序通信。 通过读写视频存储器(帧缓冲区)或硬件寄存器直接访问。参见帧缓冲区和硬件寄存器。 1.4.1 通过IOCTL和视频微端口驱动程序通信 图1.2显示了显示器驱动程序如何使用IOCTL和视频微端口驱动程序通信。 图1.2 显示器驱动程序/视频微端口驱动程序的通信 显示器驱动程序使用一个IOCTL调用EngDeviceIoControl向视频微端口驱动程序发送一个同步请求。对于输入和输出GDI使用单个的缓冲区向I/O子系统传递请求。I/O子系统把这个请求发送到视频端口,用视频微端口驱动程序处理请求。 一些IOCTL请求要求微端口驱动程序访问视频寄存器,其他的则从微端口驱动程序的数据结构中存储或取出信息。通常,没有请求要求视频微端口驱动程序执行实际的绘图操作。 一般地,除非模块性要求,否则显示器驱动程序处理绘图和其他的时间要求严格的操作。发送一个IOCTL到微端口驱动程序来执行时间要求严格的函数会使系统的性能下降。 系统定义的视频IOCTL的描述参见在线DDK中Video I/O Control Codes。通过加入一个私有的IOCTL,格式必须是在线DDK中Kernel Mode Driver Reference中描述的,可以在显示器驱动程序和视频微端口驱动程序之间扩展接口。如果需要写一个新的IOCTL,必须首先和Microsoft 技术支持联系。 1.4.2 访问帧缓冲区和硬件寄存器 减小显示器驱动程序的大小可以有几种方法。例如,可以仅实现显示器驱动程序执行比GDI快的函数,而指定GDI执行所有的其他函数。GDI经常执行大量实际的绘图到线型的帧缓冲区以减小驱动程序的大小。GDI不能直接访问分组的存储器;因此,当帧缓冲区不是线性寻址时,显示器驱动程序必须将帧缓冲区分割成一系列的组,并为GDI提供一种手段执行相应的组上的绘图操作。详细情况参见第2章支持分组的帧缓冲区。 显示器驱动程序直接访问I/O和存储器映像的视频寄存器。这个访问允许显示器驱动程序实现高性能。例如,驱动程序可能需要访问视频硬件寄存器,以高吞吐率发送绘制直线的命令。 类似地,对于图形卡,如S3,在图形引擎代码中许多最内层的循环要求读写几个视频控制器端口(例如,图形模式下的文本输出,位块传输和直线绘制)。与其请求显示器驱动程序为每个请求发送一个IOCTL到微端口驱动程序,不如显示器驱动程序允许直接访问视频硬件。 1.5 创建图形INF文件 Windows NT/ Windows 2000显示器和视频微端口驱动程序必须使用INF文件安装。Windows 2000 DDK提供了一个称作geninf.exe的工具,为显示器和视频微端口驱动程序产生一个INF文件。当geninf.exe运行时,它显示一系列对话框给出提示信息,如公司名,显示器驱动程序和视频微端口驱动程序的名字等。geninf.exe从这些信息中产生一个INF文件。 注意 由geninf.exe产生的文件可能不是一个完全有效的INF文件。geninf.exe产生一个INF文件,很可能需要客户为INF文件中描述的每个设备加入注册设置。 如果微端口驱动程序对VideoPortGetAccessRanges或VideoPortVeerifyAccessRanges的调用总是失败,可以手工编辑INF文件,包含在INF GeneralConfigData段中描述的段和适当的条目。 在运行geninf.exe时,当提示选择设备类时,选择显示器。在驱动程序初始化时,标记为Class=Display的INF文件被系统提供的显示器类安装程序解释。这确保所有与视频驱动程序相关的注册入口都能正确初始化。 在系统中Display类的INF文件只能初始化下列文件: 单个微端口驱动程序 一个或多个显示器驱动程序 控制面板扩展DLL Display类的INF文件不能安装其他类型的驱动程序或应用文件。 geninf.exe的限制 不能使用geninf.exe产生: 支持超过一个体系结构的INF文件;例如,可以创建x86体系结构或ia64体系结构的INF文件,但不能同时创建两个。 支持Windows 9x.或Windows NT 4.0的INF文件。 镜像驱动程序INF文件。作为模板使用mirror例子驱动程序提供的INF文件。细节参见第2章中镜像驱动程序INF文件。 监视器INF文件。作为模板使用名为monsamp.inf的INF文件。细节参见监视器INF文件。 这些例子INF文件与Windows 2000 DDK一起发布 。 当更新例子INF时,详细的参见第四部分、第3章即插即用、电源管理和设置设计指南中的创建一个INF文件,以及卷1 的第三部分Windows 2000 驱动程序开发指南的第1章,INF文件段和指令。 1.5.1 INF GeneralConfigData段 如果微端口驱动程序对VideoPortGetAccessRanges或VideoPortVeerifyAccessRanges的调用总是失败,可以编辑geninf.exe产生的INF文件,修改GeneralConfigData段中相应的条目。 [GeneralConfigData] [MaximunDeviceMemoryConfiguration = n] [MaximunNumberOfDevices = n] [KeepExistingDriverEnabled = n] 下面是GeneralConfigData的条目和值: MaximunDeviceMemoryConfiguration =n 指定了微端口驱动程序的设备尝试映像的物理地址空间的最大兆字节数。为使这个接口有效,需要重新启动。可以通过在DeviceManager中检查设备的状态来决定是否需要重新启动。 MaximunNumberOfDevices =n 指定了微端口驱动程序一次能够支持的适配器的最大数量。只在指定了MaximunDeviceMemoryConfiguration关键字并且在多监视器系统中微端口驱动程序可以是第2个适配器时这个条目才指定。为使这个接口有效,需要重新启动。可以通过在DeviceManager中检查设备的状态来决定是否需要重新启动。 KeepExistingDriverEnabled =n 指定了是否当前的驱动程序保持有效。为保持存在的驱动程序,设置这个注册键为1(TRUE)。为卸载当前的驱动程序,设置这个键为0(FALSE)。当INF文件编写者想使当前的驱动程序保持有效,这个键主要在镜像驱动程序INF文件中使用。 1.5.2 显示器INF文件段 下一段仅用于图形适配器安装。关于INF文件更一般的信息,参见卷1 的第三部分Windows 2000 驱动程序开发指南第1章,INF文件段和指令。 DDInstall.SoftwareSettings段 [DDInstall.SoftwareSettings]段包含AddReg和/或DelReg指令。每个指令指向一个分离的INF编写者定义的段,包含要加入或删除的注册条目。 在下例中,Addreg 指令指向命名为ACME-1234_SoftwareDeviceSettings的INF编写者定义的加入注册段。DelReg指令指向命名为ACME-1234_DeleteSWSettings的另一个INF编写者定义的删除注册段。 在加入注册段里,加入了InstalledDisplayDrivers和CapabilityOverride两个键,并给出了显示的值。删除注册段显示了从注册里删除的两个键:GraphicsClocking和MemClocking。 [ACME-1234.SoftwareSettings] AddReg=ACME-1234-SoftwareDeviceSettings DelReg= ACME-1234-DeleteSWSettings [ACME-1234-SoftwareDeviceSettings] HKR.. InstalledDisplayDrivers, %REG_MULTI_SZ%, Acmel HKR.. CapabilityOverride, %REG_DWORD%, 0 [ACME-1234-DeleteSWSettings] HKR.. "GraphicsClocking" HKR.. "MemClocking" 1.5.3 监视器INF文件段 在Windows NT/Windows 2000中监视器必须使用INF文件安装。Windows 2000 DDK提供监视器INF文件示例,monsamp.inf,为监视器用模板产生INF文件。用“创建图形INF文件”里描述的geninf.exe 工具不能产生监视器INF。 这个主题的其余部分在monsamp.inf的一些段中注释掉了监视器INF编写者感兴趣的细节。关于INF文件更一般的信息,参见卷1 的第三部分Windows 2000 驱动程序开发指南第1章,INF文件段和指令。 SourceDisksFiles段 监视器安装过程中必须在[SourceDisksFiles]段里放拷贝的文件。下例可识别分配磁盘1的.icm文件。 [SourceDisksFiles] profile1.icm=1 更一般的信息参见卷1 的第三部分Windows 2000 驱动程序开发指南第1章,INF SourceDisksFiles段。关于颜色管理和profile的更多信息参见在线DDK里的Monitor Profiles。 Model段 对于给定制造商支持的每一个模型的信息放在模型段。下例可识别由ACME制造的两个模型: [ACME] %ACME-1234%=ACME-1234.Install , Monitor\MON12AB %ACME-5678%=ACME-5678.Install , Monitor\MON34CD 每个模型用一行来描述。每行包括三个元素: 模型名。例如,%ACME-1234%是表示实际模型名的标记(在Strings段中出现)。 链接到随后的DDInstall段。例如,ACME-1234.Install是到随后的[ACME-1234.Install]段的一个链接。 硬件识别。例如,当表达式Monitor\MON12AB在设备的EDID中出现时,就组合了设备类(监视器)和设备标识(MON12AB)。 更一般的信息参见卷1 的第三部分Windows 2000 驱动程序开发指南第1章,INF Model段。 DDInstall段 当DDInstall段安装指定的设备时,给驱动程序提供关于所执行操作的信息。在这段中,每行给随后在INF文件里出现的不同INF编写者定义的段提供一个或多个链接。下例为ACME-1234模型显示DDInstall段: [ACME-1234.Install] DelReg=DEL_CURRENT_REG AddReg=ACME-1234.AddReg, 1280, DPMS CopyFiles=ACME-1234.CopyFiles DelReg指令(提供到DEL_CURRENT_REG段的一个链接,详述被删除的注册键。 AddReg指令(提供到三个段的链接,三个段是ACME-1234.AddReg、1280和DPMS,他们详述述加入的注册键。 CopyFiles指令(提供到ACME-1234.CopyFiles段的链接,指定从分配磁盘中拷贝的文件。 更一般的信息参见卷1 的第三部分Windows 2000 驱动程序开发指南第1章,INF DDInstall段。 INF编写者定义段 INF编写者定义段可以有任意的名字,只要它在INF文件里是唯一的。在其他段里的指令指向这些段。以下是来自monsamp.inf的INF编写者定义段的一部分: DEL_CURRENT_REG段(标识将被删除值的四个注册键: MODES、MaxResolution、DPMS和ICMProfile。在随后的段中用新值相应地更新这些键。 [DEL_CURRENT_REG] HKR, MODES HKR, , MaxResolution HKR, , DPMS HKR, , ICMProfile 1280段(更新MaxResolution注册键值到显示的字符串值。 [1280] HKR, , MaxResolution, , “1280, 1024” DPMS段(更新DPMS 注册键值为1(TRUE)。对于不支持电源管理的监视器,下行代替代替DPMS 键值为0(FALSE) 。 [DPMS] HKR, , DPMS, , 1 AddReg段(下列的第一行设定“MODES\1280,1024”子键为显示的字符串值。这一行也为这个子键识别Mode1这个值名。注意,这个值可建立新的Mode1时钟值而不考虑在EDID里列出的那些值。在第二行中, PreferredMode注册键被设定为在附随的字符串中显示的值。此字符串中的值是用来设定像素中水平和垂直的两个图形分辨率,以及屏幕的刷新速率,用赫兹(Hz)表示,这是较喜欢的屏幕方式。第三行设定ICMProfile键为“profile1.icm”字符串值。 [ACME-1234.AddReg] HKR, “MODES\1280, 1024”, Mode1, , “27.0-106.0, 55.0-160.0, +,+ ” HKR, , preferredMode, , “1024, 768, 70” HKR, , ICMProfile, 0, “profile1.icm” ICMProfile键也能够用内部的外形号码设置。如下例所示: HKR, , ICMProfile,1,9 此例中,数字1指出这个键是原始数据(FLG_ADDREG_BINVALUETYPE类型),而9是mnP22G21.icm的外形号码。Windows 2000发布 下表所列的外形。有关更多的信息参见第二章监视器外形。 外形编号 外形名  1 mnB22G15.icm  2 mnB22G18.icm  3 mnB22G21.icm  4 mnEBUG15.icm  5 mnEBUG18.icm  6 mnEBUG21.icm  7 mnP22G15.icm  8 mnP22G18.icm  9 mnP22G21.icm  10 Diamond Compatible 9300K G2.2.icm  11 Hitachi Compatible 9300K G2.2.icm  12 NEC Compatible 9300K G2.2.icm  13 Trinitron Compatible 9300K G2.2.icm   CopyFiles段(指出从分配磁盘,或磁盘到目标目录要拷贝的适当ICM文件。 [ACME-1234.CopyFiles] PROFILE1.ICM 1.6 显示器和视频微端口驱动程序的兼容性测试要求 这段为满足Windows Hardware Quality Lab (WHQL) 测试标准的 Windows NT?/ Windows? 2000 图形驱动程序和关联部分列出一些要求。满足或超过这些测试标准的驱动程序授权显示Microsoft? Windows? 徽标。有关更多的信息,到WWW.microsoft.com/hwdev站点查找。 1.6.1 INF和安装要求 Windows NT/ Windows 2000显示器和视频微端口驱动程序必须用INF文件安装。为确保和视频驱动程序相关联的所有注册条目完全地初始化,INF必须由系统支持的显示器类安装程序来解释并标记为Class=Display。 Windows 2000 DDK提供命名为geninf.exe的工具,它容易地为显示器和视频微端口驱动程序产生INF文件。细节参见创建图形INF文件。 1.6.2 视频微端口驱动程序请求 以下是视频微端口驱动程序的一些请求。 Windows NT/ Windows 2000视频微端口驱动程序必须是一个Single.sys文件。 微端口驱动程序由单一的二进制文件组成。微端口驱动程序的主要用途是检测,初始化,及安装一个或多个同类型的图形适配器。 微端口驱动程序仅能够做由videoprt.sys导出的调用。 微端口驱动程序仅能够调用那些由系统支持的视频端口驱动程序导出的函数。用下列语句可测定微端口驱动程序正在调用那个函数: link -dump -imports my_driver.sys 微端口驱动程序不能在机器中用无正式文件的OS函数调用来装入或安装另外的 驱动程序。 微端口驱动程序仅在接收终端用户请求时才能够启动全方位显示。 必须禁止由默认值产生的全方位显示。仅当通过控制面板请求全方位显示时,微端口驱动程序才启动它。OEMs通过把默认值当作preinstall的一部分才能够启动全方位显示 1.6.3 显示器驱动程序的要求 Windows NT?/ Windows? 2000显示器驱动程序必须满足在PC 99 设计指南中指定的要求。如果驱动程序为了WHQL测试即将被提交,它也必须满足WHQL要求。 1.6.4 控制面板的要求 以下是对显示器控制面板扩展的要求: 具有属性的页面必须保持不变。不能屏蔽,修改,删除,或替换Microsoft提供的任意属性页面(包括设置页面)。 借助于高级属性才能增加自定义属性页面。显示在最前面的特性是普通的包含在每一个Windows系统里的存取特性。因为Windows 2000 支持多重显示器,自定义属性页面不能追加到在显示器控制面板中最前面的属性页面的设置中。 控制面板的扩展必须能够操作现有的Windows控制面板的元素。 必须用除文本名以外的图标作为自定义属性页面的标志。为了避免同将来的操作系统或壳体释放相冲突,必须包括第三方的标志,除页面标志以外,图标(用公司的徽标)或着文本均可作为供应商的名字。例如,“Acme Video Controls”是容许的;“Video Controls”是不容许的。 如果必须的硬件/驱动程序结合不是当前的,控制面板的扩展就不必初始化。如果用自定义控制面板扩展封装,显示器硬件不是当前的,扩展就不必装载。同样地,如果自定义属性页面具有取决于所有权驱动程序扩展的特性(例如,在每个其他的显示器驱动程序中当前不允许扩展),当没有安装必须的驱动程序时,这些特性自己必须禁止使用或者所有权页面必须没有装载。 控制面板扩展必须考虑后台模式,监视器不能显示在监视器选项卡里的检查框。如果选择了检查框,控制面板扩展不必显示任何不通过EnumDisplaySettings计算的模式(在平台SDK文档中描述)。 控制面板状态必须存储在注册表中。不允许.ini文件。由控制面板扩展维护的任何状态必须存储在注册表的SOFTWARE键中,它在INF中通过HKR存取。 第2章 显示器驱动程序 Microsoft? Windows NT?/Windows? 2000显示器驱动程序编写者关心两个核心的软件接口: DDI接口—显示器驱动程序实现的函数设置。GDI能够调用DDI接口处理图形命令。 GDI接口—系统支持的帮助例程,显示器驱动程序调用它来简化驱动程序的实现。 这一章描述与Windows NT/Windows 2000显示器驱动程序以及一些实现信息相关的关键的概念。参见第一部分,第二章,图形驱动程序的GD I支持,以及第一部分,第三章,支持DDI,得到打印机驱动程序和显示器驱动程序两者公用的图形驱动程序设计细节,如驱动程序初始化、终止和图形输出。 也能够实现下列的DDI: DirectDraw DDI—允许供应商为DirectDraw提供硬件加速的图形接口。细节参见第三章,DirectDraw DDI。 Direct3D DDI—允许供应商为Direct3D 提供硬件加速的3-D图形接口。细节参见Direct3D DDI。 小客户端驱动程序(MCD)—为OpenGL允许低速的内务操作硬件加速的图形接口。细节参见在线DDK Graphics Driver Design Guide中Mini Client Driver。 DDI入口点和结构,以及GDI服务函数和对象的完整描述,参见在线DDK Graphics Driver Reference。 2.1 显示器驱动程序DDI 函数 Microsoft? Windows NT?/Windows? 2000 显示器驱动程序必须实现几个DDI函数。虽然利用现有GDI能力的驱动程序编写的驱动程序是比较短和简单的,必须确保驱动程序也能够比GDI更有效地实现操作。 每个显示器驱动程序最少必须满足: 启用和禁用图形硬件 提供具有关于硬件能力信息的GDI 启用绘图表面 如果是设备管理表面,执行图形操作(如最小的位图传输和绘制文本) 对于任何指定的表面,驱动程序调用是连续的。 2.1.1 请求的显示器驱动程序函数 下表列出所有的显示器驱动程序必须实现的函数。在DrvEnableDriver之后,其余的函数是按字母顺序列出的。 函数 描述  DrvEnableDriver 作为初始化驱动程序入口点,给GDI提供驱动程序版本号和可选函数支持的入口点  DrvAssertMode 为指定的视频硬件设备重新设置视频模式  DrvCompletePDEV 通知驱动程序设备安装完成的信息  DrvDisableDriver 释放驱动程序所有已分配的资源,并使设备返回到最初装入的状态  DrvDisablePDEV 当不再需要硬件时,释放设备使用的和表面创建但还没有删除的存储器和资源  DrvDisableSurface 通知驱动程序不再需要为当前设备创建的表面  DrvEnablePDEV 使PDEV有效  DrvEnableSurface 为指定的硬件设备创建表面  DrvGetModes 列出指定的视频硬件设备支持的模式   2.1.2有条件请求的显示器驱动程序函数 依赖于如何实现驱动程序以及基本适配器的特性,可以请求其他的DDI函数。例如,如果驱动程序自己管理表面(使用EngCreateDeviceSurface得到表面的句柄),它也必须最少支持以下的绘图函数: DrvBitBlt DrvStrokePath DrvTextOut 编写标准格式DIBs的驱动程序通常允许GDI管理许多或所有这些操作。支持可设置调色板的显示器必须支持DrvSetPalette函数。对所有图形驱动程序的有条件请求函数在第三章,第一部分有条件请求图形驱动程序函数表中列出。 2.1.3任意显示器驱动程序函数 为了减小驱动程序大小,显示器驱动程序编写者通常只增加在视频硬件中较好支持的那些可选函数。显示器驱动程序能够实现下列可选的函数: 函数 描述  DrvAlphaBlend 提供具有(混合的位块传送能力  DrvCreateDeviceBitmap 创建和管理驱动程序定义格式的位图  DrvDeleteDeviceBitmap 删除设备管理位图  DrvDestroyFont 通知驱动程序不再需要字体实现;驱动程序能够释放分配的数据结构  DrvDitherColor 请求设备创建一个相对于设备调色板抖动的画刷  DrvDrawEscape 实现绘画类型转义函数  DrvEscape 在设备无关DDI里从无效设备中查询信息  DrvFillPath 为设备管理表面绘制闭合的路径  DrvFree 释放与指定的数据结构相关的存储器  DrvGradientFill 为指定的图元增加阴影  DrvMovePointer 移动指针到新位置并刷新它  DrvNotify 允许GDI通知显示器驱动程序某些信息  DrvRealizeBrush 为已定义的表面实现指定的画刷  DrvSaveScreenBits 保存或恢复屏幕中指定的矩形  DrvSetPointerShape 如果驱动程序已画过指针,可从屏幕上删除它,然后可设定新的指针形状  DrvStretchBlt 在设备管理和GDI管理的表面中允许扩展的块传送  DrvStrokeAndFillPath 同时填充和绘制路径  DrvSynchronize 在GDI和显示器驱动程序支持的协处理器设备之间协调绘图操作;仅对引擎管理的表面  DrvSynchronizeSurface 允许由设备的协处理器和GDI协调完成绘图操作  DrvtransparentBlt 提供透明的位块传送能力   显示器驱动程序也能够可选地实现Microsoft? DirectDraw?,Direct3D?和/或 MCD接口。细节参见以下章节: DirectDraw DDI Direct3D DDI Mini 客户端驱动程序 2.2 显示器驱动程序初始化 显示器驱动程序初始化和图形驱动程序初始化相似,如第一部分第三章,支持初始化和结束中所描述的。这一节提供了显示器驱动程序的初始化细节。 在Windows NT Executive和Win32子系统装入和初始化以后,进行视频微端口和显示器驱动程序的初始化。系统装入在注册表中启用的视频微端口驱动程序,然后确定使用那对视频微端口驱动程序和显示器驱动程序。在处理过程中,根据Window Manager提供的信息,GDI打开所有必需的的显示器驱动程序。 在基本的显示器驱动程序初始化过程中创建桌面,如图2.1所示。 当调用GDI时为视频硬件创建第一个设备环境(DC),GDI调用显示器驱动程序函数DrvEnableDriver。返回时,DrvEnableDriver给GDI提供 DRVENABLEDATA结构,保存驱动程序的DDI版本号,以及所有由驱动程序实现的可调用的DDI函数入口点(DrvEnableDriver除外)。 然后GDI调用驱动程序的DrvEnablePDEV函数,请求驱动程序的物理设备特性的描述。在调用过程中,GDI在DEVMODEW结构中传送,并识别GDI想要设定的模式。如果GDI请求的是显示器或基本的微端口驱动程序不支持的模式,显示器驱动程序必须使这次调用失效。 图2.1 显示器驱动程序初始化 显示器驱动程序代表由GDI控制的逻辑设备。如图 2.2所示,单个逻辑设备能够管理几个物理设备,每一个特征由硬件类型、逻辑地址和支持的表面来表现。显示器驱动程序分配存储器支持它创建的设备。虽然指定的物理设备一次仅能使一个PDEV有效,但是相同的物理设备可以访问显示器驱动程序来管理多个PDEV。在独立的GDI调用DrvEnablePDEV中创建一个PDEV,并且每次调用创建另外一个使用不同表面的PDEV。 因为驱动程序必须支持多个PDEV,所以它不能使用公共变量。 图2.2 对物理设备的逻辑 当物理设备的安装完成时,GDI调用DrvCompletePDEV。当为设备请求GDI函数时,函数给驱动程序提供一个GDI产生的物理设备句柄。 在初始化的最后阶段,通过GDI调用DrvEnableSurface为视频硬件创建一个表面,它使图形输出到硬件。依靠设备和环境,显示器驱动程序用以下两种方式之一使一个表面有效: 驱动程序通过调用GDI函数EngCreateDeviceSurface获取表面的句柄来管理其自身的表面。设备管理的表面方法为不支持标准格式位图的硬件请求,对支持标准格式位图的硬件则是可选的。 如果硬件设备具有作为标准格式位图管理的表面,那么GDI能够象引擎管理表面那样完全地管理表面。驱动程序能够调用EngModifySurface把设备管理的原始位图转换成引擎管理的原始位图。驱动程序还能够hook任何绘图操作。 任何现有的GDI位图句柄是一个有效的表面句柄。驱动程序能够调用EngModifySurface把设备管理的原始位图转换成引擎管理位图。如果是引擎管理表面,GDI能够处理任一或所有绘图操作。如果是设备管理表面,驱动程序至少必须处理DrvTextOut、DrvStrokePath和DrvBitBlt。 GDI在调用DrvEnableSurface之后自动地使DirectDraw有效。在初始化DirectDraw以后,驱动程序能够用DirectDraw堆管理器执行屏幕外存储器的管理。细节参见DirectDraw 和GDI。 为了接收通知事件,,尤其是DN_DRAWING_BEGIN 事件,显示器驱动程序必须执行DrvNotify。在GDI开始绘图之前,GDI立即发送这个事件,因此它能够用来测定什么时候初始化了高速缓冲存储器。 启动过程的细节参见即插即用, 电源管理, 和 Setup设计指南。 2.3 桌面管理 显示器驱动程序必须实现DrvAssertMode和DrvGetModes来管理桌面。 如果显示器驱动程序是调色板管理的,它也将接收对DrvSetPalette的调用,把其调色板重新设置为正确的状态。 为了处理动态模式的变化,GDI的机制在 Windows 2000中已经有了较大的改变。在初始化期间分配给驱动程序的GDI HDEV不同于在完成模式改变之后分配的HDEV。因为以下原因,显示器驱动程序一般不受这个改变影响: 驱动程序总是在DrvCompletePDEV的实现中分配ppdev->hdevEng= hdev。 驱动程序总是在任一请求HDEV的回调过程中引用ppdev->hdevEng。 2.3.1 切换桌面:对DrvAssertMode的响应 当在显示器上的桌面之间切换时,窗口管理者确保正确地重画桌面、使鼠标指针有效并在正确的位置显示。当桌面切换时显示器驱动程序仅接收对DrvAssertMode 的调用。 调用这个函数时,驱动程序确保指示的PDEV或者在创建PDEV时指定的模式里,或者在文本模式里。然后窗口管理者选择正确的指针形式并移动它到当前位置。是GDI而不是驱动程序负责维护鼠标指针的状态。 GDI调用DrvAssertMode设定指定硬件设备的模式。当创建显示器驱动程序定义的PDEV结构时,这个函数选择指定的模式或者硬件的缺省模式。驱动程序保存PDEV当前模式的记录。 当用户在x86应用中从窗口应用切换成全屏应用时,或者当用户切换桌面时(在所有平台上),GDI也调用DrvAssertMode,允许把参数设定成FALSE。在对视频微端口驱动程序的EngDeviceIoControl调用中,通过发送IOCTL_VIDEO_RESET_DEVICE,显示器驱动程序必须把视频硬件恢复为缺省模式。 2.3.2返回显示器模式:DrvGetModes 显示器驱动程序也必须支持DrvGetModes。这个函数给GDI一个指向DEVMODEW结构数组的指针。此结构为它支持的各种模式定义显示器的属性,包括维数(对像素和毫米两者)、平面数、每个平面的位数、颜色信息等等。 2.3.3 支持多PDEV 这一节显示在当前的PDEV仍然在装载时,应用程序如何能够创建一个新的PDEV。因为应用能够用新桌面来创建一个新的PDEV,那么控制面板的Display applet程序要求显示器驱动程序支持另外的PDEV的启用。特别地,通过Display applet程序,最终用户通过运行一个测试程序,能够改变诸如大小、颜色数和屏幕的刷新速率这样的元素。Display applet程序动态地创建新桌面来测试显示器模式的改变。 当用户通过Display applet程序请求模式改变时,GDI执行下面的步骤。这些步骤假设当前驱动程序实例不拥有活动的Direct3D、WNDOBJ或DRIVEROBJ对象。 暂时使当前的PDEV无效。 调用DrvAssertMode(旧PDEV实例)。如果假设用微端口驱动程序控制,使用FALSE进行调用,或者如果使用PDEV。则用TRUE进行调用。 2. 装入一个新驱动程序(如果通过新PDEV实例请求)。 调用DrvEnableDriver(新驱动程序实例)。 创建一个新PDEV。 调用DrvEnablePDEV(新PDEV实例)。 调用DrvCompletePDEV(新PDEV实例)。 调用DrvEnableSurface(新PDEV实例)。 取出DirectDraw信息(如果DirectDraw通过驱动程序hook)。仅当第一次调用成功才进行第二次对DrvGetDirectDrawInfo的调用。 调用DrvGetDirectDrawInfo (新PDEV实例)。 调用DrvGetDirectDrawInfo (新PDEV实例)。 使DirectDraw有效(如果通过驱动程序hook并且前一个对DrvGetDirectDrawInfo的调用成功)。 调用DrvEnableDirectDraw(新PDEV实例)。 拷贝旧PDEV状态到新的PDEV实例(如果使用相同驱动程序和DirectDraw的两个实例是通过驱动程序hook)。 调用DrvResetPDEV。 通知每一个与新HDEV相关的驱动程序实例。对DrvCompletePDEV的第一次调用通知新驱动程序实例;第二次调用通知旧驱动程序实例。 调用DrvCompletePDEV(新PDEV实例)。 调用DrvCompletePDEV(旧PDEV实例)。 驱动程序在任何请求HDEV的对GDI回调的中使用新HDEV值。 使DirectDraw无效(如果通过驱动程序hook并且DirectDraw是活动的)。 调用DrvDisableDirectDraw(旧PDEV实例)。 使表面无效。 调用DrvDisableSurface(旧PDEV实例)。 使PDEV无效。 调用DrvDisablePDEV(旧PDEV实例)。 在这个例子中,当用户点击Apply时,GDI暂时使当前的PDEV无效,然后在对话框中创建符合显示器模式选择的第二个PDEV。在测试模式下,用户在显示器屏幕上观察位图以后,撤消第二个PDEV,并且Display applet 程序为桌面恢复原来的PDEV。注意不能恢复原来的显示器的设定,如果设置与硬件和驱动程序是不兼容的,那么系统就不可用。 如果驱动程序的当前实例拥有Direct3D、WNDOBJ或者DRIVEROBJ 对象,前面模式的驱动程序的视图按如下改变顺序变化。(注意在Windows 2000中,驱动程序一初始化DirectDraw就总是有效的。) 拥有的驱动程序实例的析构要推迟。特别地,在模式改变时第7步、第8步和第9步不产生对DrvCompletePDEV的第二次调用。由于在第1步中调用DrvAssertMode(FALSE),因此禁止旧驱动程序实例,并且保留它,直到系统还原至原来的模式或者所有对象引用实例均被撤消为止。 在撤消引用对象之前如果系统还原到原来的模式,再现原来的驱动程序实例。即不产生第2至第5步并且通过调用DrvAssertMode(TRUE) 重新启用原来的驱动程序实例。(参见第一步。) 在撤消所有的引用对象之前如果系统不还原到原来的模式,当删除撤消最后一个引用对象时撤消驱动程序实例。即当撤消最后一个引用对象时在第7步、第8步、和第9步中产生对DrvCompletePDEV的第二次调用(例如,当终止所有自己的进程时)。 这隐含着在任何时候,调用Direct3D或OpenGL驱动程序能撤消非活动的驱动程序实例。如果驱动程序是在全屏DOS模式中,或者另外的驱动程序(例如VGA驱动程序)完全地拥有硬件,那么即使另外的驱动程序实例目前有效也能够调用这些驱动程序。因此驱动程序的DrvDisableDirectDraw、DrvDisableSurface、和DrvDisablePDEV例程(参见第8至第10步)不能假定设备处于图形模式中,以及它们拥有独占访问。一般地,在DrvDisableXxx例程中驱动程序不必操作视频硬件,除非驱动程序知道它们的实例当前是活动的(从最后一次调用DrvAssertMode存储状态)。 注意 PDEV是驱动程序专用的并且包括表示相关物理设备的所有信息和数据。为了创建多PDEV,图形驱动程序必须满足以下两个要求: 驱动程序不必使用全局变量来代替间接引用PDEV结构的成员。当创建新PDEV或恢复旧PDEV时,如果使用了全局变量,它们可以包含或指向随机数据。在PDEV中必须保存所有状态信息。总是把PDEV传送给任一图形操作,并且为此用来得到或设定全局数据。 在图形驱动程序中必须执行DrvDisableSurface 、DrvDisablePDEV和DrvDisableDriver例程,以便使应用程序能够创建和撤消另外的PDEV,并且在一些情况下装载多个驱动程序。   注意 如果驱动程序的版本号是1.0,GDI不调用驱动程序来创建第二个PDEV。在DRVENABLEDATA中返回驱动程序的版本号。   注意 有时候用不同于当前装载的驱动程序来显示Display applet的测试位图。例如,如果系统用VGA驱动程序在16色模式中运行,并且用VGA64K显示器驱动程序来测试64K色模式,动态地装载VGA64K驱动程序并且当测试完成时卸载。   2.4 指针控制 每一个应用程序必须能够控制一个在已开窗口的显示器周围移动的指针,响应指示设备,如鼠标。显示器驱动程序、GDI或视频微端口驱动程序能够绘制指针。 GDI能够直接为使用线性可变址缓冲区的显示器处理所有的指针拖动。对于没有线性帧缓冲区的设备,GDI用DrvCopyBits完成指针拖动。然而在显示器驱动程序中,由硬件支持的和执行的指针代码速度更快。 显示器驱动程序有时可以选择将绘制那一种类型的指针,以及允许GDI处理那一种类型的指针。例如,设备可以支持硬件中的单色指针,但是无法进行彩色指针的调用,而允许GDI去处理彩色指针的调用。 显示器驱动程序能够控制处理器不独占的指针,并且这个指针不会被中断移动,如垂直同步中断。在这些特别的情况中,微端口驱动程序必须绘制和控制指针,因为需要某个核心模式回调(只在微端口驱动程序中可用)。这会从反面影响性能,因为它要求IOCTL为每个指针操作和微端口驱动程序通信。 为编写显示器驱动程序和微端口驱动程序对,在两个驱动程序之间传递指针信息必须包含IOCTL,并允许微端口驱动程序在必要时假定绘制任意或所有的指针。permedia和framebuf例子驱动程序使用整个方法处理指针。视频指针IOCTL定义参见在线DDK的Video I/O Control Codes。 2.4.1 指针绘制 GDI支持彩色指针和单色指针。单色指针的形状用单个位图定义。位图的宽度与显示器上指针的宽度相同,但是位图的垂直范围是显示器上的两倍,允许它包含两个掩码。 对指针函数的调用由GDI串行化。这意味着驱动程序中两个不同的线程不能同时执行指针函数。有两种可能的指针函数:DrvSetPointerShape和DrvMovePointer。 2.4.1.1 绘制单色指针 单色位图由两部分组成:第一个定义指针的AND掩码,第二个定义XOR掩码。合到一起,这些掩码为指针图像的每个像素提供两位信息。下表描述了在AND和XOR掩码中显示指定值的结果。 AND掩码值 XOR掩码值 显示结果  0 0 像素是黑的  0 1 像素是白的  1 0 像素不变(透明)  1 1 像素颜色翻转   这个位图定义和使用提供了一个黑白图像,同时提供了组成指针的像素的透明和翻转的支持。 2.4.1.2 绘制彩色指针 彩色指针的定义方法和单色指针相同,也提供了对透明和翻转的支持。 如果彩色位图是黑色(索引0)并且AND掩码值是1,结果是透明。 如果彩色位图是白色并且AND掩码值是1,结果是翻转。 如果设备不能显示彩色,指针可被画成黑色或白色。 这些约定允许应用为彩色和单色显示器使用一个指针定义。 2.4.2 控制指针:DrvSetPointerShape 如果显示器驱动程序控制指针,驱动程序必须支持DrvSetPointerShape允许改变指针的形状。对DrvSetPointerShape的一个调用产生下列结果: 函数删除任何已经存在的指针,驱动程序在显示器上已经绘制了这些指针。 设置新的请求的形状,除非它不能处理这个形状。 新指针在调用的参数指示的位置显示。 驱动程序能调用DrvSetPointerShape使GDI管理一个软件光标。 2.4.3 移动指针:DrvMovePointer 如果DrvSetPointerShape包含在驱动程序中,也必须支持DrvMovePointer。DrvMovePointer把驱动程序管理的指针移动到一个新的位置。因为GDI串行调用指针函数,当任何线程在显示器驱动程序中绘制时并不调用DrvMovePointer,除非在DEVINFO结构中设置了GCAPS_ASYNCMOVE标志。 驱动程序要调用MovePointer使GDI在设备上移动引擎管理的指针。通过调用EngSetPointerShape驱动程序请求GDI管理光标。 2.5 管理显示器调色板 如果视频硬件支持被设置的彩色,它维护一个称作调色板的颜色查找表。GDI得到每个RGB值并转换成设备颜色索引,使其能够被显示。GDI使用了预先计算和缓存的表进行转换。这些表对驱动程序可以作为用户对象XLATEOBJ访问。因此,每个GDI图像函数取到源彩色,使用XLATEOBJ结构把它们移动到目标设备,进行颜色转换。有关调色板以及GDI如何处理它们的更多的信息,参见第一部分,第2章,GDI对调色板的支持。 如果视频硬件支持被设置的调色板,当GDI结束了把颜色映像到应用请求的设备调色板,GDI调用在显示器驱动程序中的DrvSetPalette函数。GDI传递新的调色板到显示器驱动程序,驱动程序查询PALOBJ,为视频硬件设置其内部硬件调色板以匹配调色板变化。这称作调色板实现。 DrvSetPalette函数为驱动程序提供了对PDEV的处理,请求驱动程序实现设备的调色板。驱动程序设置硬件调色板,尽可能地匹配给定调色板中的入口。 如果设备支持一个能够设置的调色板,就请求这个入口点,否则就不再提供。在DrvEnablePDEV返回的DEVINFO结构的flGraphicsCaps字段中设置GCAPS_PALMANAGED位,显示器驱动程序指定设备有一个可设置的调色板。 服务例程PALOBJ_cGetColors对显示器驱动程序是可用的。这个函数从索引调色板中下载RGB颜色,在DrvSetPalette的实现中调用。 2.6 显示器驱动程序中的位图 某些设备,如16色VGA显示器,执行位块传输比非标准位图更快。为支持这一点,驱动程序可以挂起DrvCreateDeviceBitmap,允许驱动程序创建驱动程序完全管理的位图。当驱动程序创建了这样一个位图,可以用任何格式存储这个位图。它检查要传递的参数并提供至少和请求的每像素的位数一样多的位图。在创建后不定义位图的内容。如果应用请求设备管理的位图,在DrvCreateDeviceBitmap 返回控制后,GDI为绘图函数调用驱动程序。如果驱动程序返回FALSE,驱动程序管理的位图就没有创建,GDI可以在一个引擎管理的表面处理绘图操作。 DrvSaveScreenBits函数也和显示器驱动程序中的位块传输有关。一些显示器驱动程序从屏幕外的设备存储器移动数据比从DIB重画或拷贝的区域更快。这些驱动程序挂起DrvSaveScreenBits,当出现菜单或对话框时,它使调用驱动程序保存或恢复指定的显示图像的矩形更快。 注意 对位块传输调用,GDI(不是驱动程序)处理指针删除和裁剪区的锁定。 在屏幕外存储器实现设备位图的驱动程序能显著地改变系统的性能。屏幕外设备位图通过以下方法改进系统的性能: 使用加速器硬件代替GDI进行绘图。 改进位图到屏幕位块传输的速度。 减少主存储器的请求。(存储在屏幕外存储器的位图不占用主存储器的空间。) 使用硬件方法执行支持OpenGL的操作,如屏蔽位块传输和双缓冲。 驱动程序可以通过DrvCreateDeviceBitmap在屏幕外的存储器中实现设备位图。例子permedia驱动程序屏幕外的存储器中实现设备位图并提供一个完整的例子。 2.7 异步绘图 异步处理一个或多个DDI绘图操作以及通过使用EngModifySurface提供GDI访问其位图的显示器驱动程序必须实现一个同步例程。在批量进行DDI绘图操作时为了避免绘图出错,驱动程序也必须提供同步例程。 这样的驱动程序有两个选项实现DrvSynchronizeSurface或DrvSynchronize作为同步例程。GDI调用这些例程之一,仅当驱动程序将它们在EngAssociateSurface中挂起。这两个同步例程都挂起时,驱动程序在GDI中只调用DrvSynchronizeSurface。 DrvSynchronizeSurface给驱动程序提供额外的信息,不管是否有同步事件以及为什么会发生。这使驱动程序减少了同步产生的性能延迟。例如,跟踪哪个设备位图在在加速器队列中的驱动程序可能要从DrvSynchronizeSurface中立即返回,如果指定的表面当前不在队列中。 除了提供同步例程之外,驱动程序通过在DEVINFO结构的flGraphicsCaps2字段中设置下列标志也能激活一个基于时间的或可编程刷新机制。 GCAPS2_SYNCTIMER,它引起周期性地调用驱动程序的同步例程。批量DDI调用驱动程序必须指定这个标志。通过这些操作,驱动程序避免了一些问题,如软件光标移动或猝发执行的绘图产生的延迟。 当同步例程因周期性事件调用时GDI传递DSS_TIMER_EVENT标志到DrvSynchronizeSurface。 GCAPS2_SYNCFLUSH,当调用Microsoft? Win32? GdiFlush API时,引起调用驱动程序的同步例程。执行异步绘图的驱动程序必须指定这个标志并提供一个同步例程。 当同步例程因基于刷新的事件调用时,GDI传递DSS_FLUSH_EVENT标志到DrvSynchronizeSurface。有关GdiFlush更多的信息参见平台SDK文档。 批量DirectDraw绘图调用的限制 当目标表面是可视屏幕时,驱动程序必须不进行批量DirectDraw调用。这种情形在Windows DirectX?应用中出现,完整的帧通过DdBlt更新到屏幕,因此被立即显示。这个限制也作用于DirectDraw?视频端口表面,进行异步地翻转。 2.8 显示器驱动程序中的透明性 如果显示器硬件支持透明性,显示器驱动程序要实现DrvTransparentBlt。 为了减少从视频存储器读的代价,当源和目标表面都在视频存储器中时,必须实现这个函数。驱动程序让GDI处理从系统存储器到视频存储器的透明位块传输,同时让GDI处理扩展的位块传输。 例子代码参考permedia例子驱动程序。 2.9 显示器驱动程序的特别效果 Windows? 2000 支持下列特殊效果: 如果显示器硬件支持(混合,显示器驱动程序能实现DrvAlphaBlend。 如果显示器硬件支持梯度填充,显示器驱动程序能实现DrvGradientFill。 (混合 Windows 2000命令解释程序使用广泛的(混合执行一些操作如blend-in和blend-out动画以及(光标。因为(混合要求从源和目标表面读取,当源或目标是视频存储器时直传到GDI很慢。因此,在驱动程序中硬件加速器产生可视的光滑动画并改进整体系统的性能。 驱动程序为非延伸的位块传输实现DrvAlphaBlend,使用一个常量(从兼容的位图,以及使用每像素的(值从32 bpp BGRA系统存储器表面。使用三角形纹理填充能够实现DrvAlphaBlend,提供无缝的可视性。 注意DrvAlphaBlend产生的最坏情况的错误是每个颜色通道超过1。 梯度填充 Windows 2000命令解释程序在所有的标题条上使用梯度填充。 注意 DrvGradientFill产生的最坏情况的错误是,累积超过3个颜色通道,不超过8。 2.10 显示器的颜色管理 GDI支持图像颜色管理(ICM)2.0版。显示器驱动程序能不要实现任何特殊代码使用ICM。 如果显示器硬件支持(斜面,显示器驱动程序要实现DrvIcmSetDeviceGammaRamp。彩色校准应用要求颜色精确使用这个性能。DirectDraw也使用这个函数允许DirectX应用(如在RGB模式中执行调色板动画的游戏)控制(斜面。例子代码参考permedia例子驱动程序。 2.10.1 监视器Profile 监视器profile是使用彩色管理的设备profile。这个profile包含一些信息,关于如何把监视器颜色空间和颜色范围里的颜色转换到与设备无关的颜色空间的颜色。任何用户模式的应用,如设置程序或具有图形能力的字处理程序,能使用监视器profile,提供ICM已经启用及应用已知profile的格式。 虽然使用第三方工具可以创建客户监视器profile,也可以使用与Windows 2000一起封装的监视器profile之一,在下表中描述。 Profile 监视器特征  mnB22G15.icm B22 phosphor,gamma 1.5  mnB22G18.icm B22 phosphor,gamma 1.8  mnB22G21.icm B22 phosphor,gamma 2.1  mnEBUG15.icm EBU phosphor,gamma 1.5  mnEBUG18.icm EBU phosphor,gamma 1.8  mnEBUG21.icm EBU phosphor,gamma 2.1  mnP22G15.icm P22 phosphor,gamma 1.5  mnP22G18.icm P22 phosphor,gamma 1.8  mnP22G21.icm P22 phosphor,gamma 2.1  Diamond Compatible 9300k G2.2.icm 9300( Kelvin white point,gamma 2.2  Hitachi Compatible 9300k G2.2.icm 9300( Kelvin white point,gamma 2.2  NEC Compatible 9300k G2.2.icm 9300( Kelvin white point,gamma 2.2  Trinitron Compatible 9300k G2.2.icm 9300( Kelvin white point,gamma 2.2   安装一个监视器profile 用户可以用3种不同的方法安装监视器profile: 在Windows资源管理器中选择一个profile,在名字上单击鼠标右键,再点击Install Profile。 参考监视器INF文件中的profile。 在应用中用显式的代码指定profile的路径和文件名。 因为监视器profile默认的目录受变化的影响,所以不推荐使用显式的代码指定profile的路径和文件名。 使用一个监视器profile 与打印机profile不同,监视器profile不支持输出设备和应用程序之间很小的通信。例如,如果一个用户在视频缓冲区中改变(斜面,监视器profile不会注意这样一个小的变化发生。在这种情况下,通过ICM两个颜色修正在显视之前应用于图像,如下列步骤所示。 应用打开并操作图像。 应用通过调用Win32 GDI ICM函数,如SetICMMode,使ICM有效。(更多的信息参见平台SDK。) 应用发送图像到Win32 GDI。 如果ICM启用,Win32 GDI使用监视器profile转换图像中的颜色。 Win32 GDI发送图像到核心模式GDI。 核心模式GDI为显示器驱动程序格式化图像,基于这样一些DC的设备profile,如位深度、分辨率和过渡调色等。 显示器驱动程序(或视频硬件)对图像进行(修正。 2.11 DirectDraw和GDI 当显示器驱动程序初始化时,GDI自动启用DirectDraw。为在DirectDraw和驱动程序的DDI部分之间提供更好的相互作用,驱动程序也要支持DirectDraw DDI实现或调用下列函数: DrvDeciveSurface GDI驱动程序表面包裹DirectDraw驱动程序表面的驱动程序实现的函数,允许任何GDI绘制到DirectDraw视频存储器,或被硬件加速的AGP表面(而不是通过DIB引擎用软件绘制)。一般地,如果驱动程序已经支持屏幕外的设备位图,这个函数只在很少几个额外的线的代码中请求。 DrvDeciveSurface改进了也使用GDI的DirectDraw应用的性能,当软件光标在DirectDraw或Direct3D应用中使用时也消除了光标闪烁。 例子驱动程序的实现参见permedia驱动程序源代码。 HeapVidMemAllocAligned和VidMemFree 驱动程序调用的函数使用DirectDraw堆管理器进行所有的屏幕外存储器管理。DrvCreateDeviceBitmap调用HeapVidMemAllocAligned请求DirectDraw为GDI位图分配空间;DrvDeleteDeviceBitmap调用VidMemFree释放分配。 DirectDraw对于屏幕外的存储器分配比驱动程序的DDI部分更加优先。驱动程序挂起DirectDraw DdFreeDriverMemory回调,它允许驱动程序从屏幕外存储器移开GDI表面,得到为高优先级的DirectDraw表面分配的空间。 HeapVidMemAllocAligned和VidMemFree都在dmemmrg.h中声明,它发布在Windows 2000 DDK中。在包含这个头文件之前,驱动程序必须定义_NTDDKCOMP_。 例子驱动程序的实现参见permedia驱动程序源代码。 2.12 跟踪窗口变化 窗口的变化,包括在多监视器系统中,能由设备驱动程序通过WNDOBJ跟踪。WNDOBJ是一个驱动程序级的窗口对象,包含窗口的位置、大小和可视的客户区域等信息。通过对应于应用程序窗口创建一个WNDOBJ,驱动程序能跟踪窗口的位置、大小和可视的客户区域的变化。 应用程序使用Win32 API访问设备驱动程序实现的WNDOBJ_SETUP函数。通过Win32 ExtEscape函数获得访问。GDI用DrvEscape把这个出口调用传递到设备驱动程序,DrvEscape由设备驱动程序用WNDOBJ_SETUP的iEsc值实现。 应用调用ExtEscape(hdc, WNDOBJ_SETUP, …)并通过驱动程序的输入缓冲区传递句柄到应用创建的窗口(由CreateWindow或一些等效的Win32函数)。如果驱动程序保持跟踪窗口,它在ExtEscape调用的环境中调用EngCreateWnd,为给定的窗口创建一个WNDOBJ结构。从这一点,窗口的任何改变都传递到驱动程序。 驱动程序用类似于下面的方式处理ExtEscape调用: ULONG DrvEscape( SURFOBJ *pso, ULONG iEsc, ULONG cjIn, PVOID pvIn, ULONG cjOut, PVOID pvOut, { WNDOBJ *pwo; WNDDATA *pwd; if (iEsc == WNDOBJ_SETUP) { pwo = EngCreateWnd(pso,*((HWND *)pvIn),&DrvVideo, WO_RGN_CLIENT,0); //Allocate space for caching client rects. Remember the //pointer in the pvConsumer field. pwd = EndAllocMem(0,sizeof(WNDDATA),DRIVER_TAG); WNDOBJ_vSetConsumer(pwo,pwd); //Update the rectangle list for this wndobj. vUpdateRects(pwo); return(1); } ) 创建一个窗口对象包括锁定特定的窗口资源,因此,只在WNDOBJ_SETUP的环境脱离DrvEscape或DrvSetPixelFormat,才调用EngCreateWnd。 EngCreateWnd函数支持多驱动程序的窗口跟踪。通过EngCreateWnd,每个驱动程序标识自己的回调例程,GDI用来调用改变相应的窗口。例如,这个特性允许一个活的视频驱动程序跟踪活的视频窗口的变化,而OpenGL驱动程序跟踪OpenGL窗口的变化。 如果一个新的WNDOBJ 在DrvSetPixelFormat或ExtEscape中调用,GDI用最新的窗口状态回调驱动程序。当WNDOBJ引用的窗口被撤消时,GDI也回调驱动程序。 作为一个加速器,驱动程序可以访问WNDOBJ的公共域,定义如下: typedef struct { CLIPOBJ coCLIENT; PVOID pvConsumer; RECTL rclClient; } WNDOBJ; 这里coClient是CLIPOBJ,描述窗口的区域;pvConsumer是一个驱动程序定义的值,标识这个特别的WNDOBJ;rclClient是屏幕坐标中窗口客户区域无右下边的矩形。 注意 这些公共字段要确保保持不变,只在EngCreateWnd中提供给GDI的驱动程序回调例程的环境中,或者是给定WNDOBJ的DDI函数。 跟踪窗口改变包括给WNDOBJ提供支持的3个回调函数的使用。可视区域通过WNDOBJ_cEnumStart和WNDOBJ_bEnum回调函数计算。驱动程序通过调用WNDOBJ_vSetConsumer回调函数将其自己的数据与WNDOBJ相关。 2.13 支持DitherOnRealize标志 在GDI/DDI的早期版本中,GDI对显示器驱动程序函数的两个调用被请求用来抖动指定的颜色,并实现颜色的画刷。例如,当应用请求用抖动的颜色填充矩形时,GDI一般调用DrvBitBlt,传递要使用的矩形的范围和画刷对象。显示器驱动程序检查画刷,发现它还没有被实现,并用BRUSHOBJ_pvGetRbrush回调GDI进行GDI的画刷实现。因为是显示器驱动程序而不是GDI执行画刷抖动,GDI传递在DrvDitherColor回调函数中应用最初为抖动提供的RGB给显示器驱动程序。 DrvDitherColor返回一个指向彩色索引数组的指针,描述给GDI提供的抖动信息。GDI通过调用DrvRealizeBrush把这个抖动信息立即传递给显示器驱动程序。随着BRUSHOBJ的实现,控制返回到GDI并因此返回到最初的DrvBitBlt函数。 为使用以上技术实现抖动,在调用DrvRealizeBrush之后,GDI必须立即调用DrvDitherColor,两个独立的函数调用。在DEVINFO结构里设置GCAPS_DITHERONREALIZE标志,修改DrvRealizeBrush来有效地组合这些函数,排除了独立调用DrvDitherColor以及保存某些存储器分配的需求。在这个模式下,如果显示器驱动程序设置GCAPS_DITHERONREALIZE,GDI使用将要抖动的RGB和在iHatch中设置的RB_DITHERCOLOR标志调用DrvRealizeBrush。RB_DITHERCOLOR标志在iHatch的高字节中设置,而要抖动的RGB颜色包含在3个低字节中。在这个情况下排除了调用DrvDitherColor,因为两次调用的函数放进了一个中。 例子代码参见permedia例子驱动程序。 2.14 支持分组的帧缓冲区 当前大多数加速器有帧缓冲区,能够线性映射到CPU地址空间。这些设备的显示器驱动程序不必要支持分组的帧缓冲区。 GDI不能直接访问与分组的帧缓冲区相关的分组存储器。因此,具有这样的帧缓冲区的设备的 显示器驱动程序必须把帧缓冲区分成一系列连续的组,并提供一种方法使GDI在相应的帧缓冲区中执行其绘图操作。即GDI在移动到后一个组之前,向帧缓冲区的一个组写入数据,如果必要,通过一个称作“组回调”的机制完成绘图操作。 与Windows 2000 DDK一起发布的permedia例子驱动程序提供了例子代码,实现分组的帧缓冲区支持。 图2.3显示了例子加速器的帧缓冲区,一个1024(768的VGA显示器缓冲区,分成几个组。这个图仅仅是示意图。显示器驱动程序特地不使用物理地址A000;它使用一个逻辑地址通过微端口驱动程序传递。 图2.3 映射视频缓冲区到分组帧缓冲区的例子 在这个例子中,视频存储器的内容通过一系列绘图操作写到加速器,帧缓冲区的地址连续的组中。至于GDI所关心的,出现的每个绘图操作是到标准的帧缓冲区,而不是到加速器的帧缓冲区的不同的组。加速器的设备驱动程序在分组的基础上,处理引起GDI向加速器的帧缓冲区绘图的分组操作。 当加速器使用一个分组的帧缓冲区时,帧缓冲区是设备管理的表面,因此显示器驱动程序挂起绘图函数调用。当显示器驱动程序挂起一个调用,如绘制路径、填充路径或位块传输,它确定在帧缓冲区中哪一个组受被调用的绘图函数的影响。 如果驱动程序选择用GDI执行绘图函数,它调用相应的EngXxx函数。然而,在进行调用之前,显示器驱动程序必须修改它在挂起调用时接收的裁剪和表面对象,并在GDI的回调中传递这些修改的对象。修改它们可以防止GDI在组外绘图。也就是如果调用GDI绘制一部分在下一个组中的路径,以及如果裁剪和表面对象没有被修改,GDI写到当前组的范围以外。如果GDI试图在组的范围以外绘图,存取失败的结果将很难跟踪。 图2.4中分组帧缓冲区的例子显示在显示器跨越分组帧缓冲区的两个组,BANK_1和BANK_2,如何绘制一个椭圆对象。 图2.4 帧缓冲区中绘制跨越多个组的对象 为绘制这个对象,GDI必须首先绘制椭圆的顶部(在BANK_1中)到标准的帧缓冲区,然后绘制椭圆的下半部分到同一个标准缓冲区。显示器驱动程序必须通过GDI映射这两个连续的写操作到分组的帧缓冲区BANK_1和BANK_2进行显示,并防止GDI写到每组的限制以外。 当执行分组帧缓冲区时,显示器驱动程序通过检查调用的参数或回调GDI,能确定对象的边界(目标矩形的大小)。根据对象的边界,驱动程序能确定对象跨越多少组。对边界矩形接触到的每个组,显示器驱动程序回调相应的GDI绘图函数,改变每个调用的值。 驱动程序改变由GDI最初传递的CLIPOBJ成员,对应于组边界的改变。顶部和底部的扫描值重新定义,GDI不试图在组的限制以外绘制。组管理器从GDI获得原始的CLIPOBJ数据,并保留这些值为以后恢复用。然后改变边界,提供新的rclBounds.top和rclBounds.bottom值,描述要绘制的组的范围。分组期间,GDI必须裁剪到一个尺寸,防止绘制整个路径和超出当前组的限制。 如果GDI传递的原始的CLIPOBJ定义为NULL或DC_TRIVIAL,显示器驱动程序传递通过EngCreateClip创建的替代的CLIPOBJ。这个替代的CLIPOBJ被改变,用来定义一个裁剪窗口,这样GDI对一个组的范围进行裁剪。如果CLIPOBJ是复杂的,如在椭圆上的三角形对象,如图2.4所示,显示器驱动程序用rclBounds.top和rclBounds.bottom的值改变这个复杂的CLIPOBJ,在两个裁剪对象间产生一个附加的效果。作为结果,GDI防止写到组的边界以外。驱动程序必须恢复前面从GDI获得的CLIPOBJ数据的原始边界。 除改变边界值之外,显示器驱动程序在裁剪对象中设置OC_BANK_CLIP标志,通知GDI这是一个分组的回调。 GDI也必须参考标准帧缓冲区的起始点进行绘制。当调用绘图是,GDI简单地得到一个指向SURFOBJ的指针,包括pvScan0、lDelta和iBitmapFormat成员。GDI使用这些值计算在表面的什么位置绘制,算法如下: start_draw_point = pvScan0 + (y*lDalta) + (x*PixelSize(iBitmapFormat)) 这里x和y是绘图开始的坐标,start_draw_point是第一个像素绘制的地址。GDI在每个绘图调用时执行这个计算,并总是以pvScan0为SURFOBJ的参考,它是标准帧缓冲区的起始逻辑地址。 例如,如果GDI需要绘制每像素8位的64K帧缓冲区的全部内容,pvScan0的起始逻辑地址为0x100000,将在0x10FFFF(0x100000+(63*1024)+(1023))结束绘图操作,这里y是63,lDalta是1024,x是1023(最后一根扫描线的最后像素的位置)。 下一次显示器驱动程序调用GDI绘制的对象的部分落在分组的帧缓冲区的下一个组,GDI解释y的值为64。pvScan0的值为0x100000,y的值为64,GDI尝试开始在0x110000处写入数据。然而,0x110000超出了64K帧缓冲区0x10FFFF的范围,在这个操作中GDI不能进行写数据。 因此,当显示器驱动程序请求GDI写入出现在帧缓冲区的第二个及随后的组中时,驱动程序必须减pvScan0的值,使GDI计算起始点仍然参考0x100000的例子地址。继续这个例子,当在帧缓冲区的第二个组中绘图时,这个方法减去pvScan0的值到0x090000。作为对pvScan0改变的一个结果,GDI仍用地址0x100000参考绘图。即0x090000+(64*1024)+0等于0x100000,这里GDI必须开始以数据映射到帧缓冲区第二个组的顺序绘图。 2.15 卸载视频硬件 当一个表面不再被请求,GDI调用DrvDisableSurface通知显示器驱动程序,通过DrvEnableSurface为当前硬件设备创建的表面可以被禁止。驱动程序必须释放表面使用的任何资源。 表面被禁止后,GDI调用DrvDisablePDEV通知驱动程序不再需要意见设备。然后驱动程序释放在处理DrvEnablePDEV过程中分配的任何存储器和资源。 最后,GDI通过调用DrvDisableDriver禁止显示器驱动程序。驱动程序必须释放在DrvEnableDriver期间分配的任何资源,并恢复视频硬件的缺省状态。当驱动程序从DrvDisableDriver函数返回后,GDI释放它为驱动程序分配的存储器,并从存储器中移走代码和数据。 图2.5显示了GDI禁止视频硬件的调用序列。 图2.5 禁止视频硬件 2.16 显示器驱动程序中的多监视器支持 Windows 2000提供了多监视器支持,显示器驱动程序编写者为提供这个支持不必实现任何特别的代码。 显示器驱动程序的实现必须不使用任何全局变量。对一特定的显示器驱动程序所有的状态必须在PDEV中。GDI为每个通过视频微端口驱动程序创建的硬件设备的扩展调用DrvEnablePDEV。 在多监视器系统中为跟踪窗口变化,驱动程序可以请求GDI用桌面坐标创建WNDOBJ对象。驱动程序通过用WO_RGN_DESKTOP_COORD标记调用EngCreateWnd来进行这个工作。更多的信息参见跟踪窗口变化。 在多监视器系统中,GDI在DEVMODEW结构的dmPosition成员中存储设备的桌面位置。 2.17 镜像驱动程序 镜像驱动程序是一个虚拟设备的显示器驱动程序,反映一个或多个额外的显示器驱动程序的绘图操作。它的实现和属性与其他任意的显示器驱动程序很相似;然而,与其配对的视频微端口驱动程序与一般的视频微端口驱动程序相比则很小。关于镜像系统中视频微端口驱动程序的更多的信息参见第6章微端口中的镜像驱动程序支持。Windows 2000 DDK包含了一个例子镜像驱动程序,在下面3个目录中包括了组件源文件。 目录 包含的源文件  Video\displays\mirror\dll 镜像驱动程序  Video\miniport\mirror 微端口驱动程序  Video\displays\mirror\app 用户模式服务。也包含mirror.inf。   GDI支持虚拟桌面并提供在镜像设备上复制虚拟桌面的一部分的能力。GDI把虚拟桌面作为物理显示器驱动程序层上面的图形层来实现。所有的绘图操作从这个虚拟桌面开始空间;在存在于虚拟桌面的相应的物理显示器设备上,GDI裁剪并绘制它们。 在虚拟桌面上镜像设备能指定任意的裁剪区域,包括跨越多个物理显示器设备的区域。然后GDI发送驱动程序的裁剪区域交叉的镜像设备所有的绘图操作。镜像设备能设置一个裁剪区,与特定的物理设备精确匹配;因此,它能有效的镜像那个设备。 注意 在Windows 2000中,镜像驱动程序的裁剪区必须包括主显示器设备。 mirror驱动程序例子代码说明了如何实现镜像驱动程序。帮助理解这个例子的更多的信息如下: 使用例子INF文件,mirror.inf,作为一个模板。细节参见镜像驱动程序INF文件。 参见mirror.exe应用程序,它展示镜像驱动程序如何连接到虚拟桌面。细节参见镜像驱动程序安装。 参考平台SDK文档中有关使用Win32 EnumDisplayDevices函数的信息。使用这个函数确定\\.\Display#名与镜像显示器设备相连。这个数字用来改变镜像设备的设置。对于多个实例,#是每个实例的不同数值,必须反复通过可用的显示器设备确定这个数值。 要连接镜像设备和全局桌面: 加入REG_DWORD注册表条目Attach.ToDesktop到驱动程序的服务主键。 设置键的条目为1。 要禁止镜像驱动程序,设置键的条目为0。 像前面所提及的,驱动程序在设备层上面驻留的绘图层安装和操作。因为镜像驱动程序的坐标空间是桌面坐标空间,它能跨越多个设备。如果镜像驱动程序要镜像主显示器,显示坐标要符合主显示器的桌面坐标。 镜像驱动程序安装之后,它被交叉驱动程序的显示区域的所有绘图操作调用。在多监视器系统中,如果镜像驱动程序仅覆盖主显示器设备,这不包括所有的操作。 推荐使用用户模式服务维护镜像驱动程序的设置。这个应用能确保在启动时驱动程序正确装入,并且通过WM_DISPLAYCHANGE消息得到显示器改变的通知,它能响应相应的改变。 GDI调用镜像驱动程序进行任何交叉驱动程序的边界矩形的二维DDI操作。注意,如果表面是设备格式位图,即如果SURFOBJ具有STYPE_DECBITMAP的iType,GDI不执行矩形边界检查。 通常,镜像驱动程序必须不使用全局变量实现。对一特定的显示器驱动程序所有的状态必须在PDEV中。GDI为每个通过视频微端口驱动程序创建的硬件设备的扩展调用DrvEnablePDEV。 镜像驱动程序不支持DirectDraw。 在DEVINFO结构的flGraphicsCaps成员中,镜像驱动程序必须设置GCAPS_LAYERED标志为TRUE。 通过实现DrvRealizeBrush,镜像驱动程序可选择支持画刷实现。 GDI允许同一个驱动程序运行在单监视器系统或多监视器系统中。在多监视器系统中的驱动程序只需要知道它在全局桌面中的位置。当Win32 ChangeDisplaySettings调用发生,如用户通过控制面板的Display applet动态改变监视器在桌面上的位置,GDI给驱动程序提供这个位置。当这样的改变发生,GDI相应地更新DEVMODEW结构的dmPosition成员。通过实现DrvNotify,驱动程序能接收到这个改变的通知。更多的信息参见镜像驱动程序安装。 注意 镜像驱动程序不要求进行像素精度的绘制,在客户端这样精度的绘制可能很困难。例如,对于适配器/监视器接收镜像图像,不要求使用和适配器/监视器被镜像的相同的精度绘制网格交叉量化(GIQ)直线和多边形填充。 2.17.1 镜像驱动程序INF文件 可使用Mirror.inf例子镜像驱动程序INF文件作为模板,构造镜像驱动程序INF文件。 注意 不能使用与Windows 2000 DDK一起发布的geninf.exe工具创建镜像驱动程序INF文件。 更多的信息,参见第四部分,第9章,安装机器启动时请求的设备,即插即用、电源管理和Setup设计指导,以及Windows 2000驱动程序开发参考卷1的第三部分,第1章,INF文件段和指令。 2.17.2 镜像驱动程序安装 系统初始化镜像驱动程序来响应对Win32 ChangeDisplaySettings或ChangeDisplaySettingsEx的调用。需要实现一个用户模式的服务进行上面两个调用中的一个来实现镜像驱动程序并维护其设置。这个应用用于: 确保镜像驱动程序在启动时正确装载。应用要指定CDS_UPDATEREGISTRY标志保存对注册表的设置,这样在以后的启动中,驱动程序可以使用下面将描述的同一个DEVMODEW信息自动地启动。 通过WM_DISPLAYCHANGE消息得到显示器改变的通知,响应桌面相应的改变。 例子Mirror.exe,可以从Video\displays\mirror\app目录的源代码文件中生成,实现了一个操作的子集,由用户模式的服务提供装入一个镜像驱动程序。 在镜像驱动程序安装之前,用户模式的应用将填充DEVMODEW结构指定下列的显示器属性: 位置(dmPosition) 大小(dmPelsWidth和dmPelsHeight) 镜像显示器的格式(dmBitsPerPel) 通过为这些结构中每个被改变的成员包含一个标志,用户模式应用也必须设置适当的dmFields。镜像显示器的位置坐标必须用桌面坐标指定;它们可以跨越多个设备。对直接镜像主显示器,镜像驱动程序要指定其位置和主显示器的桌面坐标相一致。 在DEVMODEW结构成员被设置以后,在对Win32 ChangeDisplaySettingsEx函数调用中传递这个结构可以改变镜像显示器的设置。 镜像驱动程序安装之后,GDI调用它进行交叉驱动程序的显示区域的所有绘图操作。在多监视器系统中,如果镜像驱动程序仅覆盖主显示器设备,GDI可能不发送所有的绘图操作到镜像驱动程序。 关于ChangeDisplaySettings和ChangeDisplaySettingsEx函数以及显示器改变桌面通知的更多的信息,参见平台SDK文档。 2.18 Newdisp:动态重新装载显示器驱动程序 除了提供调试器外,Windows 2000提供下列工具测试和调试显示器驱动程序: newdisp,无须重启系统就可重新装载驱动程序。细节参见NewDisp:动态重新装载显示器驱动程序。 控制面板中Display applet的滑动控制,动态地改变驱动程序中GDI调用的加速器。细节参见允许的驱动程序加速器的动态改变。 2.18.1显示器驱动程序测试工具 Windows 2000 DDK提供了工具允许无须重启就重新动态装载显示器驱动程序。这个工具,称作newdisp.exe,在开发中更新显示器驱动程序代码时,减少必须重启的次数,加速了显示器驱动程序的测试。 要运行newdisp.exe: 关闭所有的Direct3D?和OpenGL应用。 把更新过的显示器驱动程序的DLL拷贝到\system32目录。 运行newdisp(无须任何参数)。 newdisp的每次调用都重新装载显示器驱动程序。假定在调用时没有驱动程序引用存在,newdisp通过以下操作完成动态重新装载: 用640(480(16色调用ChangeDisplaySettings,这将引起系统装入并运行16色VGA显示器驱动程序DLL,同时,旧的显示器驱动程序DLL从存储器中卸载。 立即执行另一个ChangeDisplaySettings回调到原始模式,这引起新的显示器驱动程序DLL从\system32目录装入,而卸载16色VGA显示器驱动程序DLL。 如果驱动程序有活动的Direct3D、WNDOBJ或DRIVEROBJ对象,就有对驱动程序实例的引用存在。当newdisp运行时对驱动程序实例的引用存在,旧的显示器驱动程序DLL就不会被卸载,并且,相应地,新的显示器驱动程序DLL也不能装入。 newdisp依赖已加入到Windiws 2000的动态驱动程序装入函数重新装入驱动程序而无须重启;因此,它不能在Windiws NT? 4.0或更早的系统中运行。如果VGA驱动程序不能装入到图形设备,或者本地显示器驱动程序支持的640(480(16色模式代替VGA驱动程序处理的模式,它也不能工作。 注意newdisp不立即引起视频微端口驱动程序的重新装入。如果微端口驱动程序改变,系统必须重新启动来安装和测试它。 2.18.2允许的驱动器加速的动态改变 通过使用包括在控制面板里的显示小应用程序里的游标,驱动器的加速级能够被改变成用户接口。取决于这个游标的设置值,GDI允许下面的驱动器加速级: 值 描述 允许所有的显示驱动器加速。 DrvSetPointerShape和DrvCreateDeviceBitmap是不可用的。 除过1,不允许更多复杂的显示驱动器加速,包括DrvStretchBlt. DrvFillPath. DrvGradientFill. DrvLineTo. DrvAlphaBlend.和DrvTransparentBlt. 除过2,不允许所有的DirectDraw和Direct3D加速。 除过3,除过固体颜色填充、DrvCopyBits. DrvTextOut和DrvStrokePath. DrvEscape是不能够外,不允许几乎所有的显示驱动器加速。 不允许硬件加速,仅仅在从一个系统内存表面到屏幕做位块传输时调用驱动器。 一个显示驱动器能够通过下面决定当前的加速级: 通过执行DrvNotify从GDI接收加速级改变的通知 调用EngQueryDeviceAttribute来查询当前的加速级 第3章DirectDraw DDI 微软的DirectDraw DDI是一个图形接口,该接口允许生产厂商为DirectDraw提供硬件加速。它可能是一个显示驱动程序的集成部分或者一个独立的动态链接库(DLL),此动态链接库通过被驱动程序开发者定义的一个私有接口与显示驱动程序通信。在微软的Windows2000上,一个驱动程序必须是一个32位的实现。 本章描述了DirectDraw体系结构和接口,并为DirectDraw驱动程序编写者提供了实现方针,这些方针特别针对Windows NT和Windows2000。然而,在可能的地方,对Windows95和Windows98实现的差异被指出。读者应该熟悉DirectDraw API并对Windows2000显示驱动程序模型有一个牢固的掌握。为了获取有关DirectDraw的更多信息,请参看SDK平台。 3.1 DirectDraw头文件、例子代码和参考 Windows2000 DirectDraw驱动程序编写者使用下面的头文件: Ddrawint.h,它包括DirectDraw驱动程序的基本类型、常量和结构 Ddraw.h,它包括被应用程序和驱动程序使用的基本类型、常量和结构 Dvp.h,当驱动程序支持DirectDraw视频端口扩展(VPE)时其被使用 Dxmini.h,当驱动程序包含为内核模式视频传输的微端口支持时其被使用 Ddkmapi.h,当驱动程序需要为DxApi功能获取内核模式DirectDraw支持时其被使用 Dmemmgr.h,当驱动程序需要执行它自己的内存管理而不依靠DirectDraw运行时时其被使用 Ddkernel.h,当驱动程序包含内核模式支持时其被使用 Dx95type.h,它允许你容易地将现存的Windows95和Windows98驱动程序移植到Windows2000。这个头文件映射两个平台上不同的名字。 除过ddraw.h外,所有的头文件与Windows2000 DDK一起发售,头文件ddraw.h与SDK平台一起发售。 Windows2000 DDK也在s3virge视频显示目录里为DirectDraw驱动程序提供例子代码。 DirectDraw驱动程序函数、回调和结构的参考能够在DirectDraw驱动程序函数和DirectDraw驱动程序结构里找到。 DirectDraw接口的SDK相关的方面包含在SDK平台里。 你可以通过e-mail发送问题和评论至directx@microsoft.com。 3.2 DirectDraw体系结构总览 DirectDraw包括下面的组件: 用户模式DirectDraw(ddraw.h),一个系统提供的被DirectDraw应用程序装载和调用的DLL。这个组件提供硬件仿真,管理各种DirectDraw对象,并提供显示内存和显示硬件管理服务。 内核模式DirectDraw,一个win32k.sys的集成,它是由系统提供的被一个内核模式显示驱动程序装载的图形引擎。DirectDraw的这个组件为驱动程序执行参数的确认,这样使得实现更多的健壮的驱动程序变得更为容易。自从显示驱动程序成为Windows2000操作系统的可信组件以后,这是一个关键的设计目标。内核模式DirectDraw也处理图形设备接口(GDI)和所有的隔行扫描处理状态的同步问题。 显示驱动程序的DirectDraw部分,与其余的显示驱动程序一道,它由图形硬件生产厂商来实现。这个组件在本文档里被称为DirectDraw驱动程序,显示驱动程序的其他部分处理GDI和其他的非DirectDraw相关的调用。 本文档一般称系统提供的这两个组件为DirectDraw。 图3.1给出了DirectDraw驱动程序体系结构的一个模块图表。 如图3.1所示的,一个应用程序通过GDI(用户和内核模式部分)和显示驱动程序访问显卡。显示驱动程序总是支持GDI调用和通常的DirectDraw和Direct3D调用。当设备独立的GDI位图(DIB)引擎部分不被显示驱动程序支持的时候,它从功能上仿真。 当DirectDraw被调用时,它通过DirectDraw驱动程序直接访问图形卡。不象GDI,DirectDraw被通知一个DirectDraw可能的驱动程序的能力,DirectDraw为被支持的硬件功能调用DirectDraw驱动程序或为那些必须在软件里仿真的功能调用硬件仿真层(HEL)。不是如GDI那样为它不支持的请求调用驱动程序,DirectDraw为硬件支持的请求调用驱动程序以及为非硬件支持的请求调用DirectDraw HEL。在另一方面,GDI调用被送到驱动程序,如果调用不被支持的话,那么驱动程序必须回调到DIB引擎里。 注意:如果DirectDraw驱动程序使一个操作失败,DirectDraw不把操作传递给DirectDraw HEL;相反,它将DirectDraw驱动程序的错误代码传回给应用程序。 在初始化的时间和模式改变过程当中,显示驱动程序返回能力(caps)位给DirectDraw。这能够使DirectDraw获取关于可用的驱动程序函数、它们的地址和显卡及驱动程序(如延伸、透明的blit、显示字距和另外的高级特性)的能力的信息。一旦DirectDraw有这些信息,它能够使用DirectDraw驱动程序来直接地访问显卡,而不必进行GDI调用或使用显示驱动程序的特定的GDI部分。 3.2.1 关于DirectDraw DirectDraw是DirectX的显示组件,该DirectX允许硬件设计者直接操纵显示内存、硬件位块转移、硬件重叠和交换表面。DirectDraw为游戏和Windows子系统软件提供一个设备独立的方法,例如3-D图形包和数字视频编码译码器,来获取特定显示设备的特性。 DirectDraw在一个直接的32位路径里提供设备独立的存取给特定设备显示功能。它在一个直接访问显示卡的驱动程序里存取其重要的功能,而不用Windows GDI或DIB引擎的介入。 通过利用这个直接路径,游戏和另外的显示加强应用程序运行更快且避免撕裂,一个撕裂是由一幅被同时显示和写入的图象引起的屏闪烁,直接存取允许游戏性能只被显卡性能限定。DirectDraw也使用页交换来提供平滑的动画。 快速运动和许多游戏的不断变化屏幕和多媒体应用程序给显示处理施加了重荷并有加剧撕裂的趋势。尽管GDI在画电子数据表和图形、着色TrueType字体等等非常快,这并不意味着它是一个实时图形API。DirectDraw通过在一个32位的驱动程序里处理设备独立的硬件加速器功能增强了GDI。 3.2.2 DirectDraw驱动程序 DirectDraw通过DirectDraw驱动程序,一个通常由显示硬件制造商提供的特定设备接口提供设备独立性。DirectDraw暴露方法给应用程序并使用显示驱动程序的DirectDraw部分直接与硬件工作,应用程序从来没有直接调用显示驱动程序。 在Windows2000上,DirectDraw驱动程序总是32位代码的实现,DirectDraw驱动程序能够是显示驱动程序或者一个独立的DLL部分,该DLL通过被驱动程序编写者定义的一个私有的接口与显示驱动程序通信。在Windows2000上,DirectDraw驱动程序被假定为显示驱动程序的一个部分。 注意:(对Windows95/Windows98而言)当DirectDraw驱动程序单独的DLL,它被ddhelp.exe装载,这发生在DirectDraw被调用时。 DirectDraw仍旧为BIOS调用(其一定是16位的)和模式变化(其不是时间关键的)使用Windows95/Windows98显示驱动程序的非DirectDraw特定的部分。DirectDraw在模式变化过程来更新它自己并为GDI不支持的模式提供支持,例如,GDI在低于640X480的模式下不能够很好的操作。 DirectDraw驱动程序仅仅包括设备独立的代码并没有执行仿真。如果一个功能没有被硬件执行,DirectDraw驱动程序不会将它作为一个硬件能力报告。同样,由于DirectDraw运行时在驱动程序被调用之前验证参数的有效性,DirectDraw驱动程序不验证参数的有效性。 3.2.2.1 DirectDraw驱动程序的第一步 开始实现DirectDraw功能的一个好的方法是修改一个存在的驱动程序,如果没有可用驱动程序,以DDK的DirectDraw部分里的例子代码为开始,并且使驱动程序初始化、锁定和交换工作,这将使驱动程序尽可能快的运行。从这个基本功能,你能够添加更强有力的功能,这些功能将继续改进显示性能。 DirectX软件开发包(SDK),这在SDK平台上能够找到,包括有用的help和readme文件、例子代码和工具如DirectDraw测试,它允许你在开发过程当中监视和测试驱动程序能力。一个与内核调试器连接的远程终端和调试printf状态是验证驱动程序执行的每个动作的不可或缺的工具。 微软已经尽力使开发一个DirectDraw驱动的驱动程序尽可能的容易,然而,为了使终端用户的一切变得简单,复杂被隐藏在防护罩下面。由于预期即将出现的所有无数的硬件变革和竞争标准是不可能的,一定量的复杂程度留给了驱动程序编写者,他们必须改变技术以适应特定的硬件。 DirectDraw需要的最小的驱动程序功能是锁定、打开锁定和交换一个表面。假定硬件支持相关的操作,驱动程序支持也应该增加为blits(包括透明的blits,它对加速游戏的速度是重要的)、延伸和重叠(它对视频回放是关键的)。 3.2.3 硬件仿真层次 DirectDraw驱动程序并不仿真,然而,任何没有被驱动程序处理的DirectDraw功能由DirectDraw HEL处理,HEL(被微软作为DirectDraw的一个部分写入)在软件里仿真驱动程序,并且做DirectDraw需要但DirectDraw驱动程序不能处理的任何事情。这个仿真在用户模式里被完成。 例如,如果DirectDraw驱动程序有为blits设置但不是为透明blits设置的能力位(caps),DirectDraw驱动程序为blits所调用且HEL为透明blits所调用。在透明的blit状况下,HEL被传递一个显示内存指针,并且从后备的表面与颜色键比较每个字节。如果字节不匹配颜色键,HEL使用CPU拷贝它到目标表面。仿真发生在为别的不受支持的操作或者显示卡内存不足。 注意到DirectDraw没有传递失败的DirectDraw驱动程序操作给HEL是重要的,相反,它返回DirectDraw驱动程序的错误代码给应用程序。DirectDraw决定哪一个组件将调用取决于能力位。如果HEL或DirectDraw驱动程序使一个特别的操作失败,一个错误代码被返回到应用程序。 注意:(对Windows95/Windows98而言)在一个小数目的低端卡上,该卡没有DirectDraw-capable 驱动程序,DirectDraw能够使用通用的帧缓冲器(Framebuf)显示驱动程序存取卡,这个驱动程序仅仅可以锁定、打开锁定和交换。如果没有DirectDraw驱动程序且Framebuf没有在卡上工作,DirectDraw为所有的事情使用HEL,这仍快于运行所有的通过GDI和DIB引擎的操作。HEL在驱动程序里也能够使用显示控制接口(DCI)支持在缺少DirectDraw支持的情况下来存取主要的表面。 3.3 DirectDraw驱动程序基础 这部分包括关于为DirectDraw显示驱动程序设计的一般信息,按以下几个类别组织: 3.3.1 DirectDraw表面 3.3.2 显示内存 3.3.3 内存配置例子 3.3.4 交换 3.3.5 使用图阵 3.3.6 假定状态信息的驱动程序 3.3.7 打开显卡并在Windows95/98上改变模式 3.3.1 DirectDraw表面 DirectDraw表面是DirectX图形里的基本图象单元,它或者是特定的宽度、高度和象素格式的长方形集合,或者是一个包含微软的Direct3D的命令或至高点的缓冲器。表面有相关于它们的位来表示它们的行为和用法,这些位叫作表面能力位(surface capability bits)(或者简称caps bits),表面能力位表示打算的用法如为着色(DDSCAPS_TEXTURE caps bit)保留纹理象素,为3D着色(DDSCAPS-3DDEVICE)而成为目标和许多其他的方面。为获得表面能力位的更多信息,参考DDSCAPS下的部分内容。 主表面是当前正被显卡扫描输出到监视器的表面,更多有关主表面的陈述将贯穿此文档。 3.3.1.1复杂的表面和附件 表面也能够是复杂的,这意味着它们是相关联的表面的一个更大集合的一部分。复杂表面的例子包括前缓冲和相联系的后缓冲、一个mipmap的不同级别和一个立方图的不同面。 DirectDraw运行时使用一个被称为表面附件(surface attachment)概念来管理不同的简单表面连接成的复杂表面。当应用程序调用IdirectDraw::CreateSurface来构造一个交换链(可能具有Z-buffer的前缓冲和后缓冲)时,表面能够被隐式附着;或者当应用程序通过调用IdirectDrawSurface::AddAttachedSurface用一个着色目标关联一个Z-buffer时, 它们能够被显式附着。 3.3.1.2创建和注销DirectDraw表面 DirectDraw表面在一个四状态的过程里被创建: DdCanCreateSurface。运行时调用驱动程序的DdCanCreateSurface来看是否驱动程序将允许这种类型、大小和格式的表面创建,驱动程序能够返回一个将被传送到应用程序的失败代码。 DdCreateSurface。驱动程序创建正被讨论的表面,为表面的内容潜在地分配内存。复杂的表面用对DdCreateSurface的调用能够被立即创建。这样驱动程序也许被要求在一次调用创建许多表面。 Memory Allocation。DirectDraw运行时为没被分配的任何表面分配内存来响应DdCreateSurface调用。这个过程在下面的部分里包含了更多的细节。 D3dCreateSurfaceEx。这个调用对DirectX7.0 DDI是新的,这个功能用正被讨论的表面联系一个句柄来为以后在Direct3D D3dDrawPrimitives2符号流里所使用。驱动程序也创建在这时被DirectDraw维护的自己的表面结构拷贝。参见Direct3D DDK文档获得有关D3dCreateSurfaceEx的更多信息。 表面被对驱动程序的DdDestroySurface入口点的单个调用所注销。 只要表面创建时的模式保持,表面就会保持。就驱动程序而言,当有一个模式改变,所有驱动程序控制下的表面就被注销。另外的事件也能够引起所有的表面在这种方法下被注销。决定引起一个DdDestroySurface调用的原因对驱动程序而言是不必要的。 3.3.1.3丢失表面和原子恢复 运行时表面对象生命周期比驱动程序的表面对象的生命周期长。在极少的状况下,大部分显著的模式发生变化,表面丢失。这意味着当DdDestroySurface被调用时驱动程序的表面对象被注销了,但运行时表面对象仅仅处于未决的状态,称作“丢失”。稍后,运行时对象能够被恢复,它对应于驱动程序级上的一个DdCreateSurface调用。 正常地,驱动程序不知道这个中间的丢失状态,然而,在某些状况下,对这个过程的理解将有助于驱动程序编写者。 在释放DirectX7.0 DDI之前,表面总是一次一个的被恢复,即使如果它们被最初在一组里作为一个复杂创建的部分而创建。至于DirectX7.0 DDI,驱动程序编写者能够在一个原子调用里选择处理复杂的表面恢复。在表面恢复时期,DirectDraw运行时检查驱动程序的D3dCreateSurfaceEx入口点,如果这个入口点被定义,DirectDraw运行时在一个对DdCreateSurface的调用里恢复所有的复杂表面。驱动程序将最有可能不在原始创建和由恢复表面所产生的创建之间进行区别。 3.3.1.4丢失驱动程序管理的纹理 由于驱动程序管理的表面也消耗视频内存,这些内存资源是稀有的资源,它们也需要能力被放置在未决的状态(丢失状态)。由于驱动程序管理驱动程序管理的表面的视频内存的分配,当这样的表面需要丢失时,一个方法是必要的来通知驱动程序,这个通知已经通过一个扩展的DdDestroySurface调用被添加到DirectX7.0 DDI。 当一个驱动程序管理的表面(用DDSCAPS2_TEXUREMANAGE标志标志)丢失时,驱动程序在一个用DDRAWISURF_INVALID指定的表面结构的dwFlags成员里接收到一个特别的DdDestroySurface调用。驱动程序应该释放相关于管理的表面的视频内存但不应该释放其余的私有的表面信息,包括后备表面拷贝,由于它们并没有真正从驱动程序的观点丢失,将没有新的DdCreateSurface调用来恢复丢失的驱动程序管理表面。对大多数部分,这个特殊的DdDestroySurface调用被特殊地使用来通知驱动程序它将收回它的视频内存拷贝。 3.3.2 显示内存 使用可与前台的一个缓冲器交换的后台的一个缓冲器是利用DirectDraw的最好的办法,页面交换对平滑、游戏里的自由撕裂动画和视频回放是重要的。总的来说,给DirectDraw分配尽可能多的显示内存增强了显示性能并允许游戏和其他的DirectDraw应用程序运行更快且图象质量更好。 注意:(对Windows95/Windows98而言)当DirectDraw被初始化时,显示驱动程序的非DirectDraw特定的部分必须被通知哪一个内存正被DirectDraw所使用,这样,当DirectDraw被使用时,它能够避免写到那个内存上。这对DirectDraw的兼容性是必须的,并且在显示驱动程序的非DirectDraw特定的部分里完成。 当为DirectDraw的使用分配内存时,虚拟的显示驱动程序(VDD)内存必须被保存以交付给MS-DOS。对Windows画笔、字体、刷子等等的高速缓存也必须被保存。通过检查显示驱动程序代码的非DirectDraw特定部分来看哪处的VDD内存被分配,你可以决定什么内存可以使用。最好给Windows95/Windows98一点额外的内存,比方说32KB,确信它被分配到一个4字节的边界上,那个结果的地址被指向延伸区域的显示指针所使用(例子代码里的VIDEOMEMORY结构的fpStart成员)。 通常地,显卡将行距设置成显示的宽度以便没有内存在前缓冲的右边(概念上)浪费,这在概念底部留出一个延伸区以为其他的表面所使用。在这种状况下,由于一个指针能够引用可能存取的显示内存的整个区域,所以内存存取是直接的。 如果行距大于主表面的宽度,前缓冲之概念的右端内存被浪费掉了。它不得不作为一个单个的矩形堆被收回,而不管是否卡上的内存是线性的或矩形的。(甚至如果内存是线性的,一些显示驱动程序固定行距来加速位块传输。不是重写驱动程序,内存能够被简单的作为一个单个的堆而收回。) 3.3.2.1内存堆分配 为分配一个表面,DirectDraw彻底扫描显示内存堆以便它们能够被驱动程序指定。堆在一个VIDEOMEMORY结构里的数组被指定,DirectDraw 以数组里的VIDEOMEMORY结构的次序访问堆。VIDEOMEMORY结构设置堆特定的度量,如启动和终止内存地址和标志,这些内存地址和标志描述了堆存取和什么类型的用法为放置在这个堆上的表面所限制。DirectDraw通过子分配和重新分配内存管理堆,即是说,在每个堆的权限下通过创建和注销表面。物理的限制决定怎样设置这些属性。 当企图分配内存来响应一个面创建或恢复时,DirectDraw的堆管理器通过VIDEOMEMORY结构做两个传送。VIDEOMEMORY结构的ddsCaps成员通知DirectDraw在第一个传递上堆里的什么内存不能被使用,例如,如果堆对一个后台的缓冲器是足够大的,你能通过设置DDSCAPS_OFFSCREENPLAIN标志从第一个传递上排除子画面被分配。按这样的方法,当后台的缓冲器为页面交换保留时,另外的堆将用脱屏的普通表面填充。 你能够设置VIDEOMEMORY结构ddsCapsAlt成员来允许子画面在第二次传递上,所谈及的堆可以允许子画面,但是如果当子画面不能够在任何另外的堆里创建时。不要在ddsCapsAlt里指定DDSCAPS_OFFSCREENPLAIN标志,这允许堆为它们的最高和最好利用而被优先地使用,没有取消可选择的使用。 显存堆能够是线性或者矩形,这取决于位块转移或者一个存在的显示驱动程序的需求。VIDEOMEMORY结构的dwFlags成员被使用来指定内存分配类型。线性堆描述内存的区域,在这个区域里每个表面的行距可以是不同的;矩形的堆描述内存的区域,这里每个表面的行距是固定的。如果必要,这些堆可以在相同的显卡内混合和匹配。(看内存配置例子获得更多信息。) 象素深度变化取决显示模式。行距(有时被称为跨距或位移)是被添加的字节的数目,该字节从显存的一个列到其下面行上同样的列而得到(如图3.2所示),用8位象素,这里每个象素有一个字节跨越整个水平内存长度。 图3.2 行距 如图3.2所示,行距是用字节表示的显存的整个水平延伸。在线性内存里,它与前缓冲的宽度、系数分配限制是相同的。在矩形内存里,它是前面缓冲器的宽度加上概念上相对于它右边的剩余内存。在上面的任何一种状况下,行距是被添加的字节的数目,它被添加到一个扫描行的开始地址来到达它下面的下一个扫描行的起始地址。 3.3.3 内存配置例子 下面的部分包括内存配置的三种类型:线性、矩形和混合显存分配,每一个包括例子代码,你可以修改这些例子代码来适合你的卡的物理特性并添加到HAL来分配显存堆。 3.3.3.1线性内存分配 显存被认为是线性的,不论何时行距能够被改变来匹配表面宽度、系数分配限制(如图3.3所示)。例如,如果位块转移器仅能够找到8位的跨距以及一个31象素子画面被使用,显示的每一行需要被调节1以在一个8位的边界上分配下面的行。 图3.3 线性内存堆分配 单字分配限制能够应用于内存配置三种类型的任何一种,由于行被顺序地存储,线性内存比矩形内存普遍而更有效地使用于应用程序。任何位置能够通过沿着这条线性范围向前和向后移动而被容易地存取。 行距被象素深度、表面宽度和单字分配限制决定,如果显示是640 的8位象素隔行扫描,行距是640。如果象素是16位且显示屏是640X800,行距是1280。同样的,640宽度,32位象素屏在线性显存里有2560的行距。 图3.3给出了一个具有一个主表面和一个延伸的线性显存堆分配的一个例子。指向主表面的开始部分的指针是fpPrimary,主表面的的尺寸和多种高速缓存被添加到这里来给指针延伸区的的开始部分的指针,此开始部分被VIDEOMEMORY结构的FPstart成员所表示。被VIDEOMEMORY结构的fpEND成员所表示的终端点用通过加上存留内存的大小再减去1来计算。 VIDEOMEMORY结构保留管理显存堆的信息,这个例子在VIDEOMEMORY结构的数组里仅有一个元素,因为只有一个堆。VIDMEM_ISLINEAR是在VIDEOMEMORY结构里的dwFlags成员中的一个标志,它指示这是线性内存。 下面的伪代码给出了一个VIDEOMEMORY结构怎样为线性内存而被设置: /* * video memory pool usage */ static VIDEOMEMORY vidMem [ ]= { { VIDMEM_ISLINER, 0X00000000, 0X00000000,{0},{0}}, }; 下面的伪代码给出了线性内存堆怎样被设置: /* * video memory pool information */ /* Caculate the number of video memory heaps */ ddHALInfo.vmiData.dwNumHeaps = sizeof(vidMem) / sizeof(vidMem[0]) ; /* Set up pointer to first avaliable video memory after primary surface */ ddHALInfo.vmiData.pvmList = vidMem ; /* * remender of screen memory */ VideoHeapBase = ddHALInfo.vmiData.fpPrimary + dwPrimarySurfaceSize + dwCacheSize ; VideoHeadEnd = VideoHeapBase + dwDD0ffScreenSize –1; VidMem[0].fpStart = VideoHeapBase; VidMem[0].fpEnd = VideoHeapEnd; 第一个可用的延伸区的开始通过加上GDI主表面的开始和主表面的大小以及Windows刷子、画笔和VDD高速缓存而计算,结果被用来在VIDEOMEMORY结构的第一个元素里设置起始点为延伸区的开始。 通过从延伸区的开始加上延伸区的尺寸再减去一就可以找到延伸区的末尾。结果被使用来设置在VIDEOMEMORY结构的第一个(仅仅在这种状况下)元素的终端点为延伸区的终端。如果多于一个堆,终端点被设置成这个堆的终端且下一个堆从这一个离开的地方开始。 3.3.3.2矩形内存分配 不论何时在一个给定的堆里的所有表面的行距被固定为一个特定的尺寸时,显存被认为是矩形的。 用矩形显存,分布是两维的,且有限定的宽度和高度,这个宽度并非一直与屏的宽度相同。由于显存必须考虑不同的显示分辨率并设计考虑,实际的水平宽度比当前正在显示器上显示的内容应该跨越一个更大的区域。 例如,即使一个屏幕应该被显示640的象素跨越,如果矩形显存是1280字节的跨越,行距是1280(而非640),行距跨越一个具有16位象素内存的1280象素水平延伸的行距是2560。(记住,行距基于将添加的字节的数目,该数目从一个扫描行的开始到下一个而得到。)为32位象素的行距是四倍于8位象素的行距,这样如果显示是1280的32位象素跨越,行距是5120。 矩形内存的主要的问题是它很少模仿辅助的数据大小,而应用程序想在其中存储数据。这样,它可能浪费显存,因此你应该在尽可能的避免它。DirectDraw允许这个空间基于先来先服务的原则而被存取。这些区域的使用有时被它们的笨拙的形状和尺寸所限制,经常地,矩形内存的残留的区域仅当空间可以存储小画面时才是有用的。 一个矩形堆能够与可用的内存的一个连续区域一样的大,但是由于它的尺寸以X、Y坐标来度量,它不能够是L形状的。如果矩形堆不足够高和足够宽来保留一个主平面,它不能够是一个后缓冲。如果主表面的行距不等于主表面的显示宽度,一个显示的概念右边内存的矩形块被残留下来了(堆1在图3.4里),这个块与行距减去显示的宽度一样。如果显示驱动程序假定一个固定的行距,右端的残留内存也能够出现在线性卡里。矩形的或线性的内存也可以在主表面下留存下来(但是不在这个例子中)。 图3.4 矩形内存分配 在图3.4里,矩形堆的起始点(被VIDEOMEMORY结构的FPstart成员表示)通过加上主表面的宽度和主表面的起始地址来计算。宽度和高度也被计算来给矩形堆维数,如果内存留存在Windows高速缓存下的话,一个堆也能够在此创建。 下面的伪代码给出了一个VIDEOMEMORY结构怎样为矩形内存而被设置: /* * video memory pool usage */ static VIDEOMEMORY vidMem [ ] = { { VIDMEM_ISRECTANGULAR, 0X00000000, 0X00000000,{0},{0}}, }; 矩形内存的代码和线性内存的代码仅有的区别是VIDMEM_ISRECTAGULAR标志,该标志表示这是一个矩形内存。 下面的伪代码给出了矩形内存堆怎样被设置: /* * video memory pool information */ /* Set up pointer to first avaliable video memory after primary surface */ ddHALInfo.vmiData.pvmList = vidMem ; /* this is set to zero because there may only be one heap depending on the pitch */ ddHALInfo.vmiData.dwNumHeaps = 0 ; /* * compute the Pitch here ... */ VidMem[0].fpStart = ddHALInfo.vmiData.fpPrimary + dwPrimarySurfaceWidth ; VidMem[0].dwWidth = dwPitch – dwPrimarySurfaceWidth ; VidMem[0].dwHeight = dwPrimarySurfaceHeight ; VidMem[0].ddsCaps.dwCaps = 0 ; // surface has no use restrictions 内存堆起始点被设置成主平面的起始地址加上主平面的宽度。宽度由行距减去主表面的宽度而决定,高度设置成主表面的高度。主表面能力被设置成零,表明没有强加的表面使用限定(这样,表面能够被子画面或者任何别的类型的表面使用)。 3.3.3.3混合内存分配 只要硬件允许,线性的和矩形的内存堆能够在任何方式下被混合和匹配。例如,如果一个前缓冲有一个固定的行距,DirectDraw-capable驱动程序能够分配一个矩形堆在它的右边,如图3.5所示,如果内存也被残留在主表面的下面,这个区域能够被做成一个可为后缓冲使用的线性堆(也就是说,如果它足够大)。 图3.5 混合内存分配 图3.5给出了在主表面(堆1)之下的内存的一个线性条和内存的一个矩形条,该矩形条被DirectDraw右面的主表面(堆2)收回。 下面的伪代码给出了一个VIDEOMEMORY结构怎样为线性和矩形内存的混合而被设置: /* * video memory pool usage */ static VIDEOMEMORY vidMem [ ] = { { VIDMEM_ISRECTANGULAR, 0X00000000, 0X00000000,{0},{0}}, { VIDMEM_ISLINER, 0X00000000, 0X00000000,{0},{0}}, }; 在这个例子中,显存的两个区域可以分配。主表面的右边区域必须是矩形,并且由VIDMEM_ISRECTANGULAR标志指示。主表面下的区域是线性的,并且由VIDMEM_ISLINEAR标志指示。 以下的伪代码显示了混合的矩形和线性内存堆如何被设置: /* * video memory pool information */ /* Set up pointer to first avaliable video memory after primary surface */ ddHALInfo.vmiData.pvmList = vidMem ; /* how many heaps are there */ ddHALInfo.vimData.dwNumHeaps = 2 ; /* The linear piece: */ /* * remender of screen memory */ VideoHeapBase = ddHALInfo.vmiData.fpPrimary + dwPrimarySurfaceSize + dwCacheSize ; VideoHeadEnd = VideoHeapBase + dwDDoffScreenSize –1; VidMem[0].fpStart = VideoHeapBase; VidMem[0].fpEnd = VideoHeapEnd; /* The rectangular piece */ /* Set up the pointer to the next avaliable video memory */ ddHALInfo.vmiData.pvmList = vidMem [1]; /* * compute the Pitch here ... */ VidMem[0].fpStart = ddHALInfo.vmiData.fpPrimary + dwPrimarySurfaceWidth ; VidMem[0].dwWidth = dwPitch – dwPrimarySurfaceWidth ; VidMem[0].dwHeight = dwPrimarySurfaceHeight ; VidMem[0].ddsCaps.dwCaps = 0 ; // surface has no use restrictions 一个线性内存堆通过在主表面之下确定延伸区的起始和末端点而设置,它被VIDEOMEMORY结构的Fpstart和fpEnd成员所表示(vidMem[0])。 矩形条通过使用主表面之下的延伸区的起始点而设置,它被矩形VIDEOMEMORY结构的Fpstart成员所表示(vidMem[1]);且这个区域的高度和宽度分别由dwWidth和dwHeight表示。行距(dwPitch成员)必须在矩形条被设置之前枚举,除过行距是取代第一个元素的VIDEOMEMORY结构的第二个元素,这与前面的矩形的例子是相同的。 每一个堆需要一个新的VIDEOMEMORY结构。 在一些情况下,交换寄存器仅能够处理256KB的边界。在这些实例里,一个小堆能够用尽高速缓存底端和后缓冲的起始之间的空间,且允许后缓冲开始于一个256KB的边界。本例没有给出,但是你能通过添加另外的元素到VIDEOMEMORY结构和设置起始点刚好超出高速缓存、末端点刚好在256KB边界之前而执行它。这样的一个堆应该用DDSCAPS_BACKBUFFER标志以便当堆管理器找寻一个后缓冲时它能够被跳过,这个后缓冲堆(排列的一个)也应该用DDSCAPS_OFFSCREENPLAIN标志从使用它来保留画面和纹理直到没有另外的内存在另外的堆里为脱屏平表面可用。 3.3.3.4在Windows95/98上寻址的分组转换内存 Windows2000驱动程序无须支持分组转换的内存,这个部分仅仅应用于Windows95/Windows98驱动程序。 如果线性的内存寻址被支持,就使用它。如果硬件或驱动程序仅能够使用分组转换,你必须使用虚拟的平面边框缓冲器VflatD设备。 VflatD为一个显示驱动程序帮助管理边框缓冲器,为了克服寻址多于显存的64 KB的问题,VflatD为使用分组转换技术的显卡虚拟缓冲器,它通过映射显存到内存地址的一个更小的块来完成此事。其后,无论何时一个缺页故障在绘图过程中发生,VflatD调用分组转换程序,指令它映射一个显示数据的块到物理的边框缓冲器。 如果分组转换被使用,DirectDraw从显示驱动程序得到这个信息并设置DDCAPS_BANKSWITCHED标志,这表明显存通过一个内存窗口存在于硬件,内存用VflatD被处理而出现平面。 用分组转换内存,仅仅内存的一个小窗口(通常64 KB)在任何给定的时间对应用程序是可见的,当应用程序到达窗口的末端时,一个缺页故障发生。VflatD捕捉缺页故障并移动内存指针,这样一个新的窗口对应用程序可见,如图3.6所示。 图3.6 分组转换内存 如图3.6所说明的,分组转换内存一次暴露一个可见内存64 KB的窗口,确信堆起始地址至少是双单字排列的,由于不这样做的话,将引起系统崩溃和执行变慢。如果一个分组转换出现在非双单字排列的内存的字节片内,VflatD就挂起了系统,这是由于被执行的处理器指令不能够打碎成更小的片的缘故。例如,指令可能在一个四字节的内存增长里被读。一次仅仅有内存的一页在窗口里,这样一个跨越一页边界的指令的四字节字节片将引起缺省的处理者没完没了的在两个内存窗口之间来回进行分组转换。也是这个道理,当数据被正常地排列时,标准拷贝操作达到三倍之快。 写排列好地数据意味着目标地址是四字节的排列(PTR==PTR & 0xFFFFFFFC)。 如下面的伪代码所示的,单字排列限制能够用一个简单的调整被处理: /* *required alignments of the scan lines for each kind of memory */ ddHALInfo.vimData.dwOffscreenAlign = AlignTbl[ wBpp >> 2 ]; ddHALInfo.vimData.dwOverlayAlign = AlignTbl[ wBpp >> 2 ]; ddHALInfo.vimData.dwTextureAlign = AlignTbl[ wBpp >> 2 ]; ddHALInfo.vimData.dwAlphaAlign = AlignTbl[ wBpp >> 2 ]; ddHALInfo.vimData.dwZBufferAlign = AlignTbl[ wBpp >> 2 ]; 注意:前述的例子仅仅给出了行距排列;它们没有控制表面内存的起始排列。 3.3.3.4.1使用VflatD VflatD是一个页转换的动态库,当一个分组边界被跨越时,该动态库改变内部的指针,除非在你的图形卡上的内存是分组转换的,VflatD是不必要的。当一个分组边界被跨越时,一个缺页故障触发了一个缺省处理者调用VflatD来做一个分组转换,这将暴露一个新的内存页。即使一个新的分组已经被转移,内存作为线性地址的空间出现在应用程序里。内存从一个被VflatD内存管理器追踪的新起始位置简单地继续着。正常情况下,这发生在后台,而没有错误。 为使用VflatD,驱动程序必须: 调用VflatD_Query,它核实VflatD的正确版本已被装载。 调用VflatD_Create_Virtual_Frame_Buffer,它注册驱动程序,返回一个选择器识别合法的边框缓冲器,并指定一个分组转换程序给VflatD。 调用VflatD_Begin_Linear_Access,它提供一个线性地址的边框缓冲器的存取,这个功能应该当用DDCreateDriverObject初始化DirectDraw时被使用。 调用VflatD_End_Linear_Access,当用HALDestroyDriver推出退出DirectDraw时使用这个功能。 分组转换程序必须使用32位的代码编写,且长度一定不能超过100个字节,当程序被调用时,EAX寄存器标识将要交换到物理帧中的新分组。除过EAX和EDX以外,程序必须保存所有的寄存器并且不返回值。(也就是说,EAX和EDX必须在出口和入口是相同的。) VflatD_Begin_Linear_Access启动了线性存取给边框缓冲器,边框缓冲器通过它的线性地址能够被存取。(就缺省而言,边框缓冲器必须用缺省的选择器被存取。)边框缓冲器留驻在线性存取模式里直到VflatD_End_Linear_Access被调用,该调用能够被嵌套。 如果成功的话,VflatD在EAX里返回存取记数并清除进位标志。存取记数是随每次调用VflatD_Begin_Linear_Access时增加的,而随每次调用VflatD_End_Linear_Access时是减小的。当存取记数为零时,线性存取模式被关闭。 为关于VflatD和VflatD功能的更多信息,看Windows95/Windows98 DDK的参考部分。 3.3.4 交换 主表面是正被读的内存区来绘制当前正被显示的屏,如果一个主表面有一个或更多附着的后备缓冲器,它是一个可交换的表面。 交换结构被用于DirectDraw里的页交换,就概念上的来说,它们能够被认为是由表面构成的链接的列表,前缓冲是“可见的”,后缓冲和所有附着的可交换的表面必须与前缓冲是同等的大小和象素深度。大多数现代的图形卡为高分辨率的模式里可交换的前面的和后缓冲有足够的内存。 所有的表面类型在DirectDraw里被交换;页交换是普遍的特殊情况。例如,交换没有限定在卡上的主表面,该卡支持重叠或者在3-D可能的显卡上有纹理内存,在这些状况下,重叠和纹理用相同的驱动程序入口点能够与主表面有相同的方法交换。 没有被用来交换的主表面可以有任何维数且可以存储不可交换的对象如图象数据,图象数据也能够被存在系统内存里,但是在此种状况下,由于硬件位块转移不能够正常的到达系统内存来转移图象数据,DirectDraw应该使用硬件仿真层(HEL)。一些卡允许硬件位块转移以便有系统内存的直接存取(DMA),这样DirectDraw为DMA执行一个检查。 图3.7说明了在两个可交换的表面之间的关系。 图3.7交换 如果一个前缓冲有一个或更多的后缓冲附着,如图3.7所示,它是一个可交换的表面。后缓冲和所有的附着的可交换的表面必须与前缓冲有同样的大小和象素深度,一个后缓冲表面使用交换变成了主表面。一个交换仅改变了一个指针以便它指向一个不同的可交换的表面,因此显示了新的表面,前缓冲(其不再是主表面)将变成可存取的且可以让新的数据被写到其上。 正确地被执行,交换解决了大多数屏闪烁问题,着色一个没被显示的表面的能力允许游戏和视频回放中平滑、无撕裂动画。 3.3.4.1撕裂 象在交换部分里讨论的一样,一个交换本质上改变了一个内存指针,以便它指向显存的一个新的区域。(参见Permedia2例子代码。)被转移走的表面必须在应用程序能够锁定、位块转移或改变内存之前完成显示,否则将导致一个撕裂(如图3.8所示)。如果在交换过程当中正被交换的表面正在进行数据写入,一个撕裂也许可能发生,撕裂是普遍的且可能在一张图象正被同时绘制和显示的任何时候发生,更快的帧速率没有解决这个问题。由于主表面、重叠和纹理是所有的DirectDraw表面,它们都能够阻止撕裂的以相同方法被交换。 图3.8撕裂 当一个页交换或位块转移发生在错误的时间时,一个撕裂发生。如果一个页交换发生在当监视器扫描行处于显示一个表面的中央时,如图3.8中的虚线指出的一样,一个撕裂发生了。撕裂能够通过仅在整个表面被显示之后定时交换而被避免(如图3.8中下面的例子)。当位块转移到一个正在被显示过程的表面时一个撕裂也能够发生。 3.3.4.2三倍的缓冲 增加能够拥有一个主表面的缓冲器的数量增强了显示性能,有至少三个可交换的表面是更好的。(一些游戏使用五个或更多。)当仅仅两个表面和一个页交换发生时,显示被延迟直到监视器的垂直回扫完成为止。延迟是必要的以确保后缓冲完成被显示之前没有被写入。用三倍的缓冲,第三个表面一直是可写的;由于它是一个后缓冲且可以立即绘制(如图3.9所示)。在一个没有使用子画面内存的游戏里,使用了三倍的缓冲的3-D着色比两倍缓冲快20-30%。 图3.9三倍的缓冲 除过现在三个缓冲器被使用外,图3.9中的交换结构与图3.8中的交换结构相同。由于一个缓冲器几乎一直是可写的(由于它没有包含在一个交换里),在允许后缓冲被重新写入之前,驱动程序不必等待显示扫描完成。 下面是在三倍缓冲的系统里的交换和位块转移一个简短说明,使用了图3.9中的标签。例子开始于正被显示的表面象素内存11,这是一个被前缓冲(用Windows2000 DDK提供的例子代码里的fpVidMem)所指向的主表面。在某些点,在象素内存22上位块转移到表面是期望的。由于表面的fpVidMem点开始于11(而非22)且交换状态为false(没有交换发生在被请求的表面),位块转移能够进行。驱动程序锁定表面,写入到其上,然后解锁,为了显示表面,一个交换必须发生。DirectDraw前缓冲对象现在能够改变fpVidMem(显示内存指针)来使表面在22主表面。由于没有交换待定,显示指针被交换(看图3.9的下半部分),且交换状态被设置为TRUE。前缓冲现在指向表面象素内存22,后缓冲指向表面象素内存33,且第三个缓冲器对象指向表面象素内存11(老的主表面)。不象两倍缓冲,此时DirectDraw可以自由的写入到后缓冲,换句话说,由于没有交换待定,DirectDraw能够写入到表面象素内存33。这个循环的交换过程继续为使用DirectDraw的应用程序无尽的提供平滑的动画、更快的播放和视频回放。 3.3.4.3定时一个交换 如果一个交换寄存器是可用的且知道扫描行,定时一个交换是容易的。简单地检查交换寄存器来看是否最后一个交换已经发生,然后在做交换之前确信扫描行不在垂直的空白区。然而,如果扫描行和交换寄存器不可用,你将必须使用下面的一个或更多算法。 如果硬件没有一个位来检查扫描行是否已经通过刷新循环,你应该使用多于一个方法来定时交换,可替换的方法基于消逝的时间。如果自上一次交换,有适当的时间绘图整个表面,交换是可行的。所需的时间(除非监视器更新速率已知)决定于驱动程序初始化时和通过查询直到显示处于垂直的同步,然后查询直到它不处于垂直的同步之模式改变上。这反复做20次,且线执行优先权设置为最大,结果被20所除来给出显示的持续时间,这是允许存取后台的缓冲器之前最大的时间等待。(如果你已经知道了显示的持续时间,你不需要枚举它。)在大多数状况下,一个更短的时期在做一个交换之前被需求,但是显示的持续时间被用为一个退却是由于它是完全可靠的。 如果仅仅扫描行的行数目可行,最快的方法是记录时间和扫描行,当扫描行少于它从前的数目时,它已经结束了一个更新的循环。例如,如果第一次扫描行在300行被查询,下一次它在50行被查询时,整个表面至少已经被显示过一次。然而,在所有的显卡上扫描行并非完全是可靠的,在一些状况下,通过给出两个不同的数字的一半,一个寄存器同时能够被读出和写入,在这些情况下,寄存器需要被读两次来核实它没有报告一个错误的数字。在一些状况下,当扫描行经过水平同步时,它可能是不准确的。当扫描行处于水平空白时,表明扫描行是否在当前正被显示的标志常常会返回一个为false的否定结果。 如果扫描行的行数目不可得到,你也许能够校验扫描行已经从显示经过垂直空白并返回显示。通过跟踪何时扫描行传递进和传递出显示,你能够确定何时一个整个的刷新循环已经被完成。检查是否扫描行在显示或在垂直空白里的能力应该在所有的显卡上可用,但是扫描行也许不是这样。(在任何状况下,显示的持续时间仍旧是退却。) 一定的调整也许对最后的闪烁位远离显示是需要的,如确信在地址被读之后没有计算发生(也就是说,由于地址在双单字边界上,向右转换两个距离来得到真正的地址,等等)。为避免撕裂,确信扫描行不在垂直的空白区且刚好在垂直的同步。在一个驱动程序状况下,这个问题每7分钟引起一个撕裂,所以要小心。你可以通过确信扫描行在写到交换寄存器之前处于显示状态从而容易地补救这些问题。 尽管显卡基于最初的IBM视频图形卡(VGA)规格,超级VGA有相当地变化。(看程序员的EGA、VGA和超级VGA卡指南,Richard F.Ferraro,Addison-Wesley,1990)一些卡提供非直接的方法决定是否扫描行处于垂直的空白区或者是否它甚至在显示当中。当竞争以满足高级显示能力增长的需求时这些不足将变得更为重要,同时,你必须创造性的补偿这些缺陷。 一旦可以交换,对欲显示的下一个表面的参考被装载到交换寄存器里。什么也不会发生直到扫描行得到过去的垂直的同步且放回到显示区为止,然后,交换寄存器被读一次。如果别的东西在那个时间之前被存储在交换寄存器里或者如果在它正被读的同时表面正被写入,将会发生一个撕裂。一旦寄存器被读,当表面正仍旧被绘图时,它不应该被锁定或者写入。 检查是否寄存器已经被读在大多数较新的显示卡里只是检查一个标志的事。在更旧的卡里,一个硬件中断常常为这个目的使用。当某个点跨越在交换寄存器的那个读时,任何钩住那个中断的函数都将被调用。问题是在一些较新的硬件里这个中断可能为另外的目的所使用。 3.3.4.4交换间隔 开始于DirectX6.0,DirectDraw允许一个应用程序决定何时一个交换命令被执行。这些取决于硬件能力,你能够将对这个新特性的支持添加到现存的驱动程序中。 下面的术语被用于描述相关于应用程序的Flip调用的定时: Posted 应用程序调用Flip和DirectDraw调用驱动程序的DdFlip入口点的时间。 Retired 硬件开始从新表面显示的时间段。 在DirectDraw的以前版本里,交换紧随当它们被登记时在或者接近垂直同步上一直是退役的。用DirectX6.0和后面的版本,应用程序能够指定交换立即是退役的---也就是说,正好当被登记的或者在它被登记之后的某套垂直同步的数目。DirectX6.0指定了两个新的能力位(在DDNTCORECAPS结构里的DDCAPS2_FLIPNOVSYNC和DDCAPS2_FLIPINTERVAL)和四个新的标志(在DD_FLIPDATA结构里的DDFLIP_NOVSYNC、DDFLIP_INTERVAL2、DDFLIP_INTERVAL3和DDFLIP_INTERVAL4)来使这些特征可能。 如果驱动程序设置DDCAPS2_FLIPNOVSYNC,它将在DD_FLIPDATA结构的成员里接收到DDFLIP_NOVSYNC标志,DDCAPS2_FLIPNOVSYNC标志表明只要交换一被登记,它应该是退役的。然而在这种状况下,你的硬件必须能够转换缓冲,在至少每个扫描行的基础上。如果直到下一个垂直的同步显示实际上没有退役交换,对驱动程序来说为DDCAPS2_FLIPNOVSYNC接受支持是不合法的,即使如果驱动程序立即返回。 在DDFLIP_INTERVAL2、DDFLIP_INTERVAL3和DDFLIP_INTERVAL4终端的数目表示了硬件在退役一个被登记的交换之前应该等待多少个垂直的同步,举例来说,DDFLIP_INTERVAL2意味着硬件应该记数两个垂直的同步,然后在或者接近第二个垂直的同步上退出交换。 如果你的驱动程序暴露DDCAPS2_FLIPINTERVAL,DirectDraw放置垂直同步的数目,通过垂直同步可延迟一个交换而进入DD_FLIPDATA结构的dwFlags成员的大多数重要的字节。由于DDFLIP_INTERVAL2、DDFLIP_INTERVAL3和DDFLIP_INTERVAL4标志被定义使这个为真,驱动程序不应该作为位标志对待这三个标志。同样,如果驱动暴露DDCAPS2_FLIPINTERVAL,DirectDraw确保dwFlags成员的大多数重要的字节被按照当DDFLIP_INTERVAL2、DDFLIP_INTERVAL3和DDFLIP_INTERVAL4标志没有被设置时而设置。在被登记的交换之后,自从一个交换调用的缺省行为将在或者接近第一个垂直的同步上退出交换时,DDFLIP_NOVSYNC引起一个将被放置在大多数重要的字节里零数字,大多数重要的字节的缺省值变为1。 自从DirectX1.0以来,当一个交换待定时(也就是说,当交换已经被登记但还没有退役时),驱动程序已经被要求返回DDERR_WASSTILLDRAWING,这个需求已经为交换间隔而被扩展了。既然当DDFLIP_NOVSYNC交换被登记时它们退役了,驱动程序将不会作为这样的一个交换(既然它将不会是待定的)的结果返回DDERR_WASSTILLDRAWING。相反,使用DDFLIP_INTERVAL2、DDFLIP_INTERVAL3和DDFLIP_INTERVAL4标志中的一个意味着驱动程序必须为一个长的时间段返回DDERR_WASSTILLDRAWING,这是由于在登记和退役的这段时间里一个交换被扩展了。 DirectDraw没有排除这些带有重叠表面的标志的使用,但是甚至如果驱动程序确实设置了DDCAPS2_FLIPINTERVAL或DDCAPS2_FLIPNOVSYNC能力位,驱动程序不需要考虑它们。如果驱动程序有这种能力的话,它们可能为重叠选择考虑这些标志,但是应用程序不可能利用这些特征。 注意:DDFLIP_INTERVAL2、DDFLIP_INTERVAL3和DDFLIP_INTERVAL4标志被用来利用硬件能力。驱动程序不应该通过在驱动程序里循环直到交换能够因请求而退役而尝试仿真这些标志。因为当调用一个驱动程序时,重要的操作系统互斥量是保留的,这样的一个实现可能严重地损害系统性能。 3.3.4.5重叠支持 DirectDraw也支持重叠,一个重叠表面是一个可被显示在主表面的顶端而没有改变其下表面的物理位的表面。用一个重叠,寄存器被设置以在一个包含重叠表面的主表面上定义一个矩形。数字到模拟的转换器(DAC)改变了矩形的位置。扫描行在主表面内存里读数据直到它到达为重叠留下的矩形为止。它从重叠表面读出直到在重叠里的那行被完成,然后,它继续最初的主表面图象,从主表面到重叠及其后面的这个转换发生在扫描行的每一次传递,并且继续直到重叠被完全地显示为止。 重叠表面能够有与主表面不同的象素深度,例如,当每个象素(bpp)8位时,主表面可能看起来很好,一个视频限幅可能需要16来可接受地显示,象素深度在主表面和重叠之间无缝地转换。为了获得有关具有DirectDraw的重叠,看DirectX的视频端口扩展。 重叠交换与主表面相同,DirectDraw表面对象交换指针,这样当扫描行到达包围重叠的矩形时,新的重叠表面被读出。在“定时交换”里描述的同样的交换算法以阻止撕裂。 3.3.4.6纹理支持 在3-D空间里交换一个纹理与其他任何表面是相同的,一个纹理恰好是一个平面的图象,该平面的图象有位设置来指定它能够被转移(纹理映射的)到一个3-D的表面上,一个纹理能够被映射到一个3-D的表面上且动作能够被通过页交换纹理光滑地着色。交换等待着色器来完成读出(象等待扫描行一样),如果交换驱动程序支持纹理,它必须能够恰当地记忆和处理它们。看第四章的Direct3D纹理管理获得纹理的更多细节。 3.3.5 使用图阵 如果一个位块转移发生在一个表面内,且资源和目标区交迭,必须决定适当的方向来避免在部分资源被拷贝之前而被重叠,这恰好能够用位于表面相对角的两个潜在的起始点来完成。所有的位块转移引擎的需求是每个图象的位置和维数。 你应该做每件可能的事情来加速实际的位块转移,例如,复制代码的各个部分来避免IF语句则可能使驱动程序运行更快。也许这个技术的最佳实现是将代码放置在一个宏代码里且在不同的位置使用代码而不是做函数调用,这是最优化速度的绝对合适的位置。参见在线DDK的DdBlit。 3.3.5.1透明的位块转移 在一个透明的位块转移里,一个颜色键正常地指定了将不被移去的颜色。资源颜色键与使用在动画里的蓝屏是相似的,颜色与每个象素做比较,如果它们匹配的话,象素不会被拷贝,如果它们不匹配的话,象素则被拷贝。DirectDraw也支持范围内的颜色键。 在一些情况下,一个透明的位块转移可能仅仅被硬件部分地支持,这可能仍旧比在软件里执行它要更快。在这些情况下你应该设置DDCAPS_COLORKEYHWASSIST标志。 一个部分地支持透明的位块转移的例子是一个要求位屏蔽而不是颜色键的显卡。在此情况下,不是与颜色键比较每个象素来决定是否拷贝它,而是一个单色屏蔽被构造。也就是说,所有的象素与颜色键比较且整个表面转换成一个位屏蔽(通常每个字节一位,取决于颜色深度)。当使用DirectDraw时,在表面未锁定时完成这个过程。 一旦alpha屏蔽被构造,它与源表面比较。没有设置在alpha屏蔽上的每件事物被拷贝到目标表面这实现了与源颜色键相同的作用,但是需要一个屏蔽被首先构造而不是在同时比较和拷贝。屏蔽必须被构造颜色键设置的任何次数,由于在那时一个颜色键重载可被指定,无论何时一个位块转移发生,它也必须被检查。当应用程序的功能被调用时,检查颜色键重载(传递给位块转移的唯一的颜色键)与设置在表面上的颜色键相同。如果它们是相同的,将不是一个真正的重载且屏蔽无须构造,如果它们是不同的,屏蔽确实需要构造。(驱动程序的功能一直认为颜色键为一个重载。) 3.3.5.2限幅列表 限幅列表从来没有传递给Windows NT/Windows2000上的驱动程序,DD_BLTDATA的IsClipped成员一直为FALSE,且限幅列表一直为NULL。 注意:对Windows 95/Windows98来说,为限幅列表里的每个矩形,DirectDraw支持传递一个整个的限幅列表给驱动程序(如果驱动程序支持这个),而不是多次调用驱动程序。如果驱动程序能够获得一个矩形列表并在一个FOR循环里全部位块转移它们,驱动程序应该在DDNTCORECAPS结构里设置DDCAPS_CANCLIPSTRETCHED和DDCAPS_CANCLIP标志。 在DdBlt里,如果一个限幅器被附着到目标表面上,且限幅器表明目标矩形是阻塞的,DirectDraw打破位块转移到一个简单的阻塞矩形列表里。如果驱动程序设置DDCAPS_CANCLIPSTRETCHED和DDCAPS_CANCLIP标志,调用驱动程序一次,仅仅期望它运行通过在DD_BLTDATA结构里描述的矩形列表。如果驱动程序没有设置这些标志,DirectDraw为每个未被阻塞的矩形调用驱动程序一次,并设置DD_BLTDATA以便它每次仅包含一个矩形。 3.3.5.3颜色填充和图案填充 颜色填充,如被一个矩形描述的一样,用一个特定的颜色填充屏幕的一个区域。在大多数卡上,显存的地址、矩形的维数和颜色都是需要的。一些卡需要起始和终止X、Y坐标,知道Windows会自动地落到这些坐标的最后一行。例如,当它们从0到640计数时,Windows落到640行。 一些卡使用一个图案填充,它们能够完成一个颜色填充同样的事情。象素(图案)的8X8区域弥补了希望的颜色,且那个图案被用来填充指定的区域,图案设置等同希望的颜色且与一个颜色填充是相同的。一个图案填充获得四个分别的颜色,这些颜色可以被混合,这样缩减了必要的指令的数目。 3.3.5.4 DirectDraw和颜色空间转换 DirectDraw允许表面被创建和以YUV格式存储,四个字符代码(FourCC)表示了什么样的颜色空间转换正被使用,然后,在重叠过程中,图象转换为16位RGB。与每个象素(bpp)16位有相同的密度,YUV 4:2:2是管用的,但是颜色可信度更好。图象能够如YUV一样写入且如RGB一样进入显示内存,但是当它在显存之外被读出时,正常地转换被执行以便压缩可被维护,这样节省了显存并加速了回放。对Windows2000来说,一些YUV格式(UYVY和YUY2,都归到4:2:2类)被仿真,但是仅仅是当作为纹理使用时。注意到未被支持的YUV格式表面在系统内存里是不能够创建的。 三个普通的YUV颜色空间(它有许多类型)是: 4:2:2(标准的电视出于这个类型) 4:1:1(更多压缩的) 4:4:4(类似于RGB) 今天多种这些颜色空间和其他的YUV格式在用,总结这些超出了本文档的范围,但是FOURCC信息的一个极好的资源可在www.webartz.com/fourcc/上找到。 3.3.6 假定状态信息的驱动程序 当大多数显示驱动程序做一个操作时,它设置位块转移的状态,然而,一些显示驱动程序期望位块转移处于一个为人所知的状态。例如,一些显示驱动程序假定起点被设置来做一个标准的位块转移而不是一个透明的位块转移,诸如此类。在这些情况下,在DirectDraw使用过状态之后,状态必须被重设。 举个例子,为源和目标表面或者用一个固定的起点或者用一个单个的起点,两个表面之间的位块转移能够被实现。如果显示驱动程序期望起点保持不变且DirectDraw改变它来存取一个次要的表面,当操作完成之后,老的指针必须被保留和存储。 如果当操作正被这样执行的时候这迫使DirectDraw等待,以便它能够恢复寄存器到它们从前的状态时,性能将受损。这是由于DirectDraw的速度是异步的。 在这些状况下你必须小心来最小化显示状态的改变,在这个场景下移动起点也浪费堆栈上的空间,否则的话这个空间将为传递参数而用。 3.3.7 打开显卡并在Windows95/ Windows 98上改变模式 下面的功能被用来打开显卡并在Windows95/ Windows 98上改变它的模式: Enable ReEnable 3.3.7.1 Enable UINT FAR PASCAL _loadds Enable ( LPVOID lpDevice , UINT style , LPSTR lpDeviceType , LPSTR lpOutPut , LPVOID lpStuff ) Enable被调用来打开显卡,或者通过ReEnable它可被调用来改变显示模式。Enable是显示驱动程序的16位部分里的一个功能。(关于Enable的更多信息,看Windows95/ Windows 98 DDK文档的图形设备接口参考部分。) 3.3.7.2 ReEnable UINT FAR PASCAL _loadds ReEnable ( DIBENGINE FAR * lpPDevice, GDIINFO FAR * lpGDIInfo ) { DPF(“ReEnable”); If ( Enable(lpPDevice,EnableDevice,NULL,NULL,NULL)) { Enable( lpGDIInfo,InquireInfo,NULL,NULL,NULL); Return TRUE; } return FALSE; } 每次当显卡模式改变时ReEnable被调用,依次的,ReEnable用参数调用Enable来重新初始化驱动程序对象。取决于当它被调用时传递给它的参数,Enable可以或者打开显卡或者改变显示模式。这样,当Enable通过ReEnable(这是ReEnable所做的所有的事情)被调用时,模式被改变但是没有另外的对象结构产生。在Enable里DDCreateDriverObject被调用,但是bReset参数被设置为1,这正好重设了模式。 如果成功,ReEnable返回TRUE,否则返回FALSE。 3.4 DirectDraw驱动程序初始化 在Windows 95/98和Windows NT上,仅仅当一个应用程序请求时,才检索驱动程序信息。换句话说,为响应一个DirectDraw应用程序的请求而创建一个DirectDraw对象实例,图形引擎调用驱动程序函数来初始化一个DirectDraw驱动程序。 从Windows 2000开始,这个顺序在引导时及每个模式改变之后被执行,这有一个副作用,在Windows 95/Windows 98上,驱动程序典型地有两个操作模式:GDI模式和DirectDraw模式。如果DirectDraw运行,它不会允许GDI高速缓存位图等等,相反,它给DirectDraw(和当在GDI模式里的viceversa)所有的内存,这个行为会损害窗口化的应用程序(如使用DirectX的Web页)。这样,在Windows 2000上,GDI和DirectDraw对相互协作内存如何被使用是必须的。打包在Windows 2000 DDK里的Permedia2和S3 Virge例子驱动程序都是怎样实现这个的极好的例子。 驱动程序初始化顺序通过调用下面的函数而得到: DrvGetDirectDrawInfo,检索关于硬件能力的信息,调用这个功能两次: 第一次调用决定现存堆的大小和驱动程序支持的FOURCCs的数目。GDI为pvmList和pdwFourCC参数传递NULL,驱动程序应该初始化并仅仅返回pdwNumHeaps和pdwNumFourCC参数。 在GDI分配显存和FOURCC内存之后,第二次调用被进行,显存和FOURCC内存基于在pdwNumHeaps和pdwNumFourCC参数里从第一次调用所返回的值。在第二次调用里,驱动程序应该初始化并返回pdwNumHeaps、pvmList、pdwNumFourCC和pdwFourCC参数。 GDI分配和零初始化pHalInfo指向的DD_HALINFO结构,DrvGetDirectDrawInfo功能应该用驱动程序特定的信息在DD_HALINFO结构的相关成员里填充: 驱动程序应该初始化VIDEOMEMORYINFO结构的适当成员来描述显存的总体格式。看显存部分。 驱动程序应该初始化DDNTCORECAPS结构的适当成员来描述驱动程序的DirectDraw内核能力。 如果驱动程序支持DirectX特征的任何一种,该特征通过发送一个GUID给驱动程序的DdGetDriverInfo回调而被查询,驱动程序必须初始化GetDriverInfo成员来指向驱动程序的DdGetDriverInfo回调并在dwFlags里设置DDHALINFO_GETDRIVERINFOSET位。 驱动程序必须设置dwSize成DD_HALINFO结构的字节大小。 运行时用DrvEnableDirectDraw来使DirectDraw硬件可用,并决定一些驱动程序的回调支持。GDI分配和零初始化DD_CALLBACKS、DD_SURFACECALLBAKS和DD_PALETTECALLBACKS参数结构,驱动程序应该为这些它所执行的回调中的每一个做下面的事情: 设置适当结构的对应成员来指向回调。 在适当结构的dwFlags成员里设置对应的DDHAL_XXX_XXX位。 一个驱动程序的DrvEnableDirectDraw实现也能够致力于硬件资源,如仅仅为DirectDraw使用的显存。 如果它不是NULL,GetDriverInfo回调被驱动程序的DrvGetDirectDrawInfo返回到DD_HALINFO结构里。GDI分配和零初始化DD_GETDRIVERINFODATA并为描述在DdGetDriverInfo参考部分里的每个GUID调用DdGetDriverInfo,所有的GUID在ddrawint.h里定义。 锁定表面内存(为整个表面或者一个表面的部分)确保一个应用程序和硬件不能够在同时获得表面内存的存取,这阻止了当一个应用程序写到内存表面时的错误发生。此外,一个应用程序不能够分页交换直到表面内存解锁。 3.5 DirectX视频端口扩展 具有硬件视频端口的设备的驱动程序开发者应该可以执行DirectX的VPE。硬件视频端口致力于视频设备之间的连接,通常在一个硬件动画专家组(MPEG)设备或者国家电视标准委员会(NTSC)的译码器和视频卡。这个固定的连接用视频数据传送水平同步(H-sync)和垂直同步(V-sync)信息,硬件视频端口和重叠可以使用这个同步信息自动地在多个缓冲器之间交换,当重叠显示另外一个时写入到一个表面上。这允许自由撕裂视频而没有加重应用程序的负担。 DirectDraw VPE允许客户---典型地是微软的DirectShow---协商在MPEG或NTSC译码器和硬件视频端口之间连接。VPE也允许客户在视频流里控制效果,包括裁剪,定标等等。一个VPE实现应该仅仅做客户请求的事情:例如,它应该仅当客户请求裁剪时裁剪。 VPE对象没有控制视频译码器是由于它提供它自己的服务,DirectDraw也没有控制视频源:它超出了VPE的范围。宁可,一个DirectDraw VPE对象代表了硬件视频端口自己,它监视进入的信号并传递图象数据到帧缓冲器,使用通过它的接口方法设置的参数来修改图象、执行交换或者执行另外的服务。 VPE不与Windows NT和Windows 2000视频端口系统模块。 3.5.1 视频端口扩展背景 VPE技术是一个DirectDraw扩展,它支持与一个视频译码器直接的硬件连接并在图形帧缓冲器里自动地交换。 当视频数据放到帧缓冲器时,一个视频重叠可用来显示它。在图形系统里的视频重叠告诉DAC在它不能够普通显示的区域里显示数据。重叠的使用是有效的,由于不需要位块转移来使数据可见,由于视频数据可在一个比可见的帧缓冲器更高的颜色深度里被浏览。 在一个VGA图形控制器上的硬件视频端口为了使数据能够到达帧缓冲器而提供一个快速的机制。硬件端口是一个致力于设备之间的连接,特别在一个MPEG设备或者一个NTSC译码器和显卡之间。这个致力于的连接用视频数据输送H-sync和V-sync信息。硬件视频端口和重叠能够使用这个同步信息自动地在多个缓冲器之间交换,当重叠显示另外一个的时候写入到一个表面上,这允许自由撕裂视频而没有加重应用程序的负担。 硬件视频端口是一个视频流输送选项,它可以代替PCI或者AGP总线使用。对期望与其他应用程序协作运行的应用程序,且这些其他的应用程序缺少PCI波宽,使用硬件视频端口确保低等待时间视频在译码器和VGA图形控制器之间的传输,是有利的。这个路径不象PCI总线那样灵活是由于它连接译码器到一个特别的图形成套芯片,但是它提供一个机会当希望如此时绕过PCI总线。 在内核模式视频传输部分里描述的内核模式视频传输能力,也提供VPE支持确保增强的视频回放质量和增强的视频捕获支持。驱动程序必须支持内核模式视频传输以便支持Windows NT/Windows 2000的VPE。 注意:(对Windows 95/Windows 98而言)内核模式视频传输功能在Windows 98上是可选择的,但是Windows 95不支持,这是因为它需要操作系统支持微软的Win32驱动程序模式。 VPE仅仅支持基于硬件的数据连接,内核模式视频传输支持数据转移的直接存取。 VPE不是WDM技术。在Windows 98和Windows 2000上,WDM使用于其他的外设但不使用于显示驱动程序。 由于VPE支持是DirectX 5.0及以后版本的部分,且它在两个操作系统里都是被支持的,应用程序能够利用这些能力确保这个分辨率将在任何图形卡上起作用。该图形卡支持Windows 2000或Windows 98的VPE。 3.5.1.1视频的显示问题 在下面的两个场景里,隔行扫描的视频必须为Windows 2000或Windows 98的回放而整合: 隔行扫描的内容被显示在一个隔行扫描的电视和一个具有隔行扫描的监视器(NTSC /PAL)的枚举机上。 现存的隔行扫描的内容被显示在一个隔行扫描的电视上,它也需要用最可能好的质量在一个顺序的扫描(proscan)计算机监视器上被显示。 3.5.1.1.1 NTSC和隔行扫描的数据 NTSC标准每秒提供一系列59.94的隔行扫描的域,每个以1/59.94秒隔离。偶数行域的扫描行在奇数行域的扫描行之间。然而,由于电视监视器的磷光体持续,两个域从来不会同时显示在电视屏上。观众一直看或者一个偶数域或者一个奇数域,但从来不能两个都看。 在NTSC里的一个帧是两个完全不相关的顺序域的一个任意的分组,也就是说,帧里的第一个域只是相关于相同帧的第二个域,而不是从前帧里的第二个域。逐行倒相制式(PAL)格式和顺序传送彩色与记忆制式(SECAM)标准以每秒50域相同的工作。 如果视频包含高动画内容,奇数域里的数据可能与偶数域里的数据不同,由于不会同时看到这两种域,且眼睛做了这个整合数据的好工作,所以这不会在电视监视器上引起问题。然而,在一个枚举机上,这个隔行扫描的数据被交织进一个单个的缓冲器里,然后使用顺序的扫描被显示。这意味着同时这两个域都是可见的,并有高的动画人工痕迹的可能。 放置视频到一个数字视频硬盘(DVD)之上的过程和然后重新播放它是复杂的。典型地,盘上的源素材给电视创建,这样每个帧有两个隔行扫描的域。然而,每秒(fps)24帧的胶片镜头必须转化成每秒59.94域以兼容一个电视显示。 3.5.1.1.2 NTSC /PAL转换 简单地稍快一点播放胶片,以25 fps的速度播放,可以完成从NTSC 到PAL的转换,两个顺序的域从相同的帧创建,且以1/50秒apart显示。通过用一个调用了3:2的拉片距的过程重复每第五个视频域,可以实现从PAL到NTSC的转换。使用第一个胶片帧创建两个视频域,然后使用第二个胶片帧创建三个视频域,这个过程被重复以便奇数和偶数域以Ao Ae Ao Be Bo Ce Co Ce次序送出。 这样,从一个胶片创建的一个隔行扫描的视频包含着域对,但是不象在一个标准的电视信号,此处每个域是1/60或1/50秒apart,这些域的许多对包含从相同的胶片帧而来的数据,同时显示这些域对不会引起任何人工痕迹,且提供比电视监视器所提供的更好的结果。然而,不从相同的帧而来的任何域对将是1/24秒apart且可能潜在地引起更大的人工痕迹。 当译码隔行扫描的视频时,设置一个标志表示怎样解码流。在一个理想的世界里,由于为DVD或DSS的一个MPEG-2或NTSC译码器的输出在理论上是每秒50或60隔行扫描的域,译码和选择地显示被译码的图片将有足够的信息。然而,更旧的胶片和一些更新的胶片常常在标志节奏里有一个中断,特别在胶片卷完全转变点时。这需要一个方法标识这样一个顺序的内容,以便选择适当的方法在一个帧到帧的基础上显示信息。 3.5.1.1.3 MPEG和顺序的内容 MPEG-2语法提供必要的信息来标识顺序的内容3:2的拉片距,这个信息在下面的一位标志里存在每个帧的头文件里: PROGRESSIVE_FRAME:当为TRUE时,这表示两个域(帧的)实际上来自于相同的时间instant(顺序的胶片)。当为FALSE时,这表示域可能是帧时间apart的一半(隔行扫描的视频)。 TOP_FIELD_FIRST:表示哪一个域及时的首先到来。 REPEAT_FIRST_FIELD:表示是否一个域为3:2的拉片距而重复。 用DirectX 5.0或以后版本下的VPE和DirectDraw,视频一直能够以最可能好的质量显示,如果这些标志或出自于这些标志的一个符号能够在每个域的基础上传送到系统。隔行扫描的视频也能够为DirectShow所支持,用媒体例子里的新标志表示是否没有压缩的媒体例子或者是一个完全的帧,或者是一个域,并加上任何别的信息。如下面的部分所描述的,通过使用或者帧基础的媒体例子,或域基础的媒体例子,DirectShow可在每个帧的基础之上被指令在显示模式之间转换。 3.5.1.1.4 顺序扫描监视器和隔行扫描的数据 在一个proscan监视器上,行作为帧显示---也就是说,行1先被显示,然后是行2,行3 等等。典型的电视显示一个2:1光栅---也就是说,它在奇数行显示一个帧的第一个域,然后它在偶数行显示帧的第二个域。任何将要显示在电视上的信息必须在显示在一个proscan监视器之前被处理。 3.5.1.2 用VPE:Bob和Weave显示隔行扫描的视频 许多昂贵和不完善的方法为解除隔行扫描而存在。当为大尺寸背投影显示解除隔行扫描时,专业的电视制作人使用设备如行加倍装置,且当创建焦距和低运动序列时,它们使用具有运动适应过滤器的印象系统。 对在一个顺序的枚举机监视器上显示隔行扫描有两种简单的方法是可行的:Bob和Weave。枚举机和电视工业为这些方法使用不同的术语;为了简单,这个文档使用了Bob和Weave。 3.5.1.2.1 Bob方法 显示数据的bob方法使用一个重叠单独地(类似于电视)显示每个域,结果图象是正常的一半高度,这样它必须使用一个内插的重叠延伸在垂直方向放大为200%。然而,如果这是bob算法所做的全部,结果图象将上下起伏,因为奇数域与偶数域有一行冲突。只要垂直延伸使用一个内插器被执行,添加一行到奇数域的重叠起始地址可解决这个问题。 Bob方法每秒产生了60(NTSC)顺序的帧,并且保留了所有从隔行扫描源来的临时信息。如果用一个视频相机创建一个视频,且图象是动画,bob是一个顺序的监视器最好的低开销显示过程。这个方法对所有的隔行扫描的视频数据源都起作用,但是weave方法创建一个勾边的图象。 缺省时,DirectShow使用bob方法更正隔行扫描显示。 3.5.1.2.2 Weave方法 weave方法使用硬件视频端口显示数据以交织隔行扫描的域到一个重叠的表面里,然后同时显示两个域。如果这是weave算法所做的全部,动画的人工痕迹会出现,weave算法也依赖MPEG驱动程序记忆3:2图案,然后使用内核模式视频传输的功能解开它。内核模式视频传输可能引起硬件视频端口丢弃重复的域,引起所有的域对偶来自于相同的帧。结果是全帧的视频,它以每秒24帧显示,如同它使用胶片原始采集一样。 每个源胶片帧以两个或三个域的NTSC信号表示,这可以认为是弥补了一个帧的两个一个域。四个胶片帧的每个序列转换成五个电视帧,胶片帧A、B、C和D变成了五个具有域特征AA、BB、BC、CD和DD的电视帧。当REPEAT_FIELD标志在一个MPEG流里编码这个特征时,MPEG数据有效载荷仅包含四个帧,但是所有五个电视帧的域序列被保留下来了。 weave方法每秒产生24个顺序的帧并从一个隔行扫描的原理保留全部的垂直分辨率。如果一个视频从使用一个3:2拉片距的胶片创建,或者它不包含动画,weave是顺序的监视器可付的起的最好的显示过程。 3.5.1.2.3 模式指示器和变形格式 好的驱动程序设计调用bob、weave和边缘适应的过滤的一个综合。动画探测电路能够动态地控制每个使用在一个象素到象素的基础之上的技术所使用的程度。不幸的是,将完成这个的显示逻辑是难于实现的,除非额外的数据可用。随着大型枚举机等级显示能力的提高,操作系统需要这个额外的数据来确保基本的图形电路能够信赖的传输一个可选择的图象。 在DVD世界里,许多格式可能被综合在一张盘上,例如,24fps胶片可能被编辑成525/60视频且然后编辑成30fps胶片,这样的编辑发生的每个时候,它潜在的改变了怎样最好的显示数据。当前的DVD标签没有保证在显示系统里处理这个混合格式的好的执行。 另外一个可能的场景是一个胶片的显示,此处由于一个错误,REPEAT_FIELD标志是不规则性的或缺少的。当译码器尝试解释一个不规则性的3:2图式时,它尝试重新获得同步,但这可能是杂乱的。对一个紧随REPEAT_FIELD标志的显示而言,字面上说在镜头的中间,从weave模式改变到bob模式或者反之是完全可能的,对一个观众来说这将非常糟。 显示一个偶然有3:2图式不规则性的胶片的方法是从weave模式到bob模式转换并返回围绕不规则性的镜头截除。在其他情况下,特别的标识一个新图案第一个域对和维持weave模式可能是最好的。 你可以想象许多内容场景,一些自动显示方案比其他的工作的更好,这取决于所包含的特定的内容。并非所有的内容提供者都需要保证回放平台的最大范围的可选择结果,但是DirectShow给了内容作者判断显示模式对它们的标题的影响的一个方法并传送它们的选择权。 3.5.1.3 在DirectDraw API和驱动程序里的VPE功能 在DirectX 5.0和后来的版本里的VPE是低级的DirectDraw API扩展,VPE允许客户---通常DirectShow---协商在MPEG或NTSC译码器和硬件视频端口之间连接,VPE也允许客户在视频流里控制效果,包括裁剪,定标等等。 VPE不是为应用程序广泛使用的一个高级API,应用程序应该使用DirectShow,它提供对VPE自由支持。图3.10给出了VPE和内核模式结构的一个简单的浏览;欲知更多的信息。请看内核模式视频传输部分。 图3.10 VPE和内核模式结构 图3.10 给出了VPE与DirectDraw结构里的其他组件的关系。DirectShow使用VPE协商连接,它提供这样的信息,即关于数据和V-sync和H-sync信息是怎样被传送的,这个信息可以是一个APIC连接(ITU 656)、具有额外插针的外部数据行或者专有的数据流,如被Brooktree和Philips实现的那些。 在关于连接的协商里,VGA硬件表明什么样的连接能够被支持,MPEG或NTSC译码器表明了它的参考。DirectShow协商在这两个之间的最好连接。连接被描述为一个GUID,它具有标志来描述其他的参数,如双倍定时、视频激活等等。 3.5.2 DirectX VPE初始化 为了使VPE功能可能,驱动程序必须做下面的事情: 当调用DrvGetDirectDrawInfo时,初始化下面的DDNTCORECAPS结构成员,该结构嵌套在pHalInfo参数指向的DD_HALINFO结构里: 当调用DrvGetDirectDrawInfo时,实现一个函数并设置DD_HALINFO结构的GetDriverInfo成员来指向这个函数。驱动程序的DdGetDriverInfo函数必须分析GUID_VideoPortCallbacks和GUID_VideoPortCaps GUIDs。 当用GUID_VideoPortCallbacks GUID调用DrvGetDirectDrawInfo时,填充一个具有适当的驱动程序回调和标志设置的DD_VIDEOPORTCALLBACKS结构。然后驱动程序必须拷贝这个被初始化过的结构到DirectDraw-allocated缓冲器里,DD_GETDRIVERINFODATA结构的lpvData成员指向了该缓冲器,并在dwActualSize里返回写入到缓冲器里的字节的数目。 当用GUID_VideoPortCaps GUID调用DrvGetDirectDrawInfo时,填充一个具有每个硬件视频端口能力的DDVIDEOPORTCPS结构的数组。每个硬件视频端口在数组里有一个入口点,硬件视频端口首先被指定为0,接下来指定为1,等等。如果设备仅支持一个硬件视频端口,将只有一个DDVIDEOPORTCPS结构在数组里。然后驱动程序必须拷贝这个数据到DirectDraw-allocated缓冲器里,DD_GETDRIVERINFODATA结构的lpvData成员指向了该缓冲器,并在dwActualSize里返回写入到缓冲器里的字节的数目。 3.5.3内核模式视频传输支持的DirectDraw接口 内核模式视频传输必须明了为它使用的每个表面和VPE对象的表面信息,这个信息必须当DdUpdateOverlay或DdVideoPortUpdate每次为表面或硬件视频端口而被调用时得以更新。在DirectDraw发送这个信息到内核模式视频传输之前,它调用两个驱动程序功能的一个:DdSyncSurfaceData或DdSyncVideoPortData。这些功能允许驱动程序出于它自己的目的填充或修改信息的一些结构并使用DD_SYNCSURFACEDATA的四个dwDriverReserved成员或DD_SYNCVIDEOPORTDATA结构的三个dwDriverReserved成员。这些驱动程序功能对内核模式视频传输正确地工作是必要的。 驱动程序怎样使用这些dwDriverReserved成员的一个好的例子是设置一个标志,该标志表明了如果硬件支持超过一个重叠时,哪一个物理重叠表面正在使用。 3.6 彩色控制初始化 DdControlColor被添加到DirectX 5.0来控制亮度/一个重叠的亮度控制和/或主表面,为了使颜色控制可能,DirectDraw HAL必须在初始化时间里做下面的工作: 如果重叠和/或主表面包含颜色控制,在DDVIDEOPORTCPS结构的dwCaps2域里设置DDCAPS2_COLORCONTROLOVERLAY和/或DDCAPS2_COLORCONTROLPRIMARY标志,该结构嵌套在DD_HALINFO结构里。 驱动程序必须在DD_HALINFO结构里指定一个功能,DirectDraw能够调用这个结构得到附加的信息,这在DdGetDriverInfo里有描述。 DdGetDriverInfo回调必须与指定的DD_ColorControlCallbacks GUID一起被调用,驱动程序必须填充一个具有适当的驱动程序回调和标志设置的DD_VIDEOPORTCALLBACKS结构,然后拷贝这个结构到输入结构的lpvData成员里。 3.7 AGP支持 DirectDraw对待高级图形端口(AGP)内存为一个子类的显存,这个内存类型参照非本地显存,从DirectDraw和DirectDraw驱动程序的角度来看,术语AGP内存和非本地显存是同步的。 AGP内存被认为是一个显存的纯子类。也就是说,如果一个驱动程序表明它支持AGP内存,那么在大多数情况下,它为本地和非本地显存应具有同样的功能,尽管性能差异被允许。它必须有同样的功能能力。例外是如果设置DDCAPS2_NONLOCALVIDMEMCAPS标志,在这种情况下,非本地显存的位块转移能力能够与本地显存不同。 例如,如果一个驱动程序声明它能够从显存组织纹理,它必须能够从本地和非本地显存组织纹理,位块转移被相似的对待。一个输出了源颜色键位块转移能力的驱动程序必须能够到和从非本地显存做一个源颜色键位块转移。这个规则的一个例外是,有可能排除某些表面类型在非本地显存中被分配,例如,使用堆阻止重叠表面在AGP内存里被分配是可能的。 由于AGP内存被看待为显存的一个子类,DirectDraw没有为AGP内存单独设置显示驱动程序的入口点。AGP表面和本地显存表面都使用了存在的显示驱动程序调用,一个AGP兼容的驱动程序必须检查进入的表面来看它们是否处于非本地或本地显存,然后采取适当的步骤。从系统到AGP(和反之)的位块转移如平常一样检查DirectDraw仿真层,除非一个驱动程序支持系统到显存的位块转移(在这种状况下,它也必须支持系统到AGP的传输)。 由于Direct3D纹理管理器在AGP内存里保留了它的后备表面,驱动程序应该尽可能多的设置DDCAPS2_TEXMANINONLOCALVIDMEM标志。 本章的其余部分使用了DirectDraw非本地显存特征,讨论修改存在的驱动程序以支持AGP内存所必须的步骤。 3.7.1为非本地显存标志支持 一个驱动程序必须通过在DDNTCORECAPS结构的dwCaps2成员里指定能力位DDCAPS2_NONLOCALVIDMEM来通知DirectDraw(和DirectDraw应用程序),说驱动程序是AGP兼容的,DDNTCORECAPS结构是传递到DirectDraw的DD_HALINFO结构的部分。 如果DirectDraw正在一个不支持AGP服务的操作系统上运行,它关闭DDCAPS2_NONLOCALVIDMEM能力位和所有相关的非本地堆。 3.7.2指定非本地显存堆 一个DirectDraw驱动程序控制多少AGP内存是可用的和在传回到DirectDraw的 DD_HALINFO结构里返回堆到哪一个表面。通过在描述了堆的VIDEOMEMORY数据结构的dwFlags成员里指定VIDMEM_ISNONLOCAL标志,驱动程序标识了非本地堆。此外,通过指定除过VIDMEM_ISNONLOCAL之外的VIDMEM_ISWC标志,一个驱动程序能够选择使得内存综合写入一个非本地堆。(然而,VIDMEM_ISWC标志当前没有在DirectDraw里执行。) 一个AGP兼容的DirectDraw驱动程序负责描述DirectDraw的大小(线性的或矩形的)、属性(写结合)和堆不应该和不能够使用的表面类型。然而,驱动程序不负责实际的堆的保存地址空间或提交内存给它。DirectDraw代表驱动程序处理这个事情,并隐藏了从驱动程序管理AGP内存的细节。 对一个非本地显存堆而言,驱动程序指定的起始地址没有意义,当DirectDraw请求创建一个对时,操作系统决定一个非本地堆的起始地址。这样,驱动程序能够为起始地址返回任何值。对一个矩形堆来说,DirectDraw忽略了这个起始地址,DirectDraw需要的所有用来决定内存需求的事情是指定的宽度和高度。对一个线性堆来说,起始地址有意义,但是仅仅用来枚举堆的大小。 DirectDraw通过枚举(fpEnd-fpStart)+1决定了线性堆的大小。(注意到被指定的末端是堆里的最后一个字节,而不是在堆的末端之后的第一个字节。)象这样一样,只要当DirectDraw从末端地址减去那个地址并加上1,就能够指定任何起始的地址,结果是堆的最大尺寸。 尽管仅当需要时(即是,当分配表面时)物理内存被提交给AGP堆,不指定每个大的非本地堆是重要的。甚至在提交物理内存之前,这样的堆就消耗了共享的地址空间和其他重要资源。 注意到DirectDraw和Windows操作系统给内存量强加了对AGP内存的政策限制,此AGP内存能够在任何给定的时间里被提交。这阻止了剩余系统的资源“饥饿”。这样,对一个非本地显存表面的一个请求来说,即使非本地堆没有完全被提交,失败是很有可能的。 当DirectDraw已经决定了正确的堆地址(线性的和物理的),就将它们存储在它的堆描述器里。它也提供一个机制在初始化时通知驱动程序这些地址,如何完成这个是特定平台相关的: 在Windows NT/Windows 2000上,用一个DdGetDriverInfo调用GUID_UpdateNonLocalHeap可以完成,当这个GUID被传递给DDGetDriverInfo时,UpdateNonLocalHeap数据在数据结构里被传输。 在Windows 95/Windows 98上,用DD_MISCELLANEOUSCALLBACKS可以完成此事,用DDGetDriverInfo调用从驱动程序提取MISCELLANEOUSCALLBACKS结构。在初始化时,DirectDraw运行时检查一个FOR循环,调用被UpdateNonLocalHeap成员指定的回调。然后,DirectDraw为每个堆请求驱动程序提供的功能,并为功能传递堆数目和堆的线性高端的起始物理地址。 3.7.3实际堆基本地址的通知 一个驱动程序在DirectDraw初始化时(例如,在模式改变的过程当中)可能需要知道堆的线性物理基地址,而不是等待一个表面创建请求和在全局DirectDraw表面对象里考虑堆。为了支持这个,DirectDraw用一个GUID调用驱动程序提供的DdGetDriverInfo回调功能,GUID标识了驱动程序将要返回的信息。如果驱动程序认出了GUID且有信息返回,它拷贝这个信息到提供的数据结构里并传回给DirectDraw。 驱动程序使用两个GUIDs来收集和提供关于DirectDraw堆的更多信息。 GUID_GetHeapAlignment GUID_UpdateNonLocalHeap GUID_GetHeapAlignment发送信号到驱动程序来收集传递给它的任何DirectDraw堆的堆分配信息,使用DD_GETHEAPALIGNMENTDATA结构传递堆信息给驱动程序,GUID_GetHeapAlignment被定义为: DEFINE_GUID ( GUID_GetHeapAlignment, 0x42e02f16, 0x7b41, 0x11d2, 0x8b, 0xff, 0x0, 0xa0, 0xc9, 0x83, 0xea, 0xf6); GUID_UpdateNonLocalHeap给驱动程序发出信号,以便用DirectDraw提供的具有非本地堆结构的堆信息来更新它的内部状态。这个信息被包含在DD_UPDATENONLOCALHEAPDATA结构里,GUID_UpdateNonLocalHeap被定义为: DEFINE_GUID ( GUID_UpdateNonLocalHeap, 0x42e02f16, 0x7b41, 0x11d2, 0x8b, 0xff, 0x0, 0xa0, 0xc9, 0x83, 0xea, 0xf6); 如果驱动程序必须通过它自己为AGP表面分配内存,但是已经暴露堆给DirectDraw,为了这个目的,HeapVidMemAllocAligned被暴露作为一个Eng功能。HeapVidMemAllocAligned仅仅处理堆地址,这样它返回一个偏移,驱动程序必须做它所需要的任何内存映射工作,来转变从HeapVidMemAllocAligned返回的信息为一个虚拟的地址。 3.7.4处理非本地显存表面的回调 按照驱动程序回调,以完全相同的方法对待非本地显存表面与本地显存表面。例如,当试图创建非本地(如同本地)显存表面时,一个驱动程序的DdCanCreateSurface回调被调用;当在本地和非本地显存表面之间进行位块转移时,DdBlt被调用了;当表面显存正被丢弃时,DdDestroySurface被调用了。 既然本地和非本地显存表面使用相同的驱动程序功能,驱动程序必须明确地检查进入的表面的类型。可通过检查传递到驱动程序的本地表面对象DD_SURFACE_LOCAL的ddsCaps.dwCaps成员来标识内存类型,而不是能力位DDCAPS_LOCALVIDMEM和DDCAPS_NONLOCALVIDMEM。 应用程序和AGP使用了两个不同的地址存取一个DirectDraw表面的位,应用程序使用了一个虚拟地址,该虚拟地址通过操作系统的页表被转变成物理地址空间的一个部分,这个物理地址被GART硬件映射为连续的。硬件存取这个物理线性地址(重新被GART映射为不连续的真的内存页),DD_SURFACE_GLOBAL结构的fpVidMem成员保留这个对应用程序(和一些潜在的驱动程序操作)有用的虚拟线性地址,通过计算可以发现设备一方的物理地址。 FpStartOffset = pSurface -> fpHeapOffset – pSurface->lpVidMemHeap->fpStart; 这个偏移然后被添加到设备的GART物理基地址中(包含在VMEMHEAP结构的liPhyAGPBase成员)。 在其他所有的方面,非本地显存表面表现得与本地显存表面完全相似。当一个应用程序试图存取非本地显存表面的表面数据时,驱动程序接收到锁定请求。如在非本地显存与本地显存之间的位块转移操作能够是异步的,就如同它们能够在本地显存表面之间一样。当包括在这些表面的操作仍旧待处理时,锁定非本地显存表面的努力将由于在通常方法下具有DDERR_WASSTILLDRAWING错误代码的驱动程序而失败。 此外,尽管DirectDraw代表驱动程序管理分配和释放非本地显存表面的任务,驱动程序仍旧被通知创建和注销非本地显存表面里的表面。当一个非本地显存表面被注销时,驱动程序不应该返回直到表面不再使用。 非本地显存恰好以与本地显存同样的方法里丢失---也就是说,当一个模式转换发生时,或当唯一的模式改变发生时,所有的本地和非本地显存表面丢失了,且DdDestroySurface驱动程序回调为每个表面而被请求。然而,没有保证实际的保留地址范围和调拨的内存将被保存。可能选择丢弃所有调拨的内存和保留的地址范围,或者它可能选择解除调拨内存但是保存地址范围,它也可能保存这两个且简单地将表面标志为丢失状态,一个驱动程序不应该基于这三个情景的任何一个来做出假设。 3.7.5在非本地显存里重新排序纹理 在特殊的情况下,驱动程序编写者可能想要在AGP内存里重新排序纹理以便允许更有效的纹理管理。DDCAPS2_SYSTONONLOCAL_AS_SYSTOLOCAL标志发出了信号表明驱动程序能够支持从后备表面到仍然使用了caps的非本地视频内存的位块转移,该caps为后备表面内存而指定到本地视频内存的位块转移。 仅仅如果DDCAPS2_NONLOCALVIDMEMCAPS标志被设置, DDCAPS2_SYSTONONLOCAL_AS_SYSTOLOCAL标志才是有效的。如果设置了DDCAPS2_SYSTONONLOCAL_AS_SYSTOLOCAL,驱动程序必须设置DDCAPS_CANBLTSYSMEM标志,且所有相关的后备表面位块转移caps必须正确。DDCAPS2_SYSTONONLOCAL_AS_SYSTOLOCAL表示了后备表面到视频内存DDCAPS位块转移caps也应用于后备表面到非本地视频内存位块转移。例如,保证DDCAPS(DDNTCORECAPS)的dwSVBCaps、dwSVBCKeyCaps、dwSVBFXCaps和dwSVBRops成员将被正确地填充。任何从后备表面到匹配于这些caps位的非本地内存位块转移被传递到了驱动程序。 注意:这个特征的目的是使驱动程序自己能够做有效的纹理里重新排序,这并不意味着硬件能够写入AGP内存,直接地写入AGP内存的硬件在当前并不被支持。 3.7.6处理DMA-style AGP 一个AGP兼容的显卡能够用两个方法中的一个使用AGP内存:使用执行模式或直接内存存取(DMA)模式。 在执行模式里,如果在非局部视频内存里有一个纹理,显卡直接地存取AGP内存,也就是说,如果一个显卡从AGP内存被组织纹理,它直接从一个后备表面读纹理象素。 在一个DMA模式里,表面的内容必须在执行一个纹理操作之前被明确地移到显卡上的本地显存。 注意到模式提到了显卡的一个客户如何看待传输是重要的,例如,当进行纹理时,一个显卡可能自动地从一个后备表面移动纹理象素到一个小的本地显存高速缓冲。这也许象DMA模式,但是由于客户应用程序没有关于这个传输发生的信息,事实上,是显卡暴露了一个执行模式。仅仅当客户应用程序不得不采取明确的步骤移动一个后备表面的内容到本地显存时,显卡才被认为将暴露DMA模式。 前面处理AGP内存的部分描述了一个驱动程序如何能够和暴露AGP用法的执行模式,这一部分描述了一个驱动程序必须采取的步骤来给应用程序暴露DMA模式AGP的使用。注意到你必须决定当写驱动程序时是否暴露执行模式或DMA模式,驱动程序应该暴露一个模式或者另一个,但不能够是全部。 在从一个驱动程序露DMA模式之前,你应该考虑隐含DMA模式给应用程序作者。如果一个驱动程序暴露执行模式AGP支持,DirectDraw保证在AGP(非本地显存)和本地显存里的表面在功能上是一样的。这样,显卡能够从非本地或本地显存组织纹理,而没有应用程序的额外步骤。当设置一个着色状态时,一个应用程序能够直接地指定处理给一个纹理表面,而不管是否表面在非本地或本地显存里。 然而,如果一个驱动程序暴露DMA模式,在非本地显存里的表面可能有与本地显存里的表面不同的能力。这样,在尝试从一个非本地显存里的表面组织纹理之前,应用程序必须检查是否硬件能够从非本地显存里组织纹理。通过检查驱动程序暴露的能力可以完成这个工作,对位块转移来说这也是相同的。 一个应用程序通过用DDCAPS2_NONLOCALVIDMEM指定DDCAPS_VIDEOMEMORY明确地请求AGP内存,如果一个应用程序没有指定一个内存类型或仅指定了DDCAPS_VIDEOMEMORY,将不会考虑非本地显存。同样,如果一个调用没有指定本地或非本地显存的话,表面是一个纹理,设备设置了D3DDEVCAPS_TEXTURENONLOCALVIDEONEMORY标志,且能够在AGP内存里分配表面。 这意味着如果一个驱动程序暴露DMA模式,不会从AGP内存分配表面。这相对于暴露了执行模式的驱动程序,在它里面甚至如果应用程序没有明确地请求它,AGP内存仍然被分配,因而暴露了执行模式的驱动程序对应用程序的使用来说更为简单。此外,一个执行模式驱动程序允许一个合法的应用程序获得AGP收益,然而一个DMA模式驱动程序仅仅加速为AGP而明确地写入的新应用程序。当决定是否要暴露执行模式或DMA模式时,你应该考虑到这些。 3.7.6.1为DMA模式的非本地显存标志支持 除过指定DDCAPS2_NONLOCALVIDMEM标志来报告AGP支持外,一个DMA模式驱动程序也必须输出能力标志DDCAPS2_NONLOCALVIDMEMCAPS,这个标志表示非本地(AGP)内存跟本地显存相比有不同的能力。 3.7.6.2为DMA模式的非本地显存报告DirectDraw能力 对非本地内存和本地显存来说,一个DMA模式驱动程序的能力不同。例如,一个显卡可能延伸位块转移本地显存表面,但是不能够延伸非本地显存表面。如果驱动程序指定DDCAPS2_NONLOCALVIDMEMCAPS标志,非本地显存表面的DirectDraw能力通过DdGetDriverInfo驱动程序入口点探测驱动程序。标识这个探测的GUID是GUID_NonLocalVidMemCaps。 注意到DirectDraw的这个版本是重要的,一个驱动程序能够仅为非本地显存到本地显存的位块转移指定能力。从本地显存到非本地显存及从非本地显存到本地显存的转移一直被DirectDraw HEL所访真,这个限制可能会在以后的版本里放松。 3.7.6.3为DMA模式的非本地显存报告Direct3D能力 一个DMA模式驱动程序也必须为非本地显存表面输出Direct3D能力,这比报告DirectDraw能力要简单的多,唯一被影响的能力是D3DDEVCAPS_TEXTURENONLOCALVIDEOMEMORY。如果输出DMA模式的一个显卡能直接地从非本地显存组织纹理,它应该在它的Direct3D设备描述里设置这个能力。如果它不能够,且应用程序必须在执行纹理之前明确地装载或位块转移非本地显存表面到一个本地显存表面,则它不能够设置这个能力。为了完整起见,一个执行模式驱动程序应该一直设置这个能力位。 3.8 内核模式视频传输 内核模式视频传输指一个DirectDraw组件,如DirectShow,用户可以使用这个组件来增强视频功能,这个功能的主要任务是当V-sync发生时调用微端口来告诉它执行硬件视频端口和重叠交换。只要硬件视频端口支持V-sync中断请求(IRQ),这个能力能够支持达到10个缓冲器而没有遇到硬件限制。当客户指定自动交换且硬件不能够自动交换时,DirectX5.0及以后的版本提供的DirectDraw版本自动地使用这个能力。 内核模式视频传输也确保增强的捕获支持。在Windows 98和Windows 2000上,基于WDM的视频捕获驱动程序在内核模式里运行,且直接存取帧缓冲器,捕获驱动程序能够“手工地”交换重叠。Windows 2000微端口视频传输驱动程序能够从硬件视频端口或显示提供V-sync通知;它也能够得到极性域,当捕获垂直清空间隔(VBI)数据时可使用该极性域。 尽管内核模式驱动程序的主要目的是增强硬件视频端口自动交换的能力,它也支持视频总线主设备,当处于内核模式时此主设备能够写入数据。由于一个模式的改变或者由于全屏命令提示实例启动,总线主设备能够在失去表面之前被通知。又由于新的驱动程序支持允许在改变发生之前调用一个总线主设备,因而总线主设备能够关闭而不会引起问题。 3.8.1 VPE和内核模式视频传输的体系结构 本部分为DirectX 5.0及以后的版本里的VPE和内核模式视频提供了Windows 2000体系结构的一些细节。内核模式视频传输的体系结构在Windows 98和Windows 2000上是相同的;它基于微软作为设备独立的代码所添加的新功能。内核模式视频传输包括一个DxApi功能,此功能作为DirectDraw的部分,驱动程序在微端口里提供的DxApi回调,和作为DirectDraw部分而提供的COM接口方法。 在Windows 98里,DxApi回调被添加到小DVD,其包含特定图形设备的代码。 在Windows 2000里,如图3.11所示,DxApi回调是Windows 2000微端口的部分。为获得关于DxApi回调的更多信息,请看Windows 2000 DxApi微端口功能。 图3.11 内核模式视频传输的体系结构 图3.11给出了与其他内核模式和用户模式组件相关的内核模式视频传输的体系结构。(虚线意味着内核转变。)在这个体系结构里,DirectShow(或另外的用户模式客户)调用IDirectDrawKernel和IDirectDrawSurfaceKernel DirectDraw COM接口来处理DirectDraw对象和表面对象。 注意:体系结构也为MPEG设备和VGA设备之间的数据流支持使用PCI总线。 在Windows 2000里,客户然后传递这些处理给微端口驱动程序,在内核模式视频传输的调用里指定这些处理。图3.12给出了如何传递这些处理一个简单的版本。 图3.12 在Windows 2000视频传输里传递句柄 图3.12 给出了如何在用户和内核模式视频传输里传递这些句柄。 3.8.2 使用内核模式视频传输 视频微端口驱动程序通过与dxapi.lib链接存取内核模式视频传输功能,这个dxapi.lib文件允许它以后调用dxapi.sys,仅仅当装载了DirectDraw时,这个功能才是可用的。 一个硬件译码器使用与内核模式DirectDraw一起提供的DxApi功能来存取DxApi微端口回调功能。DxApi函数是一个单个的入口点,这个入口点接受一个功能识别器、一个输入缓冲器及其大小和一个输出缓冲器及其大小。这个功能的行为以及输入、输出缓冲器的大小和格式均依赖于指定的功能识别器,DxApi功能和它的功能识别器在ddkmapi.h文件里定义。 DirectShow或者另一个客户存取驱动程序通过DirectDraw提供的DxApi微端口回调功能,DxApi微端口功能在dxmini.h文件里定义。 为了使用内核模式视频传输接口,微端口驱动程序必须首先接收用户模式句柄的每个DirectDraw对象、表面和VPE对象。对捕获和MPEG模式来说,这些句柄是通过其现存的API向下传递,如果一个非流类驱动程序需要这个功能,一个用户模式组件能够通过使用IDirectDrawKernel和IDirectDrawSurfaceKernel COM接口重新获得这个句柄,并向下传递给驱动程序,COM接口及其方法在ddkernel.h文件里做了标识。 3.8.2.1获得用户模式句柄 下面的程序给出了如何获得用户模式句柄。 为获得DirectDraw处理 在DirectDraw接口上调用QueryInterface(lpDD,&IID_IDirectDrawKernel,&pNewInterface)。 在新的接口上调用IDirectDrawKernel::GetKernelHandle方法。 IDirectDrawKernel::GetKernelHandle方法返回DirectDraw驱动程序的一个内核模式句柄。为释放这个句柄,使用IDirectDrawKernel::ReleaseKernelHandle方法。 为获得DirectDrawSurface句柄 在DirectDrawSurface接口上调用QueryInterface(lpSurface,&IID_IDirectDrawSurfaceKernel,&pDDSK)。 在新的接口上调用IDirectDrawSurfaceKernel::GetKernelHandle方法。 IDirectDrawSurfaceKernel::GetKernelHandle方法返回DirectDrawSurface驱动程序的一个内核模式处理。为释放这个句柄,使用IDirectDrawSurfaceKernel::ReleaseKernelHandle方法。 3.8.3 Windows 2000的DxApi微端口功能 由于DxApi微端口支持需要改变操作系统,所以它仅在Windows 2000上可行。 DxApi微端口支持对下面的应用程序是有用的: 使用了设备的IRQ的自动交换。这个设备不支持硬件自动交换,或由于有某些限制而不可靠。当无法使用硬件自动交换时,DirectDraw就总会转变成软件自动交换。 使用了一个IRQ来支持MPEG驱动程序的域跳读,该驱动程序能够松开最初从胶片采样的MPEG数据的3:2拉片距。 设备的总线主控。这些设备需要连续地总线主控数据而不必为每个帧调用DdLock/DdUnlock,由于支持这些设备的驱动程序是WDM驱动程序,这在微端口里特别有用。 视频和VBI捕获。在微端口驱动程序里,很容易捕获基于一个硬件视频端口IRQ或图形IRQ的视频。 对这个API的支持已经添加在Windows 2000的微端口驱动程序里。 3.8.3.1 DxApi微端口初始化 为了使用DxApi微端口驱动程序的功能,DirectDraw驱动程序必须在初始化时间里执行下面的任务: 它必须指定DD_HALINFO结构里的DdGetDriverInfo功能,这样DirectDraw能够调用来得到额外的信息。 它必须用指定的GUID_KernelCallbacks GUID调用DdGetDriverInfo回调。驱动程序必须用适当的回调和标志集填充一个DD_KERNELCALLBACKS结构,然后将这个结构拷贝到DD_GETDRIVERINFODATA结构的lpvData成员。 它必须用指定的GUID_KernelCaps GUID调用DdGetDriverInfo回调。驱动程序填充DDKERNELCAPS结构,然后将这个结构拷贝到DD_GETDRIVERINFODATA结构的lpvData成员。 内核用MajorFunction=IRP_MJ_PNP、MinorFunction=IRP_MN_QUERY_INTERFACE和InterfaceType=GUID_DxApi调用微端口驱动程序的IOCTL处理程序,这使得微端口驱动程序填充DXAPI_INTERFACE结构,且指针指向DirectDraw能够调用的微端口驱动程序的功能。 微端口驱动程序能够在DXAPI_INTERFACE结构的Context成员里指定一个值,该结构在每次调用其中一个功能时传递给微端口驱动程序。 3.8.3.2视频VBI捕获 DirectX 5.2引进了视频捕获的两个DirectDraw驱动程序功能:DxTransfer和DxGetTransferStatus。 DxTransfer功能给视频和VBI捕获带来了便利。由于这个功能在IRQ时间调用,驱动程序必须调用DxTransfer并尽可能快的返回,如果显示硬件在DxTransfer被调用的时候没有准备执行一个总线主设备,驱动程序应该保留总线主设备数目的一个内部队列。(保存在队列里的总线主设备的实际数目由驱动程序开发者决定。)这使得当硬件准备好时它可以执行总线主设备,换句话说,驱动程序不应该查询和等待总线主设备。 当DirectDraw调用DxTransfer功能时,它在DDTRANSFERININFO结构的DxTransfer成员里返回一个转移的ID,然后当调用DxGetTransferStatus功能时,DirectDraw驱动程序能够使用这个ID。 当总线主设备完成时,驱动程序必须创建一个IRQ,然后驱动程序必须调用在DxEnableIRQ里指定的IRQCallback功能并指定DDIRQ_BUSMASTER标志之后,DirectDraw调用DxGetTransferStatus功能来决定完成哪一个总线主设备。驱动程序必须返回DirectDraw先前传递给驱动程序的转移的ID(dwTransferID),用这样的方法,驱动程序在队列里有五个总线主设备,DirectDraw能够决定哪一个是最近完成的。 3.9 扩展的表面定位 DirectDraw在每个堆的基础上支持表面分配需求,在DirectX 5.0中有对这个支持的介绍。驱动程序能够为矩形堆指定X和Y分配,并为线性堆指定行距和启动冲突分配,这些分配根据表面类型的不同而有变化。 一些显示硬件不能够在一个原子的操作里设置它的显示启动偏移,在刚开始显示时,当驱动程序未能彻底地设置这个值时,这样的一个硬件可能闭锁一个新的显示启动偏移。DirectDraw现在允许驱动程序为可见的后备缓冲器指定分配需求,一些硬件可能能够为潜在的后备缓冲器表达分配需求,该后备缓冲器迫使显示启动偏移为一个需要仅一个寄存器写入的值。这个技术能够帮助避免偶而的闪烁,当主平面在一个高频上交换时,这个闪烁就有可能是可见的。 3.9.1旧式定位方法回顾 在DirectX 5.0以前的DirectDraw版本允许驱动程序为线性堆表达行距分配需求。为了便于讨论,这些分配需求的DirectDraw的使用分为以下三步: 创建表面并填充一个分配的IPitch成员,基于驱动程序的全局分配需求(如在VIDEOMEMORY结构返回的一样)和表面的ddsCaps成员,行距增加到成为适当的分配需求的一个倍数。 如果定义了,就调用驱动程序的DdCreateSurface回调,驱动程序能够修改IPitch值,但是Windows 2000忽略了这个改变。 如果驱动程序的调用没被处理或者它请求分配,从驱动程序的一个堆中给表面分配显存。被分配的表面的宽度适用于在第一步里所决定的行距,除非在第二步里驱动程序对行距做了修改。 如果驱动程序实现了DdCreateSurface回调,就能确保任何后继的表面具有其设置不分配值的lPitch成员。为了向后的兼容性,新的DirectDraw版本里仍旧保留了这个行为。除非驱动程序已经暴露了一个GetHeapAlignment入口点,第三步严格维持了同样的行为。如果,且仅仅如果定义了这个入口点,从前枚举的lPitch分配被丢弃了,且所有的表面分配遵从使用GUID_GetHeapAlignment而被报告的需求。当驱动程序在老的DirectDraw运行时运行时,驱动程序能够如它们一样保留它们的VIDEOMEMORYINFO结构分配需求,并期望同样的分配行为,这个分配行为在新的(DirectX 5.0及以后的版本)DirectDraw运行时已经被代替。暴露GetHeapAlignment关闭了所有堆的这个合法的分配程序,并非仅仅是GUID_GetHeapAlignment报告分配需求的那些堆。 3.9.2使用扩展的表面定位 为了使用扩展的表面分配功能,DirectDraw驱动程序必须在初始化时执行下面的任务: 它必须在DD_HALINFO结构里指定一个DdGetDriverInfo功能,这样DirectDraw就能够调用这个结构得到额外的信息。 它必须用指定的GUID_GetHeapAlignment GUID调用DdGetDriverInfo回调。驱动程序必须填充一个DD_GETHEAPALIGNMENTDATA结构,然后将这个结构拷贝到DD_GETDRIVERINFODATA结构的lpvData成员。 驱动程序应该为这个堆里需要分配的任何类型的表面,在HEAPALIGNMENT结构里驱动程序应利用DDSCAPS_xxxx标志的逻辑OR,来填充被指向的DDSCAPS结构。如果在DDSCAPS里设置了一个位,DirectDraw遵从在一个适当的SURFACEALIGNMENT结构成员里表达的分配限定。DDSCAPS_FLIP位和Flip Target成员适用于那些主交换链里的后备缓冲器的表面---也就是说,适用于一个潜在的主(可见的)表面。下面的列表给出了当前允许的表面能力设置,分配可被指定于这些能力: DDSCAPS_OFFSCREENPLAIN DDSCAPS_EXECUTEBUFFER DDSCAPS_OVERLAY DDSCAPS_TEXTURE DDSCAPS_ZBUFFER DDSCAPS_ALPHA DDSCAPS_FLIP 注意:DirectDraw比较一个新表面的能力与HEAPALIGNMENT结构里的入口点以便在这个结构里对其进行指定。例如,由于这是第一次指定了分配的可应用的能力位,所以按照HEAPALIGNMENT结构的Texture成员来分配具有DDSCAPS_MIPMAP|DDSCAPS_TEXTURE|DDSCAPS_FLIP设置的表面。(也就是说,在HEAPALIGNMENT结构里,Texture在Flip Target之前出现。)在这个例子里没有考虑Flip Target成员。既然没用DDSCAPS_FLIP和指定了分配的位来标志一个主交换链里的后备缓冲器,那么就可以按照Flip Target成员来指定这样的表面。可潜在地变成一个主交换链里(这些与主表面具有相同的象素格式和大小)成员的表面也按照Flip Target成员被分配。 3.10 扩展的表面能力 从DirectX 6.0开始,DirectDraw包括除在以前的版本里能看到的能力之外的表面能力,这些扩展的能力需要额外的几个新结构,特别是DDSCAPS2和DD_SURFACE_MORE结构。DDSCAPS2结构包括最初在DDSCAPS结构里发现的dwCaps成员,但是,它也包括三个新成员:dwCaps2、dwCaps3和dwCaps4。只有dwCaps2是用在DirectX 6.0的DirectDraw中,DDSCAPS2结构的最后三个成员也是相同的安排在DDSCAPSEX结构里。 3.10.1新的表面能力 当应用程序在DDSCAPS2结构的dwCaps2成员里设置适当的标志时,在DirectX 6.0里及更新版本添加给DirectDraw的新的表面能力对驱动程序来说是可见的。 应用程序仅可以在与DDSCAPS2_OVERLAY标志相连接时设置DDSCAPS2_HARDWAREDEINTERLACE标志,如果一个驱动程序在CreateSurface时间里看到这个标志设置,这意味着DirectDraw期望驱动程序需要做任何必要的事情来确保硬件视频端口帧速率与设备帧速率匹配。 DDSCAPS2_HINTDYNAMIC、DDSCAPS2_HINTSTATIC和DDSCAPS2_OPAQUE标志是应用程序在CreateSurface时间设置的暗示,它通知驱动程序应用程序打算利用表面做什么。DDSCAPS2_HINTDYNAMIC标志意味着应用程序将定期更新表面,DDSCAPS2_HINTSTATIC标志意味着将很少更新表面但是仍旧需要存取。这意味着驱动程序必须能够实现表面上的锁定,这包括隐藏的解压和压缩步骤。DDSCAPS2_OPAQUE标志意味着应用程序将从来不会为那个表面生命时间的剩余部分锁定、位块转移或者更新表面,驱动程序可以自由解压或者重新排序表面而不必解压它。 注意:驱动程序不需要设置使其可用的这些标志。当调用DdCreateSurface时,DirectDraw仅仅给驱动程序传递这些位。 你也许想使用DirectDraw的扩展堆限定特征(在“扩展的堆限定”里描述)来自动地在最优堆里放置DDSCAPS2_OPAQUE纹理,这完全取决于你。 在微软的Direct3D DDK文档里,有DDSCAPS2_HINTDYNAMIC、DDSCAPS2_HINTSTATIC和DDSCAPS2_OPAQUE标志的详细的描述。 DDSCAPS2_TEXTUREMANAGE标志与驱动程序不相关,这个标志通知Direct3D运行时,在适当时,它负责从显存的一个后备表面移动表面以能够加速3-D组织纹理。 3.10.2暴露扩展的表面能力 DDNTCORECAPS结构包括一个DDSCAPS域,驱动程序将填充这个域来表明它们支持什么类型的表面。在向应用程序报告这些caps时会返回一个稍微不同的结构,即DDCAPS。这个DDCAPS结构从驱动程序的DDNTCORECAPS和另外的结构中构造,使用DdGetDriverInfo接口查询这些结构。对DirectX 6.0来说,一个应用程序可见的DDCAPS包括一个DDSCAPS2成员,这个DDSCAPS2成员从DDNTCORECAPS结构的DDSCAPS成员和DD_MORESURFACECAPS结构的ddsCapsMore成员构成。 在驱动程序初始化时间里使用DdGetDriverInfo调用从驱动程序里查询DD_MORESURFACECAPS结构,如在ddrawint.h文件里定义的,适当的GUID应是GUID_DDMoreSurfaceCaps。 响应GUID_DDMoreSurfaceCaps查询完全是可选择的,这表示允许驱动程序做两个不同的事情: 暴露新的表面能力,驱动程序能够在显存里产生这种能力。 新的表面能力给DirectDraw传达这些作为堆的新限制的表面新能力。 第一项已在前面提到过而且每个人对此都有自己的解释。第二项更复杂,在读下一章之前,读者应该熟知VIDEOMEMORY结构的ddsCaps和ddsCapsAlt成员的重要性,在内存堆分配里有对它们的描述。 3.10.3扩展的堆限制 DD_MORESURFACES结构的大小可变,它总是有ddsCapsMore成员,但是它能够有零个或者多个ddsExtendedHeapRestrictions入口点。如果驱动程序响应GUID_DDMoreSurfaceCaps请求,它应该返回一个DD_MORESURFACECAPS结构,该结构包括如它在DD_HALINFO结构里返回的显示内存堆一样多的ddsExtendedHeapRestrictions入口点。(DirectDraw保证在驱动程序报告DD_HALINFO之后执行GUID_DDMoreSurfaceCaps请求。) 驱动程序也应该在DD_MORESURFACECAPS结构里填充一个适当的dwSize值,dwSize值按这种方法计算: DDMORESURFACECAPS.dwSize = (DWORD) (sizeof(DDMORESURFACECAPS) + (((signed int) DDHALINFO.vimData.dwNumHeaps)-1) * sizeof(DDSCAPSEX)*2); 注意到-l说明了将声明DD_MORESURFACECAPS结构有一个tagExtendedHeapRestrictions成员。 ddsCapsEx和ddsCapsExAlt成员与IDEOMEMORY结构数组dsCaps和ddsCapsAlt成员严格相似,该结构在OMEMORYINFO结构的pvmList成员里返回。IDEOMEMORYINFO结构作为DD_HALINFO结构的一个成员而被包含。任何设置在ddsCapsEx里的位意味着具有这种位设置的表面不能够放置在那个堆里,任何设置在ddsCapsExAlt成员里的位意味着表面不能够在那个堆里放置。当分配表面时,DirectDraw首先经过所有的堆,且如果它发现对一个堆来说,在VIDEOMEMORY结构里的ddsCaps成员的能力位不能匹配那个表面的DDSCAPS位,则它在那个堆里分配表面。如果这个传递没有发现这样的堆,DirectDraw就做同样的传递,但要检查ddsCapsEx域。如果这个传递未能发现任何堆,则不能够在任何堆里创建表面。 3.10.4扩展的表面描述结构 扩展的DirectDraw表面描述结构的DDSURFACEDESC2与DDSURFACEDESC结构相同,作为除了在结构的末端的指向DDSCAPS结构的指针已经用指向DDSCAPS2结构的一个指针代替。 DdCreateSurface和DdCanCreateSurface驱动程序调用的数据块每个包括指向DDSURFACEDESC结构的指针。至于DirectX 6.0数据块,这些指针可能实际上指向DDSURFACEDESC2结构,即使这些指针被定义为LPDDSURFACEDESC。如果驱动程序选择,它能够检查DDSURFACEDESC指针的dwSize成员,然后决定指针是否实际上指向一个DDSURFACEDESC2结构。如果你的驱动程序必须在比DirectX 6.0更早的版本上运行,它就必须做这个检查。 如果返回的大小是sizeof(DDSURFACEDESC2),驱动程序然后能够检查DDSCAPS2结构的dwCaps2、dwCaps3和dwCaps4成员。 3.11 压缩的纹理表面 一个表面可以包括将被用作组织纹理3-D对象的一个位图,为了减少纹理消耗的内存量,DirectDraw支持纹理表面的压缩。 注意:压缩过的纹理表面来得到新回调的支持,DirectDraw通过现存的驱动程序回调向驱动程序传递压缩的纹理表面信息。 表3.1给出了驱动程序支持的压缩纹理的五种类型。 表3.1压缩的纹理类型 FourCC Description AlphaPremultiplied? DXT1 Opaque/one-bit alpha n/a DXT2 Explicit alpha Yes DXT3 Explicit alpha No DXT4 Interpolated alpha Yes DXT5 Interpolated alpha No 如果要了解更多压缩纹理格式的信息,参见SDK平台文档的DirectDraw部分“压缩的纹理格式”。 3.11.1枚举DXT格式 在DirectX里,驱动程序有两种方法枚举象素格式。第一种方法枚举使用于纹理的格式,该方法由D3DNTHAL_GLOBALDRIVERDATA结构的lpTextureFormats成员实现。第二种方法枚举DDSCAPS_OVERLAY表面或DDSCAPS_OFFSCREENPLAIN表面用到的格式。第二种方法使用包括在DD_HALINFO结构和lpdwFourCC数组里的DDNTCORECAPS结构的dwNumFourCCCodes成员,该lpdwFourCC数组也包括在DD_HALINFO结构里。 由于DXT格式是主要作为纹理使用的,所以驱动程序仅仅通过第一种方法枚举DXT格式,无须给lpdwFourCC数组添加DXT格式。 3.11.2创建压缩的纹理表面 无论何时DirectDraw请求驱动程序创建表面,驱动程序必须决定是否它被请求创建一个压缩的纹理表面。为了做出决定,它必须检查DirectDraw以前在DDSURFACEDESC2结构里已经为正在创建的表面所设置的信息。驱动程序必须包括下面的校验步骤(对任何表面来说): 检查DD_CANCREATESURFACEDATA结构的bIsDifferentPixelFormat成员,从而决定创建的表面的象素格式是否要与主表面一样。 如果不是,在DDCAPS结构的dwFlags成员里检查DDSCAPS_TEXTURE标志。 在DDPIXELFORMAT结构的dwFlags成员里检查正在创建的表面的DDPF_FOURCC标志。 在DDPIXELFORMAT结构的dwFourCC成员里检查正在创建的表面的一个DXT代码。 检查高度和宽度(DDSURFACEDESC2的dwWidth和dwHeight),DirectDraw将设置这些值为若干个四个象素。 3.11.3使用压缩的纹理表面 仅仅当设置了DDCAPS2_COPYFOURCC标志时,DirectDraw才调用驱动程序在两个具有相同类型的表面之间进行位块转移。如果没有设置这个标志,DirectDraw HEL将执行位块转移。由于这是一种将纹理从后备表面下载到显存上的机制,所以这对后备表面到显示拷贝位块转移是最重要的。其结果是,暴露DXT纹理表面可有效地要求驱动程序支持DDCAPS2_COPYFOURCC标志。 DDCAPS2_COPYFOURCC标志有一些另外的含义,驱动程序必须能够用至少以下的这些属性在FourCC格式之间执行位块转移: 源和目标格式是相同的FourCC。 源和目标表面不是同样的表面。 源和目标表面大小一样。 源和目标矩形都是整个的表面(也就是说,没有延伸也没有子矩形)。 两个表面都在显存里。 驱动程序必须能够为每个在显存里支持的FourCC格式执行这个位块转移。 注意:DirectShow使用DDCAPS2_COPYFOURCC标志来加速一些视频功能,所以这个标志必须暗示能够拷贝所有的FOURCC格式。 如果位块转移操作要求压缩一个DXT格式,DirectDraw HEL就总能执行这个位块转移,这意味着DirectDraw从来没有请求驱动程序为以下执行位块转移: 目标表面有一个DXT格式。 源和目标表面的格式不相同。 DirectDraw的DDCAPS_CANBLTSYSTEM能力位语义暗示显示驱动程序为所有的后备表面到显存的位块转移调用。因此,可能从DXT表面到非DXT表面为所有的后备表面到显存的位块转移调用驱动程序,在这种状况下的唯一要求是如果驱动程序不能够执行解压,就返回DDHAL_DRIVER_NOTHANDLED。这将引起DirectDraw传播一个DDERR_UNSUPPORTED错误代码给应用程序,在驱动程序里为后备表面到显存的位块转移实现解压是可接受的,但不是必须的。 DirectDraw显示内存分配例程对象素格式不考虑。举例来说,当它的参数输入时,HeapVidMemAllocAligned需要一些字节。同样的,DDHAL_PLEASEALLOC_BLOCKSIZE表示DD_SURFACE_GLOBAL结构的dwBlockSizeX和dwBlockSizeY成员分别是一些字节和行数。因此,如果驱动程序使用这些机制中的任何一种来通过DirectDraw分配器分配显存,驱动程序必须能够以一个DXT表面自己的字节枚举内存消耗。 当应用程序在一个压缩的表面上调用它的lock或GetSurfaceDesc方法时,驱动程序必须在DDSURFACEDESC2结构的dwFlags成员里设置DDSD_LINEARSIZE标志。此外,驱动程序必须设置被分配的字节数以便在相同结构的dwLinearSize成员里包括压缩的表面数据。(dwLinearSize成员存留在具有lPitch成员的一个联盟里,这样这些成员互相是唯一所选的,如DDSD_LINEARSIZE和DDSD_PITCH标志一样。) 你的硬件或驱动程序能够在你选择的(典型地是一个额外的硬件效率布局的重新排序)任何格式里转换和存储压缩的纹理。然而,硬件或驱动程序必须能够转换压缩的纹理返回为它最初的DXT代码格式。(无论何时DirectDraw需要它----也就是说,无论何时当应用程序调用lock方法。) 3.12 运动补偿 如果两个连续的视频数据帧大致显示了相同的图片,仅仅是做了水平或垂直上的转换,那么只需通过转换第一个帧,然后描述除到达第二个帧时的转变之外的其他改变,就可以取得一个巨大的压缩增益。这就是动画补偿所做的。 动画补偿是MPEG解码处理的最后阶段,许多显示设备为硬件里的这个执行一些支持级。除了动画补偿外,一些设备执行相反的离散余弦传输(IDCT)和另外的硬件功能,软件MPEG译码器能够使用这些硬件功能来加速译码处理。动画补偿设备驱动程序接口足够灵活以处理这些设备。 软件MPEG译码器的输入数据被很好地定义了,也就是说,如果译码器为MPEG-2而设计,将以MPEG-2格式输入。译码器的输出也被很好的定义---许多格式的未被压缩的帧。然而,却没有对在软件译码器和显示设备之间的临时格式进行良好的定义;许多设备需要它们自己的属性数据格式。因此,动画补偿设备驱动程序接口是灵活可变的,且临时格式被描述为GUID。显示驱动程序报告描述它支持能力的GUID,软件译码器选择最好地匹配了它的需求的GUID。 为了使用动画补偿功能,驱动程序必须做下面的工作: 执行DdGetDriverInfo功能并设置DD_HALINFO结构的DdGetDriverInfo成员以便当DrvGetDriverInfo调用时指向这个功能,驱动程序的DdGetDriverInfo功能必须分析GUID_MotionCompCallbacks GUID。 当用GUID_MotionCompCallbacks GUID调用DdGetDriverInfo时,用适当的驱动程序回调和标志设置填充DD_MOTIONCOMPCALLBACKS结构。驱动程序然后拷贝这个被初始化过的结构到DirectDraw-allocated缓冲器,这里DD_GETDRIVERINFODATA结构的lpvData成员指向这个缓冲器,并返回在dwActualSize里写入到缓冲器里的字节数目。 3.13 返回DirectDraw和Direct3D的值 负值表示了错误,且错误不能够被结合。在下一页有一些值,这些值由在Windows 2000 DDK里描述的DirectDraw和Direct3D功能返回。为了获得每个功能能够返回的错误代码的一个列表,参见参考部分里的功能描述。如要了解一个完全的错误代码列表和返回的数值,请参考DirectDraw头文件ddraw.h。 DirectDraw驱动程序必须返回两个返回码中的一个:DDHAL_DRIVER_HANDLED或者DDHAL_DRIVER_NOTHANDLED。如果驱动程序返回DDHAL_DRIVER_HANDLED,它也必须返回DD_OK,或者在ddraw.h中列出的错误码之一。 DD_OK 请求完全成功。 DDHAL_DRIVER_HANDLED 驱动程序已经执行了操作并为此操作返回一个合法的返回码。返回码在传给驱动程序回调的结构的成员ddrval中。如果是DD_OK,DirectDraw或Direct3D继续执行。否则,DirectDraw或Direct3D返回由驱动程序提供的错误码,并放弃执行。 DDHAL_DRIVER_NOTHANDLED 驱动程序对请求的操作不处理。如果驱动程序被要求实现一个特别的回调,DirectDraw或者Direct3D报告一个错误条件。否则,DirectDraw或者Direct3D通过执行设备独立的实现来处理此操作,就好像驱动程序的回调没有定义。DirectDraw或者Direct3D忽略任何在回调的结构参数的ddrval成员中返回的值。 DDHAL_DRIVER_NOCKEYHW 显示驱动程序不能处理调用,因为它用尽了颜色键硬件资源。 DDERR_UNSUPPORTED 操作不被支持。 DXERR_GENERIC 有一个未定义的错误条件。 DXERR_OUTOFCAPS 请求的操作需要的硬件已经被分配。 DXERR_UNSUPPORTED 操作不被支持。 第4章Direct3D DDI 微软Direct3D?设备驱动程序接口(DDI)是一个图形接口,它允许厂商为Direct3D提供硬件加速。这个接口很灵活,厂商可以基于硬件能力来提供Direct3D加速。驱动程序的编写者把实现Direct3D设备驱动程序接口作为显示驱动程序的完整部分。 这一章描述了Direct3D的体系结构和接口,并对Direct3D驱动程序的编写者提供了实现上的指导。它假设编写者熟悉Direct3D和微软DirectDraw APIs,对于Windows NT/Windows 2000显示驱动程序模型,包括DirectDraw设备驱动程序接口编程有牢固的掌握。 所有的为Windows 2000编写的Direct3D驱动程序必须符合微软Direct X 7.0 Direct3D驱动程序模型。Direct X 6.0 驱动程序模型在Windows 2000正式版中不再被支持。(为了适应升级的需要,Windows 2000 Beta3仍然支持DirectX 6.0) 4.1 Direct3D DDI 头文件、示例代码和参考 Windows NT?/Windows 2000?的驱动程序编写者开发微软Direct3D驱动程序应该引用以下头文件: d3dnthal.h 包含了由驱动程序实现的回调原型和为驱动程序层次结构定义。枚举类型D3DNTHAL_DP2OPERATION就定义在这个文件中。这个头文件被包含在winddi.h中,winddi.h包括了所有Windows NT/Windows 2000的显示驱动程序。 d3dtypes.h 包含了应用程序和驱动程序都要用到的Direct3D类型定义。除了D3DNTHAL_DP2OPERATION,所有的Direct3D枚举类型都定义在此头文件中。 d3dcaps.h 包含了描述Direct3D驱动程序各方面的结构和定义。 dx95type.h 允许编写者写可在Windows NT/Windows 2000和Windows 95/98间移植的代码。 ddrawint.h 这个头文件用来开发显示驱动程序的DirectDraw部分。它包含在winddi.h中。 所有这些头文件都和Windows 2000 DDK一起发布。Windows 2000 DDK也为Direct3D驱动程序提供了示例代码,它位于permedia2 视频显示器目录中。 Direct3D DDI函数、回调和结构的参考可在Windows 2000 DDK Graphics Driver Reference找到。 Direct3D接口和SDK相关的方面的参考在平台SDK文档中。 对于缺乏图形编程经验的程序员,可以参考Foley、van Dam、Feiner和Hughes所著的《计算机图形:原理和实践》。 4.2跨平台Direct3D驱动程序开发 因为命名的差异和一些成员类型的改变,编译时,Windows NT/Windows 2000和Windows 95/98 Direct3D DDI类型并不直接兼容。但是逻辑上所有的成员用于同一目的。 随同Windows 2000 DDK提供了dx5type.h,它用于辅助编写能在Windows NT/Windows 2000和Windows 95/98之间移植的代码。它包含一整套类型和宏定义,用以映射在Windows NT/Windows 2000和Windows 95/98见的命名差异,从而使一些公共代码对两者都能使用。 4.3 Direct3D驱动程序的回调函数 以下几节列出了包含Windows NT/Windows 2000 Direct3D DDI的回调函数。有一些回调必须的,另外一些是可选的。是否包括它们取决于硬件的性能。回调的名字是驱动程序的编写者决定的伪名字。 Direct3D DDI回调的原型如下: typedef DWORD (APIENTRY *LPD3DNTHAL_MYFUNCTIONCB)(LPD3DNTHAL_MYFUNCTIONDATA); 这里, LPD3DNTHAL_MYFUNCTIONCB指向一个驱动程序实现的回调,称为MyFunction。所有回调的名字都是假设的,由驱动程序的编写者来决定。 LPD3DNTHAL_MYFUNCTIONDATA是一个指向D3DNTHAL_MYFUNCTIONDATA结构的指针,它被传送给回调函数。回调参数结构的特点如下: 每一个结构的第一个成员dwContext是设备环境句柄,它描述了回调操作时的三维环境。唯一的例外是D3DNTHAL_CONTEXTCREATEDATA结构。 每一个结构的最后一个成员是ddrval。这个成员用来将回调的返回值传给Direct3D,从而可以返回给调用的应用程序。 决定如何初始化Direct3D回调参见Direct3D驱动程序初始化。 4.3.1请求的Direct3D驱动程序回调函数 下表列出了必须在Direct3D 驱动程序中实现的Direct3D回调函数。 请求的回调 描述  D3dContextCreate 环境的创建和注销  D3dContextDestroy   D3dCreateSurfaceEx 在纹理句柄和表面之间生成一个联系  D3dDestroyDDLocal 注销所有以前由D3dCreateSurfaceEx生成的Direct3D表面,它属于同一个给定的本地DirectDraw对象  D3dDrawPrimitives2 着色原语并返回对Direct3D的更新状态  D3dGetDriverState 返回DirectDraw和Direct3D运行时的状态信息  D3dValidateTextureStageState 执行纹理阶段状态刷新,所有支持纹理的驱动程序都要请求   为了支持Direct3D,驱动程序编写者最少必须要支持DirectDraw。这个驱动程序也必须实现某些DirectDraw DDI函数和回调。那些有关对Direct3D的支持列在下表中。 DDI函数 描述  DrvGetDirectDrawInfo 这个函数提取了图形硬件的性能。它位于初始化函数中指示它支持Direct3D  DdGetDriverInfo 运行时用GUID查询这个回调以获取驱动程序的额外信息。有几个GUID特别属于驱动程序的Direct3D支持。   DirectDraw函数和回调的实现细节在DirectDraw DDI文档中讨论。 4.3.2操作码处理 为了支持DirectX 7.0 Direct3D驱动程序模型,必须使驱动程序对D3dDrawPrimitives2中的许多新的操作码做出反应。这些操作码中,一些代替旧的请求的回调和有条件请求的回调,而另外一些提供了新的功能。最重要的新操作码总结在下表中,从被代替的回调开始。 操作码 条件/描述  D3DDP2OP_SETRENDERTARGET 总是必要的。在当前环境中映射一个新的着色目标表面和深度缓冲区。替换了D3dSetRenderTarget。  D3DDP2OP_CLEAR 总是必要的。用来清除环境的着色目标。替换了D3dClear2。也用来清除硬件模板缓冲区以及深度缓冲区,它通常不能被深度填充位块完全清除。  D3DDP2OP_SETPALETTE 用来在调色板句柄和表面句柄间映射一个联系,并描述调色板的特性。仅在支持调色版纹理的驱动程序中需要,否则应置为NO_OP。  D3DDP2OP_UPDATEPALETTE 用来对支持调色版纹理的驱动程序更改调色版纹理。否则应置为NO_OP。  D3DDP2OP_TEXBLT 描述了从源纹理到目标纹理的一个BLT操作。   其他有用的操作码在参考部分的D3DNTHAL_DP2OPERATION描述中总结。 4.3.3对Direct3D驱动程序回调的返回码 对所有驱动程序回调的返回值必须是以下值之一: D3DHAL_CONTEXT_BAD 说明传送的环境不合法。 D3DHAL_OUTOFCONTEXTS 说明在这个处理中在没有更多的环境。 DDHAL_DRIVER_HANDLED 驱动程序已经执行了操作并为那个操作返回一个合法的返回码,此操作是将结构的成员ddrval传送给驱动程序的回调。如果返回码是DD_OK,Direct3D继续处理此函数。否则,Direct3D返回由驱动程序提供的错误码并放弃此函数的处理。 DDHAL_DRIVER_NOTHANDLED 驱动程序对于请求的操作不做处理。如果驱动程序被要求实现一个特别的回调,Direct3D报告一个错误条件。否则,Direct3D处理这个操作就好像驱动程序回调没有被定义一样,它执行Direct3D的设备独立的实现。通常Direct3D忽略任何由回调的参数结构成员ddrval返回的值。 4.4 Direct3D驱动程序初始化 当驱动程序的函数DrvGetDriectDrawInfo被DirectDraw运行时所调用以便初始化DirectDraw支持,驱动程序必须做以下的事以指示它的Direct3D能力: 在结构DD_HALINFO的成员ddCaps.dwCaps设置DDCAPS_3D标志以指示此驱动程序的硬件有三维加速功能。 在结构DD_HALINFO的成员ddCaps.ddsCaps设置以下任何DDSCAPS_Xxx标志以描述此驱动程序的视频存储器的三维能力: DDSCAPS_3DDEVICE 指示驱动程序的表面能被用来作为三维着色的目标。 DDSCAPS_TEXTURE 指示驱动程序的表面能被用来做三维纹理映射。 DDSCAPS_ZBUFFER 指示驱动程序的表面能被用来做Z缓冲区。 设置结构DD_HALINFO的成员GetDriverInfo以指向驱动程序的回调函数DdGetDriverInfo。驱动程序必须设置DDHALINFO_GETDRIVERINFOSET标志,此标志在结构DD_HALINFO的成员dwFlags中。以指示它已实现了DdGetDriverInfo回调功能。 申请和初始化结构D3DNTHAL_CALLBACKS的成员并且将此结构返回到结构DD_HALINFO的成员lpD3DNTHALCallbacks中。 申请和初始化结构D3DNTHAL_GLOBALDRIVERDATA的成员并且将此结构返回到结构DD_HALINFO的成员lpD3DGlobalDriverData中。 为了指示能和DriectX 7.0协调工作,驱动程序应当作以下事情: 在结构D3DNTHALDEVICEDESC_V1的devCaps域包含D3DDEVCAPS_DRAWPRIMITIVES2EX标志,这在Direct3D驱动程序初始化时被报告。 通过设置结构DD_MISCELLANEOUS2CALLBACKS的GetDriverState,CreateSurfaceEx和DestroyDDLocal域对回调DdGetDriverInfo中的GUID_Miscellaneous2Callbacks响应。这些都是指向Driect3D驱动程序适当回调的指针,并且要将域dwFlags的DDHAL_MISC2CB32_CREATESURFACEEX,DDHAL_MISC2CB32_GETDRIVERSTATE和DDHAL_MISC2CB32_DESTROYDDLOCAL位分别置位。 在DrvGetDirectDrawInfo返回后,GDI为不同的GUID调用驱动程序的回调DdGetDriverInfo以完成驱动程序的初始化。DdGetDriverInfo必须对以下的GUID响应以支持Direct3D: GUID_D3Dcallbacks3 驱动程序应申请和初始化结构D3DNTHAL_CALLBACKS3的成员,并将此结构返回到结构DD_GETDRIVERINFODATA的成员lpvData中。 GUID_Miscellaneous2Callbacks 驱动程序应申请和初始化结构DD_MISCELLANEOUS2CALLBACKS的成员, 并将此结构返回到lpvData中。 GUID_D3DextendedCaps 驱动程序应申请和初始化结构D3DNTHAL_D3DEXTENDEDCAPS中适当的成员, 并将此结构返回到lpvData中。 GUID_ZpixelFormats 驱动程序应为每个它所支持的Z缓冲区格式申请和初始化结构DDPIXELFORMAT中适当的成员,并将此结构返回到lpvData中。如果此驱动程序在它实现D3drawPrimitives2时支持D3DDP2OP_CLEAR操作码,它就必须响应这个GUID。 GUID_D3DparseUnknownCommandCallback 驱动程序应存储指向Direct3D运行时的回调函数D3DparseUnknownCommand的指针。这个指针被传递给驱动程序的lpvData。驱动程序的回调D3drawPrimitives2将调用D3DparseUnknownCommand以解析驱动程序不能识别的命令。 参考第三章的DirectDraw驱动程序初始化以更详细的了解如何初始化DirectDraw驱动程序。 4.5 Direct3D环境管理 一个环境为应用建立的Direct3D硬件抽象层(HAL)设备封装了所有的状态信息;也就是此环境描述了驱动程序应当如何“画”。状态包括表面着色、深层表面、阴影信息、纹理信息等信息。 Direct3D驱动程序负责建立和管理它自己的着色环境。 4.5.1 建立环境 驱动程序必须建立和初始化设备相关的环境以封装所有它需要执行着色的信息。状态并不在多个环境中所共享,因此驱动程序必须为每个它所生成的环境维护完整的状态信息。 为建立环境,驱动程序应当: 申请设备相关的环境并以零初始化它。 参见DDK在线文档的D3DcontextCreate获得更详细的信息。当应用建立Direct3D HAL设备时D3DcontextCreate被调用。 驱动程序必须能够在已经建立的环境中引用所有的由D3dCreateSurfaceEx建立的纹理句柄,这样当调用D3dContextDestroy时,它能在这个环境中清除所有设备相关的纹理数据。 4.5.2 删除环境 当应用程序请求注销Direct3D HAL设备时,Direct3D调用D3dContextDestroy。驱动程序应当释放它对此环境申请的所有资源。 4.5.3 在环境中维护状态 当驱动程序的回调函数D3DrawPrimitives2被调用时,驱动程序更新它所联系的环境的内部状态。此函数同时必须返回更新过的环境的公共状态给Direct3D。参见DDK在线文档的D3DrawPrimitives2获得更详细的处理和更新状态的信息。 4.5.4 Direct3D表面处理总览 DirectX 7.0 DDI提出了一个模型,Direct3D运行时组件在把命令传递给驱动程序前解析尽可能少的命令流。命令流应是格式化的,这样理论上在将来它能够被硬件直接理解。 对这些目标的一个重要改变是所有表面相关的数据从Direct3D/DiretcDraw拥有的中间结构移到了由驱动程序拥有并更新和格式化的结构中。 处理内嵌的命令流涉及到表面。在这些高频率的操作中,驱动程序能很容易的从句柄察看它自己的表面请求,而不必借助于潜在的、代价高的锁住表面的操作如EngLockDirectDrawSurface。 分派这些句柄的机制是一个新的驱动程序入口点称为D3dCreateSurfaceEx。在现存的DdCanCreateSurface和DdCreateSurface调用以及视频存储器的地址和其句柄被赋给一个表面之后,这个入口点被直接调用。在调用DdCreateSurfaceEx时,驱动程序可以从DiretcDraw的运行时的表面结构中拷贝所有的相关信息倒它自己的表面结构中。表面数据如尺寸、各适和fpVidMem经常是被拷贝的对象。 句柄对于每个设备和每个进程都是独一无二的,这是由运行时所保证的。但对于每个环境句柄并不保证是独一无二的;这一点将在下一节做更详细的讨论。 对于DestroySurfaceEx调用没有响应,所以驱动程序一方的表面结构在DdDestroySurface运行时被注销。 4.5.5 用D3dCreateSurfaceEx建立驱动程序方的表面结构 DiretcDraw运行时在它调用DdCreateSurface入口点后会调用驱动程序的D3dCreateSurfaceEx入口点并会为此表面申请内存。它仅为那些标记为DDSCAPS_TEXTURE,DDSCAPS_EXECUTEBUFFER,DDSCAPS_3DDEVICE或者DDSCAPS_ZBUFFER的表面调用D3dCreateSurfaceEx。 在调用D3dCreateSurfaceEx前,运行时会为表面分配一个整型值作为句柄。这个值存储在结构DDRAWI_DDSURFACE_MORE的成员dwSurfaceHandle中。 这些整型值从一开始,并尽量小(0对于表面句柄是不合法的)。目的是使驱动程序能保持指向它自己表面的一个指针数组。当驱动程序收到超过数组大小的句柄时(即D3dCreateSurfaceEx被调用)它能重申请数组的大小并继续运行。Direct3D在D3dCreateSurfaceEx没有生成句柄以前并不将句柄值传递给驱动程序。但是,驱动程序应当足够健壮以处理超范围的值或引用已经释放的句柄(即对某个句柄调用DdDestroySurface)。注意因为0是非法值,在句柄表中的0项能被用作别的目的。Permedia2例子驱动程序使用0项存储当前数组的长度。 4.5.6 为什么驱动程序方的结构不能是一个指向表面的指针? 你也许试图在你的驱动程序方的表面结构中使用一个指向DiretcDraw表面结构的指针。这将会在Windows 2000中失败,因为DiretcDraw的内核数据结构的访问通过将这些结构和用户模式及驱动程序隔离开的管理配置来作为中介。这就是为什么EngLockDirectDrawSurface存在的原因。它提供了一个指向结构的指针,这个指针在很短的时间内有效,直到EngUnlockDirectDrawSurface被调用。在这个锁/解锁操作以外,此结构并不保证处在相同的位置。此外,这个锁/解锁操作对于性能并不一定是构成阻碍。如果驱动程序保留它自己的表面结构的拷贝,锁就不需要。不常用的调用如D3dCreateSurfaceEx使驱动程序方的表面结构的数据被更新。结果使经常用的调用如D3DrawPrimitives2需要更少的代码来执行。 4.5.7 D3dCreateSurfaceEx和后援表面 D3dCreateSurfaceEx也为后援表面被调用。这允许驱动程序为表面申请驱动程序方的结构,从而响应D3DDP2OP_TEXBLT令牌。 驱动程序不会由于它不支持像素格式,当为后援表面格式调用D3dCreateSurfaceEx时失败,这是很重要的。这样的表面也许被建立来和软件光栅使用。驱动程序应当简单的忽略它不支持的后援表面。(它能建立驱动程序方的结构,但相应的句柄却不会传送给驱动程序)驱动程序必须要小心,因为对这些表面调用D3dCreateSurfaceEx失败会引起失效码传播到应用程序。因此驱动程序能潜在的影响一个应用程序。通过运行DirectX 7.0 SDK中的程序ddtest.exe,能测试驱动程序对此种情况的响应。运行此程序,建立一个驱动程序不支持但是DirectDraw仿真层却支持的后援表面纹理格式。 4.5.8 使用D3dCreateSurfaceEx要考虑的其他问题 某些情况能引起为一个已经注销的表面调用D3dCreateSurfaceEx。驱动程序通过忽略任何为fpVidMem是0的视频存储器表面的D3dCreateSurfaceEx调用来处理这种情况。 4.5.8.1 D3dCreateSurfaceEx和复杂表面 正如在DirectDraw文档中所解释的那样,复杂表面的建立引起一个表面数组被传给DdCreateSurface。但是,即使在很复杂的情况下,仅有一个指向根表面的指针被传给D3dCreateSurfaceEx。驱动程序必须从根表面遍历链表,建立所有表面的驱动程序一方的拷贝。如果驱动程序试图处理如立方图、mipmap等的建立,这会是个技巧型的操作。 请温习第三章的DirectDraw驱动程序基础并参见D3dCreateSurfaceEx参考中的示例代码。 4.5.8.2 D3dCreateSurfaceEx和MipMap 在一个mipmap的每一级都联系有不同的句柄值,但这些句柄不一定要连续。Direct3D被设计为仅将顶级表面的句柄作为参数传送给SetTexture函数,然后当前级的细节被另外的着色状态所说明。最自然的方法是构造驱动程序方的结构以代替整个mipmap。 4.5.8.3 D3dCreateSurfaceEx句柄和Flip DirectDraw的表面结构被设计成代表概念表面,并不必须说明视频存储器的位置。这个抽象的主要用法在于一个主要的flipping链,应用程序使用一个恒定的表面对象代表后台缓冲区,即使由于DdFlip的操作结果使此缓冲区在视频存储器中移动。 DdFlip方法接受一个表面环并且绕着环旋转视频存储器指针。在有两个表面对象的情况下,处理被简化为交换它们的视频存储器的指针。此外,DirectDraw旋转每个表面相联系的D3dCreateSurfaceEx句柄和驱动程序拥有的dwReserved域的内容。这样的行为对DirectX 7.0驱动程序带来一些有趣的后果并有效的排除了将指向DirectDraw的表面结构的指针嵌入到驱动程序自己的表面结构中。 考虑两个表面对象A和B,句柄分别为Ha和Hb,fpVidMem是Fa、Fb。假设应用程序正使用表面结构A来引用一个flipping链的的后台缓冲区,在DdFlip操作时,句柄和fpVidMem被交换,这样A有Hb、Fb,B有Ha、Fa。应用程序现在想对后台缓冲区绘画,应用认为在Fb处表面A代表了视频存储器。一个绘图命令被送给驱动程序,然后它查询和表面相联系的句柄(现在是Hb而不是Ha)。 如果驱动程序仅仅存储指向DirectDraw的表面结构的指针,将会怎样?驱动程序将查到Hb,然后沿着指向表面B的指针,它现在有一个fpVidMem为Fa的值。绘画将从Fa处的视频存储器开始。、这并不是应用所期望的。另外一方面,如果驱动程序存储表面数据在它自己的结构中而不是指针指向DirectDraw的表面结构,Hb会解析出Fb,绘画将画在正确的表面上。后者正是当前DDI实现的方式。 4.5.8.4 D3dCreateSurfaceEx句柄和DirectDraw DDI 句柄没有完全隔离DirectX 7.0和DirectDraw管理的DDRAWI_DDSURFACE结构。在DirectDraw DDI(DdBlt、DdFlip等)中,驱动程序仍然传递表面结构的指针并仍然需要使用这些结构。 4.5.8.5 当恢复复杂的Flipping链时要考虑的问题 当一个复杂的主表面建立时,它不一定有一个依附的Z缓冲区。当表面被恢复,应用程序可能增加一个相依附的Z缓冲区。当在D3dCreateSurfaceEx中遍历表面链表时,应注意这些问题。Permedia2例子驱动程序提供了一种典型的技术,就是当为一个表面调用D3dCreateSurfaceEx时,标记此表面的dwReserved域。驱动程序标记此表面仅仅在fpVidMem不为0时。这是因为由于应用程序恢复了一个主表面,此表面有一个后台缓冲区和一个依附的Z缓冲区,但Z缓冲区还没有被恢复,从而fpVidMem也许是0。在以后,应用程序将恢复Z缓冲区,然后驱动程序才能标记它。注意如果应用程序在恢复主链前恢复Z缓冲区,驱动程序也许接收已经标记的Z缓冲区,并当调用D3dCreateSurfaceEx时将其依附于后台缓冲区。 4.5.8.6 表面句柄、丢失表面和环境 丢失表面的概念在DirectDraw SDK文档中介绍。丢失表面在DirectX 7.0 DDI模型中有一些涵义。 运行时的表面对象的生命期比驱动程序的表面对象要长。在几种情况下,最明显的模式改变导致表面丢失。这意味着驱动程序的表面对象在调用DdDestroySurface时被注销,但是运行时的表面对象仅仅被置于一个挂起状态,称为“丢失”。以后,运行时对象能被恢复,这对应着驱动程序级的DdCreateSurface调用。 一些表面必须被引用到一个环境中(如着色目标、Z缓冲区、纹理),典型情况下是通过存储驱动程序环境中那些表面的句柄。这些表面也许已经丢失,就驱动程序而言即为注销。环境也许有已经过时的句柄指向注销掉的表面。运行时保证在这种状态下没有着色命令被传给环境,但是仍有一个问题,即在着色能继续前,怎样联系环境和恢复的表面。运行时保证丢失表面的句柄不会改变。这反过来保证了如果环境保持表面的句柄(着色目标、Z缓冲区、纹理),它就不需要担心被这些句柄引用的表面对象已经被注销。这些表面将重新建立(恢复)成同一个句柄值,从而在这个环境中着色能继续。 一些驱动程序的编写者也许想优化环境的状态,通过存储表面数据在环境中而不是对每个D3dDrawPrimitives2调用做重复引用句柄的工作。由于同执行一个D3dDrawPrimitives2批处理的代价相比,重复引用的代价是很小的,这种优化并不被鼓励。但是如果真的想这样做,你必须清楚当表面恢复后,它也许在显存中的位置已经移动了。这意味着尽管句柄相同,但是并不能保证恢复的表面的fpVidMem指针和它建立时的一样。在这种方式下被优化的环境有过时的显存指针,没有已经移动的表面的信息。 处理这种情况是相当有技巧性的,但有几种方法是可能的。一种方法是驱动程序标记任何联系到环境的表面(着色目标、Z缓冲区、纹理),然后在D3dCreateSurfaceEx中,它能搜索任何引用此表面的环境并更新环境。 并不推荐表面保持环境的指针,因为表面也许和多个环境相联系。 4.6 Direct3D纹理管理 尽管纹理支持是可选的,大多数驱动程序能支持它。支持纹理映射的驱动程序必须对所有Direct3D DDI中纹理相关的操作码做出响应。 驱动程序也必须用回调D3dValidateTextureStageState验证纹理阶段状态。 4.6.1 多纹理 Direct3D驱动程序能支持同时使用多个纹理。这种多纹理支持在驱动程序使用纹理阶段状态类型(D3DTEXTURESTAGESTATETYPE)时可用。这些允许纹理的多个属性被定义并和定点扩展结合,它们说明了独立的一套纹理坐标数据。 对Direct3D驱动程序增加多纹理支持需要设置正确的性能位(Caps),实现纹理混合并实现D3dValidateTextureStageState。 为了和Direct X 6.0和后续版本兼容,驱动程序必须适当的解析八个纹理坐标系列,即使此设备仅仅能反复使用在dwFVFCaps中定义的坐标的数目。驱动程序使用D3DTSS_TEXCOORDINDEX(s)来得到正确的纹理坐标。 Flexible vertex formats(FVFs)允许多纹理,因为它允许在一个定点结构中传送多个纹理坐标。多纹理然后能被混合在一起。 纹理处理不再由Direct3D驱动程序产生,相反它们被Direct3D运行时产生。纹理高速缓冲区管理完全由Direct3D运行时完成,所以对于驱动程序,纹理似乎总是从应用程序自身而来。所有的纹理状态在D3dDrawPrimitives2命令流中被送给驱动程序。 除了多纹理,混合和纹理过滤的方法业已被精华,使一个更清楚和良好定义的混合机制成为可能。你必须理解这些混合和纹理过滤怎样工作。由于这个原因,大力推荐你温习DirectX SDK文档而不只是这里讲的内容。 纹理阶段 纹理阶段指示在纹理管道中纹理的位置。最高的、非NULL的纹理的位置最靠近帧缓冲区。每个阶段是一个纹理混合单位,它执行结合纹理和多边形的操作,如图4.1所示。 图4.1 单纹理阶段 图4.1说明了单纹理阶段。当前的纹理进入此阶段并和另一个纹理混合,产生的结果传送到纹理管道中的下一个阶段。(如果是最后的阶段,则是帧缓冲区) 有八个纹理阶段,标记为0到7,0离帧缓冲区最远,并对应着色状态纹理句柄D3DRENDERSTATE_TEXTUREHANDLE。驱动程序必须处理八个纹理坐标,即使硬件并不支持那么多。 在多纹理着色中,小标号的纹理阶段离帧缓冲区远。挑出和过滤最小的纹理阶段以得到一个texel。一个混合操作发生在纹理管道中直到帧缓冲区。 例如,如果两个纹理Texture0和Texture1混合到一起,结果texel进入光栅化的管道,就好像一个单纹理将使用以前的纹理。对于三个纹理,Texture0和Texture1先混合,然后结果texel按照一些(可编程)权值与Texture2混合。这意味着Texture0不能直接影响Texture2;它仅能通过代理这样做,即和Texture1混合。第二个例子在图4.2中说明。 图4.2中显示了一个三阶段纹理管道。每个纹理阶段只引入一个纹理到管道中。像素管道是分离的,并在多纹理操作以后到来。这可能包括雾化应用和帧缓冲区alpha混合。 图4.2 三阶段纹理管道 命令流 在驱动程序层,指令来自于对D3dDrawPrimitives2的调用。输入结构D3DNTHAL_DRAWPRIMITIVES2DATA包含一个指针指向命令缓冲区。这是一个D3DNTHAL_DP2COMMAND结构序列,每个结构包含bCommand成员,它说明了在缓冲区中后续数据的类型。这个说明来自于D3DNTHAL_DP2OPERATION枚举类型的形式,如D3DDP2OP_INDEXEDDTRIANGLESTRIP,或者设置纹理状态的情况D3DDP2OP_TEXTURESTAGESTATE。 例如,如果操作码是D3DDP2OP_TEXTURESTAGESTATE,wVertexCount的值是7,那么在下一个D3DNTHAL_DP2COMMAND指令到达前,将会跟随7个D3DDP2OP_TEXTURESTAGESTATE结构。为将这个例子举得完整,D3DDP2OP_TEXTURESTAGESTATE结构包含一个dwStage成员,它说明哪个纹理混合管道的阶段需要让一个纹理状态改变。D3DDP2OP_TEXTURESTAGESTATE的成员TSState说明哪个D3DDP2OP_TEXTURESTAGESTATE的枚举类型状态要被设置,成员dwValue包含了要设置的状态的值。 这个过程对所有的着色状态或者任何别的指令类型都是相同的。如果D3DNTHAL_DP2COMMAND结构的成员bCommand是D3DDP2OP_RENDERSTATE,那么接着的结构是D3DNTHAL_DP2RENDERSTATE,那个结构中的信息用来设置相应的着色状态。 每个着色状态值没有用布尔值着色状态来控制坐标,而是一系列的由D3DWRAP_U和D3DWRAP_V组成的标志。这个变化是为了和更高维度的纹理相兼容。 其他属于多纹理实现的有用的信息能在DirectX SDK文档中的覆盖混合平衡、颜色操作和alpha操作部分找到。更多的兼容DirectX 6.0和后续版本的纹理阶段状态类型,参见D3DTEXTUREOP,D3DTEXTUREMAGFILTER,D3DTEXTUREMINFILTER和D3DTEXTUREMIPFILTER枚举类型。 这一节剩下部分被分成以下主题: ·纹理定位和过滤操作 ·纹理阶段操作 ·纹理阶段参数 ·多纹理验证 纹理定位和过滤操作 在Direct3D中,纹理定位、过滤和混合操作由一个称为纹理阶段的独立的逻辑单元来执行。在这里描述纹理定位和过滤操作,是因为它们组成了一个逻辑上成组独立的混合操作。有关纹理操作更进一步的信息参见D3DTEXTUREOP枚举类型。 尽管定位和采样操作在DirectX 6.0及后续版本中与混合操作连在一起,在以后的DirectX版本中他们可能独立于混合操作。以下的纹理阶段状态被用来为纹理管道中的每个阶段设置纹理定位和过滤操作: D3DTSS_MAGFILTER 定义了当纹理被放大时(即当一个texel被展开到多个着色表面像素上),对纹理采样的过滤类型。对纹理放大能使用的过滤器是D3DTEXTUREMAGFILTER。 D3DTSS_MINFILTER 定义了当纹理被缩小时(即当一个texel被映射到少于一个屏幕像素上),对纹理采样的过滤类型。对纹理缩小能使用的过滤器是D3DTEXTUREMINFILTER。 D3DTSS_MIPFILTER 定义了在mipmap层间对纹理采样的过滤类型。能使用的过滤器是D3DTEXTUREMIPFILTER。 D3DTSS_MIPLEVEL 当硬件不能设置mip级时,允许应用程序设置。当硬件决定mip级时,它被重载。 D3DTSS_MIPMAPLODBIAS 这个D3DVALUE说明了mipmap级的详细斜线。这个斜线影响miomap级的计算,允许所期望的或多或少的模糊纹理。单位在mip级中。 D3DTSS_MAXMIPLEVEL 这个DWORD类型说明了最大可使用的mipmap级。它指示这个纹理不应当对比这个值大的miomap级进行采样。因此最大维度是2^MAXMAPLEVEL,0说明没有限制。 D3DTSS_ANISOTROPY 这个DWORD类型说明各向异性过滤的比例限制。它说明在纹理采集中各向异性过滤最大方向的比例。 D3DTSS_TEXCOORDINDEX 这个DWORD类型说明纹理坐标集的索引。它说明定位单元应该采集的起始坐标。这些坐标按数字顺序列在新来的FVF定点数据中,0是标准的DirectX坐标集,1是第二个纹理坐标集,等等。这就允许纹理共享纹理坐标集。 注意 为了和Direct3D兼容,驱动程序必须适当解析八个纹理坐标集,即使硬件设备仅仅能使用定义在dwFVFCaps中的坐标。驱动程序必须使用D3DTSS_TEXCOORDINDEX(s)获取相应的坐标。 纹理阶段操作 应用程序通过调用IDirect3Ddevice3::SetTextureStageState方法为纹理阶段进行混合操作。多纹理混合操作通过纹理混合单元阶段集来执行。这些单元中的每一个能被单独编程,以执行各种纹理混合操作,这些操作由参数选择。 在每个混合阶段Direct3D不提供说明多个纹理的机制。管道中的纹理阶段之间会发生饱和,但是在每个阶段它应尽可能迟地发生。 合法的操作的集合在D3DTEXTUREOP中枚举。这些操作要求与PC98兼容性标准相符合: ·D3DTOP_DISABLE ·D3DTOP_SELECTARG1,D3DTOP_SELECTARG2 ·D3DTOP_MODULATE ·D3DTOP_ADD ·D3DTOP_BLENDTEXTUREALPHA 在阶段一中,D3DTOP的默认值是MODULATE,在其他阶段则是DISABLE。MODULATE用在阶段一是为了后向兼容性,但默认情况下纹理应当是失效的。 纹理阶段参数 每个多纹理混合操作组合两个输入。通过调用IDirect3Ddevice3::SetTextureStageState方法以及说明以下D3DTEXTURESTAGESTATETYPE枚举类型来选择: D3DTSS_COLORARG1 对颜色操作控制第一个输入。 D3DTSS_COLORARG2 对颜色操作控制第二个输入。 D3DTSS_ALPHAARG1 对alpha操作控制第一个输入。 D3DTSS_ALPHAARG2 对alpha操作控制第二个输入。 参数标志 在每个纹理阶段,任何前面的四个参数使用以下纹理参数标志来设置: D3DTA_CURRENT 纹理参数是以前混合阶段的结果。在第一个纹理阶段(阶段0),这个参数等价于D3DTA_DIFFUSE。如果前面的混合阶段使用一个bump-map纹理(D3DTOP_BUMPENVMAP操作),系统从bump-map纹理以前的阶段选择纹理。(如果s代表当前的纹理阶段,s-1包含一个bump-map纹理,此参数变为s-2纹理阶段的输出结果) D3DTA_DIFFUSE 迭代的颜色数据从Gouraud插值算法中获得。在第一阶段它经常被用作ARG2,因为在那个阶段尚无D3DTA_CURRENT纹理。 D3DTA_TFACTOR 在Direct3D中使用D3DRENDERSTATE_TEXTUREFACTOR设置的值。 注意:一些实现可能不能同时使用D3DTA_TFACTOR和D3DTA_DIFFUSE。 D3DTA_TEXTURE 纹理阶段使用IDirect3Ddevice3::SetTexture(n,lpTex3)方法绑定纹理,这里n是阶段号。SetTexture定义了当D3DTA_TEXTURE是参数之一时,对这个阶段的纹理采用哪个纹理对象。D3DTA_TEXTURE只能由D3DTSS_ALPHAARG1和D3DTSS_COLORARG1提供,不能是D3DTSS_ALPHAARG2和D3DTSS_COLORARG2。 修饰标志 这两个值应当使用位或操作和前面的标志之一结合使用。 D3DTA_COMPLEMENT 指示纹理参数应被进行反转操作。 D3DTA_ALPHAREPLICATE 指示纹理参数在被使用到此操作以前,应将它的alpha通道复制到所有的颜色通道。如果是仅有一个部件的纹理,它自动被复制到所有颜色通道。此标志对于ALPHA_ARGs不需要说明,但是使用它也不会产生错误。 默认值 如果应用没有完全设置状态参数,就使用以下的默认值。这些默认值已经被定义,以使多纹理操作更加方便,健壮的代码总是完全说明了所期望的状态。 如果已经设置相应的纹理,D3DTSS_COLORARG1和D3DTSS_ALPHAARG1对于D3DTA_TEXTURE都是默认值。如果没有设置纹理,二者的默认值都是D3DTA_DIFFUSE。 D3DTSS_COLORARG2和D3DTSS_ALPHAARG2对于D3DTA_CURRENT都是默认值。注意在第一阶段,D3DTA_CURRENT默认值是D3DTA_DIFFUSE(除了在D3DTA_CURRENT描述中的例外情况)。 ARG2的默认是D3DTA_DIFFUSE,但是如果操作的默认值是D3DTOP_SELECTARG1,它则被忽略。 如果在FVF顶点中没有说明特别的颜色,D3DTA_DIFFUSE的默认值是0xFFFFFFF。 如果在第一阶段,D3DTA_CURRENT默认值是D3DTA_DIFFUSE,除了当前面的混合阶段是BUMPENV*MAP颜色操作,在这种情况下,有以下结果: ·如果前面的阶段是BUMPENV*MAP,此参数是倒数第二阶段的结果。 ·在第二纹理阶段(阶段1),此参数默认是D3DTA_DIFFUSE。 对于任何阶段的D3DTSS_COLORARG1和D3DTSS_ALPHAARG1状态,D3DTA_TEXTURE是一个值,或者如果这个阶段没有纹理绑定,它的默认值是0x0。 多纹理验证 当前的硬件不需要实现Direct3D所支持的所有特性。应用决定了是否要通过首先设置期望的混合模式来执行一个特别的混合操作以及随后调用IDirect3DDevice3::ValidateDevice方法。驱动程序必须在初始化的时候准确报告它的性能,并且支持D3DvalidateTextureStageState,以允许验证它的性能。验证覆盖了TBLEND级指定的操作。 IDirect3DDevice3::ValidateDevice返回码 WRONGTEXTUREFORMAT 硬件不能支持当前选择的纹理格式状态。 UNSUPPORTEDCOLOROPERATION 不支持指定的颜色操作。 UNSUPPORTEDCOLORARG 不支持指定的颜色参数。 UNSUPPORTEDALPHAOPERATION 不支持指定的alpha操作。 UNSUPPORTEDALPHAARG 不支持指定的alpha参数。 TOOMANYOPERATIONS 硬件不能处理所说明的操作的个数。 CONFLICTINGTEXTUREFILTER 硬件不能同时进行三线过滤和多纹理。 UNSUPPORTEDFACTORVALUE 硬件不支持比1.0大的D3DTA_TFACTOR。 4.6.2 调色板纹理 Direct3D也允许调色板和纹理一起使用。调色板可以依附于纹理,就象它能依附于任何别的DirectDraw表面一样。为了支持调色板纹理,驱动程序必须在D3dDrawPrimitives2的实现中对操作码D3DDP2OP_SETPALETTE和D3DDP2OP_UPDATEPALETTE做出响应。这些操作码在命令流中分别在D3DNTHAL_DP2SETPALETTE和D3DNTHAL_DP2UPDATEPALETTE结构之后。D3DDP2OP_SETPALETTE在被使用的调色板句柄和表面句柄(已经由D3DcreateSurfaceEx建立)之间建立一个联系。然后,D3DDP2OP_UPDATEPALETTE能发送多次,以设置为纹理使用的调色板的值。 4.6.3 调色板Blitting 在DirectX7.0生效的一个重要的Direct3D DDI改变是纹理被blitted——不是直接调用DdBlt,而是通过在D3dDrawPrimitives2命令流中嵌入一个新的记号。这个新的记号D3DDP2OP_TEXBLT向驱动程序说明纹理必须从后援表面传输到本地或者非本地的视频存储器中。 同样,驱动程序不必通过旧的D3dTextureCreate/D3dTextureDestroy回调来负责为纹理建立内部句柄,而是由运行时为每个在Direct3D环境中建立的DirectDraw表面分配一个句柄号。驱动程序通过D3dCreateSurfaceEx回调得知此句柄号。 D3dCreateSurfaceEx在每个HAL DDCreateSurface调用完成后被调用。D3dCreateSurfaceEx在每个内部HEL CreateSurface调用完成后也被调用。HEL调用通常发生在后援DirectDraw表面被建立时。这些调用也发生在Direct3D环境用D3dContextCreate建立前或建立后。 当应用运行时,调用D3dDestroyDDLocal清除和注销任何为这些表面建立的驱动程序数据。在Direct3D环境被建立前也发出此调用以保证没有“脏”的句柄和任何没有被清除的环境相联系。这只是一个预防性措施,如果环境在使用后被适当清除,它不应注销任何数据。 4.6.3 驱动程序管理的纹理 驱动程序可以管理标记为可管理的纹理。这些DirectDraw表面使用lpSurfMore->ddCapsEx引用的结构中的域dwCaps2中的标志DDSCAPS2_TEXTUREMANAGE来标记为可管理的纹理。 驱动程序通过设置DD_HALINFO结构dwCaps2域中的DDCAPS2_CANMANAGETEXTURE来表示支持驱动程序纹理管理。作为对驱动程序DirectDraw部件初始化的响应(DrvGetDirectDrawInfo),DD_HALINFO被返回。 驱动程序然后以一种“懒惰”方式在视频存储器或者非本地存储器建立必须的表面。也就是驱动程序把它们留在后援表面直到需要的时候(即光栅化一个使用纹理的原语前)。 表面应当按照它们的优先权顺序注销。驱动程序应当对D3dDrawPrimitives2命令流中的D3DDP2OP_SETPRIORITY做出响应,以对给定的表面设置优先权。第二个措施是驱动程序使用最近最少使用算法(LRU)。无论何时,在一个特定的情况下,两个或者多个纹理的优先权是一样的时候采用这种策略。逻辑上,任何正在使用的表面不应被注销。 驱动程序必须注意,管理纹理的时候仍不能忽略DdBlt和DdLock。这是因为后援表面的任何改变必须在纹理被再次使用以前,传播到表面在视频存储器中的拷贝中。驱动程序应决定是更新表面的一部分还是表面的全部。 驱动程序被允许执行纹理管理是为了优化纹理上的变形以及能自己决定何时、何地将纹理传送到内存中。 4.7 绘画原语和状态改变 所有的Direct3D?原语和状态在命令和顶点缓冲区中传递到D3dDrawPrimitives2回调。驱动程序负责解析这些缓冲区并处理所有的绘画和状态改变的请求。 以下几小节讨论了命令和顶点缓冲区的布局,并描述了驱动程序应如何处理它们。 4.7.1 命令和顶点缓冲区 D3dDrawPrimitives2 DDI使用两种类型的缓冲区:命令缓冲区和顶点缓冲区。(参见主输入结构中D3DNTHAL_DRAWPRIMITIVES2DATA的描述)命令缓冲区包含了指令,紧随其后的是结构数据,它类似于执行缓冲区中的结构。它们包含索引或者未索引的原语以及内联的顶点数据。命令缓冲区或者是API级的执行缓冲区,或者是Direct3D的内部命令缓冲区。 对于内部命令缓冲区,驱动程序申请内存,也许作多个缓冲区。内部命令缓冲区是只写的。指令格式参见D3DNTHAL_DP2COMMAND。 如果D3DNTHALDP2_USERMEMVERTICES标志被设置,顶点缓冲区被用户内存指针说明。否则,顶点缓冲区是一个DirectDraw表面,它可以是API级的执行缓冲区、内部隐含的顶点缓冲区或者API级的顶点缓冲区。 顶点缓冲区API能建立、注销、加锁和解锁顶点缓冲区,也能使用ProcessVertices处理从源缓冲区到目标缓冲区的顶点。DrawPrimitivsVB和DrawIndexedPrimitiveVB是主要的API级的调用。顶点缓冲区也能优化,但是优化的顶点缓冲区不能加锁。 4.7.1.1命令和顶点缓冲区的申请 在Direct3D使用三种类型的缓冲区: ·隐含的顶点缓冲区(IVB)仅用于内部使用;应用并不知道IVBs。一个IVB总是在环境建立后才建立。Direct3D存储顶点数据在IVBs。 ·显式的顶点缓冲区(EVB)在应用请求时建立。Direct3D然后在EVB存储顶点数据。 ·命令缓冲区仅用于内部使用;应用并不知道命令缓冲区。Direct3D在命令缓冲区中存储命令数据。 IVB是特殊的顶点缓冲区,被Direct3D用于内部批处理。它们在设备初始化时建立,可以是多缓冲区。它们可读可写,因此不应将它们放在视频存储器中。通过辨别DDSCAPS2_VERTEXBUFFER和DDSCAPS2_COMMANDBUFFER标志的不存在来区分这种类型的缓冲区。 EVB由应用建立和控制。它们不能是多缓冲区,不能置于本地或者非本地视频存储器中,除非设置了DDSCAPS_WRITEONLY。这种类型的缓冲区由DDSCAPS_VERTEXBUFFER标记。 命令缓冲区被Direct3D用于批处理命令。它们可以是多缓冲区,能用于所有的API,除了TLVERTEX或者未处理的执行缓冲区API调用。这种类型的缓冲区由DDSCAPS2_COMMANDBUFFER标记。命令缓冲区总是只写的,它们从不包含不合法的指令。 默认情况下,Direct3D运行时申请所有的这些缓冲区。IVB和命令缓冲区通过与它们相联系的表面被访问。所有缓冲区传递给驱动程序的D3dDrawPrimitives2回调。 驱动程序申请顶点和命令缓冲区 Direct3D驱动程序能执行顶点和命令缓冲区的申请。这是通过划分DD_D3DBUFCALLBACKS结构,并将DD_HALINFO结构的lpD3DbufCallbacks成员指向它。在DD_D3DBUFCALLBACKS中的回调报告包括:CanCreateD3Dbuffer,CreateD3Dbuffer,DestroyD3Dbuffer,LockD3Dbuffer和UnlockD3Dbuffer。这些函数与Dd*Surface回调函数(如DdCanCreateSurface)一样以同样的方式调用,并且DDSCAPS_EXECUTEBUFFER标志置1。缓冲区建立标志是DDSCAPS_WRITEONLY,DDSCAPS_VERTEXBUFFER和DDSCAPS2_COMMANDBUFFER。 驱动程序通过检查传给回调函数CanCreateD3Dbuffer和CreateD3Dbuffer的DD_SURFACE_LOCAL结构中的ddsCaps成员,来决定被请求的缓冲区的类型。DdsCaps可以是以下标志: ·DDSCAPS_VERTEXBUFFER指示驱动程序应申请EVB。 ·DDSCAPS_COMMANDBUFFER指示驱动程序应申请命令缓冲区。 ·以上两者都没有设置说明驱动程序应申请IVB。 IVB不应当被置于视频存储器中,因为它们是可读/可写的。只有设置了DDSCAPS_WRITEONLY标志的EVB能安全地置于视频存储器中。 驱动程序能使用存在的执行缓冲区回调申请顶点缓冲区和命令缓冲区。驱动程序应当暴露vidmem堆,即使它们自己做堆管理,这样通过暴露vidmem堆报告合适的大小。 驱动程序能内部申请N个顶点和命令缓冲区,并循环利用这些驱动程序。当硬件从别的队列缓冲区进行异步着色,Direct3D填充给定的一对缓冲区。这对使用DMA是非常有用的。 一个多纹理集的缓冲区能在各种不同的内存类型中。这种方式工作的原因是调用驱动程序建立第一个缓冲区。它能立即建立缓冲区集并返回第一个缓冲区。对于每个D3dDrawPrimitives2调用,如果D3DNTHALDP2_SWAPVERTEXBUFFER或者D3DNTHALDP2_SWAPCOMMANDBUFFER标志被置1,驱动程序应返回一个新的缓冲区。如果返回的缓冲区在视频存储器中,相应的D3DNTHALDP2_VIDMEMVERTEXBUF或者D3DNTHALDP2_VIDMEMCOMMANDBUF标志应置1。 偶然情况下,Direct3D将为下一个缓冲区请求最小的尺寸。如果太大的话,驱动程序应该在后援表面申请缓冲区。如果太小,驱动程序应给一个较大的缓冲区。驱动程序应该跟踪有多少缓冲区以及它们是什么内存类型,并在退出时清除所有的缓冲区。 4.7.2 Direct3D命令缓冲区 图4.3示例命令缓冲区 图4.3显示了一个逻辑命令缓冲区例子的一部分。驱动程序的D3dDrawPrimitives2回调函数接收一个指针,指向D3DNTHAL_DRAWPRLMITIVES2DATA结构lpDDCommands成员中的一个命令缓冲区。命令缓冲区总是被顺序处理。 正如图4.3所示,命令缓冲区包含数目不详的D3DNTHAL_DP2OPCOMMAND结构,每个结构的bCommand成员说明了以下命令之一: D3DDP2OP_REBDERSTATE指示在命令缓冲区后面有wStateCount个D3DNTHAL_DP2RENDERSTATE结构。驱动程序应当从这些结构解析状态,并相应地更新它自己私有的驱动程序状态。驱动程序也应当更新lpdwRState指向的状态数组中相应的状态。如果驱动程序不支持在命令流中的状态请求,它就应该用它支持的值重载请求的值。 D3DDP2OP_TEXTURESTAGESTATE指示在命令缓冲区后面有wStateCount个D3DNTHAL_DP2TEXTURESTAGESTATE结构。驱动程序应当从这些结构解析状态,并相应地更新与指定的纹理阶段相关的驱动程序的纹理状态。驱动程序不必向Direct3D运行时报告纹理阶段状态。 不论驱动程序实际支持几个坐标集,它都要求适当的解析八个纹理坐标集。 D3DDP2OP_VIEWPORTINFO指示一个结构D3DNTHAL_DP2VIEWPORTINFO跟在命令缓冲区后面。驱动程序应解析此结构,并更新存储驱动程序内部着色环境中的w-buffer信息。 任何剩下的D3DDP2OP_Xxx命令指示有足够的数据跟在命令缓冲区后面,以着色wPrimitiveCount次原语。取决于原语命令,驱动程序应当从命令缓冲区中解析D3DNTHAL_DP2Xxx结构,以及来自于顶点缓冲区、命令缓冲区或者两者的与顶点相关的数据。驱动程序应该尝试处理所有合法的D3DDP2OP_Xxx命令;即它不能选择忽略某些已经定义的原语类型。参见D3DNTHAL_DP2Xxx结构的详细参考。 取决于当前命令,下面的额外信息存储在命令缓冲区中: 所有的D3DDP2OP_INDEXEDXxx原语命令的索引信息。 D3DDP2OP_TRIANGLEFAN_IMM和D3DDP2OP_LINELIST_IMM原语命令的顶点数据。 额外的操作也定义为D3DNTHAL_DP2OPERATION中的D3DDP2OP_Xxx命令。 命令缓冲区偶然也包括只能被Direct3D理解的命令。如果驱动程序的D3dDrawPrimitives2的回调函数不能识别此命令,驱动程序应当调用Direct3D的回调函数D3dParseUnknownCommand,以试图解析此命令。当D3dParseUnknownCommand成功返回时,驱动程序继续解析和处理命令缓冲区。如果D3dParseUnknownCommand返回D3DERR_COMMAND_UNPARSED表示解析不成功,D3dDrawPrimitives2应在设置以下D3DNTHAL_DRAWPRIMITIVES2DATA结构中的成员后返回: 在dwErrorOffset中,将第一个不能处理的D3DNTHAL_DP2COMMAND命令的偏移写入lpDDCommands的缓冲区中。 在ddrval中设置D3DERR_COMMAND_UNPARSED。 参见Direct3D驱动程序初始化,以更详细地了解如何初始化D3dParseUnknownCommand。 为了简化D3dDrawPrimitives2的实现,可以从permedia2示范代码中拷贝解析代码,只写和具体驱动程序相关的着色及状态更新代码。 并不是总是通知Direct3D当前的着色状态。例如,执行缓冲区在到达驱动程序以前,并不被运行时所检查。驱动程序使用D3DNTHAL_DRAWPRIMITIVES2DATA的lpdwRState域跟踪着色状态数组。lpdwRState是一个指向内部着色状态数组的指针,从而使驱动程序能及时更新状态的变化。 4.7.3 Direct3D顶点缓冲区 顶点缓冲区包含了在D3dDrawPrimitives2调用中与命令缓冲区的原语相关的顶点数据。顶点以FVF的形式来表示,这里每个顶点可以和以下数据相关: ·位置(x,y,z,可选的w)(必须的) ·扩散的颜色(可选) ·反射颜色(可选) ·纹理坐标(可选)。Direct3D能发送八套坐标值。驱动程序必须提供FVF支持。 实际的顶点和它们被处理的顺序取决于D3DD2OP_*原语命令从命令缓冲区解析的顺序。参见D3DNTHAL_DP2*结构的详细参考。 4.7.4加速状态管理 加速状态管理是为了在一个单独的调用中,使API和DDI在大的状态改变时通信的机制。这种安排允许应用定义一整套状态设置调用,它作为一个状态块,被定义为一个整数。发送这个整数,就可以在一个调用中执行所有的状态改变。 通过减少必须的SetRenderstate函数调用的数目,减少了API负载;驱动程序通过将定义的状态块的阶段变化“预编译”成它们自己的硬件格式,而不是执行每个状态的变化改善了效率。 大多数应用仅在少数状态中着色,所以有微小的状态转换不是很重要。重要的是当驱动程序在公共着色脚本中切换时能定义可以被交换的状态块。这是状态块管理的核心。 状态设置符号被用来设置驱动程序中的状态。一个句柄引用了一个状态的集合。D3DNTHAL_DP2STATESET结构用来通知驱动程序要执行的状态集操作。 如果D3DNTHAL_DP2STATESET的dwOperation成员是D3DNTHAL_DP2STATESETBEGIN,驱动程序开始为dwParam中的句柄纪录状态。当驱动程序收到D3DNTHAL_STATESETEND时,就停止纪录状态。 如果dwOperation成员是D3DNTHAL_DP2STATESETDELETE,dwParam中的句柄引用的状态集应被删除。 如果dwOperation成员是D3DNTHAL_DP2STATESETEXECUTE,dwParam中的句柄引用的状态块应被应用于设备。 如果dwOperation成员是D3DNTHAL_DP2STATESETCAPTURE,当前驱动程序的状态以一种指定的方式被捕获,给在状态块中定义的当前状态一个“快照”。即只有状态块中的状态才被捕获。这样,状态快就像一个掩码,仅纪录定义在它里面的状态。例如,如果D3DRENDERSTATE_ZENABLE在状态块中,D3DRENDERSTATE_ZENABLE的当前状态被捕获并放置在状态块中。如果在状态块没有D3DRENDERSTATE_ZENABLE,此状态就不被捕获。 状态分组用来定义一般的、能为不同的着色脚本稍作修改的状态块。这些预定义的分组(在D3DSTATEBLOCKTYPE中枚举)定义了能被顺序修改的通用状态块,这些状态块的状态会因为着色脚本的不同而发生变化。例如驱动程序也许建立100个通用的预定义状态块,然后修改每一个状态以适应不同的着色脚本。状态块类型被传送给D3DHAL_STATESET结构的sbType成员中。 sbType成员仅仅对D3DHAL_STATESETBEGIN和D3DHAL_STATESETEND有效,并使用以下的D3DSTATEBLOCKTYPE枚举说明预定义的状态块:NULL代表无状态,D3DSBT_ALL代表所有的状态,D3DSBT_PIXELSTATE代表像素状态,D3DSBT_VERTEXSTATE代表顶点状态。 驱动程序应忽略sbType,除非它实现了扩展的着色状态。如果驱动程序实现了扩展的着色状态(着色状态超过了Direct3D运行时所提供的),它就能使用sbType决定使用的预定义的状态块的类型。从它获取的这个信息,它能决定如何添加状态块以支持它的扩展。 4.8 柔性顶点格式(FVF) 驱动程序的D3dDrawPrimitives2回调函数接收柔性顶点格式(FVF)的顶点数据。因为顶点格式是柔性的,对于数据没有定义全面的数据结构。驱动程序必须实现完全的FVF功能。 注意DirectX7.0对FVF的更新包括1D、3D、4D以及通常的2D纹理。参见permdia2示范驱动程序和Platform SDK文档,以得到这方面更多的信息。 4.8.1决定顶点缓冲区数据格式 为了识别顶点缓冲区的数据格式,驱动程序应判断以下的信息: ·纹理维度(1D,2D,3D或4D)。 ·FVF数据中的组成部分。 ·部件的顺序。 纹理维度 驱动程序应从D3DTEXTURETRANSFORMFLAGS纹理坐标计数标志(D3DTTFF_COUNT*)判断顶点的维度。计数标志的数字说明提供了多少纹理坐标。注意正如以下所解释的,它并不一定等于纹理的维度。 对于非映射纹理 ·D3DTTFF_COUNT1指示光栅期望1D纹理坐标。 ·D3DTTFF_COUNT2指示光栅期望2D纹理坐标。 ·D3DTTFF_COUNT3指示光栅期望3D纹理坐标。 ·D3DTTFF_COUNT4指示光栅期望4D纹理坐标。 如果使用映射纹理,D3DTTFF_PROJECTED标志被置位,以指示纹理坐标按照纹理坐标集的最后一个元素分割。这样,对于2D映射纹理,计数值将是3,因为前两个元素被第三个分割,导致2D纹理查询是两个浮点数,也就是D3DTTFF_COUNT2和D3DTTFF_COUNT3|D3DTTFF_PROJECTED都引用一个2D纹理。 FVF顶点数据组成部分 驱动程序通过分析D3DNTHAL_DRAWPRIMITIVES2DATA结构的dwVertexType的成员说明的标志,判断提供了什么组成部分。具体来讲,下表指示了在dwVertexType中可能设置的位以及所表示的部件: 值 含义  D3DFVF_XYZRHW 每个顶点有x,y,z,w  D3DFVF_DIFFUSE 每个顶点有扩散色  D3DFVF_SPECULAR 每个顶点有反射色  D3DFVF_TEX0 顶点数据没有提供纹理坐标  D3DFVF_TEX1 每个顶点有一个纹理坐标集  D3DFVF_TEX2 每个顶点有两个纹理坐标集  D3DFVF_TEX3 每个顶点有三个纹理坐标集  D3DFVF_TEX4 每个顶点有四个纹理坐标集  D3DFVF_TEX5 每个顶点有五个纹理坐标集  D3DFVF_TEX6 每个顶点有六个纹理坐标集  D3DFVF_TEX7 每个顶点有七个纹理坐标集  D3DFVF_TEX8 每个顶点有八个纹理坐标集   D3DFVF_TEXn标志只能有一个被设置。 FVF顶点组成部分顺序 包含顶数据的组成部分被严格排序。具体讲,Direct3D仅仅给驱动程序提供了图4.4所示的顶点数据顺序。注意此图表示的是2D纹理,尽管1D、2D、3D和4D纹理对于DirectX7.0和后续版本也是有效的。 图4.4 柔性顶点格式 如图4.4所示,Direct3D总是发送x,y,z,w值;其余的数据只有当应用需要时才发送。具体讲,顶点数据有以下组成部分: 位置(x,y,z,w)(必须有) 第一个顶点成分是四个D3DVALUE,它说明了顶点的位置。Direct3D总是在dwVertexType中设置D3DFVF_XYZRHW位。 扩散色(可选) 如果被提供,此组成部分是一个D3DCOLOR值,它说明这个顶点的扩散色。Direct3D在dwVertexType中设置D3DFVF_DIFFUSE位。 反射色(可选) 如果被提供,此组成部分是一个D3DCOLOR值,它说明这个顶点的反射色。Direct3D在dwVertexType中设置D3DFVF_SPECULAR位。 纹理数据(可选) 这部分按照纹理的维度是可变的。对于纹理的每个维度,一个D3DVALUE值说明了每个部分的u、v、w或q(参见FVF纹理维度的解释)。例如,如果使用2D非映射纹理,需要每个纹理的两个D3DVALUE值描述每个纹理以至全部八个纹理的顶点的u、v值。u、v值对代表的是n,这里n对应的是dwVertexType的D3DFVF_TEXn标志。例如,如果设置dwVertexType的D3DFVF_TEX3标志,每个顶点提供三个u、v值对。 FVF数据总是被紧密包装;也就是说,在顶点缓冲区中未作说明的组成部分不会浪费内存。例如当dwVertexType是D3DFVF_XYZRHW| D3DFVF_TEX2,纹理维度是2D,在缓冲区中的每个顶点由八个紧包装的D3DVALUE组成。这些为两纹理(tu,tv,tu,tv)说明了位置(x,y,z,w)和纹理坐标,如图4.5所示。 注意图4.5假设仅有两个纹理坐标。为驱动程序提供的顶点数据总要被转换。驱动程序从不接收常规数据。在FVF纹理坐标集的所有数据是单精度IEEE浮点数。 图4.5 顶点缓冲区 4.9 Direct3D驱动程序的高级主题 这一节讲述Direct3D的特别方面的主题和优化问题。这些特性并不集中于实现Direct3D驱动程序的核心功能;应当在核心驱动程序功能完成之后,再实现这些特性。 这一节描述的主题有: ·4.9.1 优化的纹理 ·4.9.2 模版位面 ·4.9.3 Guard Band Clipping ·4.9.4 范围调整 ·4.9.5 W缓冲区 ·4.9.6 Bump Mapping ·4.9.7 硬件转化和照明 4.9.1 优化的纹理 为提高性能,一些硬件厂商改变了它们的纹理格式,不同于标准的DirectDraw表面格式。一种方式是把像素叠起来,这样它们以2D引用位置被安排在内存中。例如,不是以下面的线性关系安排像素: 0 1 2 3 …width width+1 width+2 width+3 width+4 … 它们被安排在4×8的块中: 0 1 2 3 4 5 6 7 这样,一个单独连续的32位内存引用包含了4×8的像素块。这种布置对于由光栅生成的内存引用模式,与标准的DirectDraw布置相比,是更好的。这种安排被称为“swizzling”或者“弥补”纹理。这些操作执行起来相对较快,也不影响申请的内存表面的大小。但是,这个操作仍然引入了显式的应用必须控制的等待时间。 另外一种方式是将纹理压缩到视频存储器中,把它在加速器的本地高速缓存中解压缩。这减少了为全速运行光栅化所需要的视频存储器带宽。在DirectX5.0中,驱动程序也可以这样做,但是它是个较慢的操作,由于导致纹理变小,影响了申请应用的视频存储器。 为了适应硬件厂商,DirectX6.0和后续版本能“优化”纹理,这给了驱动程序一个机会,当使用纹理时,将表面转化成私有格式。一旦优化,表面不能加锁,比原来要大或小一些。 市场上许多三维加速芯片有自己的格式,不能用标准的DirectDraw机制描述。这个特性的目的是想用这样的表面类型包容所有的硬件。 目前,应用有一些需要经常更新的纹理,也有些纹理在应用存活期间保持不变。拥有后一种纹理的应用能从改善了的填充率中获益。驱动程序如何接收这样的优化的细节参见优化纹理API。 4.9.1.1优化纹理API 三个新的caps指示了能用于DirectDraw表面的优化级。在DirectX6.0和后续版本中,仅有纹理能用caps位标记。尽管和纹理的语义不同,优化也许在将来能扩展,以至覆盖所有类型的表面。 为使这个问题更清楚,给DirectDrawSurface4::Create()提供了三个标志。当这三个标志没有说明任一个,是否优化就完全由驱动程序来决定。这些标志如下: DDSCAPS2_HINTDYNAMIC 对驱动程序指示此表面将会经常加锁(例如每帧一次)使用,如视频流或程序上的纹理。这个标志对所有的驱动程序枚举纹理表面格式都起作用。驱动程序应避免对这些纹理的任何转变,特别是如果它需要一些负载。 DDSCAPS2_HINTSTATIC 指示驱动程序此表面在Load()和Blt()中会被重排序/重叠/优化。此操作不改变纹理的大小。因为应用也许仍然锁住了这些位,它相对较快,也是对称的(这样做的时候尽管使性能受影响)。驱动程序不允许在这些表面上加锁失败,因此不能使用压缩技术。Mipmap表面在这种情况下能被交叉存取。 这个标志并不是要在任何环境下都要优化,特别是那些对性能无益的情况下。特别的,一些格式对优化会失败,但并不报错。 DDSCAPS2_OPAQUE 指示驱动程序此表面不会被应用再次存取。这个标志像DDSCAPS2_HINTSTATIC,但是允许使用硬件相关的压缩方式进行实际压缩。这个操作相对较慢,但是允许使用简单、对称的压缩算法(如YUV4:2:0或者颜色单元压缩),提供了2到6倍的压缩率。不应再次使用非对称方法如VQ,因为它会导致不可接受的基准。 Mipmap纹理能被驱动程序任意交错存取。这种技术仅在着色循环的内部或外部也许是必须的,如当纹理被从磁盘装入时。在这样的纹理装入后,如果压缩被采用,堆大小的报告反映了内存的消耗。记住在纹理上有额外的头负载,所以压缩许多小的纹理不会节省很多内存。 总的来说,此标志并不意味着对纹理压缩率或者压缩质量有任何保证。 用此标志建立的表面在下列情况下会失效: Lock()调用 GetDC()调用 子矩形blit到这种表面 从这种表面来的所有blit 将数据放入这种表面的唯一方式是用Load()或者完全表面blit调用。 4.9.2模版位面 模版位面能控制在每像素基础上的绘画。它们典型用在多通过(multipass)算法中以达到特殊的效果,如贴纸、轮廓、阴影和实心几何着色。 一些硬件设计用来加速Direct3D的模版位面的实现。由模版位面带来的特效对于娱乐应用特别有用。 假设模版位面嵌入在z缓冲区中。 在DirectX5.0,应用程序使用在D3DDEVICEDESC的dwDeviceZBufferBitDepth成员中设置的DDBD_*标志找到可用的z缓冲区位深度。为了支持带模版的z缓冲区和使用现存的DDBD_*标志不能表示z缓冲区位深度,在DirectX6.0和后续版本有一个新的API入口点IDirect3D3::EnumZBufferFormats,它返回DDPIXELFORMAT结构数组,描述可能的z缓冲区/模版像素格式。DDPIXELFORMAT结构增加了三个新的z缓冲区相关的域:dwStenciBitDepth,dwZBitMask和dwStenciBitMask。一个新的标志DDPF_STENCILBUFFER,指示在z缓冲区中是否有模版位。dwZBufferBitDepth(以前就存在)给出了z缓冲区位的总数,包括模版位。DDPIXELFORMAT结构的新成员如下: dwStenciBitDepth 说明模版位的位数。 DwZBitMask 说明z值占的是哪些位。如果非0,这个标志意味着z缓冲区是一个标准的、无符号整数z缓冲区格式。 DwStenciBitMask 说明模版值占的是哪些位。 DirectX6.0和后续版本的驱动程序应当为它们所支持的z缓冲区格式在dwDeviceZBufferBitDepth中设置合适的DDBD_*标志。如果不支持模版位面,DDBD_*标志能代表所有可用的z缓冲区格式,设置这些标志就足够了(因为通过EnumZBufferFormats它们将会被翻译成DDPIXELFORMAT)。否则,通过返回一个缓冲区(此缓冲区的第一个DWORD指示有效的z缓冲区DDPIXELFORMAT 的位数,紧随的是DDPIXELFORMAT结构本身),Direct3D驱动程序必须对DdGetDriverInfo查询做出响应(它使用GUID_ZpixelFormats GUID)。 DDPIXELFORMAT的更多信息参见DirectDraw DDK参考文档。 新的和模版位面相关的着色状态如下: 名称 类型 描述  D3DRENDERSTATE_STENCILFUNC D3DCMPFUNC 比较函数。如果以下的表达为真,则测试通过: (ref&mask)操作符(stencil&mask)。这里refs是引用值,stencil是stencil缓冲区的值,mask是D3DRENDERSTATE_STENCILMASK。  D3DRENDERSTATE_STENCILREF DWORD 在stencil测试使用的引用值  D3DRENDERSTATE_STENCILMASK DWORD 在stencil测试使用的掩码值  D3DRENDERSTATE_STENCILWRITEMASK DWORD 写掩码,对任何写入stencil缓冲区的值都适用  D3DRENDERSTATE_STENCILFAIL D3DRENDERSTATE_STENCILZFAIL D3DRENDERSTATE_STENCILPASS D3DSTENCILFUNC 参见下一段的解释   分别定义了三个新的着色状态— D3DRENDERSTATE_STENCILFAIL,D3DRENDERSTATE_STENCILZFAIL,D3DRENDERSTATE_STENCILPASS,通知硬件当模版测试失败时、或当模版测试通过但是z测试失败时和两者都通过时这三种情况下,它该如何去做。这些着色状态接收D3DCMPFUNC枚举类型的数据作为参数。 4.9.2.1模版操作码 D3DSTENCILOP枚举类型包含被用来说明所期望执行的模版操作。 4.9.3 Guard Band Clipping 当DdGetDriverInfo中填入GUID_D3DextendedCaps GUID时,说明驱动程序支持Guard Band Clipping。一个guard band是一个比视口(甚至着色的目标)还要大的矩形,里面的顶点能被驱动程序自动裁剪。Direct3D裁减码针对的是矩形而不是视口。因此,由于裁剪而要求产生新的顶点的需求就减少了,由于驱动程序说明的是大的guard band矩形。 一个合理的真实世界的例子是:只要屏幕的X和Y坐标落在(-2048)到2047的范围内,硬件就能正确着色。 Guard band clipping对于抗干扰的硬件也是有益的,因为过滤区域能扩展至着色表面的范围之外,并且如果原语在此范围内被几何裁剪,过滤错误就会引进。 为了做裁减,视口信息被传送给驱动程序。这说明了应用要求被裁减的几何图形的实际的视口。如果不想实现guard band clipping,可以忽略此信息。建议驱动程序不应通过剪取或掩码操作来使用这个数据实现裁减,因为这些可能比让Direct3D做裁减更慢。 4.9.4范围调整 一些硬件使用抗干扰内核,就能影响由屏幕空间顶点所定义的扩展矩形之外的像素。为了“脏”矩形的处理而使用D3DCLIPSTATUS结构中的扩展矩形的应用能感受到人工着色,因为扩展矩形不能覆盖由硬件所修改的象素。 Direct3D使硬件驱动程序要求扩展矩形向外调整在D3DNTHAL_D3DEXTENDEDCAPS结构的dvExtentsAdjust域说明的像素数(当驱动程序响应在DdGetDriverInfo的GUID_D3DextendedCaps GUID时被填入)时提出了这个问题。扩展矩形被裁剪到着色目标表面的范围。默认是0。 4.9.5 W缓冲区 通常,为了比较深度以及在z缓冲区中存储,使用透视矫正z。因为这种方式下为了维护平面多边形,必须产生光栅迭代。一些实现通过用相对于眼睛的w或z表达的深度信息填充z缓冲区,可以执行隐藏表面的消除。这就是在本文中被称为w的缓冲区。通过线性插入在经典变换中说明的顶点1/w项以及顶点结构(TLVERTEX),计算它的每像素的倒数,然后为了深度比较及有条件地将它存储到深度缓冲区中,然后使用此w值。 典型的,硬件存储一个浮点值到缓冲区中。以下精度格式是常用的: 16位 12.4 24位 IEEE单精度浮点,没有低位尾数 32位 标准IEEE单精度浮点 常规的z缓冲区是为使用CAD或写作工具的技术市场开发的,它的视面积/空间是已知的和有限范围的。被存储的深度值的范围因此也是有限的,它允许远、近(远近平面的距离)的比例是2到10。 为这种应用设计的典型硬件重复透视矫正z,并将它直接存储在z缓冲区中。由于涉及到数学,这种透视矫正z在z缓冲区的范围内没有均匀分布。使用远近比例为100将导致深度缓冲区的90%被花费在前10%的场景深度。对于工具这也许是足够的,但是典型的有外部场景的娱乐或者可视化仿真应用要求远近比例为1000到10000。在1000时,范围的98%花费在深度的前2%上。这能引起远处对象的隐藏表面的虚假化,特别是当使用16位深度缓冲区时。 相反,当w(或相对于眼睛z)被使用,在世界空间的近和远裁剪平面之间申请的缓冲区位更均匀。最关键的好处是远近比例不再是个问题,它允许应用支持最大几英里的范围,但是在视点几英寸里仍可得到合理的准确的深度缓冲区。 4.9.5.1 w缓冲区API ZBUFFERENABLE着色状态支持以下的三种枚举类型设置: D3DZBUFFERTYPE D3DZB_FALSE 禁止所有的深度缓冲区。 D3DZB_TRUE 允许使用透视矫正z的z缓冲区。 D3DZB_USEW 禁止z缓冲区,但是允许w缓冲区,它是相对于眼睛的z。 因为存储w的确切格式变化很大,它应被认为是透明的。 当使用w缓冲区,表面申请和深度填充操作同样的工作。所有的z缓冲区比较模式在任何一种情况下同样的工作。 4.9.5.2 w缓冲区DDI 通过设置D3DPRIMCAPS结构dwRasterCaps域的D3DPRASTERCAPS_WBUFFER位,驱动程序支持w缓冲区。D3DRENDERSTATE_ZENABLE着色状态被传给驱动程序以允许或者禁止w缓冲区或z缓冲区。 D3DNTHAL_DP2VIEWPORTINFO结构支持对应于世界空间的前面和后面裁剪平面的域。这个信息能也被用来调整雾化表。 4.9.6 Bump Mapping bump mapping 是一种很好的技术,它能使表面看起来起皱或者起涟漪,但不需要从几何上使表面改变。 在通常意义上,bump mapping涉及到按照由二维“bump map”给定的信息而生硬的干扰表面的标准。这欺骗了本地反射模型(这种模型中强度是表面标准的一个主要功能),导致在光滑的表面产生了局部变化。当产生影子的时候这种技术很突出,因为那种情况下,在边缘的干扰不再可见。也就是,影子跟随模型的变化,因此没有被干扰。这是一项重要的技术,因为看起来对表面增加了纹理而不是调整表面的颜色。 因为Direct3D的bump mapping通过在着色阶段给表面增加纹理而不干扰几何图形的方式来实现的,它绕过了严重的模型问题。如果对象是多边形,要足够好的技巧来接受来自纹理映射的干扰。如果纹理是一个选项,这就是一个严重的缺点。 bump mapping干扰环境映射,它允许说明任意数目、形状、颜色的灯光。DirectDraw为bump说明了一种新的像素格式:DDPF_DU和DDPF_UV。通过D3DTEXTURESTAGESTATETYPE,它集成到多纹理混合中。环境映射由应用来进行。 在这种情况下,bump mapping被更准确地解释成由于为扩散和反射环境地图的光栅化对每个像素纹理坐标的干扰。这种能力被集成到Direct3D多纹理支持中。bump mapping被在本地表面坐标系统(纹理u,v)中执行。 这种技术允许场景的照明环境在一个图像环境地图中表现(为了扩散或者反射的效果)。它允许任意数目、形状、颜色或强度分布的灯光在这样的地图中表现。能事先安排这些地图,这是为了静止的情况,或者为改变光源而使用blit方法在飞行中容易更新。 从Phong shading导出的传统bump mapping设计受限于恒定颜色和固定下降曲线的球体光源。这些不需要每像素环境映射的额外的纹理发布能力,并且对扩散照明效果工作地更好,但是它们不能产生光现实要求的可视的、结构化的反射强光。在将来,这种技术的使用会由于环境映射计算直接集成到Direct3D几何管道中而更方便。 bump mapping通常在照片着色中提供。它的使用显著地增加表面细节效果不必将表面镶嵌进很多小三角中。当和反射效果使用时,bump map能模仿反射粗糙的表面,如湿的石头。当由3D加速器支持,这种效果能在实时执行,允许动态光源的改变。Direct3D对bump mapping的支持减少了余下的几个用照片质量表示表面和环境的障碍。 当一个Texture2对象从一个bump map格式的DirectDraw表面建立时,此纹理被认为是一个bump map。使用D3DRENDERSTATE_TEXTUREn状态的任何一个都能使此Texture2对象绑定到Direct3D设备中。通过设置D3DTOP_BUMPENVMAP,一个可编程的纹理混合阶段能设置执行bump map操作。为这个阶段定义的纹理应是从一个bump map格式的DirectDraw表面建立。它使用FVF纹理坐标,此坐标由这个Texture2阶段的纹理坐标的标识说明。它也会注意纹理状态的控制过滤、包裹等。 在此纹理中的bump的值干扰紧随纹理所使用的纹理坐标,在扩散或反射环境地图中应考虑到这一点。 仿真 这种方法能在任何支持8位调色板纹理方式的硬件中被仿真。限制是所使用的环境地图(紧随bump纹理地图的纹理)必须有确切的16X16 texel的分辨率,并且没有过滤。这由D3DTEXOPCAPS_MAXBUMPENVMAP16X16标志指示。别的部分对bump map能干扰的环境地图的大小没有限制。 当额外的信息如常规矢量数据在DDI中提供,在将来的DirectX版本中也许支持bump map的环境映射的更一般的方法。 在多纹理混合操作D3DTOP_BUMPENVMAP和D3DTOP_BUMPENVMAPPREMODULATE中允许Bump mapping。后一个操作是bump mapping和gloss mapping的结合,从而允许镜面反射被编码在同一个表面,就如同bump map数据一样。 在每个阶段都提供四个额外的纹理状态: D3DTS_BUMPENVMAT00 D3DTS_BUMPENVMAT01 D3DTS_BUMPENVMAT10 D3DTS_BUMPENVMAT01 4.9.7硬件变换和照明 允许几何操作的硬件加速,如照明和变换,同时修改了DirectX7.0的DrawPrimitives2 DDI。在API一级,分别列举了在硬件中支持顶点操作的设备和那些仅仅作光栅化的设备。 现存的caps结构已经被扩展到指示硬件加速变换设备也许能提供的特性上。例如,支持的光源数目被设置在D3DLIGHTINGCAPS结构的dwNumLights成员中,在D3DNTHALDeviceDesc_V1结构中报告。 其他标志有: ·D3DDEVCAPS_DRAWPRIMITIVES2EX,指示驱动程序是DirectX7.0兼容的,它支持扩展DrawPrimitives2。 ·D3DDEVCAPS_SEPARATEXTUREMEMORIES,指示设备从独立的内存池中进行纹理。 ·D3DDEVCAPS_HWTRANSFORMANDLIGHT,指示设备支持硬件变换和照明。 ·D3DDEVCAPS_CANBLTSYSTONONLOCAL,指示设备支持从后援表面到非本地视频存储器的纹理Blt。 ·D3DDEVCAPS_HWRASTERIZATION,指示设备是否对光栅化有硬件支持。 ·D3DTRANSFORMCAPS_CLIP,指示当进行变换时硬件能裁剪。 因为硬件几何加速的特性集也许不同(如支持的光源的数目),caps结构指示设备执行哪个几何操作的子集。支持的光源的数目为0是有效的,指示硬件仅仅作变换。 软件实现几何管道使用的所有的关键状态和数据结构在DDI级可以得到。一些显示卡仅仅实现了硬件照明,而由主机处理器作变换和裁剪。 4.9.7.1顶点混合 在DirectX7.0支持顶点混合操作。顶点混合的工作方式是:模型空间的对象乘以世界空间的4x4的矩阵,结果将模型的原点置于一个相对于世界空间的原点的特别的世界空间。矩阵的一部分定方位,另一部分定位置。直到三个世界空间矩阵能被申请,通过混合顶点和对象上不同的权,这就允许对象被“弯曲”。一个顶点混合的例子可在Windows 2000中找到。 其次,视矩阵被申请,它有效地压缩相对于一个特别视点的空间,就像一个照相机将现实世界压缩成一个两维图像。 4.9.7.1.1多矩阵顶点混合 多矩阵顶点混合是用光滑的混合表层着色对象的技术。尽管在当前的软件中,这种技术用的并不多,但是大多数涉及动画角色的游戏使用某种光滑表层。 多矩阵顶点混合更新每个顶点的位置,而不需对每个顶点说明一个单独的4x4变换,从而允许执行光滑表层混合。它适合有连续光滑表面的常规情况,并仅仅要求一个额外的值---表层权值Beta---每个顶点最小的额外带宽要求。 指明的顶点位置数据被所有的世界空间矩阵变换,结果再与相应的顶点权值混合。 对任何顶点的标准都执行相同的步骤。这就产生一个单顶点(带着位置和标准),然后它被注入管道的剩下部分以便进行常规照明和裁剪。更多的细节参见多矩阵顶点混合实现算法。 有多种几何混合技术。这里描述的技术是几何混合的一种形式。它混合穿过独立的关节模型接合部位的顶点,用减少的多边形数目提供逐步增加的现实表现。当每帧的结合部位改变时,几何管道更新附近顶点的位置,使在结合部位的段之间进行光滑的混合。这个能力在除了旋转以外的结合类型(如转换、伸缩、修剪或者任何由4x4变换矩阵能表示的结合类型的结合)的段之间也可以实施。 一般原理是通过混合结合部位的顶点以扩展典型的被分割的角色。分段模型被认为从段或者坐标系统逐步建立,每个由一个变换矩阵定义。传统上,每个顶点只属于一个单独的段。此范例已被扩展以允许顶点可以部分属于别的段。在两个或多个段的结合部位附近的每个顶点有一个表层权值Beta---也就是属于别的段的比例。 当用在经典的层次结构模型中,“根”段没有混合。因为没有别的变换来混合,所以它总是固定的形式。表层权值Beta被定义为使用的父坐标系统的比例。如果段完全固定(DirectX7.0以前都是这种情况),则所有的对象的表层权值Beta为0.0。 对那些严格依附父段的部分Beta是1.0。它随着父段对混合的贡献的降低而降低。一些点必须是1.0,这是为了维护几个段之间的几何连续性。 角色经常包含大多数场景中的多边形,是一个关键的性能问题。对帧率等级行为有严重影响。对交互式应用,水平帧率比高平均帧率更重要。 顶点混合必须在变换后执行。如果混合没有被集成进着色管道中,变换必须执行两次:为表层执行一次,为着色执行一次。 集成顶点混合到几何管道中允许顶点数据的一次变换,在有限带宽的情况下这就使它和未混合的顶点一样快。 4.9.7.1.1.1 多矩阵顶点混合实现算法 此处实现的算法假设仅使用两个矩阵。 当矩阵改变,更新下面的四个矩阵: CTM 当前变换矩阵=WORLD*VIEW-1*PROJ CTM2 第二CTM(父坐标)WORLD1*VIEW-1*PROJ ITCTM 如果照明,需要反转的CTM ITCTM2 如果照明,需要反转的CTM2 注意在一些情况下首先使用顶点的权来混合矩阵也许更有效率,然后仅做一次矩阵与顶点的乘积。 FVF编码的变化 对多矩阵顶点混合API的关键性影响是对FVF的位置部分增加了顶点混合权参数。这些参数存储为32位IEEE单精度浮点数。通过在FVF编码增加的四个新的位模式来指示它们在输入的顶点数据中提供:D3DVFV_XYZB2,D3DVFV_XYZB3,D3DVFV_XYZB4,D3DVFV_XYZB5。 注意这些编码标识占据的额外的DWORD空间也许已经被别的应用所申请,如粒子半径或雾化参数,这取决于哪一个特性被激活。 注意 如果说明的混合权数字小于当前正在被混合的矩阵数目,那么赋给最后的矩阵的权被定义为(1.0-Bt),此处Bt是那个顶点的别的权的的总和。 D3DTRANSFORMSTATE的变化 多矩阵混合也要求三个额外的世界空间变换矩阵的说明。 除了原来的矩阵---D3DTRANSFORMSTATE_WORLD(技术上称为WORLD0),D3DTRANSFORMSTATE_VIEW,D3DTRANSFORMSTATE_PROJECTION---现在有:D3DTRANSFORMSTATE_WORLD1(要混合的第二个矩阵),D3DTRANSFORMSTATE_WORLD2(要混合的第三个矩阵),D3DTRANSFORMSTATE_WORLD3(要混合的第四个矩阵)。 注意这些并不是在原来的D3DTRANSFORMSTATE_WORLD0之后的连续的枚举。 没有被这个调用定义的但是被允许混合的矩阵被认为是和默认的矩阵相同的矩阵。 D3DRENDERSTATETYPE的变化 定义一个新的着色状态以激活和控制多矩阵顶点混合操作:D3DRENDERSTATE_VERTEXBLEND。它接受以下的D3DVERTEXBLENDFLAGS参数: ·D3DVBLEND_DISABLE(仅使用TRANSFORMSTATE_WORLD0) ·D3DVBLEND_1WEIGHT(在两个矩阵间混合) ·D3DVBLEND_2WEIGHTS(在两个矩阵间混合) ·D3DVBLEND_3WEIGHTS(在三个矩阵间混合) ·D3DVBLEND_4WEIGHTS(在四个矩阵间混合) 注意当使用D3DVBLEND_1WEIGHT时,两个矩阵仍被混合,第二个矩阵的权被计算为(1.0-第一个的权)。 即使额外的混合世界空间矩阵也许已经用SetTransform定义,任何权值超过在此着色状态所说明的权的大小的矩阵,其权被设置为0。 4.9.7.2用户裁剪位面 用户定义的裁剪位面在DirectX7.0中使用。这些工作就象其他裁剪位面一样,但是由应用来设置。驱动程序通过在D3dDrawPrimitives2响应D3DDP2OP_SETCLIPPLANE操作码,处理这些位面。 4.9.7.3纹理坐标变换 纹理坐标变换在DirectX7.0中使用。 纹理坐标变换是顶点级别的转换操作。这些操作能被任何变换和允许照明的HAL驱动程序以及任何HAL设备类型执行。 通过定义和每个纹理阶段相联系的4x4矩阵,纹理变换被激活。所有的软件实现几何管道用到的关键状态和数据结构在DDI级可以得到。 使用纹理变换,纹理坐标可相对于它们被绘制的起始点被移动。纹理变换描述了纹理坐标如何在纹理图中移动。每次进行一个纹理变换时,矩阵变换纹理的坐标。标准的世界空间矩阵在这些变换中使用。 在API一级,Idirect3Ddevice3::SetTransform(D3DTS_TEXTUREi,mat)入口点定义了和当前第i个纹理状态绑定的纹理相联系的纹理变换矩阵。这个矩阵应当在着色时适用于那个纹理的纹理坐标。注意同样的顶点级纹理坐标集和一个分开的纹理变换矩阵能用在不同的阶段,这也许适用于不同的纹理。 纹理变换操作被一个由SetTextureStageState API调用(使用D3DTSS_TEXTURETRANSFORMFLAGS标志)设置的激活标志控制。 D3DTEXTURETRANSFORMFLAGS枚举用来控制纹理坐标集的维度,它由纹理坐标变换操作产生;也用来控制任何透视部分是否应当适用于它。 D3DTTFF_DISABLE指示纹理坐标被直接传送给硬件。 D3DTTFF_COUNT* 标志的数字说明会提供多少纹理坐标。注意如果使用映射纹理,它并不必须等于它们自己的纹理维度。 如果使用映射纹理,D3DTTFF_PROJECTED标志被设置以指示纹理坐标被纹理坐标集的最后一个元素(第COUNT个)划分。这样,对一个2D映射纹理,计数(COUNT)将是3,因为前两个元素被第三个划分,导致2D纹理查询是两个浮点数。也就是D3DTTFF_COUNT2和D3DTTFF_COUNT3| D3DTTFF_PROJECTED都引用一个2D纹理。 对于非映射纹理: ·D3DTTFF_COUNT1指示光栅期望1D纹理坐标。 ·D3DTTFF_COUNT2指示光栅期望2D纹理坐标。 ·D3DTTFF_COUNT3指示光栅期望3D纹理坐标。 ·D3DTTFF_COUNT4指示光栅期望4D纹理坐标。 第一个字节是在一个特别的阶段期望使用的纹理坐标的计数值的编码。若将它设为0,即使用SetTransform调用定义了一个纹理变换,也将不会有纹理变换被应用。这是从纹理坐标变换阶段输出的数字。 传给纹理变换的纹理坐标个数由Direct3D API FVF码定义。 D3DTSS_TEXTURETRANSFORMFLAGS的默认是D3DTFF_DISABLED且D3DTTFF_PROJECTED标志没有设置。纹理变换矩阵默认是4x4的一致矩阵。 在非变换和照明HAL设备(即那些要求在主机处理器上进行变换操作)上,输出的顶点以及合适的DDI级的FVF编码被提供给驱动程序,这也许不同于在API级所说明的。不支持硬件加速变换和照明的设备也许仍使用映射纹理,所以驱动程序必须仍要对D3DTSS_TEXTURETRANSFORMFLAGS做出响应。 4.9.7.4立方环境图支持 在Direct3D中多纹理支持允许为照明和反射使用环境图。但是,单图360度解决方案如环形或球形图并不足够健壮,使它能够被广泛用在实时环境中。对于实时产生和引入360度环境的最适合的办法是一个由六个纹理(面)组成的立方图。每个面通过将90度视野的照相机置于合适的方向而产生。每个顶点矢量(常态、反射、折射)提供给光栅化硬件,然后硬件将它们穿过多边形并计算每个立方图面的增加的矢量的交集。如果应用或API产生立方环境图,驱动程序不需要变换矩阵或坐标空间(每个顶点矢量在其中被定义)的信息。这是因为矢量仅被用来引入环境图,它们逻辑上在同一坐标空间。 引入环形图涉及矢量规范化;引入球形图要求使用三角函数。所有类型的单图环境都是非线性的:环形图的外围附近是极度变形和各向异性的,然而球形图在它的轴附近有很大的变形。这就使每次视点改变(中心视区变得扭曲)时必须重建环境图。 立方环境图,通过放置一个真的或者仿真的90度视野的照相机在六个不同的方向而形成,就避免了上述的那些缺点。它们能更快地产生,不需要频繁更新,变形较小,能通过使用类似于为透视矫正纹理映射使用的等价物而引入。 总之,立方图对于为复杂的照明和反射提供实时环境映射是好的选择。使用最广的方法在D.Voorhies和J.Foran所著的Siggraph 94中有详细论述。 使用D3dDrawPrimitives2着色状态机制使立方图传给驱动程序。FVF纹理坐标和它的FVF编码01也被传送。 立方图定义在世界空间坐标;即:它的世界空间变换矩阵是相同的。如果纹理变换被用在相应的纹理坐标索引上,立方图可在不同的空间中出现。这些纹理坐标索引正对者第四面---+z面。Y轴默认向上。u、v格的原点在每个面的左上角,通过从立方的中心放置照相机而不需要任何变换,从而允许面的建立。 DirectDrawCreate接受一个指示立方图要建立的标志。一些面在API级不需分配,尽管驱动程序也许宣称它需要。表面描述符包含一个位域,六位指示出应用期望使用的面。当用GetAttachedSurface枚举面时,面为NULL的被略去。每个面的维度从它的表面描述符中可以得知,面的位域指示它是哪个面。 从DirectDrawCreate返回的指针实际上是指向立方图第一个非空的面的指针。面标识符可以用表面的位编码获得。这就是在CreateTexture使用的指针,以得到一个纹理对象,此对象被传给SetTexture使此图可以在多纹理管道中可用。 如果任何表面想要着色,立方图必须用D3DPTEXTURECAPS_CUBEMAP标志集建立。 任何没有被此调用建立的面都假设为使用在表面描述符的EmptyFaceColor域说明的颜色填充。 注意 (当前限制)所有的立方面必须有相同的大小、面积、2的幂。它们能被mipmapped。 立方图纹理不支持颜色按键。和别的纹理一样,alpha通道和alpha调色板都被支持。 4.9.7.4.1环境映射和标准扩散照明的集成 标准的扩散照明计算能在几何管道中执行。但是,为了获得最好的真实感,反射部分可以被立方环境映射产生。当两者都在操作中时,应用说明的FVF应包含一个为照明计算的矢量和一个独立的3D纹理坐标集(使用在矢量进入立方图时)。 4.9.7.5顶点和像素雾化 雾化有三个主要的类型:线性,指数和指数的平方。有两种主要的实现方法:顶点雾化(也称为重复或局部雾化)和像素雾化(也称为表或全局雾化)。 雾化混合因子f用在所有的计算中。它代表了雾的颜色和对象颜色的比例。最终的颜色由对象颜色和雾化混合因子f的乘积加上雾化颜色和(1-f)的乘积所决定,即:Color=f*objColor+(1.0-f)*fogColor。因此,雾化混合因子为0时,颜色是全雾色;为1时,则是对象色。典型的,f随着距离而减小。图4.6是线性雾化的图。 图4.6 线性雾化 如图4.6所示,线性雾化密度随距离的增加而呈线性增加。这不同于指数雾化,它的雾化密度是指数增加。线性雾化可以如下设置:FOGTABLESTART设置为Zfront且f=1.0;FOGTABLESTART设置为Zback且f=0.0。FOGTABLEDENSITY被忽略。 4.9.7.5.1顶点雾化 顶点雾化使用D3DRENDERSTATE_FOGENABLE着色状态激活。顶点雾化对于大的多边形并不理想,因为顶点离得太远。顶点雾化能用来透视校正。它用于两种方式。第一种使用Direct3D照明码,用D3DVERTEX结构定义雾化混合因子。另一种使用D3DLVERTEX或者D3DTLVERTEX结构。对于建立定制的雾化效果如层次的、基于范围的、体积的等是有用的。 当使用D3DVERTEX结构建立雾化,驱动程序应当设置D3DPRIMCAPS.dwRasterCaps中的D3DPRASTERCAPS_FOGVERTEX标志。用D3Ddevice::SetRenderState将FOGENABLE设为TRUE,FOGCOLOR设为24位RGB颜色。D3Ddevice::SetRenderState被用来设置FOGMODE为LINEAR,EXP,EXP2。对线性雾化,FOGSTART和FOGEND被设置,对指数或指数平方雾化,FOGDENSITY被用D3DLIGHTSTATETYPE设置。 当使用D3DTLVERTEX结构建立雾化,D3DDevice::SetRenderState用来设置D3DRENDERSTATE_FOGENABLE为TURE,设置D3DRENDERSTATE_FOGCOLOR和D3DRENDERSTATE_FOGTABLEMODE为D3DFOG_NONE。(后者在D3DTLVERTEX结构本身中设置。)雾化混合因子f在每个顶点都定义。这是反射RGBA的alpha成分。 这种类型的雾化允许应用使用层次的大气模型,如图4.7所示。 图4.7显示了层次大气模型中纬度和雾的密度的关系。 雾化混合因子在照明阶段计算,并放在顶点的反射色的alpha成分中。然后按照当前由D3DRENDERSTATE_SHADEMODE设置的阴影模式添加进去。 对于顶点V1,V2和V3的雾化混合因子,计算如下: 基于当前阴影模式,f1,f2,f3被内插入三角。如果: src_color是源、被内插的、纹理颜色。 fog_color是当前雾化颜色(被着色状态D3DRENDERSTATE_FOGCOLOR设置)。 f是源、内插的、雾化混合因子。 c,新的颜色计算如下: c=(1-f)*fog_color+f*src_color 这意味着如果雾化混合因子f=0,新颜色c将全是雾化颜色。如果f=1,将没有雾。 在一个顶点中的雾化因子是照相机位置到顶点的距离的函数。通过设置照相机空间的Z值,此距离可以被拉近。对于每个顶点的雾化,使用Mworld*Mview变换顶点分别计算 (在照相机空间),然后计算到顶点的距离。 在RGB模式,雾化因子f的范围从0到255,并被写到反射输出色的alpha成分中。 在Ramp模式,扩散和反射成分与雾化因子f相乘并被限制在0.0到1.0的范围中。 4.9.7.5.2像素雾化 像素雾化类似于顶点雾化,但是雾化混合因子在光栅化时而不是在照明时计算。像素雾画比顶点雾化更准确。在LVERTEX或者D3DTLVERTEX中说明的雾化混合因子被忽略。像素雾化受限于三种雾化类型(线性,指数和指数的平方)。它工作的方式是硬件在插入在每个像素的深度值中做一个表查询。如果在两者间它们被线性的插入,那么无需256个表项。未来版本的DirectX也许会提供非线性、z分布的补充。ZFront/Zback从0到1。通过设置PRIMCAPS.dwPRasterCaps成D3DPRASTERCAPS_FOGTABLE来使用像素雾化。D3DDevice::SetRenderState用来设置D3DRENDERSTATE_FOGENABLE为TURE,D3DRENDERSTATE_FOGCOLOR为24位RGB,D3DRENDERSTATE_FOGTABLEMODE为D3DFOG_LINEAR、D3DFOG_EXP或D3DFOG_EXP2。此处,雾化混合因子按照三个着色状态计算如下: fStart由着色状态D3DRENDERSTATE_FOGTABLESTART决定,范围为[0,1]。 fEnd由着色状态D3DRENDERSTATE_FOGTABLEEND决定,范围为[0,1]。 fDensity由着色状态D3DRENDERSTATE_FOGTABLEDENSITY决定,范围为[0,1]。 雾化混合因子f的计算基于z和上面描述的三个着色状态。实际的计算取决于着色状态D3DRENDERSTATE_FOGTABLEMODE。仅仅D3DFOGMOD_LINEAR使用雾化开始和结束值。 ·D3DFOGMODE_NONE 没有像素雾化被应用。 ·D3DFOGMODE_LINEAR 线性雾化的增长。 ·D3DFOGMODE_EXP 指数雾化的增长。 ·D3DFOGMODE_EXP2 指数平方雾化的增长。 典型的指数和指数平方雾化太过于昂贵而不能直接去做。相反,查询表使用当前雾化密度,在范围[0.0,1.0]的许多z值是事先计算好的,然后最近的表项能为当前z值使用或者一个在两个z值之间的线性混合被用来得到合适的雾化因子。 最终的雾化颜色以和顶点雾化相同的方式计算如下: src_color是源、被内插的、纹理颜色。 fog_color是当前雾化颜色(被着色状态D3DRENDERSTATE_FOGCOLOR设置)。 f是雾化混合因子。 c,新的颜色计算如下: c=(1-f)*fog_color+f*src_color 这意味着如果雾化混合因子f=0,新颜色c将全是雾化颜色。如果f=1,将没有雾。 4.9.7.5.3基于范围的雾化 雾化也可以是基于范围的。基于范围的雾化是有用的,因为在通常的基于z的雾化,一个对象能在视的一侧出现,但是当观察者绕着它旋转,因为它的z值的变化,此对象就消失在雾中。这在图4.8中说明。如果雾化是基于范围而不是深度,当观察者绕着旋转时它不会改变。对于飞行仿真、坦克游戏和别的应用(在此应用中,不希望当观察者绕着旋转时,在一定距离中对象会消失或者重现)这是必须的。为设置雾化为基于范围的,D3DPRASTERCAPS_FOGRANGE和D3DRENDERSTATE_RANGEFOGENABLE应被设置。 图4.8中说明了对基于范围的雾化的需要。注意如果在一个指定的范围内,仅有雾可见,观察者就能旋转而对象不会在雾中进出,就象通常的基于深度的雾化。即:能被看见的对象仍然可见而不管是否旋转。 4.10 Direct7.0发行事项 以下的章节描述了在DirectX7.0中的关键性升级。 4.10.1 FVF 更新 原来定义在DirectX6.0中的FVF编码现在支持在DirectX7.0中的对纹理坐标集的说明(在以下定义)。 除了DirectX6.0支持的常规2D纹理,DirectX7.0支持1D、3D、4D纹理。此外,纹理能被映射。当调用D3dDrawPrimitives2时,能检查D3DNYHAL_DRAWPRIMITIVES2DATA的dwVertexType以决定每个纹理坐标集的维度。 例如,如果一个顶点有五个纹理坐标集,每个纹理可以为1D、2D、3D或者4D,也可以是映射纹理。每个纹理阶段是独立的,所以每个坐标集的维度可以不同。包含在dwVertexType中的FVF编码的上16位被检查以决定每个纹理坐标集的维度。 纹理坐标计数是一个4位的域,范围从0 到8。这给出了在字的上16位给定的纹理坐标集的个数。FVF编码的上16位的每两位被分配给8个纹理坐标集之一。纹理坐标位的含意如下: ·00b 0 u,v 2维纹理坐标对 ·01b 1 u,v,q 3维纹理坐标集 ·10b 2 u,v,w,q 4维纹理坐标集 ·11b 3 u 1维纹理坐标 三维纹理坐标集可为三种不同目的的任何一种使用:映射纹理(D3DTTFF_PROJECTED指定),体积纹理,或者立方图矢量纹理(由一套类似于D3DRENDERSTATE_WRAP0-7模式的着色状态决定。D3DRENDERSTATE_WRAP0-7已在纹理坐标集的基础里说明。)。 为了1D到4D纹理坐标和D3DRENDERSTATE_WRAP着色状态使用的标志分别如下:D3DWRAPCOORD_0(和D3DWRAP_U相同,说明了在u坐标的包装),D3DWRAPCOORD_1(和D3DWRAP_V相同,说明了在v坐标的包装),D3DWRAPCOORD_2(说明了在w坐标的包装),D3DWRAPCOORD_3(说明了在q坐标的包装)。 当使用投影纹理时,从相应的纹理坐标域而不是位置域得到RHW值。然而,位置域的RHW仍然为w缓冲区和模糊计算使用,因此不管哪一个使用都要提供。 4.10.2光栅更新 引用光栅被抽取到一个独立的DLL,使额外的WHOL测试异步地加入普通的DirectX发布循环(一般每季度一次)。它的更新用于支持任何加进API的光栅级操作,API或者在内核中,或者作为要求担保实现一致性的扩展。 光栅产品不能更新用于支持这些技术,因为在软件中运行时,环境在顶点级映射可能比在像素级快。 对于等同的情况,这个光栅在性能的术语中可能已经升级。 第5章 小型客户驱动程序 小型客户驱动程序(MCD:Mini Client Driver)是一个图形接口,其主要的目标是通过简化OpenGL、减少硬件的开销来加速OpenGL。MCD可用于设计所有的硬件加速器特别是高端的3-D硬件加速器。这样,它可以允许不同产品提供商在光栅化级别全面加速OpenGL。 小型客户驱动程序的名称来源如下: “小”指的是这样一个事实,即MCD驱动程序模型只使用了OpenGL的API部分子集用于加速。 “客户驱动程序”则是指一部分OpenGL驱动程序是一个用户模式的动态链接库这样一个事实。确切地说,就是MCD32.DLL这一在用户模式下运行的动态链接库。 本章描述了MCD的体系结构及接口,并对MCD驱动程序的编写人员提供了一个设计及实现的纲要。 由于MCD接口设计用于加速OpenGL,因而所有的MCD实现都必须通过OpenGL相应的测试。全部OpenGL一致性的套件都是随Microsoft Windows 2000 DDK中一起发布的。 5.1 MCD头文件,示例代码及参考 开发MCD驱动程序所需的头文件是mcdrv.h,该头文件包含在Windows 2000 DDK中。实现MCD模型的示例代码应该考虑作为MCD的最根本的定义。Windows 2000 DDK在mga视频显示目录中提供了一个实现MCD驱动程序的示例代码。MCD函数的功能及结构引用可通过DDK在线式的图形驱动程序参考中查找到。 Windows 2000 DDK之外的MCD材料,如MCD的绘制及光栅化,可参考以下资料: ■The OpenGL graphics System:A Specification by Kurt Akeley and Mark Segal(Silicon Graphics,Inc.) ■OpenGL Programming Guide by Neider,Davis,and Woo(Addison-Wesley,1993) ■OpenGL Reference Manual by OpenGL ARB(Addison-Wesley,1992) Windows 2000 DDK中不提供OpenGL专用的设计及实现的细节,因为这些内容都包含在以上枚举的这些材料之中。 Win32相关的MCD接口方面的基本内容,如OpenGL,则可参考平台的SDK文档。 对一些经验欠缺的图形编程人员来说,Computer Graphics:Principles and practice by Foley,van Dam,Feiner,and Hughes(Addison-Wesley,1990)是一本非常有用的一般性图形参考书。 5.2 MCD体系结构 图5-1显示了Windows NT或Windows 2000中图形子系统的小客户驱动程序的体系结构。 插入图5-1 MCD体系结构图。 MCD由下面的部分组成: ■MCD32,由系统提供的,客户端的DLL,由OpenGL装载及调用。它是一个较薄的层次,主要执行数据建立并调用Win32?的ExtEscape函数,并从OpenGL用户模式发送命令到MCD的内核部分。该模块在本文档中也称为客户端MCD。 ■MCDSRV32,由系统提供,是一个由内核模式的显示驱动程序装载的服务器端DLL。MCDSRV32管理着OpenGL与显示驱动程序之间的传输层。也执行大对象管理及MCD驱动程序的参数校验。这些任务需要驱动程序开发者的大量开发工作,即一般常说的设备独立性以及和硬件加速无关的工作。因此,MCD的这种体系结构使得3-D驱动程序的编写人员设计实现健壮的驱动程序更加容易——其中一个关键的设计目标就是因为显示驱动程序是Windows NT/Windows 2000操作系统信任的组件。该模块在本文档中也指的是MCD Library或服务器端MCD。 ■MCD,是指显示驱动程序的MCD部分。整个的显示驱动程序包括MCD部分在内,是由不同的图形硬件厂家实现的。该模块在本文档中指的就是MCD驱动程序。 MCD接口提供了显示驱动程序存取OpenGL光栅化的状态以及被转换的顶点数据。为保证最高性能,在前端(transform/lighting)及MCD之间没有数据的翻译。就是说,MCD能处理的顶点数据量也就是OpenGL能处理的顶点数据量。相应的,MCD接口就需要显示驱动程序能处理浮点数据及操作,但该驱动程序不必使用GDI提供的特殊的FLOATOBJ_Xxx例程。 在处理绘制命令的任一点上,驱动程序都可以向OpenGL返回失败值,这就导致OpenGL必须使用它自已的软件仿真来结束未完成的绘制操作,MCD体系结构允许它利用帧缓冲并直接存取DirectDraw/DCI来进行。这样,便将软件因为返回原点造成的性能损失减少到最小程度,并且免去编写一些硬件不支持操作的软件仿真。另外,能为那些性能可以被改善的情况选择实现软件仿真。 MCD最显著的特征是它不具备位能力,这样便大大简化了驱动程序的实现,因为MCD驱动程序只需要实现用来加速硬件速度的函数。 MCD模块提供了一个语言/编译独立的、可以容易地升级驱动程序以及系统所需服务的实现环境。 5.3 MCD接口 本部分枚举了构成MCD接口的函数,其中一些函数是必须的,有一些是可选的,可选函数是否包含于MCD驱动程序决定于其被优化的功能。 一个MCD驱动程序的DLL不需要导出任何MCD函数。 以下列出支持MCD实现的显示驱动程序中所必须的、某些特定条件下必须的以及可选的MCD函数。 5.3.1必需的MCD函数 下表列出了MCD实现中必需的函数,其函数按字母顺序排列。 必需的函数 描述 MCDrvAllocBuffers 为专用的表面分配缓冲  MCDrvBindContext 将专用的设备环境内容与MCD表面绑定  MCDrvClear 清空所需的缓冲区  MCDrvCreateContext 产生一个新的绘制设备环境  MCDrvDeletecontext 删除一个绘制设备环境  MCDrvDescribePixelFormat 枚举MCD实现中所支持的像素格式  MCDrvDraw 通过MCD实现绘制简单的OpenGL图像  MCDrvGetEntryPoints 提供一个MCD驱动程序的入口点  MCDrvInfo 向MCD调用者提供MCD驱动程序的基本信息  MCDrvSpan 在软件绘制仿真中,传输像素数据  MCDrvState 修改一个绘制设备环境的相关状态  MCDrvTrackWindows 通知表面的驱动程序以及窗口地区的变化  MCDrvViewport 提供具有运行简单剪辑信息的驱动程序   5.3.2特定条件下必需的MCD函数 下表列出了MCD实现中一些特定条件必需的函数,并描述了每一个函数所需的条件。 一定条件下必需的函数 描述 MCDrvGetHdev 对一些专用的接口提供HDEV查询,在 WindowsNT/Windows 2000 MCD驱动程序中是必需的  MCDrvSwap 在前台缓冲显示后台缓冲区中的内容,在支持双缓冲的像素格式中是必需的  MCDrvSync 同步硬件绘制,对那些并不需要等待到硬件完成的从绘制调用返回的驱动程序是必需的   5.3.3可选的MCD函数 下表列出了MCD实现中可选的函数。 可选函数 描述 MCDrvCopyPixels 拷贝一像素的矩形块到当前光栅位置  MCDrvCreateMem 分配驱动程序内存  MCDrvCreateTexture 在驱动程序内存中创建纹理  MCDrvDeleteMem 释放由MCDrvCreateMem所分配的内存  MCDrvDeletetexture 删除由专用的纹理使用的驱动程序分配的资源  MCDrvDescribleLayerPlane 枚举专用的像素格式的分层平面  MCDrvDrawPixels 将一个像素块写入某一特定缓冲区  MCDrvGetbuffers 返回设备的前面、后面及更深的缓冲区的信息  MCDrvPixelMap 建立一个由MCDrvDrawPixels,MCDrvReadPixels及MCDrvcopyPixels所用的翻译表  MCDrvReadPixels 从特定缓冲区中读矩形的像素块  MCDrvSetLayerPalette 对所有的特定分层平面设置调色板  MCDrvTextureStatus 检索特定纹理的信息  MCDrvUpdateSubTexture 修改已经建立的纹理的矩形区域  MCDrvUpdateTexturePalette 修改纹理调色板  MCDrvUpdateTexturePriority 指派或重新指派高效驱动程序管理纹理的高速缓存等特定纹理的优先级  MCDrvUpdateTextureState 将纹理的边界/范围修改成为纹理的对象   5.4 MCD初始化 OpenGL通过装载客户端模块或者调用MCD初始化函数来开始初始化过程。MCD模块用Win32的ExtEscape函数从OpenGL向显示驱动程序发送命令。由于显示驱动程序直接通过DrvEscape函数处理Escape序列,该驱动程序必须能区别这些MCD Escape命令的含义并将它们传送到服务器端的MCD。那些需要MCD处理的Escape命令通过MCDFUNCS给出的Escape值区别开来。而一个MCD驱动程序检查一个Escape必须用它的Escape句柄进行检查。 如果一个escape是MCDFUNCS类型的escape,该显示驱动程序必须检查它是否之前已装载服务器端的MCD。驱动程序必须装载此模块以初始化OpenGL与驱动程序中MCD部分之间的通信层。装载是通过调用具有MCDENGDLLNAME这一常量的EngLoadImage函数来实现的,用已定义的常量可以保证与以后版本的MCD驱动程序的代码兼容性以及跨Windows NT/Windows 2000/Windows 95平台的兼容性。 在服务器端的MCD装载之后,显示驱动程序将通过执行以下步骤初始化其中的MCD部分: 获得服务器端MCD初始化例程的地址。驱动程序要将MCDENGINITFUNCNAME的常量值传递给EngFindImageProcAddress函数以确定初始化函数的地址。 调用上一步骤所确定的初始化例程,并将一个指针传递给MCD驱动程序的MCDrvGetEntryPoints例程。本步骤中,服务器端MCD调用MCDrvGetEntryPoints取回显示驱动程序给所有其他MCD函数提供的指针。 获得服务器端MCD的过滤函数地址。驱动程序将MCDENGESCFILTERNAME常量传递给EngFindImageProcAdress函数以确定过滤函数的地址,驱动程序将所有Escape命令的值等于MCDFUNCS的Escape全部过滤掉。 支持MCD的驱动程序中的DrvEscape例程示例代码可以在mga\escape.c中找到。 在服务器端MCD初始化以及MCD驱动程序的函数表获得之后,OpenGL调用驱动程序的MCDrvInfo函数以获得该驱动程序的基本信息。MCDrvInfo报告一些支持信息,诸如驱动程序支持的MCD版本、驱动程序版本的识别符、一次绘制的大小以及它的DMA能力等等。 5.4.1 Hdev句柄 每一个Windows NT/Windows 2000显示驱动程序都需要一个自已设备对象的句柄,这个句柄就是我们所知道的Hdev。Hdev是由GDI在驱动程序初始化时创建的并且被驱动程序保存以备调用GDI引擎服务时使用。由于服务器端的MCD也进行GDI引擎服务的调用以执行驱动程序对象管理以及内存分配等函数功能,因此,它同样需要设备的句柄。相应的,Windows NT/Windows 2000显示驱动程序必须在MCD初始化时调用MCDrvGetHdev函数之后返回Hdev这一句柄。 5.4.2像素格式 在初始化过程中——也就是在任何的设备环境还没有产生或者绘制还未完成之前,驱动程序被请求决定它可以支持的像素格式。像素格式是MCD驱动程序中唯一可以反映设备硬件能力的部分。 驱动程序的MCDrvDescribePixelFormat函数可以根据调用者专用的iPixelFormat索引返回相应的描述。该信息在pMCDPixelFmt指针的MCDPIXLFORMAT结构中返回,描述了特定格式设备的硬件能力。当驱动程序完成了该像素格式结构的填充,它可以返回它可以支持的总的像素格式的数量,如果失败则返回0值,如遇到不合法的像素格式索引或未完成的MCDPIXELFORMAT结构。如果pMCDPixelFmt指针返回空值(NULL),驱动程序应当返回它可以支持像素格式的总数值,在这种情况下,返回0值并不意味着失败。像素格式标识符应当是一个基于索引的值。 为了支持OpenGL提供的软件仿真,驱动程序的像素格式应当支持下面的能力: ■总的色彩位数(即MCDPIXELFORMAT的成员值cColorBits)必须是6,16,24或32位。 ■总是深度位数(即MCDPIXELFORMAT的成员值cDepthBufferBits)必须是16或32位。 当然,驱动程序也可以支持除上面之外的其他物理缓冲区配置,但是没有上面这些配置,它就不能凭空支持软件仿真。 MCDrvDescribePixelFormat同样标识一个驱动程序格式是否支持分层平面。该函数应该在MCDPIXELFORMAT结构中写出cOverlayPlanes和cUnderlayPlanes的数字以表示该驱动程序可以支持的最大分层平面数和最低数。如果一个驱动程序不支持分层平面则应将这两个值均设为零。支持分层平面的驱动程序,则可用MCDrvDescribeLayerPlane函数返回每一层的详细信息,详细内容可参考分层平面部分的内容。 5.5绘制设备环境 绘制设备环境定义了一个绘制应当怎样发生。一个MCD驱动程序负责生产、管理它自已的绘制设备环境。驱动程序开发者,则必须实现以下列出的设备环境相关的函数。 函数 描述 MCDrvCreateContext 产生一个新的绘制设备环境  MCDrvBindContext 将专用的绘制设备环境与某MCD接口绑定  MCDrvDeleteContext 删除一个绘制设备环境  MCDrvState 修改一个绘制设备环境的相关状态  MCDrvViewPort 提供给驱动程序要执行原始裁剪所需的信息   下面的部分,我们将讨论设备环境相关的问题。 5.5.1设备环境的创建 在驱动程序执行任何的绘制之前设备环境就应该创建,因为当前的OpenGL的状态还没有明确地传递到驱动程序绘制的例程。在设备环境建立时将发生以下一些情况: OpenGL按照默认的色彩规模,视口调节,方向及深度规模等分配并初始化MCDRCINFO结构。 服务器端MCD分配MCDRC结构并初始化iPixelFormat、iLayerPlane以及createFlags成员值。 服务器端MCD调用驱动程序的MCDrvCreaterContext函数,并传递MCDRCINFO和MCDRC结构。 驱动程序的MCDrvCreateContext函数主要执行以下步骤: ■检验驱动程序支持的像素格式以及在MCDRC结构中指定的层次平面。 通过调用EngAlocMem函数分配设备专用的绘制设备环境并初始化绘制用的信息内容。MCDRC结构是驱动程序独立的绘制设备环境,它允许将所有的设备环境信息封装于同一个地方。这样,驱动程序就可以对被创建的驱动程序独立的绘制设备环境存储一个指针,该指针是以MCDRC结构的成员pvUser存储的。 如果说明的表面没有被初始化,则需调用EngAllocMem函数给该表面的特定设备数据分配空间并按需要初始化,对窗口式表面,驱动程序可以在MCDWINDOW结构的pvUser成员中存储一个指针,MCDWINDOWS由服务器端的MCD来分配并维持;它的指针由MCDSURFACE结构中pWnd成员传递给驱动程序,该指针实质就是pMCDSurface指针。当没有别的设备环境和表面绑定时,此表面未被初始化。驱动程序可以检验缓存在MCDWINDOW结构pvUser变量中的信息以决定是否一个窗口式的表面是绑定到设备环境。 1.0版的MCD不支持非窗口式的表面。 驱动程序可以选择性地将绘制设备环境绑定到表面。特别是,绘制设备环境像素格式、创建标识以及如果支持的话,分层平面都将受到表面的限制,随后驱动程序的MCDrvBindContent被调用,绘制设备环境与表面的绑定也推迟到这个时候进行。设备环境产生的表面通常是限定于该绘制设备环境的表面。 如果确定的表面被初始化,则检验该表面设备环境像素格式。一个表面的像素格式一旦受第一次设备环境限制,则设定便不能被改变。相应地,如果驱动程序需要的像素格式与已经存在的表面的像素格式不一致,其设备环境产生也将失败。 将MCDRCINFO结构中的值随意地设为任意值以修改OpenGL计算方式比修改硬件达到此目的更具优势。如果驱动程序需求OpenGL给的不同的值时,一定要多加注意,因为在许多情况下,OpenGL对驱动程序不支持的绘制函数根本不能执行软件仿真。 一个独立的绘制设备环境不足以执行绘制。驱动程序必须还具有绘制的缓冲资源如后续或更深级的缓冲区。但是,驱动程序的MCDrvCreateContext函数并不分配这些资源,因为这由另外一些函数来处理。可以参考在绘制缓存区的帧缓冲管理部分获得更多的信息。 5.5.2设备环境绑定 调用驱动程序的MCDrvBindContext函数可以将已经存在的绘制设备环境与MCD表面绑定,这里,允许具有相同像素格式的表面共享同一绘制设备环境。 MCDrvBindContext函数实现的细节与MCDrvCreateContext实现类似,除非设备环境已经存在。如果没有以前的绑定存在,驱动程序将给新的特定设备表面数据分配地址并设置相应的像素格式,否则,驱动程序应当保证该专用的绘制设备环境的像素格式与表面匹配。如果像素格式不匹配,MCDrvBindContext函数调用失败。可以参考设备环境创建部分获得更详细的内容。 5.5.3设备环境删除 驱动程序的MCDrvDeleteContext函数用于释放任何由驱动程序分配的绘制设备环境数据。驱动程序分配的表面资源,如窗口数据以及绘制缓冲区等则不能在这时时释放,这些资源要等到窗口注销之后释放。 5.5.4状态 客户端MCD将状态信息存储于包含一个或多个MCDSTATE结构的缓冲区中。对于驱动的MCDrvState函数,这个缓冲状态总是在绘制完成以前刷新。 图5-2显示了一个包含两个MCDSTATE结构的状态缓冲区的例子。MCDrvState收到由pStart指针所指的存储于一个连续存块中的所有的状态命令及关联数据。 插入图5-2 MCDSTATE缓冲区 包含MCDSTATE命令缓冲区可能包含任何如下类型的状态: 状态 意义 MCD_PIXEL_STATE 处理包含像素状态的状态  MCD_RENDER_STATE 处理包含的光栅化级别的OpenGL的状态  MCD_SCISSOR_RECT_STATE 处理包含删除的状态  MCD_TEXENV_STATE 处理包含纹理环境状态的状态   MCD驱动程序应该处理所有的状态缓冲区,关联由绘制设备环境所支持的状态。驱动程序必须按它们发生在缓冲区中的顺序处理MCDSTATE命令。在处理任何的状态之前,驱动程序首先应当校验专用的绘制设备环境。 驱动程序可以跳过那些它不加速的状态并且依然返回真值(TRUE),如果驱动程序返回假值(FALSE),将导致所有的绘制失败直至收到一套完整的状态信息之后。否则,驱动程序可能以错误的或者不完全的状态信息运行。 MCDSTATE缓冲由客户端分配,但是驱动程序可以通过从MCDrvInfo的MCDDRIVERINFO结构的drvBatchMemsizeMax成员返回一个较大值,从而影响这种分配。 5.5.5视口 客户端的MCD调用MCDrvViewport提供驱动程序在3-D剪辑所需的缩放比例以及偏移值。驱动程序应当将这一信息存储在它的设备环境信息中,以备用户在以后的剪辑中使用。 5.6内存管理 默认情况下,服务器端的MCD分配数据传输所需的内存块并通过MCDMEM提供驱动程序存取内存。驱动程序的MCDrvCreateMem函数可以通过分配特殊区域支持DMA或者其他设备专用的能力而覆盖默认的分配值。一个驱动程序可以在设备有特殊的能力或需求时实现MCDrvCreateMem功能,例如,设备能够直接处理传递给MCDrvDraw的绘制缓冲区时。 驱动程序可以通过MCDrvDeleteMem释放任何特殊类型的内存。 5.6.1 DMA支持 不需要重新格式化数据缓冲区,只要通过设定MCDDRIVERINFO结构中成员drvMemFlags成员的MCDRV_MEM_DMA标识,驱动程序就可以利用DMA直接处理批次绘制。OpenGL分配好几个数据缓冲区并且允许设备在另一个缓冲区正在被OpenGL填充时处理一个缓冲区。服务器端的MCD库导出MCDEngSetMemStatus以协调驱动程序访问这些数据缓冲区。 5.7帧缓冲区管理 绘制设备环境产生之后,一个表面的像素格式所需要的绘制缓冲区必须在绘制完成之前分配,资源分配则是由MCD驱动程序来处理。 以下部分叙述了绘制缓冲是怎样产生、存取及删除,并且讨论了窗口跟踪的问题。特别是关于以下函数实现的基本指导。 函数 描述 MCDrvAllocBuffers 给专用的表面分配所需要的缓冲  MCDrvGetBuffers 返回设备前面、后面以及更深层缓冲的信息  MCDrvSwap 在前面的缓冲区上显示后面缓冲区的内容  MCDrvTrackWindows 通知驱动程序表面及窗口区域的改变   5.7.1分配绘制缓冲区 如果需要后面或更深层的缓冲区,驱动程序的MCDrvAllocBuffers函数将会决定是否专用的表面已经与这些缓冲区关联。如果需要的缓冲区不存在,该驱动程序将基于设备的像素格式及它们与表面的关联进行分配。缓冲的分配要靠显示驱动程序的中央视频内存堆管理程序来完成。 如果驱动程序成功分配完缓冲区,则它返回真值,如果该缓冲区已经存在,则它依然返回真值。因为,在缓冲区已经存在的情况下,MCDrvAllocBuffers函数仍然调用。否则MCDrvAllocBuffers将会调用失败。 缓冲区可以按每个窗口尺寸或全屏尺寸分配,全屏分配需要驱动程序剪贴所有的绘制缓冲区到这个窗口。 5.7.2查询绘制缓冲区 OpenGL调用MCDrvGetBuffers以获得对软件仿真描述分配绘制缓冲区的存取能力信息。如果该绘制缓冲区不存在,则返回FALSE。否则,驱动程序将根据pMCDBuffers指针所指的MCDBUFFERS结构返回前面、后面及更深层缓冲区的信息。 5.7.3交换绘制缓冲区 支持双缓冲区像素格式的MCD驱动程序一定有一个MCDrvSwap函数,对专用的表面来说,这一函数将后面缓冲区的内容在前面级冲区内显示。考虑到以下的因素,必须实现一种合理的交换机制: 如果绑定于特定表面的绘制设备环境用MCDCONTEXT_SWAPSYNC的标识集产生,则驱动程序应当尽可能同步交换缓冲区的请求与垂直返回显示,这样可以最小化图像撕裂现象,或者可以在一定程度上减少这种现象。 如果MCDrvSwap调用失败,将不提供软件仿真。 5.7.4 MCD中的窗口跟踪 显示驱动程序通过WNDOBJ结构来跟踪一个窗口的变化。在第二章的跟踪窗口变化部分中描述了WNDOBJ结构是如何产生的并且怎样随着对窗口的处理而变化,这里讲解一下特定于MCD的详细内容。 当设备环境被创建或被限定时,服务器端的MCD建立窗口跟踪。它调用GDI的EngCreateWnd函数以建立WNDOBJ并且注册WNDOBJCHANGEPROC回调结构。它注册的回调结构,反过来调用MCDrvTrackWindow例程,同时使得MCD驱动程序可以跟踪窗口变化。 驱动程序允许服务器端MCD处理大多数的窗口跟踪事件,驱动程序必须处理WOC_DELETE来释放所有被删除窗口的表面所占有的资源。 绘制受到剪贴矩形及其他剪辑的影响。驱动程序可以下面三种方式之一执行剪辑。 利用OpenGL提供的MCDWINDOW结构中的成员pClip和pClipUnscissored传递给MCDrvTrackWindows的信息,即剪贴或剪辑的信息。这种情况下,驱动程序可以忽略由MCDSTATE结构中的MCD_SCISSOR_RECT_STATE类型确定的矩形剪贴。 可以利用MCDWINDOW结构中的pClipUnscissored成员以及由MCDSTATE结构中的MCD_SCISSOR_RECT_STATE类型确定的矩形剪贴。 可以构建或者维持它自已的剪辑清单并使用由MCDSTATE结构中的MCD_SCISSOR_RECT_STATE类型确定的矩形剪贴。 驱动程序主要靠跟踪WOC_RGN_CLIENGT来跟踪缓冲区大小的变化。如果设备精度已经改变,则驱动程序必须重新分配所有的表面绘制资源;如果设备精度没有改变,则驱动程序只有在它们与窗口尺寸相依赖时才重新分配缓冲区资源。 MCDrvTrackWindows的pMCDWnd参数一直标志表面信息为空值NULL,即,pMCDWnd对WOC_RGN_SURFACE及WOC_RGN_SURFACE_DELTA一直是NULL值。 5.8绘制操作 一个MCD驱动程序必须实现所有的MCD接口的绘制函数,这些函数在下表中列出: 绘制函数 意义 MCDrvClear 清除特定缓冲区  MCDrvDraw 绘制OpenGL原语  MCDrvSpan 绘制软件仿真时像素数据传输的传输范围   在返回绘制调用之前,一个驱动程序不必等到硬件结束绘制就必须对OpenGL的同步机制实现MCDrvSync函数。 5.8.1绘制OpenGL的原语 所有的OpenGL原语是按批次传递给MCDrvDraw的。批次传送时包含一个或多个MCDCOMMAND结构,每个结构包含OpenGL原语类型、顶点指针、到下一原语的指针等信息。任意的OpenGL原语都可以结合在一起出现于命令缓冲区中,尽管驱动程序永远都不会被请求进行直线的循环绘制(GL_LINE_LOOP),那是因为OpenGL将它们翻译成为线条(GL_LINE_STRIP)。 图5-3 说明了部分示例命令缓冲区可以传送到MCDrvDraw。 pStart所指示的指针包含两个MCDCOMMAND结构用于驱动程序处理。第一个MCDCOMMAND结构包含按次序排列的MCDVERTEX结构;第二个包含一个块,这个块列出了所有发现的MCDVERTEX结构按指针索引的列表。所有的命令和它们相关的顶点数据都存储于同一个缓冲区中。 驱动程序在MCDrvDraw函数中主要执行以下步骤: 在处理任何原语之前,检验专用的绘制设备环境及窗口,检查窗口是否没有完成剪辑。 按顺序处理每一个MCDCOMMAND结构直到任一个或全部命令绘冲区被处理,或者驱动程序控制软件仿真处理余下的缓冲区。如果命令缓冲区中的原语被专用于软件描述,则驱动程序不能处理它们。 驱动程序在处理每个MCDCOMMAND结构时必须考虑以下问题: flags成员意指是否挑选出来的原语是一个单色,或者它的线条需要重新设置。 clipCodes成员提供了在原语中所有顶点的剪辑代码集合。驱动程序可以用这一信息来测试整个原语的接收或拒绝信息。 如果pIndices成员值为NULL,则原语在MCDVERTEX结构中按次序批次处理。pStartVertex指针指示MCDVERTEX结构的开始,则驱动程序应当按顺序单步执行MCDVERTEX结构。如果pIndices值非空,原语将按根据MCDVERTEX结构中UCHAR的最大索引顺序批次处理。在pStartVertex[pIncices[0]]指示的MCDVERTEX结构,驱动程序能够按顺序处理这些索引,并根据索引指示存取MCDVERTEX结构。 如果驱动程序支持纹理,它应该提供纹理的签别符textureKey,如果不支持纹理,则绘制时需根据原语。 在原语绘制时,状态变化是禁止的,驱动程序应使用绘制设备环境所提供的相关状态信息。 OpenGL对所有绘制都提供软件仿真,以下情况除外: 由于OpenGL不支持的像素格式导致软件仿真不能完成。 色彩值及MCDRCINFO结构中顶点可变值已经被驱动程序修改时软件仿真不能完成。 MCDrvInfo在初始化过程中调用时,MCD驱动程序可以覆盖OpenGL的批次处理的默认值,即MCDDRIVERINFO结构中的drvBatchMemSizeMax成员值。 MCD驱动程序不需要处理OpenGL的结构如顶点数组、显示列表及选择等等。 5.8.1.1每个最大值的考虑 驱动程序在处理每个MCDVERTEX结构时应当考虑如下几点: 所有顶点指针都必须是验证为有效的,当被说明时,所有顶点的索引也必须是验证有效的。 MCDrvDraw函数必须注意防止未屏蔽浮点数异常问题,所有坐标值及色彩值都说明为浮点值。 MCDrvDraw应当提供专用的色彩范围、视口调节、方向、深度范围值等以适应MCDVETEX的成员。这些值是由OpenGL在设备环境创建时建立的,MCDrvCreateContext函数具有是否将它们覆盖到MCDRCINFO结构的可选功能,但是驱动程序却不是依赖OpenGL来为绘制执行软件仿真。 默认情况下,OpenGL提供了一个在windowCoord函数中指定窗口座标的优选项,从而提供从浮点数向固定点数(float-to-fixed-point)的完全转换。这一优选项在MCDRVINFO结构中的viewportXAdjust及viewportYAdjust成员说明。 5.8.2绘制变化范围 图5-4显示了MCDrvSpan是怎样取变化范围信息及内存块的。 插入图5-4 pMCDSpan参数指向一个MCDSPAN结构,该结构的pPixels成员指向第一个像素的区间。pMCDMem参数指向一个MCDMEM结构,它的pMemBase成员指向内存块的开始位置。要注意像素数据并不需要在该内存块的开始位置,由服务器端的MCD校验第一个像素是否在即定的内存区域内,驱动程序并负责检查最后的像素是否也是在区间范围之内。 在实现MCDrvSpan函数时,应当考虑以下问题: 校验绘制设备环境及表面。 校验传输的像数数据不是负数。 根据读到的(或者被写的)第一像素到最后像素计算在适当的缓冲区的位置,并根据需要剪辑相关区间。 在仿真过程中或出现下边的任一种情况,将调用MCDrvSpan: 不能获得对软件OpenGL的帧缓冲区的存取控制。 对软件OpenGL的帧缓冲区的存取控制可以获得,但是帧缓冲区的格式与仿真不兼容。 MCDrvSpan函数每次调用时收到一个区间。对所有调用的完成,MCDrvSpan只提供部分的接口。 5.8.3清除缓冲区 MCDrvClear可以在绘制设备环境中用当前的值清除由buffers参数指定的缓冲区。在指定的缓冲区可以是色彩、深度、图案等的缓冲区的结合。 5.8.4同步化 驱动程序不必等待硬件完成绘制操作,并必须在返回绘制调用之前对OpenGL以同步机制实现MCDrvsync函数。驱动程序可以利用覆盖CPU及硬件应用等来实现此例程调用的最大性能。 MCDrvSync应当保证所有的硬件绘制在返回给调用者之前完成并实现刷新。 驱动程序如果回复软件仿真,则它将负责同步。但是,OpenGL在执行任何的像素功能前先调用MCDrvSync函数。 5.9纹理映像 MCD驱动程序开发者可以任选以下的纹理映像函数来实现。 纹理映像函数 意义 MCDrvCreateTexture 创建驱动程序边(driver-side)的纹理  MCDrvDeleteTexture 删除任何由特定纹理使用的驱动程序分配的资源  MCDrvTextureStatus 检索特定纹理的信息  MCDrvUpdateTexturePalette 修改已经创建的纹理的矩形区域  MCDrvUpdateTexturePalette 修改特定纹理的调色板  MCDrvUpdateTexturePriority 对特定纹理分配或重新分配优先值  MCDrvUpdateTextureState 修改限定于纹理对象的纹理   在纹理映像中有两种相关的状态,即:纹理环境状态及纹理状态,这些将在下面部分讨论。 5.9.1纹理环境状态 MCD驱动程序可以利用纹理环境状态来决定纹理在绘制过程中是怎样应用的。在同一个时间OpenGL中可以存在好几个纹理,但是只有当前的纹理可以在绘制时被原语应用,正好是纹理环境状态应用的这一纹理。 MCDrvState由MCDTEXENVSTATE结构中收到纹理环境状态,当输入状态缓冲中包含一个MCDSTATE结构而它的state成员正好用于设置MCD_TEXENV_STATE。可以参考5.5.4状态部分学习驱动程序如何收到并处理状态的详细信息。 5.9.2纹理状态 纹理状态受限于专用的纹理,而不管它是否是当前纹理。图5-5显示了定义纹理状态的MCD结构的帧框架。 插入图5-5纹理状态结构 MCDTEXTURE是包含所有纹理信息的最上层的结构,所有的MCD纹理信息用MCDTEXTURE结构来存取每一个纹理的数据。驱动程序可以改变以下成员: 驱动程序的MCDrvCreatTexture函数的成功调用必须产生并且返回一个单一的textureKey成员的成功标识符,无论它是否是装载的纹理,这个标识符随后便在绘制时传递到MCDCOMMAND结构的textureKey成员中。 一个驱动程序可以根据需求使用userFlags和pvUser成员并可以随时修改这些成员。 pSurface和createFlags当前保留为系统使用。OpenGL提供并管理所有的纹理状态。也就是,驱动程序不能通过pMCDTextureData结构改变任一纹理的状态。由于MCDTEXTUREDATA结构在客户端空间来存放,驱动程序必须通过EngProbeForRead函数利用try……except在存取它的数据之前对其进行校验。校验必须包含任意的可通过MCDTEXTUREDATA结构指针存取的纹理数据。 MCDTEXTUREDATA结构包含以下信息: 在TextureDimension中表示的纹理的维数。 纹理调色板数据主要用于解释在paletteSize、paletterBaseFormat以及paletteRequestedFormat中的纹理色彩。纹理调色板中的一系列条目都可以通过驱动程序的MCDrvUpdateTexturePaletter函数来修改,也可以用于调色板动画中。 所有的每个纹理状态,由textureObjState在MCDTEXTUREOBJSTATE结构说明。这包括纹理的包裹模式、边界颜色和过滤信息。MCDrvUpdateTextureState在和纹理关联的状态改变时调用。驱动程序相应地更新任何驱动程序的纹理状态。 每个纹理对象的状态都由textureObjState函数中的MCDTEXTUREOBJSTATE给定的,它包括纹理对象的标识符、优先权。客户端的MCD调用MCDrvUpdateTexturePriority给驱动程序一个优先权的提示。驱动程序可以在对纹理进行高速缓存时利用这一信息。MCDrvTextureStatus则查询是否驱动程序在设备内存中对其进行了高速缓存。 机器指令处理映像的信息则在level中保存。Level指向了一个MCDPIPMAPLEVEL结构的数目。每个结构提供一个指向文本数据以及其他可获得的机器指令处理器层的信息的指针。最大的纹理总是可以通过MCDMIPMAPLEVEL的第一个结构(level[0])获得。接下来的每个纹理的维数则一半是水平排列的另一半是垂直排列。如果其中的一个维数是1,即该维数一直处于1直到最高层的机器指令处理器映射达到1*1时。而每个纹理的大小则常常是2的幂。 当MCDTEXTURESTATE的成员minFilter或magFilter分别不等于GL_LINEAR或者GL_NEAREST时,机器指令处理器映像是允许的。如果机器指令处理器映像允许,并且所有的机器指令处理器映像层次都不在level指针所指的块中,则纹理的绘制必将失败。 5.10像素操作 MCD驱动程序开发者可以选择性地实现下列像素函数: 像素函数 意义 MCDrvCopyPixels 拷贝一个像素块到当前的矩形块  MCDrvDwawPixels 将一个像素块写到一个特定缓冲区中  MCDrvPixelMap 建立起翻译表或映像,它由MCDrvDrawPixels、MCDrvReadPixels和MCDrvCopyPixels使用  MCDrvReadPixels 从一个专用的缓冲区中读矩形的像素块   驱动程序实现的像素函数常常比软件仿真实现更好的加速功能。如果驱动程序的像素函数调用失败,OpenGL一般通过软件仿真来实现这些操作。 5.10.1像素状态 驱动程序在执行像素操作时应当使用当前的像素状态。驱动程序不能以任何方式改变像素状态。 当输入状态缓冲区中包含一个MCDSTATE结构而它的state成员设置为MCD_PIXEL_STATE时,MCDrvState函数可获得在MCDPIXELSTATE结构中的像素状态。参考5.5.4状态部分,了解驱动程序如何接收并处理状态的详细信息。 图5-6显示了MCD结构定义像素状态的框架 插入图5-6 像素状态结构 MCDPIXELSTATE是一个包含所有像素状态的顶层结构,它包括以下信息: 像素的传输状态。在MCDPIXELTRANSFER结构的pixelTransferModes指定这一状态。该状态确定了驱动程序的MCDrvCopyPixels,MCDrvDrawPixels以及MCDrvReadPixels函数应当怎样将像素块从帧缓冲的一部分传输到另外一部分,从帧缓冲区传输到客户端内存,或各自从客户端内存传输到帧缓冲区。 像素的打包状态。在MCDPIXELPACK结构的pixelPackModes指定这一状态。这一状态确定了驱动程序的MCDrvReadPixels函数应当怎样对从帧缓冲区读到的数据进行格式化并在被返回的像素阵列中存储。 像素的解包状态。在MCDPIXELUNPACK结构的pixelUnpackModes来指定,这一状态确定了驱动程序的MCDrvDrawPixels函数应当怎样从收到的像素阵列中读取数据。如果MCDrvDrawPixels以packed值等于TRUE来调用,则驱动程序应当按以下表格的值覆盖MCDPIXELUNPACK中的指定值。 MCDPIXELUNPACK成员 覆盖值 swapEndian FALSE  lsbFirst FALSE  lineLength Width  skipLines 0  skipPixels 0  alignment 1  ReadBuffer中的读缓冲区。该缓冲区被驱动程序的MCDrvCopyPixels和MCDrvReadPixels函数用作源缓冲区。 当前光栅位置,即rasterPos。它指定了驱动程序的MCDrvCopyPixels或MCDrvDrawPixels函数绘制缓冲区时指定像素块的左下角。它在窗口的坐标系中指定。 在用户模式的内存中,传递给MCDrvDrawPixels的指针参数pPixels指向一个像素块。驱动程序在读取任何数据之前,必须通过调用包含try……except从句的EngProbeForRead函数来检验其读取位置的有效性。类似的,驱动程序也必须在写任何数据之前,通过调用包含try……except从句的EngProbeForReadAndWrite。像素数据也是在窗口的坐标系中指定的。 5.11分层平面 驱动程序开发者可以选择性地实现以下分层平面函数: 重叠平面函数 意义 MCDrvDescribeLayerPlane 枚举MCD实现中支持的特殊像素格式的所有分层平面  MCDrvSetLayerpaletter 对专用的分层平面设置物理调色板的值   在MCD驱动程序中,所有的分层平面支持有选择的实现。软件仿真不支持分层平面,因此在驱动程序中实现分层平面支持的程序开发者必须处理驱动程序中所有的操作。 本部分接下来的内容指定驱动程序怎样描述它的分层平面并设定分层平面调色板。 5.11.1描述分层平面 任何的设备环境产生或绘制完成之前,驱动程序的MCDrvDescribePixelFormat函数调用以确定它支持的像素格式。像素格式是MCD驱动程序唯一暴露设备硬件能力的部分。驱动程序在MCDPIXELFORMAT结构中以如下的内容指出它所支持的像素格式: cOverlayPlanes成员指定了由驱动程序像素格式提供的重叠的平面的数量。 cUnderlayPlanes成员指定了由驱动程序像素格式提供的其下面的平面数量。 dwTransparentColor成员指定了与一像素格式相关的这一分层平面指定的透明式的色彩索引。 dwFlags成员中指定的PFD_SWAP_LAYER_BUFFERS标识指示分层平面的交换缓冲区的特征,当设备分层平面之间可以独立交换而不是作为一个整体交换时,驱动程序应当设置该值。 如果驱动程序支持分层平面,MCDrvDescribeLayerPlane将通过填写MCDLAYERPLANE结构中的专用的分层平面来指定其特征。 MCDrvCreateContext函数处理设备环境产生并使其可以绘制分层平面。可以参考设备环境的产生部分以获得详细资料。 5.11.2交换分层平面 驱动程序的MCDrvDescribePixelFormat函数指示了通过设置PFD_SWAP_LAYER_BUFFERS,即MCDPIXELFORMAT中的dwFlags位,来实现对独立交换分层平面的支持。若驱动程序的MCDrvSwap函数随后调用,则它应当考虑以下内容: 如果没有设置PFD_SWAP_LAYER_BUFFERS,则驱动程序应当同时交换所有的层,包括主层,而不管传递给MCDrvSwap的flags参数的值。 如果设置了PFD_SWAP_LAYER_BUFFERS,则 flags指定了那些平面之间应当交换。驱动程序可以忽略不支持的平面的flags。如果flags为零,驱动程序则应同时交换所有的层,包括主层,而不管PFD_WAP_LAYER_FUBBERS是否设置。也就是说,驱动程序应当将flags等于零当作flags的所有位都被设定来看待。 5.11.3管理分层调色板 OpenGL负责管理分层模板。驱动程序的MCDrvSetLayerPalette函数只有在需要实现调色板时才调用。MCDrvSetLayerPalette常常在一个具有分层平面的窗口接收到前台的程序初始化时的通知消息时才调用。 MCDrvSetLayerPaletter应当一对一映射专用的分层调色板到物理的调色板,也就是,对专用的调色板数据不需要翻译。驱动程序应当支持RGB和色彩索引调色板。 pPaletterEntries指针所指向的调色板缓冲区保证是校验有效的,因此,MCDrvSetLayerPaletter不需要再对之进行校验。 第6章 视频微端口驱动程序 Windows NT/Windows 2000视频微端口驱动程序(Video Miniport Driver)是特定适配器的、内核模式的驱动程序。每一种可在Windows NT/Windows 2000中应用的图形卡都必须有视频微端口驱动程序和显示驱动程序。 视频小驱动程序将它自身链接到视频端口驱动程序上,视频端口驱动程序是一个系统提供的内核模式的动态链接库(DLL)。视频端口驱动程序的VideoPortXxx函数调用微端口驱动程序来与系统及它的硬件进行通信。 Windows 2000微端口驱动程序必须以即插即用(PnP:Plug and Play)的驱动程序来实现。Windows 2000视频端口驱动程序同时继承性地支持Windows NT 4.0驱动程序。 本章提供了视频微端口驱动程序实现及编写的详细资料,描述了微端口驱动程序与显示驱动程序以及视频端口驱动程序的交互操作。对所有视频端口和微端口驱动程序的函数参考可以在图形驱动程序参考的在线DDK中查到。 显示驱动程序将主要在第一章显示介绍及第二章显示驱动程序中讨论。 6.1视频微端口头文件、示例代码及参考 Windows NT/Windows 2000视频微端口驱动程序包括下列头文件: 文件 内容 dderror.h 由微端口驱动程序返回给视频端口驱动程序的win32状态常量,也可以返回到与微端口驱动程序对应的内核模式显示驱动程序  devioctl.h 用于定义I/O控制代码的宏及常量  miniport.h 视频微端口和SCSI微端口驱动程序所需的基本类型、常量和结构  ntddvdeo.h 系统定义的I/O控制(IOCTLs)以及相应的结构等,可以按视频请求包VRPs传送到视频微端口驱动程序。  tvout.h 用于实现TV连接以及拷贝保护支持的VIDEOPARAMETERS结构以及此结构中使用的常量  video.h VideoPortXxx和SvgaHwIoPortXxx视频端口函数声明。视频专用的结构如VIDEO_REQUEST_PACKET以及HwVidXxx视频微端口函数原型  videoagp.h AGP专用的结构,AgpXxx微端口驱动程序函数原型。以及支持视频微端口驱动程序的实现图形端口(AGP:Accelerated Graphics Port)加速所需的VideoPortXxx函数声明   这些头文件与Windows 2000 DDK一起发布。需要包含在这些头文件中的函数、结构、系统定义的I/O控制代码、常量的更详细信息,可参考在线DDK中图形驱动程序参考部分的内容。 Windows 2000 DDK同样提供好几个视频微端口驱动程序工作的实例代码,其内容放在video\miniport目录下。3dlabs微端口驱动程序实例实现了广泛的功能;s3virge微端口驱动程序实例则实现了VPE;mirror实例则包含该驱动程序专用的微端口驱动程序实现。 6.2在图形体系结构中的视频微端口驱动程序 图 6-1显示了Windows NT/Windows 2000图形子系统中的视频微端口驱动程序。 插入图6-1 Windows NT/Windows 2000图形体系结构 每一个视频微端口驱动程序提供硬件级的显示驱动程序支持,显示驱动程序调用图形引擎函数EngDeviceIocontrol请求下面的视频微端口驱动程序支持,并依次调用一个I/O系统服务以通过视频端口驱动程序向微端口驱动程序发送请求。 在大多数情况下,显示驱动程序完成对用户可见的时间关键的操作,而其下层的微端口驱动程序则提供对非经常性请求操作的支持,或者提供对不能被中断或设备环境转换到另外一个进程所剥夺的真正的时间关键性操作的支持。 显示驱动程序不能支持设备中断,只有微端口驱动程序可以建立起设备内存并将它映射到显示驱动程序的虚拟地址空间。 视频端口驱动程序是系统提供的用于支持视频微端口的驱动程序模块,并充当显示驱动程序与视频微端口驱动程序之间的中介。 要获得更多的Windows NT/Windows 2000的显示驱动程序的信息,参考第一章显示介绍及第二章显示驱动程序中的内容。 6.2.1视频微端口驱动程序特定平台的详细内容 在基于x86的Windows NT/Windows 2000平台,有两种视频微端口驱动程序:非VGA兼容微端口驱动程序和VGA兼容微端口驱动程序。 6.2.1.1非VGA兼容、基于x86的微端口驱动程序 许多微端口驱动程序是非VGA兼容的并因此非常容易实现。非VGA兼容的视频微端口驱动程序依赖于系统提供的VGA微端口驱动程序(vga.sys)或另外一个当前装载的VGA兼容的SVGA微端口驱动程序。这些微端口驱动程序的设置是在registry注册文件中将vgacompatible设置为零(FALSE)并且具有以下特征: 它对全屏的、基于X86机器的MS-DOS应用程序不支持。但是,它是随着系统提供VGA(或者可能是VGA兼容的SVGA)微端口驱动程序的装载来提供对全屏的MS-DOS应用程序的支持。 在大多数情况下,它是对没有VGA兼容模式适配器而编写的或者是为独立工作于VGA的加速器而编写的。 6.2.1.2VGA兼容的基于x86的微端口驱动程序 VGA兼容的微端口驱动程序主要包括以下特点: 它基于系统提供的VGA微端口驱动程序,可以修改一定的代码来支持特定适配器的特性。系统提供的VGA显示驱动程序利用VGA兼容的微端口驱动程序支持,这样VGA兼容的适配器的微端口驱动程序开发者就不需要再编写新的显示驱动程序。 它提供全屏MS-DOS应用程序的支持可以直接对适配器寄存器进行I/O操作。它同时作为一个视频检验器以防止这些应用程序发布任何使系统中止的指令序列。 许多SVGA适配器的微端口驱动程序也归入此类。但是对非SGVA适配器的任何新的微端口驱动程序可以根据驱动程序设计者的判断来提供VGA兼容支持。 VGA兼容的微端口驱动程序可以由在注册表中的vgacompatible设置成为1(TRUE)来配置。 6.3视频微端口驱动程序接口 接下来的部分枚举了构成视频微端口驱动程序接口的函数,一些函数是必需的,其他的函数有的是有条件的,有的是可选择的。而它们在微端口驱动程序中的包含则取决于适配器的特性和驱动程序的开发者。 除了DriverEntry函数,本文档建议对每一个必需的标准的微端口驱动程序都用假名。换句话说,即给出的每一个标准的微端口驱动程序除DriverEntry之外都可以由微端口驱动程序的开发者任意选择。 6.3.1必需的微端口驱动程序函数 以下系统定义的函数必须在每一个视频微端口驱动程序中实现。除了DriverEntry函数,所有其他的微端口驱动程序都是按字母顺序排列的。 必需的函数 描述 DriverEntry 初始化视频微端口驱动程序  HwVidFindAdapter 获取设备可枚举的总线的范围,如果可能,确定设备的类型。  HwVidGetPowerState 查询设备是否可以支持需要的电源状态  HwVidGetVideoChildDescriptor 枚举附加于特定设备的子设备  HwVidInitialize 执行以前的对于相应的显示驱动程序的适配器的初始化。该函数调用是作为打开适配器的请求的响应。  HwVidSetPowerState 设置特定设备的电源状态  HwVidStatrtIO 开始处理进入的VRP   6.3.2一定条件下必需的视频微端口驱动程序函数 下面的系统定义的函数是在一定条件必需的视频微端口驱动程序函数。依赖于给定的适配器的特征及驱动程序的设备。它们同样按字母顺序排列: 一定条件下必须的函数 描述 HwVidInterrupt 处理适配器产生的中断,如果微端口驱动程序的适配器产生中断则该函数是必需的。  HwVidResetHw 重新设置适配器到字符模式,这样HAL可以在系统被控制宕机或崩溃时显示系统的关闭信息,或者在软启动时显示初始化信息。如果适配器的寄存器不能自动设定为INT 10复位,该函数是必需的。  HwVidSynchronizeExecutionCallback 同步存取由HwVidInterrupt中断所共享的数据。允许关键部分的代码以更高的软件或硬件优先级别运行(IRQL,具有设备或系统中断屏蔽)。如果与HwVidInterrupt小驱动程序函数共享,则它是必需的。   6.3.3可选的视频微端口驱动程序 下面的系统定义的函数可以在视频微端口驱动程序中选择性地实现,函数以字母顺序排列。 可选函数 描述 HwVidLegacyResources 返回一个特殊设备的继承资源  HwVidQueryDeviceCallback 和/或 HwVidQueryNamedValueCallback 处理存储在寄存器中的配置信息。该函数在小驱动程序的HwvidFindAdpter函数利用I/O总线类型专用的配置信息或驱动程序提供的默认值时是可选的。  HwVidQueryInterface 返回一个微端口驱动程序实现的函数接口,该函数接口可以由子设备来调用  HwVidTimer 由视频微端口驱动程序大约隔一秒钟周期性调用一次  SvgaHwIoPortXxx 函数 由基于x86的全屏的MS-DOS应用程序校验存取I/O端口的有效性,这些函数是对VGA兼容的SVGA微端口驱动程序。可以参考VGA兼容的微端口的SvgaHwIoPortXxx获取更多的信息。   6.4视频微端口驱动程序初始化 视频微端口驱动程序初始化发生在NT内核、HAL及核心驱动程序如PCI总线驱动等装载及初始化之后。基本的系统初始化发生顺序如下: NT内核及HAL加载并初始化。 内核驱动程序如PCI总线驱动程序加载并初始化。 PCI总线驱动程序从每个子PCI的配置空间获得PCI资源信息及设备ID、供应商ID,并将这些信息报告给系统。 如果PnP管理识别出设备或供应商的ID,I/O管理器将从已知的位置加载相应的视频微端口驱动程序和视频端口驱动程序。如果PnP管理器不能识别出这些ID,则它给用户提示加载微端口驱动程序的位置。 I/O管理器用两个系统提供的指针调用微端口驱动程序的DriverEntry例程。DriverEntry用驱动程序特定及适配器专用的值分配并初始化一个VIDEO_HWINITIALIZATION_DATA结构,包括指向微端口驱动程序其他条目的指针。DriverEntry也必须声明其他继承资源,这些资源没有被列入设备PCI配置空间但是由设备进行解码。可参考声明遗资源部分获得更详细信息。 微端口驱动程序的DriverEntry函数调用VideoPortInitalize函数。VideoPortInitialize执行微端口驱动程序初始化方面的工作,这是对所有的微端口驱动程序者非常一般性的工作。例如,对非即插即用的驱动程序,VideoPortInitialize验证微端口驱动程序初始化的VIDEO_HW_INITIALIZATION的结构,初始化一些系统产生的设备对象的公有成员,并对这些设备对象及扩展对象分配内存,收集并存储相关的扩展设备的信息。可以参见视频微端口驱动程序扩展部分获得更详细的信息。对即插即用的驱动程序来说,设备的对象相关动作则发生在以后的时间里。 如果VideoPortInitialize返回,DriverEntry将其返回值再传播给VideoPortInitialize调用者。微端口驱动程序开发者应当对VideoPortInitialize的返回值不作任何的假设。 在这一点上来说,系统已经装载并且初始化了视频微端口驱动程序。下一步就是PnP管理器启动设备,可参考启动视频微端口驱动程序部分了解详细内容。 6.4.1启动视频微端口驱动程序 PnP管理器向视频端口驱动程序发送IRP消息请求启动图形适配器,视频端口驱动程序分派HwVidFindAdapter例程对IRP响应,HwVidFindAdapter的详细任务的讨论将在本部分的后面内容中叙述到。 6.4.1.1建立视频适配器存取区间 6.4.1.2设置注册表中的硬件信息 6.4.1.3改变适配器中的状态 6.4.1.1建立视频适配器存取区间 一个VIDEO_ACCESS_RANGE类型的元素数组描述一个视频适配器解码内存和/或I/O端口的一个或多个区间,在这个数组中的每个存取区间元素都包含总线相关的物理地址值。 微端口驱动程序例程HwVidFindAdapter例程必须声明所有的PCI内存及端口或者适配器可以响应的所有区间的端口。依赖于适配器及在VIDEO_PORT_CONFIG_INFO中的AdapterInterfaceType值,HwVidFindAdapter可以调用下面VideoPortXxx函数以得到必要的总线相关的配置数据: VideoPortGetAccessRanges VideoPortGetBufData VideoPortGetDeviceData VideoPortGetRegistryParameters VideoPortVerifyAccessRanges 如果HwVidFindAdapter不能通过调用VideoPortGetBusData或VideoPortGetAccessRanges函数,或者利用VideoPortGetDeviceData或VideoPortGetRegistryParameters函数从寄存器中得到总线相关的可存取区间的相关信息,微端口驱动程序应当有一套总线相关的存取区间的默认值。 小驱动程序试图与适配器通信之前,HwVidFindAdapter函数应当映射每一个声明的总线相关的物理地址区到用VideoPortGetDeviceBase函数得到的内核模式的地址空间的区间。HAL能够将总线相关的存取区间值重新映射到系统地址的逻辑地址区间,尤其是在多条总线的机器中。 利用映射的逻辑区间地址,驱动程序可以调用VideoPortReadXxx和VideoPortWriteXxx函数以读或写到一个适配器,这些内核模式的地址也可以被传递到VideoPortCompareMemory,VedeoPortMoveMemory,VideoPortZeroDeviceMemory和/或VideoPortZeroMemory。对一个被映射的I/O空间中的区间来说,微端口驱动程序调用VideoPortReadPortXxx和VideoPortWriterPortXxx函数。对一个在内存中映射的区间来说,微端口驱动程序调用VideoPortReadRegisterXxx及VideoPortWriterRegisterXxx函数。 HwVidFindAdapter函数应当总是在调用VideoPortGetDeviceBase函数之前成功调用VideoPortVerityAccessRanges或者VideoPortGetAccessRanges。 任何对VideoPortVerifyAccessRanges或VideoPortGetAccessRanges的成功调用将建立起一个微端口驱动程序的声明,即声明特定总线的视频内存和寄存器地址或适配器在注册表中的I/O端口。非常值得注意的是接下来对VideoPortVerityAccessRanges或VideoPortGetAccessRanges的任何顺序的调用都会导致驱动程序以前声明的资源被删除或者传送给最近调用函数的值所代替。因此,如果驱动程序单独调用函数声明区间,它必须传递到所有区间的数组,包括那些已经声明的区间。 HwVidFindAdapter可以从适配器要求一小部分的存取区间,利用这一小部分区间来决定该适配器是否是微端口驱动程序支持的,并且利用另外一个函数调用VideoPortGetAccessRanges或者VideoPortVerityAccessRanges要求一个全部的对适配器支持的存取区间。同样的,对某专用的适配器的这些VideoPort…AccessRanges例程的成功调用,都会导致调用者以前在注册表中声明的信息覆盖。 声明其他类型的硬件资源,如中断向量,一个微端口驱动程序应当在VIDEO_PORT_CONFIG_INFO中设置合适的值并调用VideoPortVerityAccessRanges或它应当调用VideoPortGetAccessRanges函数。 对VideoPortGetAccessRanges或VideoPortVerifyAccessRanges的成功调用保证了微端口驱动程序不去使用已经由另外一个驱动程序使用的寄存器或设备内存地址。 在寄存器中声明适配器的总线相关硬件资源,防止后来加载的驱动程序企图利用其他适配器的同一存取区间(即其他硬件地址)。它同样防止按顺序装载的驱动程序更改视频适配器的初始状态。 硬件解码的微端口驱动程序继承资源时必须通过它的DriverEntry例程来声明,或者,如果实现,则必须通过HwVidLegacyResources例程,继承资源是那些没有列入设备的PCI配置空间但由设备解码的资源。可以参考声明继承资源部分获得详细内容。 在微端口驱动程序装载之后,它的HwVidInitialize函数便已经运行,微端口驱动程序的HwVidStartIO函数调用,以映射那些微端口驱动程序对显示驱动程序可见的视频内存存取区间。 6.4.1.2在注册表中设置硬件信息 HwVidFindAdapter可以调用VideoPortGetRegistryParameters及VideoPortSetRegistryParameters函数来获得并设置注册表中的配置信息。例如,HwVidFindAdapter可以调用VideoPortsSetRegistryParameters来设置下一次启动时非易失性的注册表配置信息。也可以调用VideoPortGetRegistryParameters来获得由一个安装程序写入到注册表中特定适配器的、总线相关的配置参数。 推荐使用通过微端口驱动程序设置注册表中的一定的硬件信息,来向用户显示有用的信息或在调试时显示信息。微端口驱动程序可以设置芯片类型、DAC类型、适配器内存大小、鉴别适配器的字符串等,这些信息都是通过控制面板(Control Panel)的显示小应用程序来显示的。 驱动程序通过调用VideoPortSetRegistryParameters函数来设置这些信息,而且通常是驱动程序在它的HwVidFindAdapter例程中调用。 下表描述了驱动程序可以注册并提供的VideoPortSetRegistryParameters中ValueName和ValueData参数的详细信息: 条目信息 ValueName ValueData Chip type HardwareInformation.ChipType 包含芯片名称的以NULL结尾的字符串  DAC type HardwareInformation.DacType 包含DAC名称或ID的以NULL结尾的字符串  Memory Size HardwareInformation.MemorySize 包含ULONG,以MB为单位,适配器上视频内存的总量  Adapter ID HardwareInformaiton.AdapterString 包含适配器名称的以NULL结尾的字符串  BIOS HardwareInformation.BiosString 包含BIOS信息的以NULL结尾的字符串   6.4.1.3改变适配器的状态 微端口驱动程序在调用HwVidInitialize例程之前不能永久性改变适配器的状态。微端口驱动程序例程在HwVidInitalize函数之前调用,如HwVidFindAdapter函数,它不能改变任何不需要的视频适配器的状态并且不能永久性改变任何视频适配器的状态。 当HwVidFindAdapter运行时,HAL已经控制了视频适配器,因此它可以在系统启动的早期阶段将信息写到屏幕上。如果HwVidFindAdapter试图签别适配器是否影响适配器状态,这一例程应当立即恢复原先的状态信息。因此一经HwVidFindAdapter返回,HAL就可以继续显示启动的消息。 例如,HwVidFindAdapter应当推迟决定一个适配器的DAC类型到HwVidInitalize函数,由于做这一决定并不会影响微端口驱动程序是否已经被装载,但是永久性地改变了适配器的状态。 6.4.2声明继承资源 视频微端口驱动程序必须在驱动程序初始化过程中在VIDEO_HW_INITIALIZATION_DATA结构中声明并报告所有的继承资源。继承资源是那些没有列在PCI配置空间但由设备解码的资源。Windows NT/Windows 2000遇到那些没有在本部分以提纲形式列出的未报告的继承资源时,将禁止电源管理及停靠功能。 微端口驱动程序必须在报告继承资源时做如下事情: 如果对设备列出的继承资源在编译时可知,则将填充VIDEO_HW_INITIALIZATION_DATA结构中的两上字段,它是在DriverEntry例程中创建和初始化的。 结构成员 定义 HwLegacyResourceList 指向一个VIDEO_ACCESS_RANGE结构,每一个结构描述了视频驱动程序未列入PCI配置空间的设备I/O端口或者内存区间。  HwLegacyResourceCount HwLegacyResourceList指向的数组所包含的元素个数  如果列出的设备的继承资源在编译时是未知的,则实现一个HwVidLegacyResources函数,初始化VIDEO_HW_INITIALIZATIONH_DATA结构的GetLegacyResources成员并指向该函数。例如,一个支持具有不同继承资源设置的两个设备微端口驱动程序可以实现HwVidLegacyResources以在运行时报告继承资源。小视频端口驱动程序实现HwVidLegacyResources函数时,视频端口驱动程序将忽略VIDEO_HW_INITIALIZATION_DATA结构的HwLegacyResourceList及HwLegacyResourceCount成员。 根据微端口驱动程序的定义,对每一个VIDEO_ACCES_RANGE结构填充其RangePassive字段值。设置RangePassive为VIDEO_RANGE_PASSIVE_DECODE意味着该区由硬件进行解码,但是显示及视频微端口驱动程序将永不会接触到它。设置RangePassive为VIDEO_RANGE_10_BIT_DECODE意指设备对该区的端口地址进行10位解码。 同样,驱动程序应当包括一种由硬件解码的但没有被PCI声明的资源。驱动程序中声明的较小的继承资源的代码也许看起来如下边代码所示: // RangeStart RangeLength // | | RangeInIoSpace // | | | RangeVisible // +……+……+ | | | RangeShareable // low high | | | | RangePassive // v v v v v v v VIDEO_ACCESS_RANGE AccessRanges[]={ //[0] (0x3b0-0x3bb) {0x000003b0,0x00000000,0x000000c,1,1,1,0}, //[1](0x3c0-0x3df) {0x000003c0,0x0000000,0x00000010,1,1,1,0}, //[2](0xa0000-0xaffff) {0x000A0000,0x00000000,0x00010000,1,0,0,0}, }; //Within the DriverEntry routine: VIDEO_HW_INITIALIZATION_DATA hwInitData; hwInitData.HwLegacyResourceList=AccessRanges; hwInitData.HwLegacyResourceCount=3; 微端口驱动程序可以按顺序调用VideoPortVerityAccessRanges重声明继承资源。但是,视频端口驱动程序简单地忽略任何以前声明的继承资源。如果微端口驱动程序企图在VideoPortVerifyAccessRanges声明一个继承存取区间,但是并没有在DriverEntry的HwlegacyResourceList中声明过或通过hwVidLegacyResources的legacyResourceList参数返回时,将不支持电源管理及停靠管理。 6.4.3初始化视频微端口与显示驱动程序的通讯 对每一个由PnP管理器发现的适配器,都可以由微端口视频驱动程序进行成功的配置,微端口驱动程序的HwVidInitialize函数在相应的显示驱动程序加载时调用。HwVidInitialize可以初始化软件的状态信息,但是它不应当配置成适配器上的可见状态信息。对HwVidInitialize函数返回,适配器应当设置成与微端口驱动程序的HwVidResetHw例程返回值相同的状态值。更多的HwVidResetHw信息,参考重新设置视频微端口驱动程序部分。 如果需要,微端口驱动程序的HwVidInitialize函数可以对适配器执行一次由HwVidFindAdapter发起的初始化操作。例如,一个微端口驱动程序可以推迟对适配器加载微代码并利用hwVidInitalize函数调用VideoportGetRegistryParameters。 当HwVidInitialize函数返回控制,图形引擎则对微端口驱动程序适配置器进行处理。相应的显示驱动程序可以调用引擎的EngDeviceIoControl函数以请求存取映射的视频内存或请求任何其他操作。视频端口驱动程序以VRP将这样的请求发送到微端口驱动程序的HwVidStartIO函数。参考视频请求处理部分获得更多信息。 通常,显示驱动程序控制显示给终端用户所见的内容,除了在偶然情况下即基于X86的Windows NT/Windows 2000机器上运行一个全屏的MS-DOS应用程序。关于支持这一特征的VGA兼容模式的微端口驱动程序,可以参考VAG兼容的微端口的SvgaHwIoPortXxx部分获得详细内容。 HwVidInitialize函数调用VideoPortGetRegistryParameters或者VideoPortSetRegistryParameters以获得或设置在注册表中的配置信息。例如,HwVidInitaialize可以调用VideoPortSetRegistryParameters来在注册表中设置下一次启动时的非易失性信息。它也可以调用VideoportGetRegistryParameters来获得特定适配器、总线相关的配置参数并由安装程序写入注册表中。 6.5视频微端口设备扩展 设备扩展是每个微端口驱动程序特定适配器状态信息唯一的全局存储地。 每个微端口驱动程序定义大小、内部结构及设备扩展的内容。视频端口驱动程序将一个指针作为一个系统定义的除DriverEntry之外的微端口驱动程序函数HwVidSynchronizeExecutionCallback及SvgaHwIoPortXxx函数的输入参数传送到每一个设备扩展,同时,许多VideoPortXxx函数需要将这一指针作为参数。 微端口驱动程序必须用设备扩展来维持单一适配器的状态信息。每一个系统检测到的适配器都在设备扩展里有一份单独的状态信息。微端口驱动程序不需要全局变量来存储任何适配器的状态。这对提供无缝的多显示器支持也是格外严格的。 6.6视频微端口驱动的注册回调信号 微端口驱动程序的HwVidFindAdapter函数可以请求检索注册表中的配置信息,并通过用驱动程序提供的回调函数调用下面一种的视频端口驱动程序函数。 VideoPortGetDeviceData函数。该函数的 CallbackRoutine参数设置的指针指向一个驱动程序提供的HwVidQueryDeviceCallBak。 VideoPortGetRegistryParameters函数。该函数的CallbackRoutine参数设置的指针指向一个驱动程序提供的HwVidQueryNamedValueCallback。 VideoPortGetDeviceData和VideoPortGetRegistryParameters从注册表的不同关键字中检检信息。相应地,每一个都声明微端口驱动程序来提供一个系统定义的具有不同格式参数的HwVid…Callbak函数。 任一HwVid…Callbak函数都不能检索显示驱动程序注册表中默认的配置信息,因为,它们常常是在显示驱动程序被系统加载之前调用的。 微端口驱动程序也可以调用VideoPortsSetRegistryParameters来存储信息,如在注册表中…\CurrentControlSet\Services\DriverName\DeviceNumber关键字下的芯片及DAC类型信息。更多的注册表中关于硬件的信息,可以参考设置注册表中硬件信息部分的内容。以后再从注册表中检索信息,则微端口驱动程序可调用VideoPortSetRegistryParameters。 需要注意的是,注册表信息可以通过调用VideoPortGetDeviceData来得到,但这是易失性的:它每一次都在系统装载时重新生成。同时也要注意,对视频微端口驱动程序,视频端口驱动程序由VideoPortGetRegistryParameters函数调用得到这些信息及配置信息。微端口驱动程序不能调用VideoPortSetRegistryParameters来向它相应的显示驱动程序传递配置信息。 6.7处理视频请求 那些产生于显示驱动程序调用EngDeviceIoControl的所有的I/O请求都被视频端口驱动程序从IRP映射到VRP。视频端口驱动程序然后用一个它自已建立起来的到每一个VIDEO_REQUEST_PACKET结构的指针。所有的VRP发送到HwVidStatIO,其IoControlCode成员被设置为IOCTL_VIDEO_XXX。 视频端口驱动程序同样同步管理所有对视频微端口驱动程序的请求,它向每一个微端口驱动程序HwVidStartIO例程一次只发送并处理一个VRP。HwVidStartIO拥有每一个输入VRP,直到微端口驱动程序完成请求的操作并且返回控制。当微端口驱动程序完成当前的VRP,视频端口驱动程序特有I/O管理器发送的任何突出的IRP,以响应由相应显示驱动程序对EngDeviceIoControl的后续调用。 对于收到一个视频请求,HwVidStartIO必须检查VRP,处理对适配器的视频请求,设置适当的状态以及其他在VRP中的信息,并返回TRUE值。 6.7.1系统定义的IOCTL_VIDEO_XXX请求 典型地,许多视频微端口驱动程序支持下列请求: IOCTL_VIDEO_QUERY_NUM_AVAIL_MODES IOCTL_VIDEO_QUERY_AVAIL_MODES IOCTL_VIDEO_QUERY_CURRENT_MODE IOCTL_VIDEO_SET_CURRENT_MODE IOCTL_VIDEO_RESET_DEVICE IOCTL_VIDEO_MAP_VIDEO_MEMORY IOCTL_VIDEO_UNMAP_VIDEO_MEMORY IOCTL_VIDEO_SHARE_VIDEO_MEMORY IOCTL_VIDEO_UNSHARE_VIDEO_MEMORY IOCTL_VIDEO_QUERY_PUBLIC_ACCESS_RANGES IOCTL_VIDEO_FREE_PUBLIC_ACCESS_RANGES IOCTL_VIDEO_GET_POWER_MANAGEMENT IOCTL_VIDEO_SET_POWER_MANAGEMENT IOCTL_VIDEO_GET_CHILD_STATE IOCTL_VIDEO_SET_CHILD_STATE_CONFIGURATION IOCTL_VIDEO_VALIDATE_CHILD_STATE_CONFIGURATION 依赖于适配器的特性,视频微端口驱动程序可以支持下列附加的请求: IOCTL_VIDEO_QUERY_COLOR_CAPABILITIES IOCTL_VIDEO_SET_COLOR_REGISTERS(如果设备有模析则必需) IOCTL_VIDEO_DISABLE_POINTER IOCTL_VIDEO_ENABLE_POINTER IOCTL_VIDEO_VIDEIO_QUERY_POINTER_CAPABILITIES IOCTL_VIDEO_QUERY_POINTER_ATTR IOCTL_VIDEO_SET_POINTER_POSITION IOCTL_VIDEO_QUERY_POINTER_POSITION IOCTL_VIDEO_SET_POINTER_POSITION IOCTL_VIDEO_HANDLE_VIDEOARAMETERS VGA兼容的SVGA微端口驱动程序需要支持下列附加的请求: IOCTL_VIDEO_SAVE_HARDWARE_STATE IOCTL_VIDEO_RESTORE_HARDWARE_STATE IOCTL_VIDEO_DISABLE_CURSOR IOCTL_VIDEO_ENABLE_CURSOR IOCTL_VIDEO_QUERY_CORSOR_ATTR IOCTL_VIDEO_SET_CURSOR_ATTR IOCTL_VIDEO_QUERY_CURSOR_ATTR IOCTL_VIDEO_SET_CURSOR_POSITION IOCTL_VIDEO_GET_BANK_SELECT_CODE IOCTL_VIDEO_SET_PALETTER_REGISTERS IOCTL_VIDEO_LOAD_AND_SET_FONT 每一个IOCTL的详细信息可以在在线DDK的视频I/O控制代码部分查找到。微端口驱动程序开发者不应当使用不在文档中的、由系统定义的IOCTL。 6.7.2自定义的显示微端口IOCTL_VIDEO_XXX请求 一个微端口驱动程序可以为自已相应的显示驱动程序定义一个或多个自定义I/O控制代码。 但是,只有专用的显示微端口驱动程序对可以使用自定义的I/O控制代码。也就是,被设计运行于一个已经存在的显示驱动程序下的微端口驱动程序不应当定义私有的I/O控制代码,因为已经存在的显示驱动程序如果不被重写或者如果可能不破坏它已使用的微端口驱动程序就不能构建新的I/O控制请求。已经存在的在许多不同模式的适配器上的一类显示驱动程序,如SVGA适配器,同样不能依赖于私自定义的I/O控制代码来在每一个下层的微端口驱动程序中起到相同的作用。 6.7.3处理不支持的IOCTL_VIDEO_XXX请求 每一个HwVidStartIO函数也必须处理对不支持的IOCTL_VIDEO_XXX函数的接收,如下: 设置输入的VRP的Status字段值为ERROR_INVALID_FUNCTION。 设置输入的VRP的Information字段为零。 返回TRUE值意味着请求被处理。 参考VIDEO_REQUEST_PACKET和STATUS_BLOCK结构获得更详细信息。 6.8在视频微端口中支持PnP及电源管理 所有的Windows 2000微端口驱动程序必支持PnP和电源管理,包括枚举子设备如DDC监示器、I2C(I2C)设备以及二级适配器的能力。 视频端口驱动程序为微端口驱动程序管理许多PnP需求,包括创建功能设备对象(FDO:Functional Device Object)及代表微端口驱动程序接收和发送PnP专用的IRP。 微端口驱动程序必须实现以下的函数以支持PnP和电源管理: HwVidSetPowerState HwVidGetPowerState HwVidGetVideoChildDescriptor 一个继承的微端口驱动程序的图形适配器不能在系统运行时被删除掉,也不能在加入到一个正在运行的系统时自动检测到。 可参考图形适配器子设备部分获得关于检测及与适配器子设备通信的内容。对PnP驱动程序的一般性知识,参考即插即用、电源管理及建立设计指南部分。 6.9在视频微端口驱动程序中使用AGP 视频微端口驱动程序提供AGP支持,这样便允许微端口驱动程序设备直接存取系统的(物理)内存。 为使用AGP,微端口驱动程序必须首先检索视频端口的驱动程序实现的AGP来支持调用VideoPortGetAgpServices例程。视频端口对AGP返回一个指针并支持在微端口驱动程序中分配的将VIDEO_PORT_AGP_SERVICES结构传送给VideoPortGetAgpServices的例程。 一个微端口驱动程序可以基于系统的物理内存总量来设计它的AGP内存使用,这一信息保存在VIDEO_PORT_CONFIG_INFO结构的SystemMemorySize成员中。 6.9.1通过AGP存取系统内存 微端口驱动程序应当调用以下驱动程序提供的视频端口函数来使其设备可以存取主内存: 调用AgpReserverPhysical来在系统总线上保留一段物理内存区间,AGP控制器可以对其响应。 调用AgpCommitPhysical来锁定物理内存并将它映射到AgpReservePhysical返回的AGP解码的一部分物理地址上,微端口驱动程序可以多次调用AgpCommitPhysical来执行多次小的提交而不是执行一次大的提交。 这样,对一个可以看见并使用这一提交的AGP控制的内存应用程序来说,视频微端口驱动程序必须调用如下函数: 调用AgpReserverVirtual来在应用程序的地址空间预留一段虚拟地址空间。 调用AgpCommitVirtual来锁定虚拟内存并将虚拟内存映射到由AgpReservePhysical返回的AGP解码的物理地址区间。 应用程序可以使用这些提交的虚拟地址空间,如同这些虚拟地址空间是连续的一样。 6.9.2从AGP使用中释放内存 微端口驱动程序负责释放所有它保留的以及它提交的虚拟内存和物理内存,如同在通过AGP存取系统内存部分中简述的一样,它是通过调用下列的视频端口驱动程序的AGP服务例程来释放内存的。 AgpFreeVirtual去除以前的AgpCommitVirtual函数调用所锁住的虚拟内存映射。 AgpReleaseVirtual释放由以前的AgpReserveVirtual函数所保留的虚拟地址区间。 AgpFreePhysical去消由以前的AgpCommitPhysical函数所锁住的物理内存的映射。 AgpReleasePhyscal释放由以前的AgpReservePhysical函数所保留的物理地址区间。 6.10图形适配器的子设备 以下两部分讨论具有一个或多个子设备的图形适配器对微端口驱动程序的影响问题。 6.10.1侦测子设备 必须在微端口驱动程序中实现HwVidGetVideoChildDescripter来使PnP管理器可以侦测到图形适配器的子设备。 默认情况下,HwVidGetVideoChildDescriptor函数在父设备未启动之前不能调用。也就是,HwVidGetVideoChildDescriptor在HwVidFindAdapter结束之后才可以调用。为了覆盖这一默认值,以允许枚举在任何时间发生的的子设备,可以设置VIDEO_HW_INITIALIZATION_DATA结构的AllowEarlyEnumeration成员为TRUE即可。 一个设备在新硬件连接到系统或已经存在的硬件脱离开系统时都会产生中断,为处理这些中断,微端口驱动程序必须做以下事情: 实现一个调用VideoPortEnumerateChildren函数的DPC(PMINIPORT_DPC_ROUTINE)。 当一个设备上的中断产生时,实现一个调用VideoPortqueue到DPC队列的中断句柄(HwVidInterrupt)。 对每一个父设备的子设备,如果调用了HwVidGetVideoChildDescriptor函数,则也需调用VideoPortEnumerateChild函数对适配器的子设备进行重行枚举,PnP管理器根据需要修改父设备与子设备之间的关系。 6.10.2与子设备驱动程序通信 视频微端口驱动程序与子设备的驱动程序可以人为地定义一个允许子设备驱动程序与它的硬件通过父微端口驱动程序通信的接口。子设备驱动程序通过向父微端口驱动程序的视频端口驱动程序发送一个IRP_MN_QUERY_INTERFACE请求获得这一接口,接收到这样的请求后,视频端口驱动程序调用微端口驱动程序的HwVidQueryInterface函数,如果它已定义,并且微端口驱动程序返回一个接口的接针,则子设备的驱动程序就可以在任何时间通过HwVidQueryInterface函数将它调入到微端口驱动程序中。 如果微端口驱动程序没有实现HwVidQueryInterface或者调用失败,则视频端口驱动程序将请求传给微端口驱动程序设备的父,如果子驱动程序向该微端口驱动程序的另一个子设备发送了IRP_MN_QUERY_INTERFACE且另一子设备没有实现HwVidQueryInterface或者调用失败,则视频端口驱动程序返回一个错误。 由于子设备驱动程序可以无需知道任何视频端口驱动程序的相关知识而调入微端口驱动程序,微端口驱动程序就必须在所有HwVidQueryInterface发起的函数中同步存取它自身。它可以通过分别调用VideoPortAcquireDeviceLock和VideoPortReleaseDeviceLock来获得或释放视频端口驱动程序维持的设备锁。 子设备枚举是通过HwVidGetVideoChildDescriptor函数完成的。 6.11视频微端口中的中断 适配器的微端口驱动程序产生中断必须实现一个HwVidInterrupt例程,微端口驱动程序的DriverEntry例程应当初始化VIDEO_HW_INITALIZATION_DATA的成员HwInterrupt以指向中断句柄。 如果适配器产生中断,则视频端口驱动程序为微端口驱动程序建立起中断对象,由于它是由视频端口驱动程序产生并管理的,不需要了解进一步的关于中断对象的信息。如果感兴趣,可以参考内核模式的驱动程序指南获得详细信息。 如果微端口驱动程序的HwVidFindAdapter函数发现视频适配器实际上没有产生中断或者它不能决定对该适配器有效的中断向量或中断层次,则HwVidFindAdapter应当将VIDEO_PROT_CONFIG_INFO结构中的InterruptLevel和InterruptVector设置为零。 当HwVidFindAdapter返回控制,视频端口驱动程序检查在VIDEO_PORT_CONFIG_INFO中的中断配置成员,都为零,它不将中断连接到一个微端口驱动程序。如果微端口驱动程序允许函数入口点,则它的驱动程序是由DriverEntry函数建立来的。HwVidfindAdapter中断成员明确地设置为零将禁止HevidInterrupt的入口点, 需要注意的是,HwVidInterrupt可以存取微端口驱动程序的设备扩展,即使它没有分页。依赖于微端口驱动程序的设计,也许对其他的驱动程序函数安全地使用HwVidInterrupt,从共享设备或设备扩展一些特殊地区是不可能的。。 例如,假定微端口驱动程序的HwVidStartIO函数在适配器中断时用于存取设备扩展,HwVidInterrupt运行在另外一个处理器上,并且hwVidInterrupt也可以存取设备扩展,如果这种情况发生,则HwVidStartIO应当由驱动程序提供的HwVidSynchronizeExecutionCallback的函数去调用VideoPortSynchronizeExecution。 6.11.1何时实现HwVidSynchronizeExecutionCallbak例程 适配器的产生中断的微端口驱动程序很少用HwVidSychronizeExecutionCallback函数去调用VideoPortsynchronizeExecution,实际上,微端口驱动程序都有一个HwVidInterrupt函数而没有必要有HwVidSynchronizeExecutionCallback函数。因为视频端口驱动程序在处理完以前的请求之前不向微端口驱动程序的hwVidStartIO函数发送请求(参考处理视频请求部分),微端口驱动程序很少调用VideoPortSynchronizeExecution。 对微端口驱动程序HwVidSynchronizeExecutionCallback函数有两种可能的使用: 用微端口驱动程序的驱动程序函数的设备扩展而不是HwVidInterrupt函数存取适配器寄存器。当HwVidsynchronizeExecutionCallback函数给定控制,从适配器来的中断也被屏蔽,这样微端口驱动程序的HwVidInterrupt函数将不能改变HwVidSynchronizeExecutionCallback函数运行在SMP机器上时设备扩展的状态。 如果适配器需要,向适配器寄存器或者端口写命令是非常快的。当HwVidSynchronizeExecutionCallback给定控制,几乎所有的系统中断被屏蔽掉,这样HwVidSynchronizeExecutionCallback函数将不能被一个设备中断(或时钟中断)剥夺。并且,hwVidsynchronizeExecutionCallback函数必须尽可能快地返回控制。 对上面第一种类型的HwVidsynchronizeExecutionCallback函数,微端口驱动程序按设置给VpMediumPriority的Priority值来调用VideoPortsynchronizeExecution。对第二种类型的HwVidsynchronizeExecutionCallback函数,如果驱动程序没有HwVidInterrupt函数,则微端口驱动程序同样按设置于VpMediumPriority的Priority值来调用。否则,这样的微端口驱动程序将按设置于VpHighPriority的Priority来调用。 一般情况下,微端口驱动程序不能用第二种类型的HwVidSynchronizeEcecutionCallback函数调用VideoPortsynchronizeExecution,除非驱动程序设计者有其他的选择——即,适配器被用系统的中断屏蔽来编程。否则,微端口驱动程序应按设置于VpLowPriority的priority值来调用VideoPortSynchronizeExecution函数。 HwVidSynchronizeExecutionCallback函数,像hwVidInterrupt函数不能被分页也不能不用系统重启就调用某些VideoPortXxx函数。对VideoPortXxx函数总体来说,HwVidSynchronizeExecutionCallback函数可安全地调用它们,可参考在线DDK。 6.12视频微端口的定时器 任何微端口驱动程序都可以根据驱动程序开发者的意愿而有一个HwVidTimer函数。HwVidtimer函数允许微端口驱动程序中止操作或者通过VideoPortStallExecution来监控大致的时间间隔中的状态变化。HwVidTimer同时也防止其他系统的操作发生,如VideoPortStallExecution所做的那样。 例如,适配器的微端口驱动程序仿效VGA的功能可以有一个HwVidTimer函数来定期监视它的适配器的VGA寄存器的状态,这样驱动程序就可以仿效VGA格式的图形。 在调用VideoPortStartTimer之后,端口驱动程序又每秒调用一次HwVidTimer函数,直到微端口驱动程序调用VideoPortStopTimer之后。微端口驱动程序可以允许或禁止对HwVidTimer函数重复调用。 需注意的是,HwVidTimer函数不能用VideoPortStopTimer禁止对它自身的调用,另外一个微端口驱动程序函数必须控制通过VideoPortStartTimer和VideoPortStopTimer允许或禁止对HwVidTimer函数的调用。 6.13在视频微端口中对适配器重置 每一个微端口驱动程序必须有HwVidResetHw函数。 HwVidResetHw由HAL在机器将要崩溃或用户想软启动机器时调用。HwVidResetHw重置适配器到专用的字符模式,这样HAL就可以在关闭系统时显示关闭信息而在软启动机器时显示初始化信息。 HwVidResetHw不能调用BIOS,不能调用任何的可分页代码,也不能进行分页。如果可能,它只调用VideoPortReadXxx和VideoPortWriteXxx函数,但也可以调用下面函数的任何一个。 VideoPortStallExecution VideoPortZeroDeviceMemory VideoPortZeroMemory 6.14微端口驱动中的镜像驱动程序支持 Windows 2000提供了视频微端口驱动程序对镜像驱动程序的支持,这样,微端口驱动程序不需要任何特殊的代码来支持。可以参考第二章镜像驱动程序部分的内容来获得关于在镜像系统中的显示驱动程序的详细信息。 微端口驱动程序中的镜像驱动程序的需求是最小的,唯一必须实现的函数是DriverEntry,它是由微端口驱动程序导出的,也可以由以下函数导出: HwVidFindAdapter HwVidInitialize HwVidStartIo 既然没有物理的显示设备与一个镜像的表面相关联,这三个函数可以空执行并且总是返回成功。 6.15视频微端口的TV连接器及拷贝保护支持 所有的视频微端口驱动程序应当用IOCTL_VIDEO_HANDLE_VIDEOPARAMETERS I/O控制代码处理VRPs,这个IOCTL被发送到微端口驱动程序查询硬件能力、TV连接器当前设置及拷贝保护或者设置拷贝保护的硬件功能。微端口驱动程序根据检查VIDEOPARAMETERS结构的dwCommand字段值决定要执行的动作,它被传送到VRP的InputBuffer中。如果一个微端口驱动程序不处理这一VRP,则系统根本不允许DVD回放。 如果设备没有TV连接器,微端口驱动程序将总是在VRP的StatusBlock结构的Status字段值返回NO_ERROR值,这样的微端口驱动程序不能以任何方式修改VIDEOPARAMETERS结构值。 接下来的两部分将提供微端口驱动程序设备的TV连接器实现的详细内容。 6.15.1查询TV连接器及硬件的拷贝保护 微端口驱动程序在VIDEOPARAMETERS结构的dwCommand字段值为VP_Commnad_GET时及设备具有TV连接器时做如下工作: 验证Guid(必需的) 如果硬件可以更改模式或标准,则在dwFlags中都设置VP_FLAGS_TV_MODE和VP_FLAGS_TV_STANDARD。微端口驱动程序也应当对其他所支持的能力设置合适的标志位。例如,如果设备支持闪烁过滤,则它应当设置VP_FALGS_TV_FLAGS 在dwMode中设置合适的位则意指目前设备的显示器模式(可选的)。如果TV连接器正处于激活状态即向TV显示图像,则微端口驱动程序设置VP_MODE_TV_PLAYBACK,如果设备只是向CRT播放,则应当设置为VP_MODE_WIN_GRAPHIC,或者两个位同时都设置则表示设备同时向TV及CRT播放。 用设备支持的回放模式填充dwAvailableModes,所有的驱动程序必须设置成VP_MODE_WIN_GRAPHICS。如果设备能够打开全部扫描而关闭闪烁过滤,则微端口驱动程序还应当设置VP_MODE_TV_PLAYBACK。 如果设备正通过TV连接器在显示,则应以目前世界上电视显示的标准频率填充dwTVStandard(可选)。如果TV输出被禁止,微端口驱动程序将仅设置VP_TV_STANDARD_WIN_VGA位。 用TV连接器支持的TV标准填充dwAvailableTVStandard。 设置dwMaxUnscaledX和dwMaxUnscaledY,即TV显示器能支持的最大x值及y值,不是硬件尺度必需的。 如果,对TV连接器来说,设备还提供了硬件拷贝保护,则微端口驱动程序将修改下列函数的VIDEOPARAMETERS结构的字段值。 在dwFlags中设置VP_FLAGS_COPYPROTECT(如同VP_FLAGS_TM_MODE和VP_FLAGS_TV_STANDARD)。 设置dwCPType为拷贝保护状态。 设置dwCPStandard为拷贝保护工作状态时的TV标准。 6.15.2设置TV连接器及拷贝保护硬件 对由微端口驱动程序设置的VIDEOPARAMETERS的dwFlags成员的VP_COMMAND_GET任一位来说,微端口驱动程序可以执行对VP_COMMAND_SET的一个设定。调用者负责调用微端口驱动程序并设置微端口驱动程序支持VP_COMMAND_GET的未设定功能。微端口驱动程序应当为VP_COMMAND_GET作出响应,而这是通过设置硬件的VIDEOPARAMETERS字段的dwFlags中的相应的字段值来实现的。例如: 如果微端口驱动程序在VP_COMMAND_GET上设置了VP_FLAGS_TV_MODE位,当VP_FLAGS_TV_MODE在VP_COMMAND_SET上设置时,它应当改变VP_MODE_WIN_GRAPHICS状态到VP_MODE_TV_PLAYBACK状态。 如果微端口驱动程序在VP_COMMAND_GET上设置VP_FLAGS_TV_STANDARD位,当VP_FLAGS_TV_STANDARD设置在VP_COMMAND_SET上时,它应当改变TV标准到dwTVStandard指示的标准。 如果微端口驱动程序在VP_COMMAND_GET上设置VP_FLAGS_CONTRAST位,当VP_FLAGS_CONTRAST设置于VP_COMMAND_SET上时,它应当设置对比值为dwContrast中的值。 如果相应的位在dwFlags中没有设置,则VIDEOPARAMETERS字段包含未定义的数据 6.15.2.1设置拷贝保护硬件(Copy Protection Hardware) 那些在VIDEOPARAMETERS结构的dwFlags成员在VP_COMMAND_GET上返回VP_FALGS_PROTECTED的微端口驱动程序,应当按以下方式对VP_COMMAND_SET命令进行响应,并依赖VIDEOPARAMETERS结构的dwCPCommand成员: 如果dwCPCommand是VP_CP_CMD_ACTIVATE,微端口驱动程序应当打开拷贝保护,产生并返回一个在dwCPKey中的唯一性保护键。 如果dwCPCommand是VP_CP_CMD_DEACTIVATE并且在dwCPKey中的拷贝保护键是有效的,则微端口驱动程序应当关闭拷贝保护。 如果dwCPCommand是VP_CP_CMD_CHANG并且在dwCPKey中的拷贝保护键也合法,微端口驱动程序将根据bCP_APSTriggerBits中的触发数据改变保护状态。 对没有拷贝保护硬件的设备的微端口驱动程序,只需简单地在VRP的StatusBlock结构的Status字段返回NO_ERROR。 6.15.2.2多会话拷贝保护 具有拷贝保护的设备的微端口驱动程序可选择性地支持多个同步拷贝保护会话,如果这样做的话,微端口驱动程序应当如下这样做: 对每一个拷贝保护的激活,都返回一个在dwCPKey中的唯一的拷贝保护关键字。 在所有的会话都临时性关闭(通过VP_CP_CMD_CHANGE)或解除(VP_CP_CMD_DEACTIVATE)之前都可以保持拷贝允许。例如,微端口驱动程序可以在每次拷贝保护激活及解除或关闭时,增加或减少引用的总数,当总数为零时,所有的拷贝保护才被禁止。 6.16VGA兼容的微端口SvgaHwIoPortXxx 在基于x86的机器中,VGA兼容的微端口驱动程序替换了系统提供的VGA微端口驱动程序,因而,VGA兼容的微端口驱动程序可以有一套SvgaHwIoPortXxx函数来支持全屏的MS-DOS应用程序,就如同系统提供的VGA微端口驱动程序所做的一样。 VGA兼容的新的SVGA微端口驱动程序的设计者应当使之适应一种根据适配器特性的由系统提供的SVGA微端口驱动程序的SvgaHwIoPortXxx函数。如同在基于x86的VGA兼容微端口部分中提到的,对其他基于x86类型适配器的微端口驱动程序的机器可以有一套SvgaHwioPortXxx例程并且可以根据设计者自身判断是否提供相同的支持或者在VGA微端口驱动程序加载时能不能被同时装载。 6.16.1基于x86机器的窗口式VDM 每一个MS-DOS应用程序都作为一个Windows VDM来运行,它同样在Win32保护子系统中作为一个控制台管理程序运行。 在Windows NT/Windows 2000平台上,一个称作V86仿真器的内核模式的部件捕获MS-DOS应用程序的指令,只要这一应用程序运行于一个窗口,任何对视频适配器的访问都被捕获并反射回系统提供VDD,VDD主要用于仿真适配器对应用程序的行为。换句话说,显示驱动程序在VDM运行于一个窗口时保留有对视频适配器的控制。 6.16.2基于x86机器的全屏VDM 由于性能的原因,当用户切换一个基于X86机器的MS-DOS应用程序到全屏模式时,显示驱动程序放弃对适配器控制。系统的VGA或VGA兼容的微端口驱动程序则从V86仿真器中分出所有的I/O指令,如应用程序相关的IN,REP INSB/INSW/INSD,OUT以及REPOUTSB/OUTSW/OUTSD指令到视频I/O端口。这些被分出的I/O操作转交给VAG兼容的微端口驱动程序的SvgaHwIoPortSxx函数。 然而,为了更高的性能,微端口驱动程序可以调用VideoPortSetTrappedEmulatorPorts来允许一些I/O端口可以直接被应用程序访问,微端口驱动程序继续用它的SvgaHwIoPortXxx分出到其他的I/O端口,以验证对这些端口的应用程序相关的指令流。 为了防止全屏应用程序发布一系列指令而使系统挂起,SvgaHwIoPortXxx函数监视到一个驱动程序确定的适配器寄存器应用程序的指令流。微端口驱动程序必须允许只对这些I/O端口的完全安全的直接存取。例如,对顺序生成器端口及杂项输出寄存器端口应当始终被V86仿真器分出并并捕获到微端口驱动程序提供的SvgaHwIoPortXxx函数进行验证。 应用程序是否直接存取I/O端口取决于VGA兼容的微端口驱动程序通过调用VideoPortSetTrappedEmulatorPorts设置的IOPM(I/O permission map:即x86上的I/O允许映射)。但要注意,微端口驱动程序可以通过调用这一函数,使其具有一定的存取区间来调节IOPM,它描述了I/O端口,释放给应用程序直接存取或者由SvgaHwIoPortXxx重新捕获。当前的IOPM决定了哪能一端口能被应用程序直接存取以及哪一个保留有V86仿真器的钩子或以SvgaHwIoProtXxx函数捕获进行验证。 默认情况下,所有的I/O端口建立起这一个微端口驱动程序仿真器的存取数组,并被捕获到相应的SvgaHwIoPortXxx函数。但是,VGA兼容的微端口驱动程序常常在收到一个IOCTL_VIDEO_ENABLE_VDM,即请求重新为VDM设置IOPM以允许直接存取一些I/O端口时调用VideoPortSetTrappedEmulatorPorts。大多数情况下,这一驱动程序允许直接存取除VGA顺序生成器寄存器、杂项输出寄存器及任何SVGA特定适配器寄存器等驱动程序开发者认为应当一直由SvgaHwIoPortXxx函数验证有效性之外所有的视频适配器寄存器。 关于VideoportSetTrappedEmulatorPorts,可参考在线DDK中的图形驱动程序参考部分。 6.16.3VGA兼容的微端口的HwFindAdapter VGA兼容的微端口驱动程序的HwVidFindAdapter函数(或寄存器HwVid…Callback)必须在VIDEO_PORT_CONFIG_INFO中建立起如下缓冲区: NumEmulatorAccessEntries,意指在EmulatorAccessEntries数组中的条目数。 EmulatorAccessEntries,指向一个包含给定数量的EMULATOR_ACCESS_ENTRY类型元素的静态数组,每一个都描述了一个从V86仿真器分出的I/O端口。默认的,其值都传递给SvgaHwIoPortXxx函数。每一个条目都包括一个起始的I/O地址、一个区间长度、被捕获的存取的大小(UCHAR,USHORT或ULONG),微端口驱动程序是否支持通过I/O端口输入或输出字符串数据,微端口驱动程序提供的SvgaHwIoPortXxx函数以进行实际验证,可能的话还会传输数据。每一个SvgaHwIoPortXxx函数处理读(IN或REP INSB/INSW/INSD)和/或写(OUT或REP OUTSB/OUTSW/OUTSD)传输的UCHAR-,USHORT-,或ULONG-尺寸的数据。 EmulatorAccessEntriesContext,一个存储的指针,如微端口驱动程序的设备扩展中的一个区域,在这个区域中,微端口驱动程序的SvgaHwIoPortXxx函数可以批次处理一个验证需要的应用程序相关的指令序列。 VdmPhysicalVideoMemoryAddress和VdmPhysicalVideoMemoryLeagth描述了一个必须映射到VDM地址空间以支持MS-DOS全屏应用程序BIOS INT10调用的视频内存的区间。当一个应用程序改变视频模式到微端口驱动程序适配器可以支持的一种模式时,微端口驱动程序可以调用VideoPortInt 10函数。 HardwareStateSize,它描述了对适配器IOCTL_VIDEO_SAVE_HARDWARESTATE响应的存储硬件状态信息所需的最小字节数。当用户切换到运行在一个窗口的MS-DOS全屏模式的应用程序时,微端口驱动程序必须在显器驱动程序重新获取对视频适配器的控制之前存储适配器的状态。需要注意的是VGA兼容的微端口驱动程序同样必须支持可逆的IOCTL_VIDEO_RESTORE_HARDWARE_STATE请求,因为用户也可能从窗口化的应用程序转回到全屏模式。 VGA兼容的微端口驱动程序仿真器存取专用的条目的子集,即适配器存取区间数组中指定的子集。仿真器存取条目可以是,而且经常是由HwVidFindAdapter函数建立起来的被映射的存取区间数组的所有I/O端口,存取的区间传送给VideoPortSetTrappedEmulatorPorts调用,它确定了当前的IOPM并决定由全屏MS-DOS应用程序可直接存取的I/O端口,指定了微端口驱动程序仿真器可存取条目的子集。 6.16.4在SvgaHwIoPortXxx验证指令 如同在VGA兼容的微端口HwFindAdapter中提到的,IOPM设置了可直接存取的I/O端口包括除顺序生成器寄存器和杂项输出寄存器之外的所有的SVGA寄存器,这些I/O端口继续VGA兼容的视频微端口驱动程序用SvgaHwIoPortXxx函数监视着。顺序生成器寄存器控制内部VGA兼容显示适配器上的芯片时钟。如果全屏MS-DOS应用程序在同步重新设置时触及其他适配器寄存器,则机器可以挂起。同样地,如果杂项输出寄存器设置选择一个不存在的时钟,机器同样将挂起。 VGA兼容的微端口驱动程序必须保证全屏的MS-DOS应用程序不发布引起机器挂起的指令。每一个这样的微端口驱动程序必须提供SvgaHwIoPortXxx函数来监视应用程序的发给I/O端口的为适配器顺序生成器寄存器及杂项输出寄存器发出的指令。每一个具有新特性的适配器的新VGA兼容的微端口驱动程序必须监视并继续验证任何可能由应用发送到任何I/O端口的能引起机器挂起的任何指令序列。 无论何时应用程序企图存取顺序生成器的时钟寄存器,SvgaHwIoportXxx函数必须改变IOPM的顺序以捕获所的有在同步重设置时进入的指令。一旦一个应用程序发送了一个影响顺序生成器或者企图向杂项输出寄存器写入的指令,则SvgaHwIoPortXxx函数将通过调用VideoPortSetTrappedEmulatorPorts来调节IOPM以禁止它直接存取所有的适配器寄存器。 微端口驱动程序提供的SvgaHwIoPortXxx函数应当在EmulatorAccessEntriesContext区域中缓冲接下来的IN(或INSB/INSW/INSD)和/或OUT(或OUTSB/OUTSW/OUTSD)指令,这一区域是直到同步重置完成之后或者应用程序重新存储了杂项输出寄存器或重置它为安全时钟后建立起的VIDEO_PORT_CONFIG_INFO(参考VGA兼容的微端口的HwFindAdapter驱动程序)。 微端口驱动程序这时负责检查缓冲的指令不能挂起机器。如果缓冲的指令不能挂起机器,微端口驱动程序将处理缓冲指令,通常情况都是通过用驱动程序提供的HwVidSynchronizeExecutionCallback调用VideoPortSynchronizeExecution函数,否则的话,微端口驱动程序将摒弃缓冲区指令。 6.16.5VGA兼容的HwVidStartIO 当用户从一个全屏的MS-DOS应用程序切换到一个窗口下运行时,VGA兼容的微端口驱动程序的HwVidStartIO函数发送了一个带有I/O控制代码IOCTL_VIDEO_SAVE_HARDWARE_STATE的VRP。微端口驱动程序必须存储适配器的状态以备用户再切换回到应用程序的全屏模式。 值得注意的是,微端口驱动程序的SvgaHwIoPortXxx函数必须具有一个缓冲的应用程序的IN和/或OUT序列,如同在SvgaHwIoPortXxx验证指令部分中所描述的那样。当它的HwVidStartIO例程调用以存储适配器的状态,在这种情况下,微端口驱动程序应当存储当前的状态,包括缓冲区中的指令,这样SvgaHwIoProtXxx函数可以重新准确地验证用户什么地方又将应用程序切换到全屏模式等一类的操作。 当微端口驱动程序完成一个存储操作,端口驱动程序自动禁止当前对VDM和微端口驱动程序的SvgaHwIoPortXxx函数的IOPM。如果应用程序又切换到全屏模式,则视频微端口驱动程序重新自动存储IOPM。它也在响应IOCTL_VIDEO_RESTORE_HARDWARE_STATE请求并调用微端口驱动程序的HwVidStartIO例程之后重新调用微端口驱动程序的SvgaHwIoPortXxx函数。 6.17将Windows NT 4.0的微端口驱动程序转换到Windows 2000 好的Windows NT 4.0微端口驱动程序可以很容易地成为Windows 2000的微端口驱动程序,下面是一些在Windows 2000微端口驱动程序中由PnP支持提供的需要修改的内容: 实现在视频微端口驱动程序中支持PnP和电源管理部分中列出的新函数。但要确保初始化VIDEO_HW_INITALIZAITON_DATA的新成员要指向这些新函数。 在DriveEntry函数中修改对VideoPortInitialize函数的调用。在Windows 2000中,第四个参数(Hwcontext)必须为NULL值。 修改HwVidFindAdapter函数,对可枚举的总线上的设备来说,必须如下改变HwVidFindAdapter: 删去设备侦测到的大多数代码(由于在Windows 2000上对HwVidFindAdapter的调用意味着PnP管理器已经探测到设备)。 调用VideoPortGetAccessRanges来获得设备响应的总线相关的物理地址,这些地址是由PnP管理器分配的。 如果驱动程序支持一个以上的设备类型,则确定设备的类型。 忽略Again参数。(由于系统对每个设备只调用一次HwVidFindAdapter)。 对ISA等不可枚举的总线上的设备来说,PnP依旧企图启动设备,尽管HwVidFindAdapter负责决定设备是否存在。 修改驱动程序INF文件的.Mfg部分以包含设备及生产厂的ID。这一工作是必需的,这样PnP管理器可以与它的INF文件协调设备。Windows NT 4.0的例子及Windows 2000的修改后的.Mfg部分如下: [ABC.Mfg] ;Windows NT V4.0 INF %ABC% ABC Graphics Accelerator A=abc %ABC% ABC Graphics Accelerator B=abc [ABC.Mfg] ;Windows 2000 INF %ABC% ABC Graphics Accelerator A=abc,PCI\VEN_ABCD&DEV_0123 %ABC% ABC Graphics Accelerator B=abc,PCI\VEN_ABCD&DEV_4567 可以用与Windows 2000 DDK一起发布的geninf.exe工具来产生.INF。但要记住,geninf.exe不能为Windows NT 4.0产生INF,如果用geninf.exe生产的INF想支持Windows NT 4.0而不是Windows 2000,则必须修改由geninf.exe产生的INF文件。更多的内容可参考第一章创建图形的INF文件。 Windows 2000的视频端口作为Windows NT 4.0的继承来支持微端口驱动程序。当系统正在运行时,微端口驱动程序继承的图形适配器不能删除掉。同样,继承的微端口加入到一个正在运行的系统时也不能被自动地侦测到。 第三部分 打印驱动程序和假脱机组件 第1章 打印介绍 第2章 打印假脱机体系结构 第3章 打印驱动程序体系结构 第4章 微软统一打印驱动程序 第5章 微软PostScript打印驱动程序 第6章 微软绘图仪打印驱动程序 第9章 Internet打印 第10章 安装配置打印驱动程序 第11章 CPSUI(一种创建基于属性表单用户接口的工具) 第12章 打印机的色彩管理 第1章 打印介绍 微软的Windows 2000打印体系结构由打印假脱机和一套打印驱动程序组成。通过调用设备独立的Win32打印和GDI函数,应用程序可以创建打印作业并将它们发送到任何类型的设备,包括激光打印机、矢量绘图仪、光栅打印机及传真机等。打印驱动程序包括一个允许用户控制打印机选项的接口组件。 应用程序对Win32 GDI函数的调用传送到GDI图形引擎,GDI图形引擎可以将绘制指令假脱到一个EMF文件中,或者与打印驱动程序连接,向假脱机绘制一个可打印的图像。假脱机组件解释EMF文件,它们可以在数据流中插入页面布局信息以及作业控制指令。假脱机然后将数据流发送给具有目标打印机I/O端口的串行、并行或网络端口的驱动程序。 假脱机和驱动程序组件设计为可替换的,这样硬件的厂商可以很容易地提供对新硬件的支持,关于打印假脱机机驱动程序组件的更多信息,可参考以下章节: 第2章打印假脱机体系结构 第3章打印驱动程序体系结构 支持一个新的打印机常常需要创建一些新的数据文件并与微软提供的打印驱动程序一起使用。关于微软的打印驱动程序的更多信息,参考以下章节: 第4章微软统一打印驱动程序 第5章微软PostScript打印驱动程序 第6章微软绘图仪打印驱动程序 可以定制微软统一打印驱动程序及微软PostScript打印驱动程序的行为。更多的信息参考第7章定制微软打印驱动程序部分。也可以定制Windows 2000打印假脱机,可以参考第8章定制打印假脱机组件。 其后的其他章节包含以下一些内容: 第9章Internet打印 第10章安装配置打印驱动程序 第11章CPSUI(一种创建基于属性表单用户接口的工具) 第12章打印机的色彩管理 第2章 打印假脱机体系结构 微软Windows 2000打印假脱机由一套微软提供的和一些可选的厂商提供的组件组成,其职责包括: 决定一个打印作业应当本地处理还是通过网络进行。 与打印机驱动程序相连接,接收由GDI创建的数据流,并向特定类型的打印机上输出。 如果假脱打印允许,则将数据假脱打印到文件。 在逻辑打印机队列中选择第一个可用的物理打印机。 将假脱机格式(如EMF)的数据流转换成可以发送到打印机硬件的格式(如PCL)。 向打印机硬件发送数据流。 为假脱机组件及打印机表格维护一个基于注册表的数据库。 2.1打印机组件 本部分提供下列主题 2.1.1假脱机组件介绍 2.1.2打印提供者 2.1.1假脱机组件介绍 微软Windows 2000打印假脱机最基本的组件如图2-1所示: 插入图2-1Windows 2000打印假脱机组件 应用程序 一个打印应用程序,主要通过调用GDI函数创建打印作业。 GDI 图形设备接口(包括用户模式和核心模式的组件) Winspool.drv 这是一个到假脱机的客户端接口。它导出的函数构成了假脱机的Win32 API。并对存取服务器提供RPC栈。(GDI是最基本的客户端,但是应用程序也可以调用一些Win32函数)。 Spoolsv.exe 这是一个假脱机的API服务器。它作为Windows 2000的一个服务来实现的,并且随着操作系统的启动而启动。这一模块对服务器端的假脱机的API导出一个RPC接口。客户端的spoolsv.exe包括winspool.drv(本地)以及Win32spl.dll(远程)。该模块实现一些API函数,但是许多的函数调用通过发送的手段(spoolss.dll)通知到打印提供者。 Spoolss.dll 这是一个假脱机的发送器。它决定哪一个打印提供者去调用,它基于打印机的名称或每一个函数调用提供的句柄,并将函数调用传递到正确的提供者。 打印提供者 打印提供者支持专用的打印设备。 如果打印机硬件在应用程序运行的本地系统,则客户端及服务器端是同一系统(尽管这在上图中并不是很明显)。 所有的假脱机组件者在用户模式下执行。 2.1.2打印提供者 本部分提供以下主题: 2.1.2.1打印提供者介绍 2.1.2.2打印提供者示例 2.1.2.3打印提供者能力 2.1.2.4由打印提供者定义的功能 2.1.2.5本地打印提供者 2.1.2.1打印提供者介绍 打印提供者负责将打印作业直接送到本地或远程的打印设备。它们同样负责打印队列管理的操作,如开始、停止以及枚举一个服务器端的打印队列等。打印提供者定义了一个高层的、机器独立的、操作系统独立的打印服务器的视图。 所有的打印机提供者提供实现一个普通的打印提供都能力集。这些能力由一套API函数定义的,并被假脱机发送器(spoolss.dll)调用。 许多由打印提供者定义的函数需要一个打印句柄作为输入,假脱机的客户端在winspool.drv中通过调用OpenPrinter获取一个打印机的句柄(在平台的SDK中描述),该客户端调用API服务器(spoolsv.exe)。假脱机发送器(spoolss.dll)调用每一个打印提供者的OpenPrinter函数直至其中一个函数提供一个打印机的句柄并且返回一个值,即指明打印提供者识别出专用的打印机名称。发送器此时则返回它的句柄到API服务器,发送器的句柄包括打印机的句柄以及打印提供者的句柄。该句柄返回到应用程序,这样应用程序的后续的调用就可以直接送到正确的提供者和打印机。 微软在Windows 2000中提供以下打印提供者: localspl.dll 本地打印提供者。从本地服务器进行管理,处理所有的到打印机的打印作业。 win32spl.dll Windows网络打印提供者。处理所有的到远程的Win32(Windows NT/Windows 2000/Windows for Workgroup)服务器的打印作业,当作业到达远程服务器,它又传递给服务器的本地打印提供者。 nwprovau.dll Novell Netware的打印提供者,直接处理打印作业到Novell Netware的打印服务器。 inetpp.dll HTTP打印提供者。处理发送到一个URL的打印作业 (参考第9章,Internet打印)。 不同的厂商可以创建附加的网络打印提供者,更多的信息,可以参考第8章编写网络打印提供者部分的内容。 以下图示了包括这些打印提供者的可能的流程: 插入图2-2 打印提供者流程 当看上图时,应当考虑以下几点问题: 如果打印机由客户系统管理,打印作业被本地打印提供者(localspl.dll)处理。由localspl.dll管理的打印机则不必在物理上在本地与客户相连,它们可以直接连接到网络卡上。 如果打印机设置在Windows NT/Windows 2000服务器上,网络提供者(win32spl.dll)利用RPC重定向从客户端发送器发送到服务器spllsv.exe处理的调用。因为打印机对服务器是本地的,因此服务器本地打印提供者将处理打印作业。 如果打印机设置在其他一些类型服务器上,它则可以被本地打印提供者或支持该服务器类型、使用服务器支持的数据格式及网络协议的网络打印提供者存取。 对本地打印提供者存取远程打印机,它必须包含一个端口监视器,它使用远程打印机或服务器能识别的网络协议。 2.1.2.2打印提供者举例 在DDK中包含了一个打印提供者的实例。该例子不是一个完全功能的打印提供者。它是一个实现了打印提供者定义的函数基本轮廓的模板。实例代码放在DDK树形实例代码的pp子目录下。 2.1.2.3打印提供者能力 通过支持预定义的API函数集,Windows 2000打印提供者可以支持以下能力: 打印队列管理:增加、删除、打开、关闭、枚举及设置打印队列的参数,也能提供打印队列状态变化的通知。 打印机驱动程序管理:增加、删除、枚举及指定一个打印机驱动程序的目录。 打印作业创建:开始和结束一个文档,开始和结束一个文档分页,将作业的数据流写入一个端口,并读取打印机的状态信息。 打印作业安排:安排、枚举、并设置打印作业的参数。 表格管理:增加、删除、枚举并设置打印表格的参数。 打印处理器管理:增加、删除、枚举、指定目录及打印处理器支持的数据类型。 打印监视管理:增加、删除以及枚举打印监视器。 端口管理:增加、删除、配置、枚举并设置打印机端口的参数。 注册表管理:创建、删除并枚举与打印提供者相关的注册表键及值。 其他能力:显示消息框、关闭打印提供者、读一个内存映像的假脱文件、提供与端口监视器用户接口UI DLL与端口监视器服务器端DLL的通信通道。 这些能力都按照打印提供者定义的一套函数来实现的。 2.1.2.4打印提供者定义的函数 本部分主题列出了所有打印提供者可以提供的函数,这里面的许多函数在平台的SDK文档中都有介绍。如果函数在DDK中有介绍,则根据函数的名称提供一个到相关参考页的链接。 所有的打印提供者必须对所有列出的函数提供指针。但是,许多厂商提供的打印提供者是“部分的提供者(Partial Providers)”,其实质并没有提供支持打印提供者的很多的由函数定义的操作。因此,许多的函数指针可以是空值NULL。对更多的关于部分打印提供者的信息,参考第8章编写网络打印提供者。 在下面列出的函数表格中,必须支持的函数都标记有“必需的”字样。 所有的打印提供者必须导出初始化的函数InitializePrintProvidor。指向所有其他函数的指针都必须在PRINTPROVIDOR结构中提供。(注意这两个名称用错误码拼写,但是与在头文件winsplp.h中的名称是一致的)。 函数分成以下几组: 初始化函数 打印队列管理函数 打印机驱动程序管理函数 打印作业创建函数 打印作业调度函数 表格管理函数 打印处理器管理函数 打印监视器管理函数 端口管理函数 注册表管理函数 其他函数 初始化函数 函数 描述 InitializePrintProvidor(必需的) 初始化打印提供者并对提供的函数返回一个指针   打印队列管理函数 函数 描述 AddPrinter 向打印提供者管理的列表中增加一个打印队列并且为该打印队列协调相应的打印处理器  AddPrinterConnection 创建一个到特定打印队列的连接  ClosePrinter(必需的) 禁止调用者存取专用的打印队列  DeletePrinter 从打印提供者管理的列表中删除一个打印队列  DeletePrinterconnection 删除到某特定打印队列的连接  EnumPrinters(必需的) 枚举由打印提供者管理的当前的打印队列  FindClosePrinterChangeNotifcication 禁止打印机改变由FindFirstPrinterchangeNotification函数允许的通知信息。  FindFirstPrinterChange Notification 对一个等待的调用者的对象返回一个句柄,调用者可用它来等待专用的打印机消息  GetPrinter(必需的) 返回特定打印队列的当前参数值  OpenPrinter(必需的) 允许调用者存取一个专用的打印队列  RefreshPrinterchangeNotification 如客户端以PRINTER_NOTIFIY_OPTIONS_REFRESH标志调用FindNextPrinterChangeNotification则由发送器调用(可参考平台的SDK文档)  ResetPinter 修改一个打印队列的数据类型或DEVMODE结构  SetPrinter(必需的) 对一个专用的打印队列设置参数  WaitForPrinterChange 已废弃   打印驱动程序管理函数 函数 描述 AddPrinterDriver 给专用的服务器增加打印机驱动程序文件  AddPrinterDriverEx 功能与AddPrinterDriver相同,参数不同  DeletePrinterDriver 删除专用的服务器上的打印机驱动程序  EnumPrinterDrivers 返回一个通过调用AddPrinterDriver或AddPrinterDriverEx增加到一个服务器上的打印机驱动程序的列表  GetPrinterDriver 返回一个打印驱动程序的信息,其信息可以被通知到AddPrinterDriver函数(返回的信息主要是从.inf文件获得的)  GetPrinterDriverEx 同GetPrinterDriver功能相同,参数不同  GetPrinterDriverDirectory 返回服务器的打印驱动程序的目录名称   打印作业创建函数 函数 描述 AborPrinter(必需的) 试图删除从专用的打印队列中删除当前作业  AddJob(必需的) 返回一个作业的标识符及假脱机文件路径,调用者可以用CreateFile和WriteFile将数据发送到假脱文件  EndDocPrinter(必需的) 执行打印作业结束的操作  EndPagePrinter 执行分页结束的操作  ReadPrinter 获得一个双向打印机的状态信息  ScheduleJob(必需的) 通知打印提供者一个专用的作业可以安排调度,该作业由之前的AddJob函数返回的标识符来鉴别  StartDocPrinter(必需的) 准备使打印提供者开始假脱一个打印作业  StartPagePrinter 准备使打印提供者接收一个打印作业页面  WritePrinter(必需的) 收到打印作业的部分数据流  注意:从AddJob……ScheduleJob的顺序是从StartDocPrinter……EndDocPrinter顺序的变化。 打印作业调度安排 函数 描述 EnumJobs(必需的) 返回一个调度好的打印作业的列表  GetJob(必需的) 返回作业参数  SetJob(必需的) 撤消、暂停、恢复或重新开始一个打印作业或者设置作业参数   表格管理函数 函数 描述 AddForm 将一个专用的表格增加到一个指定打印机的可用列表中  DeleteForm 从一个指定的打印机可用的列表中删除一个特定表格  EnumForms 从一个指定的打印机可用的列表中返回一个表格的列表  GetForm 返回一个指定表格的特征  SetForm 修改一个指定表格的特征   打印处理器管理函数 函数 描述 AddPrintProcessor 在一个专用的服务器上安装打印处理器并将它增加到打印提供者可以调用到的列表中  DeletePrintProcessor 从打印提供者可以调用的列表中删除一个特定处理器  EnumPrintProcessorDataTypes 返回一个打印处理器支持并且可以打印提供者调用的数据类型的列表  EnumPrintProcessors 返回打印提供者可以调用的打印处理器的列表  GetPrintProcessorDirectory 返回打印处理器文件必须存储的目录路径  AddMonitor 增加一个打印监视器到打印提供者可以调用的列表中   打印监视管理函数 函数 描述 DeleteMonitor 从打印提供者可以调用的列表中删除一个打印监视器  EnumMonitors 返回一个打印提供者可以调用的打印监视器列表   端口管理函数 函数 描述 AddPort 给可用的列表中增加端口,通常是通过调用专用的AddPortUI这一端口监视函数  AddPortEx 与AddPort相同,参数不同  ConfigurePort(必需的) 配置一个打印机的端口,通常是通过调用ConfigPortUI这一端口监视函数  DeletePort(必需的) 从可用的列表中删除打印机端口,通常通过调用DeletePortUI这一端口视示器函数  EnumPorts(必需的) 返回一个可用的打印机端口列表  SetPort 给一个专用的打印机端口设置参数   注册表管理函数 函数 描述 DeletePrinterData 删除当前分配给一个特定值名称的值,即特定打印机的PrinterDriverData关键字下的值  DeletePrinterdataEx 与DeletePrinterData相同,参数不同  DeleteprinterKey 删除专用的键及子键,如果他们当前存储于特定打印机的注册表中PrinterDriverData关键字下  EnumPrinterData 返回每一个值的名称以及当前分配的、存储于特定打印机的注册表中的值  EnumPrinterDataEx 与EnumPrinterData相同,参数不同  EnumPrinterKey 返回包含于当前注册表中的专用的键字名称下的子键的列表  GetPrinterData 返回当前分配给特定值名称的值,它存储于特定打印机的注册表中PrinterDriverData关键字下   其他函数 函数 描述 GetPrinterDataEx 与GetPrinterData相同,具有附加的参数  SetPrinterData 存储特定打印机的PrinterDriverData关键字下的专用的值名称以及在注册表中的值  SetPrinterDataEx 与SetPrinterData相同,具有附加的参数  XcvData 提供在端口监视器UI DLL和端口监视器的服务器端DLL之间的通信通路   2.1.2.5本地打印提供者 Windows 2000的本地打印提供者为所有可以通过本地打印提供者的端口监视器存取的打印机提供作业控制以及打印机管理能力。(客户端的管理者在使用增加打印机向导Add Printer Wizard时通过选择“我的电脑”选项建立对这些打印机的存取)。这样的打印机包括那些连接到本地系统串口及并口的打印机,他们也能包括连接到其他I/O,如连接在远程非Windows NT/Windows 2000服务器的SCSI端口打印机。 本地打印提供者实现整套由打印提供者定义的函数,它也支持以下能力: 打印作业假脱机,可以不假脱打印作业而直接将其打印到可存取的打印队列中 支持具有到本地打印机接口DLL调用的Windows 2000打印驱动程序 支持不同厂商提供的打印处理器(参考第8章的写打印处理器部分) 支持不同厂商提供的打印监示器(参考第8章的写打印监视器部分) 下面的图示,提供了应用程序创建打印作业时(某种程度上是简化了的),在本打印机提供者组件之间的流控视意图。 如图所示,一个应用程序通过调用GDI创建了一个打印作业,不管打印作业初始时的输出格式是否是EMF,本地打印提供者作业创建的API则创建了一个假脱文件,以后,当作业安排调度,假脱机文件被读并且如果该格式是EMF,则EMF打印处理器将此作业送回到GDI并在打印机图形的DLL(Pinter Graphics DLL)帮助下转换成为RAW格式,转换后的数据流可以通过本地打印提供者被送回到打印机(不需要再进行假脱)。 插入图2-3 本地打印提供者流控 厂商可以创建与本地打印提供者相连的部分打印提供者以支持自定制的网络配置。 第3章 打印机驱动程序体系结构 打印作业是由应用程序通过微软的Win32 GDI函数调用来创建的,这些函数调用可以被假脱为EMF数据类型记录以备EMF打印处理器以后回放,或者它们可以对每个文档页面立即绘制一个可打印的图像。是否立即执行或在EMF记录的回放时期进行图像的绘制,是由GDI绘制引擎来控制的,在绘制的操作中,GDI绘制引擎将调用合适的Windows 2000打印驱动程序来支持。 Windows 2000打印机驱动程序具有如下责任: 通过提供GDI不能支持的特定打印机的绘制能力来帮助GDI绘制打印作业。 发送绘制的图像数据流到打印假脱机。 提供一个与打印机及打印文档相关的可修改配置参数的用户接口,如选择的输入输出的托架、打印的份数、图像的精度及方向等等。 Windows 2000打印驱动程序由一套打印驱动程序组件组成,这些组件将驱动程序的绘制以及用户接口操作分到独立的DLL中。 本章的目的是解释怎样为Windows 2000编写驱动程序,但非常重要的一点是,必须记住下面三个与操作系统一起发布的打印机驱动程序: 微软统一打印机驱动程序 微软Postscript打印机驱动程序 微软绘图仪打印机驱动程序 这三个驱动程序支持当今市场上许多打印设备。只需要在打印机设备与微软提供的合适的打印驱动程序不兼容时才写打印机驱动程序,许多新的打印机可以简单到只需增加一个打印机的数据文件到这些驱动程序中就可以获得支持。那些包含由私有的命令序列控制的硬件绘制加速器的设备可能才需要重新写驱动程序。 3.1打印机驱动程序组件 所有的Windows 2000打印机驱动程序由以下组件构成: 一个打印机图形DLL,它帮助GDI绘制打印作业并发送绘制的数据流到打印假脱机。 一个打印机接口的DLL,它为驱动程序参数配置提供一个用户接口和一个假脱机可以调用以来通知驱动程序系统打印相关事件的接口。 另外,微软提供的打印机驱动程序利用打印机的数据文件。 3.1.1打印机图形DLL 本部分提供以下主题: 3.1.1.1打印机图形DLL介绍 3.1.1.2由打印机图形DLL定义的函数 3.1.1.3打印机图形DLL实例 3.1.1.4绘制一个打印作业 3.1.1.5支持设备字体 3.1.1.6返回专用的打印机信息 3.1.1.7选择用户模式或内核模式 3.1.1.8构建一个打印机图形DLL 3.1.1.1打印机图形DLL介绍 打印机图形DLL实现了在第一部分的第3章支持DDI中描述的Drv为前辍的DDI函数。这些DLL具有以下列两项责任: 帮助GDI绘制打印作业:一个打印机图形DLL可以支持DDI绘制函数来处理那些必须以特定设备的方式执行以及那些不能由GDI的绘制引擎完成的绘制操作。 发送绘制数据流到假脱机:打印机图形DLL通常以RAW数据类型(包括命令序列)产生一个输出流,假脱机可以通过打印监视器将它发送到打印机硬件。 一个打印机图形DLL必需提供的、特定打印机类型的绘制帮助的总数量,依赖于硬件的绘制能力并包括以下情形: GDI绘制引擎用一个GDI管理的表面执行所有的绘制,图形的DLL不提供任何的DDI绘制函数。 图形DLL提供一些DDI绘制函数用一个GDI管理的表面与GDI绘制引擎一起工作。由图形DLL提供绘制DDI函数可以选择性地回调GDI绘制引擎的GDI支持的服务。 图形DLL通过提供DDI绘制函数和一个设备管理的表面来执行所有的绘制。 例如,当微软统一打印机驱动程序使用一个设备管理的表面时,微软统一打印机驱动程序(Unidrv:Microsoft Universal Printer Driver)使用一个GDI管理的表面并提供一些DDI绘制函数。 更多的关于在图形驱动程序中提供绘制帮助的信息,参考第一部分的第2章表面类型和第一部分的第3章支持DDI部分的内容。 打印机图形DLL在EMF假脱一个打印作业时不能调用,当EMF打印处理器回放EMF记录时适当的图形DLL调用。 但要注意,打印机图形DLL不能发送绘制的打印作业到硬件,被绘制的数据流返回到假脱机,假脱机发送该数据流到一个端口监视器,该端口监视器是一个用户模式的接口到一个内核模式的本地并行端口或网络端口的微端口驱动程序 对Windows 2000来说,可以设计一个打印机的图形DLL以在内核模式执行,或者可以设计它在用户模式执行,更多的信息可以参考选择用户模式或内核模式部分的内容。 下面的两个图表示当一个应用程序用GDI创建一个打印作业时发生的数据流,第一个图表是一个用户模式的打印机图形DLL。 插入图3-1 打印作业数据流,使用一个用户模式的打印机图形DLL 第二个图表是一个内核模式的打印机图形DLL。 插入图3-2 打印作业数据流,使用一个内核模式的打印机图形DLL 注意,在图表中,如果从GDI输出的格式是EMF,打印机图形DLL在EMF打印处理器回放EMF作业记录前不接收作业,也要注意的是EMF打印处理器改变输出格式到非EMF格式。 上面两个图表示了一个完整的本地环境,如果打印机连接到一个服务器,EMF记录则通常客户的GDI绘制引擎(GRE)拷贝来产生,然后假脱到一个本地文件后送到服务器,服务器的假脱机拷贝读该文件并将这些记录送到服务器的EMF打印处理器,并且服务器端的GRE拷贝调用服务器打印机图形DLL。 3.1.1.2由打印机图形DLL定义的函数 如果所有的图形驱动程序,打印机图形DLL负责定义以下的DDI函数,由初始化驱动程序DrvEnableDriver函数的入口点开始,其余的函数以字母顺序列出。 函数名称 描述 DrvEnableDriver 允许驱动程序初始化它自身并返回支持DDI函数的指针  DrvCompletePDEV 对一个设备实例提供具有GDI句柄的驱动程序  DrvDisableDriver (可选)允许驱动程序在卸载之前执行清除工作  DrvDisablePDEV 允许驱动程序删除设备特定实例的信息  DrvDisableSurface 允许驱动程序删除绘制表面  DrvEnablePDEV 允许驱动程序提供具有物理设备特征的GDI并初始化设备特定实例的信息  DrvEnableSurface 允许驱动程序创建绘制表面  DrvQueryDeviceSupport (可选)返回被请求的特定设备的信息  DrvQuery (可选)返回被请求的特定驱动程序的信息   打印机图形DLL也对定义以下特定打印的DDI函数负责,它们在绘制打印作业过程中的某一特定点时被调用。 函数 何时调用 DrvEndDoc 当GDI已经结束发送一个文档到驱动程序以备绘制时  DrvNextBand (可选)当GDI已经结束绘制一个物理页面的一个区带(band),这样驱动程序可以将该区发送到打印机  DrvQueryPerBandInfo (可选)在GDI开始为一个物理页面绘制区带之前,这样驱动程序可以提供具有特定区带信息的GDI。  DrvSendPage 当GDI已经结束绘制物理页面,这样驱动程序可以将此页面发送到打印机。  DrvStartBanding (可选)当GDI准备绘制时开始发送一个物理页面的区带信息到驱动程序  DrvStartDoc 当GDI准备绘制时开始发送一个文档到驱动程序  DrvStartPage 当GDI准备绘制时开始发送一个文档页到驱动程序   通常,一个打印机图形DLL也定义任何必需的附加DDI函数以完成打印作业绘制。被定义函数的数量及类型依赖于: 是否驱动程序支持绘制表面时使用被管理的GDI或被管理的设备(或者两者都支持),更多的信息,可以参考第一部分的第2章表面类型。 一个绘制操作可以被GDI函数处理而不是由驱动程序自身执行的程度。更多的信息,参考第一部分第3章支持DDI部分的内容。 所有的由打印机图形DLL定义的函数都是被GDI内核模式图形绘制引擎(GRE)来调用的。 DrvEnableDriver和DrvQueryDriverInfo函数由图形DLL导出,所有其他支持DDI函数的地址放置于一个由DrvEnableDriver函数返回的表中。 3.1.1.3打印机图形DLL实例 plotter.dll的源代码是打印机图形DLL为微软绘图仪驱动程序提供的,与些DDK一起提供,其代码放置于包含DDK实例目录的子目录中。 3.1.1.4绘制一个打印作业 打印作业或者是在它们创建时绘制,或者是被假脱成为EMF记录绘制。在后一种情况下,绘制发生在当EMF打印处理器(Winprint.dll)回放EMF记录时。绘制则由一系列对GDI的Win32绘制函数的调用构成,并以CreateDC为调用的开始。 当一个应用程序调用GDI的CreateDC函数来创建打印机的设备设备环境,GDI检查看是否有合适的打印机图形DLL被装载,如果没有,GDI装载DLL并调用它的DrvEnableDriver函数,该函数不会被再次调用,除非驱动程序被重新装载。 接下来,GDI调用打印机图形DLL的DrvEnablePDEV函数,这样驱动程序可以创建一个设备实例并返回设备特征。GDI用返回的信息以创建设备实例的内部描述。图形DLL必须用这一句柄作为一个Eng为前缀,由GDI绘制引擎(参考第一部分第2章GDI支持服务)提供的回调的输入。 调用图形DLL的DrvEnableSurface函数之后,驱动程序可以通过调用EngCreateBitmap为设备实例创建一个绘制表面,或者,如果绘制表面是设备管理的,则调用EngCreateDeviceSurface。如果EngCreateBitmap不能提供一个足够大的位图来包含整个的物理页面,并且如果驱动程序支持页面分区带的功能,EngMarkBandingSurface可以被调用以通知进行GDI分区带的工作。最后,EngAssociateSurface必须被调用以允许GDI将创建的表面与特定设备实例协调,并允许GDI知道哪一个驱动程序提供的DDI绘制函数(如果任意)应当在绘制这一特殊表面时调用。 从这一点来看,一个绘制表面已经创建并且绘制可以开始,对每一个绘制的文档来说,GDI在打印机图形DLL中调用以下的函数: DrvStartDoc For each physical page{ DrvStartPage DrStartBanding For each banding surface{ DrvQueryPerbandInfo Rendering operations DrvNextBand } DrvSendPage } DrvendDoc 除DrvqueryPerBandInfo函数之外,这些函数都是为了允许打印机图形DLL向打印机硬件发送控制序列(通过调用EngWritePrinter),并执行初始化或完成处理一个文档、页面或区带等任何内部操作。 打印机图形DLL负责在适当的时间发送绘制图像(即,绘制表面的内容)到打印机(通过调用EngWritePrinter),如下: 对GDI管理或设备管理的位图表面:绘制表面是GDI提供的或驱动程序提供的位图,打印机图形DLL可以分出一些起绘制函数(参考第一部分第2章的表面协商部分),如果使用了页面区带,DrvNextBand函数应当发送绘制表面内容。如果不使用区带,DrvSendPage函数应当发送绘制表面内容。 对设备管理的向量表面:绘制表面是在设备里,打印机图形DLL分出的所有绘制函数(参考第一部分第2章的表面协商部分),并且这些函数在绘制操作过程中发送图像数据到打印机,而页面分带区不能被使用。 如果预料任意由打印机DLL提供的DDI函数可以潜在执行5秒以上,就应当包括至少每5秒一次调用EngCheckAbort至少每5秒一次的代码并看是否打印作业被中断。 在GDI调用DrvendDoc以指明一个文档已经绘制完成后,它调用DrvDisableSurface。如果DrvEnableSurface调用EngCreateBitmap,则DrvdisableSurface必须调用EngDeletesurface。 GDI在一个应用程序调用DrvDeleteDC时调用一个打印机图形DLL的DrvDisablePDEV函数。 如果一个应用程序在打印一个文档时调用GDI的ResetDC函数,GDI创建一个新的设备设备环境并为该新设备设备环境调用打印机图形DLL的DrvEnablePDEV函数。然后,GDI调用DrvResetPDEV函数,这样图形DLL可以用从旧的设备环境获得的信息修改新的设备环境。接着,DrvDisableSurface及DrvDisablePDEV为旧的设备环境而调用,对新的设备环境,接下来被调用的函数就是DrvEnableSurface。最后,GDI调用DrvStartDoc在一个新页上进行绘制。 GDI调用DrvDisableDriver优先于卸载打印机图形DLL。 如果打印机硬件支持GDI绘制函数不支持的绘制操作,则打印机图形DLL可以提供一个DrvDrawEscape函数。 如果需要支持通过GDI函数不能实现的绘制或非绘制的操作,则一个打印机图形DLL需要提供一个DrvEscape函数。例如,微软的Postscript打印机驱动程序用escape来支持Postscript入口点,或者,一个应用程序也许需要获得一个传真机的电话号码,DrvEscape函数也用于指出由DrvDrawEscape函数支持的操作。 3.1.1.5支持设备字体 如果一个打印机提供设备字体,打印机图形DLL必须定义一个DrvTextOut函数以产生文本输出命令,图形DLL必须也定义以下函数: DrvQueryAdvanceWidths DrvQueryFont DrvQueryFontData DrvQueryFontTree 更多的关于支持设备字体的信息,参考第一部分第3章中的支持DDI字体及文本函数部分。 3.1.1.6返回专用的打印机信息 GDI有时请求打印机图形DLL返回在打印作业之间的打印机专用的信息,通过调用这些DrvQuery前缀的DDI函数如DrvqueryAdvanceWidths(如果被图形DLL定义)。 什么时候会发生这些的一个例子是在字处理应用程序中维持了一个所见即所得(WYSIWYG)屏幕显示的可打印页面。为了准确地显示文本中的线的间断,字处理器必须对字符宽度及其他选定的打印机实现的字体的尺度进行基准线的合适性计算。 3.1.1.7选择用户模式或内核模式 在4.0版以前的Windows NT中,GDI及所有的图形驱动程序都是在用户模式下执行,在4.0版中,这些组件被移到内核模式,这一改变提高了显示设备的性能,但对打印设备的性能获得并不显著,不足以抵消继承用户模式失去的优点。 因此,对Windows 2000,可以设计一个打印机图形DLL以在内核模式下执行,或者可以设计在用户模式下执行。用户模式下的打印机图形DLL执行提供以下超过内核模式下执行的优点: 无限制的栈空间 存取Win32 API 更少的导致系统崩溃的潜在因素 用户模式的纠错器,更容易纠错 更好的浮点计算能力,尽管DDI浮点函数不是必需的 调用任何定制的,厂商提供的用户模式的但不是Windows 2000打印机驱动程序体系结构中描述的部分的DLL的能力。 在用户模式下使用图形DDI 一个用户模式打印机图形DLL并没有限定于去调用GDI支持的服务(第一部分第2章)和其他以Eng为前缀的图形DDI回调函数。但是,有一些必须遵从的规则: 如同内核模式图形DLL,用户模式图形DLL必须调用图形DDI的版本函数以创建或修改绘制表面,这些回调函数是GDI支持的服务,并且不允许调用这些绘制函数在Win32中的等效的函数。 对用户模式的DLL,调用这些绘制的回调函数被用户模式的GDI客户端截听,之后,客户端会通知GDI的内核模式图形绘制引擎(GRE)。 下面的以Eng为前缀的DDI函数不能被用户模式的DLL调用。 EngCreatePath EngCreateClip EngDeleteClip EngGetTypeFontList EngMapModule EngDebugBreak 所有其他以Eng为前缀的函数对内核模式的打印机图形DLL都是可用的,在用户模式下也是也用的。对那些与Win32完全等效的函数,如EngGetPrinter或EngGetPrinterDriver,则Win32等效的函数被调用。没有完全与Win32等效的函数,则在GDI客户端来实现。 用户模式的打印机图形DLL可以继续用DDI函数以实现对浮点的支持。 将一个存在的打印机图形DLL转换到用户模式 如果以前开发过运行于内核模式的打印机图形DLL,对Windows 2000来说,会有以下选择: 什么也不做。已经存在的内核模式的打印机图形DLL将会继续在内核模式下工作,微软也许会在以后的操作系统发行版本中删除对内核模式打印机图形DLL的支持。 转换该DLL到用户模式下执行。为了执行,只需增加一个DrvQueryDriverInfo函数到DLL,然后遵从构建打印机图形DLL的规则。 在创建一个新的在用户模式下的打印机图形DLL 当开发一个新的执行在用户模式下的打印机图形DLL时,可以继续使用所有的被内核模式DLL使用的图形DDI函数。但是,也有下边这些选择: 对以Eng为前缀有完全等效Win32的函数,可以调用Win32函数,这些函数由以下一些函数构成: EngAllocMem EngGetForm EngMulDiv EngAllocUserMem EngGetLastError EngSetLastError EngEnumForms EngGetPrinter EngSetPrinterData EngFreeMem EngGetPrinterData EngUnloadImage EngFreeUserMem EngGetPrinterDriver EngWritePrinter EngFindImageProAddress EngLoadImage 对以Eng为前缀的那些与Win 32函数符合一些简单功能的函数,可以调用Win32函数,这些函数由以下一些函数组成: EngAcquirSemaphore EngGetCurrentCodePage EngMultiByteToWideChar EngCreateSemaphore EngGetDriverName EngQueryLocalTime EngDeleteSemaphore EngGetPrinterDataFileName EngReleaseSemaphore EngFindResource EngLoadModule EngUnicodeToMultiByteN EngFreeModule EngMultiByteToUnicodeN EngWideCharToMultiByte 对那些创建或修改一个绘制服务的函数,新的驱动程序必须继续调用GDI支持服务而不是它们等效的Win32函数。 不使用DDI函数来支持浮点,可以使用FLOAT数据类型。 3.1.1.8构建一个打印机图形DLL 当构建一个打印机图形DLL时,必须清楚打算在用户模式下执行和打算在内核模式下执行的DLL之间的以下差别: 构建打印机图形DLL的规则 用户模式图形DLL 内核模式图形DLL 在source文件中,设置TARGETTYPE=DYNLINK 在source文件中,设置TARGETTYPE=GDI_DRIVER  处理器宏USERMODE_DRVER必须在winddi.h包含之前在source文件中被定义 处理器宏USERMODE_DRIVER不能被定义  对象模块必须被与umpdddi.lib和gdi32.lib导入库链接在一起 对象模块必须与win32k.lib导入库链接  DrvQueryDriverInfo函数必须返回DRVQUERY_USERMODE DrvQueryDriverInfo函数必须给DRVQUERY_USERMODE返回FALSE(或二者选择其一,该函数可以被省略)   3.1.2打印机接口DLL 本部分提供以下主题: 3.1.2.1打印机接口DLL介绍 3.1.2.2由打印机接口DLL定义的函数 3.1.2.3打印机接口DLL实例 3.1.2.4为打印机创建原型表单页面 3.1.2.1打印机接口DLL介绍 打印机通常给用户提供大量的可修改配置的选择,这些选择项可以在每一个被付印的文档中被改变。如页面、托架、字体选择、图像精度、尺寸、色彩等等都必须可以通过用户接口被存取,也可以被应用程序调用。 一个打印机驱动程序的打印机接口DLL,它是执行在用户模式下,负责导出一个用户的接口到打印机的配置选项。提供这一接口包括为打印机创建原型表单页面。应用程序(如打印折叠器)通过调用由打印假脱机导出的Win32函数显示接口,并且,假脱机返过来,又调用由打印机接口DLL定义的函数。 为配置选项提供一个用户接口不是打印机接口DLL的唯一责任,DLL同时也导出假脱机可以调用的函数以通知驱动程序打印相关的系统事件,如驱动程序安装、升级或者添加打印机及打印机连接等。 3.1.2.2由打印机接口DLL定义的函数 打印机接口DLL导出函数列于下面表格中: 函数 目的 DllEntryPoint 初始化DLL的入口点,通常调用DLLMain(在平台的SDK文档中有描述)  DrvConvertDevMode 转化专用的DEVMODE结构到另外一个版本  DrvDeviceCapabilites 返回请求的关于一个打印机能力的信息  DrvDevicePropertySheets 调用CPSUI以产生描述打印机属性的表单页面属性  DrvDocumentEvent (可选)允许打印机接口DLL去处理与打印文档相关的一定的事件  DrvDriverEvent (可选)允许打印机接口DLL对假脱机来的已经发生的关于特定打印机事件的消息通知作响应  DrvDocumentPropertySheets 调用CPSUI以产生描述打印文档属性的表单页面属性  DrvPrinterEvent 允许打印机接口DLL对假脱机来的已发生的关于特定打印机的事件的消息通知作响应  DrvQueryColorProfile (可选)允许打印机接口DLL指定一个ICC的profile以在色彩管理中使用  DrvQueryJobAttributes (可选)允许打印机接口DLL指定诸如打印多个文档页面在一个物理页上(“N-up”打印)、打页某一页面的多个拷贝以及校对页面等  DrvQueryPrintEx 决定一个打印作业是否能用打印机当前配置进行打印  DrvUpgradePrinter (可选)当一个新版本的驱动程序增加到系统时,修改打印机的注册表设置   3.1.2.3打印机接口DLL实例 plotui.dll的源代码,即微软绘图仪驱动程序的打印机接口DLL与此DDK一起提供,其代码放置于包含DDK示例代码的目录树的子目录中。 3.1.2.4为打印机创建原型表单页面 打印机接口DLL,与CPSUI连在一起,负责创建Windows 2000用户从事的与打印机及打印文档相关的视图及修改配置参数表单页面的属性。每一个打印机接口的DLL必须提供一个DrvDevicePropertySheets函数以创建特定打印机页面和一个DrvDocumentPropertySheets函数以创建特定文档的页表。 为了理解这些函数是怎样被设计,阅读描述CPSUI、显示表单页面属性包括应用程序之间的交互、打印假脱机、打印机接口DLL以及CPSUI执行流等许多在打印机驱动程序中使用CPSUI一章中描述的内容是非常重要的。 3.1.3打印机数据文件 微软提供的打印机驱动程序利用数据文件来描述各个打印设备,更多的信息,可以参考以下几章内容: 微软统一打印机驱动程序:描述了.gpd文件 微软Postscript打印机驱动程序:描述了.ppd文件 微软绘图仪打印机驱动程序:描述了.pcd文件 第4章 微软统一打印机驱动程序 本章叙述了微软统一打印机驱动程序(Unidrv),解释了怎样增加小驱动程序来允许Unidrv来支持新的打印机,主要包括了下面的主题: ■4.1统一打印机驱动程序介绍 ■4.2GPD文件介绍 ■4.3打印机属性 ■4.4打印机命令 ■4.5打印机特性 ■4.6打印机选项 ■4.7打印机字体描述 ■4.8条件语句 ■4.9压缩光栅数据 ■4.10过滤光栅数据 ■4.11处理色彩格式 ■4.12用Unidrv进行中间色调整 ■4.13控制图像质量 ■4.14处理可安装的特性及选项 ■4.15指定特性和选项显示的顺序 ■4.16描述打印机内存配置 ■4.17指定纸张大小 ■4.18引用场所 ■4.19安装一个Unidrv小驱动程序 4.1统一打印机驱动程序介绍 统一打印机驱动程序(Unidrv)是微软公司对非Postscritp打印机的标准打印机驱动程序,对Unidrv的介绍包括下列主题: ■4.1.1Unidrv能力 ■4.1.2Unidrv组件 ■4.1.3Unidrv小驱动程序 ■4.1.4Unidrv用户接口 ■4.1.5Unidrv绘制器 ■4.1.6GPD文件实例 ■4.1.7微软小驱动程序开发工具 4.1.1Unidrv能力 Unidrv提供以下能力: 用描述每一个打印机的特征的特定打印机Unidrv小驱动程序,支持所有的非Postscript打印机。 一个Unidrv的用户接口,基于Treeview控件和属性表单,它对所有的打印机者是一致的,而且对每一个打印机的单一选项也是可以修改的。 一个单一的Unidrv绘制器,即与GDI图形引擎一起,转换微软Win32 GDI调用从应用程序到打印机的可以发送给假脱机的命令。 为提供对以前的打印机驱动程序用GPC文件编写的小驱动程序的支持,RasDD,即微软小驱动程序开发工具(Microsoft Minidriver Development Tool)提供从RasDD GPC文件到Unidrv GPD文件的自动转换。 4.1.2Unidrv组件 Unidrv组件由DLL加上文本及二进制数据文件构成,如下图所示: 插入Unidrv组件图??? 在图中的组件包括: 应用程序 一个用户应用程序,如字处理程序,它提供给用户打印的能力。 gdi32.dll 用户模式的DLL,导出Win32 GDI函数 内核模式图形引擎 实现GDI函数功能的NT的可执行代码 小驱动程序文本文件 基于文本的Unidrv小驱动程序,它利用GPD文件的条目描述打印机 二进制数据文件 Unidrv在解析了包含于小驱动程序文本文件中的信息后创建的临时文件(具有.bud扩展名) Unidrvui.dll Unidrv用户接口的DLL,对所有Unidrv支持的打印机提供一个通用的UI代码。 用户接口插件 可选的、特定打印机的用户接口插件 compstui.dll CPSUI给打印机的用户接口 Unidrv.dll Unidrv绘制器,它绘制图像及发送图像数据流到打印假脱机 绘制插件 可选的、特定打印机的绘制插件 4.1.3Unidrv小驱动程序 Unidrv小驱动程序是包含打印机说明的文本文件。每一个小驱动程序描述了一种生产厂家的打印机类型。这一基于文本的说明即称为GPD(Generic Printer Desciption),并且每一个文件都称为GPD文件,每一个小驱动程序由一个或多个GPD文件构成。 利用GPD文件来描述一个打印机,Unidrv支持以下能力: 在许多打印机上发现的一般的、标准的打印机特性。 打印机提供的唯一的、定制的打印机特性。 可安装的打印机选项,只要在选项被安装的情况下可以选择。 选项约束,允许指定不兼容的选项。 条件语句,允许指定一些依赖于其他特征的打印机的特征。 打印机命令的规范说明,它可以包括当前一大堆选择的标准变量的值,也可以对这些变量执行算术操作。 一个定制的帮助文件,它不同于Unidrv提供的标准的帮助文件,而是用于描述定制的特性。 关于创建GPD文件的信息,参考GPD文件介绍部分。 一个Unidrv小驱动程序可以由一个或多个GPD文件构成,更多的信息,可以参考在一个小驱动程序中使用多个GPD文件部分的内容。 当一个打印机被安装,Unidrv的GPD解析阅读所有的GPD文件,GPD文件中包含的信息被用于创建打印机的临时的二进制文件。所有的Unidrv用户接口和Unidrv绘制器的参考都在这个二进制文件中。 通常,小驱动程序必须提供资源,如字体、位图以及本地化文本字符串等。这些资源被置于一个资源DLL中。更多的信息,参考在小驱动程序中使用资源DLL部分的内容。 4.1.4Unidrv用户接口 Unidrv用户接口用CPSUI以创建以下的属性表单页面: 设备设置(Device Settings)页面:用于打印机属性表单,它在一个用户选择了来自打印文件夹或打印机窗口的“属性”菜单项的时候被显示。该页面列出了特定打印机的配置信息。 文档属性表单的布局(Layout)、纸张/质量(Paper/Quality)以及高级(Advanced)属性页面:它在一个用户选择了打印机折叠器或打印机窗口的文档默认值(Document Defaults)菜单项时被显示,或者当一个应用程序调用了PrinterProperties或者DocumentProperties函数被显示(在SDK平台文档中有描述)。该页面列出了特定文档的配置信息。 这些属性表单页包含有由一个打印机的Unidrv小驱动程序指定的打印机的特性及打印机的选项,它们也允许用户修改选项值。 Unidrv用户接口作为一个用户模式的打印机接口DLL来实现,代码都含在DLL中,并与CPSUI连接来指定了属性表单的内容。DLL基于在小驱动程序中的信息,实施哪一个打印机选项可以被组合的限制。它也保证用户不选择没有在打印机上安装的选项。 4.1.5Unidrv绘制器 Unidrv绘制器作为一个打印机图形DLL来实现,并导出由微软设备驱动程序接口(Microsoft Device Driver Interface DDI)为图形驱动程序定义的函数。当一个应用程序调用图形设备接口(Graphics Device Interface GDI)函数以发送图像到一个打印机设备,内核模式的图形引擎调用绘制器的DDI函数,这些DDI函数帮助GDI绘制一个打印作业的页图像。 绘制器也负责与打印机的命令序列一起发送已绘制的图像数据到打印假脱机,打印假脱机然后再直接将图像和命令送到打印机硬件。打印机绘制器发送的命令是在Unidrv小驱动程序被指定的。 可以通过提供一个绘制的插件来修改Unidrv的绘制操作,该绘制插件在第7章定制微软打印机驱动程序部分有详细描述。 4.1.6GPD文件实例 DDK包括大量的小驱动程序实例,对一些类型的打印机来说,这些实例包括GPD以及字体和资源文件。实例文件被放置于包含DDK实例的树形目录的mini子目录中。 4.1.7微软小驱动程序开发工具 微软小驱动程序开发工具(Microsoft MDT),即包含在该DDK中的minidev.exe,是设计用于Unidrv的小驱动程序开发的软件开发工具。 微软的MDT提供下列特征: 一般打印机描述(GPD:Generic Printer Description)编辑器 一个用于创建构成Unidrv小驱动程序的GPD文件的、可以进行语法检查的文本编辑器。 Unidrv字体规格(UMF:Unidrv Font Metrics)编辑器 基于属性表单的产生Unidrv字体规格文件的编辑器 符号翻译表(GTT:Glyph Translation Table)编辑器 基于属性表单的产生符号翻译表文件的编辑器 小驱动程序工作间 对Unidrv小驱动程序的基于Windows的编辑工作间,提供对小驱动程序的GPD文件、资源文件及GTT文件的存取。 小驱动程序转换向导 自动转换二进制小驱动程序为基于文本的Windows 2000小驱动程序,用于以前的Windows及Windows NT版本。当使用该转换向导,它将根据转换下表列出的文件而产生一个Windows 2000的小驱动程序。 输入文件 文件内容 输出文件 Windows GPC文件 打印机能力描述 Windows 2000GPD文件  Windows PFM文件 字体描述 Windows 2000UFM文件  Windows CTT文件 符号翻译描述 Windows 2000GTT文件  另外,转换向导将用正确的文件名及资源标识符产生资源文件。 INF向导 对被转换的小驱动程序自动产生一个INF文件(参考安装一个Unidrv小驱动程序部分) 关于怎样使用微软的MDT的教学指导,可以运行该开发工具并阅读其帮助文件。 4.2GPD文件介绍 GPD文件用于创建Unidrv小驱动程序,一个Unidrv小驱动程序由一个基于文本的一般打印机描述(GPD)构成,它可以包含一个或多个GPD文件。 GPD文件用GPD语言描述一个打印机,GPD文件包含用GPD语言提供下列类型信息的GPD文件条目: 描述打印机特征的打印机属性 控制打印机操作的打印机命令 可以被Unidrv控制的描述打印机能力的特性 表示可以分配于打印机特性的打印机选项 打印机字体描述,说明了和硬件驻留与Cartridge字体相联系的特性。 描述打印机特性及打印机配置的相互依赖关系的条件语句 GPD语言同样定义了GPD文件的条目,这些条目用于控制以下操作: 压缩光栅数据 处理色彩格式 用Unidrv过渡调色 处理可安装的特性及选项 描述打印机内存配置 这一介绍性部分也包括了对在小驱动程序中使用多个GPD文件和在小驱动程序中使用资源DLL的基本单位(Master Unit)的讨论。 4.2.1GPD文件条目 GPD文件由条目构成,并使用描述每一个光栅打印机的特定设备的特征的GPD语言,这一部分包括以下的主题,这们描述了GPD文件的条目: GPD文件条目格式 GPD值类型 行连续(Line Continuation) 4.2.1.1GPD文件条目格式 所有的GPD文件和条目符合下面的格式: *条目名:条目值{GPD文件条目(GPD_FileEntry),GPD文件条目,……} 条目名总是一个预定义的GPD解析器可识别出的关键字,其前置星号。 条目值必须是GPD值类型的一种。 每一个GPD_FileEntry是另外一个GPD文件的条目,符合上面显示的格式。每一个条目的子条目必须对指定的包含该它自身的条目是有效。 一些条目名称不接受包含花括号或封闭的子条目。 每一个GPD条目由行尾(end-of-line)或右括号(})终止。 一个简单的GPD文件条目,它不接受子条目,是一个下面这样属性的条目: *MaxCopies:99 这一条目指定了打印机可以处理的最大的复制数量是99。 下面,是一个更复杂的例子,描述了一个可以两种方向(portrait或landscape,即横向或竖向)打印的打印机。例子中了指定了驱动程序必须发送以选择每个方向的命令。 *Feature:Orientation { *Name:”Orientation” *DefaultOption:Portrait *Option:Portrait { *Name:”Portrait” *Command:CmdSelect { *Order:DOC_SETUP.7 *Cmd:”<1B>&100” } } *Option:Landscape_CC90 { *Name:”Landscape” *Command:CmdSelect { *Order:DOC_SETUP.7 *Cmd:”<1B>&110” } } } 4.2.1.2GPD值类型 所有的GPD文件条目包含一个值的规范说明,GPD语言定义了下列值类型: Symbolic names(符号名称) Text strings(文本字符串) Constants(常量) Numeric values(数字值) Boolean values(布尔值) Lists(列表) Pairs(对值) Rectangles(矩形) Symbolic names 符号名称是一个用作标识符的字母串,它具有如下特征: 第一位字符必须是大写或小写的字母 在第一位字符之后的名称部分可以由大写或小写的字母、数字或下划线组成。 名称是大小写敏感的。 名称中的字母数不受限制。 GPD语言预定义了大量的符号名称,预定义的名称为打印机属性、命令名称、标准特性、标准选项以及标准变量等而存在。 可以为定制的特性、定制的选项及宏等定义符号名称。 Text Strings 文本字符串是文字字母的字符串,以引号作为分隔符,应用于Unidrv小驱动程序的字符串可以被置于以下两种位置的任一个: 可以被放置在资源文件中。需要本地化的字符串,如用户接口文本,就应放置当在资源文件中,在小驱动程序中使用资源DLL部分中已经描述过。 可以包含于一个GPD文件中。字符串代表了构成打印机命令的escape序列,这些通常都包含于GPD文件中,但这些字符串不需要被本地化。 字符串必须服从以下规则: 字符串必须以引号分隔开(“……”)。 十六进制字节值可以被置于一个尖括号中,括起来的十六进制数就是一个字符串,如,<03><1B>。在一套尖括号中,每两个数字被解释为一个十六进制的字节值,因此,<03><1B>,<03 1B>和<031B>都是等同的。 百分号(%)被用作escape字符,为了在字符串中包括引号或左尖括号(“<”),先于它们之前使用一个百分号。为了指定一个以百分号结束的字符串,必须指定百分号的十六进制值,如“<25>”。另外,为在一个文本字符中包括一个百分号并代表一个打印机命令,必须用另外一个百分号来处理它,为指定一个以百分号结束的打印机命令,必须指定两个十六进制的%值,如“命令字符串<25><25>” 一个字符串的实例是这样一个命令,它为佳能BJC-600打印机选择信纸大小的纸张,这一命令的字节序列是1B 28 67 03 00 6E 01 72,可以被指定如下: “<1B>(g<03 00>n<01>r” 每一个包含于字符串中的ASCII字符都被转换成为一个字节的十六进制等值。 在GPD文件中包含的字符串必须服从下列附加规则: 为扩展超过一个单行上的字符串,在处理第一行之后每行之前加上一个连续字母(+),并且在每一行上以引号分隔文本。 一个字符串值可以由多个文本字符串构成,例如,下列的两上GPD条目是等同的: *Name: “abc”“def”* % Comment + “gh”“ijk” *Name: “abcdefghijk” 对属于被定义于资源文件中的附加的字符串规则,可以参考平台的SDK文档中的STRINGTABLE语句声明的描述。 更多的关于指定打印机命令escape序列的信息,参考命令字符串格式部分的内容。 Constants 常量是由GPD语言预定义的名称值,常量名称通常由大写字母构成。如GPD常量可以是PAGE、SERIAL、TTY,它们是可以赋给*PrinterType属性的值。 Numeric Values 数字型值,所有的指定的条目值或GPD文件中的参数值的Numeric Values数字值都必须是整数。十进制小数是不允许的,除非在文本字符串中。 数字值被假定为正数,除非在其前有一个减号(“-”)。 数字值被假定为十进制数,除非在其前有0x的情况时,它们是非负的十六进制值。 如果可适用于一个特殊的GPD文件的设备环境,则星号(“*”)可以被用于指明或者是无穷大值,或者是忽略值, Boolean Values 布尔值被定义为等于预先定义的常量TRUE或FALSE。根据常规,所有打印机属性的字符名称都接受以问号标识结束的布尔值,如下例所示: *RotaterasterData?:FALSE Lists 列表值。用于分配属性的一系列值,利用LIST关键字,其格式为: LIST(值1,值2,值3,……,值n) 这里,值1,值2,值3,……,值n代表了一套一个或多个值,所有的类型都是为属性指定的。例如,打印机的色彩平面数据的顺序可以按如下指定的顺序来发送: *ColorPlaneOrder:LIST(YELLOW,MAGENTA,CYAN,BLACK) Pairs 分配一对值给属性,可利用PAIR关键字,其格式如下: PAIR(值1,值2) 当值1和值2是数字型值时,例如,光标的起始点位置可以被指定为基本单位,如:*CursorOrigin:PAIR(120,180) Rectangles 矩形值描述在GPD文件中的一个矩形,利用RECT关键字,其格式为: RECT(left,top,right,bottom),其中left,top,right,bottom都代表基本单位格式的数字值。 4.2.1.3行连续 GPD文件条目如果太长而不能在一行容纳下,则可以放在接下面的行里。为了连续一个条目,在第一行之后的每一行都必须以加号(“+”)打头,加号必须是每一行的第一个字母,不用预置空格,如下边例子所示: #DeviceFonts: + LIST( + =RC_FONT_courier_10pt_regular + =RC_FONT_CGTimes_regular + =RC_FONT_Univers_regular + =RC_FONT_Univers_condensed_regular + =RC_FONT_Antique_Olive_regular + =RC_FONT_Albertus_Medium + =RC_FONT_Albertus_Extra_Bold + =RC_FONT_Courier_regular + =RC_FONT_Letter_Gothic_regular + =RC_FONT_Letter_Gthic_regular + =RC_FONT_Wingdings 在下面行的开始,不需要使用行连接字母: 以星号开始的行。 以左括号开始的行。 4.2.1.4注解及忽略块 GPD文件可以包含注释,注释的格式如下: *%注解字符串 注解字符串可以是任何以行结束符终止的字符串。在一个多行注解的每一行注解里都必须以*%字开始,*%必须放置于空格或分行符之前。 下面是一个有效的注解的例子: *% This section of the GPD file *% Contains macro definitions *Macros:HP4L { *% These macros define command prefixes for the paper size feature LetterCmdPrefix:”<1B>&12a8c1E<1B>*p0x0Y” *% Prefix for letter option. A4CmdPrefix:” <1B>&126a8c1E<1B>*p0x0Y” *% Prefix for A4 option Env10CmdPrefix:” <1B>&181a8c1E<1B>*p0x0Y” *% Prefix for Env10 option. } 为请求GPD解析器忽略一组GPD条目,可以创建一个包含被忽略条目的被忽略块,忽略块的格式如下: *IgnoreBlock{IgnoredEntries} 被忽略的条目是一套GPD文件的条目,包含有相同数目左括号和右括号。 在下边的例子中,GPD解析器将忽略GPD条目描述Landscape_CC90的选项。 *Feature:Orientation { *Name:”Orientation” *DefaultOption:Portrait *Option:Portrait { *Name:”Portrait” *Command:CmdSelect { *Order:DOC_SETUP.7 *Cmd:”<1B>&100” } } *IgnoreBlock { *Option:Landscape_CC90 { *Name:”Landscape” *Command:CmdSelect { *Order:DOC_SETUP.7 *Cmd:”<1B>&110” } } } } 4.2.1.5宏 GPD语言支持两种类型的宏: 块宏(Block Macros),它允许指定一套GPD文件条目,并可以插入到GPD文件的任何条目位置。 值宏(Value Macros),它允许指定一套值,它可以被分别插入到GPD文件的任何位置。 块宏 块宏用于划定一套GPD的将重复插入到GPD文件中的一套GPD文件条目。可以包括任何条目类型到块宏定义中,如特性、选项语句、属性说明及对值宏的引用或者其他块宏。 下面的规则将应用于块宏中: 在一个GPD文件中的块宏定义必须位置于任何对它的引用之前。 在根层次(即不在括号里)的块宏定义可以通过GPD文件的定义来获得,并在GPD定义之后。否则,块宏的范围将是设置于左括号与右括号之间的定义部分。 块宏的定义可以包含另外块宏或值宏的定义。 块宏的定义可以引用其他前边定义的块宏及值宏,但是它不能引用它自身。 块宏不能接受参数。 如果括号包含于一个宏体中,它们必须成对的(即,必须有相同数量的左、右括号)。 如果用同一个名称创建两个块宏,则直到解析器遇到第二个定义之前第一个定义一直是有效的,这时第二个将替代第一个。如果第二个定义的范围结束,则第一个块宏又处于有效的任命状态。 块宏格式 在GPD文件中,用如下格式定义块宏: *BlockMacr:BlockMacroName{BlockMacroBody} BlockMacroName是一个唯一的名称,而BlockMacroBody则是一套一个或多个GPD文件的条目。如果BlockMacroBody包含括号,则相同数量的左括号及右括应当被包含其中。 例如,可以定义一个名为EnvelopeDefaults的块宏,并定义如下: *BlockMacro:EnvelopeDefaults { *PrintableArea:PAIR(4646,6738) *PrintableOrigin:PAIR(150,150) *RatateSize:TRUE } 引用块宏 引用块宏,可以用以下的格式: *InsertBlock:=BlockMacroName BlockMacroName是一个唯一名称,之前指定在*BlockMacro条目中定义该宏。 例如,为了在一个选项说明中引用EnvelopeDefaults宏,可以使用以下的条目: *Option:Env9 { *InsertBlock:=EnvelopeDefaults } 值宏 值宏用于指定可重复地分别插入到GPD文件中的一套一个或多个值。值可以是任何GPD值类型。 以下的规则适应于所有的值宏: 一个定义于GPD文件中的值宏必须放置在任何对它引用之前。 在根层次(即,不在括号内)的值宏定义可以通过GPD文件定义并获得,并且要在定义之后。否则,值宏的范围将是包含在左右括号之间的定义部分。 值宏必须解析一种GPD值类型。 如果所有的值都是文本字符串,值宏的定义就可以引用其他前边定义的值宏,但是一个值宏不能引用它自身。 值宏不接受参数。 如果创建了具有相同名称的两上值宏,则在GPD解析器遇见第二个定义之前,第一个值宏一直是有效的,并且第二个替换了第一个值宏。如果第二个值宏定义的区间结束,则第一个又重回到任命状态。 值宏的格式 为了在GPD文件中定义一个或多个值宏,可以使用以下格式: *Macros:ValueMacrogroupName{ValueMacroBody} 这里ValueMacroGroupName是一个唯一名称,ValueMacroBody是一套与值相关的单一的值名称,如下: ValueMacroName:MacroValue 这里,ValueMacroName是一个唯一的宏名称,MacroValue代表了一个GPD值类型。(只要被解析的字符串代表了一种GPD值类型,MacroValue就可以包括对以前定义的值宏的引用。) 如下例,可以为一套常用的命令前缀定义值宏: *Macros:HP4L { LetterCmdPrefix:”<1B>&12a8c1E<1B>*p0x0y” A4CmdPrefix:” <1B>&126a8c1E<1B>*p0x0y” Env10CmdPrefix:” <1B>&181a8c1E<1B>*p0x0y” } 但要注意,ValueMacroGroupName(例中为HP4L)是可选的,并被当作注解来看待。 引用值宏 引用值宏,使用下边这种格式: =ValueMacroName 这里ValueMacroName是一个唯一名称,之前在定义宏的*Macros条目中被说明。 例如,为了在一个命令规范说明中引用一个HP4L宏,可以使用以下条目: *Command:CmdSelect { Cmd:=LetterCmdPrefix” <1B>*c0t5760x7680y” } 唯一一个可以通过联接宏引用与非宏值来分配值的时间,是当所有的宏定义及其他值表示文本或命令子串时,如在例中所示的。在其他情况下,宏引用必须表示整个被分配的值。 4.2.1.6预处理器定向 GPD文件可以包含预处理器定向,它可以被用于控制对GPD文件中不同部分的条件解析,下表描述了可用于GPD文件中的预处理器定向。 预处理器定向 定义 *Define:symbolName 定义一个符号  *Undefine:symbolName 取消以前定义的一个符号  Ifdef:symbolName 意指一个GPD文件条目块的开始,如果专用的符号被定义,则在该定向与下一定向*Ifdef,*Elseifdef,*Else,或*Endif定向之间的GPD文件条目,将会由GPD处理器进行处理  Elseifdef:SymbolName 如果指定的符号被定义,并且该符号指定的由之前的*Ifdef或*Elseifdef定向没有被定义,在这一定向及下一*Ifdef,*Elseifdef,*Else,或*Endif定向间的GPD文件条目将会由GPD解析器来处理  *Else 如果由以前的*Ifdef或者*Elseifdef定向指定的符号没有被定义,在这一定向与下边的*Ifdef或*Endif定向间的GPD文件条目将会由GPD解析器来处理。  *Endif 意指GPD文件条目块的结束  *Include:”FileName” 指定一个附加GPD文件的名称,参考在小驱动程中使用多个GPD文件部分的内容  *SetPPPrefix:PrefixString 改变为预处理器定向准备的前置字符串,参考改变预处理器前缀部分的内容   条件预处理器定向可以被嵌套,在每一个嵌套层,使用条件预处理器定向的顺序如下: *Ifdef:symobl1 GPD file setcion *Elseifdef:Symbol2 GPD file seciton *elseifdef:Symbol3 GPD file section …… *Else: GPD file section *Endif: 对每一个应用的*Ifdef定向,*Endif是必需的,*Elseifdef及*Else定向是可选的。每一个GPD的文件部分(file seciton)可以包含GPD文件条目以及可选择的,一个条件预处理器定向的嵌套序列。 用*Define定义的所有的符号明确地保留其定义,直到用*Undefine取消其定义之后。 *Include定义允许指定一个附加的GPD文件的名称,更多的信息,可以参考在小驱动程序中使用多个GPD文件部分的内容。 注意*IgnorBlock的GPD条目不影响预处理器定向,因为预处理器在GPD解析之前执行。 改变预处理器定向前缀 *SetPPPrefix定向允许改变用于预处理器定向的前缀。也就是,可以用这一定向来将星号所代替的预处理器定向替换为另外一个字母或字符串。 例如,如果GPD文件包含下列定向: *SetPPPrefix:#SpecialPrefix# 当预处理器停止查找以*号开始的预处理器定向并开始寻找以#SpecialPrefix#开始的定向时。接着的序列将临时改变预处理器前缀为#SpecialPrefix#,并将它再恢复为*。 *SetPPPrefix:#SpecialPrefix# #SpecialPrefix#Ifdef:WINNT_50 #SpecialPrefix#Include:”ExtraGPD.gpd” #SpecialPrefix#Endif: #SpecialPrefix#SetPPPrefix:* 这一特性最初的目的是允许为以后版本的操作系统写的GPD文件可以与Windows 2000兼容。例如,支持未来操作系统版本的GPD文件可以包括GPD与以星号打头的预处理器定向冲突的但是windows 2000支持的文件条目。通过改变前缀,一个为未来操作系统版本写的GPD文件可以在Windows 2000中使用,如下面的实例: #Ifdef:WINNT_70 *SetPPPrefix:#SpecialPrefix# *%Do special,OS-Specific Proessing of *%GPD file entries that might confilict with *%asterisk-prefixed preprocessor directives #Endif 但是要注意,这一技术只改变预处理器查找的前缀,由解析器识别的关键字必须总是前置星号*。 预定义的预处理器符号 微软定义了以下预处理器符号 符号 何处被定义 定义 WINNT_50 Windows 2000的GPD预处理器 环境是Windows 2000  WINNT_40 Windows NT 4.0及2000的预处理器 环境是Windows NT 4.0  PARSER_VER_1.0 Windows NT 4.0及2000的预处理器 GPD解析器(Version 1)  WINNT_40和WINNT_50符号对创建与Windows NT 4.0及Windows 2000都兼容的GPD文件时是非常有用的。如果,例如Windows 2000支持一个打印机的能力但是Windows NT 4.0不支持,这样一能力就可以指定于一个GPD文件中并由*Ifdef:WINNT_50和*Endif定向限定。 4.2.1.7标准变量 GPD语言定义了一套标准变量,它们可以在命令字符串中被引用,利用命令字符串格式,Unidrv驱动程序对这些变量赋值。从GPD文件的观点来说,这些变量是只读的。 所有的标准变量以一个DWORD整型类型存储。 下面的打印机命令条目指定了当一个光栅数据块准备好时发送给一个HP LaserJet 4P打印机的命令字符串: Command:CmdSendBlockData:”<1B>*b”%d{NumOfDataBytes}”W” 下表包含所有的标准变量,以字母顺序排列。 标准变量 名称 值 注解 BlueValue 当前色彩的蓝色组件 在CmdDefinePaleterEntry命令字符串中使用时是有效的(也可参考GreenValue,RedValue)  CurrentFontID 当前下载的软字体的标识代码 如果当前打印作业包含下载的软字体就是有效的  CurrentPaletterIndex 目前色彩模板的索引 在CmdSelectPaletteEntry命令字符串中使用是有效的(也可参考GreenValue,RedValue  CusorOriginX 基本单位中坐标源点的X坐标 无论何时打印机作业正在处理时都有效  CusorOriginY 基本单位中从标源点的Y坐标 无论何时打印机作业正在处理时都有效  DestX 基本单位中坐标目标点的X坐标 在CmdXMoveAbsolute字符串命令中使用时是有效的  DestXRel 档准单位中相对于当前坐标的目标点的X坐标 在CmdXMoveRelLeft及CmdXMoveRelRight命令字符串中使用时是有效的  DestY 基本单位中坐标目标点的Y坐标 在CmdYMoveAbsolute命令字符串中使用时是有效的  DestYRel 档准单位中相对于当前坐标的目标点的Y坐标 在CmdYMoveRelUp和CmdYMoveRelDown命令字符串中使用时都时有效的  FontBold 如果当前字体是bold则设为1,否则都设为0 一种字体被指定后是有效的  FontHeight 在基本单位中,当前字体的高度 一种字体被指定后是有效的  FontItalic 如果当前字体是italic则设为1,否则都设为0 一种字体被指定后是有效的  FontStrikeThru 如果当前字体允许中划线则设为1,否则都设0 一种字体被指定后是有效的  FontUnderLine 如果当前字体有下划线则设为1,否则都设为0 一种字体被指定后是有效的  FontWidth 在基本单位中,当前字体的宽度 一种字体被指定后是有效的  GraphicsXRes 当前图形的水平精度(DPI) 无论何时打印机作业正在处理时都有效  GraphicsYRes 当前图形的垂直精度(DPI) 无论何时打印机作业正在处理时都有效  GrayPercentage 进行灰色填充时的级别(百分比) 使用CmdRectGrayFill命令字符串时是有效  GreenValue 当前色彩的绿色组件 在使用CmdDefinePaletterEntry命令字符串时是有效的(也可参考BlueValue和RedValue)  LinefeedSpacing 垂直空间的总量,基本单位中代表一个回行 在使用CmdSetLineSpacing命令字符串时是有效的  NextFontID 下一个被下载的软字体的标识码 使用CmdSetFontID命令字符串时是有效的  NextGlyph 要下载的下一符号的两字节代码 在使用CmdSetCharCode命令字符串时是有效的  NumOfCopies 用户请求的打印份数 无论何时打印机作业正在处理时都有效  NumOfDataByes 准备传送的光栅数据的字节数 在使用CmdSendXXXData命令字符串时是有效的,如果数据被压缩,该值是被压缩后的值  PaletterIndexToProgram 程序的下一个入口点的调色板索引 在CmdDefinePaletterEntry命令字符串中使用时是有效的(也可参考RedValue,GreeValue,BlueValue, CurrentPaletterIndex)  PatternBrushID 一个下载的调色板画刷的标识码 与CmdDownloadPattern和CmdSelectPattern命令字符串一起使用时是有效的  PatternBrushsize 当前调色板画刷的字节大小 与CmdDownloadPattern命令字符串一起使用时是有效的  PatternBrushType 当前调色板画刷的类型,其值可为: 2:Shading pattern 3:Cross-hatch pattern 4:User-defined pattern 与CmdDownloadPattern与CmdSelectPattern命令字符串一起使用时是有效的  PhysPaperLenth y轴基本单位中,当前打印用纸的竖向模式的长度 无论何时打印机作业正在处理时都有效  PhysPaperWidth 基本单位中,当前打印用纸竖向模式的宽度 无论何时打印机作业正在处理时都有效  RasterDataHeightInPixels 当前数据代表的图像的像素高度 在任何CmdSendXXXData命令字符串及在CmdSetSrcBmpHeight命令字符串中时都是有效的,压缩不修改该值  RasterDataWidthInBytes 一个扫描行中包含的字节数量 在任何CmdSendXXXData命令字符串及在CmdSetSrcBmpWidth命令字符串中都是有效的,压缩不修改该值  RectXSize 矩形宽度,以x轴基本单位 在CmdSetRectWidth命令字符串中使用时是有效的  RectYSize 矩形高度,以y轴基本单位 在CmdSetRectHeight命令字符串中使用时是有效的  RedValue 当前色彩的红色组件 在CmdDefinePaletterEntry命令字符串中使用时是有效的(也可参考GreenValue,BlueValue)  TextXRes 文本的当前水平精度(DPI) 无论何时打印机作业正在处理时都有效  TextYRes 文本的当前垂直精度(DPI) 无论何时打印机作业正在处理时都有效   4.2.2基本单位 许多打印机支持具有可变化的水平及垂直精度的命令,例如,一个特殊打印机的Immediate Line Feed命令提供一个1/288th每英寸的精度,而同样的打印机也可以支持垂直图像的1/96th每英寸精度。类似的,这一打印机可以提供水平的1/80th,1/160th和1/320th 每英寸的精度。 Unidrv提供一个单一的坐标系统来处理这些可变的精度,这一坐标系统中的单位就称为基本单位(master units)。一个打印机的单位可以表示为(x,y)这样一对值,这时x是一个水平平面的基本单位,而y是垂直平面的基本单位。 为了确定一个平面的基本单位,必须计算实际精度的分母的最小公倍数,利用示例的打印机,可以按如下这样去做: 计算80,160,320的最小公倍数,结果是320。这样,水平的基本单位就是1/320th每英寸。 计算288和96的最小公倍数,结果是576。这样,垂直的基本单位就是1/576th每英寸。 为了指定一个打印机的基本单位,需要使用*MasterUnits属性。该属性格式如下: *MasterUnits:PAIR(X_Denominator,Y_Denominator) 这里,X_Denominator是水平精度分母的最小公倍数,Y-Denominator是垂直精度分母的最小公倍数,下面的GPD条目指定了例子中的基本单位。 *MasterUnits:PAIR(320,576) 一般地,在GPD文件条目中使用的位置及大小值必须以基本单位指定,例如,为指定最大的可定制页面的尺寸为9*12英寸,下边的条目应当被用到,这里9*320=2880,12*576=6912: *MaxSize:PAIR(2880,6912) 在计算基本单位时,只使用Unidrv支持的设备的精度。例如,如果打印机支持水平精度1/80th,1/96th,1/160th,1/320th每英寸,但是不打算在GPD文件中指定1/96th每英寸的精度,则在最小公倍数LCM计算中,不要包含该精度即可。 如果打印机在移动光标位置时支持光标命令(cursor commands),则该值可以在*XmoveUnit及*YmoveUnit两个光标属性中指定并被包含于基本单位计算中。假设,如一个GPD文件包含下面的条目: *XMoveUnit:60 *YmoveUnit:60 在计算这一打印机的基本单位时,1/60th每英寸则必须被包含于水平及垂直的基本单位计算中。 4.2.3在小驱动程序中使用多个GPD文件 Unidrv小驱动程序可以由多于一个的GPD文件组成,这就允许置换对一些打印机的一处或多处对GPD文件来说通用的特征,并将这些通用的GPD文件包含于打印机的一个特殊GPD文件中。 为包含附加的GPD文件,就必须用*Include定向,它在预处理定向中描述。可以使用多个*Include定向,如下面的例子所示: *Include:”Common1.gpd” *Include:”common2.gpd” *Include:”common3.gpd” *Include定向的文件名参数不能是一个宏引用,也不能包含一个路径说明。 每一个被包含的文件都必须以GPD文件条目结束,并且该文件必须包含相等数目的左括号和右括号,如果一个GPD文件条目重复,则最近被解析的条目将代替前一个条目,而没有重复的条目将被增加到Unidrv的数据库中。 4.2.4在小驱动程序中使用资源DLL 通常,打印驱动程序需要这样一些外部存储的资源,如字体、图标及其他位图、可本地化的用户接口文本字符串等。这些项目的描述被放置于资源的DLL中,在平台的SDK文档有描述。 在Unidrv小驱动程序中使用资源DLL,必须以下面的任一种方式对资源进行鉴别: 如果使用多于一个的资源DLL,可以用RESDLL特性来鉴别它们,使用RESDLL特性使用的实例如下: *Feature:RESDLL { *Option:FirstRes {*Name:”MyFirstRes.dll”} *Option:SecondRes {*Name:”MySecondRes.dll”} *Option:ThirdRes {*Name:”MyThirdRes.dll”} } 如果要引用包含于这些资源DLL中的一种资源,使用下面的格式: RESDLL.ResourceOptionName.ResourceID 下面是一个例子,它的OptionName是从上边的特性条目例子中来的: *rcNameID:RESDLL.SecondRes.288 这一例子,引用了包含于MySecondRes.dll上的一个资源标识符为288的一个字符串资源。 如果只使用一个资源DLL,可以通过给*ResourceDLL特性赋值来使用它。 为了引用包含于这一DLL中的资源,可以简单地指定适当的资源标识符,如下例子所示: *rcNameID:288 所有的与小驱动程序使用的资源DLL必须在一个打印机的INF文件中被指定,参考安装一个Unidrv小驱动程序部分的内容。 在一个GPD文件中,在给任何以*rc开始(如*rcIconID,*rcPromptMsgID和*rcCartridgeNameID)的条目赋值时都必须使用资源标识符。 另外,如果打印机包含硬件驻留的字体,必须以.ufm或.ifi的表格文件形式给这些字体提供打印机字体描述,并且必须在一个资源DLL中标识这些文件,并分别使用RC_UFM和RC_FONT资源类型。 微软提供了一个资源DLL,即unires.dll,它为标准特性及标准选项包含字符串资源。微软提供的GPD文件,stdnames.gpd,给每一个资源标识符都分配了一个宏符号名称,这就允许通过它们的宏名称引用这些资源,如例所示:*rcNameID:=LETTERSMALL_DISPLAY。 4.3打印机属性 打印机属性是GPD文件条目(GPD file entries)代表的打印机的特征。要设置一个特性值,可以在GPD文件中包含特性名称及想要设定的值。 以下主题描述了怎样在GPD文件中指定打印机的特性: ■4.3.1打印机特性格式 ■4.3.2特性类型 ■4.3.3一般特性 4.3.1打印机属性格式 为在GPD文件条目中指定一个打印机的属性,使用下面的格式: *AttributeName:AttributeValue 这里AttributeName是一个预先定义的属于属性类型(Attribute Types)中的一个名称,AttributeValue是GPD值类型(Value types)的一种。 例如,*ModelName属性用于指定一个描述打印机硬件的文本字符串,为了给该属性赋值,可以将下面一行放入GPD文件中: *ModelName:”Canon Buble-Jet BJC-600” 所有的属性名称都是由Unidrv的GPD解析器来预先定义并识别的。 4.3.2属性类型 一共定义了好几种类型属性,包括: 一般属性:与打印机设备相关 命令属性:与打印机命令相关 特性属性:与专用的打印机特性相关 选项属性:与属于特定特性的可选项相关 4.3.3一般属性 一般属性代表了由GPD语言定义的属性类型中的一种。一般属性与特殊的特性属性或可选属性都不相关。一般属性可以分为以下几种: 根层次属性 一般打印属性 文本打印属性 光栅打印属性 向量打印属性 经常情况下,将GPD文件中的一般属性放置于根层次(即,不置于括号内),根层次属性必须总是置于根层次。 偶然情况下,一般属性(除过根层属性)的值依敕于配置的参数。这时,属性条目可以被一个*Option语句替换,或者用*Case语句替换(置于根层次或包含于一个*Option语句)。如果属性不是根层次的(因为它包含于一个*Option语句或者它在于个非根层次的*Case语句),这一属性名称必须以EXTERN_GLOBAL符号为前缀,如下: EXTERN_GLOBAL:*AttributeName:AttributeValue 更多的关于指定相依关系的配置信息,可参考条件语句部分。 4.3.3.1根层次属性 根层次属性是描述特定驱动程序的一些特处,如资源文件名称、帮助文件或另外的包含于GPD文件,连同驱动程序的基本单位、版本号、字符代码页的一般特性。 附加的根层次特性指定这些特定设备的特征,如打印机的名称、类型、最大可复印能力及字体的Cartridge槽数。 这些属性被称为根层次属性,由于它们必须总是置于一个GPD文件的根层次(即不含于括号内)。 下表列出根目录的属性: 属性名称 属性参数 注解 *Codepage 数值型Windows的代码页标识符,在平台SDK文档中有描述 可选。如果没有被指定就使用Unicode码,这代码页用于所有的被显示的字符串  *FontCartSlots 表示打印机能提供的字体Cartridge槽的数值型数值 可选,如果没有被指定,默认值为0  *GPDFileName 加引号的文本字符串,表示了GPD文件名(没有路径) 可选  *GPDFileVersion 加引号的文本字符串,表示当前GPD文件的版本。推荐的格式是MajorVersion.MinorVersion,即主版本号.小版本号,如“1.0” 可选。如果没有被指定,这一字符串显示于Unidrv的About对话框中。  *GPDSpecVersion 加引号的文本字符串,表示当前GPD说明的版本,所需的格式是MajorVersion.MinorVersion,即主版本号.小版本号,如“1.0” 必需的。且必须是GPD文件中的第一个条目,在任何注解之前。对Windows 2000,这一值必须的“1.0”  *HelpFile 加引号的字符串,包含一个定制的帮助文件的名称,具有.hlp的扩展名 可选。如果包含,它可以在帮助文件中增加主题或覆盖已有的主题。帮助文件的索引由*HelpIndex的特征及选项来指定  *Include 加引号的字符串,包含附加GPD文件的名称 已废弃。这一条目已经被重定义为处理器重定向  *InstalledOptionName 被显示的加引号的字符串,用于指明一个可以安装的特征或已经安装的选项。通常,这一字符串是“Installed”,但是也可以指定为任何合适的字符串 对任何特征或选项来说,如果*Installable?是TRUE时,且*rcInstalled OptionNameID没有被指定时,是必需的(参考特性属性部分)  *MasterUnits 一个对值(PAIR),表示打印机的基本单位 必需的。为了减少潜在的四舍五入法造成的错误,使用在*MasterUnits中指定的相同的精度值及字体规格(参考Unidrv字体规格文件)  *MaxCopies 数字型值,表示打印机可以支持的最大的打印拷贝的份数 可选。如果没有被指定,其默认值是1  *ModeName 加引号的字符串,表示打印机模式名称 如果*rcModeNameID没有被指定时是必需的,字符串必须与setup.inf中的名称相一致  *NotInstalledOptionName 被显示的加引号的字符串,用于指明一个可以安装的特性或没有安装的选项。通常,这一字符串是“Not installed”,但是也可以指定为任何合适的字符串 如果对任何特征或选项*Installable?为TRUE时(参考特性属性部分),并且*rcNotInstalledOptionNameID没有被指定时是必需的。  *Personality 加引号的字符串,表示打印机应用的打印机语言 可选。如果被指定,字符串由目录服务(Directory Services)来显示,也可以参考*rcPersonalityID  *PrinterType PAGE,SERIAL或TTY 必需的  *PrinterRate 数字型值,表示单色打印率,单位可以由*PrintRateUnit来指定 可选。如果没有被指定,默认值为0。  *PrintRatePPM 数字型值,表示打印的速度,即每分钟的页数(pages per minute) 可选。如果没有被指定,默认值为0。  *PrintRateUnit PPM-Pages/min CPS-Characters/sec LPM—Lines/min IPM-Inches/min (IPM用于扫描仪) 如果*PrinRate被指定时是必需的。指定的单位应当与打印机类型相匹配。例如,PPM应当为打印机打印页面时指定  *rcInstalledOptionNameID 一个字符串资源的ID,用于显示可安装特性或被安装的选项。通常,这一字符串是“Installed”,但是也可以指定任何合适的字符串 如果对任何特征或选项*Installable?都为TRUE(参考特性属性部分),且*InstalledOptionName没有被指定。  *rcNotInstalledOptionNameID 一个字符串资源的ID,用于显示可安装特性或被安装的选项。通常,这一字符串是“Not Installed”,但是也可以指定任何合适的字符串 如果对任何特征或选项*Installable?都为TRUE(参考特性属性部分),且*NotInstalledOptionName没有被指定。  *rcPersonalityID 一个字符串资源的资源ID,表示打印机应用的打印机语言 可选。如果被指定,该字符串由目录服务来显示。(参见*Personality)  *rcPrinterIconID 一个RC_ICON资源的资源ID,表示与打印机相关的图标 可选。如果没有被指定,一个默认的打印机图标将被显示,推荐所有的RC_ICON资源ID应当从1开始连续计数  *ResourceDLL 加引号的字符串,包含名称的资源DLL,但没有路径信息 可选。参考在小驱动程序中使用资源DLL。  有关实例,可以参考GPD文件示例。 4.3.3.2一般打印机属性 一般打印机属性是用于指定与打印类型相关的属征,这些属性被分为以下组: 打印机能力属性 色彩属性 光标属性 打印机能力属性 打印机能力属性是指定打印机属征如页边距、旋转及文本打印能力等影响所有的打印纸张尺寸和方向的一般打印属性。 属性名称 属性参数 注解 *MemoryUsage 枚举打印机内存中存储的数据类型变量,可是FONT、RASTER VECTOR中的一种或多种,如果列出的数据类型打印机不支持,它将被忽略。 可选。如果没有被指定,其默认值是LIST(FONT,RASTER,VECTOR)。更多的信息,可以参考描述打印机内存配置部分  *OEMCustomData 加引号的文本字符串,当它调用IPrintOemDriverUni::DrvGetGPDData提供一个绘制的插件 如果一个绘制插件调用IPrintOemDriverUni:: DrvGetGPDData函数时是必需的  *OutputOrderReversed? TRUE或FALSE,指明是否多页文档是否从最后到第一页被排序 可选。如果没有被指定,默认值为FALSE。EXTERN_GLOBAL符号也不能与*OutputOrderReversed?一起使用。  *ReselectFont 枚举常量,意指在Unidrv必需重新选择当前字体。可以是下面的一个:AFTER_GRXDATA-在任何CmdSendxxxxData光栅数据删除命令之后AFTER_XMOVE-在任意x轴光标移动命令之后AFTER_FF-在CmdFF命令之后 可选。如果没有被指定,Unidrv不重新选择字体  *RotateCoordinate? TRUE或FALSE,意指打印机是否支持旋转坐标系统以匹配页面方向的命令 可选。如果没有被指定,默认值是FALSE。如果是TRUE,方向特性的*Option条目必须指定打印机命令。但在*Case条目中不能被替换  *RotateFont? TRUE或FALSE,意指打印机是否自动旋转字体以匹配页面方向 可选。如果没有被指定,默认值为FALSE。如果是TRUE,则*RotateCooradinate?也必需是TRUE。不能在*Case条目中被置换。  *RotateRaster? TRUE或FALSE,意指打印机是否自动旋转光栅数据以匹配页面方向 可选。如果没有被指定,默认值为FALSE。如果为TRUE,则*RotateCoordinate?也必须是TRUE。不能在*Case条目中被置换  *TextCapes 枚举指明打印机的文本能力的常量。可以由一个或多个TC_XXX标记构成,在平台的SDK文档的GetDeviceCaps中有描述 可选。如果没有被指定,Unidrv 则假定为不支持文本能力  有关实例,可以参考GPD文件示例。 色彩属性 色彩属性是指定控制色彩打印特征的一般打印属性。 下表列出了色彩特性。 属性名称 属性参数 注解 *ChangeColorModeOnDoc? TRUE或FALSE,意指在文档的不同页面中一个打印机的色彩模式是否可以被改变,并且不会产生负面作用 可选。如果没有被指定,默认值是TRUE,Unidrv用这一值来优化打印速度  *CyanInMagentaDye 数字值,从0到1000,意指青色染紫色的百分比,该值是青色的百分比乘以100。例如,8.4%的青色指定为840,而10%的值就为1000。 可选。如果没有被指定,一个Unidrv提供的默认值就被使用  *CyanInYelowDye 数字值,从0到1000,意指青色染黄色的百分比,该值是青色的百分比乘以100。例如,8.4%的黄色指定为840,而10%的值就为1000。 可选。如果没有被指定,一个Unidrv提供的默认值就被使用  *EnableGDIcolorMapping TRUE或FALSE,意指GDI是否应当执行从显示器到打印机色彩空间的色彩映射 可选。如果没有被指定,默认值是FALSE。如果是TRUE,Unidrv则在GDIINFO结构中设置HT_FLAG_DO_DEVCLR_XFORM标志  *MagentaInCyanDye 数字值,从0到1000,意指紫色染青色的百分比,该值是紫色的百分比乘以100。例如,8.4%的紫色指定为840,而10%的值就为1000。 可选。如果没有被指定,一个Unidrv提供的默认值就被使用  *MagentaInYellowDye 数字值,从0到1000,意指紫色染黄色的百分比,该值是紫色的百分比乘以100。例如,8.4%的紫色指定为840,而10%的值就为1000。 可选。如果没有被指定,一个Unidrv提供的默认值就被使用  *YellowInCyanDye 数字值,从0到1000,意指黄色染青色的百分比,该值是黄色的百分比乘以100。例如,8.4%的黄色指定为840,而10%的值就为1000。 可选。如果没有被指定,一个Unidrv提供的默认值就被使用  *YellowInMagentaDye 数字值,从0到1000,意指黄色染紫色的百分比,该值是黄色的百分比乘以100。例如,8.4%的黄色指定为840,而10%的值就为1000。 可选。如果没有被指定,一个Unidrv提供的默认值就被使用   有关实例,可以参考GPD文件示例。 光标属性 光标属性是指定一个打印机光标的特征的一般属性。 下表列出了所有的光标属性。 属性名称 属性参数 注解 *BadCursorMoveInGrxMode 列出在光栅图形模式中代表不合法的光标移动的值。其值可以是下边值中的一个或多个:X_PORTRAIT,X_LANDSCAPE,Y_PORTRAIT,Y_LANDSCAPE 可选。如果没有被指定,默认值没有限制。如,LIST(X_PORTRAIT)意指X轴方向的运动对Portrait方向是不允许的。  *CursorXAfterCR AT_PRINTABLE_X_ORIGIN或AT_CORSOR_X_ORIGIN的一种,意指在回车之后光标的x轴位置 可选。如果没有被指定,默认值是AT_CURSOR_X_ORIGIN,它是物理零点的位置  *EjectpageWithFF? TRUE或FALSE。意指打印机是否用表格送纸(form-feed)式进纸。 可选。如果没有被指定,默认值是FALSE。  *MaxLineSpacing 数字型值,表示在y轴的基本单位中的最大空间距 可选。如果没有被指定,Unidrv假定没有最大值  *UseSpaceForXMove TRUE或FALSE。意指执行x轴方向运动时能否用空格字符 可选。如果没有被指定。默认值是TRUE。如果是TRUE,Unidrv用空格进行粗略移动而用NULL来进行精细移动。如果是FALSE,Unidrv用NULLs来进行所有的移动。  *XmoveThreshold 数字型值,在x轴的基本单位中表示用CmdXMoveAbsolute时的阀值而不是CmdXMoveReLeft或CmdXMoveRelRight的阀值。 可选。如果没有被指定,默认值是零,意指CmdXMoveAbsolute应当总是被应用。只有所有三个x方向移动命令被指定才是可用的。  *XmoveUnit 数字型值,即每英寸的点数,表示打印机能够进行的最小的水平移动。例如,如果运行单位是1/600th每英寸,则指定值即为600。 如果打印机支持水平光标运动命令时是必需的。(如果被指定,当计算基本单位时将包括该值)  *YmoveAttributes LIST意味y轴运动的属性值,可以是下边的一个或多个:FAV_LF(favor LF spacing),SEND_CR_FIRST 可选。如果没有被指定,则不假定任何属性。  *YMoveThreshold 数字型值,在y轴基本单位上,表示用CmdXMoveAbsolute时的阀值而不是CmdXMoveReLeft或CmdXMoveRelRight的阀值。 可选。如果没有被指定,默认值是零,意指CmdYMoveAbsolute应当总是被应用。只有所有三个x方向移动命令被指定才是可用的。  *YMOveUnit 数字型值,即每英寸的点数,表示打印机能够进行的最小的垂直移动。例如,如果运行单位是1/600th每英寸,则指定值即为600。 如果打印机支持垂直光标运动命令时是必需的。(如果被指定,当计算基本单位时将包括该值)  相关实例,可以参考实例GPD文件。 4.3.3.3文本打印属性 文本打印属性是指定一个打印机的文本打印能力的一般属性。这些属性可以分为以下几组: 设备字体属性 下载字体的属性 仿真字体属性 设备字体属性 下表列出了描述打印机支持的设备字体的属性: 属性名称 属性参数 注解 *CharPosition UPPERLEFT或BASELINE。意指在打印字符之前的限定于特定区域的字符在打印头的位置 可选。如果没有被指定,则默认值是UPPERLEFT。  *DefaultCTT 数字型值,表示默认字符翻译表的RC_CTT资源标识符 可选。如果没有被指定,将没有翻译表。(这一属性只在对向后兼容的GPC文件时提供)  *DefaultFont 数字型值,表示默认字体的资源标识符RC_FONT或RC_UFM。 如果打印机支持设备字体时必需。  *LookAheadRegion 数字型值,表示在基本的Y轴单位时预准备的区域大小。 可选。如果没有被指定,默认值是零。只与串行打印机时使用(如HP的DeskJeT),以绘制文本和位图数据  *MaxFontUsePerpage 数字型值,表示打印机每一页可用的最大字体号 可选。如果没有被指定,就没有限制  *TextYOffset 数字型值,代表在y轴基本单位中的垂直距离,根据它驻留的字体必需重新根据位图字体的基准线重排位置。 可选。如果没有指定,默认值是零(用于一些点阵打印机)。  相关实例,可以参考GPD实例文件。 下载字体的属性 下表中列出属性描述了打印机对下载字体的支持。 属性名 属性参数 注解 *DLSymbolSet 常量(PC-8,ROMAN-8等)表示下载TrueType字体时设置的符号 可选。如果没有被指定,符号区间假定为由*MinGlyphID和*MaxGlyphID间指定的连续区间,可无限制的使用。  *FontFormat 常量值,意指支持的下载字体的类型。必需是HPPCL,HPPCL_RES,HPPCL_OUTLINE,OEM_CALLBACK中的一种 打印机能够下载字体时是必需的。如果OEM_CALLBACK被指定,字体的回调函数必需提供。更多的信息,可以参考定制微软打印机驱动程序部分的这些回调函数  *MaxFontID 数字型值,表示软字体的最大标识符数 可选。如果没有被指定,默认值是65535  *MaxGlyphID 数字型值,表示对被下载的字体符号的最大标识符数 可选。如果没有被指定,并且*DLSymbolSet也没有被指定,默认值是255;如果*DLSymbolSet被指定,则将被忽略。  *MaxNumDownFonts 数字型值,表示在同一时间可以存储于打印机内存中的软字体的最大数量 可选。如果没有被指定,Unidrv假定无限制的软件字体可以被存储。  *MinFontID 数字型值,表示软件字体的最小标识符 可选。如果没有被指定,默认值是1  *MinGlyphID 数字型值,表示对被下载的字体符号的最小标识符数 如果*DLSymbolSet没有被指定,默认值是32,如果*DLSymbolSet被指定,则将被忽略。   相关实例,可以参考GPD实例文件。 仿真字体属性 下表列中列出的属性描述了打印机对仿真字体的支持。 属性名 属性参数 注解 *DiffFontsperByteMode? TRUE或FALSE。对支持单字节及双字节模式的打印机来说,这意味着打印机是否对每一种模式保持一种独立的字体及字体特征。 可选。如果没有被指定,默认值是FALSE。  相关实例,可以参考GPD实例文件。 4.3.3.4光栅打印属性 光栅打印属性是指定打印机的文本打印能力的一般属性,这些属性可分成以下几组: 光栅数据压缩属性 光栅数据发送属性 光栅数据压缩属性 没有与光栅数据压缩属性相关的属性。 更多的关于光栅数据压缩的命令,可以参考光栅数据压缩命令部分。 光栅数据发送属性 下列列出的属性描述了打印机支持的光栅数据发送属性。 属性名 属性参数 注解 *CursorXAfterSendBlockData 常量值,意指一块光栅数据发送之后的光标的x轴位置,可以是AT_RXDATA_END,AT_GRXDATA_ORIGIN,AT_CURSOR_X_ORIGIN中的一种,意思分别是在图形块的开始的像素,最后一块图形块的像素及光标原点 可选。如果没有被指定,默认值是AT_GRAXDATA_END  *CursorYAfterSendBlockData 常量值,意指一块光栅数据发送之后的光标的y轴位置,可以是NO_MOVE,AUTO_INCREMENT中的一种 可选。如果没有被指定,默认值是NO_MOVE,意思是光标的y轴位置没有被改变  *MirrorRasterByte? TRUE或FALSE。意指Unidrv是否应当镜像(保留)图像的每一字节信息 可选。如果没有被指定,默认值是FALSE  *MoveToX0BeforeSetColor? TRUE或FALSE。意指是否光标的X轴坐标在一个明确的色彩选择命令被发送之前,必需被设置为零 可选。如果没有被设定,默认值是FALSE。只有在*UseExpcolorSelectCmd?也是TRUE时其值也为TRUE。  *OptimizeLeftBound? TRUE或FALSE。意指是否Unidrv应当删除每一个区带的左边界限的空格 可选。如果没有被指定,默认值是FALSE  *OutputDataFormat H_BYTE或V_BYTE,意指是否在数据类型中的字节位被映射到水平像素或垂直像素 可选。如果没有被指定,默认值是H_BYTE  *RasterSendAllData? TRUE或FALSE。意指Unidrv是否应当发送所有的光栅数据,包括扫描仪之间的空格或空行 可选。如果没有被指定,默认值是FALSE  *SendMultipleRows TRUE或FALSE。意指是否由CmdSendBlockData指定的命令可以在同一时间发送多个块   *StripBlanks LIST意指哪些在光栅数据块中的数据应当被删去,可以是LEADING,ENCLOSED,TRAILING中的一个或多个 可选。如果没有被指定,Unidrv将不去除任何空格,可以参考精度特性中的选项属性内容中的*MinStripBlankPiexels  *UseExpColorSelectCmd? TRUE或FALSE。意指打印机是否需要分离于色彩光栅数据的明确的色彩选择命令 可选。如果没有被指定,默认值是FALSE。点阵打印机需要值为TRUE  关于光栅数据发送相关的命令及信息,可以参考光栅数据发送命令部分的内容。 相关实例,可以参考GPD实例文件。 4.3.3.5向量打印属性 向量打印属性是指定打印机的向量打印属征的一般属性。这些属性由一个单独的组构成: 矩形区域填充属性 矩形区域填充属性 下表列出的属性描述了打印机对填充矩形区域的支持。 属性名 属性参数 注解 *CursorXAfterRectFill AT_RECT_X_ORIGIN或AT_RECT_X_END,指明在打印机填充了一个矩形区域后光标的x坐标在什么地方 可选。如果没有被指定,默认值是AT_RECT_X_ORINGIN  *CursorYAfterRectFill AT_RECT_Y_ORIGIN或AT_RECT_Y_END,指明在打印机填充了一个矩形区域后光标的y坐标在什么地方 可选。如果没有被指定,默认值是AT_RECT_Y_ORGIN  *MaxGrayFill 数字型值,表示作为cmdRectGrayFill命令的允许的最大的灰色填充百分比 可选。如果没有被指定,默认值是100  *MinGrayFill 数字型值,表示作为cmdRectGrayFill命令的允许的最小的灰色填充百分比 可选。如果没有被指定,默认值是1   4.4打印机命令 每一个打印机命令可以下面两种方式的任一种实现: 可以在GPD文件中置放一个特定设备命令字符串,Unidrv将在适当的时间发送该命令字符串到打印假脱机。 可以实现IPrintOemUni::CommandCallback COM方法,它动态地产生一个命令字符串。Undirv在它需要向假脱机发送命令时调用该方法。更多的信息可以参考第7章动态产生打印机命令中的定制微软打印机驱动程序部分的内容。 下面的主题描述了怎样在GPD文件中指定打印机命令: ■4.4.1命令条目格式 ■4.4.2命令名称 ■4.4.3命令属性 ■4.4.4命令字符串格式 ■4.4.5命令执行顺序 4.4.1命令条目格式 为了在一个GPD文件中指定打印机命令,可利用下面格式: *Command:CommandName{CommandAttributes} 这里CommandName是一个预定义的命令名,CommandAtributes是一套命令属性。 例如,一个GPD文件可以包含下面的CmdStartPage命令的说明,它初始化一个将要打印的页面: *Command:CmdStartPage { *Order:PAGE_SETUP.100 *Cmd:”<0D>” } 如果,对于一个特殊的CommandName值,只需要指定*Cmd属性,可以使用缩略式命令条目格式,如下: *Command:CommandName:CommnadString 这里,CommandString是一个文本字符串,表示一个打印机命令的escape序列,更多的关于指定escape序列的信息,可以参考命令字符串格式部分的内容。例如,一个GPD文件也许可以包含下面的CmdBoldOn命令的说明,它打开了对字符文本加粗(Bold)的功能: *Command:CmdBoldOn:”<1B>(s3B” 4.4.2命令名称 GPD语言对每一个普通打印机命令定义了一个唯一的命令名称,另外,它对指定用于选择打印机选项定义了CmdSelect命令名称。 下面列出的命令名称包括以下主题: ■4.4.2.1一般打印命令 ■4.4.2.2打印机配置命令 ■4.4.2.3选项选择命令 ■4.4.2.4文本打印命令 ■4.4.2.5光栅打印命令 ■4.4.2.6向量打印命令 4.4.2.1一般打印命令 一般打印命令是那些可与任意类型打印关联的命令,这些命令可以分为下面二组: 色彩命令 光标命令 色彩命令 下面的主题描述了色彩命令: 选择基础背景色彩命令 控制打印机调色板命令 选择调色板画刷命令 所有的命令都用命令条目格式来指定。 选择基础背景色彩命令 下表中列出的打印机命令被打印机应用但不支持可编程的色彩调色板,如平面色彩打印机(例如,点阵打印机)及一些调色板打印机(例如,较早期的喷墨打印机)。 命令 描述 注解 CmdSelectBlackColor 选择黑色背景的命令 可选  CmdSelectBlueColr 选择蓝色背景的命令 可选  CmdSelectCyanColor 选择青色背景的命令 可选  CmdSelectGreenColor 选择绿色背景的命令 可选  CmdSelectMagentaColor 选择紫色背景的命令 可选  CmdSelectRedColor 选择红色背景的命令 可选  CmdSelectYellowColor 选择黄色背景的命令 可选  CmdSelectWhiteColor 选择白色背景的命令 可选  相关实例,可以参考GPD实例文件。 控制打印机调色板的命令 下表列出了用于支持对前景(文本及向量)打印或光栅打印时的打印机可编程调色板的打印命令。 命令 描述 注解 CmdBeginPaletteDef 色彩调色板定义初始化的命令 可选。如果没有被指定,则不需要调色板初始化定义  CmdEndPaletterDef 结束一个调色板定义的命令 可选。如果没有被指定,则不需要一个命令去结束一个调色板定义。 *Order属性可以被指定,如果没有指定,则*Order属性必需与最近执行的选项选择命令的*ColorMode属性相关。  CmdBeginPaletteReDef 色彩调色板重定义初始化的命令 可选。如果没有被指定,则不需要调色板初始化的重新定义  CmdEndPaletteReDef 结束一个调色板的重新定义 可选。如果没有被指定,则不需要命令来结束调色板重新定义  CmdDefinePaletteEntry 定义一个调色板条目的命令 如果打印机支持调色板,则是必需的。  CmdRedefinePaletteEntry 重新定义一个调色板条目的命令 可选。如果没有被指定,CmdDefinePaletteEntry则被用于重新定义调色板条目  CmdSelectPaletteEntry 选择一个调色板条目作为当前色彩的命令 如果打印机支持调色板,则是必需的   相关实例,可以参考GPD实例文件。 选择调色板画刷的命令 下表列出的是被打印机用于支持下载及选择调色板画刷的打印机命令。 命令 描述 注解 CmdDownloadPattern 发送一个调色板画刷到打印机的命令 可选。如果被指定,CmdSelectPattern就需被指定  CmdSelectBlackBrush 选择一个实体黑色的画画刷为当前调色板画刷的命令 如果打印机支持画刷,则是必需的  CmdSelectPattern 选择一个可下载的调色板画刷的命令 可选。如果被指定,CmdDownloadPattern也必需被指定  CmdSelectWhiteBrush 选择一个实体黑色的画刷为当前调色板画刷的命令 可选。   光标命令 下表列出的打印机命令,用于控制光标的移动,所有的命令都用命令条目格式来指定。 命令 描述 注解 CmdBackSpace 将光标向回移动到最后一个被打印的字符处 可选。可在字体加粗时应用  CmdCR 移动光标到它的x位置的最左边 必需的  CmdFF 进页命令 如果*EjectPageWithFF?设置为TRUE时,是必需的  CmdLF 移动光标到下一行 可选。移动的总量由CmdSetLineSpacing指定  CmdPopCursor 从栈中弹出存储的光标的最后位置 如果CmdPushCursor被指定,则是必需的  CmdPushCursor 将当前的光标位置压入到栈中 可选。  CmdSetLineSpacing 当涉及CmdLF时,设置光标移动的距离 可选。  CmdUniDirectionOff 禁止单向打印的命令,因此允许双向打印 可选。  CmdUniDirectionOn 允许单向打印的命令 可选。如果没有提供,以双向模式打印  CmdXMoveAbsolute 移动光标到一个绝对的x轴位置的命令 可选。命令字符串可以包括一个标准变量,它用于指定距。  CmdXMoveRelLeft 根据被指定的总量,将光标从当位x位置移动到其左边一定位置的命令 可选。命令字符串可以包括一个标准变量,它用于指定距离  CmdXMoveRelRight 根据被指定的总量,将光标从当位x位置移动到其右边的一定位置的命令 可选。命令字符串可以包括一个标准变量,它用于指定距离  CmdYMoveAbsolute 移动光标到一个绝对的y轴位置的命令 可选。命令字符串可以包括一个标准变量,它用于指定距离  CmdYMoveRelDown 根据被指定的总量,将光标从当前y轴位置向下移动到一定位置的命令 可选。命令字符串可以包括一个标准变量,它用于指定距离  CmdYMoveRelUp 根据被指定的总量,将光标从当前y轴位置向上移动到一定位置的命令 可选。命令字符串可以包括一个标准变量,它用于指定距离  相关实例,可以参考GPD实例文件。 4.4.2.2打印机配置命令 下面的表格列出了打印配置命令。所有的命令都由命令条目格式来指定。 命令 描述 注解 CmdStartJob 初始化一个打印作业的命令 可选。命令执行顺序必需被指定  CmdStartDoc 初始化一个文档的命令 可选。命令执行顺序必需被指定  CmdStartPage 初始化一个页面并将光标位置设为坐标系原点(0,0)的命令 可选。命令执行顺序必需被指定  CmdEndPage 结束打印一页的命令 可选。命令执行顺序必需被指定  CmdEndDoc 结束打印一个文档的命令 可选。命令执行顺序必需被指定  CmdEndJob 完成一个打印作业的命令 可选。命令执行顺序必需被指定  CmdCopies 指定打印时的拷贝份数的命令 可选。命令执行顺序必需被指定  CmdSleepTimeOut 指定打印机进入省电模式前等待的分钟数的命令 可选。命令执行顺序必需被指定  相关实例,可以参考GPD实例文件。 4.4.2.3选项选择命令 下表中列出了选项选择命令,命令由命令条目格式指定。 命令 描述 注解 CmdSelect 选择一个打印机选项的命令 对每一个选项都是必需的,且命令执行顺序必需被指定  相关实例,可以参考GPD实例文件。 4.4.2.4文本打印命令 文本打印命令是那些与打印文本相关的命令,这些命令可以分为下面几组: 设备字体命令 下载字体命令 仿真字体命令 设备字体命令 没有与设备字体相关的命令。更多的关于设备字体的特性,参考设备字体属性部分。 下载字体命令 下表列出的命令用于控制下载字体,所有的命令都用命令条目格式来指定。 命令 描述 注解 CmdDeleteFont 通过指定字体标识符来删除软字体的命令 可选。如果被删掉的字体相关的内存可以被立即收回,指定这一命令  CmdDeselectFontID 撤消对当前字体ID的选定以使该字体处于非活动状态的命令 可选。如果不是当前状态,一种新字体被选定时,当前的字体不必要进行撤消选定  CmdSelectFontHeight 选择被下载字体的高度的命令 可选。如果不是当前状态,打印机不支持可缩放、下载的TrueType轮廓字体。该命令在HPPCL_OUTLINE时是需要的  CmdSelectFontID 选择当前的字体ID以使该字体处于活动状态的命令 如果打印机支持下载字体时,是需要的。  CmdSelectFontWidth 选择下载字体的宽度的命令 可选。如果不是当前状态,下载字体的宽度可根据它的高度适当调整  CmdSetCharCode 指定下一个将要被下载或被删除的字体的代码的命令 如果打印机支持下载字体时,是需要的。  CmdSetFonID 设置当前字体ID的命令 如果打印机支持下载字体时,是需要的。  相关实例,可以参考GPD实例文件。 仿真字体命令 下表列出的命令用于控制仿真字体。所有的命令用命令条目格式来指定。 命令 描述 注解 CmdBoldOff 禁止加粗(Bolding)的命令 可选。如果CmdBoldOn被指定,则它必需被指定  CmdBoldOn 允许加粗的命令 可选。如果被指定,Undriv发送这一命令以允许加粗并且发送CmdBoldOff以禁止加粗  CmdClearAllFontAttribs 单一命令,以禁止加粗、斜体、及下划线的命令 可选。如果打印机支持加粗、斜体或下划线时可以被指定,但是只支持一个命令来禁止加粗、斜体及下划线。而不是用CmdBoldOff,cmdItalicOff及CmdUnderlineOff  CmdItalicOff 禁止斜体的命令 可选。如果CmdItalicOn被指定,则它必需被指定  CmdItalicOn 允许斜体的命令 可选。如果被指定,Unidrv发送这一命令以允许斜体并发送CmdItalicOff以禁止斜体  CmdSelectDoubleByteMode 允许双字节打印的命令 可选。如果CmdSelectSingleByteMode被指定,则它必需被指定  CmdSelectSingleByteMode 允许单字节打印的命令 可选。如果打印机可以在单字节打印有双字节打印之间切换,则必需被指定  CmdSetFontSim 单一命令,以设置加粗、斜体、下划线及加删除线能力的命令 可选。如果在每一次字体被应用时字体特征都必须被指定时必须指定。  CmdStrikeThruOff 禁止加删除线的命令 可选。如果CmdStrikeThruOn指定,则必需被指定。  CmdStrikeThruOn 允许加删除线的命令 可选。如果被指定,Unidrv将发送命令以允许加删除线并发送CmdStrikeThruOff以禁止加删除线  CmdUnderlineOff 禁止下划线的命令 可选。如果CmdUnderlineOn被指定时必需被指定,  CmdUnderlineOn 允许下划线的命令 可选。如果被指定,Unidrv发送这一命令以允许加下划线并发送CmdUnderlineOff以禁 止加下划线  CmdVerticalPrintingOff 禁止垂直打印的命令 可选。如果CmdVerticalPrintingOn被指定,则必需指定  CmdVerticalPrintingOn 允许垂直打印的命令 可选。如果打印机支持垂直打印则必需被指定  CmdWhiteTextOff 禁止打印白色文本的命令 可选,如果CmdWhiteTextOn被指定则必需被指定  CmdWhiteTextOn 允许打印白色文本的命令 可选。如果被指定,Unidrv发送这一命令以允许打印白色文本并发送CmdWhiteTextOff以禁止打印白色文本(这一命令是为与GPC3.0向后兼容提供的)  相关实例,可以参考GPD实例文件。 4.4.2.5光栅打印命令 光栅打印命令是与那些与打印光栅图形的命令相关的打印命令。 这些命令被分成为以下几组: ■光栅数据压缩命令 ■光栅数据发送命令 光栅数据压缩命令 下表列出的光栅数据压缩命令,所有的命令都用命令条目格式指定。 更多的关于光栅数据压缩命令的信息,可以参考压缩光栅数据部分。 命令 描述 注解 CmdDisablecompression 禁止打印机接受所有压缩数据类型的命令 可选。  CmdEnableDRC 允许打印机接受DRC压缩的数据的命令 可选。如果没有被指定,Unidrv不使用DRC压缩(Delta-Row compression)  CmdEnableFE_RLE 允许打印机接受FE_RLE压缩的数据的命令 可选。如果没有被指定,Unidrv不使用FE_RLE压缩  CmdEnableOEMComp 允许打印机接受定制类型压缩的数据 可选。如果没有被指定,Unidrv不使用定制的数据压缩  CmdEnableTIFF4 允许打印机接受TIFF 4.0压缩的数据 可选。如果没有被指定,Unidrv不使用TIFF 4.0压缩  相关实例,可以参考GPD实例文件。 光栅数据发送命令 下表列出了光栅数据发送的命令,所有的命令都是由命令条目格式指定的。 命令 描述 注解 CmdBeginRaster 初始化一个光栅数据传输的命令 可选。如果没有被指定,Unidrv认为不需要初始化  CmdEndRaster 完成一个光栅数据传输的命令 可选。如果没有被指定,Unidriv认为不需要传输完成的操作(transfer-completion)  CmdSetDestBmpHeight 设置目标位图的高度的命令 可选。只有在打印机支持可缩放的位图时才适用  CmdSetDestBmpWidth 设置目标位图的宽度的命令 可选。只有在打印机支持可缩放的位图时才适用。  CmdSetSrcBmpHeight 设置一个源位图的高度的命令 可选,只有在打印机支持可缩放位图时才适用  CmdSetSrcBmpWidth 设置一个源位图的宽度的命令 可选。只有在打印机支持可缩放位图时才适用  CmdSendBolckData 发送一个数据块到打印机的命令 如果*OutputDataFormat是V_BYTE时是需要的,一个块包含打印头的物理传送的数据(参考*PinsPerPhysPass)。如果*OutputDataFormat是H_BYTE,则一个块包含打印头逻辑传送的数据(参考*PinsPerLogpass)  CmdEndBlockData 一个意味着用CmdSendBlockData发送数据时结束的命令 可选。如果没有被定,Undriv假定不需要什么命令来指明传送块的结束(在一些点阵打印机中用到)  CmdSendBlackData 发送黑色平面数据到打印机的命令 如果使用*UseExpColorSelectCmd?属性值是FALSE时是需要的  CmdSendBlueData 发送蓝色平面数据到打印机的命令 如果使用*UseExpColorSelectCmd? 属性值是FALSE时是需要的  CmdSendCyanData 发送青色平面数据到打印机的命令 如果使用*UseExpColorSelectCmd? 属性值是FALSE时是需要的  CmdSendGreenData 发送绿色平面数据到打印机的命令 如果使用*UseExpColorSelectCmd? 属性值是FALSE时是需要的  CmdSendMagentaData 发送紫色平面数据到打印机的命令 如果使用*UseExpColorSelectCmd? 属性值是FALSE时是需要的  CmdSendRedData 发送红色平面数据到打印机的命令 如果使用*UseExpColorSelectCmd? 属性值是FALSE时是需要的  CmdSendYellowData 发送黄色平面数据到打印机的命令 如果使用*UseExpColorSelectCmd? 属性值是FALSE时是需要的  相关实例,可以参考GPD实例文件。 4.4.2.6向量打印命令 向量打印命令是那些与打印向量图形相关的命令。这些命令组成一个单一的组: 矩形区域填充命令 矩形区域填充命令 以下表格列出了矩形区域填充命令,所有的命令都用命令条目格式指定。 命令 描述 注解 CmdRectBlackFill 黑色填充一个矩形区域 可选的。如果没有被指定,Unidrv企图通过指定用灰色百分比*MaxGrayFill的CmdRectGrayFill命令来进行黑色填充  CmdRectGrayFill 灰色填充一个矩形区域(不删除背景) 可选。如果没有被指定,Unidrv认为没有灰色填充的能力,命令字符串通常包括GrayPercentage变量  CmdRectwhiteFill 白色填充一个矩形区域(不删除背景) 可选。如果没有被指定,Unidrv假定没有删除白色填充。这种情况下,如果应用程序请求白色填充,Unidrv将返回失败,因为灰色填充不能删除背景  CmdSetRectHeight 设置矩形高度的命令 可选。如果CmdSetRectWidth被指定则必需被指定  CmdSetRectSize 设置矩形高度和宽度的命令 不支持Windows 2000 可选。用于打印机设置在一个命令中设置高度和宽度  CmdSetRectWidth 设置矩形宽度的命令 可选。如果CmdRectHeight被指定,就必需被指定。  相关实例,可以参考GPD实例文件。 4.4.3命令属性 当指定一个打印机的命令,使用属性来给Unidrv提供以下的信息: 如果操作在打印机硬件实现,则引起硬件执行操作的escape序列。 如果操作在一个绘制插件中实现时,回调标识符及IPrintOemUni::CommandCallback方法所需要的参数。 一个命令应当被发送的、与其他命令相关的顺序。 下表列出的以字母顺序排出的命令属性描述了它们的参数: 属性名 属性参数 注解 *CallbackID 正的数值型值,被传送到绘制插件的IPrintOemUni::CommandCallback方法作为它的dCmdCbID参数 在动态产生打印机命令时需要,如果*Cmd被指定,则不有效  *Cmd 文本字符串,包含一个打印机命令的escape序列,用字符命令串格式指定 除非*CallbackID被指定时必需  *NoPageEject? TRUE或FALSE。意指是否执行命令以引起打印机跳过本物理页。只有在*Order指定了DOC_SETUP区段并且允许DUPLEX打印时使用。以避免在重复的文档页面中过早跳页。如果可能,Unidrv只讨论这一特性设置为TRUE的问题 可选。如果没有被指定,默认值是FALSE。意指命令可能会引起跳页。 如果一个命令引起副作用(即,如果命令修改打印机设置超出将*NoPageEject?设置为TRUE的控制以外)就不能为TRUE。  *Order 区段名以及顺序号码,如在命令执行顺序中所描述的 只有在与配置命令及定制的选项命令时是有效的,除非在命令描述中给出状态。  *Params 列出标准变量,传寄给绘制插件的在EXTRAPARAM结构中的IPrintOemUni::CommandCallback方法,并作为一个pdwParams参数被传递 只在*CallbackID被指定时是有效的。  相关实例,可以参考GPD实例文件。 4.4.4命令字符串格式 命令字符串用于指定Unidrv必须发送给打印机硬件的escape序列。命令字符串可以由以下元素构成: 加引号的文本字符串,它们有这样的格式:“TextString”,这里TextString是一个文本字符串。 命令参数,它有这样的格式:%ArgumentType{StandardVariableExpression} 这里,ArgumentType是一个字母,它表示一个命令字符串参数的类型,StandardVariableExpression是一个标准的变量表示。 任何数量的加引号的文本字符串和任何数量的命令参数都可以在命令字符串中组合起来。 如一个实例,一个打印机的命令用于设置矩形的灰色填充也许可以指定如下: Command:CmdRectGrayFill:”*<1B>c” %d{GrayPercentage}”g2p” 发送一个百分号符号(%)到打印机,则命令字符串中包括两个百分号符号。如果百分号符在命令字符串的末尾,必须使用十六进制的等制来表示,如: “String<25 25>” 4.4.4.1命令字符串参数类型 当在命令字符串中包括参数,必须指定每一个参数的类型,每一个参数类型的说明是一个单一字母,其前放置了一个百分号。 下表列出所有的参数类型的指定符号。 参数类型指定符 结果值的描述 %<Digits>d ASCII字符串,表示一个十进制的值,如果是负数还包括一个减号。<Digits>是一个可选的数字,意指字符串的长度  %<Digits>D ASCII字符串,表示十进制的值,包括一个加号或减号。<Digits>是一个可选的数字,意指字符串的长度  %c 二进制字节  %C 附加了ASCII“0”的二进制字节  %f 无符号ASCII字符串,表示一个十进制的值,具有一个从右边开始第三位字母是十进制的小数点,如“12.25”  %g 2*ABS(parameter)+IS_NEGATIVE(parameter)作为一个64位数,从最小的有效位数到最多的有效位数。最多的有效位数(0-63)由192字节到254字节来表示,其他的位数由63字到126字节来表示。如果parameter为负数则“IS_NEGATIVE(parameter)”值为1,其他值时结果为0  %l 二进制字,最小的有效字节数  %m 二进制字,最多的有效字节数  %n 佳能整型编码(Cannon integer encoding)。从最多的有效字数到最小的有效字节数的二进制值编码。四个最小位数被编码为001sbbb,这时s表示符号(0是负数,1是正数),b表示整数的有效位数。下一个最大小效的6位数被编码为01bbbbbb。例如,254(11111110)表示为(01001111 00111110)  %q ASCII字符串,表示一个对Toshiba/Qume设备的QUME十六进制数  %v NEC VFU(Vertical Format Unit)编码,指定变量的值按1/6英寸等分,结果值是VFU被发送到打印机的次数   可以为任何参数指定可以接受的值的区间。为这样做,包括参数的最大值及最小值都可以置于一个方括号中([,]),接着是参数类型的指定符,并用一个逗号分开其值。例如,下边的命令指定0到255为可接受的LinfeedSpacing/2的值区间: Command:CmdSetLineSpacing{*Cmd:”<1B>3”%c[0,255]{(LinefeedSpacing/2)} 4.4.4.2标准变量表达式 当指定命令中的参数时,可以作为一个表达式来指定参数的值。这一表达式可以用当前标准变量的值来执行操作。每一个在命令字符串中的标准变量表达式由圆括号分隔开来({,})。 一个标准变量表达式由下面的部分组合构成: 0,1或更多的标准变量 整形数值 表达式操作符 一个标准变量表达式不能包含嵌入的宏引用。 表达式操作符包含于下面的表中。 操作符 定义 Val1+Val2 加法  Val1-Val2 减法  Val1/Val2 除法  Val1*Val2 乘法  Max(Val1,Val2) 取最大值,其值为Val1,Val2中的最大值  Max_repeat(Val1) 参考使用Max_repeat()  Min(Val1,Val2) 取最小值,其值为Val1,Val2中的最小值  Mod(Val1,Val2) 取模,其值为Val1除以Val2的余数  () 优先操作符,如果不使用,则使用C语言的优先操作符  标准的变量表达式不修改赋给标准变量的值,计算结果的值被放置于escape序列中,利用由命令字符串类型指示符指定的格式来存放。 利用Max_repeat max_repeat最好用一个实例来解释。假定一个GPD文件包含下面的条目: *Command:CmdXMoveRelRight{*Cmd:”<1B>[”$d[0,9600]]{max_repeat((DestXRel/4))}”a”} 这一命令包含一个单一参数,即%d类型。它也包含一个参数的区间说明。无论何时Unidrv发送这一命令到打印机,它首它计算DextXRel/4并确定它是否在指定的区间内,如果计算的值大于9600,Unidrv将以最大值为9600重复发送命令,直到指定的值被发送为止。这样,如果DesXRel/4等于20,000,Unidrv将发送下面的命令: <1B>[9600 <1B>[9600 <1B>[800 max_repeat操作符只有在下面的条件下才可以遇到: 命令字符串只包括一个参数 参数包括一个区间说明 4.4.5命令执行顺序 打印机命令必须以一个具有意义的顺序被发送到打印机硬件。对许多定义在GPD语言中的命令名称,Unidrv知道什么时候向打印机发送escape序列的命令,只有两种特例: 选项选择命令 打印机配置命令 对这两种类型的命令,必须指定一个命令执行的顺序。 命令执行顺序由两项组件组成——一个作业区段名称和一个序列顺序号码。Unidrv驱动程序将打印作业分成六个区段,对每一个区段,Unidrv按指定的顺序发送分配于该区段的打印机命令。下面的区段都是被定义的: JOB_SETUP 分配于JOB_SETUP区段的命令对每个作业只发送一次,它们是一个新的作业开始时的第一批发送的命令。这些命令从Unidriv的DrvStartDoc函数实现中发出的。 DOC_SETUP 分配于DOC_SETUP区段的命令是在文档的第一页发送之前发送的。命令从Unidrv的DrvStartDoc函数实现中发出。(这些命令也可以在一个应用程序调用Win32的ResetDC函数之后发送,这一区段不能删除一个下载的信息,如软字体及调色板等)。 PAGE_SETUP 分配于区段PAGE_SETUP的命令在每一页的开始、绘制开始之前被发送的。这些命令从Unidrv的DrvStartPage函数实现中发出。 PAGE_FINISH 分配于区段PAGE_FINISH的命令在每一页的末尾、绘制完成时发送,这些命令从Unidrv的DrvSendPage函数实现中发出。 DOC_FINISH 分配于DOC_FINISH区段的命令在文档的最后一页发送完之后发送。这此命令从Unidrv的DrvEndDoc函数实现中发出。(在这一区段的命令不能去除下载信息,如软字体及调色板等) JOB_FINISH 分配于JOB_FINISH区段的命令在每一个作业中只发送一次,它们是当一个作业结束时的最后的命令。这些命令从Unidrv的DrvEndDoc函数实现中发出。 在这每一个区段中,命令由它们的序列号指示按顺序执行。 为了指定一个命令区段及序列号,用*Order属性,它是一个命令属性,其格式为: *Order:SectionName.SequenceNumber 这里,SectionName是JOB_SETUP,DOC_SETUP,PAGE_SETUP,PAGE_FINISH,DOC_FINISH或JOB_FINISH中的一个,SequenceNumber是一个数字型值。 序列号不需要保持连惯,但每一个区段中指定的序列号必须是唯一的。每一个区段中的命令按照所有区段序列号从最小到最大的顺序执行。例如,下面的条目示意了InputBin,PaperSize及Resolution特性的选项,并被指定于DOC_SETUP区段,以指定的顺序进行发送: *Feature:InputBin { *DefaultOption:Auto *Option:Auto { *Name:”Auto Tray” *Command:CmdSelect { *Order:DOC_SETUP.50 *Cmd:”<1B>(1<010014>” } } …… } *Feature:PaperSize { *DefaultOption:Letter *Option:Letter { *Name:”Letter size” *Command:CmdSelect { *Order:DOC_SETUP.60 *Cmd:”<1B>(g<0300>n<01>r” } } …… *Feature:Resolution { *DefaultOption:360dpi *Option:360dpi { *Name:”360dpi x 360dip” *Command:CmdSelect { *Order:DOC_SETUP.70 *Cmd:”<1B>(d<020001>” } } …… } 4.5打印机特性 打印机特性是可以由Unidrv驱动程序控制的能力。通过在一个GPD文件中列出一个特性及它的特征,就可以通知Unidrv驱动程序打印机支持的特性。 每一个打印机的特性可以分配给一个或多个状态(states),打印机选项(pinter options)被用于定义可能的状态。例如,如果打印机接收所有信纸大小尺寸及合法大小尺寸的页面,GPD文件应当在选择Letter和Legal选项时并指定PaperSize的属性。 所有的特性及相关的选项是被枚举于打印机相关的属性表单或文档属性表单上。更多的关于这些属性表单的信息,可以参考Unidrv用户接口部分。 这一部分解释GPD语言支持标准特性及定制特性,本部分队附加主题还讨论了特性条目格式、特性属性及特性冲突优先级。 4.5.1标准特性 标准特性是许多打印机都提供的打印机的一般特性。它们通过预置GPD语言能识别的名称来鉴别(字符串的资源标识符表示这些名称包含于stdnames.gpd,该DDK中即提供stdnames.gpd)。一些标准特性是必需的并且必须对每个打印机都指定,另外一些则是可选的。 下表中列出所有的标准特性,以字母排序,并指明是否每一个特性接受标准的选项或定制的选项。 特性名称 描述 标准选项 注解 Collate 页面整理 参考标准选项部分内容。不允许定制选项 可选。如果没有被指定,Unidrv不支持页面整理  ColorMode 色彩打印模式 无。所有的选项都是定制选项。参考色彩模式特性的选项属性 可选。如果没有被指定,Unidrv将以单一平面、每像素1位的格式绘制图像  Duplex 双倍打印 参考标准选项部分内容。不允许定制选项 可选。如果没有被指定,Unidrv只执行单面打印  Halftone 过渡调色的能力 参考标准选项部分内容。不允许定制选项。也可参考选项属性中的中间色属性 可选,如果没有被指定,Unidrv选择一个GDI支持的过渡调色的方法。 也可参考用Unidrv过渡调色部分的内容  InputBin 二进制输入的类型 参考图形驱动程序设计与实现中的标准选项部分的内容。不允许定制选项。也可参考二进制输入特性的选项属性部分 必需的。定制的二进制名称必需是24个字符或以下  MediaType 打印媒体的类型 参考标准选项。不允许定制选项 可选。如果没有被指定,则一直使用打印机的默认媒体  Memory 打印机内存配置 所有的选项都是定制的。参考内存特性的选项属性部分 可选。如果被指定,Unidrv企图追踪内存的使用。默认的*FeatureType的值是PRINTER_PROPERTY  Orientation 纸的方向 参考标准选项。不允许定制选项 可选。如果没有被指定,默认值是竖向  OutputBin 输出二进制的类型 没有标准选项。所有的选项都是定制选项。参考二进制输出特性的选项属性 可选。如果没有被指定,Unidrv不企图选择二进制输出  PageProtect 允许打前打印页的页保护 参考标准选项。不允许定制选项 可选。如果没有被指定,默认值是OFF。Unidrv只在有足够的打印机内存时才进行页面保护。默认的*FeatureType值是PRINTER_PROPERTY,也可参见*PageProtectMem  PaperSize 页面尺寸 参考标准选项。不允许定制选项 可以参考纸张尺寸的选项属性部分内容 必需的。至少一个选项必需被指定,即CUSTOMSIZE选项允许打印机用户指定纸的尺寸  RESDLL 资源DLL 所有的选项都是定制的。参考在小驱动程序中使用资源DLL部分的内容 可选。参考*ResourceDLL  Resolution 打印精度 所有的选项都是定制的。参考精度特性的选项属性部分的内容 必需的。至少一个选项必需被指定  Stapling 分类能力 所有的选项都是定制的 可选。如果被指定,目录服务指明打印机支持分类  相关实例,可以参考GPD实例文件。 4.5.2定制特性 定制特性是那些特定于硬件的特性。为这些特性创建了一个唯一的名称。每一个定制的特性,必须指定一套定制的选项。例如,假设打印机提供经济模式的操作。由于GPD语言不提供描述这一能力的特性,必须定义一个定制的特性及它的选项。该特性的说明也许可以如下: *Feature:EcomoMode { *Name:”Economy Mode” *FeatureYype:PRINTER_PROPERTY *DefaultOption:EconoModeOff *Option:EconoModeOff { *Name:”Off” *Command:CmdSelect { *Order:DOC_SETUP.5 *Cmd:”@PJL SET ECONOMODE=OFF<0A>” } } *Option:EconoModeOn { *Name:”On” *Command:CmdSelect { *Order:DOC_SETUP.5 *Cmd:”@PJL SET ECONOMODE=ON<OA>” } } } 4.5.3特性条目格式 为在一个GPD文件中指定一个打印机特性条目,用下面的格式: *Feature:FeatureName{FeatureAttributes} 这里,FeatureName或者是预先定义的标准特性的名称或者是一个定制的特性名称,FeatureAttributes是一套特性属性。 例如,一个GPD文件也许可以包含以下标准二进制输入特性的说明。 *Feature:InputBin { *Name:”Paper bin” *DefaultOption:Upper *Option:Upper { *Name:”Upper Tray” *Command:CmdSelect { *Order:DOC_SETUP.10 *Cmd:”<1B>&11H” } *Constraints:PaperSize.env10 } *Option:Manual { *Name:”Manual Feed” *Command:CmdSelect { *Order:DOC_SETUP.10 *Cmd:”<1B>&12H” } *Installable?:TRUE } } 如果重复一个特性说明,如包含两个或更多的二进制输入特性条目,则必须应用下面的规则: 将没有重复的属性及选项加到Unidrv的数据库中。 那些重复的特性及选项被覆盖,Undriv只保存最后的说明。 可以控制顺序,在这一顺序中所有的特征按序显示给用户。参考指定特性及选项显示顺序部分的内容。 4.5.4特性属性(Feature Attributes) 当指定一个打印机的特性,使用属性来给Undriv提供以下的信息: 一个文本字符串,以表示特性的显示的名称 与特性相关的打印机的选项集 一个布尔值,意指一个特性是否一直处于当前状态或可以安装 特性的类型及优先级。如果特性是定制的,意指那一个属性表单可以被显示以及它的相关的优先级。 下表列出的以字母排序的特性属性,描述了它们的参数: 属性名称 属性参数 注解 *ConcealFromUI? TRUE或FALSE。意指是否该特性应当在用户接口中显示。对Windows 2000不支持 可选。如果没有被指定,默认值是FALSE,意指特性被显示。当一个特性只有一个选项(如,一种精度值)时并且不是用户可修改的,或者如特性的选项选择由设置另外一个特性的选项来控制时,其值必需是TRUE。  *ConfictPriority 数字型值。表示特性的优先级,1是最高优先级 可选。参考特性冲突优先级部分  *DefaultOption 一种特性选项名称 可选。如果没有被指定,列在*Feature条目中的第一个选项是默认值  *Featuretype DOC_PROPERTY JOB_PROPERTY PRINTER_PROPERTY 如果DOC_PROPERTY或者JOB_PROPERTY,该特性分配为言语档的属性表单,如果是PRINTER_PROPERTY,则特征分配为打印机属性表单 对定制的选项是必需的。对标准特性可选。如果没有被指定,对标准特性的默认值是DOC_PROPERTY,除非有其他注解。如果是PRINTER_PROPERTY,特性选项的值被保存于注册表中。如果是DOC_PROPERTY或JOB_PROPERTY,特性选项值与文档一起保存。  *HelpIndex 数字型值,表示一个到帮助文件的索引,由*HelpFile这一根层次特性来指定 (也是一个选项特性)  *Installable? TRUE或者FALSE,意指该特性是否能被安装(FALSE意指一直安装着) 更多的信息可以参考处理可安装特性及选项 可选。如果没有被指定,默认值是FALSE,如果是TRUE,所有特性的选项也是可安装的。除了第一个是被指定的。如果是FALSE,必须至小有一个特性的选项必需一直被安装(也是一个可选特性)  *InstallableFeatureName 文本字符串,用于显示询问用户是否是一个可安装的特性是否是实际安装的特性。 更多的信息可以参考处理可安装特性及选项 如果*Installable?是TRUE并且*rcInstallableFeatureNameID没有被指定(也是一个选项特性)  *Name 文本字符串,用于在打印机的属性表单上显示该特性名称 可选。如果没有被指定,*rcNameID必须被指定(也是一个可选特性)  *Option 可选参数,如在选项条目格式中所描述的 必需的。对每一个特性相关的选项都用一个*Option条目  *rcIconID 与一个特性相关的图标资源的资源ID 可选。如果没有被指定,Unidrv在打印机属性表单上不显示一个特性相关的图标。(也是一个可选特性)  *rcInstallableFeatureNameID 一个文本字符串的资源ID,用于显示询问用户是否是一个可安装的特性是否是实际安装的特性。 更多的信息可以参考处理可安装特性及选项 如果*Installable?是TRUE并且*nstallableFeatureName没有被指定时是必需的。(也是一个可选特性)  *rcNameID 一个字符串资源的ID,表示特性的名称(0是一个不有效的资源ID) 可选。如果没有被指定,则*Name必须被指定(也是一个可选特性)  *UpdateQualityMacro? TRUE或者FALSE,指明特性是否包含于一个指定质量设置的条件语句中(参考控制图像质量部分内容) 可选。如果没有被指定,默认值是FALSE。(如果特性包含于一个指定质量设置的条件语句中时,该值必须是TRUE)  相关实例,可以参考GPD实例文件。 4.5.5特性冲突优先级 一个特性冲突优先级标识了一个特性在Undriv的用户接口代码实施选项约束时应当有的优先级。 GPD解析器给按下面的顺序给特性分配一个冲突优先级,从最高优先级到最低优先级: 实际安装着的可可安装特性。(参考处理可安装特性及选项) 具有*FeatureType的特性设置为PRINTER_PROPERTY。 具有*FeatureType的特性设置为DOC_PROPERTY或JOB_PROPERTY。 在每一个特性类型中的属性被分配给一个相关的优先级,该优先级是基于指定在特性的*ConfilictPriority属性的值。这样,例如,一个PRINTER_PROPERTY特性具有值为1的*ConfictPriority属性优先值高于一个具有DOC_PROPERTY特性具有值为3的*ConflictPriority的属性。没有*ConfilictPriority属性的特性具有比有*Confilictpriority属性更低的优先级。 更多的关于*FeatureType和*ConflictPriority属性的信息,可以参考特性属性部分的内容。 4.6打印机选项 打印机选项表示了可以分配给打印机特性的状态。例如,如果一个打印机可以用信纸大小的尺寸和法定尺寸大小的尺寸的纸张,则LETTER及LEGAL被指定为一个纸张尺寸特性的选项。 本部分讨论GPD语言支持的所有标准选项及定制选项。 本部分附加的主题讨论了选项条目格式,选项属性及选项约束等。 4.6.1标准选项 标准选项是那些与标准特性相关的选项。它们被GPD语言所识别的预定义的名称所鉴别。(这些用字符串表示的资源标识符的名称被包含于stdnames.gpd中,在DDK也提供)。 下表列出的标准选项名称对每一个标准的特性都是允许的。这些名称被作为参数用于*Options条目中。 特性名称 标准选项名称 是否允许定制选项 Collate ON,OFF 不  ColorMode 没有标准选项 是,参考处理色彩格式及控制图像质量部分内容  Duplex HORIZONTAL,VERTICAL,NONE 不  Halftone HP_PATSIZE_2x2 HP_PATSIZE_2x2_M HP_PATSIZE_4x4 HP_PATSIZE_4x4_M HP_PATSIZE_6x6 HP_PATSIZE_6x6_M HP_PATSIZE_8x8 HP_PATSIZE_8x8_M HP_PATSIZE_10x10 HP_PATSIZE_10x10_M HP_PATSIZE_12x12 HP_PATSIZE_12x12_M HP_PATSIZE_14x14 HP_PATSIZE_14x14_M HP_PATSIZE_16x16 HP_PATSIZE_16x16_M HP_PATSIZE_SUPERCELL HP_PATSIZE_SUPERCELL_M HP_PATSIZE_ATUO 是,参考用Unidrv过渡调色部分  InputBin AUTO CASSETTE ENVFEED ENVMANUAL FORMSOURCE LARGECAPACITY LARGEFMT LOWER MANUAL MIDDLE SMALLFMT TRACTOR UPPER 是  MediaType GLOSSY STANDARD TRANSPARENCY 是,参考控制图像质量部分  Memory 无标准选项 是  Orientation PORTRAIT,LANDSCAPE_CC90 LANDSCAPE_CC270 不  PageProtect ON,OFF 不  PaperSize 10X11 10X14 11X17 12X11 15X11 9X11 A_PLUS A2 A3 A3_EXTRA A3_ExTRA_TRANSVERSE A3_ROTATED A4 A4_EXTRA A4_PLUS A4_ROTATED A4_TRANSVERSE A4SMALL A5 A5_EXTRA A5_ROTATED A5_TRANSVERSE A6 A6_TOTATED B_PLUS B4 B4_JIS-ROTATED B5 B5_EXTRA B5_JIS_ROTATED B5_TRANSVERSE B6_JIS B6_JIS_ROTATED CSHEET CUSTOMSIZE DBL_JAPANESE_POSTCARD DBL_JAPANESE_POSTCARD_ROTATED DSHEET ENV_10 ENV_11 ENV_12 ENV_14 ENV_9 ENV_B4 ENV_B5 ENV_B6 ENV_C3 ENV_C4 ENV_C5 ENV_C6 ENV_C65 ENV_DL ENV_INVITE ENV_ITALY ENV_MONARCH ENV_PERSONAL ESHEET EXECUTIVE FANFOLD_LGL_GERMAN FANFOLD_SD_GERMAN FANFOLD_US FOLIO ISO_B4 JAPANESE_POSTCARD JAPANESE_POSTCARD_ROTATED JENV_CHOU3 JENV_CHOU3_ROTATED JENV_CHOU4 JENV_CHOU4_ROTATED JENV_KAKU2 JENV_KAKU2_ROTATED JENV_KAKU3 JENV_KAKU3_ROTATED JENV_YOU4 NENV_YOU4_ROTATED LEDGER LEGAL LEGAL_EXTRA LETTER_EXTRA_TRANSVERSE LETTER_PLUS LETTER_ROTATED LETTER_TRANSVERSE LETTERSMALL NOTE P16K P16K_ROTATED P32K P32K_ROTATED P32KBIG P32KBIG_ROTATED PENV_1 PENV_1_ROTATED PENV_10_ROTATED PENV_2 PENV_2_ROTATED PENV3 PENV_3_ROTATED PENV_4 PENV_4_ROTATED PENV_5 PENV_5_ROTATED PENV_6 PENV_6_ROTATED PENV_7 PENV_7_ROTATED PENV_8 PENV_8_ROTATED PENV_9 PENV_9_ROTATED QUARTO STATEMENT TABLOID TABLOID_EXTRA 是,定制的名称不能超过在wingdi.h中由CCHFORMNAME指定的长度  Resolution 没有标准选项 是   4.6.2定制选项 定制选项是那些没有被GPD语言中的预定义名称定义的选项。必须为这些选项选择唯一的名称。 定制选项可以与定制的特性关联。与定制特性相关的定制选项的实例,可以参考定制特性部分的内容。 也可以为一些标准特性指定定制选项。如,若打印机提供一个没有由纸张尺寸特性的标准选项描述的纸张尺寸,可以通过为该选项创建一个唯一的名称来指定一个定制的纸张尺寸选项。 定制选项名称必须在特性中是唯一的,但选项名称可以被在不同特性中重用。也可以在定制特性中用标准的选项名称。这样,例如,可以在几个*Feature条目中为定制特性使用标准的选项名称ON及OFF。 4.6.3选项条目格式 为在一个GPD文件中指定一个打印机选项条目,可以使用如下格式: *Option:OptionName{OptionAttributes} 这里,OptionName是一个预定义标准选项的名称或者是一个定制选项的名称,OptionAttributes是一套选项属性。 与特性收相关的一套*Option条目的实例,可以参考特性条目格式部分的内容。 如果重复一个选项说明,例如通过给纸张尺寸特性的LETTER选项指定两个或更多的*Option条目,在*Options条目中每个重复的选项属性覆盖前边一个,并且GPD解析器只保留最后一次遇到的。另外一方面,如果GPD解析器遇到两上或更多的对纸张尺寸(或其他特性)的*Feature条目,*Option条目以前没有指定的将被增加到解析器数据库中。 可以控制这些选项显示给用户的顺序。参考指定特性及选项显示的顺序。 4.6.4选项属性 当指定打印机选项时,可以使用属性给Unidrv提供以下信息: 文本字条符串表示一个选项显示的名称、用户提供及帮助信息等。 一个布尔值意指一个选项是否总是处于当前状态或可以被安装 能引起选项被选择的打印机命令 不同类型的特定选项的信息 这一部分包含以下主题: ■4.6.4.1所有特性的选项属性 ■4.6.4.2色彩模式特性的选项属性 ■4.6.4.3过渡调色特性的选项属性 ■4.6.4.4二进制输入特性的选项属性 ■4.6.4.5内存特性的选项属性 ■4.6.4.6二进制输出特性的选项属性 ■4.6.4.7纸张尺寸特性的选项属性 ■4.6.4.8精度特性的选项属性 4.6.4.1所有特性的选项属性 下表中,以字母顺序,列出了所有特性可用的选项属性,并描述了它们的参数。 属性名称 属性参数 注解 *Command 一个cmdSelect的选项选择命令,指定了一个必须发往打印机以选择不同选项的命令字符串 必需的。  *DisabledFeatures 枚举特性名称字符串时,鉴别那些那些已经被选择而被禁止的特性 可选。枚举的特性不能有*Installable?设置为TRUE。更多的信息,参考处理可安装特性及选项部分的内容  *HelpIndex 数字型值,表示一个由根层次特性的*HelpFile指定的对帮助文件的索引, (也是一个特性属性) 索引值不能是0或-1。  *Installable TRUE或FALSE。意指是否该选项是可安装的。(FALSE意指一直安装着)更多的信息,可以参考处理可安装的特性和选项部分 可选。如果没有被指定,默认值是FALSE。(也是一个特性属性)  *InstallableFeatureName 文本字符串,用于显示用询问用户是否一个可安装的特性是实际安装着的信息。可参考处理可安装特性和选项部分 必需的。如果*Installable?是TRUE并且*rcInstallableFeatureNameID没有被指定(也是一个特性属性)  *Name 文本字符串,用于选项在打印机属性表单上的选项显示名称 可选。如果没有被指定,则*rcNameID必须被指定(也是一个特性属性)  *OptionID 数字型值,表示一个Unidrv 存储于打印机的DEVMODE结构中的单一选项标识符,只与纸张PaperSizes,InputSlot,Halftoning,MediaType,Resolution等特性一起使用。其值分别是存储于DEVMODE结构中的dmPaperSize,dmDefaultSource,dmDitherType或dmMediaType成员。 可选。如果没有被指定,Unidrv分配一个标识符值(>256)以避免与Unidrv-分配的标识符的冲突,指定的值必须大于512  *PromptTime UISETUP或PRTSTARTDOC,指明何时Unidrv应当显示由*rcPromptMsgID指定的提示信息。如果是UISETUP,显示发生于用户在打印机属性表单中选择了选项之后;如果是PRTSTARTDOC,显示发生在打印作业开始时。 如果*rcPromptMsgID被指定,如PRTSTARTDOC为多个选项指定,提示的信息在一个具有OK按键的对话框中显示。  *rcIconID 与一个选项有关的图标资源的资源ID 可选。如果没有被指定,Unidrv不在打印机属性表单上显示该选项的图标(也是一个特性属性)  *rcInstallableFeatureNameID 一个文本字符串的资源ID,用于显示询问用户是否一个可安装的特性是实际安装着的信息。可参考处理可安装特性和选项部分。更多信息,可以参考处理可安装特性及选项部分的内容 如果*Installable?是TRUE并且*InstallableFeatureName没有被指定时,是必需的。 (也是一个特性属性)  *rcNameID 一个字符串资源的ID,表示选项的名称 可选。如果没有被指定,则*Name必须被指定。(也是一个特性属性) 只有对纸张尺寸特性的标准选项,设置这一特性为RCID_DMPAPER_SYSTEM_NAME会引起Unidrv用一个预选定义好的选项名称字符串  *rcPromptMsgID 字符串资源的资源ID,表示一个用户的提示信息 对二进制输入(InputBin)特性的MANUAL选项,指定一个消息,Unidrv可以显示请求更多的纸张。否则,可选的,Unidrv显示消息在一个有OK按键的对话框中。当Unidrv显示消息时,*rcPromptTime属性进行控制  相关实例,可以参考GPD实例文件。 4.6.4.2色彩模式特性的选项属性 下表列出的属性都与色彩模式(ColorMode)特性相关,更多的关于色彩模式的信息,可以参考标准特性部分。 属性名称 属性参数 注解 *Color TRUE或FALSE。意指选项是否产生色彩 可选。如果没有被指定,对*DrvBPP>1时,默认值是TRUE。为创建灰色的缩放比例,其置设置成FALSE,且*DrvBPP>1。  *ColorPlaneOrder 列表,意指Unidrv应当发送平面色彩数据的顺序。如: LIST(YELLOW,MAGENTA,CYAN,BLACK),LIST(RED,GREEN,BLUE),在列表中色彩可以重复。 在以下条件时必需,即如果*DevNumOfPlanes大于1,指定的色彩的数量必须等于*DevNumOfPlanes  *DevBPP 数字型值,意指打印机支持每个像素的色彩数据的位数。 可选。如果没有被指定,默认值是1。  *DevNumOfPlanes 数字型值,意指打印机支持的色彩平面的数量 可选。如果没有被指定,默认值是1(对彩色打印机来说,值为1也指像素格式)  *DrvBPP 数字型值,意指Unidrv在给缓冲区绘制位图时应当使用的像素格式的位数。位图的格式是Windows DIB,有效的值是1,4,8,16,24或32 可选。如果没有被指定,默认值是1(对彩色打印机来说,值等于1指的是平面模式planar mode) Windows DIB总是使用一个色彩平面  *IPCallbackID 正数字型值,被以IPrintOemUni::ImageProcessing的参数IPCallbackID传递给绘制插件的IPrintOemUni::ImageProcessing方法, 如果提供的绘制插件包含IPrintOemUni::ImageProcessing方法时是必需的  *PaletteProgrammable? TRUE或FALSE。意指色彩的调色板是否是可编程的 可选。如果没有被指定,默认值是FALSE。  *PaletteSize 数字型值,表示在色彩调色板中应用的指定选项条目的数量 可选。如果没有被指定,默认值是2  *RasterMode DIRECT或INDEXED,意指是否光栅数据被直接发送到打印机或通过一个色彩模板建索引 可选。如果没有被指定,默认值是INDEXED  相关实例,可以参考GPD实例文件。 关于附加的选项属性,可参考所有特性的选项部分的内容。也可以参考控制图象质量部分。 4.6.4.3过渡调色特性的选项属性 下表列出的属性与过渡调色特性相关,更多的关于过渡调色特性的信息,参考标准特性部分。 属性名称 属性参数 注解 *HTCallbackID 正数字型值,作为IprintOemUni::HalftonePattern方法的dwCallbackID参数被传递给IprintOemUni::HalftonePattern 如果提供了IprintOemUni::HaftonePattern方法时是必需的,可以参考用Unidrv过渡调色部分的内容  *HTNumPatterns 数字型值,表示过渡调色模板提供的数量 可以参考用Unidrv过渡调色部分的内容 可选。可以是1或3,如果是3,意指对红、绿及蓝色按顺序采用各自独立的调色板。如果没有被指定,默认值是1,可以和*rcHTPatternID或*HTCallbackID一起使用  *HTPatternSize 一对(PAIR)数字型值,表示以像素格式的调色板的宽度和高度,由*rcHTPatternID来指定 如果*rcHTPatternID被指定时是必需的,最大的调色板尺寸是PARI(256,256),宽与高的乘积,必须能被4整除,作为一个DWORD型值存储。  *rcHTPatternID RC_HTPATTERN资源标识符,表示过渡调色调色板的数据 如果一个过渡调色的调色板在一个资源DLL中提供。可以参考用Unidrv过渡调色  相关实例,可以参考GPD实例文件。 更多的关于使用这些属性的信息,可参考用Unidrv过渡调色部分。这些属性不能与小驱动程序提供的过渡调色一起使用。 4.6.4.4二进制输入特性的选项属性 没有与二进制输入特性相关的属性。更多的关于二进制输入的信息,可参考标准特性部分。 另外的关于选项属性的信息,参考所有特性的选项属性部分的内容。 4.6.4.5内存特性的选项属性 下表列出的属性与内存特性相关,更多的关于内存特性的信息,参考标准特性部分。 属性名称 属性参数 注解 *MemConfigKB 一对(PAIR)数字型值,意指总的及打印机驻留的可用的内存,以千字节(Kilobytes)为单位。例如,PAIR(1024,450)意指总数为1024KB,450KB可用,具有一个GPD产生的选项名称“1024KB” 可选。参考描述打印机内存配置  *MemConfigMB 一对(PAIR)数字型值,意指总的及打印机驻留的可用的内存,以MB(megabytes)为单位。如,PAIR(2,1)意指总共2Mb,1Mb可用,具有GPD产生的选项名称“2MB” 可选。参考描述打印机内存配置  *MemoryConfigKB 一对(PAIR)数字型值,意指总的及打印机驻留的可用的内存,以千字节(Kilobytes)为单位。例如,PAIR(1024,450)意指总数为1024KB,450KB可用。 可选。参考描述打印机内存配置部分  相关实例,可以参考GPD实例文件。 更多的关于使用这些属性的信息及一些实例,可以参考描述打印机内存配置部分的内容。 关于附加的选项属性,参考所有特性的选项部分内容。 4.6.4.6二进制输出特性的选项属性 尽管对一个专用的输出二进制来说,包含*OutputOrderReversed?条目不是一般的情况,但没有与二进制输出特性相关的属性。更多的关于二进制特性的信息,参考标准特性部分。 关于附加的选项属性,参考所有特性的选项属性部分的内容。 4.6.4.7纸张尺寸特性的选项属性 下表列出了与纸张尺寸特性相关的属性,更多的关于纸张尺寸特性的信息,参考标准特性部分。 注意:所有的对下边的属性的纸张尺寸说明,都必须按竖向方向来表示,即使一些属性被用于描述不同的方向,如横向。 属性名称 属性参数 注解 *BottomMargin 数字型值,表示允许的最小的底端页边值,以x基本单位,对用户指定的纸张尺寸相关的CUSTOMSIZE选项值,其值与底端的物理页面相关 可选,如果没有被指定,默认值是0。只与CUSTOMSIZE选项一起使用,假定为竖向方向,参考指定纸张尺寸部分的内容  *CenterPrintable? TRUE或FALSE,意指由*MaxPrintableWidth指定的值在中间位置 可选。如果没有被指定,可打印区域在由*MinLeftMargin指定的右边位置。只与CUSTOMSIZE选项一起使用,参考指定纸张尺寸部分的内容  *CursorOrigin 一对数字型值,表示以基本单位的光标原点,这时PAIR(0,0)是左上角值,对CUSTOMSIZE选项是可以变化的,用*CustCursorOriginX和*CustCursorOriginY指定这些值 可选。如果没有被指定,默认值是PAIR(0,0)。Unidrv假定光标原点,相对于打印机,对不同的纸张尺寸是一个常量  *CustCursorOriginX CUSTOMSIZE参数表达式,用于创建*CursorOrigin的x轴索引的值 可选,只在CUSTOMSIZE选项时使用,参考指定纸张尺寸  *CustcursorOriginY CUSTOMSIZE参数表达式,用于创建*CursorOrigin的y轴索引的值 可选,只在CUSTOMSIZE选项时使用,参考指定纸张尺寸部分的内容  *CustPrintableOriginX CUSTOMSIZE参数表达式,用于创建*PrintableOrigin的x轴索引的值 可选,只在CUSTOMSIZE选项时使用,参考指定纸张尺寸部分的内容  *CustPrintableOriginY CUSTOMSIZE参数表达式,用于创建*PrintalbeOrigin的y轴索引的值 可选,只在CUSTOMSIZE选项时使用,参考指定纸张尺寸部分的内容  *CustPrintableSizeX CUSTOMSIZE参数表达式,用于创建*PrintableArea的x值的一个值 可选,只在CUSTOMSIZE选项时使用,参考指定纸张尺寸部分的内容  *CustPrintableSizeY CUSTOMSIZE参数表达式,用于创建*PrintableArea的y值的一个值 可选,只在CUSTOMSIZE选项时使用,参考指定纸张尺寸部分的内容  *MaxSize 一对数字型值,表示可允许的最大的页面长度(x)和高度(y)值,对用户专用的纸张尺寸的CUSTOMSIZE选项,用基本单位 对CUSTOMSIZE选项是必需的,假定为竖向方向。参考指定纸张尺寸部分部分的内容  *MaxPrintableWidth 数字型值,表示最大的可打印宽度,对用户专用的纸张尺寸的CUSTOMSIZE选项,用基本单位用x轴基本单位 对CUSTOMSIZE选项是必需的,假定为竖向方向。参考指定纸张尺寸部分的内容  *MinLeftMargin 数字型值,表示允许的最小的左边页边,对用户指定的纸张尺寸相关的CUSTOMSIZE选项用x基本单位,其值与左边的物理页面相关 可选。如果没有被指定,默认值是0,只与CUSTOMSIZE选项时使用,假定为PORTAIT方向。参考指定纸张尺寸部分的内容  *MinSize 一对数字型值,表示允许的最小的页面长度(x)和宽度(y)值,对用户指定纸张尺寸与CUSTOMSIZE相关的选项用标准单 对CUTOMESIZE选项是必需的,假定为竖向方向。参考指定纸张尺寸部分的内容  *PageDimensions 一对数字型值,表示页面的长度(x)及高度(y)值,对任何关于纸张尺寸的定制选项都用基本单位 只用于厂商定义的纸张尺寸。参考指定纸张尺寸部分的内容  *PageProtectMem 数字型值,表示打印机内存的总量,以KB为单位,保护页面时是必需的 如果指定了页面保护特性时是必需的,参考指定纸张尺寸部分的内容  *PrintableArea 一对数字型值,表示x及y平面的长度的可打印页面的长度,用基本单位。 对除CUSTOMSIZE之外的所有纸张尺选项都必需。参考指定页面尺寸部分内容  *printableOrigin 一对数字型值,表示可打印区域的原点,相对于纸张的左上角,用基本单位 对除CUSTOMSIZE之外的所有纸张尺选项都必需。可以用*CustPrintableOriginX及*CustPrintableOriginY来指定这些值  *RotateSize? TRUE或者FALSE,指明是否因为纸张(通常是信封纸张)是单面送纸方式,因此Unidrv应当旋转页面的维数 可选的。如果没有被指定,默认值是FALSE。可以和纸张尺寸特性的任何标准选项一起使用,除过CUSTOMESIZE  *TopMargin 数字型值,表示允许的最小的页面顶端页边,对特定用户的与纸张尺寸相关的CUSTOMESIZE用y轴基本单位。其值相对于顶端的物理页边 可选。如果没有被指定,默认值为零,只与CUSTOMESIZE选项一起使用,假定为竖直方向打印,可以参考指定纸张尺寸部分的内容。  相关实例,可以参考GPD实例文件。 CUSTOMSIZE参数表达式 CUSTOMESIZE参数表达式是一个命令字符串的约束格式。文本字符串是不被允许的。 在表达式的ArgumentType区段,下边的约束应当被应用: 唯一允许的ArgumentType值是%d。 括在一起的值区间是不被允许。 在表达式的StandardVariableExpression区段,下边的约束被应用: 只有PhysPaperWidth及PhysPaperLength标准变量可以被使用。 Max_Repeat操作符不被允许。 下面是一些表达式的实例: *CustCursorOgiginX:%d{((PhysPaperWidth-14040)/2)+300} *CustCursorOriginY:%d{180} *CustPrintableOriginX:%d{300} *CustPrintableOriginY:%d{300} *CustPrintableSizeX:%d{PhysPaperWidth-600} *CustPrintableSizeY:%d{PhysPaperLength-600} 4.6.4.8精度特性的选项属性 下表列出的与精度特性相关的属性。更多的关于精度特性的信息,请参考标准特性部分的内容。 属性名称 属性参数 注解 *DPI 一对数字型值,设置了打印机精度的x及y值,以每英寸所含的点数为单位 必需的。X和y的值必须等于*TextDPI中的x和y值,或者它们必须等于*TextDPI的x值和y值分别二等分相除的结果。例如,如果*TextDPI是PAIR(300,300),则*DPI的值可以是PAIR(300,300),PAIR(150,150)或者PAIR(75,75),但是不能为PAIR(100,100)  *MinStripBlankPiexels 数字型值,表示Unidrv在剥离所有的空白字符之前,在一个扫描行中应当遇到的最小数字的空白字节数 可选。如果没有被指定,默认值是0。该属性只在*StripBlanks条目指定为ENCLOSED时相关。参考光栅数据发送特性部分  *PinsPerLogPass 数字型值,表示一个逻辑传送的打印头中包含的最小的打印行数。必须是多个*PinsPerPhysPass,因为每一个逻辑传送是由一个或多个物理传送构成的 可选。如果没有被指定,默认值是1。如果打印机交替执行时是必需的。需要在跨一系列行时多个打印头的传递,以打印所有的扫描行。  *PinsPerPhysPass 数字型值,表示打印头在页面上移动时被打印的扫描行的数量。必须是1或8的倍数 可选。如果没有被指定,默认值是1  *RequireUniDir? TRUE或FALSE,指明是否指定的精度需要单一方向的打印被允许 可选。如果没有被指定,默认值是FALSE。  *SpotDiameter 数字型值,表示一个点的直径大小,作为一个像素大小的百分比,由*DPI为精度指定的 必需的。如100,指点的直径等于像素大小,200,指点的直径是像素大小的两倍,50,指点的直径是像素大小的四倍  *TextDPI 对值(PAIR)或数字型值,表示打印机的文本精度的x及y值,以每英寸的点数(DPI)为单位 必需。参考*DPI注解。该精度用于绘制字体和向量图形   相关实例,可以参考GPD实例文件。 关于附加选项的属性,参考所有特性的选项属性部分。也可参考控制图象质量部分。 4.6.5选项限制 有些时候,需要限制用户安装或选择与打印机特性相关的某些打印机选项的能力。可以指定以下类型的选项限制: 可以限制一个选项,这样,如果一个冲突选项已经被选择,它自身就不能再被选择。这些限制叫做选择限制。 可以限制一个可安装的选项,这样,如果一个冲突的选项已经被安装,则它自身就不能再被安装。这些限制叫做安装限制。 可以限制一个选项,这样,如果一个冲突的可安装的选项已经被安装,则它自身就不能再被选择,或者如果必需的一个可安装选项没有被安装,自身也不能被选择。这些限制称为选择与安装之间的限制。 驱动程序的用户接口代码在用户选择了打印机属性表单的对话框上的OK键时实施限制。如果选择了不有效的选项组合,用户便给了这样一个选择,即或者是通过修改属情表单页面来改正错误,或者是让驱动程序基于特性冲突优先级自动执行错误改正。 4.6.5.1选择限制 经常,对不同打印机特征的一些选项不能同时被选择。例如,如果选择了一个信封送纸器,则非信封尺寸的纸张,如信纸尺寸或A4尺寸的纸张就不能被选择。 为指定不能同时被选择的打印机选项的组合,可以用*InvalidCombination或*Constraint条目。如果一个用户企图选择已指定为无效的选项组合,Unidrv将会拒绝这种选择。 *InvalidCombination条目有下面的格式: *InvalidCombination:LIST(FeatureName.OptionName,FeatureName.OptionName,……) 这里,FeatureName是一个特性的名称,OptionName是与特性相关的一个选项的名称。 在单一*InvalidCombination条目中列出的选项指明一套不能用于组合的选项。例如,下面的条目指定CMYK色彩模式不能与普通纸张及720DPI一起使用。 *InvalidCombination:LIST(Resolution.720dpi,MediaType.Plain,ColorMode.CMYK) 所有的*InvalidCombination条目必须被置于GPD文件的根目录位置(即,不在括号内)。在一个条目中包括的选项数则是不限制的。 如果只是想指明两个选项之间的非法关系组合,可以用一个*Constraints条目。它的格式是: *Constraints:FeaturneName.OptionName 这里,FeatureName是一个特性的名称而OptionName是与一个特性相关的选项名称。一个*Constraints条目必须置于*Option条目之中。例如,为了指明信纸大小及A4大小的纸张不能使用信封送纸器,可以用下列的条目: *Feature:InputBin { *Option:ENVFEED { *Constraints:PaperSize.Letter *Constraints:PaperSize.A4 } } 或者,等同于: *Feature:InpubBin { *Option:ENVFEED { *Constraints:LIST(PaperSize.Letter,PaperSize.A4) } } 这些实例指定如一个用户想选择一个信封送纸器及信封信纸大小的纸张或者信封送纸器A4大小的纸张,Unidrv将拒绝这些选择。 4.6.5.2安装限制 有些时候,一些专用的可安装的打印机选项不能被同时安装。例如,也不能同时安装信封送纸器及加倍单元。 为了指定可以同步被安装的打印机选项的组合,要使用*InvalidInstallableCombination条目。 *InvalisdInstallableCombination条目有下面这样的格式: *InvalidInstallableCombinations:LIST(FeatureName.OptionName,FeatureName.OptionName,……) 这里,FeatureName是一个特性的名称而OptionName是与特性相关的一个选项的名称。这一个列表也可以包含特性及选项,这种情况时,就不能包含句号及选项名称。 在单一*InvalidInstallableCombination条目中列出的特性及选项表示一套可以用于组合的特性及选项。例如,下面的条目指定一个信封送纸器不能和双倍单元同时安装。 *InvalidInstallableCombination:LIST(InputBin,ENVFEED,Duplex) 所有的*InvalidInstallationCombination条目必须放置于GPD文件的根目录(即,不放置于括号中),包含于条目中的特性及选项的数目则没有限制。 如果一个特性或选项被包含于*InvalidInstallationCombination条目,则该特性或选项的*Installable?属性必须被设置为TRUE。 4.6.5.3选择及安装之间的限制 有时,有必要指定一定的选项在有其他选项安装时不能被选择,或者一定的选项在其他选项没有安装时不能被选择。例如,如果一个打印机没有安装大纸张送纸托架格式,则一个用户就不能选择小报式纸张。 为指定选择与一些具有其他选项安装状态信息的选项之间的关系,需要应用*InstalledConstraints及*NotInstalledConstraints条目,他们的格式如下: *InstalledConstraints:FeatureName.OptionName *NotInstalledConstraints:FeatureName.OptionName 这里,FatureName是一个特性的名称,OptionName是与该特性相关的一个选项的名称,如果参数是一个特性,则不能包括句点及选项名称。 一个*InstalledConstraints或*NotInstalledConstraints条目必须被置于一个*Feature或*Option条目中。例如,为指明或一个打印机没有安装大纸张送纸托架格式,则一个用户就不能选择小报式纸张,可以用下面的条目: *Feature:InputBin { *Option:LARGEFMT { Installable?:TRUE NotInstalledConstraints:PaperSize.TABLOID } } 如果一个特性或选项包括*InstalledConstraints或者*NotInstalledConstraints条目,该特性或选项的*Installable?属性必须被设置为TRUE。 4.7打印机字体描述 GPD语言提供一些条目,这些条目描述硬件驻留字体、Cartridge字体(Cartridge Font)、TrueType字体替换为相似的硬件驻留字体或Cartridge字体。 可以选择性地提供定制代码来处理软字体。关于这些定制代码的信息,参考第7章定制的字体管理中的定制微软打印机驱动程序部分的内容。 4.7.1硬件驻留字体 如果打印机包含硬件驻留字体,必须在.ufm或.ifi文件中提供这些字体规格的说明。为创建一个.ufm或.ifi文件,使用微软小驱动程序开发工具。 每一个硬件驻留字体都在一个单独的.ufm或.ifi文件中描述,为使这些文件对Unidrv可用,需要下面这样去做: 在打印机的资源DLL中,通过使用RC_UFM资源类型指定.ufm文件,并在.ifi文件中用RC_FONT资源类型来指定 在打印机的GPD文件中,用*ResourceDLL特性来指定资源DLL的名称 在打印机的GPD文件中,用*DeviceFonts条目来指定与在资源DLL中的RC_UFM及RC_FONT条目相关的资源标识符 *DeviceFonts条目的格式如下: *DeviceFonts:LIST(FontResourceID,FontResourceID,……) 这里,FontResourceID是与一个.ufm文件相关的RC_UFM资源标识符,或者与.ifi文件相关的RC_FONT资源标识符。 下面是一个实例: *% Assume that RC_Font_xxx id’s are references to *% value macros defined by the GPD file creator. *DeviceFonts:LIST(=RC_FONT_COURIER10, =RC_FONT_ARIALR, + =RC_FONT_ARIALI, =RC_FONT_ARIALB, + =RC_FONT_ARIALBI, =RC_FONT_TIMESNRR, + =RC_FONT_TIMESNRI, =RC_FONT_TIMESNRB, + =RC_FONT_TIMESNRBI) 可以在一个Unidrv小驱动程序中包括几个*DeviceFonts条目。GPD解析器连接多个条目并使所列出的字体对打印机特性的所有配置都可用。如果需要指定一些字体只对一定的配置可用,可以在条件语句中使用*DeviceFonts条目。 4.7.2Cartridge字体 如果打印机接受Cartridge字体,这些字体可以被*FontCartridge 这一GPD文件条目描述。这一条目格式是: *FontCartridge:CartridgeName{FontCartidgeAttributes} 这里,Cartridgename是一个文本字符串,表示字体的名称,而FontCartridgeAttributes则是一个或多个Cartridge字体属性的集合。 可以选择地,由Cartridge字体提供的字体可以使用Undirv字体格式文件(.uff文件)来指定。通常,最常见的由Cartridge字体提供的都是在GPD文件中描述的,而较少的不太常用的字体则用.uff文件指定。 4.7.2.1Cartridge字体属性 下表包括有可以包含于*FontCartridge GPD文件包目的属性。 字体名称 属性参数 注解 *CartridgeName 文本字符串,表示Cartridge字体的名称 必需的。除非指定了*rcCartridgeNameID  *Fonts RC_UFM及RC_FONT资源标识符的列表,表示包含于一个Cartridge中的可以用于横向方向的字体 至少一个*Fonts,*PortraitFonts或者*LandscapeFonts必需被指定。字体资源标识符应当从1开始连续计数  *LandscapeFonts RC_UFM及RC_FONT资源标识符的列表,表示包含于一个Cartridge中的可以用于横向的字体 至少一个*Fonts,*PortraitFonts或者*LandscapeFonts必须被指定。字体资源标识符应当从1开始连续计数  *PortraitFonts RC_UFM及RC_FONT资源标识符的列表,表示包含于一个Cartridge中的可以用于竖向方向的字体 至少一个*Fonts,*PortraitFonts或者*LandscapeFonts必须被指定。字体资源标识符应当从1开始连续计数  *rcCartridgeNameID 一个字符串资源的资源标识符,表示Cartridge字体的名称 必需的。除非*CartridgeName被指定   4.7.3字体替换 对提供硬件驻留字体或Cartridge字体的打印机来说,可以指定一个字体替换表。通过提供一个字体替换表,可以指定硬件驻留或Cartridge字体来替换那些必须被下载的TrueType字体。当Unidrv收到以TrueType字体的文本,它首先检查替换表是否包含用硬件驻留字体来替换该TrurType字体,如果Unidrv发现一个可替换的硬件驻留字体,并且该字体的规格(如字符集、深浅、斜体、方向等等)是兼容的,则驻留字体将被使用。 可以通过使用一系列的*TTFS条目来创建一个默认的替换表,其中每一个条目的格式为: TTFS:FontName { *TTFontName:”TTFontNameString” DevFontName:”DeviceFontNameString” } 这时,FontName是一个指定条目名称的符号。TTFontNameString是一个文本字符串标识将要被替换的TrueType字体,DeviceFontNameString是一个标识将要用到的硬件驻留字体或Cartridge字体的文本字符串。以下是一个实例: *TTFS:Arial { *TTFontName:”Arial” *DevFontName:”Arial” } *TTFS:TNR { *TTFontName:”Times New Roman” *DevFontName:”Times New Roman” } *TTFS:Curriernew { *TTFontName:”Courier New” *DevFontName:”Courier New” } 如果有重复的具有相同FontName值的*TTFS条目,解析器后边读到的一个将取代前一个。 指定的替换表是一个默认表,因为Unidrv允许用户修改该替换。 所有的*TTFS条目必须置于GPD文件的根目录层次(即不放在括号里)。为了控制是否默认状态允许字体替换,可以用*TTFSEnabled?条目。这一条目的格式为: *TTFSEnabled?:BooleanValue 这里,BooleanValue是TRUE或FALSE。如果BooleanValue是TRUE,Unidrv允许字体替换。如果是FALSE,或者在GPD文件中没有包括*TTFSEnalbled?条目,则Unidrv不允许字体替换,直到用户允许其替换为止。 *TTFSEnalbe?条目可以重置,但*TTF条目不能。(更多的关于可重置条目的信息,参考第10章在*Switch,*Case,*Default语句中包含什么部分的内容) 默认的TrueType字体替换 TrueType字体替换的默认表在一个称为ttfsub.gpd文件里提供,为使用它,可以在GPD文件的根目录层次(不包含于括号中)加入下面的条目: *Include: ttfsub.gpd 另外,该文件必须被安装。更多的信息,可以参考打印机INF文件安装部分的内容。 4.8条件语句 GPD语言提供一个类似于C语言的条件语句以允许描述一些打印机配置上的打印机属性的依赖性。例如,一页的页边及光标原点可以依赖于页面的方向。*Swith及*Case语句允许表达这些依赖性。这些语句的格式如下: *Switch FeatureName { *Case Option1_Name { } *Case Option2_Name { } etc. *Case OptionN_Name { } *Default } FeaturnName必须是在GPD文件中用*Feature条目指定的特性的名称。选项的名称则必须是与该指定的特性名称相关的选项名。 为表达页面页边及光标原点依赖于页面的方向的选择情况,以下的条目必须用到: *Feature:Orientation { *DefaultOption:Portrait *Option:Portrait { *Name:”Portrait” *rcIconID:=RC+ICON_PORTRAIT } *Option:Landscape_CC90 { *Name:”Landscape” *rcIconID:=RC+ICON_LANDSCAPE } } *Feature:PaperSize { *DefaultOption:Letter *Option:Letter { *Name:”Letter 8.5 x 11 inch” *switch:Orientation { *case:Protrait { *PrintableArea:PAIR(4800,6324) *PrintableOrigin:PAIR(150,150) *CursorOrigin:PAIR(150,100) } *case:Landscape_CC90 { *PrintableArea:PAIR(4860,6360) *PrintableOrigin:PAIR(120,120) *CursorOrigin:PAIR(100,6480) } } } } 在这一个实例中,打印机纸张大小(PaperSize)的特性依赖于打印机方向特性的选择选项。 如果不在*Case语句参数中列出所有的特性选项,可以包含一个*Default语句,如同在C语言中一样。如果不包括所有的选项,也不包括*Default语句,必须在GPD文件的别处(在*Switch语句前)估算相关的属性(如在实例中的*PrintableArea,*PrintalbeOring及*CorsorOrigin)。 指定多个依赖关系 可以在*Case和*Default语句中包括*Switch语句。这样,允许指定多个依赖关系,如下: *Feature:feature1{*Options:optina{…}*Option:optionB{…}} *Feature:feature2{*Options:optina{…}*Option:optionB{…}} *Feature:feature3 {*Option:optionE {*Switch:feature1 {*Case:optionA {*Swtich:feature2 {*Case:optionD {AttributesX:ValueX} *Default {AttributeX:ValueY} } } *Default {AttributeX:ValueZ} } } *Option:optionF{…} } 在这个例子中,AttributeX,属于feature3的optionE,依赖于feature1和feature2。 如果用户已经为feature1选择了optionA,为feature2选择了optionD,为feature3选择了optionE,则AttributesX被设置于ValueX。 如果用户已经为feature1选择了optionA,为feature2选择了optionC,为feature3选择了optionE,则attributesX被设置于ValueY。 如果用户已经为feature1选择了optionB,为feature3选择了optionE,则AttributesX被设置于ValueZ,设置与Feature2无关。 以下的规则在指定多个依赖关系时是适用的: 多个依赖关系必须在一个*Switch条目的范围中指定。用上边的实例,如,不能使用*Switch条目来指明feature3依赖于feature1,接着,非嵌套的*Switch语句指明feature3依赖于feature2。 在每一个嵌套的*Switch条目中,不能超过两次指定相同的特性名称。 何处放置一个*Swicth语句 在一个GPD文件中,可以将*Switch语句放置于以下位置。 在一个*Option语句中 在一个*Feature语句中 在一个*Case语句中 在一个*Default语句中 在一个文件最顶层(即,不是在一系列括号中) 在*Switch,*Case及*Default语句中都放置什么 在一个*Switch条目中,只能放置*Case条目及*Default条目。 那些可以被置于*Case或*Default条目中的GPD文件条目被看作为可重新置位的(relocatable)条目。下面类型的GPD条目都是可重新置位的。 大多数的打印机属性,除过根层次属性(除非*Switch条目在根层次——不是包含在括号中,一般属性必须在其前置EXTERN_GLOBAL) 嵌套的*Switch条目,它允许指定多个依赖性 *Command条目 *TTFSEnalbed?,它允许字体替换 下面类型的GPD条目是不能重新置位的: 根层次的属性 指定字体替换的*TTFS条目 *Constraints,*InvalidCombination,*InvalidInstallableCombination,*NotInstalledConstraints等这些定义无效选项组合的条目,如在选项限制部分中描述的。 *Features及*Option条目(尽管特性属性及选项属性是可以重置的) 一个确定是否条目被置于*Case语句的正确位置的方法是删除所有的*Switch及*Case语句,如果在*Case语句中的条目是正确的,则在*Switch及*Case语句被删除之后仍然是正确的。 4.9压缩光栅数据 Unidrv用下面三种格式支持光栅数据压缩: TIFF version 4.0(Tagged Image File Format) DRC(Delta-Row compression) FE-RLE(Far East Run-Length Encoding) 这些方法都在HP PCL 5打印机语言技术参考中被定义。 可以在GPD文件中包含条目以指明打印机支持那一种压缩格式。也可以提供自已的压缩算法。 关于光栅数据压缩的更多信息,可以参考下面的主题: ■4.9.1使用Unidrv支持的压缩 ■4.9.2使用定制的压缩 4.9.1使用Unidrv支持的压缩 如果在GPD文件中包含一个CmdEnableTIFF4命令条目,Unidrv将使用TIFF 4.0压缩。 如果在GPD文件中包含一个CmdEnableDRC命令条目,Unidrv将使用DRV压缩。 如果在GPD文件中包含一个CmdEnableFE_RLE命令条目,Unidrv将使用FE-RLE压缩。 如果打印机支持更多的这些压缩方法,可以对每一个支持的方法包含一个命令条目。对每一个扫描行,Unidrv将试用每种压缩算法并选择会产生最好压缩结果的压缩方法。(也可以选择一种定制的压缩方法,参考使用定制压缩方法部分)。当Unidrv发现一个最好的压缩算法,它压缩扫描行的数据,然后在压缩的数据之后,将合适的命令条目指定的命令发送到打印机。 如果指定一个CmdDisableCompression命令条目,则不管哪些压缩方法可用,Unidrv在遇到没有压缩的数据块小于它的压缩格式时,将临时禁止发送被压缩的数据。 为限制不必要的计算,当压缩算法不可能产生一个可用的结果的时,不应使用这种算法。 对许多的打印机,允许接收或禁止接收压缩数据可以通过在数据块外发送命令字符串来实现。当为这些打印机指定CmdEnableTIFF4,CmdEnableDRC,CmdEnableFE_RLE和CmdDisableCompression条目时,应当包括一个命令字符串。 对一些打印机(通常是Far East打印机),压缩选择命令被嵌在光栅数据中并同CmdSendBlockData命令一起发送。当为这些打印机指定CmdEnableTIFF4,CmdEnbleDRC或CmdEnableFE_RLE条目时,不包括一个命令字符串。相反,应当指定一个空的加引号的字符串以表示命令,这将告诉Unidrv使用压缩但是不向它发送单独的命令允许使用压缩。对这些打印机,只有一种压缩算法可用。CmdDisableCompression条目是不需要的,因为在这种情况下,Unidrv没有方法去关掉压缩。 更多的关于CmdEnableTIFF4,CmdEnableDRC,CmdEnableFE_RLE及CmdDisableCompression条目的信息,可以参考光栅数据压缩命令部分。 关于更多的关于CmdSendBlockData的信息,可以参考光栅数据发送命令部分。 4.9.2使用定制的压缩 如果想提供一种定制的压缩算法,应当包括一个CmdEnableOEMComp命令条目以指定允许压缩方法的命令。如果打印机禁止压缩,可以选择性地包括一个CmdDisableCompression条目以指定禁止压缩的命令。也必须提供一个绘制插件,以实现IPrintOemUni::Compression方法。 如果提供一个定制的压缩算法,可以允许使用Unidrv支持的算法。对每一个扫描行,Unidrv将试验每一种压缩算法并选择产生最好压缩结果的算法(更多的关于Unidrv支持的算法,参考使用Unidrv支持的压缩部分的内容)。当Unidrv找出最好的压缩算法,它压缩扫描的行数据,然后,在压缩的数据之后,将合适的命令条目指定的命令发送到打印机。 更多的关于CmdEnableOEMComp及CmdDisableCompression条目的信息,可以参考光栅数据压缩命令部分。 更多的关于定制压缩的信息,参考第7章定制数据流压缩的定制微软打印机驱动程序部分的内容。 4.10过滤光栅数据 如果想提供定制的后处理(Post-Processing)的扫描行的光栅数据流,并在它被假脱处理之前提供,可以通过在绘制插件中实现IPrintOemUni::FilterGraphics方法。没有与这一Unidrv特性相关的GPD文件条目。 更多的信息,可参考第7章定制数据流压缩中的定制微软打印机驱动程序部分的内容。 4.11处理色彩格式 一个打印机支持的每一种色彩格式被作为一个色彩模式特性的选项来指定。通过使用色彩模式特性的选项属性,可以描述打印机能接受的每一种色彩格式。下表列出了Unidrv可以处理的色彩数据格式。 设备中的色彩平面数(*DevNumOfPlanes) 设备中的每像素的位数的数量(*DevBPP) 1 1(黑与白)  1 8  1 24  3 1(CMY and RGB)  4 1(CMYK)   对这些格式,Unidrv可以转换DIB数据为合适的格式并将它发送到打印机。(在这一数据上可执行的过渡调色操作将在用Unidrv过渡调色部分讲述到)。 如果打印机支持没有列在上表中的色彩格式,则必须采用如下做法: 设置*DevnumOfPlanes及*DevBPP属性为零。这样做可以防止Unidrv发送DIB数据到打印机。 提供一个实现IPrintOemUni::ImageProcessing方法的绘制插件。 IPrintOemUni::ImageProcessing方法必须执行以下的操作: 转换DIB数据为打印机的色彩格式 对数据执行过渡调色的操作 发送数据到打印机假脱机 更多的关于提供IPrintOemUni::ImageProcessing函数的信息,可参考第7章定制色彩格式中的定制微软打印机驱动程序部分。 绘制高质量图象 对每一种色彩格式,可以指定打印机硬件可以接受的每像素的位数以及在创建DIB时想让Unidrv使用的每像素的位数。这些值被分别用*DevBPP及*DrvBPP属性来指定。有时, 对打印机绘制比打印机能处理的更高数量的每像素位数的位图却更合人的心意(如,想创建高质量的图形)。因此,它允许指定一个*DrvBPP值,它是大于由*DevNumOfPlanes值相乘的*DevBPP的值。 例如,假设想定义一个色彩模式的选项,它可以使图像以24位/像素的位图绘制,但是又想该位图以CMYK数据送往打印机。这可以如下定义这种模式: *Feature:ColorMode { *Option:24toCMYK { *Name:”Photographic Quality” *DrvBPP:24 *DevNumOFPlanes:4 *DevBPP:1 *ColorPlaneOrder:LIST(CYAN,MAGENTA,YELLOW,BLACK) *IPCallbackID:1 } other option } 这在一个例子中,*DevBPP及*DevNumOfPlanes属性表示一个四个平面、每平面一位的CMYK格式,Unidrv可以绘制并发送给打印机。但是,在这种情况下,过渡调色操作必须在绘制图象并被打印之前执行,且必须用到小驱动程序提供的过渡调色。 4.12用Unidrv进行中间色调整 过渡调色操作可以由GDI或驱动程序软件来控制,或者通过设备硬件来控制。它所使用的类型取决于色彩的格式,下面的主题讨论了Unidrv驱动程序支持过渡调色的操作: ■4.12.1GDI提供的过渡调色 ■4.12.2小驱动程序提供的过渡调色 ■4.12.3设备提供的过渡调色 4.12.1GDI提供的过渡调色 如果一个指定的色彩格式是用于绘制图象(*DrvBPP)的每像素位数的数字,并且与打印机支持的(*DevBPP)乘以*DevNumOfPlanes相同,则过渡调色操作由GDI操作。一个例子是*DrvBPP的值是4,而打印机的*DevBPP等于1并且*DevNumOfPlanes等于4。 对这种情况,唯一允许的过渡调色的方法是那些GDI提供的方法。这些过渡调色的方法在GPD文件中通过标准的过渡调色的选项名称来表示,它们列于标准选项之下。为指定想要Unidrv支持打印机的并且是GDI支持的过渡调色方法,可以在*Option条目中为过渡调色特性指定它们的名称(过渡调色特性是一个标准的打印机特性)。 如果在GPD文件中指定了几个过渡调色的方法及色彩模式,并且如果想限制那些过渡调色的方法在那些色彩模式时可选,可以使用选项限制。 如果没有在GPD文件中指定过渡调色的选项,Unidrv使用标准的HP_PATSIZE_AUTO选项。HP_PATSIZE_AUTO将导致Unidrv使用对选择的精度及色彩模式最佳的标准的过渡调色的方法。这就使得用户可以在多种精度和色彩模式中间转换而不需要知道对任何特殊的组合的最好的过渡调色的选项。 当使用GDI提供的过渡调色能力时,可以提供小驱动程序提供的过渡调色模式。 更多的关于色彩格式的信息,可参考处理色彩格式部分。 4.12.1.1小驱动程序提供的过渡调色 如果用了GDI支持的过渡调色方法,GDI允许定制的过渡调色模式的说明。为指定定制的过渡调色模式,使用过渡调色特性的选项属性,如下: *rcHTPatternID,*HTPatternSize及*HTNumPattern属性允许描述存储于资源DLL里的过渡调色模式。过渡调色模式资源是一个三维的二进制数据的数组,以一个DWORD地址开始。可以使用下面的格式来指定,它计算正确的尺寸并提供需要的地址调整。 BYTE HTPatternResource[HTNumPatterns][(HTPatternSize.y*HTPatternSize.x+3)&~3]; 在一个用于创建资源DLL的.rc文件中,该模式可以以如下模式指定: RC_HTPATTERN LOADONCALL DISCARDABLE HALFTONE.BIN 这里,halftone.bin是一个包含过渡调色模式的文件 *HTCallbackID属性允许指明在一个绘制插件中实现IPrintOemUni::HalftonePattern方法。一个唯一的*HTCallbackID值必须为每一个IPrintOemUni::HalftonePattern方法支持的模式提供。 可以提供过渡调色模式资源,一个IPrintOemUni::HalftonePattern方法,或者都提供,如下: 如果只提供过渡调色模式,Unidrv从资源DLL中获取该模式并将它们传送到GDI。该模式不能被加密。 如果只提供IPrintOemUni::HalftonePattern方法,该方法必须给Unidrv产生并返回过渡调色模式,Unidrv将它传送到GDI。 如果想在资源DLL中置放加密的取中间模式,然后必须提供一个IPrintOemUni::HalftonePattern方法以解码该模式并将它们返回给Unidrv,Unidrv按次序将它们发送给GDI。 更多的关于过渡调色的信息。参考第7章定制过渡调色中的定制微软打印机驱动程序的方法。 4.12.2驱动程序提供的过渡调色 如果一个指定的色彩格式是用于绘制图象(*DrvBPP)的每像素位数的数量,并且大于打印机支持的(*DevBPP)乘以*DevNumOfPlanes的数量,则必须提供定制的过渡调色能力。 为提供定制的过渡调色能力,必须如下做: 提供一个实现IPrintOemUni::ImageProcessing方法的绘制插件 在GPD文件中包含一个过渡调色*Feature的条目,对每一个定制的过渡调色的方法,包括一个描述过渡调色方法的*Option条目。(不要使用任何过渡调色特性的选项属性)。 在GPD文件中包括一个色彩模式的*Feature条目。对每一个指定的色彩格式选项,如果希望IPrintOemUni::ImageProcessing方法去为该种色彩格式过渡调色,必须包括一个*IPCallbackID属性。 下面的实例定义了两种色彩格式及四种过渡调色方法: *Feature:ColorMode { *Option:ColorFormat1 { *Name:”Color Format 1” *DevBPP:1 *DevNumOfPlanes:4 *ColorPlaneOrder:LIST(CYAN,MAGENTA,YELLOW,BLACK) *DrvBPP:4 *Constraints:LIST(Halftone.CustomHalftoneMethod1, + Halftone.CustomHalftoneMethod2) } *Option:ColorFormat2 { *Name:”Color Format 2” *DevBPP:24 *DevNumOfPlanes:1 *DrvBPP:8 *IPCallbackID:100 *Constraints:LIST(Halftone.CustomHalftoneMethod1, + Halftone.CustomHalftoneMethod2) } } *Feature:Halftone `*Option:StandardHalftoneMethod1 { *Name:”Standard Halftone Method1” } *Option:StandardHalftoneMethod2 { *Name:”Standard Halftone Method2” } *Option:CustomHalftoneMethod1 { *Name:” CustomHalftoneMethod1” } *Option:CustomHalftoneMethod2 { *Name:” CustomHalftoneMethod2” } 在实例中,ColorFormat1及ColorFormat2的色彩模式选项表示了Unidrv可以处理的色彩格式,如在处理色彩格式部分所讲述的。对ColorFormat2来说,一个*IPCallbackID属性已经被指定,如果打印机用户选择了ColorFormat2作为色彩的格式,Unidrv将调用打印机的IPrintOemUni::ImageProcessing COM方法去处理过渡调色。该方法的一个参数是一个指定表示当前选择的过渡调色方法的字符串名称的指针。 在该例中,有两个标准的过渡调色方法及两个定制的过渡调色方法是可用的。例子中使用了选项限制来指定Unidrv应当选取那一个过渡调色方法来允许用户对每一种色彩格式选择的支持。 更多的关于过渡调色的信息,参考第7章定制过渡调色中的定制微软打印机驱动程序部分。 4.12.3设备提供的过渡调色 如果打印机提供在内部过渡调色能力,小驱动程序必须指定Unidrv发送到打印机以激活这些能力的命令。对每一种打印机支持的过渡调色选项,GPD文件过渡调色的*Feature条目对每一个设备提供的过渡调色选项必须包含*Command条目,如下: *Feature:Halftone { *Option:CustomHalftoneMethod1 { *Name:”Custom halftone Method 1” *Command:CmdSelect:”<Printer command string>” } *Option:CustomHalftoneMethod2 { *Name:”Custom Halftone Method2” *Command:CmdSelect:”<printer command string>” } } 色彩模式特性条目必须被指定,并且它们必须包括*DevBPP及*DevNumOfPlans条目描述打印机期望的输入格式。 4.13控制图像质量 Unidrv的用户接口提供一套三个单选按钮(Radio button)以允许用户对打印作业选择“草图(draft)”“较好(better)”“最好(best)”三种图象质量。草图质量加强了打印机对图象质量的速度,而“最好”的质量级别则相反。 这些单选钮的目的是允许用户很容易地选择获得希望质量时必需的特性选项,没有必要单独选择必需的选项的必要。 当一个单选按钮按下时Unidrv应当选择的选项被指定在?打印机的GPD文件中,GPD语言定义了下面一个条目: *DraftQualitySetting *BetterQualitySettings *BestQualitySettings 每一个条目都是与其中一个单选钮相关的,并且每一个条目接受一个选项的列表。当一个用户选择了相关的按钮,Unidrv检查列表并设置指定的选项。 每一个设备条目的格式如下: *XXXXQualitySettings:LIST(FeatureName.Optionname,FeatureName.OptionName,FeatureName.OptionName,……) 这里,每一个FeatureName与*Feature条目相关,Optionname是与一个特性的*Option条目相关的选项名称。一个空的列表可以引起相关的单选钮变灰(不可选)。 一个附加的,必需的条目指定了默认的图象质量。其格式如下: *DefaultQuality:DefaultQuality 这里,DefaultQuality可以是DRAFTQUALITY,BETTERQUALITY或BESTQUALITY。 这些GPD文件条目可以与任何色彩模式或媒体类型特性的选项相关。通常,它们被置于条件语句中,如下面例子所示。 *Switch:ColorMode{ *Case:Mono{ *BestQualitySettings:LIST(ColorMode.Mono, Resolution.Option1, TextQuality.Option3) *BestQualitySettings:LIST(ColorMode.Mono, Resolution.Option1, TextQuality.Option1) *BestQualitySettings:LIST(ColorMode.Mono, Resolution.Option2, TextQuality.Option2) *DefaultQuality:BETTERQUALITY} *Default:{ *BestQualitySettings:LIST(ColorMode.24bpp, Resolution.Option2 TextQuality.Option3) *BestQualitySettings:LIST(ColorMode.Color, Resolution.Option2 TextQuality.Option1) *BestQualitySettings:LIST(ColorMode.Color, Resolution.Option2 TextQuality.Option2) *DefaultQuality:BETTERQUALITY}} 如在例中所示,一个好的策略是为单一色彩模式指定一个*Case条目为,然后对所有的多色彩模式用*Default条目。这是由于Unidrv的页面设置(PageSetup)的页面表单属性给用户提供了两个选择——色彩或非色彩打印。如果用例子中的格式,当用户选择色彩打印选项时,Unidrv显示质量选择的按钮。 下面是一个更复杂的例子,它将图象质量绑定于所有的色彩模式及媒体类型: *switch:Colormode{ *case:Mono{ *switch:MediaType{ *case:CLAYCOATED{ *DraftQualitySettings:LIST(Option List) *BetterQualitySettings:LIST(Option List) *BestQualitySettings:LIST(Option List) *DefaultQuality:BESTQUALITY } *case:GLOSSY{ *DraftQualitySettings:LIST(Option List) *BetterQualitySettings:LIST(Option List) *BestQualitySettings:LIST(Option List) *DefaultQuality:BESTQUALITY } *default: *DraftQualitySettings:LIST(Option List) *BetterQualitySettings:LIST(Option List) *BestQualitySettings:LIST(Option List) *DefaultQuality:BESTQUALITY } *default:{ *switch:MediaType{ *case:CLAYCOATED{ *DraftQualitySettings:LIST(Option List) *BetterQualitySettings:LIST(Option List) *BestQualitySettings:LIST(Option List) *DefaultQuality:BESTQUALITY } *case:GLOSSY{ *DraftQualitySettings:LIST(Option List) *BetterQualitySettings:LIST(Option List) *BestQualitySettings:LIST(Option List) *DefaultQuality:BESTQUALITY } *default:{ *DraftQualitySettings:LIST(Option List) *BetterQualitySettings:LIST(Option List) *BestQualitySettings:LIST(Option List) *DefaultQuality:BESTQUALITY }}} } 当使用质量设置GPD条目时,必须遵守下面的规则: 必须使用所有的四个条目。允许指定一个空的选项列表,并且导致相关的单选按钮变灰。 所有的四个条目必须为所有的色彩模式及媒体类型组合而指定。例子中在一个条件语句中使用了一个*Default条目以达到这一目标。 在一个质量设置的条目的选项列表中不能与在选项限制中指定的相互矛盾。 包括一个选项列表的选项不能改变选定的媒体类型。同样,如果它被接受,例如,为最好质量设置色彩模式为24bits/pixel,为较好质量设置8bits/pixel,为草图质量设置4bits/pixel,设置为1bits/pixel(单色模式)是不能接受的。 如果一个特性被包括于指定质量设置的条件语句中,解析器设置特性的*UpdateQualityMacro?属性为TRUE(参考特性属性部分)。 4.14处理可安装的特性及选项 一些打印机的特性或选项也许可以被安装。例如,一个打印机可以接受一个可选的信封送纸器,它能被或者不被当前选用。这一从封送纸器必须在一个GPD文件中以两种方式描述: 作为一个二进制输入特性的选项。 作为一个可安装的“特性”(尽管它实际是一个选项),它允许用户指明它是否被实际安装着。 第一,为指定信封送纸器,与一个自动的送纸器,作为二进制输入特征的选项,应当使用下面的GPD条目。 *Feature:InputBin { *Name:”Input Bin” *Option:AUTO { *Name:”Automatic Feeder” *Command:CmdSelect{Command Attributes} } *Option:ENVFEED { *Name:”Envelope Feeder” *Command:CmdSelect{Command Attributes} } } 为使得信封送纸器被安装,另外的GPD条目是需要的,如下: *InstalledOptionName:”Installed” *NotInstalledOptionName:”Not installed” *Feature:InputBin { *name:”Input Bin” *Option:AUTO { *Name:”Automatic Feeder” *Command:CmdSelect{Command Attributes} } *Option:ENVFEED { *Name:”Envelope Feeder” *Command:CmdSelect{Command Attributes} *Installable?:TRUE *InstallableFeatureName:”Optional Envelope Feeder” } } 在一个信封送纸器的*Option条目中,增加了两个属性: *Installable?属性,以指明选项被安装。 *InstallableFeatureName属性以指明一个Unidrv是显示的文本字符串,这样用户可以指明选项是否被实际安装。 无论何时当*Installable?为一个特性或选项被设置为TRUE,Unidrv对显示属性表单创建一个附加的特性(注意既使可安装的项目是选项,Unidrv也会为它在属性表单中创建一个特性表示)。这一Unidrv综合的特性是由*InstallableFeatureName提供的字符串来鉴别的。该特性提供两个选项,即“已安装(Installed)”及“未安装(Not installed)”,并允许用户选择这中间的一个选项。字符串“installed”和“Not installed”是用*InstalledOptionName及*NotInstalledOptionName特性来指定的,这样如果有更合适的文本就可以修改它们。 因此,对我们的例子来说,属性表单将会包括一个二进制输入特性,并标记为“InputBin”,它包括两个选项,标记为自动送纸器“Automatic Feeder”及信封送纸器“Enveloper Feeder”。属性表单将会包括一个附加的特性,标记为可选的信封送纸器“Optional Envelope Feeder”,并有两个选项,标记为“Intalled”及“Not Installed”。用户可以只选择在“InputBin”下的“Envolope Feeder”选项或者最初就选择“Optional Envelope Feeder”下的“Installed”选项。 有时,有必要指明一些可安装选项不能被同时安装,或者一个不能安装的选项不能在一些其他可安装的选项已被安装时被选择。为处理这些情况,可以使用GPD文件中的选项限制条目来处理。 不能使用具有可选特性的*Installable?属性,而它又需要一个*DisabledFeatures条目。对这些特性,必须明确指定可选的特性具有“Installed”或“Not installed”选项。例如,假设一个打印机具有一个可选的双倍单位。如果没有安装双倍单位,则双倍打印特征(见标准特性)必须被禁止。必须定义一个“可选的双倍打印单位”特性,并具有“Installed”及“Not installed”选项。在“Not installed”*Option条目中,应当为双倍特性包含一个*DisableFeatures条目。下面的GPD条目可以被应用: *Feature:DuplexUnit { *ConfilictPriority:3 *% Make priority higher than Duplex feature *Name:”Optional Duplexing Unit” *Option:Installed { *name:”Installed” } *Option:NotInstalled { *name:”Not installed” *DisabledFeatures:LIST(Duplex) *Constraints:LIST(Duplex.LongEdge,Duplex.ShortEdge) } 但是一定要指定任一相关的选项限制,如例子中所示。 4.15指定特性和选项显示的顺序 为控制特性及选项在Unidrv产生的属性表单页面上显示的顺序,应当在GPD文件中包括一个空的*Featrue及*Option条目。这些条目必须被置放于文件的开始位置,在其他的全部的*Feature及*Option条目之前,并在任何其他对该特性名或选项名的引用之前。空的条目被列出的顺序就是特性及选项应当在属性表单上显示的顺序。(需要注意的是,无论如何,纸张尺寸特性的选项总是以字母的顺序列出的,并且这个顺序不能被改变)。 下面是一个一套空的*Feature及*Option条目的实例: *Feature:EconoMode { *Option:OFF{} *Option:On{} } *Feature:Orientation { *Option:PORTRAIT{} *Option:LANDSCAPE_CC90{} } *Feature:PaperSize { } *Feature:Resolution { *Option:Option1{} *Option:Option2{} *Option:Option3{} } 该例子指定的顺序为EconoMode,Orientation,PaperSize及Resolution这样的一个特性的显示顺序。另外,它也指定了在EconoMode,Orientation,PaperSize及Resolution的属性必须以字母的顺序来显示。 如果一个GPD文件不包括空的*Feature及*Option条目指定的显示的顺序,GPD解析器将决定显示的顺序。而且,一般解析器都是以特性及选项在GPD文件中的顺序来显示,这一顺序不能被保证。另外,默认情况下,解析器总是将二进制输入的特性最先显示。 由包含有空的*Feature及*Option条目明确指定的顺序的方法是推荐使用的,而不推荐用解析器创建顺序。 4.16描述打印机内存配置 Unidrv小驱动程序可以包含一个打印机的可能的及默认的内存配置说明,这样Unidrv可以试图跟踪打印机内存使用情况。每一个内存配置使用包括所有的总内存值、可用内存等。可用内存可用于下载字体、保护页面及其他由Unidrv进行的控制操作。 在一个GPD文件中,可以用两种方法去描述一个打印机的可能的内存配置。两种方法包括为内存特性(是一种标准特性)在*Featrue条目中指定属性。两种方法如下: 1.可以在一个*Feature条目的不同的*Option条目中指定每一种可能的配置。每一个*Option条目必需包含一个*MemoryConfigKB属性,该属性在内存特性的选项属性部分中被描述。 例如,为指定一个打印机可以有两种内存配置,即1M配置,其中有450K可用及2M配置其中有1350K可用,可以用下面的GPD条目: *Feature:Memory { *Name:”Printer Memory” *DefaultOption:1MB *Option:1MB { *Name:”Standard 1MB” *MemoryConfigKB:PAIR(1024,450) } *Option:2MB { *Name:”Standard 2MB” *MemoryConfigKB:PAIR(2048,1350) } } 2.做为另外一种选择,*Feature条目可以包含一个或多个*MemConfigKB或*MemconfigMB属性而不是*Option条目。这是一种简单地不用包含一套*Option条目的指定内存的方法。每一个*MemConfigKB或*MemConfigMB属性表示一个内存选项。 例如,为指定上面同样的两种配置,即1M配置,其中有450K可用及2M配置其中有1350K可用,可使用下面的条目: *Feature:Memory { *Name:”Printer Memory” *DefaultOption:1024KB *MemConfigKB:PAIR(1024,450) *MemConfigKB:PAIR(2048,1350) } GPD解析器为每一种配置创建一个可显示的选项名称,基于第一个条目的PAIR语句。在上例中,选项名可以是“1024KB”及“2048KB”。*DefaultOption属性的参数必须与这两个名称之中的一个匹配。 方法1和方法2可以在一个单一的*Feature条目中使用。 如果解析器产生的选项名称与局部的需求不兼容,便只能使用方法1而不能使用方法2。 无论使用哪一种方法,Unidrv用户接口在设备的打印机属性表单上显示内存特性选项。 如果小驱动程序指定了内存配置,它也可以指定那些可以存储于打印机内存的数据的类型及用完它可用的内存空间。*MemoryUsage属性是一个打印机能力特性的属性,可以使用它向Unidrv指明是否字体、光栅或者向量数据,或者这三者的组合被存储于打印机的内存中。对每一种类型的指定,Unidrv试图跟踪到底有多少打印机内存可用。 4.17指定纸张大小 Unidrv支持三种规格的纸张尺寸——标准纸张尺寸、厂商定义的纸张尺寸、用户定义的纸张尺寸。 标准纸张尺寸 标准纸张尺寸是许多一般打印机都支持的尺寸,这些纸张大小的尺寸是预先定义好的。更多的信息,可以参考支持标准纸张尺寸部分的内容。 厂商定义的纸张尺寸 厂商定义的纸张尺寸(也称为非标准纸张尺寸non-standard paper sizes)是由打印机厂商定义的,在一个打印机的gpd文件中。更多的信息,请参考支持打厂商定义的纸张尺寸部分。 用户定义的纸张尺寸 用户定义的纸张尺寸(也称为定制的纸张尺寸),是由系统管理员根据系统的打印文件夹来定义的,这样便是系统专用的。更多的信息,参考支持用户定义的纸张尺寸部分。 4.17.1支持标准纸张尺寸 标准的纸张尺寸由纸张尺寸特性的标准选项来表示。 打印机支持的每一种标准尺寸的纸张,在GPD文件的纸张大小特性中必须包含一个*Option条目,它的参数是标准选项名称中的一个(除过CUSTOMESIZE)。这一条目中,下面的选项特性是必需的: *PrintableArea *PrintableOrigin *rcNameID *Command 下面的选项属性可以被用到,但不是必需的: *CursorOrigin *RotateSize? *PageProtectMem 对所有的标准的纸张大小尺寸,RCID_DMPAPER_SYSTEM_NAME资源标识符(在stdnames.gpd中定义)应当被用作*rcNameID的一个参数。 4.17.2支持厂商定义的纸张尺寸 厂商定义的纸张尺寸是厂商专用的并且必须由每一个打印机的GPD文件来描述。这些大小尺寸也称为非标准的纸张大小尺寸,因为它们没有包含于纸张大小特性的标准选项中。 对打印机支持的每一种厂商定义的纸张大小,GPD文件的纸张大小特性必须包括一个*Option条目,它的参数不是标准选项名中的一个。在这一条目中,下面选项属性是必需的: *PageDimensions *PrintableArea *PrintalbeOrigin *rcNameID 或*Name *Command 下面的选项属性可以被使用,但不是必需的: *CursorOrigin *RotateSize? *PageProtectMem 4.17.3支持用户定义的纸张尺寸 用户定义的纸张尺寸可以特定于一个打印机服务器并且通常对专用的应用程序是定制的。尽管他们经常称为定制的纸张尺寸。系统管理员用打印文件夹去定义定制的纸张尺寸。如果一个打印机可以处理定制的纸张尺寸,厂商必须用打印机的GPD文件去指定可接受的大小尺寸范围。 对描述可接受的定制的纸张尺寸,提供有两种方法: 可以明确地指定大小尺寸区间。 可以相对于打印机最大的纸张尺寸指定大小区间。 明确地指定大小尺寸区间 为使用这一方法,GPD文件的纸张大小尺寸必须包括一个*Option条目并具有一个CUSTOMSIZE参数,这一条目必需包含下面的选项属性: *MinSize *MaxSize *MaxPrintableWidth *MinLeftMargin *TopMargin *BottomMargin *CenterPrintable? *CursorOrigin *Command 可以用这些GPD条目为具有下面特性的打印机创建定制的纸张尺寸描述: 打印机支持明确选择定制纸张尺寸(通常是通过移动光标)的命令。 相对于纸张的左上角,光标原点保持固定,对所有的定制的纸张尺寸。(通常对以横向及竖向模式打印或者对那些中间进纸或右边进纸的打印机是不正确的) 顶部与底部页边相对于纸张尺寸独立。 如果纸的宽度小于由*MinLeftmargin及*MaxPrintableWidth指定的尺寸,将没有右边页边。也就是,打印机可以打到纸张的右页边上。 如果用到了标准变量表达式,则命令参数(在*Command条目中指定)可以在打印时被计算,通常包括PhyspaperLenth及PhyspaperWidth变量。这些变量表示打印作业实际需要的尺寸,如同一个应用程序指定的那样。 相对于打印机最大的纸张尺寸指定大小区间 对那些不能明确支持指定定制纸页尺寸区间特征的打印机,提供了一个变更的方法,它相对于打印机能支持的最大纸张尺寸指定大小区间。 为使用这种方法,GPD文件纸张尺寸特性必须包含一个*Option条目并具有CUSTOMSIZE参数。这一条目必须包含下面的选项属性: *MinSize *MaxSize *MaxPrintableWidth *CustCursorOriginX *CustCursorOriginY *CustPrintableOriginX *CustPrintableOriginY *CustPrintableSizeX *CustPrintableSizeY *Command 当指定相对计算机能支持的最大纸张尺寸的区间时,命名用下面调整的规则: 对左边进纸的打印机,纸张尺寸的顶部及左边的页边必须对齐。 对右边进纸的打印机,纸张尺寸的顶部及右边的页边必须对齐。 对中间进纸的打印机,纸张尺寸的顶部及顶部中间点必须对齐。 必须涉及下面的步骤: 1.为打印机的最大纸张尺寸确定下面的信息: 选择最大纸张尺寸的命令。 最大纸张尺寸可能会用到的*PageDimensions,*CursorOrigin,*PrintableOrigin及*PrintableArea等GPD条目的值,如同它们它必包含于GPD文件中。但是,不能实际将这些条目置于文件中。 2.对每一种定制的纸张尺寸,相对于打印机能支持的最大的纸张尺寸,创建一个指定或计算下列信息的公式。 每一页纸张可打印区域的原点及大小。 每一页纸的光标原点。 对步骤2,必须是CUSTOMSIZE参数表达式,它被指定为下面的GPD条目的值: *CustCursorOriginX *CustCursorOriginY *CustPrintableOriginx *CustPrintableOriginY *CustPrintableSizeX *CustPrintableSizeY CUSTOMSIZE选项必须包含一个*Command条目,它用于指定选择最大打印机尺寸的命令。这一命令将被送往所有的定制的纸张尺寸,并且由公式指定的可打印区域的光标原点将控制打印机实际打印到纸张上的位置而不管它的尺寸。 示例计算 如一个简单的实例,假设打印机支持定制的纸张尺寸,并与打印机能支持的最大的页面的页边相同。则必须包括下面这样的步骤: 1.决定最大纸张尺寸的*PageDimensions,*CursorOrigin,*PrintableOrigin及*PrintableArea条目的值(不要把这些条目放于GPD文件中)。 2.根据这些值确定每一页最大纸张尺寸页边的宽度,如下面的一些伪表达式: LeftMarginWidth=*PrintableOrigin.x RightMarginwidth=*PageDimension.x-*PrintableArea.x-LeftMarginWidth TopMarginWidth=*PrintableOrigin.y BottomMarginWidth=*PageDimensions.y-*PrintableArea.y-TopMarginwidth 在这些伪表达式中,.x及.y表示每个条目的对值(PAIR)的水平及垂直部分。对横向打印来说,用*PrintableArea及*PrintableOrigin的横向的值。 3.创建一个指定或计算为非标准纸张的可打印区域伪表达式。 *CustPrintableOriginX:%d{LeftMarginWidth} *CustPrintableOriginY:%d{TopMarginWidth} *CustPrintableSizeX:%d{PhysPaperWidth-LeftMarginWidth-RightMarginWidth} *CustPrintableSizeY:%d{PhysPaperLength-TopMarginWidth-BottomMarginWidth} 注意用两个标准变量,即PhysPaperWidth及PhysPaperLength。在运行时,这些变量包含着应用程序请求的实际纸张尺寸大小的长度及宽度值。 并且要注意,这些伪指令无论对从左边进纸、右边进纸、中间进纸,都是有效的。 4.插入真值到这些伪表达式中以创建GPD条目决定于步骤1,其例子可以是: *CustPrintableOriginX:%d{300} *CustPrintableOriginY:%d{300} *CustPrintableSizeX:%d{PhysPaperWidth-600} *CustPrintableSizeY;%d{PhyssPaperLength-600} 5.创建计算光标原点索引的伪表达式。在下面的伪表达式中,*CursorOrigin.x及*CursorOrigin.y是存放最大纸张尺寸光标原点的水平及垂直的部分的对值(PAIR)的地方。 对左边进纸的打印机: *CustCursorOriginX:%d{*CursorOrign.x} *CustCursorOriginY:%d{*CursorOrign.y} 对右边进纸的打印机: *CustCursorOriginX:%d{*CursorOrign.x+PhysPaperWidth-*PageDimensions.x} *CustCursorOriginY:%d{*CursorOrign.y} 对中间进纸的打印机: *CustCursorOriginX:%d{*CursorOrign.x+(PhysPaperWidth-*PageDimensions.x)/2} *CustCursorOriginY:%d{*CursorOrign.y} 6.插入步骤1决定的实型值到这些伪表达式中以创建GPD条目,其实例可能如下(对中间进纸) *CurstCursorOriginX:%d{((PhysPaperWidth-14040)/2)+300} *CustCursorOriginY:%d{180} 7.指定余下的三个GPD条目值——*MinSize,*MaxSize及*MaxPrintableWidth。指定于*MaxPrintableWidth的值不是实际用于该方法的值,但是解析器需要该条目的存在,因此,它的值可以被设置成为1。 一个实际的例子 下面实例是一个GPD文件片段,描述了可接受的可定制的中间进纸打印机的纸张尺寸。对纵向打印模式,所有定制纸张的尺寸的四个页边都是300个基本单位(1/4英寸)。对横向打印模式,顶部及底端的页边是240基本单位,而左边和右边的页边是200个基本单位。 *Option:CUSTOMSIZE { *rcNameID:=USER_DEFINED_SIZE_DISPLAY *MinSize:PAIR(4200,9000) *MaxSize:PAIR(14040,21240) *MaxPrintableWidth:14040 *MinLeftMargin:100 *CenterPrintable?:FALSE *PageProtectMem:1692 *InsertBlock:=PaperConstraints *switch:Orientation { *Case:PORTRAIT { *CustCursorOriginX:%d{((PhysPaperWidth-14040)/2)+300} *CustCursorOriginY:%d{180} *CustPrintableOriginX:%d{300} *CustPrintableOriginY:%d{300} *CustPrintableSizeX:%d{PhysPaperWidth-600} *CustPrintableSizeY:%d{PhysPaperLength-600} *Command:CmdSelect { *Order:DOC_SETUP.13 *Cmd:”<1B>&1101a8c1e99F<1B>*px0Y<1B>*c0t8064x12528Y” } } *Case:LANDSCAPE_CC90 { *switch:Opiton20 { *%The 8100 rotates the landscape job 180 degrees if a stapler *%is attached ,so the staple can be placed in the top left *%corner of the document.The printer will always rotate the *%landscape job,even if stapling is not selected. *case:3Kstapler { *CustCursorOriginX:%d{((PhysPaperWidth-14040)/2)+200} *CustCursorOriginY:%d{PhyspaperLength} *CustPrintableOriginX:%d{200} *CustPrintableOriginY:%d{240} *CustPrintableSizeX:%d{PhysPaperWidth-400} *CustPrintableSizeY:%d{PhyspaperLength-480} *Command:CmdSelect { *Order:DOC_SETUP.13 *Cmd:”<1B>&1101a8c1e63F<1B>*p0x0Y<1B>*c0t12456x8184Y” } } *case:MBM5S { *CustCursorOriginX:%d{((PhysPaperWidth-14040)/2)+200} *CustCursorOriginY:%d{PhyspaperLength} *CustPrintableOriginX:%d{200} *CustPrintableOriginY:%d{240} *CustPrintableSizeX:%d{PhysPaperWidth-400} *CustPrintableSizeY:%d{PhyspaperLength-480} *Command:CmdSelect { *Order:DOC_SETUP.13 *Cmd:”<1B>&1101a8c1e63F<1B>*p0x0Y<1B>*c0t12456x8184Y” } } *Default { *CustCursorOriginX:%d{((PhysPaperWidth-14040)/2)+200} *CustCursorOriginY:%d{PhyspaperLength} *CustPrintableOriginX:%d{200} *CustPrintableOriginY:%d{240} *CustPrintableSizeX:%d{PhysPaperWidth-400} *CustPrintableSizeY:%d{PhyspaperLength-480} *Command:CmdSelect { *Order:DOC_SETUP.13 *Cmd:”<1B>&1101a8c1e63F<1B>*p0x0Y<1B>*c0t12456x8184Y” } } }*%switch Option20 }*% case LANDSCAPE_CC90 }*%switch Orientation } 4.18引用场所(Referencing Locales) GPD文件可以引用一个系统的场所。通常,场所的标识符被用于*Switch语句中,而诸如默认纸张大小尺寸及资源DLL等参数可以用手工方式在一个场所中指定。 为引用场所信息,GPD文件必须必含一个*Include语句,该语句包含了locle.gpd(这一DDK提供)这一文件,如下: *Include:locale.gpd 这一GPD文件定义了一个名为“Locale”的特性,并对许多场所都定义了选项(访问该文件看定义了哪些场所)。下面是一个这些场所的用法实例,其实例在场所中支持默认的纸张尺寸。 *Feature:PaperSize { …… Option:A4 { } …… *switch:Locale { *case:English_United_States { *DefaultOption:Letter } *case:English_United_Kingdom { *DefaultOption:A4 } *default: { *defaultOption:Letter } }*% End of switch }*%End of Feature:PaperSize 在运行时,Unidrv通过调用GetsystemDefaultLCID(在平台的SDK文档中描述)确定系统的默认场所。当一个打印机被安装,GPD解析器读该打印机的GPD文件并使用与默认场所相关的在*Case语句中的信息。(注意,如果系统的场所在打印机被安装之后发生变化,基于场所的选项却没有变化)。 这里有另外一个例子,它基于场所来选择一个资源DLL。资源DLL可以包含特定场所的资源,如显示字符串等。 *switch:Locale { *case:English_United_States { *ResourceDLL:english.dll } *case:German_Standard { *ResourceDLL:german.dll } *default: { *ResourceDLL:english.dll } } 4.19安装一个Unidrv小驱动程序 安装一个Unidrv的小驱动程序需要一个printer.inf文件,它标识小驱动程序的文件。如果一个打印机的模式不被微软打印机的.inf文件或nfprint.inf支持,就需要一个商家提供的.inf文件。.inf文件应当引用printer.inf文件的数据部分及printer.inf文件的安装部分,printer.inf被定义于ntprint.inf中。对一个名为abc100的小驱动程序,如果打印机是双向打印,支持TrueType字体替换,并且使用了一个单一的资源DLL,下面的.inf条目通常是需要的: [Manufacturer] “ABC Printers” [ABC Printers] “ABC Printer 100”=ABC100.GPD,ABC_Printer_100 [ABC100.GPD] CopyFiles=@ABCres.dll,@ABC100.gpd ;Resource DLL and GPD file DataSection=UNIDRV_BIDI_DATA ;Unidrv Data Section DataFile=ABC100.gpd Include=NTPRINT.INF ;Include NTPRRINT.INF Needs=TTFSUB.OEM,UNIDRV_BIDI.OEM ;Install TrueType subs,Unidrv ; and PJL Language monitor 如果提供一个用户接口的插件或一个绘制插件,需要包含这些部分的名称在.inf文件中。关于安装定制代码的信息,参考第7章安装定制驱动程序组件部分的内容。 第5章 微软PostScirpt打印机驱动程序 PostScript打印机驱动程序,即PostScript Printer Driver(Pscript)是微软公司PostScript打印机的标准打印机驱动程序。关于Pscript主要描述了下列一些主题: ■5.1Pscript能力 ■5.2Pscript组件 ■5.3Pscript小驱动程序 ■5.4Pscript用户接口 ■5.5Pscript绘制器 5.1 Pscript能力 PostScript打印机驱动程序提供了如下的能力: 支持所有的PostScript打印机,用特定打印机的基于PPD的Pscript小驱动程序描述每一种打印机的特征。 一个Pscript用户接口,基于树形视图控件及属性表单,对所有的打印机是一致的,但对每一种打印机的单独选项也是可以改变的。 一个单独的Pscript绘制器,与GID图形引擎一起,将微软应用程序对Win32 GDI的调用转换成可以发送到打印假脱机的的打印机命令。 支持文档结构转换的3.1版本标准(Document Structuring convertion V3.1),该标准在Adobe System ,Inc公司出版的PostScript Language Reference Manual中有描述。 为打印机提供PostScript Level 1,Level 2,Leve3特性的支持。 下列类型字体的支持: 增量可下载的OpenType字体,作为PostScript Type 1及 Type 2字体。 增量可下载的TrueType字体,作为PostScript Type 1、 Type 3、Type 32、Type 42或基于CID的Type 42字体。 增量可下载的主机驻留的光栅字体作为PostScript Type 3或者Type 32字体。 完全可下载的主机驻留的PostScript Type 1字体。 打印机驻留的PostScript Type1、Type2及CID字体。 存在于打印机字符集中的每一个可被替换符号的字体, 对ICM2.0的支持,允许在主机系统上或通过打印机硬件来执行图形色彩管理。 5.2 Pscript组件 Pscript组件由一些DLL组成,加上文本及二进制数据文件,如在下图中所示: 插入图??? 图表中的组件包括: 应用程序 一个用户的应用程序,如可给用户提供打印能力的字处理程序。 gdi32.dll 用户模式的DLL,导出Wind32 GDI函数 内核模式的图形引擎 实现GDI功能的Windows NT 执行代码 小驱动程序文本文件 基于文本的Pscript小驱动程序,用PPD文件创建 二进制数据文件 Pscript在解析包含于小驱动程序的文本文件中的信息后创建的临时文件(具有.bpd扩展名) ps5ui.dll Pscript用户接口的DLL,向所有的支持Pscript的打印机提供通用的UI代码。 用户接口插件 可选,特定打印机的用户接口插件。 compstui.dll 为打印机的CPSUI用户接口 pscript5.dll pscript 绘制器,它处理文本输出并绘制图像,然后发送文本数据及图像数据到打印假脱机。 绘制插件 可选,特定打印机的绘制插件 5.3 Pscript小驱动程序 Pscript的小驱动程序是从.ppd及.ntf文件创建的。 PPD文件 基于文本的PostScript printer Description(.ppd)文件,描述了PostScript打印机的特征。Pscript驱动程序对Windows 2000支持的.ppd文件是与Adobe Systems Inc公司的v4.3版的PPD规范说明相兼容的。Pscript读一个打印机的.ppd文件并将文本转换为二进制的格式,在本地存储为一个.bpd文件并在.ppd文件每一次改变时被重新生成。 NTF文件 Windows 2000字体文件(.ntf文件)是用于描述由Pscript支持的打印机的设备字体。 微软提供了一个默认的.ntf文件,名为pscript.ntf,它包含有对遇到的US设备字体的通用描述。对Far East打印机,微软也提供了一个默认的.ntf文件,名称为pscriptfe.ntf,它包含对遇到的Far East打印机字体的通用描述。 另外,硬件厂商可以提供没有被pscript.ntf支持的设备字体描述。这些字体描述可以由转换AFM文件到NTF文件来被创建。定制的、特定模型的打印机可以通过将它们列为独立的打印机INF文件来安装。更多的信息,可以参考安装Pscript小驱动程序部分的内容。 Pscript根据最先检查到的特定打印机模型的.ntf文件来搜索字体的规格,然后用第一次发现的字体描述来检查pscript.nft文件。 5.3.1转换AFM文件到NTF文件 对Windows 2000,Adobe Font Metrics(AFM)文件必须被转换为.ntf文件。一个名为makenft.exe的命令行工具可以执行这一转换,该工具在本DDK中也提供。 为转换一个或多个.afm文件,用下面的命令语法: makentf NTF_FileName.ntf AFM_FileName[-v][-o] 这里,NTF_FileName是一个要被产生的.ntf文件的名称,AFM_FileName是一个或多个将被转换的AFM文件名称。 下面的命令行选项也是支持的: -v 冗余式。该选项创建一个包含对产生的NFT文件的文本式显示的命令输出流。 -o 省略标准的西文字符集。默认情况下,在产生.ntf文件时makentf包括一个标准的西文字符集。如果正在创建多个.ntf文件,只要所有的文件是一起使用,只需要在一个文件中包括西文字符集。例如,假设想创建一个包含罗马字体规格和另外一个包含日文字体规格的.ntf文件,可以使用下面的命令: makentf roman.ntf roman1.afm roman2.afm roman3.afm makentf –o jpn.ntf jpn1.afm jpn2.afm jpn3.afm 如果这些文件被一起使用,西文字符集信息总可以从roman.ntf文件获得,因此,在jpn.ntf文件中重复这样的信息是不必要的,只能是浪费附加的空间。另外一方面,如果jpn.ntf单独使用,-o就必须被指定。 另一个命令语法也是被支持的,如下: makentf filename 这里filename是一个接收输出文本的文件名,该语法导致makentf以创建一个文件,这个文件包含PostScript字符名称列表及被makentf所知的每一个代码页的Unicode值。 另外一个文件,PSFamily.dat,是该DDK提供的并必须驻留在与包含makentf.exe文件同一目录的位置。这是一个文本文件,它可以向makentf提供每一种字体的显示及家族名称。 在一个标准的.afm文件被转换之前,必须加入下面一行: Comment UniqueID Idnumberi 这里Idnumber表示字体的唯一标识符,由字体的厂商发布的。 一个将被转换成.ntf文件的.afm文件可以包含FontBBox2关键字。这个关键字的参数与FontBBox(参考Adobe System Inc.的Adobe字体规格文件格式规范说明)的那些参数非常相似,除了FontBBox2参数为所有在指定字符集(如90ms)中的字符描述了一个限制框,而FontBBox参数为所有在.afm文件中描述的字符联合描述限制框。如果FontBBox2没有被发现,则为FonBBox指定的值将被用于限制框。 5.3.2安装Pscript小驱动程序 安装Pscript小驱动程序需要一个打印机的printer.inf文件,它标识了小驱动程序的文件。如果一种打印机模式不被微软的打印机的inf文件、ntprint.inf文件所支持,就需要有厂商提供的.inf文件。.inf文件应当引用定义于ntprint.inf中的printer.inf文件数据部分及printer.inf文件的安装部分。对一个名称为abc100的小驱动程序,下面的.inf条目是通常需要的: [Manufacturer] “ABC Printers” [ABC Printers] “ABC Printer 100 PS”=ABC100.PPD,ABC_Printer_100_PS [ABC100.PPD] CopyFiles=@ABC100.ppd ;PPD file DataSection=PSCRIPT_DATA ;PSCRIPT Data Section DataFile=ABC100.ppd Include=NTPRINT.INF ;Include NTPRINT.INF Needs=PSCRIPT.OEM ;Install PSCRIPT 如果提供了用户接口插件或绘制插件,需要在.inf文件中包括这些组件的名称。更多的关于安装定制代码的信息,参考第7章安装定制驱动程序组件部分的内容。 5.4 Pscript用户接口 Pscript用户接口用CPSUI以创建下面的属性表面页面: 打印机属性表单的设备设置(Device Settings)页面,当一个用户从打印文件夹或打印机窗口选择了属性菜单项目时它就会显示。该页面列出了打印机专用的配置信息。 文档属性表单的布局(Layout)、纸张/质量(Paper/Quality)、高级(Advanced)等页面。这些页面会在用户选择了从打印文件夹或打印机窗口的打印优选项(Printing Preference)菜单项时显示。或者一个应用程序调用PrinterProperties或DocumentProperties函数(在平台的SDK文档中描述)。这些页面列出了特定文档的配置信息。 这些属性表单页面包含了打印机的特性及由打印机的Pscript小驱动程序指定的打印机的选项。也允许用户修改这些选项值。 Pscript用户接口是作为一个用户模式的打印机接口DLL来实现的。在这一DLL中的代码,与CPSUI连接起来,指定了属性表单页面的内容。基于小驱动程序中的信息,DLL在这些打印机可组合的选项上施加限制。它也确保了用户不去选择没有安装在打印机上的选项。 可以通过提供一个用户接口的插件来修改Pscript提供的属性表单页面,这些内容在第7章中的定制微软打印机驱动程序部分描述。 5.5 Pscript绘制器 Pscript绘制器是作为一个打印机图形的DLL实现的,这样可以导出由微软设备驱动程序接口(DDI:Device Driver Interface)为图形驱动程序定义的函数。当一个应用程序调用图形设备接口(GDI:Graphics Deivce Interface)函数以向打印机设备发送文本及图像,内核模式的图形引擎调用绘制器的DDI函数。这些DDI函数帮助GDI绘制打印机作业的页面图像。 绘制器同时也负责按照打印机命令序列发送文本及绘制图像到打印假脱机,假脱机然后直接将数据流及命令发送到打印机硬件。 可以通过提供一个绘制插件来修改Pscript的绘制操作,这些内容也在第7章中的定制微软打印机驱动程序部分中描述。 第6章 微软绘图仪驱动程序 微软绘图仪驱动程序是微软公司对支持HP图形语言(Hewlet-packard Graphics Language)的绘图仪的标准打印机驱动程序。对微软绘图仪驱动程序,主要包括以下几个主题: ■6.1绘图仪驱动程序能力 ■6.2绘图仪驱动程序组件 ■6.3绘图仪驱动程序的小驱动程序 ■6.4绘图仪驱动程序的用户接口 ■6.5绘图仪驱动程序的绘制器 6.1绘图仪驱动程序能力 微软的绘图仪驱动程序(MSPlot)提供下面的能力: 通过对用HP图形语言的HPGL/2版本的使用,利用绘图仪特定模型的绘图仪驱动程序的小驱动程序手段来提供对所有绘图仪的支持。 一个绘图仪驱动程序的用户接口,基于树形视图控件及属性表单,对所有的绘图仪都是一致的。 一个绘图仪驱动程序的绘制器。与GDI图形引擎一起,将应用程序的Win32 GDI调用转换成为可以发送到打印假脱机的打印机命令。 为提供对新的HPGL/2兼容设备类型的支持,需要做的只是提供一个描述该设备的小驱动程序。 6.2绘图仪驱动程序组件 MSPlot组件由DLL及二进制数据文件组成,如下面的图中所示: 插入图??? 图中的组件包括有: 应用程序 一个提供用户打印能力的应用程序 gdi32.dll 用户模式的DLL,它导出Win32 GDI函数。 内核模式的图形引擎 实现GDI函数的Windows NT 执行代码 小驱动程序 MSPlot小驱动程序(.pcd文件) 缓存的.pcd文件数据 从.pcd文件读来的小驱动程序数据 plotui.dll 绘图仪用户接口的DLL,为所有支持MSPlot的打印机提供通用的UI代码 compstui.dll 打印机的CPSUI用户接口 plotter.dll 绘图仪驱动程序绘制器,它绘制图像并发送图像数据流到假脱机 6.3绘图仪驱动程序文件示例 本DDK包括下面的MSPlot实例代码: 绘图仪驱动程序用户接口DLL(plotui.dll)的源代码 绘图仪驱动程序绘制器(plotter.dll)的源代码 plotgpc.exe的源代码,它产生.pcd文件 一个小驱动程序的文本文件的实例,它可用于作为plotgpc.exe的输入。 这些例子都放在该DDK树形目录的实例目录的子目录中。绘图仪驱动程序绘制器(plotter.dll)可以用户模式或内核模式之任一种来编译。更多的信息,请参考第7章中的选择用户模式或内核模式部分的内容。 6.4绘图仪驱动程序的小驱动程序 微软绘图仪驱动程序的特定模型的小驱动程序,是由厂商提供的、从描述设备特征文本文件产生的二进制的.pcd文件。 PCD文件 为产生一个.pcd文件,必须首先创建一个采用PCD源文件格式的文本文件,然后运行plotgpc.exe,该文件在DDK中提供。这一程序将转换一个文本文件为二进制的.pcd文件,用下面的命令语法: plotgpc source-file-path.txt traget-file-path.pcd 对源文件及目标文件来说,必须明确地指定其文件的扩展名,不支持默认值。 一个实例的文本文件,可作为plotgpc.exe的输入的文件被包括于实例绘图仪驱动程序文件中。 关键字 值定义 默认值 PushPopPal 1=驱动程序必须在TRL及HPGL2之间切换时推出或弹出调色板 0=不需要推出/弹出 0  RasterByteAlign 1=设备必须根据x坐标的字节分配接收所有的光栅数据 0=不需要字节分配 0  RasterCap 1=光栅设备 0=笔设备 0  RasterDPI 两个DWORD大小的值表示x及y的精度,以DPI为单位 对光栅绘图仪来说,这是光栅精度 对笔绘图仪来说,这时GDI提供给应用程序的理想的精度 {300.300}  RollFeedCap 1=设备能进行转动进纸 0=不支持 0  ROPLevel ROP_LEVEL_0=不支持光栅操作 ROP_LEVEL_1=ROP1支持 ROP_LEVEL_2=ROP2支持 ROP_LEVEL_3=ROP3支持 ROP_LEVEL_0  RTLMonoEncode5 1=支持光栅传输语言(RTL)单色压缩模式5 0=不支持 0  RTLMonoFixPal TRL单色模板0=白色,1=黑色 0  RTLMonoNoCID 1=用RTL单色板式,不需要CID命令 0=用RTL单色模式,需要CID命令 0  RTLNoDPIxy 1=不支持RTL DPI X,Y等移动命令 0=支持这些命令 0  TransparentCap 1=设备支持透明模式 0=不支持 0  WindingFillCap 1=设备支持绕组填充 0=不支持 0   笔描述(Pen Discriptions) 每一个笔描述必须具有下面的格式: PlotpenData {Pen Number,Color} 这里,Pen Number标识笔的槽数(Slot Number)而Color是一个PC_IDX_为前缀的色彩标识符。下面是笔描述的例子。 PlotPenData {1,PC_ICX_WHITE} PlotPenData {2,PC_ICX_BLACK} PlotPenData {3,PC_ICX_RED} 表格描述 每一个表格描述必须采用下面的格式: FormInfo{“Form Description”,Width,Length,Left Margin,Top Margin,Right Margin,Bottom Margin} 这里,Form Description是一个描述表格工字符串,Width和Length以1/1000mm单位指定表格的大小,页边也是以1/1000mm为单位指定的。下面是三个实例: FormInfo{“Roll Paper 24 in ”,609600,0,0,0,0,0} FormInfo{“ANSI A 8.5 x 11 in”,216900,279400,0,0,0,0} FormInfo{“ISO A$ 210 x 297mm”,210000,297000,0,0,0,0} 6.5绘图仪驱动程序用户接口 绘图仪驱动程序用户接口用CPSUI创建下面的属性表单: 打印机属性表单的设备设置(Device Settings)页面,当一个用户从打印文件夹或打印机窗口选择了属性菜单项目时它就会显示。该页面列出了打印机专用的配置信息。 文档属性表单的布局(Layout)、纸张/质量(Paper/Quality)、高级(Advanced)等页面。这些页面会在用户选择了从打印文件夹或打印机窗口的打印优选项(Printing Preference)菜单项时显示。或者一个应用程序调用PrinterProperties或DocumentProperties函数(在平台的SDK文档中描述)。这些页面列出了特定文档的配置信息。 这些属性表单包含了绘图仪的特性及由绘图仪小驱动程序指定的选项。它们也允许用户修改选项值。 绘图仪用户接口是作为用户模式的打印机接口DLL实现的。该DLL中的代码与CPSUI相连,指定了属性表单页的内容。基于小驱动程序的信息DLL对绘图仪可组合的选项实施限制,这也确保了用户不选择没有安装在绘图仪上的选项。 6.6绘图仪驱动程序绘制器 绘图仪驱动程序绘制器是作为打印机图形DLL来实现的,并对图形驱动程序导出由设备驱动程序接口(DDI)定义的函数。当一个应用程序调用图形设备接口(GDI)函数将图像发送到绘图仪,内核模式的图形引擎将调用绘制器的DDI函数。这些DDI函数帮助GDI绘制一个打印作业的页面图像。 绘制器也同时负责根据命令序列向假脱机绘制图像数据,假脱机然后再把图像及命令直接传送到绘图仪硬件。 第7章 定制微软的打印机驱动程序 微软统一驱动程序(Unidrv)及微软PostScript打印机驱动程序(Pscript)的设计都是基于Windows? NT/Windows? 2000打印机驱动程序体系结构。因此,每一个都是由两部分构成——即一个打印机接口DLL和一个打印机图形DLL。本章讲述怎样定制这些组件。 为提供对Unidrv或Pscript的打印机接口DLL的定制,必须提供一个或多个用户接口的插件。可以使用这些插件来修改驱动程序的用户接口,并对某个打印机事件提供附加的处理。 为提供对Unidrv或Pscript的打印机图形DLL的定制,必须提供一个或多个绘制插件。可以用这些插件去修改将要发送到打印机假脱机中的在打印机作业数据流中的数据。 本章也解释了怎样实现打印机驱动程序的COM接口,它被插件用于与打印机驱动程序之间的通讯,也解释了如何安装定制的驱动程序组件。 7.1用户接口插件 这一节包含如下一些解释怎样开发用户接口插件的主题: ■7.1.1用户接口插件介绍 ■7.1.2UI插件的例子 ■7.1.3UI插件的COM接口 ■7.1.4提供DEVMODE结构附加项 ■7.1.5从UI插件存取驱动程序设置 ■7.1.6修改一个驱动程序提供的属性表单页面 ■7.1.7增加新的属性表单页面 ■7.1.8定制其他的打印机接口操作 7.1.1用户接口插件介绍 当增加对一新的微软统一驱动程序(Unidrv)或者微软Postscript打印机驱动程序(Pscript)打印机设备的支持,可以通过修改打印机的属性表单或文档的属性表单来定制驱动程序的用户接口。 可以通过提供一个用户模式的DLL来完成这些,这一DLL即指的是用户接口的插件,或称UI插件。 一个UI插件可以通过增加、删除、或者替换在属性表单中的“设备设置(Device Settings)”页面的选项来修改打印机属性表单。它也可以增加新的页面。同样,插件可以通过增加、删除、替换在属性表单的“布局(Layout)”、“纸张/质量(Paper/Quality)”、“高级(Advanced)”页面的选项来修改文档属性表单,或者它也可以增加新页。 UI插件被称为Unidrv或Pscript的打印机接口DLL,用了一套COM接口。打印机接口DLL是通过使用CPSUI来实现的,并且一个UI插件通过驱动程序的打印机接口DLL与CPSUI进行间接的交互。因此,在开发UI插件之前,阅读CPSUI一章是非常有益的。 除修改打印机驱动程序的用户接口外,一个UI插件可以执行其他的操作,如处理某个打印机事件并报告被支持的能力。更多的信息,请参考定制其他打印机接口操作部分的内容。 7.1.2 UI插件的例子 在该DDK中提供了用户接口插件的源代码实例。其代码包含在\oemui源代码目录的子目录中,并且创建了一个可以与Unidrv使用的插件。 7.1.3 UI插件的COM接口 下面的COM接口是为微软打印机驱动程序与UI插件之间的通讯定义的: IPrintOEMUI COM Interface——允许为Unidrv或Pscript的打印机接口DLL调用UI插件。 IPrintOemDriverUI COM Interface——对UI插件提供实用的操作。 7.1.3.1 IPrintOemUI COM 接口 IPrintOemUI COM接口实际提供了一种Unidrv或Pscript的打印机接口DLL与UI插件通讯的一种手段。IPrintOemUI COM由每一个UI插件来实现的。 下面的表格列出并描述了所有的由IPrintOemUI接口提供的方法。UI插件必须定义所有列出来的方法。如果一种方法不需要,它可以只返回E_NOTIMPL。 方法 描述 IPrintOEMUI::CommonUIProp 允许一个用户接口插件修改一个已经存在的属性表单页面或者文档的属性表单页面  IPrintOEMUI::DeviceCapabilities 允许一个用户插件去指定定制的设备能力  IPrintOEMUI::DevicePropertySheets 允许一个用户插件给一个打印机设备的打印机属性表单增加新页  IPrintOEMUI::DevMode 在一个用户接口插件上执行操作的私有DEVMODE成员  IPrintOEMUI::DevQueryPrintEx 允许一个用户插件帮助确定是否一个打印作业是可打印的  IPrintOEMUI::DocumentPropertysheets 允许一个用户插件给一个打印机设备的文档属性表单增加新页面  IPrintOEMUI::DriverEvent 在处理驱动程序专用的、需要打印机驱动程序行为的事件时,由打印假脱机调用  IPrintOEMUI::FontInstallerDlgProc 替换Unidrv字体安装程序的用户接口  IPrintOEMUI::GetInfo (需要实现部分)返回一个用户接口插件标识符的信息  IPrintOEMUI::PrinterEvent 允许一个用户接口插件处理打印机事件  IPrintOEMUI::PublishDriverInterface (需要实现部分)提供一个对Unidrv或Pscript驱动程序的IPrintOemDrvierUI COM接口的指针  IPrintOEMUI::QuerycolorProfile 允许一个打印机接口DLL为用户的色彩管理指定一个ICC的初始化文件profile  IPrintOEMUI::UpdateExternalFonts 允许一个打印机接口DLL修改一个打印机的Unidrv字体格式文件(.uff文件)  IPrintOEMUI::UpgradePrinter 允许一个用户接口插件升级存储于注册表中的设备选项值  更多的信息,可参考实现打印机驱动程序COM接口部分。 7.1.3.2 IPrintOemDriverUI COM接口 IPrintOemDriverUI COM接口允许一个UI插件去查看并修改由Unidrv及Pscript打印机接口DLL管理的信息。 下表列出并描述了所有由IPrintOemDriverUI接口定义的方法: 方法 描述 IprintOemDriverUI::DrvGetDriverSetting 允许一个用户接口插件获得当前打印机特性的状态及其他内部信息  IprintOemDriverUI::DrvUpdateUISetting 允许一个用户接口插件通知驱动程序修改了的用户接口选项  IprintOemDriverUI::DrvUpgradeRegistrySetting 允许一个用户接口插件修改存储于注册表中的设备设置  更多的信息,参考实现打印机驱动程序COM接口部分。 7.1.4提供DEVMODE结构附加项 用户接口插件可以向DEVMODE结构增加附加的私有成员,如图所示: 插入图??? 一个UI插件可以使用这些私有的DEVMODE成员存储与定制的打印机选项相关的值。通过修改驱动程序提供的属性表单页面,或增加新属性表单页面时,插件使得这些选项对一个用户可用。 如果UI插件增加一个私有的DEVMODE成员,被增加的成员必须通过OEM_DMEXTRAHEADER结构来预置。 不需要向DEVMODE结构增加成员,但如果做了,插件必须实现IPrintOemUI::DevMode方法。依赖于输入的参数,这一方法的目的是返回另外增加的DEVMODE成员的初始化、转换或有效的大小。 7.1.5从UI插件存取驱动程序设置 一个用户接口插件可以获得当前打印机特性及其他内部信息的状态。IPrintOemDriverUI::DrvGetDriverSetting COM接口方法是在为微软打印机驱动程序的打印机接口DLL中实现的并能被UI插件调用。 另外,下面的方法允许UI插件修改驱动程序信息。 IPrintOemDriverUI::DrvUpdateUISetting允许一个UI插件在用户已经修改了驱动程序的设定时通知驱动程序。 IPrintOemDriverUI::DrvUpgradeRegistrySetting允许一个UI插件修改在注册表中的设备设定,这样由旧的驱动程序版本使用注册表设定就可以被修改。 7.1.6修改一个驱动程序提供的属性表单页面 一个UI插件可以直接通过实现IPrintOemDriverUI::CommonUIProp方法及一个回调函数来修改Unidrv或Pscript提供的属性表单页面。 IPrintOemDriverUI::CommonUIProg方法被UI插件使用以指定一套CPSUI可以在打印机属性表单的“设备设置(Device Settings)”页面或者文档属性表单的“布局(Layou)”“纸张/质量(Paper/Quality)”“高级(Advanced)”页面中增加、删除或替换的选项项目。 回调函数,其类型为OEMCUIPCALLBACK,用于处理用户对定制选项项目的修改。 增加选项项目 UI插件必须描述新的选项项目,这是通过将它们放置于一个由驱动程序提供的OPTITEM结构的数组中来完成的。驱动程序打印机接口DLL两次调用UI插件的IPrintOemDriverUI::CommonUIProp方法。该方法第一次被调用时,它应当返回需要OPTITEM结构的数目,驱动程序对OPTITEM数组分配空间并在一个OEMCUIPPARAM结构中描述该数组。然后,驱动程序再次调用该方法,提供OEMCUIPPARAM结构的地址,这样该方法可以用选项描述去装载OPTITEM结构。 删除选项项目 从一个Unidrv或Pscript提供的属性表单页中删除一个选项,UI插件的IPrintOemDriverUI::CommonUIProp方法可以遍历由OEMCUIPPARAM结构指向的OPTIITEM结构的数组,可以设置OPTITEM结构的OPTIF_HIDE标识(注意,这种做法实际上并没有删除选项,它只是对用户隐藏了选项,这样它默认的值就不能被改变)。 替换选项项目 为在一个Unidrv或Pscript提供的属性表单页中替换一个选项,应当根据在删除选项项目部分的教学内容去删除已经存在的选项项目,然后根据增加选项项目部分的内容以创建一个新的选项项目以替换旧的项目。 处理对定制选项值的修改 为了处理用户对定制选项项目的修改,必须至少提供一个回调函数。可以指定一个单一的回调函数用于处理对文档属性表单及打印机属性表单的处理,或者可以分别为它们各自指定一个单独的函数,这些回调函数属于OEMCUIPCALLBACK类型。 对一个回调函数的指定只需将它的地址设置在OEMCUIPPARAM结构中即可。UI插件作为对自身的IPrintOemDriverUI::CommonUIProp方法的输入收到这一结构的地址。 当一个用户打开打印机的属性表单或者文档属性表单并修改选项时,CPSUI调用打印机驱动程序的打印机接口DLL。这一DLL处理包含于它自身的OPTITEM结构中的选项值,然后,对每一个UI插件,打印机接口DLL调用由IPrintOemDriverUI::CommonUIProp 前面指定的OEMCUIPCALLBACK类型的回调函数。 7.1.7增加新的属性表单页面 如果想给打印机接口为Unidrv及Pscript提供的属性表单增加一个新的页面,UI插件必须实现下面的IPrintOemUI方法: IPrintOemDriverUI::DevicePropertySheets 用于给打印机属性表单增加页面,当在一个用户从打印文件夹或打印机窗口选择了“属性(Properties)”菜单项时显示,或者当一个应用程序调用了PrinterProperties函数时显示(在平台的SDK文档中有描述)。 IPrintOemDriverUI::DocumentPropertySheets 用于给文档的属性表单增加新页,它在一个用户从打印文件夹或打印机窗口上选择了“打印机优选项(Printer Preference)”的菜单选项时或者应用程序调用了DocumentProperties/AdvancedDocumentProperties函数(在平台的SDK文档中有描述)。 如果实现了其中的一种方法,通常会提供一个_CPSUICALLBACK类型的回调函数以处理用户的修改。这一回调函数必须调用IPrintOemDriverUI::DrvUpdateUISetting以在与用户接口相关的设定值被修改后通知驱动程序,而这个设定值是要存储于驱动程序的DEVMODE结构或注册表的关键字中。 7.1.8定制其他的打印机接口操作 一个UI插件可选择性地实现下面的任一种IPrintOemDriverUI方法: IPrintOemDriverUI::DeviceCapabilities IPrintOemDriverUI::DevQueryPrintEx IPrintOemDriverUI::PrinterEvent IPrintOemDriverUI::UpgradePrint 这些方法等同于由Unidrv及Pscript使用的用户模式的打印机接口DLL导出的相同名称的函数。这些定制的方法不替换在驱动程序的打印机接口DLL中的等同函数。在每一种情况下,打印机接口DLL函数被首选调用,然后驱动程序调用插件的定制方法。 7.2绘制插件 下面的主题描述了怎样定制打印机绘制操作: ■7.2.1绘制插件介绍 ■7.2.2绘制插件实例 ■7.2.3绘制插件的COM接口 ■7.2.4定制的DDI函数 ■7.2.5定制的PDEV结构 ■7.2.6从绘制插件存取驱动程序设置 ■7.2.7特定Pscript的定制绘制 ■7.2.8特定Unidrv的定制绘制 7.2.1绘制插件介绍 当增加对一个新的打印机设备的微软通用打印机驱动程序(Unidrv)或微软PostScript打印机驱动程序(Pscript)的支持,可以实现COM接口方法以修改打印机发送给打印假脱机的数据。 可以通过提供用户模式的DLL来完成这一定制。这个DLL即被看作为一绘制插件。 有两种类型的定制被支持: 提供一些DDI绘制函数的定制版本 实现特定Unidrv或特定Pscript的COM接口方法,它们在修改被绘制的图象数据或扫描行数据流,或者在专用的注入点插入Postscript代码,这些都是在数据流被发送到假脱机之前进行的。 7.2.2绘制插件实例 这一DDK也提供有绘制插件的源代码示例。代码包含于\oemuni源代码的子目录中并创建了一个可用于Unidrv的插件。 7.2.3绘制插件的COM接口 下面的COM接口是为微软打印机驱动程序及绘制插件之间的通讯定义的。 IPrintOemUni COM接口 ——允许Unidrv的打印机图形DLL调用绘制插件。 IPrintOemDriver COM接口 ——给Unidrv的绘制插件提供实用操作。 IPrintOemPS COM接口 ——允许Pscript的打印机图形DLL调用绘制插件。 IPrintOemDriverPS COM接口 ——给Pscript的绘制插件提供实用操作。 7.2.3.1 IPrintOemUni COM接口 IPrintOemUni COM接口是Unidrv的打印机图形DLL与绘制插件通讯的手段。IPrintOemUni COM接口是通过每一个绘制插件来实现的。 下面列出并描述了所有由IPrintOemUni COM接口提供的方法。绘制插件必须定义列出所有的方法,如果一个方法不需要,则它必须返回E_NOTIMPL。 方法 描述 IPrintOemUni::CommandCallback 允许一个绘制插件提供动态产生的打印机命令  IPrintOemUni::Compression 允许一个绘制插件提供定制的位图压缩方法  IPrintOemUni::DevMode 在绘制插件的私有DEVMODE成员上的执行操作  IPrintOemUni::DisableDriver 释放由绘制插件IPrintOemUni::EnableDriver方法提供的插件  IPrintOemUni::DisablePDEV 允许一个绘制插件删除私有的由IPrintOemUni::EnablePDEV方法分配的PDEV结构  IPrintOemUni::DownloadCharGlyph 允许一个绘制插件给打印机下载专用的软字体字符  IPrintOemUni::DownloadFontHeader 允许一个绘制插件下载字体头信息给打印机  IPrintOemUni::DriverDMS 允许一个绘制插件指明它将用一个设备管理的绘制表面  IPrintOemUni::EnableDriver 允许一个绘制插件分出(hook out)一些DDI函数  IPrintOemUni::EnablePDEV 允许一个绘制插件创建它自已的PDEV结构  IPrintOemUni::FilterGraphics 允许一个绘制插件修改扫描行数据并将它发送到假脱机  IPrintOemUni::GetImplementedMethod (需要实现)允许Unidrv决定哪一个IPrintOemUni接口方法已经被绘制插件所实现  IPrintOemUni::GetInfo (需要实现)返回绘制插件的标识信息  IPrintOemUni::HalftonePattern 允许一个绘制插件创建或修改一个过渡调色调色板,并且在它被用于过渡调色的操作之前  IPrintOemUni::ImageProcessing 允许一个绘制插件修改图像位图数据,以执行色彩格式或过渡调色  IPrintOemUni::MemoryUsage 允许一个绘制插件指定被IPrintOemUni::ImageProcessing方法需要的内存的总量  IPrintOemUni::OutputCharStr 允许一个绘制插件控制字体符号的打印  IPrintOemUni::PutlishDriverInterface (需要实现)提供一个Unidrv驱动程序的IPrintOemDriverUni COM接口  IPrintOemUni::ResetPDEV 允许一个绘制插件重置它的PDEV结构  IPrintOemUni::SendFontCmd 允许一个绘制插件修改一个打印机的字体选择命令并然后将它发送到打印机  IPrintOemUni::TextOutAsBitmap 允许一个绘制插件创建一个文本字符串的位图图像  IPrintOemUni::TTDownloadMethod 允许一个绘制插件去指明Unidrv应当为一个专用的TrueType字体使用的格式  IPrintOemUni::TTYGetInfo 允许一个绘制插件给Unidrv提供与打印机相关的文本信息  更多的信息,可以参考实现打印机驱动程序COM接口部分。 7.2.3.2 IPrintOemDriver COM接口 IPrintOemDriver COM接口提供一个绘制插件,它能够存取Unidrv的图形DLL提供的功能操作。这些操作包括发送数据流到打印机假脱机并获取驱动程序管理的信息。 下表列出并描述所有的由IPrintOemDriverUni接口定义的方法。 方法 描述 IPrintOemDriverUni::DrvGetDriverSetting 返回打印机特性的当前状态及其他内部信息  IPrintOemDriverUni::DrvGetGPDData 使绘制插件获得在打印机GPD文件中定义的数据  IPrintOemDriverUni::DrvGetStandardVariable 使绘制插件获得Unidrv的标准变量的当前值  IPrintOemDriverUni::DrvUniTextOut 使绘制插件使用一个设备管理的绘制表面以很容易地输出文本字符串  IPrintOemDriverUni::DrvWriteSpoolBuf 发送打印机数据到假脱机  IPrintOemDriverUni::DrvXMoveTo 通知Unidrv关于x位置的变化  IPrintOemDriverUni::DrvYMoveTo 通知Unidrv关于y位置的变化  更多的信息,参考实现打印机驱动程序COM接口部分。 7.2.3.3 IPrintOemPS COM接口 IPrintOemPS COM接口是Pscript的打印机图形DLL与绘制插件通讯的手段。IPrintOemPS接口是由每一个绘制插件来实现的。 下表列出并描述了所有由IPrintOemPS提供的接口。绘制插件必须定义所有的被列出的方法。如果一个方法是不需要的,则它应返回E_NOTIMPL。 方法 描述 IPrintOemPS::Command 允许一个绘制插件插入postscript命令到打印作业的数据流中  IPrintOemPS::DevMode 在绘制插件的DEVMODE的私有变量上的执行操作  IPrintOemPS::DisableDriver 释放由绘制插件的IPrintOemPS::EnableDriver分配的资源  IPrintOemPS::DisablePDEV 允许一个绘制插件删除私有的PDEV结构,该结构是由它的IPrintOemPS::EnablePDEV方法分配的  IPrintOemPS::EnableDriver 允许一个绘制插件分出一些DDI函数  IPrintOemPS::EnablePDEV 允许一个绘制插件创建它的PDEV结构  IPrintOemPS::GetInfo (需要实现)返回绘制插件的标识信息  IPrintOemPS::PublishDriverInterface (需要实现)提供一个Pscript驱动程序的指向IPrintOemPS COM接口的指针  IPrintOemPS::ResetPDEV 允许一个绘制插件重置它的PDEV结构  更多的信息,参考实现打印机驱动程序COM接口部分的内容。 7.2.3.4 IPrintOemDriverPS COM接口 IPrintOemDriverPS COM接口提供一个可以存取Pscript的、由打印机图形DLL提供的功能进行操作的绘制插件。这些操作包括发送数据流到打印机假脱机以及获取驱动程序管理的信息。 下面的表格列出并描述了由IPrintOemDriverPS接口定义的方法: 方法 描述 IPrintOemDriverPS::DrvGetDriverSetting 返回打印机特性的当前信息以其他内部信息  IPrintOemDriverPS::DrvWriteSpoolBuf 发送打印机数据到假脱机  更多的信息,参考实现打印机驱动程序COM接口部分的内容。 7.2.4定制的DDI函数 一个绘制插件可以分出一些DDI函数,提供这些函数的定制实现。为提供定制的函数,绘制插件必须实现IPrintOemUni::EnableDriver或IPrintOemPS::EnableDriver方法,这两个方法被填写在DRVENABLEDDATA结构并具有每一个函数的地址。 一个绘制插件可以分出一个DDI函数,但只是在该函数是由Unidrv或Pscript驱动程序定义时。对这些函数的列表,参考DDK中包括的IPrintOemUni::EnableDriver或IPrintOemPS::EnableDriver。 如果提供一个特殊的定制的分出函数,该函数优先于驱动程序的等同DDI函数。当设计一个定制的分出函数,可以有下面的选项: 分出函数可以完全在内部处理DDI操作。 分出函数可以回调打印机驱动程序等同的DDI函数。 通过调用驱动程序的DDI函数,分出函数可以执行预处理或后处理的函数参数,但是仍然允许驱动程序实际执行DDI操作。对绘制插件的IPrintOemUni::EnableDriver或IPrintOemPS::EnableDriver方法的一个参数是DRVENABLEDDATA?结构包含的指向驱动程序DDI函数的指针。如果想回调这些函数,应当存储这些结构的内容。 对来说,也许有必要提供一个定制的PDEV结构。可以通过每一个分出函数作为输入接收的SURFOBJ结构指针,从一个DDI的分出函数中引用这一结构。特定地,SURFOBJ结构的dhpdev成员指向一个DEVOBJ结构,并且DEVOBJ结构的pdevOEM成员指向定制的PDEV结构。 7.2.5定制的PDEV结构 绘制插件通过实现下面的方法来支持私有的PDEV结构: IPrintOemUni::EnablePDEV或IPrintOemPS::EnablePDEV IPrintOemUni::DisablePDEV或IPrintOemPS::DisablePDEV IPrintOemUni::ResetPDEV或IPrintOemPS::ResetPDEV PDEV结构是一个一般的项目,它指向一个私有的,本地定义的结构并由定义它的模块来使用。通常,它用于存储物理设备特征。每一个打印机驱动程序,以及每一个绘制插件,定义它自已的PDEV结构。没有被定义的全局范围的“PDEV”类型的结构。 7.2.6从绘制插件存取驱动程序设置 一个绘制插件可以获取打印机特性的当前状态及其他内部驱动程序信息。下面的COM接口方法在微软打印机驱动程序内实现在方法可以被绘制插件来调用: 由Unidrv实现的方法: IPrintOemDriverUni::DrvGetDriverSetting IPrintOemDriverUni::DrvGetStandardVariable IPrintOemDriverUni::DrvGetGPDData 由Pscript实现的方法: IPrintOemDriverPS::DrvGetDriverSetting 7.2.7特定Pscript的定制绘制 Pscirpt允许特定设备的定制代码注入Postscript命令到Pscript发送给打印机设备的数据流中。如果想提供这种类型的定制代码,必须提供一个绘制插件以实现IPrintOemPS::Command方法。 Pscript在打印机作业数据流的很多点调用IPrintOemPS::Command方法。函数的参数之一指定了一个表示当前在数据流中点的位置的索引值。函数的每次调用,它可以检验索引的值并提供另外的数据流或者不提供。 7.2.8特定Unidrv的定制绘制 下面的主题提供了关于Unidrv允许绘制插件执行的操作的类型的信息: ■7.2.8.1定制的色彩格式 ■7.2.8.2定制的过渡调色 ■7.2.8.3定制的数据流压缩 ■7.2.8.4定制的数据流过滤 ■7.2.8.5定制的字体管理 ■7.2.8.6动态产生的打印机命令 ■7.2.8.7处理设备管理的表面 7.2.8.1定制的色彩格式 Unidrv支持好几种色彩格式,这些色彩格式在微软统一打印机驱动程序一章的处理色彩格式中列出。对这些格式来说,Unidrv在向打印机发送之前,转换GDI位图到正确的格式。如果打印机接受Unidrv不支持的格式,必须提供一个交换插件以实现IPrintOemUni::ImageProcessing方法。 如果实现IPrintOemUni::ImageProcessing方法,并且用户已经选择了Unidrv不能处理的色彩格式(色彩模式选项),这样,每次一个缓冲区中的GDI位图数据准备打印时,Unidrv调用该方法并传寄位图的地址作为一个输入的参数。该方法必须转换位图到专用的格式,如果需要,将执行定制的过渡调色操作,并调用IPrintOemUni::DrvWriteSpoolBuf方法以发送被修改了的位图到打印机假脱机。它也必须调用IPrintOemUni::DrvXMoveTo及IPrintOemUni::DrvYMoveTo方法以修改光标的位置。更多的关于这些操作的信息,参考关于IPrintOemUni::ImageProcessing的描述。 如果一个绘制插件实现IPrintOemUni::ImageProcessing,它也能实现IPrintOemUni::MemoryUsage方法。 7.2.8.2定制的过渡调色 如在微软统一打印机驱动程序一章中的用Unidrv过渡调色部分所解释的,Unidrv允许GDI执行过渡调色操作,也可以由打印机设备、或由定制的驱动程序代码来执行。本部分解释了怎样在定制的驱动程序代码中执行过渡调色的操作。 有两种类型可用的定制: 定制的过渡调色模式 定制的过渡调色方法 定制的过渡调色模式 过渡调色模式可以在资源DLL中指定,或者它们可以由一个实现IPrintOemUni::HalftonePattern方法的绘制插件来产生。如在微软统一驱动程序一章用Unidrv过渡调色中所解释的,实现IPrintOemUni::HalftonePattern有两个原因: 定制的模式是在一个资源DLL中提供的,并且该模式是加密的。 定制的模式不是在资源DLL中提供的,相反,是由IPrintOemUni::HalftonePattern产生的。 IPrintOemUni::HalftonePattern方法的目的是给Unidrv返回一个可用的取中色模式,它按次序传递给GDI。该方法可以解码一个以加密格式存储于资源DLL中的模式,或者它可以在执行过程中产生一个模式。 如果实现IPrintOemUni::HalftonePattern,GPD文件必须在每一个取中色的的*Option条目中包括一个*HTCallbackID属性,这一个*Option条目是用于使用哪一种定制的模式时指定过渡调色的方法。更多的关于这一属性的信息,可参考第4章微软统一打印机驱动程序的取中色间特性的选项属性部分。 定制的过渡调色方法 对一个使用Unidrv的打印机来说,提供实现定制的过渡调色的方法的代码需要下面的步骤: 提供一个实现IPrintOemUni::ImageProcessing方法的绘制插件。 在打印机的GPD文件中包括一个过渡调色的*Feature条目,用每一个包含*Option的条目表示一个过渡调色方法。(也可以包括标准的或定制的过渡调色方法) IPrintOemUni::ImageProcessing方法作为输入收到GDI位图,该方法必须执行过渡调色操作,基于当前选择的过渡调色方法,并返回结果位图到Unidrv。 如果一个绘制插件这现了IPrintOemUni::ImageProcessing,它也可以实现IPrintOemUni::MemoryUsage。 更多的关于过渡调色的信息,参考第4章微软统一打印机驱动程序中的用Unidrv过渡调色部分的内容。 7.2.8.3定制的数据流压缩 Unidrv允许数据压缩操作由定制的代码来执行。 为执行定制的压缩操作,必须做下面的内容: 提供一个实现IPrintOemUni::Compression方法的绘制插件。 在GPD文件中包括一个CmdEnableOEMComp命令条目。更多的关于这一命令的信息,参考第4章微软统一打印机驱动程序中的光栅数据压缩命令部分的内容。 IPrintOemUni::Compression方法作为输入收到扫描行数据。该方法必须压缩数据并将结果返回到Unidrv。CmdEnableOEMComp命令条目指定必须发送到打印机的命令,从而使打印机可以接收这些压缩数据的命令。对要发送到打印机的每一个扫描行,Unidrv调用IPrintOemUni::Compression以压缩这些扫描行,然后,如果这是唯一可用的压缩方法,Unidrv在压缩数据之后,向打印机发送由CmdEnableOEMComp命令条目指定的命令。 如果打印机小驱动程序包括允许Unidrv支持的压缩方法的GPD条目,则Unidrv将对每一个扫描行数据试用每一种压缩算法并选择产生最好结果的压缩算法。更多的关于Unidrv的压缩能力的信息,参考第4章微软统一打印机驱动程序中的压缩光栅数据部分的内容。 在同一时间,只能有一种定制压缩算法被允许。 7.2.8.4定制的数据流过滤 Unidrv允许定制的代码执行最终图像数据的后处理,并在其被送至假脱机之前。这样的处理包括删除相邻的点或者其他任何类型的Unidrv不能提供的数据过滤操作。 为执行最终的图像数据的后处理,必须提供一个实现IPrintOemUni::FilterGraphics方法的绘制插件。 IPrintOemUni::FilterGraphics方法接收扫描行数据作为输入,该方法必须处理数据并将数据通过调用IPrintOemUni::DrvWriterSpoolBuf发送到假脱机。如果IPrintOemUni::FilterGraphics方法被实现,Unidrv不支持假脱打印机数据。相反,它发送每一个数据块到IPrintOemUni::FilterGraphics方法。 7.2.8.5定制的字体管理 Unidrv对PCL打印机支持下载的软字体作为位图或TrueType字体的轮廓。对设备字体,Unidrv支持PCL、CAPSL以及PPDS打印机命令格式。对其他格式,定制的字体管理代码必须被在一个绘制插件中提供,下面的一系列IPrintOemUni方法,可以被实现。 IPrintOemUni::DownloadFontHeader 用于从Unidrv获得软字体的头信息并将信息下载到打印机 IPrintOemUni::DownloadCharGlyph 用于给打印机下载一个软件字体的字符集 IPrintOemUni::OutputCharStr 用于控制字母的打印 IPrintOemUni::SendFontCmd 用于修改一个打印机的设备字体选择命令,如果必要,然后就发送它到打印机。 IPrintOemUni::TextOutAsBitmap 用于创建文本字符串的位图图像 IPrintOemUni::TTDownloadMethod 用于指定Unidrv在向打印机发送专用的软件字体时应当使用的符号格式。 Unidrv提供了一个回调函数,UNIFONTOBJ_?GetInfo,那个绘制的插件可以调用获得字体或字符的信息。 对设备字体,字体描述必须被以Unidrv字体规格文件(.ufm文件)及翻译表文件(.gtt文件)的格式来提供。 对Cartridge字体,可以在资源DLL中提供字体描述并在GPD文件中用Cartridge字体条目来指定。字体描述可以Unidrv字体格式文件(.uff文件)的形式提供。 对可下载的PCL软件字,字体描述必须以Unidrv字体格式文件(.uff文件)来提供。 Unidrv字体规格文件 打印机支持的每一种设备字体必须用Unidrv字体规格文件(.ufm文件)来表示。.ufm文件是一个用在DDK的打印机驱动程序函数及结构一章中的Unidrv字体规格中描述的结构组建的二进制文件。.ufm中的第一个结构是UNIFM_HDR,它包含有到文件的其他结构的偏移量。 更多的关于创建.ufm文件的信息,可参考第四章中的微软小驱动程序开发工具部分的内容。 Unidrv也支持.ifi文件,是一种为Windows NT 4.0创建的字体规格文件。 字符翻译表文件 打印机支持的每一种设备字体文件必须用一个字符翻译表文件(.gtt文件)来表示,一个.gtt文件是一个用在Unidrv字符翻译表一章中的打印机驱动程序函数及结构部分中描述的结组构建的二进制文件。.gtt中的的第一个结构是UNI_GLYPHSETDATA,它包含有到文件的其他结构的偏移量。 更多的关于创建.gtt文件信息,参考第4章中的微软小驱动程序开发工具部分的内容。 Unidrv也支持为Windows NT 4.0创建的字符翻译文件,它用RLE压缩并具有一个.rle的扩展名。 Unidrv字体格式文件 对那些没有在GPD文件中用字体Cartridge条目指定的Cartridge字体来说,该字体必须以Unidrv字体格式文件(.uff文件)来描述。另外,下载的PCL软字体必须用.uff文件来指定。 一个.uff二进制文件是用下面的结构组建起来的。 Unidrv字体格式结构。它定义了内容及.uff文件的结构。 Unidrv字体规格结构。它定义了每种字体的规格。 Unidrv字符翻译表结构。它定义了字体所用的字符集。 另外,对下载的PCL软字体来说,二进制数据存储于.ufm文件中。 创建.uff文件是商家提供的字体安装软件的责任。Unidrv读取一个打印机的.uff文件以获得字体及字符信息。字体安装程序应当在字体被增加或删除时修改.uff文件内容。更多的关于创建字体安装程序的信息,参考定制的Unidrv的字体安装程序部分的内容。 打印机的所有.uff文件必须被保存在%SystemRoot%\System32\Spool\Drivers\Unifont目录中。为关联单独的.uff文件与专用的打印机,安装软件必须调用SetPrinterData函数(在平台的SDK文档中有描述)以在每一个打印机的注册关键字中创建注册值,并指明每一个值的维持者。 注册表值名称及类型 值定义 维持者 “ExternalFontFile” REG_SZ 一个.uff文件的文件名,指定当前被安装的字体。字体可以被下载或者包含在一个Cartridge中 字体安装程序  “ExtFontCartFile” REG_SZ 一个.uff文件的文件名,指定所有所有包含在字体Cartridge中为“ExtFontCartNames”列出的所有字体 字体安装程序  “ExtFontCartNames” REG_MULTI_SZ 所有的可以被安装在一个打印机上的Cartridge字体 字体安装程序  “FontCart” REG_MULTI_SZ 当前安装在一个打印机上的所有的字体Cartridge Unidrv用户接口  在给一个打印机增加了字体Cartridge之后,系统管理者将运行字体安装程序,它负责从由“ExtFontCartFile”指定的.uff文件中拷贝字体描述到由“ExternalFontFile”指定的.uff文件中。同样,字体安装程序必须删除.uff中在一个Cartridge字体被删除时由“ExtFontCartFile”指定的字体描述。 7.2.8.6动态产生的打印机命令 每一次在一个GPD文件中为一个Unidrv的小驱动程序指定打印机命令时,可以采用下面两种方法中的一种: 把命令字符串放于GPD文件中 当将命令字符串放于GPD文件中时,Unidrv在适当的时间发送命令到打印机假脱机。这些命令字符串可以包括标准变量,这些变量在Unidrv发送命令之前都会进行求值。 提供一个回调函数 如果提供一个回调函数,Unidrv在它发送命令时调用该函数,该函数主要负责发送命令到打印机假脱机。这就使得可以包括那些动态产生的命令字符串并然后将它送往打印机。 为设置一个命令字符串在GPD文件中,需要在命令的*Command条目中包括一个*Cmd属性。 为提供动态产生的命令字符串的代码,必须做下面的事情: 提供一个绘制的插件以实现IPrintOemUni::CommandCallback方法。 包括一个*CallbackID命令属性,并包括一个可选的*Param属性,在GPD文件的命令的*Command条目中。 当Unidrv准备发布一个打印机命令,它检查小驱动程序的数据库以确定是否该命令已经用一个*Cmd属性或一个*CallbackID属性指定。在前一种情况下,Unidrv发送命令字符串到打印假脱机;在后一种情况,Unidrv调用IPrintOemUni::CommandCallback方法,传递*CallbackID及*Params值作为输入参数。 打印机命令被在第4章微软打印机驱动程序中讨论过。更多的关于*Cmd、*Callback及*Params属性的信息,同样参考第4章中的命令属性部分。 7.2.8.7处理设备管理的表面 当Unidrv绘制器打印页面图像时,它用GDI管理的绘制表面。所有的图像被作为位图绘制。对设备提供能力不能在此种情形下采用,如绘制向量等。可以对一个设备管理的绘制表面提供定制的驱动程序支持。为支持一个设备管理的表面,必须提供一个插件以实现下面的内容: 对所有的Unidrv支持的DDI绘制函数的一套分出函数。必须能分出下面的这些函数 DrvAlphaBlend DrvBitBlt DrvCopyBits DrvDitherColor DrvFillPath DrvGradientFill DrvLintTo DrvPlgBlt DrvRealizeBrush DrvStretchBlt DrvStretchBltROP DrvStrokeAndFillPath DrvStrokePath DrvTextOut DrvTransparentBlt IPrintOemUni::EnableDriver方法,它用于向Unidrv提供指向DDI分出函数的指针。 IPrintOemUni::DriverDMS方法,它通知Unidrv一个设备管理的表面被应用,并指定对这一表面哪一个被定义的分出函数将被用到。 当绘制一个设备管理的表面时,分出函数不能回调GDI的以Eng-为前缀的支持服务。但是,它们可以创建一个临时的位图表面,并传递那个表面的句柄到以Eng-为前缀的函数(参考第3章中的绘制一个打印作业部分)。 一个打印作业要被绘制的每一次,都要调用IPrintOemUni::DriverDMS方法,因此绘制插件可以指定对每个作业绘制表面的类型(GDI管理或设备管理)。基于所需要的用户接口上的可选择的选项或者是提供一个用户接口插件以进行表面选择。 在设备管理的表面上绘制文本 绘制插件必须分出Unidrv的DrvTextOut函数(以及其他DDI绘制函数)。为一个设备管理的表面创建文本并包括在以下四个函数之间的相互作用: Unidrv的DrvTextOut函数 绘制插件的DrvTextOut分出函数 Unidrv的IPrintOemUni::DrvUniTextOut方法 绘制插件的IPrintOemUni::TextOutAsBitmap方法 步骤包括在设备管理的表面上显示文本,如下: GDI调用Unidrv的DrvTextOut函数。 Unidrv调用绘制插件的DrvTextOut分出函数。 分出函数发送命令到设备以指定文本刷、旋转及裁剪区域。 分出函数调用Unidrv的IPrintOemUni::DrvUniTextOut方法,它试图使用可下载的字体到输出文本。这一方法也处理基于字符的剪贴。 如果IPrintOemUni::DrvUniTextOut不能用可下载的字体(由于该字体不能获得或者旋转),它调用插件的IPrintOemUni::TextOutAsBitmap方法,它当绘制一个位图来绘制文本。 在IPrintOemUni::DrvUniTextOut返回后,DrvTextOut分出函数必须绘制下划线及中横线,基于由DrvTextOut函数的prclExtra参数指定的距形,并使用矢量命令(如果支持)。 7.3实现打印机驱动程序的COM接口 这一部分解释了怎样基于DDK提供的示例代码构建一个插件。也解释了用于打印机驱动程序及插件通讯的调用顺序。提供了下面的主题: 打印机驱动程序的接口标识符 创建插件 从打印机驱动程序存取插件接口 从插件存取打印机驱动程序接口 7.3.1 打印机驱动程序的接口标识符 在prcomoem.h中定义了一套GUID结构,在这些GUID结构中的每一个都是用于打印机驱动程序(Unidrv及Pscript)及插件之间通讯的一个COM接口的接口标识符。 对Windows 2000,定义了下面的GUID: IDD_IPrintOemUI IDD_IPrintOemDriverUI IDD_IPrintOemUni IDD_IPrintOemDriverUni IDD_IPrintOemPS IDD_IPrintOemDriverPS 每一个GUID标识一个接口的版本。对Windows 2000,每一个接口只有一个版本,随着未来的一些版本的操作系统的发布,也许需要去修改一个或多个接口。如果定义了一个新版本的接口,一个新的GUID将被加入到这一列表中。 用户接口插件及绘制插件必须鉴别它们支持的接口版本。打印机驱动程序(Unidrv或Pscript)将调用一个插件的IUnknown::QueryInterface方法(在平台的SDK文档中有描述),指定一个接口标识符作为输入。如果插件支持专用的版本,方法必须返回一个接口的指针并返回有S_OK的状态值。否则,它必须返回E_NOINTERFACE。驱动程序用一个对最新版本的接口标识符开始并继续使用以前版本的标识符调用QueryInterface,直到方法返回S_OK或者驱动程序穷举完列表中的版本标识符为止。 同样,Unidrv及Pscript为IPrintOemDriver、IPrintOemDriverUni及IPrintOemDriverPS COM接口提供IUnknown::QueryInterface方法。插件则应当调用合适的接口的QueryInterface方法以确定驱动程序的支持接口版本并接收一个接口指针。 7.3.2创建插件 所有的打印机驱动程序插件必须定义DllMain,DllGetClassObject及DllCanUnloadNow函数。他们也必须实现IclassFactory COM接口及IPrintOemUI,IPrintOemUni或IPrintOemPS COM接口中之一。 当创建一个用户接口插件或者一个绘制插件,必须使代码基于这一DDK提供的UI插件的实例或绘制插件的实例。 定义一个DllMain函数(在平台的SDK文档中有描述),这是所有Win32 DLL的入口点。 定义并导出一个DllGetClassObject函数(在平台的SDK文档中有描述)。 打印机驱动程序调用这一函数以获得对插件实现的IClassFactory接口(在平台的SDK文档中有描述)的存取。当驱动程序调用DllGetClassObject,它指定下面一类的标识符(在prcomoem.h中定义) CLSID_OEMNI——对UI插件 CLSID_OEMRENDER——对绘制插件 驱动程序也指定IID_IClassFactory的接口标识符。 DllGetClassObject函数必须创建一个它的IclassFactory接口的实例并为它返回一个指针,如在示例代码所示。 实现IClassFactory COM接口 IClassFactory接口的CreateInstance方法应当创建一个下面的COM接口的插件实现的实例: IPrintOemUI COM接口 IPrintOemUni COM接口 IPrintOemPS COM接口 CreateInstance方法的一个输入是接口的标识符。驱动程序用IID_Iunknown的一个接口标识符调用CreateInstance,意思是CreateInstance方法必须为被创建的实例的IUnknown接口(在平台的SDK文档中描述)返回一个指针,如在示例代码中所示。 实现IPrintOemUI,IPrintOemUni或者IPrintOemPS COM接口中的一种,包括标准的IUnknown接口,如在例子代码中所示。 由驱动程序调用的第一个被实现的方法是IUnknown接口的QueryInterface方法(在平台SDK文档中有描述)。这一方法为打印机以一个输入参数收到一个接口的标识符,驱动程序调用该方法以确定插件支持那一个版本的接口并收到指向支持接口的指针。 定义并导出DllCanUnloadNow函数(在平台的SDK文档中有描述)。 如果所有被实现的插件的IPrintOemUI,IPrintOemUni或者IPrintOemPS接口的实例已经被发布,DllCanUnloadNow函数必须返回S_OK,返回S_OK意指对驱动程序来说,插件不能被加载。 7.3.3从打印机驱动程序存取插件接口 如果一个UI插件或绘制插件已经被安装,打印机驱动程序(Unidrv或Pscript)将使用下面的调用序列以获取对插件的IPrintOemUI,IPrintOemUni或者IPrintOemPS接口的存取。 驱动程序调用LoadLibrary以加载插件的DLL,它引起插件的DllMain函数被调用。 驱动程序调用插件的DllGetClassObject函数,它返回一个指向插件的IDlassFactory接口的指针。 驱动程序调用IClassFactory接口的CreateInstance方法,指定一个IID_Unknown的接口标识符,它引起方法创建插件的IPrintOemUI,IPrintOemUni或者IPrintOemPS接口的实例并返回指向实例的IUnknown接口的指针。 驱动程序调用IUnknown接口的QueryInterface方法以确定插件支持IPrintOemUI,IPrintOemUni或者IPrintOemPS接口的那一版本,并接收一个指向支持接口的指针。 驱动程序调用插件的接口的PublishDriverInterface方法以使得驱动程序的IPrintOemUI,IPrintOemUni或者IPrintOemPS接口对插件可用。 如果插件已经实现了IPrintOemUni接口,驱动程序调用IPrintOemUni::GetImplementedMethod以确定哪一个接口方法已经被实现。 7.3.4从插件存取打印机驱动程序接口 如果一个插件能够调用属于驱动程序的IPrintOemUI,IPrintOemUni或者IPrintOemPS接口,它必须从驱动程序获得一个指针,如下: 插件必须实现IPrintOemUI,IPrintOemUni或者IPrintOemPS接口的PublishDriverInterface方法。 当驱动程序(Unidrv或Pscript)调用插件的PublishDriverInterface方法,它提供一个指向IPrintOemUI,IPrintOemUni或者IPrintOemPS实例的IUnknow接口。 插件必须使用IUnknown接口指针去调用Iunknown::QueryInterface,指定表示是期望的版本的IPrintOemUI,IPrintOemUni或者IPrintOemPS接口的接口标识符(更多的信息,可参考打印机驱动程序的接口标识符部分)。 如果插件指定一个接口的标识符表示一个驱动程序支持的接口,QueryInterface返回一个指向IPrintOemUI,IPrintOemUni或者IPrintOemPS接口的指针。注意,驱动程序在返回对插件的接口指针之前调用接口的AddRef方法(在平台的SDK文档中描述)。插件应当保存该指针,这样它可以在以后被用于调用接口方法。 当IPrintOemUI,IPrintOemUni或者IPrintOemPS接口指针不再需要时,插件必须调用接口的Release方法(在平台的SDK文档中有描述)。 7.4安装定制的驱动程序组件 当为微软打印机驱动程序提供定制的组件时,必须为组件的安装提供一个.ini文件(如果打印机不被ntprint.inf支持,必须提供一个printer.inf文件)。 .ini文件必须包含一个OEMFiles部分,在这一部分,每一个定制的组件是用下面之一的条目来描述的: OEMDriverFilen 命名一个绘制插件 OEMConfigFilen 命名一个用户接口插件 这时,n指定了安装程序安装文件的顺序,对所有类型的插件来说,为n指定的数字必须是连续的,从1开始。 例如,如果提供两个绘制插件及一个用户接口插件,打印机模型是XYZ,.ini文件必须如下所示: [OEMFiles] OEMDriverFile1=XYZDRV1.DLL OEMConfigFile1=XYZUI1.DLL OEMDriverFile2=XYZDRV2.DLL 在等号(=)之前或之后,都不允许有空格,文件名不能名含路径说明。 在例子中,指定了两个绘制插件,基于为OEMDriverFilen指定的n值。xyzdrv1.dll在xyzdrv2.dll之前被安装。Unidrv及Pscript驱动程序调用插件,并按其安装的顺序,此后,当一个驱动程序需要调用DDI分出函数及由这些插件提供的COM方法时,xyzdrv1.dll将在xyzdrv2.dll之前被调用。 .ini文件名应当反映打印机产品名称并且是唯一的以避免在.ini文件中与其他打印机名称相冲突。注意如果提供一个与Windows NT 4.0后端口(Back-Porting)通信的绘制插件或用户接口插件。.ini文件名必须与.gpd及.ppd文件匹配。(即xyz.ini必须用于xyz.gpd或xyz.ppd)。这一限制对Windows 2000及以后版本不适用。 一个.ini文件可以包含ANSI或UNICODE文本,但是推荐使用UNICODE文本。在一个.ini文件中,每行以#开始的是注解的内容。 更多的关于.inf及.ini文件的信息,参考Windows 2000驱动程序开发指南中的第一卷,即插即用,电源管理及建立设计指南部分。也可参考第4章微软统一打印机驱动程序中的安装一个Unidrv小驱动程序部分。 7.4.1Unidrv的定制的字体安装程序 对没有在打印机的GPD文件中通过字体Cartridge条目描述的Cartridge字体,需要厂商提供的字体安装程序软件。这些字体需要用Unidrv字体格式文件(.uff文件)来描述,而厂商提供的字体安装程序就负责创建.uff文件。 厂商提供的字体安装程序应当提供对下载的PCL软字体的支持。 下面两种技能为创建定制的安装程序提供: 提供一个用户接口插件 插件必须实现下面的COM接口方法: IPrintOemUI::FontInstallerDlgProc IPrintOemUI::UpdateExternalFonts 提供一个单独的执行文件 在字体安装时,可执行文件必须通过调用SetPrinterData(在平台的SDK文档中描述)存储它的名字在注册表中,并为“FontInstaller”关键字指定一个值。 Unidrv用下面的算法定位字体安装程序: 1.如果字体安装程序可执行文件的名字被存储于注册表中,Unidrv不允许系统管理员从打印机的属性表单中选择字体安装程序。相反,管理员必须运行提供的可执行文件。 2.如果一个安装程序执行文件不可用,Unidrv允许从打印机的属性表单中选择字体安装操作。Unidrv确定是否一个用户接口插件已经被安装。如果是,它的字体安装方法被调用。如果一个用户接口插件没有被安装,或者它的字体安装方法返回了E_NOTIMPL,则驱动程序使用它自已的容错安装程序。 第8章 定制打印假脱机组件 本章解释怎样定制打印机假脱机组件,并提供了以下这些主题: 编写打印处理器 编写打印监示器 编写网络打印提供者 8.1编写打印处理器 本部分提供下面的主题: ■8.1.1对打印处理器的介绍 ■8.1.2打印处理器示例 ■8.1.3由打印处理器定义的函数 ■8.1.4处理一个打印作业 ■8.1.5安装一个打印处理器 8.1.1对打印处理器的介绍 打印处理器是用户模式的一些DLL,主要负责转换打印作业的假脱数据到打印机监示器的格式。也负责处理应用程序对暂停、重新开始及撤消打印作业等的请求。 打印作业的假脱数据包含在一个假脱文件中。打印处理器读取这些文件,在数据流上执行转换操作,并将转换的数据写到假脱机。假脱机然后发送数据流到合适的打印机监示器。 微软Windows 2000包括列在下表中的打印处理器。 打印处理器 输入数据类型 输出数据类型 winprint.dll EMF RAW TEXT RAW  stmpsprt.dll PSCRIPT1 RAW  关于数据类型的信息,参考下面的主题: EMF数据类型 RAW数据类型 TEXT数据类型 PSCRIPT1数据类型 可以创建一个定制的打印处理器以支持一个Windows 2000不支持的数据类型。也可以提供一个定制的打印处理器以支持一种或更多种的被支持的数据类型,这样,就允许修改由打印处理器所提供的能力。 打印处理器与在打印机驱动程序安装时与打印机的驱动程序相关,因此,可以并存多个打印处理器支持同一种数据类型。更多的信息,参考安装打印机处理器部分的内容。 EMF数据类型 增强型元文件(EMF:Enhanced Metafile)数据类型由调用GDI函数的指令构成。打印处理器必须调用GDI函数以绘制可打印的图像。GDI函数对打印驱动程序的打印机图形DLL做调用,它绘制图像并将它作为RAW数据向假脱机发送(通过调用EngWritePrinter)。 Windows NT?/Windows 2000客户端发送EMF数据到Windows NT/Windows 2000打印服务器。EMF数据是设备独立并可以比RAW数据类型更快速地发送到服务器。当一个应用程序请求是从本地到服务器,打印作业也就被假脱处理为EMF数据,这样就允许在作业被后台假脱机绘制时能够对应用程序的快速返回。 更多的关于EMF数据类型的信息,参考Windows 2000专业版资源包(Windows 2000 Professional Resource Kit)或Windows 2000服务器版的资源包(Windows 2000 Server Resource Kit)。更多的关于增强型元文件的信息,参考平台SDK文档。 RAW数据类型 RAW数据可以不用进一步处理就被发送到打印机监示器。打印机处理器只发送这一数据到假脱机(通过调用WritePrinter,在平台的SDK文档中描述),有时插入表格。一个RAW数据文件的实例是一个由PCL命令组成的。无论客户端或服务器端那一方不支持Windows NT/Windows 2000 EMF或者服务器管理员禁止了EMF支持,打印作业以RAW数据格式从客户端发送到服务器端。在这些情况下,客户端的图像绘制在打印作业发送到服务器端之前进行。 (Postscript命令可以被当作RAW数据,如果目标打印机支持Postscript。另一方面,stmpsprt.dll打印处理器将Postscript的输入向非Postscript的打印机进行解释,在这种情况下,Postscript不是RAW数据。) 更多的关于RAW数据类型的信息,参考Windows NT工作站资源指南或者Windows NT资源指南部分的内容。 TEXT数据类型 单纯由ANSI文本构成的。打印机处理器调用GDI以用打印机设备容错字体绘制字母,并发送RAW格式的结果输出到假脱机(通过调用Writeprinter,在平台的SDK文档中描述)。处理器等同于用写字板打开输入文件并然后打印文件(该格式用于不能打印文本字符的打印机)。 更多的关于TEXT数据类型的信息,参考Windows NT工作站资源指南或者Windows NT 服务器资源指南部分的内容。 PSCRIPT1数据类型 PSCRIPT1数据的目的是要在一个非Postscript打印机上打印的Postscript数据。打印处理器解释Postscript命令并且向输出文件写RAW格式的结果数据。 更多的关于PSCRIPT1数据类型的信息,参考参考Windows NT工作站资源指南或者Windows NT 服务器资源指南部分的内容。 8.1.2打印处理器示例 genprint.dll的源代码,一个接受EMF数据、RAW数据及TEXT数据作为输入的打印处理器的实例,包含于本DDK中。其代码位于DDK实例的目录树中的genprint的子目录中。 8.1.3由打印处理器定义的函数 打印机处理器必须导出下表列出的函数: 函数名称 描述 ClosePrintProcessor 关闭打印处理器  ControlPrintProcessor 提供对打印文档的控制  EnumPrintProcessorDatatypes 枚举打印机处理器支持的数据类型  GetPrintProcessorCapabilities 为专用的输入数据类型返回打印处理器的能力  OpenPrintProcessor 为打印打开打印处理器  PrintDocumentOnPrintProcessor 在一个打印处理器上打印文档   8.1.4处理一个打印作业 当一个假脱机准备发送一个打印作业到打印处理器时,它调用打印处理器的OpenPrintProcessor函数。这一函数执行初始化活动并返回一个句柄。 假脱机可以调用PrintDocumentOnPrintProcessor,它是打印处理器的一个函数,可以从输入格式到输出格式转换数据流,并返回被转换的流到假脱机。 如果输入的格式是Windows NT/Windows 2000的EMF,则PrintDocumentOnPrintProcessor函数将通过使用GDI的打印处理器函数来控制EMF记录的回放。这些函数提供了一个打印处理器与打印机驱动程序之间的接口。该接口允许打印处理器控制打印机页面的物理布局及实现诸如在一个物理页面上打印多个文档页面(“N-up”打印)、反向打印、每页打印多个拷贝等等。 一个打印处理器的输出数据流必须返回到假脱机。通常,如果数据转换需要与打印机驱动程序的打印机图形DLL(对EMF格式作为输入数据时)之间交互作用,则图形DLL通过调用EngWritePrinter。另外一方面,如果转换不调用打印机图形DLL(当输入数据是RAW类型时),则打印处理器调用WritePrinter(在平台的SDK文档中描述)。 PrintDocumentOnPrintProcessor函数可以从假脱机到打印处理器的ControlPrintProcessor函数同步调用中断。这一函数实现应用程序的暂停、恢复及撤消一个打印作业等能力。 在PrintDocumentOnPrintProcessor结束转换数据流并返回后,假脱机调用打印机处理器的ClosePrintProcessor函数。 8.1.4.1为打印处理器使用GDI函数 一套用户模式的GDI函数被gdi32.dll导出,对被打印处理器用于处理Windows NT/Windows 2000的EMF作为输入格式。下面的表列出了可以提供的函数: 函数名称 描述 GdiDeleteSpoolFileHandle 释放一个假脱机文件句柄  GdiEndDocEMF 对一个打印文档完成EMF回放操作  GdiEndPageEMF 为一个物理页面完成EMF回放操作,并从打印机跳页  GdiGetDC 返回一个打印机设备设备环境的句柄  GdiGetDevmodeForPage 返回一个文档页面的DEVMODE结构  GdiGetPageCount 返回文档页面的数字  GdiGetPageHandle 返回文档页面的句柄  GdiGetSpoolFileHandle 返回一个假脱文件的句柄,它对其他的GDI函数是作为一个必需的输入  GdiPlaypageEMF 播放与文档页面相关的EMF记录  GdiResetDCEMF 重置打印机的设备设备环境  GdiStartDocEMF 对打印作业执行初始化操作  GdiStartPageEMF 对物理页面执行初始化操作   一个EMF打印处理器的PrintDocumentOnPrintProcessor应当调用GdiGetSpoolFileHandle以获得假脱文件的句柄及调用GdiGetDC以获得打印机设备设备环境句柄。然后它可以执行下面的操作: 对每一个打印作业文档,GdiStartDocEMF必须在任何EMF记录被运行之前调用并且GdiEndDocEMFnt必须在EMF记录运行完之后被调用。 对每一个要被打印的物理页面来说,GdiStartPageEMF必须在任何文档页面在页面上被绘制之前被调用,GdiEndPageEMF必须在最后一个文档页面被在物理页面上绘制完之后被调用 对每一个将要绘制在物理页上的文档页面来说,GdiGetDevmodeForPage是否必须被调用取决于DEVMODE结构的内容是否在最后一个文档页被绘制之后改变。如果DEVMODE已经改变,一个新的物理页而必须开始(通过调用GdiEndPageEMF及GdiStartPageEMF),打印机的设备设备环境必须通过调用GdiResetDCEMF来改变。一个文档页面必须被绘制在物理页面上,通过首先调用GdiGetPageHandle以获得文档页面句柄,并然后调用GdiPlayPageEMF以绘制页面。 在打印作业已经完成绘制后,打印处理器必须调用GdiDeleteSpoolFileHandle。 如果一个打印处理器在它开始打印页面之前需要知道总的打印页数(如反向打印时),它可以调用GdiGetPageCount,但是这一函数将一直到假脱过程完成并且在假脱时禁止了打印能力时才返回。 如果一个打印处理器用这些GDI函数,它的EnumPrintProcessorDataTypes函数必须返回“NT EMF”作为一个支持的数据类型,它表示一般的Windows 2000 EMF格式。打印处理器不能修改EMF记录。 8.1.5安装一个打印处理器 为安装一个打印处理器,一个安装应用程序必须调用假脱机的AddPrintProcessor函数,它在平台的SDK文档中描述。 为关联一个打印机处理器与一个打印机驱动程序,它的文件名称必须通过使用PintProcessor条目而被列在.inf文件中。这一条目必须为每一个将要被与打印处理器关联的打印机驱动程序所包含。更多的信息,可以参考第10章的打印机INF文件部分的内容。 当一个安装应用程序调用假脱机的AddPrinter函数,指定一个PRINTER_INFO_2结构作为一个输入参数,它指定打印机处理器名称(从.inf文件获得)作为一个结构成员。(AddPrinter函数及PRINTER_INFO_2结构在平台的SDK文档中描述)。 8.2编写打印监示器 打印监示器负责传送打印机数据流从打印假脱机到一个合适的端口驱动程序。两种类型的打印机监示器被定义,即——语言监示器(language monitors)及端口监示器(port monitors)。这一章将描述两种监示器,并提供设计与实现的指南。 提供以下内容的主题: ■8.2.1语言监示器 ■8.2.2端口监示器 ■8.2.3语言和端口监示器交互 ■8.2.4由打印监示器定义的函数 ■8.2.5初始化一个打印监示器 ■8.2.6打开及关闭一个端口 ■8.2.7打印一个打印作业 ■8.2.8管理端口 ■8.2.9为在集群打印服务器中使用转换打印监示器 ■8.2.10安装打印监示器 8.2.1语言监示器 语言监示器是一些用户模式的DLL,主要用于两个目的: 它们在打印机假脱机与双向打印机之间提供了一个全双工的通信通道,从而具有了提供软件可存取的状态信息的能力。 增加打印机控制信息到数据流,如由打印机作业语言定义的命令等。 微软提供了一个语言监示器pjlmon.dll,它支持打印作业语言(PJL:Printer Job Language),并为PJL打印机提供双向通讯。这一监示器被作为语言监示器示例包含在这一DDK中。 对单向或双向打印机来说,定制的语言监示器可编写用于支持其他作业控制语言。 语言监示器是可选的,并且只与特殊的包含在打印机的.inf中的打印机类型相关,如在安装打印监示器部分所描述的。 如果一个语言监示器与打印机关联,语言监示器从打印机处理器收到打印机的数据流,修改它并将之传递到打印机的端口端示器。更多的信息,可参考语言监示器及端口监视器交互部分的内容。 8.2.1.1语言监示器示例 pjlmon.dll的源代码,支持双向PJL语言的语言监示器就包括在这一DDK中。其代码位于DDK的实例目录树中的\pjlmon子目录中。 8.2.2端口监示器 端口监示器由用户模式的一些DLL组成。它们负责在用户模式的打印假脱机及访问I/O硬件端口的内核模式的端口驱动程序之间提供通讯。一个端口监示器通常使用CreateFile,WriteFile,ReadFile及DeviceIOControl函数,在平台的SDK文档中描述的,以与内核模式的端口驱动程序通讯。端口监示器也负责管理及配置一个服务器的打印机端口,如在管理端口部分所描述的。 Windows NT/Windows 2000用户的“打印机”视图实际上是一个打印队列,是一个或多个物理打印机设备都可以连接到的队列。一个端口是一个在打印队列及一个单一打印机设备之间的物理连接。。每一个端口端示器支持一个或多个的一种或多种端口类型的实例。例如,localmon.dll这一示例的端口监示器可以支持所有的服务器的本地COM及LTP端口。(打印机文件夹)通过调用平台SDK文档中描述的AddPrinter函数来分配端口到端口监视器)。 对表示多个打印机设备(通过多个端口)的打印机队列来说,假脱机发送每个打印作业到第一个可用的端口。如果端口监示器指明一个可用端口忙或者遇到错误,假脱机将重新提交作业到队列,指定另外一个端口监示器支持的可用端口。 除了localmon.dll,Windows 2000提供几个附加的端口监示器。Windows NT服务器资源开发包中对每一个这样的端口监示器都做了描述。 可以写定制的端口监示器以支持附加类型的硬件I/O端口。 对Windows 2000来说,每一个端口监示器被分为两个DLL。 端口监示器UI DLL 端口监示器的用户接口DLL包含用户接口的功能并在打印客户系统上执行。 这一DLL必须驻留在客户系统的System32子目录中。 端口监示服务器DLL 一个端口监示器的服务器DLL包含端口通讯功能及在打印服务器上执行。它必须显示一个用户的接口。 UI DLL通过调用假脱机的Xcvdata函数与服务器的DLL通讯,在该DDK中包含一个端口监示器实例。 8.2.2.1端口监示器实例 localmonn.dll的源代码,即支持本地LPT及COM端口的端口监示器也在DDK中包含着。其代码位于DDK实例目录树的localmon及localui子目录中。 8.2.3语言和端口监示器交互 下面的图显示了打印机数据从打印处理器到打印机的路径图。其中图(a)包含一个与它相关联的语言监示器,图(b)不包含相关联的语言监示器。 插入图(a)(b)??? 如果一个语言监示器在打印机的安装过程中与打印机关联,语言监示器从假脱机打印处理器中收到打印机的数据流。语言监示器修改数据流并将它传递到打印机的监示器。 许多由打印机定义的监示器对语言监示器及端口监示器是相同的。通常,如果一个语言监示器在数据流通道中,假脱机将调用端口监示器的实现函数而语言监示器将调用端口监示器的同样的实现函数。例如,在PJL语言监示器(pjlmon.dll)中的WritePort函数增加PJL命令到数据流然后调用端口监示器的WritePort并将该流发送给端口驱动程序。 如果没有安装语言监示器,假脱机调用端口监示器的函数实现。 由于语言监示器及端口监示器是打印体系中分散的组件,定制的及微软提供的监示器可以被一起应用。这样,可以提供一个与微软提供的端口监示器一起工作的定制的语言监示器,反之亦然。 也可以提供一个单一的语言和端口监示器构成(combined language and port monitor)的组合式打印监示器。 8.2.3.1组合式语言和端口监示器 专用的打印机硬件可以被一个单一的定制打印监示器支持,它实际作为一个组合式的语言和端口监示器。如果这样一个监示器需要用户的交互以获得配置参数,它必须被分成UI DLL及一个服务器的DLL,下面的模型是对端口监示器的。而语言相关的功能则属于服务器DLL。 一个组合的监示器UI DLL必须定义端口监示器客户端DLL函数。它的服务器DLL必须定义端口监示器服务器DLL的函数及语言监示器的函数。 8.2.4由打印监示器定义的函数 下面的部分列出了在打印监示器中必须定义的函数: ■8.2.4.1语言监示器函数 ■8.2.4.2端口监示器客户DLL函数 ■8.2.4.3端口监示器服务器DLL函数 8.2.4.1语言监示器函数 下表列出了语言监示器必须定义的函数: 函数名称 描述 DllEntryPoint DLL入口点,通常称为DLLMain,它在平台的SDK文档中描述  ClosePort 关闭一个端口,在没有打印机与它相连时  EndDocPort 在一个端口上执行打印作业完成的任务  GetPrinterDataFromPort (可选)轮询存储在注册表中的端口值  InitializePrintMonitor2 初始化打印监示器并返回一个实例句柄  OpenPortEx 为一个新连接的打印机打开一个端口  ReadPort 从一个打印机端口读数据  StartDocPort 执行在一个端口上开始打印作业的操作  WritePort 向打印机端口写数据   8.2.4.2端口监示器客户DLL函数 下面的表格列出了一个端口监示器UI DLL必须定义的函数: 函数名称 描述 DllEntryPoint DLL入口点,通常称为DLLMain,它在平台的SDK文档中描述  AddPortUI 创建一个端口并通过显示一个通话框获得配置信息  ConfigurePortUI 配置一个前面增加的端口  DeletPortUI 删除一个端口  InitializePrintMonitorUI 初始化端口监示器的DLL   8.2.4.3端口监示器服务器DLL函数 下面的表中列出了端口监示器服务器DLL必须定义的函数: 函数名称 描述 DllEntryPoint DLL入口点,通常称为DLLMain,它在平台的SDK文档中描述  ClosePort 关闭一个端口,在没有打印机与它连接时  EndDocPort 在端口上执行打印作业完成的任务  EnumPorts 枚举打印机可用的服务器上的端口  GetPrinterDataFromPort (可选)发送一个I/O控制代码到端口驱动程序并返回结果  InitializePrintMonitor2 初始化打印监示器并返回一个实例句柄  OpenPort 打开一个打印机端口  ReadPort 从一个打印机端口读数据  SetPortTimeOuts (可选)在一个打开的端口上设置超时值  StartDocPort 执行在一个端口上要开始打印作业的任务  WritePort 向打印机端口写数据  XcvClosePort 在端口管理完成之后关闭一个端口  XcvDataPort 处理端口管理任务  XcvOpenPort 因为管理目的打开一个端口   8.2.5初始化打印监示器 当假脱机调用LoadLibrary以装载一个打印监示器DLL,系统立即调用DLL的DllEntryPoint函数。有一个一般情况都比较好的方法是,对入口函数都调用DisableThreadLibraryCalls函数(在平台的SDK文档中有描述),这样DLL就在线程被创建或删除时就不会没有通知到。 每一个DLL导出一个初始化函数,它在假脱机调用LoadLibrary之后调用。语言监示器DLL及端口监示器服务器DLL导出一个InitializePrintMonitor2函数。端口监示器UI DLL导出一个InitaializePrintMonitorUI函数。 这两个初始化函数负责给打印监示器定义的其余的函数返回指针,这样假脱机可以调用他们。初始化函数可以执行装载时的初始化操作。显示器的InitalizePrintMonitor2函数返回一个监示器的实例句柄。监示器应当分配本地内存以存储特定实例的信息,并使用监示器句柄作为一个分配内存的标识符。 第一次调用假脱机时,它装载所有的已经被安装的监示器DLL。在调用了监示器函数之后,假脱机调用每一个端口监示器的EnumPorts函数,它枚举监示器支持的端口。(一个监示器支持一个端口,是看是否该端口已被加到监示器的数据库中,如在增加一个端口中所描述的。)每一个支持的端口这时才被打开,如在打开及关闭端口部分中所描述的。 8.2.6打开及关闭一个端口 在增加一个端口之后,如在增加端口部分所描述的,假脱机就能通过调用合适的语言监示器的OpenPortEx函数打开它。 语言监示器用OpenPortEx函数以创建并返回端口句柄。通常,语言监示器调用它相关的端口监示器的OpenPort函数,并且语言监示器只返回从端口监示器的OpenPort函数获得的句柄。 如果语言监示器与一个端口不相关,假脱机直接调用端口监示器的OpenPort函数。 假脱机不允许在同一时间有多于一个的通道。这样,当它在一个特殊的监示器中调用OpenPortEx(或OpenPort)之后,就不会在关闭它之前企图打开同一个端口。 在一个端口被打开之后,假脱机能调用附加的函数以打印作业,如在打印一个打印作业部分中所描述的,用端口的句柄作为一个输入的参数。应当编写一个监示器,在一个端口被打开之后,假脱机可以在关闭端口之前发送多个打印作业。 若一个作业必须通过一个不同的语言监示器发送,如果没有与端口相关的打印队列或者当系统关闭时,则假脱机关闭一个端口。为关闭端口,假脱机调用语言监示器的ClosePort函数,该函数使端口被打开时创建的句柄无效。语言监示器通常调用ClosePort函数,该函数是由它相关的端口监示器定义的。 8.2.7打印一个打印作业 在一个端口被打开之后,如在打开及关闭端口部分所描述的,假脱机可以发送打印作业到端口。 每个打印作业通过假脱机调用语言或端口监示器的StartDocPort或EndDocPort函数来被分隔开。假脱机在打印处理器调用假脱机的StartDocPrinter及EndDocPrinter函数时调用这些函数,这些函数都在平台的SDK文档中有描述。在StartDocPort及EndDocPort函数的范围之间,可以出现对监示器的WritePort,ReadPort,GetPrinterDataFromPort函数的不限次数的调用。 这些函数中的每一个都需要由OpenPortEx(或OpenPort)返回的端口句柄做为一个输入参数来指定。通常,语言监示器通过调用在它的相关的端口监示器中的相同名字的函数来实现每一个函数。 当假脱机调用一个语言监示器的WritePort函数以发送数据流到一个端口,函数经常增加一些特定语言的信息(如PJL命令)到收到的数据流中,并且是在它被传送到相关联的端口的WritePort函数之前。 ReadPort函数用于获得双向打印机硬件的状态信息,双向打印机硬件的语言监示器可以通过调用SetPort向假脱机发送,在平台的SDK文档中描述。假脱机不调用ReadPort函数。 如果打印机硬件是双向的,它的语言监示器或端口监示器应当支持GetPrinterDataFromPort函数。语言监示器的GetPrinterDataFromPort函数应当接受一个注册表的值名称作为输入,并从那个名称(一般是调用相关端口的WritePort及ReadPort函数)获得一个值并将值返回给调用者。一个端口监示器的GetPrinterDataFromPort?函数应当接受一个I/O控制的代码作为输入,调用DeviceIoControl(在平台的SDK文档中有描述)以传递控制代码到端口的驱动程序,并返回结果。 8.2.8管理端口 端口管理活动由增加端口、配置端口及删除端口等对应用程序(如打印文件夹)的响应活动构成。这些端口管理活动被端口监示器处理,不是语言监示器,并需要管理员的优先权限。对存储端口配置信息有特殊的规则。 一个附加的、可选的管理活动包括设置端口超时值。 8.2.8.1增加端口 增加一个端口由存储端口的名字及用户可修改的在端口监示器服务器DLL的本地存储中或注册表中的配置信息构成。 当应用程序调用打印假脱机的AddPort函数(在平台的SDK文档中描述),它指定端口监示器的名称作为函数的参数。假脱机调用包含在指定的端口监示器UI DLL中的AddPortUI函数。 端口监示器的UI DLL的AddPortUI函数应当执行下面的操作: 调用打印假脱机的OpenPrinter函数(在平台的SDK文档中有描述),它引起端口监示器服务器DLL中的XcvOpenPort被调用。 多次调用打印假脱机的xcvData函数,以请求端口监示器服务器DLL增加端口并在UI DLL和服务器DLL之间传递配置信息。XcvData函数调用服务器DLL的XcvDataPort函数。AddPortUI函数通常获得从用户显示对话框栏显示配置信息。 调用打印假脱机的ClosePrinter函数(在平台的SDK文档中描述),它引起端口监示器服务器DLL中的XcvClosePort函数被调用。 关于这些操作的更多信息,参考AddPortUI函数的描述,也可以参考存储端口配置部分的内容。 端口监示器的EnumPorts函数必须枚举所有被增加的端口。假脱机可以调用每一个端口监示器的EnumPorts函数以确定在一个打印机服务器上可支持的端口集合。 8.2.8.2配置端口 配置一个端口是指修改一个端口监示器服务器的DLL的以前增加的端口的存储的配置信息。 当一个应用程序调用打印假脱机的ConfigPort函数(在平台的SDK文档中有描述)。ConfigurePort函数调用包含在适当的端口监示器UI DLL中的ConfiguePortUI函数。 端口监示器的UI DLL的ConfigPortUI函数应当执行下面的操作: 调用打印假脱机的OpenPrinter函数(在平台的SDK文档中有描述),它引起在端口监示器服务器DLL中的XcvOpenPort函数被调用。 一次或多次调用打印假脱机的XcvData函数,以在UI DLL及服务器DLL之间传输配置信息。XcvData函数调用服务器DLL的XcvDataPort函数,ConfigurePortUI函数通常通过从用户显示的对话框获得配置信息。 调用打印假脱机的ClosePrinter函数(在平台的SDK文档中有描述),它导致端口监示器服务器DLL中的XcvClosePort函数被调用。 关于这些操作的更多信息,可以参考ConifigurePortUI的描述。也可以参考存储端口配置信息部分的内容。 8.2.8.3删除端口 删除端口由删除端口的名称及删除用户可修改的端口监示器服务器DLL本地存储或注册表中的配置信息构成。 当一个应用程序调用打印假脱机的DeletePort函数(在平台的SDK文档中描述),DeletePort函数调用包含在适当的端口监示器UI DLL中的DeletePortUI函数。 端口监示器的UI DLL的DeletePortUI函数应当执行下面的操作: 调用打印假脱机的OpenPrinter函数(在平台的SDK文档中描述),它导致端口监示器服务器DLL中的XcvOpenPort函数被调用。 一次或多次调用打印假脱机的XcvData函数,以请求端口监示器服务器DLL删除端口。XcvData函数调用服务器DLL的XcvDataPort函数。 调用打印假脱机的ClosePrinter函数(在平台的SDK文档中描述),它导致端口监示器服务器DLL中的XcvClosePort函数被调用。 关于这些操作的更多信息,参考DeletePortUI的描述。 8.2.8.4设定端口超时值 如果为一个具有可修改的超时值的端口写端口监示器,超时值应当被监示器的OpenPort函数初始化。例如,在localmon.dll中的OpenPort函数,即端口监示器实例,为了这个目的调用SetCommTimeouts函数(在平台的SDK文档中描述)。 8.2.8.5存储端口配置信息 Windows 2000打印假脱机可以在集群或非集群的服务器环境中执行。当假脱机在一个服务器集群中时,打印监示器配置信息必须被存储在集群注册表中。另一方面,如果假脱机运行在一个单一的、非集群的服务器系统中,所有配置数据必须被存储在服务器的本地注册表中。 打印假脱机定义了一套注册表函数以备打印监示器使用。这些函数传送配置数据到适当的注册表中,因此,打印监示器不需要决定服务器是否集群。打印监示器不能直接使用Win32注册表API或者集群注册表API;所有的配置数据必须可以用假脱机的注册表函数来存储和存取。当假脱机调用监示器的InitializePrintMonitor2函数时,这些函数的地址在一个MONITORREG结构中被提供给打印监示器。 在一个服务器集群中,可以并存多个假脱机的实例。特别是,每个集群结点处理它自身的实例,并且一个附加的实例为集群自身存在。假脱机注册表函数的一个输入参数是假脱机句柄。这一句柄由监示器的InitializePrintMoitor2函数收到并标识假脱机已经打开的监示器的实例(结点或集群)。用假脱机句柄,假脱机注册表函数对每一个假脱机实例保持一个子键。 8.2.9为在集群打印服务器中使用转换打印监示器 打印服务器集群是Windows 2000的新特性。任一准备运行于Windows2000集群的打印机端口监示器都需要修改,这样才可以被假脱机的实例(结点的假脱机及一个集群的假脱机)所调用。下面的步骤必须执行: 监示器的InitializePrintMonitor函数必须被InitializePrintMonitor2函数替换。后一个函数返回一个监示器实例句柄。 全局存储变量必须被移动到本地分配的内存,并且这一内存必须与由InitializePrintMonitor2函数返回的监示器句柄相关联。 对Win32注册表API的调用必须被对假脱机注册表的调用函数所替换,它的地址在MONITORREG结构中传递到监示器(参考存储端口配置信息)。 端口监示器必须被分成一个端口监示器UI DLL和一个端口监示器服务器DLL。UI DLL必须通过调用假脱机的XcvData函数与服务器DLL通讯。 必须增加一个Shutdown函数。 那些没有被转换的打印监示器只能被用于一个非集群的环境中。他们不能被用于集群服务器。 一旦一个运行在Windows2000的集群结点上的打印机端口监示器已经建立起了连接(跨网络或者本地),端口监示器应当从假脱机在适当的时间里进行的调用返回。(默认的假脱机资源超时值是180秒,参考设置端口超时值部分的内容获得更多信息。) 当发生从一个集群结点到另外一个结点的失效情况,假脱机必须等待所有当前打印作业完成或失败。如果一个未完成的作业保持在一个端口监示器中期待着假脱机资源的超时,随着打印机的临时性丢失,假脱机也许可以在线返回到一个不完全的状态。这将会影响那些连接到这些丢失的打印机上的用户。 8.2.10安装打印监示器 这一部分描述可以用于安装打印监示器的方法(注意,如果打印机如果不被ntprint.inf支持,必须提供一个.inf文件来指定打印机。关于.inf文件的更多信息,可以参考即插即用、电源管理及建立设计向导部分的内容。) 安装语言监示器 为安装一个语言监示器,必须使用LanguageMonitor条目在.inf文件中列出它的文件名。这一条目必须被每一个控制打印机时需要使用语言监示器的打印机驱动程序都包括。更多的信息,参考第10章打印机INF文件部分。 增加驱动程序向导或者增加打印机向导将会去读这个.inf文件并安装与打印机驱动程序相关的的语言监示器。 作为选择,定制安装程序也可以通过调用假脱机的AddMonitor函数来安装语言监示器,并明确地只安装一个专用的监示器DLL。 (AddMonitor函数在平台的SDK文档中有描述) 安装一个端口监示器 为安装一个端口监示器,安装媒体必须包括一个叫做monitor.inf的打印机.inf文件。这个.inf文件必须基于%windir%\system32\printmon.inf,即默认的安装端口监示器的.inf文件。应当制做一份printmon.inf的拷贝,将它改名为monitor.inf,删除包含在该文件中的所有端口监示器,并为端口监示器增加说明。 如果提供一个.inf文件,像上面描述的那样,系统管理员可以通过使用增加打印机向导来安装端口监示器,或者通过使用在打印文件夹的文件(File)菜单中的服务器属性选中时被显示的属性表单来安装端口监示器。 作为选择,一个定制的安装程序可以通过调用AddMonitor函数来安装语言监示器DLL。 8.3编写网络打印提供者 偶尔,厂商也许想提供一个打印提供者以支持新的网络体系。提供新的打印提供者功能的可能方式就是创建一个全新的打印提供者,它替换了本地打印提供者。但是在实现中,这是一个挑战并且很可能是一个不必要的任务。另一种变更的方法是创建一个部分的打印提供者,它与本地打印提供者一起工作。 这一部分提供了下面的主题: ■8.3.1部分打印提供者概述 ■8.3.2支持打印机变化的通知 ■8.3.3安装一个网络打印提供者 8.3.1部分打印提供者概述 一个局部提供者DLL通常只实现管理打印队列及打印作业的提供者函数的定制版本。局部提供者只在打印客户系统上执行并依赖于驱动程序管理操作及产生打印机数据的本地打印提供者。多个局部提供者可以存在于一个客户机系统上。 在第2章中的打印提供者定义的函数一章中,某些函数被标识为“必需的”。部分打印提供者必须提供所有必需的函数。部分打印提供者一般不实现任何可选的函数。必需的函数属于下面这些函数组中: 初始化函数 打印队列管理函数 打印作业创建函数 打印作业调度函数 端口管理函数 对部分打印提供者来说,打印机端口应当被看作与打印队列是相同的。对任何函数都接收一个PRINT_INFO_2结构(在平台的SDK文档中描述),该结构的?pPort成员应当被设置成打印队列的名字。这样,如果打印队列名是\\Server\Printer1,端口名称应当也是\\Server\Printer1。部分打印提供者的EnumPorts函数(在平台的SDK文档中描述)的实现必须返回一个端口名\\Server\Printer1 。 如在第二章的打印提供者介绍中所讲述的,一个应用程序对OpenPrinter的调用将引起假脱机的发送器以调用每一个打印提供者,直到其中之一认出了专用的打印队列并返回一个句柄。 应该记住非常重要的一点,即一个部分打印提供者不替换本地提供者。一旦创建了一个用户连接,每一个对提供者函数的调用都通过本地提供者被发送,它或者自身处理这个呼叫或者将它重发送到局部提供者。所有的对那些标识为“必需的”提供者函数的调用都将从本地提供者重发送到适当的局部提供者。 局部提供者不产生打印作业,他们依赖于本地提供者并且它的打印处理器创建可以发送到打印机的RAW数据。当一个打印处理器调用本地提供者的StartDocPrinter函数(参考打印一个打印作业部分),并且打印队列是由局部提供者支持的,本地提供者调用局部提供者的StartDocPrinter函数,提供RAW数据(作为一个文件)。局部提供者的StartDocPrinter,WriterPrinter及EndDocPrinter函数应当在网络上发送RAW数据到远端的打印队列。 8.3.2支持打印机变化的通知 应用程序可以请求打印队列通过调用假脱机的FindFirstPrinterChangeNotification,FindNextPrinterChangeNotification及FindClosePrinterChangeNotification函数(在平台的SDK文档中有描述)发生的事件通知。如果认为应用程序的开发者可以为部分打印提供者支持的打印队列请求事件通知,必须在提供者中如下支持事件通知: 提供一个FindFirstPrinterChangeNotification函数 假脱机调用这一函数以给打印提供者下面这些信息: 一套标识应用程序请求通知的打印机事件类型的标记。 一个消息请求的打印队列的句柄。 一个应用程序请求的当事件发生时被提供的信息类型的列表。 函数必须返回指明是否提供者应当被轮询,以决定已经有变化发生的一个标记值。(非轮询的提供者在发生变化时发送信号到客户端。一个必须被轮询的提供者在发生变化时不发送信号到客户端。相反,假脱机则以规则的间隔时间向客户端发送信号,无论是否有变化发生。) (但要注意,在提供者层,这一函数与Win32层有不同的参数。) 保持对所有应用程序在调用FindFirstPrinterChangeNotification函数时指定的打印队列事件的跟踪。 (对一个应用程序可以请求的通知类型的列表,以及对一个可以被用于描述事件的信息类型的列表,可参考平台的SDK文档中对Win32 FindFirstPrinterChangeNotification函数的描述。应用程序可以请求的通知的事件类型包括增加或删除一个打印作业或表格。应用程序可以请求的信息类型包括作业或表格参数。) 那些没有被轮询的打印提供者必须在变化发生时调用PartialReplyPrinterChangeNotification或者ReplyPrinterChangeNotification函数,以给假脱机提供描述变化的信息。ReplyPrinterChangeNotification函数必须在一些点上被调用因为它引起假脱机向应用程序发送信号,而PartialReplyPrinterChangeNotification函数则不这样做。当应用程序收到一个从ReplyPrinterChangeNotification来的信号,它被假定为去调用FindNextPrinterChangeNotification函数。后一函数给应用程序提供假脱机以前从打印提供者那里收到的事件信息。 被轮询的打印提供者应当简单地跟踪变化。假脱机以规则的间隔向应用程序发送信息。当应用程序收到一个信号,则假定调用FindNextPrinterChangeNotification函数。这一后者的函数。对被轮询的提供者,这一函数调用提供者的RefreshPrinterChangeNotification函数。 提供一个RefreshPrinterChangeNotificaiton函数 对专用的打印队列来说,这一函数必须返回所有被监示的打印队列选项的当前状态。当应用程序以PRINTER_NOTIFY_OPTIONS_REFRESH标记调用FindNextPrinterChangeNotification(如在平台的SDK文档中所描述)时,假脱机调用这一函数。(如果前一个对FindNextPrinterChangeNotification函数的调用返回了一个具有PRINTER_NOTIFY_INFODISCARDED标记设置的PRINTER_NOTIFY_INFO结构时,应用程序就假定设置这一个值。)所有被轮询和非轮询的提供者必须支持RefreshPrinterChangeNotification。 提供一个FindClosePrinterChangeNotification函数(在平台的SDK文档中描述)。 8.3.3安装一个网络打印提供者 为安装一个新的网络打印提供者,必须提供一个安装程序,它拷贝提供者DLL到目标系统的\System32子目录并然后调用AddPrintProvidor(在平台的SDK文档中描述)。这一函数为提供者创建一个注册表条目并增加提供者到一个假脱机已经安装的提供者列表的末尾。函数然后装载提供者的DLL并调用提供者的InitializePrintProvidor函数。 为创建一个网络打印提供者支持的打印机连接,一个用户激活增加打印机向导并选择“网络打印机服务器(Network print server)”选项。用户用\\Server\Printer格式指定一个打印队列,并且提供者的OpenPrinter函数必须能识别打印队列的名称。 第9章 Internet打印 这一部分提供下面的主题: ■9.1从应用程序打印到URL ■9.2查看打印web页面 ■9.3定制打印web页面 ■9.4从web页面安装打印驱动程序 9.1从应用程序打印到URL 从应用程序的观点去看,向一个URL标识的打印队列打印等同于向一个UNC标识的打印队列打印。应用程序通常不知道哪个队列可以通过URL方式被存取。 通过视查打印web页面,一个用户可以安装并连接到一个URL标记的打印队列。当这一情况发生时,打印队列被分配给它在打印服务器上所有的相同的“友好名称(friendly name)”,这一友好名称被列在用户的打印机文件夹上。 应用程序一般根据它的友好名称引用打印队列,就像它们为UNC标识的打印队列所做的一样。对在本地打印提供者(例如,由应用程序做GDI调用引起)的OpenPrinter函数的调用。本地打印提供者,依次调用在HTTP打印提供者(inetpp.dll)中的OpenPrinter函数,指定打印队列的URL。 通过友好名称指向打印队列的应用程序,一般不知道打印队列是在本地还是在网络上,或者是否网络协议是RPC,SMB或HTTP。但是,如果必要,应用程序可以直接调用OpenPrinter函数,指定一个URL。向OpenPrinter指定一个URL,则必须用到下面的URL格式: http://ServerName/Printer/ShareName/.Printer 这里ServerName是一个服务器名称(一个Internet连接的DNS名称或者一个Intranet连接的WINS名称),Printers表示服务器上一个虚拟的目录。ShareName是一个打印队列的共享名称,如在它的属性表单中所指定的。(虚拟目录在平台的SDK文档中讨论)。 当一个客户假脱机组件或应用程序调用OpenPrinter并指定URL。对假脱机函数的接下来的调用,如对StartDocPrinter,WritePrinter等等,是由客户端的HTTP打印提供者来处理的。HTTP打印提供者给URL增加参数,并发送结果URL字符串到打印服务器。 Windows 2000打印服务器为接受包含URLs的打印请求,它必须运行在下面两种之一的情况下: Windows 2000服务器软件和微软的IIS(Internet Information Server) Windows 2000专业版(Professional)软件和微软Peer Web Server 在打印服务器上,IIS或Peer Web Server接收URL字符串。在客户机系统上,参数被inetpp.dll追加到字符串从而引起服务器调用HTTP打印服务器,它被包含在msw3prt.dll中。HTTP打印服务器接收RAW格式的打印数据并将它发送到本地打印假脱机。 打印机数据用Internet打印协议(Internet Printing Protocol IPP1.0)从客户端传送到服务器。IPP是由Internet工程任务组(IETF:Internet Engineering Task Force)的打印机工作组(Printer Working Group PWG)定义的。 下图示意了如果从客户端打印到一个URL标识的打印队列时,从客户应用程序向一个服务器假脱机打印的打印数据的通路。 插入图??? 如果客户端及服务器都是Windows 2000系统,如上图所示的,则通常(但不是一直)使用RPC协议用于客户-服务器通讯。(更多的信息,参考从web页面安装打印驱动程序部分的内容)。 如果客户端和服务器端不全为Windows 2000系统,则使用HTTP,HTTP也用于那些包含内部网络接口卡并支持IPP1.0的打印机,也因此没有被连接到服务器上。 打印服务器安全是由IIS提供的,它在打印服务器上执行。安全机制由IIS支持并在IIS资源指南部分被描述。另外,资源包特别描述了与系统管理员怎样控制与打印到URL相关的安全方法。 9.2查看打印web页面 用任何平台上的Internet浏览器,用户可以查看web的页面并显示Windows 2000打印服务器以及它所连接的打印机的状态。微软提供了一套服务器驻留的HTML文件以产生这些web页面。打印服务器的web页面以及对每一个服务器安装的打印机可以被客户机浏览器用URLs来引用。另外的页面可以从这些页面的链接来引用。 Windows 2000打印服务器为支持web页面,它必须运行在Windows 2000服务器软件和微软的IIS(Internet Information Server)或Windows 2000专业版(Professional)软件和微软Peer Web Server两种之一的环境下。 为查看一个打印服务器的页面,一个用户指定下面的URL格式: http://ServerName/Printers 这里,ServerName是一个服务器名称(一个Internet连接的DNS名称或一个Intranet连接的WINS名称)。URL指向一个产生打印服务器页面的HTML文件。 服务器页面对每一个在服务器上可用的打印队列提供一个到打印队列页面的链接。共享的打印队列可以被所有的用户存取。对共享打印机,一个用户可以通过用下面的格式来指定一个URL来引用打印队列页面: http://ServerName/ShareName 这里,ShareName是一个打印队列的共享名称,在它的属性表单中指定。 如果一个用户在打印机文件夹中选择了打印机的链接,微软的Internet Explorer浏览器则将自动启动打印队列页面的URL被存取。可选的,如已经描述的,一个用户可以通过指定页面的URL到任何HTML浏览器来查看打印服务器页面或打印队列页面。 打印web页面可以被微软的ASP(Active Server Pages)文件解释的模板文件产生。这些模板(被称为ASP文件)包含标准的HTML标记及ASP脚本语言标记(<%和%>)。 当ASP解释器遇到了具有ASP脚本标记的文本,它调用一个合适的脚本语言解释器(如JScript?及VBScript)处理这一文本,结果的HTML数据流被送到客户端的浏览器。 微软的ASP在平台的SDK文档中描述。 一套与自动化接口相关的、基于COM的打印Web页面的ActiveX对象在oleprn.dll中提供,以获得打印机属性及SNMP信息。 当一个用户想查看一个特殊的服务器或打印机的Web页面时,将进行下面的步骤: 用户使用一个浏览器以指定一个合适的URL。URL指向一个指定的打印服务器上的模板文件。 服务器驻留的ASP解释器,是IIS的一部分,它查找ASP的脚本标记,激活适当的脚本语言解释器以解释脚本文本,并将返回的结果放置于HTML数据流中。 在服务器上的ASP解释器,发送结果HTML流到客户端浏览器。 下图,示意了一个打印机URL从一个客户端发送到服务器端的处理过程以及它相关的HTML流是怎样被返回到客户端的。 插入图??? 9.3定制打印web页面 本部分提供下面的主题: ■9.3.1打印Web页面的ASP文件 ■9.3.2定制打印机细节的Web页面 ■9.3.3安装定制的打印Web页面 9.3.1打印Web页面的ASP文件 打印Web页面是通过使用ASP文件来产生的。微软提供ASP文件产生下面的打印web页面: 一个打印服务器页面,它是通过http://ServerName/printers来引用的。这里,ServerName表示一个打印服务器的DNS或WINS名称。该页面包含对每一个安装在服务器上的打印机的页面的链接。 每一个服务器打印队列的一个打印队列页面。这些页面可以通过在打印服务器页面链接来存取,或者他们可以用http://ServerName/ShareName这一URL直接从浏览器引用。 额外的排队的文档页面、打印机属性以及特定打印机的细节。这些页面在一个打印队列页面的框架中被显示。 特定打印机细节页面可以通过替换它的ASP文件来被定制。更多的信息,可以参考定制打印机细节的web页面部分的内容。 9.3.1.1ASP文件实例 DDK中也提供了ASP文件的实例,实例被放置在DDK实例的树形目录中的QueueASP子目录中。 9.3.2定制打印机细节的Web页面 如果想替换由微软提供的打印机细节页面,可以提供一个或多个定制的ASP文件。如果替换了默认的ASP页面,也可以提供安装在服务器上的另外的定制页面的链接,以及到web上其他页面的链接。 如果微软标准TCP/IP端口监示器(tcpmon.dll)用于打印机,定制的ASP文件可以被用于替换默认的每一个打印机类型的每一个厂家的打印机细节页面。如果定制的ASP文件还没有被安装,则默认的打印机细节页面被使用。 定制的ASP文件也可以为使用其他端口的监示器的打印机替换默认的打印机细节页面,但是每一个打印机类型及每一个厂家的替换则不允许。 用于安装定制的ASP文件的方法决定于是否定制的文件为一个打印机类型、一个厂家、或一个端口监示器替换了默认的文件。更多的信息,可以参考安装定制打印机web页面部分的内容。 下面的主题提供更多的关于创建打印web页面的信息: ■9.3.2.1替换默认的打印机细节页面 ■9.3.2.2哪一个打印机细节页面被显示 ■9.3.2.3打印机Web页面的ASP变量 ■9.3.2.4打印机Web页面的ActiveX对象 ■9.3.2.5修改web页面信息 9.3.2.1替换默认的打印机细节页面 可以用以下的步骤,用一个定制的页面来替换默认的打印机细节页面。 为页面提供一个定制的ASP文件。 接着是在安装定制的打印web页面部分中描述的安装过程。 一个定制的打印机细节页面可以提供到另外的定制页面或其他URL的链接。额外的定制页面的ASP文件必须如同在安装定制的打印web页面中所指明的那样被安装。更多的关于指定链接的信息,请参考平台的SDK文档中的ASP文档。 定制的打印web页面可以用到下面的技术: 打印Web页面的ASP变量 打印web页面的ActiveX对象 定制的COM对象 对创建和使用COM对象的信息,参考平台的SDK文档。 注意: 所有用ASP文件创建的打印web页面是从一个ASP应用程序创建的。 不能修改命名为globals.asp的文件,它包含在系统磁盘的打印机子目录中。 微软保留有不需通知就可修改打印web页面的权利。因此,定制的ASP文件不能依赖于微软提供的ASP文件。 9.3.2.2哪一个打印机细节页面被显示 当一个用户试图查看一个打印机的细节页面,服务器用下面的算法决定读取哪一个ASP文件。 如果打印机使用标准的TCP/IP端口监示器: 服务器首先检查看是否一套特定类型的打印机ASP文件被安装,如果是,它们将被应用。 如果特定类型打印机的ASP文件不可用,服务器检查看是否一套特定厂家的ASP文件已经被安装,如果是,它们被应用。 如果特定厂家的ASP文件不可用,并且打印机支持为SNMP的MIB(RFC 1759),则微软的默认的ASP文件被应用。 如果不支持SNMP,则打印机细节页面不被提供。 如果打印机没有使用标准的TCP/IP端口监示器,服务器检查看是否一套特定监示器的ASP文件已经被安装。如果是,则它们将被应用。如果不是,则没有提供打印机细节页面。 更多的信息,请参考安装定制打印web页面部分的内容。 9.3.2.3打印机Web页面的ASP变量 微软为定制打印web页面提供了一套ASP会话的变量。下面的表格中列出了会话的变量。定制的ASP文件不能修改这些变量,如所指示的那样,一些变量只在微软的TCP/IP端口监示器被应用于打印机时才有效。 一些变量被作为会话变量传递进来,而其他的变量则用URL修饰传递进来。会话变量可以通过使用Session(“变量名称”)来存取。通过URL修饰传递进来的参数可以使用Request(“变量名称”)来存取。如果想自动刷新状态页,也许会发现有必要用页面需要的变量来重新修饰URL。既然Request变量必须在URL中传递进来,他们也许需要编码或解码从ANSI翻译为UNICODE表示。一个帮助者的对象,它的COM ProgID是“OlePrn.OleCvt”,已经提供允许在URL中使用的ANSI向UNICODE的编码和解码。这一对象上有两个方法,EncodeUnicodeName和DecodeUnicodeName,可以分别用于从ANSI向Unicode翻译及Unicode向ANSI的翻译。对Session变量,这一转换是不需要被执行的。 变量 值 是否只是TCP/IP端口监示器 变量类型 是否被编码 MS_ASP1 到初始化的用于描述特定打印机细节web页面的目录路径 不是 请求 不是  MS_Community 打印服务器的SNMP公共名称 是 请求 不是  MS_Computer 打印服务器的计算机名称 不是 会话 不是  MS_DefaultPage 特定打印机细节的默认ASP文件 不是 会话 不是  MS_Device 打印机的SNMP设备索引 不是 请求 不是  MS_DHTMLEnabled 如果客户端支持动态HTML则为TRUE,否则为FALSE 是 会话 不是  MS_IPAddress 打印机的IP地址 是 请求 不是  MS_LocalServer 打印服务器的标识符,这或许是一个IP地址,或者是一个计算机名称 不是 会话 不是  MS_Model 打印机驱动程序的名称 不是 请求 是  MS_Portname 打印机的端口名称 不是 请求 是  MS_Printer 打印机的名称 不是 请求 是  MS_SNMP 如果打印机用到SNMP则值为TRUE,否则为FALSE 是 请求 不是  MS_URLPrinter 打印机的名称,以被编码的URL格式 不是 请求 是   会话变量指定当前当印机的属性,也就是,为一个被唤醒的ASP页的打印机。为获得当前打印机的另外的打印机属性,或者获得不同打印机的属性,参考打印机Web页面的ActiveX对象部分的内容。 9.3.2.4打印机Web页面的ActiveX对象 为打印web页面提供了三种ActiveX?对象,即IaspHelp,ISNMP及IoleCvt。ASP文件可以通过一个自动化(Automation)接口存取每一个对象,用任何的脚本语言,如VBScript等。对象及接口在oleprn.dll中提供。 IAspHelp自动化接口允许获取与一个特定打印机相关的属性。这一接口提供了对不能从ASP会话变量存取的信息的访问,并允许一个ASP页面获得除了页面被唤醒时信息之外的关于打印机的所有信息。 ISNMP自动化接口允许设置并检索与SNMP OIDs相关的值,如果一个打印机支持RFC 1759。 IOleCvt自动化接口允许从ANSI到Unicode方向转换字符串,反之亦然。为另外一个代码页转换字符串到UTF8格式及转换Unicode字符串。 ISNMP接口只可以被应用到那些用微软的TCP/IP端口监示器的打印机。这一接口本质上是一个为SNMP管理API函数的OLE自动化服务的包装器,并且在平台的SDK文档中有描述。 OID可以用数字型字符串来指定,以及通过RFC1759指定的MIB包含一个文本名称的字符串。另外的MIB名称可以在定义、编译及安装定制的MIB时被应用,如在平台的SDK文档中描述的那样。 关于ActiveX及自动化服务的更多信息,可以参考平的SDK文档。 9.3.2.5修改web页面信息 打印服务器不支持服务器对打印服务器的web页面上修改信息的推送。客户端的拖操作则被支持(假定用户的浏览器识别HTML指定的刷新参数)并且可以包括在定制页面中。(更多的关于客户端拉的信息,参考相关的HTML文档)。 9.3.3安装定制的打印Web页面 用于安装定制的打印web页面的方法,依赖于安装的页面是否特定于一种打印机类型,特定于一个生产厂家或者特定于一个端口监示器。参考下面的主题: ■9.3.3.1为一个打印机类型安装页面 ■9.3.3.2为一个生产厂家安装页面 ■9.3.3.3为一个端口监示器安装页面 9.3.3.1为一个打印机类型安装页面 如果打印机使用标准的TCP/IP端口监示器,可以安装一个特定于指印机类型的打印机细节页面。为了这样做,包含页面的ASP文件及所有的从属的文件(如用于链接页面的.gif文件或ASP文件)到打印机的INF文件中。下面是一个打印机INF文件的实例部分: [Manufacturere] “ACME” ACME “ACME Mega Laser”=ACML01.PPD [ACML01.PPD] CopyFiles=@ACML01.PPD,PSCRIPT,ACML1WEB DataSection=PSCRIPT [ACML1WEB] PAGE1.ASP,ACML1.ASP;ACML1.ASP renamed to PAGE1.ASP during Installation ACML2.ASP ACGF001.GIF [DestinationDirs] DefaultDestiDir=66000 ACML1WEB=66004 当打印机类的安装程序遇到这一INF文件部分,它将执行下面的操作: 创建一个如<Root>\<Manufacturer>\<Printer Type>格式的目录,例如,下面的子目录可以被创建: …\ACME\ACME Mega Laser 子目录在INF文件中用打印机的DIRID值66004来指定。 拷贝acml1.asp,asml2.asp及acgf001.fig到子目录 将acml1.asp改名为Page1.asp(由在ACML1WEB部分的第一个语句引起) 但是要注意,必须区别第一个被查看的ASP文件,通过将它放置在名为Page1.asp文件名之前,如在实例中所示,这一安装程序在目标目录中重新改这一文件为Page1.asp。 所有的ASP文件都有一个PageN.asp的格式,这里N是这1,2,3等等,是由微软保留的。 一个实例的INF文件与ASP文件实例一起提供。 9.3.3.2为一个生产厂家安装页面 可以安装一个特定于生产厂家的打印机细节页面,对所有的厂家的使用标准的TCP/IP端口监示器的打印机类型。为了这样做,将页面的ASP文件连同所有的附属文件(如链接页面的.gif文件或ASP文件)放置于厂家的子目录中(\%windir%\web\printers\<manufacturer>)。一个定制的设置程序必须被提供以完成它。 页面的初始ASP文件必须被命名为Page1.asp,所有的ASP文件名都有一个PageN.asp的格式,其中N是1,2,3等等,是由微软自已保留的。 厂家专用的页面可以用于用标准TCP/IP端口监示器但没有特定类型打印机的页面。 系统通过在安装时读打印机的INF文件确定一个打印机的厂家。当一个用户试图查看一个打印机的细节页面,系统首先检查看是否一个名为Page1.ASP的文件存在于<Root>\<Manufacturer>\<Printer Type>,如果没有发现文件,系统检查<Root>\<Manufacturer>。 9.3.3.3为一个端口监示器安装页面 可以提供一个定制的打印机细节页面以与那些没有标准TCP/IP端口监示器的打印机使用。将页面的ASP文件连同所有的附属文件(如链接页面的.gif文件或ASP文件)放置在监示器的子目录(<Root>\<Monitor>,这里Monitor匹配于在PORT_INFO_2结构中返回的监示器的名称)中。监示器安装程序必须执行这一任务。 页面的初始ASP文件必须被命名为Page1.asp,所有的ASP文件名都有一个PageN.asp的格式,这里N是这1,2,3等等,是由微软保留的。 9.4从web页面安装打印驱动程序 在一个用户可以发送一个作业到能通过打印web页面方式可见的打印队列之前,驱动程序文件必须被从打印机服务器发送并安装到用户的系统上。这一安装操作发生在用户查看一个打印队列的web页面并且选择了它的安装页面(安装页面不能被定制页面所替代)时。 如果客户端不能用RPC连接到服务器,这通常是指Intranet连接,打印机以常见的、非HTTP方式安装。否则,安装页面调用HTTP打印服务器,它创建了一个盒式(cabinet)文件(在平台的SDK文档中描述),该盒式文件包含了打印机的.inf文件及所有的安装需要的文件。服务器发送盒式文件到客户端,在客户端它被扩展。它启动了客户端的安装打印机向导,通过这个安装向导,完成打印机向用户打印机文件夹的增加安装工作。 作为一种选择打印机队列的安装页面的可选方法,一个用户可以通过明确地运行增加打印机向导来安装一个URL标识的打印机。当一个用户指定一个URL到增加打印机向导,客户端经常用HTTP连接到服务器。当安装包含网络接口卡并因此不连接到服务器上的打印机时,这一安装方法必须被使用到。 第10章 安装及配置打印机驱动程序 本章描述了安装及配置打印机驱动程序。提供下面的主题: ■10.1打印机的即插即用 ■10.2打印机的INF文件 ■10.3打印机的目录服务 ■10.4支持点号及打印 10.1打印机的即插即用 两个系统组件支持打印机的即插即用: 一个LPT枚举器 LPT枚举器具有从符合IEEE1284可扩展的端口协议及ISA接口标准能力(Extended Capabilities Port protocol and ISA Interface Standard)的LTP端口硬件获得标识信息的能力。 一个打印机类安装程序 打印机类安装程序,ntprint.dll通过调用AddPrinterDriverEx安装打印机驱动程序,并通过调用AddPrinter使得打印机在服务器上可用。这两个函数都在平台的SDK文档中描述。 LPT枚举器是一个总线驱动程序的实例,总线驱动程序在Windows 2000驱动程序开发引用参考的第一卷,即插即用、电源管理及安装设计指南部分描述。 打印机类是Windows? 2000内置的硬件类。 当一个Windows 2000系统开始启动,配置管理器调用LPT枚举器以枚举连接到LPT端口的IEEE 1284兼容的设备。对每一个被发现的设备,配置管理器调用打印机的类安装程序。打印机类安装程序调用SetupDi-为前缀的安装函数,它从printer.inf文件获得信息。这些函数被描述在Windows 2000驱动程序开发引用参考的第一卷,即插即用、电源管理及安装设计指南部分。 10.2打印机的INF文件 对.inf的广泛讨论提供在Windows 2000驱动程序开发引用参考的第一卷,即插即用、电源管理及安装设计指南部分。 下面的主题,提供了关于打印机.inf文件特定信息: ■10.2.1打印机DIRID ■10.2.2打印机INF文件条目 ■10.2.3打印机INF文件数据部分 ■10.2.4打印机INF文件安装部分 ■10.2.5打印机INF文件拷贝文件部分 ■10.2.6打印机INF文件实例 关于打印机.inf文件的附加的信息,参考下面的主题: 第4章中的安装一个Unidrv小驱动程序部分 第5章中的安装Pscript小驱动程序部分 第7章中的安装定制的驱动程序组件部分 也可以参考下面的章中的安装部分的内容: ■第8章中的定制打印机假脱机组件部分的内容 10.2.1打印机DIRID 当在一个.inf文件中指定目标目录时,目录标识符(DIRID)应当被使用。DIRID在本DDK的驱动程序开发者的指南中被描述。 下表列出了特定打印机的DIRID及每一个的目的。 DIRID 目的 目录内容 66000 表示由GetPrinterDriverDirectory函数返回的目录路径 驱动程序文件和相关的文件  66001 表示由GetPrintProcessorDirectory函数返回的目录路径 打印处理器文件  66002 表示一些要被拷贝到本地系统\System32的附加文件的目录路径 打印监示器文件及其他相关文件  66003 表示由GetcolorDirectory函数返回的目录路径 ICM色彩profile文件  66004 表示特定类型打印机的ASP文件要被拷贝到的目录路径 ASP文件及相关的文件(参考第9章Internet打印)   当本地体系结构的打印机驱动程序被安装在本地系统,如当x86驱动程序被安装于本地的x86系统,分配于DIRID为66022的目录中的文件被拷贝到System32子目录中。如果一个驱动程序安装在一个远程系统,这一目录中的文件被忽略。 当打印机类安装程序调用假脱机的AddPrinterDriverEx函数时,一个打印机驱动程序被安装。这一函数需要所有的驱动程序文件被放置于由GetPrinterDriverDirectory函数返回的目录中。 10.2.2打印机INF文件条目 一个安装程序在打印服务器上安装打印机,它必须调用假脱机的AddPrinterDriverEx函数以装载驱动程序文件并然后调用假脱机的AddPrinter函数以使得打印机在服务器上可用。 AddPrinterDriverEx函数需要一个DRIVER_INFO_3结构作为输入,AddPrinter函数需要一个PRINTER_INFO_2结构作为输入。默认的Windows 2000打印机类安装程序ntprint.dll,读打印机的.inf文件以获得这些函数被调用之前必须放入这些函数中的值。 AddPrinterDriverEx及AddPrinter函数,以及DRIVER_INTO_3及PRINTER_INTO_2结构,都在平台的SDK文档中描述。 为打印机的驱动程序已经定义了一套.inf文件条目,这些都是ntprint.dll可以识别的,这些条目具有如下的格式: EntryName=Value 这里,EntryName是一个标识条目的字符串,Value是一个分配给该条目的值。 下面的表中列出了应当包含于打印机.inf文件中的.inf文件条目。对每一个条目,表中包括: 应当被分配给条目的值 条目没有指定时,ntprint.dll使用的默认值 一个结构成员,ntprint.dll将指向条目值的指针放置于其中。 INF文件条目 值 默认值(如果条目没有指定) 结构成员 ConfigFile 驱动程序打印机接口DLL的名称 为DriverFile指定的值 DRIVER_INFO_3.pCinfigFile  DataFile 驱动程序相关的数据文件名称,如.ppd文件 在.inf文件中的驱动程序的部分名称 DRIVER_INFO_3.pDataFile  DefaultDataType 在Windows NT/2000中不使用    DriverFile 驱动程序打印机图形DLL的名称 在.inf文件中的驱动程序的部分名称 DRIVER_INFO_3.pDriverPath  HelpFile 接口DLL的帮助文件名称 没有。没有指定帮助文件 DRIVER_INFO_3.pHelpFile  LanguageMonitor 与打印机驱动程序相关联的语言监示器的名称。参考语言监示器值格式部分的内容 没有。没有指定语言监示器 DRIVER_INFO_3.pMonitorName  PrintProcessor 与打印机驱动程序相关联的打印机处理器的名称。参考打印机处理器值格式 没有。没有指定一个打印机处理器 PRINT_INTO_2.pPrintProcessor  VendorSetup 在一个厂商提供的DLL中的函数的名称,它处理定制的打印机安装操作 没有。没有执行定制的安装操作 没有   打印机的.inf文件条目是通常在printer.inf文件中的数据部分指定的。相关实例,可参考打印机.inf文件实例部分的内容。 语言监示器值(LanguageMonitor value)格式 当一个LanguageMonitor条目包含在一个打印机的.inf文件中时,其值的格式如下: LanguageMonitor=“MonitorName,MonitorDLLName” 这里,MonitorName是一个文本字符串,表示监示器的显示的名称,而MonitorDLLName是监示器DLL的文件名。 打印机处理器值(PrintProcessor Value)格式 当一个PrintProcessor条目包含在一个打印机的.inf文件中时,其值格式如下: PrinProcessor=“PrintProcessorName,PrintProcessorDLLName” 这里,PrintProcessorName是一个文本字符串表示打印机处理器的显示的名称,PrintProcessorDLLName,是DLL的文件名。 从属文件 对Windows 2000来说,从属文件是包含于打印机的.inf文件的安装部分并且打印机的DIRID为66000或66002的打印机驱动程序文件,但是没有分配给驱动程序的DriverFile,DataFile,ConfigFile或HelpFile条目。 对每一个打印机型号,可以指定最大64个从属文件。 10.2.2.1定制的打印机安装操作 为给那些用ntprint.dll安装的打印机提供定制的打印机安装操作,在使Windows 2000默认的打印机类安装程序时,可以在打印机的.inf文件中包括一个VendorSetup条目。 VendorSetup条目的格式如下: VendorSetup=FileName,FunctionName 这里,FileName是一个包含一个安装函数的DLL名称,FunctionName是这个函数的名称。 由FunctionName指定的函数必须匹配下面的原型: VOID WINAPI FunctionName(LPSTR pPrinterName) 这里,FunctionName是一个函数的名称,pPrinterName是一个指向打印机友好名称的指针。 打印机类安装程序调用该函数被作为安装操作的最后一步。 10.2.3打印机INF文件数据部分 Windows 2000默认的打印机类安装程序ntprint.dll允许打印机的.inf文件以包含数据部分。数据部分用下面的格式来指定: DataSectionName=SectionName 这里,SectionName是一个.inf文件部分名称。 数据部分用于指定一套对多个打印机都通用的打印机的.inf文件条目。通过对这些通用的条目在名称部分列出并分组,然后对每一个使用这个条目的打印机用DataSection语句引用那个部分。条目列表只需要在.inf文件中出现一次。 微软打印机的.inf文件即ntprint.inf,定义了下面的数据部分: [PSCRIPT_DATA] 给微软Postscript打印机驱动程序的DriverFile,ConfigFile及HelpFile条目分配值。 [UNIDRV_DATA] 给微软统一打印机驱动程序的DriverFile,ConfigFile及HelpFile条目分配值。 [UNIDRV_BIDI_DATA] 给微软双向打印机的统一打印机驱动程序的DriverFile,ConfigFile,HelpFile及LanguageMonitor条目分配值。。 这些数据部分应当从厂商提供的.inf文件中引用。相应实例,可参考第4章的安装一个Unidrv小驱动程序部分和第5章的安装一个Pscript小驱动程序部分的内容。 “Previous Names”部分 Windows 2000打印机类安装程序能识别一个称为“Previous Names”的特殊的数据部分。这些部分中的每一个在.inf文件中都允许。在该部分中,驱动程序中鉴别打印机名称的标识对Windows 2000及Windows 95/98是不相同的。指定这样不相同的名称允许当Windows 95/98客户端连接到Windows 2000服务器时支持点号及打印操作。 这一部分中的每一个条目格式如下: “Windows 2000 Printer Name”=“Windows 95/98 Printer Name” 下面是一个条目实例: [Previous Names] “HP Color LaserJet”=“HP Color LaserJet (MS)” “HP DeskJet 1200C”=“HP DeskJet 1200C (MS)” “HP DeskJet 310”=“HP DeskJet 310 (MS)” 10.2.4打印机INF文件安装部分 对Windows NT? 4.0及以前的版本,向客户提供小驱动程序的厂商也同时给客户提供适当的从微软公司获得的微软打印机驱动程序的拷贝。 对Windows? 2000,厂商不再随他们的小驱动程序一起分发微软的打印机驱动程序。相反,每一个厂商提供一个安装厂商文件的.inf文件,并然后唤醒微软的打印机.inf文件ntprint.inf,它接着安装适当的打印机驱动程序组件。 微软打印机.inf文件,即ntprint.inf包含下面一些可以被厂商的.inf文件引用的INF DDInstall 部分: [PSCRIPT.OEM] 安装微软Postscript打印机驱动程序(Pscript)。 [UNIDRV.OEM] 安装微软统一打印机驱动程序(Unidrv)。 [UNIDRV_BIDI.OEM] 安装微软统一打印机驱动程序及pjlmon.dll,pjlmon.dll是支持打印机作业语言(PJL)并为PJL打印机提供双向通讯的语言监示器。 [TTFSUB.OEM] 安装ttfsub.gpd,它被包括在该DDK中,并为用于支持统一驱动程序打印机的普通的TrueType字体替换包含一套*TTFS条目。 [sRGBPROFILE.OEM] 安装系统的sRGB色彩profile。 [LOCALE.OEM] 安装locale.gpd,它包含地区标识符。(参考第4章中的引用地区部分的内容)。 为在.inf文件中引用这些安装部分,文件必须用Include及Needs指令,如下面的例子: [Manufacturer] “ABC Printers” [ABC Printers] “PBC Printer 100ex”=ABC100EX.GPD,ABC_Printer_100ex [ABC100EX.GPD] CopyFiles=@ABCres.dll,@ABC100EX.gpd DataSection=UNIDRV_BIDI_DATA ;Unidrv Bi-Directional Data Section DataFile=ABC100EX.gpd Include=NTPRINT.INF ;Include NTPRINT.INF Needs=TTFSUB.OEM.UNIDRV_BIDI.OEM ;Install Unidrv,TrueType subs ;and PJL Language monitor 10.2.5打印机INF文件拷贝文件部分 当打印机的.inf文件包含被一个INF的CopyFiles指令引用的文件列表部分时,在文件列表中的每一个文件必须用下面的格式来指定: [file-list-section] destination-file-name[,,flag] destination-file-name[,,flag] destination-file-name[,,flag] …… destination-file-name区段是必须,而falg区段是可选的。文件说明不能包含可选的源文件名称及临时文件名称两个字段,即source-file-name或temporary-file-name字段,这两个字段是为文件列表部分定义的并与INF CopyFiles指令一起使用。这一限制在从web页面安装打印机时是必须的。 10.2.6打印机INF文件实例 该DDK提供了一个打印机.inf文件的实例,其例子放在DDK实例目录树的一个子目录中。 10.3打印机的目录服务 当用户安装一个网络共享的打印机。默认的,Windows?2000打印文件夹将会将该打印机发布到Windows 2000的目录服务(Windows 2000 Directory Services)。发布是通过调用假脱机的具有一个PRINTER_INTO_7的输入结构的SetPrinter函数来完成的,在平台的SDK文档中描述。 发布一个打印队列使得它对用户可见,这是通过在任务条中的“开始(Start)”菜单的“查找(Find)”选项完成的。 下面的主题提供了更多的关于打印机支持目录服务的信息: ■10.3.1打印假脱机对打印机目录服务的支持 ■10.3.2打印机驱动程序对打印目录服务的支持 10.3.1打印假脱机对打印机目录服务的支持 Windows 2000打印假脱机对目录服务的支持主要由以下内容构成: 公布打印队列 维持三个注册表键 允许存取假脱机维持的注册表键 返回一个打印队列的公布状态 公布打印队列 在平台的SDK文档中描述的SetPrinter,允许调用者公布、不公布或修改一个打印队列的对象。因为这样的原因,SetPrinter函数必须被PRINTER_INTO_7输入结构来调用。 一个打印队列对象只有在它与描述用户连接的打印服务器的计算机对象相关联时才可以被公布。一个用户公布打印队列的能力决定于用户自身的存取能力,如包含在用户客户端系统的设备环境。具有管理员或者打印服务器打印队列存取的用户可以公布打印队列。 维持三个注册表键 三个注册表键包含所有在打印队列对象中公布的信息。这三个关键字是用下面的标识符来引用的,并在winspool.h中定义: SPLDS_DRIVER_KEY 为存储特定驱动程序的信息,可以被假脱机或驱动程序提供  SPLDS_SPOOLER_KEY 为存储假脱机提供的、特定假脱机的信息  SPLDS_USER_KEY 为存储应用程序提供的、特定用户的信息   假脱机用SPLDS_DRVIVER_KEY存储可以通过调用平台SDK中的DeviceCpabilities函数而获得的驱动程序能力。驱动程序负责存储假脱机不能获得的的驱动程序能力,如在打印机驱动程序对打印机目录服务部分中所描述。存储在这些关键字下的值必须由定义在wnspoool.h中定义的以SPLDS_为前缀的常量来标识。 假脱机保持对这些关键字下的因为打印队列对象最后一次被修改而引起的值的修改跟踪。假脱机每一次公布或修改打印队列对象,它把拷贝所有被修改的值到对象中。 允许存取被假脱机维持的注册表键 假脱机允许打印机驱动程序通过调用SetPrinterDataEx,GetPrinterDataEx和EnumPrinterDataEx函数存取三个由假脱机维持的注册表键,这三个函数在平台的SDK中描述。SetPrinterDataEx函数设置关键字下的函数,而GetPrinterDataEx及EnumPrinterDataEx返回当前的值。(驱动程序不设置在SPLDS_SPOOLER_KEY关键字下的值)。对这些函数的调用者不用指定一个完整的注册表路径,函数自动决定到专用的打印队列的注册条目的路径。 返回一个打印队列的公布状态 GetPrinter函数,在平台的SDK函数中描述,允许调用者决定是否一个打印队列是当前公布的。为了这一个目的,GetPrinter函数被一个具有PRINTER_INFO_7输入结构的调用者调用。该函数返回打印队列的公布状态(已公布或未公布)及对象标识符。 所有前面提到的函数都在平台的SDK文档中描述。函数不允许专用于DS相关的操作。 10.3.2打印机驱动程序对打印目录服务的支持 打印机驱动程序不负责向目录服务公布一个打印队列。Windows 2000打印文件夹创建一个打印列对象(通过调用假脱机的SetPrinter函数),并且是在安装打印机过程中创建的。 打印队列属性被公布,这样一个用户可以根据特殊的属性,使用在任务条中的“开始(Start)”菜单的“查找(Find)”选项去查找打印机。打印文件夹公布一些但不是所有的对DriverCapabilities可用的打印机能力。 打印机驱动程序可以增加或修改一个打印队列对象的对象属性信息。那些可以被公布的、以SPLDS_为前缀的常量公布的特殊的属性,在winspool.h中定义。为增加或修改打印机属性,驱动程序必须用这些预定义的属性名称标识符。 接着进行下面的步骤: 增加属性名称及值到注册表的SPLDS_DRIVER_KEY之下,并通过调用假脱机的SetPrinterDataEx函数。 调用假脱机的SetPrinter函数,并具有PRINTER_INFO_7的输入结构和DSPRINT_UPDATE行为,以通知假脱机它应当修改被公布的打印队列对象(驱动程序不应当指定一个DSPRINT_PUBLISH行为)。 这些步骤应当在打印机驱动程序的DrvPrinterEvent函数中,当函数收到一个PRINTER_EVENT_INITALIZE事件时被实现。 如果一个驱动程序需要为打印机公布的属性获得当前值,它应当调用GetPrinterDataEx或EnumPrinterDataEx以从注册表获得信息,它由假脱机维持并一直是最新的。作为一种可选择的方式,可以调用GetPrinter以获得打印队列的对象标识符并然后调用ADSI函数以获得被公布的属性的值。这一技巧不是被推荐的,是因为它不仅需要大量的资源并且因为返回的数据不总是当前的数据。 10.4支持点号及打印(Point and Print) 这一部分提供下面的主题以描述Windows 2000点号及打印技术: ■10.4.1点号及打印介绍 ■10.4.2在打印机安装过程中支持点号及打印 ■10.4.3在打印机连接过程中支持点号及打印 10.4.1点号及打印介绍 点号及打印是一个术语,它指的是Windows 2000的这样一种能力,它允许用户在Windows 2000客户端上创建一个到远程打印机的连接,而不需要提供磁盘或其他安装媒体。所有需要的文件和配置信息将自动从打印服务器下载到客户端,不需要用户的干涉。 Windows 2000点号及打印技术提供了两种方法,通过它们可以指定那些应当从客户机器发送到打印服务器的文件: 可以与打印机驱动程序关联的文件。这些文件与每一个使用该驱动程序的打印队列都相关联的。 可以与单个打印队列相关联的文件。 更多的信息,参考在打印机安装过程中支持点号及打印部分的内容。 当一个用户应用程序调用AddPrinterConnection(在平台的SDK文档中描述)以做到一个打印机的连接,下面的操作必须被执行: 驱动程序相关的及队列相关的文件被从打印服务器下载到客户端。 打印机配置参数的当前值,它们被存储于服务器的注册表中的打印机的关键字下,被下载到客户端。 更多的信息,参考在打印机安装过程中支持句号及打印部分的内容。 10.4.2在打印机安装过程中支持点号及打印 为支持点号及打印技术,需要指明哪些驱动程序专用的及队列专用的文件需要在客户端用户连接到打印服务器时被发送到客户端。提供下面这些主题: ■10.4.2.1安装驱动程序专用的文件 ■10.4.2.2安装队列专用的文件 ■10.4.2.3点号及打印DLL 10.4.2.1安装驱动程序专用的文件 与打印机驱动程序关联的文件可通过将它们包含在打印机的.inf文件中实现。更多的信息,参考打印机的INF文件部分的内容。 为提供对Windows 95/98客户连接到Windows 2000服务器的点号及打印的支持,参考在打印机INF文件的“Previous Names”数据部分的描述。 10.4.2.2安装队列专用的文件 在打印机安装时,一个厂商提供的安装程序可以指定一套具有任何类型,并与特殊的打印队列相关联的文件。文件可以被下载至连接到打印服务器的任一客户。安装程序通过设置注册表中的值来指定文件。如在下表中所示: 值名称 值类型 值 Directory REG_SZ 到Files指定的目录路径。同时用作服务器上的源路径及客户端上的目的路径。这一路径与PRINT$环境变量相关  Files REG_MULTI_SZ 当客户端连接到服务器时被拷贝到客户端的文件名。文件可以是DLL,数据文件或者其他类型文件  Module REG_SZ 一个可选的点号及打印DLL的文件名  应用程序应当通过调用打印假脱机的SetPrinterDataEx函数来创建这些值。这一调用指定的注册表键值的格式应当如下: CopyFiles\ComponentName 这里ComponentName是与文件相关联的软件组件的名称。例如,微软图像色彩管理(ICM:Microsoft Image Color Management)是在CopyFiles\ICM关键字下被指定的。指定了一个注册表键名称作为SetPrinterDataEx函数的参数,函数创建该关键字作为服务器上打印队列关键字的一个子关键字。 安装实例 作为一个实例,假设一个HP Color LaserJet的彩色打印机被安装在一个打印服务器上并分配了一个名为“HpColor”的队列名称。假定微软的ICM需要下面两个文件与打印队列相关联: 一个名为hpclrsr.icm的色彩的profile文件,位于服务器的PRINT$\Color下面。 一个名为mscms.dll的DLL文件,位于服务器的PRINT$\Color下面。 在安装时,应用程序会调用ICM API函数AssociateColorProfileWithDevice,它依次调用SetPrinterDataEx以创建下面的服务器注册条目: CopyFiles\ICM\Directory:Color CopyFiles\ICM\Files:hpclrsr.icm CopyFiles\ICM\Module:mscms.dll Mscms.dll模块是一个点号及打印的DLL,它导出GenerateCopyFilePaths及SpoolerCopyFileEvent函数。 10.4.2.3点号及打印DLL 可以选择性地提供一个特殊的点号及打印DLL,并通过将它的名称与Module注册表值相关联来实现。这一DLL必须导出下面两个函数: GenerateCopyFilePaths 这一函数,是一个被服务器的假脱机和客户的假脱机同时调用的函数。可以用于修改Directory注册表值指定的目录路径,或者源路径(在服务器上),或者目标路径(在客户机器),或者两者都可以修改。 SpoolerCopyFileEvent 这一函数,也能被服务器服务器和客户的假脱机调用。它收到一个指示某一相关连接的打印机事件发生的事件代码。 一个点号及打印DLL不需要只导出这些函数。例如mscms.dll,它被微软的ICM组件所使用,同时导出一套ICM API函数。 但是,可以另外指定其他的DLL,而不是导出GenerateCopyFilePaths及SpoolerCopyFileEvent。为做到这些,分配DLL文件名到Files注册表键而不是Module注册表键(参考安装特定队列文件部分的内容)。 在打印服务器上,点号及打印DLL必须放置在一个由平台的SDK 中LoadLibrary函数搜索到的装载路径。 在安装程序通过调用SetPrinterDataEx将DLL的名称放置在服务器的注册表中时,所有接下来的对SetPrinterDataEx的调用导致一个对DLL的SpoolerCopyFileEvent函数的调用,并提供一个COPYFILE_EVENT_SET_PRINTER_DATAEX的事件代码。 不同于列在Files注册表键下的文件(参考安装特定队列的文件),点号及打印DLL不是在客户连接到服务器时从打印服务器拷贝的。相反,当创建了一个对打印服务器的连接时,DLL被假定已经驻留客户端。结果是,DLL可以用于另外的不是与点号及打印功能相关的目的。 为确定在Module关键字下命名的DLL是否能被发现,客户端假脱机检查LoadLibrary路径,然后检查驱动程序目录。安装句号及打印DLL到客户端的一种方法是在一个Printer.inf文件中指定它的名称,作为一个相关的文件,这样这个文件可以在下载驱动程序专用的文件时被拷贝到客户端的驱动程序目录。 10.4.3在打印机连接过程中支持点号及打印 当一个用户从客户端系统连接到打印服务器,驱动程序专用的和队列专用的文件必须从服务器发送到客户端。下面的主题被提供: 10.4.3.1下载驱动程序专用的文件 ■10.4.3.2下载队列专用的文件 10.4.3.1下载驱动程序专用的文件 客户系统通过调用AddPrinterConnection创建了一个到打印服务器的连接。这一调用导致在服务器上对GetPrinterDriver的调用,它读取打印机的.inf文件以填充DRIVER_INFO_3结构,且接着调用是对具有DRVIVER_INFO_3作为输入结构的AddPrinterDriver函数的调用。AddPrinterDriver函数导致列在DRIVER_INFO_3结构下面的所有文件都被发送到客户端。 函数及DRIVER_INFO_3结构在平台的SDK文档中描述。 10.4.3.2下载队列专用的文件 如果用户决定客户创建一个到打印服务器的打印机连接,并且如果安装程序已经创建了注册表条目,该注册表条目是在打印机安装过程中支持点号及打印部分中描述的,将会发生下面的事件: 用户应用程序调用AddPrinterConnection,它在平台的SDK文档中描述。 客户端的远端打印提供者(win32spl.dll)创建一个到服务器的连接。 服务器的假脱机发送驱动程序文件到客户机。 客户端的Win32spl.dll在服务器上调用EnumPrinterKey及EnumPrinterDataEx以拷贝打印机的注册表条目。 当服务器假脱机在处理EnumPrinterDataEx时枚举注册表值,在每一个遇到打印机的CopyFiles关键字诸如CopyFiles\ICM这样的子关键字时,执行下面的操作: 装载点号及打印DLL,如果被指定,调用它的GenerateCopyFilePaths函数,它可以修改源路径和/或目标路径。 创建SourceDir和TargetDir关键字,基于由GenerateCopyFilePaths返回的源路径和目标路径,并将他们作为EnumPrinterDataEx数据返回到客户假脱机。(这些关键字实际上不存在于服务器上)。 客户端的Win32spl.dll缓存收到的打印机关键字作为对EnumPrinterData及EnumPrinterDataEx调用的响应。 对打印机的CopyFiles关键字的每一个子关键字,如CopyFiles\ICM,客户端的Win32spl.dll执行下面的操作: 装载本地点号及打印DLL,如果提供了一个,并调用了它的GenerateCopyFilePaths函数,它可以修改源路径和/或目标路径(输入是从服务器收到的SourceDir和TargetDir关键字)。 从服务器下载所有与Files关键字相关联的文件。 记录一个事件,指明点号及打印文件被下载。 调用点号及打印DLL的SpoolerCopyFileEvent函数,如果一个DLL被提供,指定一个COPYFILE_EVENT_FILE_CHANGED事件。 客户端假脱机调用驱动程序的DrvprinterEvent函数,指定一个PRINTER_EVENT_CACHE_REFRESH事件。 客户假脱机再次调用驱动程序的DrvPrinterEvent函数,指定一个PRINTER_EVENT_ADD_CONNECTION事件。 如果提供一个点号及打印DLL,客户假脱机调用它的SpoolerCopyFileEvent函数,指定一个COPYFILE_EVENT_ADD_PRINTER_CONNECTION事件。 连接实例 有一个实例,假定安装程序已经定义了服务器的注册表条目,在安装实例部分描述过。另外,假定服务器被命名为NTPRINT并且客户被命名为MyClient。 为连接一个名为HpColor的打印队列到NTPRINT,一个在MyClient上的用户应用程序如下调用AddPrinterConnection: AddPrinterConnection(\\NTPRINT\HpColor) 在服务器上,假脱机装载mscms.dll并如下调用GenerateCopyFilePaths: GenerateCopyFilePaths( HpColor, Color, &SplclientInfo1, 1, \\NTPRINT\PRINT$\Color, &dwSourceDirSize, Color, &dwDestDirSize, COPYFILE_FLAG_SERVER_SPOOLER) 微软的ICM的mscms.dll模块不修改源路径和目标路径,因此它必须返回ERROR_SUCCESS。 服务器假脱机给MyClient返回如下的键: Sourcedir:\\NTPRINT\PRINT$\Color TargetDir:Color 在客户端,TargetDir的值扩展到C:\Winnt\System32\Spool\Drivers\Color下。 在MyClient上的假脱机执行下面的操作: 下载mscms.dll并如下调用GenerateCopyFilepaths: GenerateCopyFilePaths( \\NTPRINT\HPColor, Color, &SplclientInfo1, 1, \\NTPRINT\PRINT$\Color, &dwSourceDirSize, c:\Winnt\system32\Spool\Drivers\Color, &dwDestDirSize, COPYFILE_FLAG_CLIENT_SPOOLER) 微软的ICM的mscms.dll模块不修改源路径或目标路径,因此它只返回ERROR_SUCCESS。 下载hpclrlsr.icm到C:\Winnt\System32\Spool\Drivers\Color目录。 记录一个事件,指明被下载的点号及打印文件。 调用在mscms.dll中的SpoolerCopyFileEvent函数,指定一个COPYFILE_EVENT_FILE_CHANGED事件。 再次调用打印机驱动程序的DrvPrinterEvent函数,指定一个PRINTER_EVENT_ADD_CONNECTION事件。 调用在mscms.dll中的SpoolerCopyFileEvent函数,指定一个COPYFILE_EVENT_ADD_PRINTER_CONNECTION事件。 第11章 CPSUI 本章描述了公用属性表单用户接口(CPSUI:Common Property Sheet User Interface)。并提供了下面的主题: ■11.1CPSUI介绍 ■11.2CPSUI应用程序实例 ■11.3CPSUI提供的函数 ■11.4应用程序提供的回调函数 ■11.5创建属性表单页面 11.1 CPSUI介绍 CPSUI是一个用户模式的动态链接库,它使得开发者可以创建公用的、具有标准外观的属性表单页面。许多用CPSUI创建的页面由以下构成: 一个树形视图的窗口,每个树的节点表示一个可选的、用户可以修改的页面选项。 对每一个树形节点的设备环境的菜单,可以用于显示并选择与节点相关的参数值。 设备环境菜单项是用预置的一套CPSUI支持的Windows 控件。一个用户选择树形视图窗口的一个选项,然后用设备环境菜单为选项选择期望的值。 然而CPSUI设计用于任何的应用程序,它最初的应用就是Windows? NT/Windows 2000打印子系统。因此,DDK的设计指南及参考主要针对这一应用。 CPSUI为打印机和打印机文档提供预定义的属性表单页面。CPSUI为打印机提供由DeviceSettings构成的页面,以及向文档提供由Layout,paper/Quality,Advanced构成的页面。这些页面可以从打印文件夹的Printer菜单中被查看。 与打印机接口DLL相连接的打印假脱机,用这些预定义的页面为打印机和文档创建属性表单。关于怎样打印假脱机,打印机接口DLL和CPSUI之间的交互,参考打印机驱动程序中使用CPSUI部分的内容。 为微软Unidrv和Pscript驱动程序创建的定制的用户接口代码也可以使用CPSUI。更多的信息,参考第7章用户接口插件部分。 11.2 CPSUI应用程序实例 CPSUISAM,一个CPSUI应用程序的源代码,包含在这一DDK中。应用程序导致CPSUI去调用到打印假脱机为系统默认打印机创建属性表单页面。然后应用程序创建一个另外的属性表单页面,以指明在用CPSUI创建一个新页时可用的一些新技术。 ————————————————————————————————————————— 注意:打印机接口DLL不应当调用到打印假脱机。CPSUISAM指明一些CPSUI的能力但是不表示是应当被打印机接口DLL使用的技术。相反,这些DLL应当接着在打印机驱动程序中使用CPSUI部分中描述的步骤。 11.3 CPSUI提供的函数 CPSUI为应用程序提供下面二个重要的函数: CommonPropertySheetUI CommonPropertySheetUI函数是CPSUI的入口点。该函数导致属性表单页面被创建及显示,然后允许他们被一个用户查看并修改。 当一个应用程序调用CommonPropertySheetUI,它提供描述页面被创建的一个页面创建的回调函数地址。CPSUI调用这一回调函数以获得页面的描述。然后显示这一页面,允许应用程序用户修改包含在页面中的值,并用页面事件回调函数发送已修改的值到应用程序。CommonPropertySheetUI函数直到用户通过按“OK”或“Cancel”键解除了属性表单时才返回。 需要注意的是,打印机接口DLL不调用这一函数,它被打印假脱机调用。 ComPropsheet ComPropsheet函数是应用程序向CPSUI描述属性表单页面的手段。这样CPSUI可以创建并显示它们。CPSUI应用程序从页面创建回调函数来调用这一函数。通常,一个页面描述包括一个页面事件回调函数的指针,它的CPSUI将在应用程序用户修改了页面值时调用。 什么时候调用这些函数的详细说明,参考在打印机驱动程序中使用CPSUI部分的内容。 两个附加的CPSUI提供的函数,即SetCPSUIUserData和GetCPSUIUserData,可以被应用程序提供的对话框处理,以存储和检索应用程序提供的值。 11.4应用程序提供的回调函数 CPSUI与应用程序通过回调函数通讯。一个CPSUI应用程序必须提供两个类型的回调函数。 页面创建回调函数 页面事件回调函数 11.4.1页面创建回调函数 当一个应用程序对CPSUI的入口点函数(CommonPropertySheetUI)做了一个初始化的调用,它必须包括一个PFNPROPSHEETUI类型的回调函数的地址。这一回调函数负责描述属性表单页面并发送这些描述给CPSUI以在创建时使用。 CPSUI的CommonPropertySheetUI函数立即回调PRPPSHEETUI类型的函数,提供一个PROPSHEETUI_INFO结构的地址。应用程序然后调用CPSUI的ComPropSheet函数,提供CPSUI可以使用的页面描述以创建页面,如下图中所描述的: 插入图??? 更多的信息,请参考指定页面的方法部分。 11.4.2页面事件回调函数 当一个用户与一个属性表单页面交互,操作系统发送这样的窗口事件作为变化了的焦点或被修改了的值消息。一个CPSUI应用程序怎样接收一个页面的窗口事件消息依赖于应用程序怎样定义这一页面。 如果页面是用CPSUI提供的页面和模板描述的,它可以提供一个CPSUI消息句柄。 如果应用程序创建一个不是由CPSUI提供的定制的页面,它必须提供一个对话框过程。更多的信息,参考对话框过程及CPSUI部分的内容。 一个CPSUI应用程序在它调用ComPropSheet函数时,将页面事件回调函数的地址提供给CPSUI。 11.4.2.1 CPSUI消息句柄 CPSUI消息句柄是一个用_CPSUI-CALLBACK函数类型定义的回调函数。如果使用CPSUI-提供的这一类型的页面及模板,就推荐使用这一事件回调函数。 当一个用户与属性表单页面进行交互时,会导致一个事件发生,CPSUI解释这一事件并调用_CPSUICALLBACK类型的函数,提供一个CPSUICBPARAM结构并描述该回调函数被调用的原因。 回调函数必须处理该事件,然后给CPSUI返回一个状态值,它指明是否该页面应当被重新显示或者重新初始化。 一个对话框过程是一个处理由系统发送的窗口消息的回调函数。如果正在创建一个定制的属性表单页面并且它不被CPSUI支持时,就需要这一类型的事件回调。(也可以用CPSUI提供的页面及模板的对话框过程)。更多的关于对话框过程的信息,参考在平台的SDK文档中描述的DialogProc函数。 对话框过程的指针是用DLGPROC指针类型声明的,在平台SDK文档中有描述。 对所有用CPSUI创建的属性表单页面,窗口消息在被传递到应用程序提供的对话框过程之前首先被CPSUI解释。如果该页面是用CPSUI提供的模板定义的,应用程序提供的对话过程就可以提供一个指明CPSUI应当处理这一消息的返回值。 一个对话框过程可以用SetCPSUIUserData及GetCPSUIUserData函数去存储并检索应用程序提供的的值。 更多的关于在CPSUI应用对话框处程的信息,参考在线DDK中的DLGPAGE的Comments部分的内容。 11.5创建属性表单页面 用CPSUI创建的属性表单页面是由属性表单选项构成的,这里每一个选项表示一个用户可修改的值。属性表单选项的对话框是用一套CPSUI支持的窗口控件创建的,用以让用户修改选项的值。 CPSUI提供的窗口控件可以在CPSUI提供的页面及模板中显示,或者他们可以用于定制页面。有几种方法可以指定页面,这些方法都包括在调用CPSUI的ComPropSheet函数中。 为打印机和打印文档创建一个属性表单页面涉及打印机驱动程序CPSUI及打印驱动程序与应用程序、打印机接口DLL、CPSUI之间的交互。 11.5.1属性表单选项 属性表单选项是一个可显示的、可选择的属性表单页面上的项目。通常,用户可以修改一个选项的值。CPSUI帮助应用程序用一套预定义的CPSUI支持的窗口控件使用一个标准的格式创建选项。应用程序不为这些控件提供资源。 每一个属性表单页面通常包含好几个选项,对每一个属性表单页面选项,一个CPSUI应用程序必须用下面的CPSUI结构: 一个OPTITEM结构,它标识选项的显示名称和其他特征。 一个OPTTYPE结构,它标识选项的显示对话框类型(CPSUI选项类型) 一个或多个OPTPARAM结构,它标识选项的用户可选择的参数值。 为使用这些CPSUI结构描述属性表单选项,页面包含的选项必须用COMPROPSHEETUI结构来定义。 11.5.2 CPSUI支持的窗口控件 CPSUI支持一套窗口控件以向用户提供一致的接口。在为打印机设备和文档创建属性表单页面时使用这些窗口控件是特别重要的,因为用户期望对所有的打印机有一致的接口。 CPSUI支持的windows窗口控件包括: 包含两个或三个单选钮(radio button)的对话框。 滚动条及轨道条。 编辑、列表及组合框。 一个上/下箭头框 检查框 这一套窗口控件在指定属性表单选项时必须被经常使用到。窗口控件是用CPSUI选项类型指定的。而通常都不需要,其外观的表现可以被定制。更多的信息,可参考定制CPSUI支持的窗口控件。 CPSUI也定义了两个特殊的控件,叫做扩展的检查框(extended check box)和扩展的按键(extended push button),这两个控件提供超过标准检查框和按键的能力,可以分别用EXTCHKBOX和EXTPUSH结构来指定。 11.5.2.1定制CPSUI支持的窗口控件 如果用CPSUI支持的窗口控件与CPSUI提供的页面和模板相连,CPSUI提供一种方式描述这些控件并使他们恰当地组在一起的窗口控制资源。因此,不需要为控件提供资源。 另一方面,如果创建一个不使用CPSUI提供的页面或模板的属性表单页面,必须定制所使用的CPSUI支持的窗口控件。为做到这些,需要为CPSUI选项类型提供窗口控制资源。必须用每一个选项的OPTTYPE结构的BegCtroID成员为这些资源指定标识符。 如果定制CPSUI支持的窗口控件,要记住,如果在OPTITEM结构中的OPTIF_HIDE标记被设定,则CPSUI将不显示一个选项。CPSUI移动余下的控件以填充一般由隐藏的选项占据的空间。因此,如果创建一个包含几个同步显示的选项的页面,则必须服从下面的规则: 每一个选项应当占据属性表单页面的整个水平空间。 选项对话框不应当彼此重叠。 表示选项的单选按钮是从左边到右边排列的,按钮及图标应当按X轴排列。如果按钮是从上到下排列,则按钮及图标应当按Y轴排列。 如果几个项目共享一个组对话框,这个组对话框必须属于第一个OPTITEM,它是在组对话框中的最顶上的项目。组对话框必须足够大以包含所有与它相关联的项目。 同样要注意的是,如果单选钮及一个图标是从上到下排列的并且一些控件是被隐藏的,CPSUI不删除结果中的y方向上的空格。 11.5.3 CPSUI提供的页面和模板 CPSUI提供一套预定义的属性表单页面及三个页面模板。预定义的属性表单页面包括以下内容: 一套具有布局(Layout)、纸张/质量(paper/Quality)和高级(Advanced)卡片标题的三个页面。这些页面指明为打印机包含着文档属性,并可以在打印机接口DLL的DrvCocumentPropertysheets函数用于创建一个属性表单。 一个具有卡片标题为“高级(Advanced)”的单一页面。同样,这一页面指明为打印机包含着文档的属性,并且可以用于从打印机接口DLL的DrvDocumentPropertySheets函数创建属性表单。 一个具有卡片标题为“设备设置(DeviceSettings)”的单一页面。这一页面指明包含的打印机属性,也可以被用于从打印机接口DLL的DrvCocumentPropertySheets创建一个属性表单。 一个单个的、一般树形视面的页面,没有预定义的标题。任何的?CPSUI应用程序都可以用于这一页面。 使用一个预定义的页面,一个应用程序必须用COMPROPSHEETUI结构的pDlgPage成员来标识它。 CPSUI也同时提供三个预定义页面的模板。CPSUI用这些模板创建它预定义的页面。应用程序可以使用他们。模板主要是由以下构成的: 一个树形视图的页面模板,CPSUI用它去创建预定义的高级(Advanced)和设备设置(DevciceSettings)页面。这一模板由包含节点的每一个属性表单选项的树形视图控件构成。设备环境菜单与每一个树的节点相关联。每一个节点的设备环境菜单提供一种方式,通过这一方式,用户可以修改选项的值。CPSUI为这一模板提供一个对话框过程,它为所有CPSUI支持的窗口控件处理Windows消息。 另外两个控件模板,CPSUI用来创建预定义的布局(Layout)和纸张/质量(Paper/Quality)页面。CPSUI为这一模板提供一个对话框过程,它为所有CPSUI支持的窗口控件处理Windows消息。 为使用一个预定义的页面模板,一个应用程序必须用DLGPAGE结构的DlgTemplateID成员标识它。 11.5.4指定页面的方法 一个应用程序可以使用以下三种方法中的任一种方来为CPSUI指定属性表单页面。下面的每一种方法包括调用CPSUI的ComPropSheet函数指定ComPropsheet函数的一种源代码。 提供一个COMPROPSHEETUI结构 如果应用程序通过传递COMPROPSHEETUI结构到ComPropSheet来描述了一个属性表单页面,它可以: 使用一种CPSUI提供的页面及模板以指定那些预定义的、标准的打印机接口DLL能用于打印机属性表单的页面类型。 指定一套可显示在页面上的、用户可修改的属性表单选项。 指定一个当用户查看或修改页面选项时,CPSUI可以调用的页面事件的回调函数。 提供一个PROPSHEETPAGE结构 一个PROPSHEETPAGE结构(在平台的SDK文档中描述)可以用来描述一个属性表单页面。如果该页面不能在使用COMPROPSHEETUI结构时可用的普通(标准)对话框构建,打印机接口DLL则通常不使用该方法。 提供一个回调函数 一个应用程序可以传递给ComPropSheet一个PFNPROPSHEETUI类型的回调函数的地址,CPSUI立即调用它。回调函数负责调用ComPropsheet以创建属性表单页面。 打印假脱机用这一方法去通知CPSUI的已经存在的打印机接口DLL的DrvCocumentPropertySheets及DrvDevicePropertySheets函数。同样,Unidrv及Pscript驱动程序用这一技巧去通知CPSI的已经存在于用户接口插件中的IPrintOemUI::DocumentPropertySheets和IPrintOemUI::DevicePropertySheets的COM方法。 无论哪一个方法用于指定一个新的页面,该页面必须通过传递一个父组的句柄到ComPropSheet函数而被分配到一个父组中。 11.5.4.1父组 属性表单页面可以通过给赋单一的父组(group parent)的值来分成不同的组。可以通过调用CPSUI的具有一个CPSFUNC_INSERT_PSUIPAGE函数代码的ComPropSheet函数来创建一个父组,并指定PSUIPAGEINSERT_GROUP_PARENT作为一个INSERTPSUIPAGE_INFO结构的Type成员。 当一个新的父组被创建后,就返回一个句柄。当增加或删除属性表单页面时,然后句柄可以被用作到ComPropSheet的hComPropSheet参数。 另外,一个父组的句柄作为应用程序的PFNPROPSHEETUI类型的回调函数所接收的PROPSHEETUI_INFO结构的hComPropSheet成员而被接收。如果不创建新的父组,所有的属性表单应当被分配到这一个父组上。 可以在每一个被创建的父组下面创建另外的父组。属性表单自身被看作是顶层的父组,如果不能明确地创建出父组,所有增加的属性表单页面被分配给顶层父。 11.5.5打印机驱动程序中使用CPSUI 打印假脱机,与打印机接口的DLL相连,用CPSUI为打印文档和打印机设备创建属性表单页面。当应用程序(如微软的Word)为一个打印文档显示属性表单时,必须包含下面的步骤: 应用程序调用打印假脱机的DocumentProperties函数(在平台的SDK文档中描述),指定文档要被打印到的打印机。 打印假脱机调用CPSUI的入口点函数CommonPropertySheetUI,以指定一个内部的PFNPROPSHEETUI类型的回调函数。 CPSUI调用假脱机的PFNPROPSHEETUI类型的回调函数。 假脱机的PFNPROPSHEETUI类型的函数调用CPSUI的ComPropSheet函数(用一个CPSFUNC_ADD_PFNPROPSHEETUI函数代码)以通知CPSUI适当的打印机接口DLL的地址。 CPSUI调用打印机接口DLL的DrvDocumentPropertySheets函数。 打印机接口DLL的DrvDocumentPropertySheets函数调用CPSUI的ComPropSheet函数(通常用一个CPSFUNC_ADD_PCOMPROPSHEETUI函数代码)以提供CPSUI属性表面页面的描述及页面事件回调(page event callbacks)。 CPSUI的ComPropSheet函数调用CreatePropertySheetPage(在平台的SDK文档中描述)以创建由打印机接口DLL指定的属性表单页面。CPSUI然后调用PropertySheet(在平台的SDK文档中描述)以显示属性表单页面。 下面的图示意了这些步骤: 插入图??? 由于应用程序用户遍历属性表单页面及修改选项值,操作系统通知CPSUI页面事件及CPSUI,并按次序调用由打印机接口DLL提供的页面事件回调函数。页面事件回调函数处理页面事件并根据需要,存储内部新选择的选项值。 当一个用户通过按下OK或Cancel键撤消了属性表单,CPSUI释放该页并导致CommonPropertySheetUI函数对打印假脱机返回值,然后向应用程序返回控制。 当一个应用程序为打印机设备而不是打印文档显示了属性表单时,也使用相同的步骤,除过应用程序调用假脱机的PrinterProperties函数和假脱机传递打印机接口DLL的DrvDevicePropertySheets函数的地址到CPUSI。 第12章 打印机色彩管理 这一章描述了打印机驱动程序应当执行的支持色彩管理的操作。提供了下面这些主题: ■12.1允许使用色彩管理 ■12.2控制色彩管理 ■12.3放置ICC的Profile文件 ■12.4安装ICC的Profile文件 12.1允许色彩管理 色彩管理可以由应用程序或打印驱动程序来允许使用。应用程序可以使用下面两种方法之一去允许色彩管理: 调用SetICMMode(在平台的SDK文档中描述),指定ICM_ON。 这一方法允许使用系统控制的色彩管理。 当调用CreateDC创建一个打印作业时指定DEVMODE结构,并且设定DEVMODE结构的dmICMMethod成员为DMICMMETHOD_SYSTEM,DMICMMETHOD_DRIVER或者DMICMMETHOD_DEVICE三者中的一个。 这一方法允许应用程序选择系统控制的,驱动程序控制的或设备控制的色彩管理(假定指定的控制类型都被支持)。 打印机驱动程序可以通过驱动程序的默认DEVMODE结构的dmICMMethod成员设置为DMICMMETHOD_SYSTEM,DMICMMETHOD_DRIVER或DMICMMETHOD_DEVICE之一来允许使用色彩管理。(如果为CreateDC提供了一个DEVMODE结构,应用程序可以覆盖默认的设置。另外,驱动程序负责在执行驱动程序的DrvDocumentPropertySheets函数时为色彩管理存储用户的选择)。 12.2控制色彩管理 打印机的色彩管理可以被应用程序、系统(GDI)、驱动程序或设备硬件来控制。驱动程序通过检测被传递到实现它的DDI绘制函数的BRUSHOJB和XLATEOBJ结构中的标记以确定哪一个组件可以正确地处理色彩管理。 定义了下面的标记: 标记 定义 在BRUSHOJB中的BR_DEVICE_ICM 色彩管理是由驱动程序或设备执行的  在XLATEOJB中的XO_DEVICE_ICM   在BRUSHOJB中的BR_HOST_ICM 色彩管理是由应用程序或系统(GDI)执行的  在XLATEOJB中的XO_HOST_ICM    下面的主题描述了驱动程序对色彩管理的支持情况: ■12.2.1系统控制 ■12.2.2驱动程序控制及设备控制 ■12.2.3支持CMYK色彩空间 ■12.2.4对JPEG及PNG图像的色彩管理 12.2.1系统控制 系统控制的色彩管理是默认的色彩管理类型。它也是为打印机推荐的类型。如果色彩管理被允许,GDI在发送所有DIB、笔、画刷到驱动程序打印机图形接口的DLL之前修改他们的色彩,基于输入和输出的色彩空间及安装的ICC Profile文件。 不需要ICM专用的代码增加到打印机以支持系统控制的色彩管理,不同于指明对CYMK色彩空间(如果合适)的支持,如在支持CMYK色彩空间部分所描述的。 12.2.2驱动程序控制及设备控制 如果色彩管理控制由驱动程序或打印机硬件提供,驱动程序的打印机图形DLL必须在DEVINFO结构中设置GCAPS_ICM标记。 驱动程序必须指明对CYMK色彩空间的支持(如果合适),如在支持CMYK色彩空间部分所描述的内容。 打印机图形DLL必须定义下面三个函数: DrvIcmCreateColorTransform DrvIcmDeleteColorTransform DrvIcmCheckBitmapBits GDI调用DrvIcmCreateColorTransform函数用ICC Profile(在平台的SDK文档中描述)提供给打印机驱动程序。这些给定的Profile,函数可以创建一个内部的色彩变换以在更正色彩信息时候时使用。色彩变换是一个特定驱动程序,内部定义的从一个色彩空间到另外一个空间的映射。函数返回一个到变换的句柄,并且由GDI存储。 在BRUSHOBJ及XLATEOBJ结构中的标记指明色彩管理是否由系统(或应用程序)来执行或驱动程序(或设备)来执行。在每一个驱动程序实现的接收一种或所有函数的DDI绘制函数时,标记也必须检查。如果系统中应用程序当前正在处理色彩管理,驱动程序或设备则不处理。如果驱动程序或设备色彩管理被允许,DDI函数必须调用BRUSHJOBJ_hGetColorTransform或XLATEOBJ_hGetColorTransform函数。 处理专有色彩管理 对一些设备,不管ICM是否已经被允许,专有的色彩管理都执行(通过驱动程序或硬件)许。这些设备的驱动程序不管ICM是否已经被允许,都不许执行色彩改正。这些设备的驱动程序收到的图像数据已经被修改时不允许执行色彩改正。预先修改的数据可以被收到,如果: 一个应用程序有已经进行了色彩修改了的图像“在设备设备环境之外(OutSide the DC)”(参考平台的SDK文档)。 色彩管理由系统来处理。 对这两种情况的任一种,在BRUSHOBJ中的FlColorType成员的BR_HOST_ICM标记和在XLATEOBJ中的flXlate成员的XO_HOST_ICM标记必须被设定。甚至在DEVMODE的dmICMMethod成员设置为DMICMMETHOD_NONE时这些标记也可以被设定。 12.2.3支持CMYK色彩空间 不管是否一个色彩管理是由应用程序、系统、驱动程序或设备来处理,一个打印机图形DLL必须指明是否它支持CMYK色彩空间。这是由在DEVINFO结构中设定GCAPS_CMYKCOLOR标记来完成的。如查这一标记被设置并且CMYK的Profiles文件被使用,则GDI为位图、画刷及笔发送CMYK色彩数据到打印机图形DLL,而不是RGB数据。GDI也设置下面的标记: BRUSHOBJ结构中的flColorType成员中的BR_CMYKCOLOR标记。 XLATEOBJ结构中的flXlate成员中的XO_FROM_CMYK标记。 注意,如果驱动程序支持CMYK色彩空间,它也必须支持过渡调色。这样,如果驱动程序在DEVINFO结构中设置GCAPS_CMYKCOLOR标记,它也必须设置GCAPS_HALFTONE标记。 12.2.4对JPEG及PNG图像的色彩管理 对提供硬件JPEG及PNG压缩图像支持的打印机来说,色彩管理必须由驱动程序或设备来处理面不是由GDI来处理。 在应用程序发送JPEG或PNG压缩图像到打印机之前,它将用CHECKJPEGFORMAT或CHECKPNGFORMAT的序列码调用ExtEscape。这将导致对驱动程序的DrvQueryDeviceSupport函数的调用,具有一个查询类型为QDS_CHECKJPEGFORMAT或QDS_CHECKPNGFORMAT及一个缓冲区包含压缩数据。 驱动程序可以检查图像数据并决定是否它能支持该图像。如果XLATEOBJ结构的XO_DEVICE_ICM标记或XO_HOST_ICM标记被设定时,支持的图像必须执行色彩转换,由于GDI不能在这些图像做色彩转换。 对这些压缩的图象,色彩空间信息通常包含在图像数据中。一个例外就是JFIF文件,它是YcbCr编码的并且对它来说,默认的sRGB空间是一个好的近似值。但是,一个JFIF文件也许可以包含一个专有的指定色彩空间的APPx标记,在这种情况下,驱动程序必须用色彩空间转换图像。 更多的关于支持JPEG和PNG压缩图像的数据,参考在线DDK中的DEVINFO的Comments部分。 12.3放置ICC的Profile文件 当色彩管理被允许,GDI使用下列步骤查询一个适当的ICC profile: 如果驱动程序的打印机接口DLL提供一个DrvQueryColorProfile函数,GDI客户端调用该函数以驱动程序一个机会指定profile。如果函数返回一个profile,它就被使用。 如果DrvQueryColorProfile不存在或者不返回一个profile,GDI查询已经为特定打印机类型安装的色彩目录的profile。GDI使用它发现的第一个匹配的DEVMODE结构中的精度、媒体类型及抖动的profile。 如果色彩目录不包含特定打印机类型的且与指定的DEVMODE内容匹配的任何的profile,则GDI使用系统默认的sRGB Profile。 更多的信息,参考安装ICC Profile部分的内容。另外的关于ICC Profile的信息,可以在平台的SDK文档中找到。 12.4安装ICC的Profiles文件 为给一个打印机安装ICC Profiles,文件必须被列在printer.inf文件中。 下面是一个.inf文件的实例,它使得两个ICC profile被安装。但要注意,为色彩目录所写的profile文件,具有打印机DIRID值是66003。 [Version] Signature=”$Windows NT$” Provider=My Company ClassGUID={4D36E979-E325-11CE-BFC1-08002BE10318} Class=Printer [My Company] My Printer Mode1=MYDRIVER,My_Printer_Model [MYDRIVER] DriverFile=DRVR.DRV DataFile=DRVR.DRV CopyFiles=@DRVR.DRV,MY_COLOR_PROFILES DataSection=MYDATA [MYDATA] HelpFile=DRVR.HLP DefaultDataType=EMF [MY_COLOR_PROFILES] profile1.icm profile2.icm [DestinationDirs] DefaultDestDir=11 My_COLOR_PROFILES=66003 第4卷 图形驱动程序设计指南 1 第1部分 图形驱动程序 2 第1章 图形系统概述 2 1.1 文档约定 2 第2章 对图形驱动程序的GDI支持 3 2.1 从驱动程序的观点看GDI 3 2.1.1 作为应用图形语言的GDI 3 2.1.2 作为绘制引擎的GDI 3 2.1.2.1 GDI管理的位图 4 2.1.2.2 GDI管理的直线和曲线 4 2.1.2.3 GDI管理的属性:画刷 4 2.1.2.4 GDI过渡调色功能 4 2.2 GDI/驱动程序的分工 5 2.2.1 GDI和驱动程序的通信 5 2.2.1.1 GDI用户对象 5 2.2.1.2 GDI服务例程 6 2.2.2 PDEV协商 6 2.2.3 表面协商 7 2.2.3.1 表面坐标 7 2.2.3.2 DC原点 7 2.2.3.3 FIX坐标 7 2.2.3.4 表面类型 7 2.2.3.5 GDI彩色空间转换 9 2.2.3.6 Hooking和Punting 10 2.3 GDI支持的服务 11 2.3.1 GDI对表面的支持 11 2.3.2 GDI支持的调色板 12 2.3.3 路径的GDI服务 13 2.3.4 GDI绘图服务及其他 14 2.3.5 GDI字体和文本服务 14 2.3.6 GDI支持的服务及其他 15 2.4 浮点数支持 15 2.5 数据类型 15 第3章 支持DDI 16 3.1 图形驱动程序函数支持 16 3.1.1 必需的图形驱动程序函数 17 3.1.2 有条件必需图形驱动程序函数 17 3.1.3 可选的图形驱动程序函数 18 3.2 支持初始化和终止函数 19 3.2.1 驱动程序初始化和清除 20 3.2.2 PDEV初始化和清除 20 3.2.3 启用和禁止表面 21 3.3 创建设备相关位图 22 3.4 支持图形输出 22 3.4.1 绘制直线和曲线 23 3.4.1.1 装饰线 23 3.4.1.2 几何宽度直线(Geometric Wide Line) 24 3.4.1.3 花式装饰直线(Styled Cosmetic Line) 25 3.4.1.4 贝塞尔曲线 26 3.4.2 绘制和填充路径 26 3.4.2.1填充路径模式 26 3.4.2.2 填充区(闭合路径) 27 3.4.3 拷贝位图 27 3.4.4 过渡调色技术 29 3.4.5 图像颜色管理 30 3.5 支持DDI颜色和调色板函数 30 3.5.1 管理调色板 30 3.5.2 实现画刷 30 3.6 支持DDI字体和文本函数 32 3.6.1 管理和优化字体 32 3.6.1.1字体规格函数 32 3.6.1.2 字体驱动程序函数 33 3.6.1.3 TrueType字体驱动程序函数 34 3.6.2 绘制文本 34 第2部分 显示器和视频微端口驱动程序 35 第1章 显示器介绍 35 1.1 显示器体系结构 35 1.1.1 显示器驱动程序职责 36 1.1.2 视频微端口驱动程序职责 36 1.2 图形适配器的类型 37 1.2.1 帧缓冲区 37 1.2.2 加速器 37 1.2.3 协处理器 37 1.2.4 VGA 37 1.3 一般的设计和实现策略 38 1.4 访问图形适配器 38 1.4.1 通过IOCTL和视频微端口驱动程序通信 38 1.4.2 访问帧缓冲区和硬件寄存器 39 1.5 创建图形INF文件 39 1.5.1 INF GeneralConfigData段 40 1.5.2 显示器INF文件段 41 1.5.3 监视器INF文件段 41 1.6 显示器和视频微端口驱动程序的兼容性测试要求 44 1.6.1 INF和安装要求 44 1.6.2 视频微端口驱动程序请求 44 1.6.3 显示器驱动程序的要求 45 1.6.4 控制面板的要求 45 第2章 显示器驱动程序 46 2.1 显示器驱动程序DDI 函数 46 2.1.1 请求的显示器驱动程序函数 47 2.1.2有条件请求的显示器驱动程序函数 47 2.1.3任意显示器驱动程序函数 47 2.2 显示器驱动程序初始化 48 2.3 桌面管理 50 2.3.1 切换桌面:对DrvAssertMode的响应 50 2.3.2返回显示器模式:DrvGetModes 50 2.3.3 支持多PDEV 50 2.4 指针控制 52 2.4.1 指针绘制 53 2.4.1.1 绘制单色指针 53 2.4.1.2 绘制彩色指针 53 2.4.2 控制指针:DrvSetPointerShape 54 2.4.3 移动指针:DrvMovePointer 54 2.5 管理显示器调色板 54 2.6 显示器驱动程序中的位图 54 2.7 异步绘图 55 2.8 显示器驱动程序中的透明性 56 2.9 显示器驱动程序的特别效果 56 2.10 显示器的颜色管理 56 2.10.1 监视器Profile 57 2.11 DirectDraw和GDI 58 2.12 跟踪窗口变化 58 2.13 支持DitherOnRealize标志 60 2.14 支持分组的帧缓冲区 60 2.15 卸载视频硬件 62 2.16 显示器驱动程序中的多监视器支持 62 2.17 镜像驱动程序 63 VIDEO\DISPLAYS\MIRROR\DLL 63 2.17.1 镜像驱动程序INF文件 64 2.17.2 镜像驱动程序安装 64 2.18 Newdisp:动态重新装载显示器驱动程序 65 2.18.1显示器驱动程序测试工具 65 2.18.2允许的驱动器加速的动态改变 66 第3章DirectDraw DDI 67 3.1 DirectDraw头文件、例子代码和参考 67 3.2 DirectDraw体系结构总览 67 3.2.1 关于DirectDraw 68 3.2.2 DirectDraw驱动程序 69 3.2.2.1 DirectDraw驱动程序的第一步 69 3.2.3 硬件仿真层次 69 3.3 DirectDraw驱动程序基础 70 3.3.1 DirectDraw表面 70 3.3.1.1复杂的表面和附件 71 3.3.1.2创建和注销DirectDraw表面 71 3.3.1.3丢失表面和原子恢复 71 3.3.1.4丢失驱动程序管理的纹理 72 3.3.2 显示内存 72 3.3.2.1内存堆分配 72 3.3.3 内存配置例子 73 3.3.3.1线性内存分配 73 3.3.3.2矩形内存分配 75 3.3.3.3混合内存分配 76 3.3.3.4在Windows95/98上寻址的分组转换内存 78 3.3.3.4.1使用VflatD 79 3.3.4 交换 79 3.3.4.1撕裂 80 3.3.4.2三倍的缓冲 80 3.3.4.3定时一个交换 81 3.3.4.4交换间隔 82 3.3.4.5重叠支持 83 3.3.4.6纹理支持 84 3.3.5 使用图阵 84 3.3.5.1透明的位块转移 84 3.3.5.2限幅列表 84 3.3.5.3颜色填充和图案填充 85 3.3.5.4 DirectDraw和颜色空间转换 85 3.3.6 假定状态信息的驱动程序 85 3.3.7 打开显卡并在Windows95/ Windows 98上改变模式 86 3.3.7.1 Enable 86 3.3.7.2 ReEnable 86 3.4 DirectDraw驱动程序初始化 87 3.5 DirectX视频端口扩展 88 3.5.1 视频端口扩展背景 88 3.5.1.1视频的显示问题 89 3.5.1.1.1 NTSC和隔行扫描的数据 89 3.5.1.1.2 NTSC /PAL转换 89 3.5.1.1.3 MPEG和顺序的内容 90 3.5.1.1.4 顺序扫描监视器和隔行扫描的数据 90 3.5.1.2 用VPE:Bob和Weave显示隔行扫描的视频 90 3.5.1.2.1 Bob方法 91 3.5.1.2.2 Weave方法 91 3.5.1.2.3 模式指示器和变形格式 91 3.5.1.3 在DirectDraw API和驱动程序里的VPE功能 92 3.5.2 DirectX VPE初始化 92 3.5.3内核模式视频传输支持的DirectDraw接口 93 3.6 彩色控制初始化 93 3.7 AGP支持 93 3.7.1为非本地显存标志支持 94 3.7.2指定非本地显存堆 94 3.7.3实际堆基本地址的通知 95 3.7.4处理非本地显存表面的回调 95 3.7.5在非本地显存里重新排序纹理 96 3.7.6处理DMA-style AGP 97 3.7.6.1为DMA模式的非本地显存标志支持 98 3.7.6.2为DMA模式的非本地显存报告DirectDraw能力 98 3.7.6.3为DMA模式的非本地显存报告Direct3D能力 98 3.8 内核模式视频传输 98 3.8.1 VPE和内核模式视频传输的体系结构 99 3.8.2 使用内核模式视频传输 99 3.8.2.1获得用户模式句柄 100 3.8.3 Windows 2000的DxApi微端口功能 100 3.8.3.1 DxApi微端口初始化 100 3.8.3.2视频VBI捕获 101 3.9 扩展的表面定位 101 3.9.1旧式定位方法回顾 101 3.9.2使用扩展的表面定位 102 3.10 扩展的表面能力 103 3.10.1新的表面能力 103 3.10.2暴露扩展的表面能力 104 3.10.3扩展的堆限制 104 3.10.4扩展的表面描述结构 105 3.11 压缩的纹理表面 105 3.11.1枚举DXT格式 105 3.11.2创建压缩的纹理表面 106 3.11.3使用压缩的纹理表面 106 3.12 运动补偿 107 3.13 返回DirectDraw和Direct3D的值 108 第4章Direct3D DDI 109 4.1 Direct3D DDI 头文件、示例代码和参考 109 4.2跨平台Direct3D驱动程序开发 109 4.3 Direct3D驱动程序的回调函数 110 4.3.1请求的Direct3D驱动程序回调函数 110 4.3.2操作码处理 111 4.3.3对Direct3D驱动程序回调的返回码 111 4.4 Direct3D驱动程序初始化 112 4.5 Direct3D环境管理 113 4.5.1 建立环境 113 4.5.2 删除环境 113 4.5.3 在环境中维护状态 113 4.5.4 Direct3D表面处理总览 113 4.5.5 用D3dCreateSurfaceEx建立驱动程序方的表面结构 114 4.5.6 为什么驱动程序方的结构不能是一个指向表面的指针? 114 4.5.7 D3dCreateSurfaceEx和后援表面 115 4.5.8 使用D3dCreateSurfaceEx要考虑的其他问题 115 4.5.8.1 D3dCreateSurfaceEx和复杂表面 115 4.5.8.2 D3dCreateSurfaceEx和MipMap 115 4.5.8.3 D3dCreateSurfaceEx句柄和Flip 115 4.5.8.4 D3dCreateSurfaceEx句柄和DirectDraw DDI 116 4.5.8.5 当恢复复杂的Flipping链时要考虑的问题 116 4.5.8.6 表面句柄、丢失表面和环境 116 4.6 Direct3D纹理管理 117 4.6.1 多纹理 117 4.6.2 调色板纹理 121 4.6.3 调色板Blitting 122 4.6.3 驱动程序管理的纹理 122 4.7 绘画原语和状态改变 122 4.7.1 命令和顶点缓冲区 123 4.7.1.1命令和顶点缓冲区的申请 123 4.7.2 Direct3D命令缓冲区 124 4.7.3 Direct3D顶点缓冲区 125 4.7.4加速状态管理 125 4.8 柔性顶点格式(FVF) 126 4.8.1决定顶点缓冲区数据格式 126 4.9 Direct3D驱动程序的高级主题 128 4.9.1 优化的纹理 128 4.9.1.1优化纹理API 129 4.9.2模版位面 130 4.9.2.1模版操作码 131 4.9.3 Guard Band Clipping 131 4.9.4范围调整 131 4.9.5 W缓冲区 132 4.9.5.1 w缓冲区API 132 4.9.5.2 w缓冲区DDI 132 4.9.6 Bump Mapping 133 4.9.7硬件变换和照明 134 4.9.7.1顶点混合 134 4.9.7.1.1多矩阵顶点混合 134 4.9.7.1.1.1 多矩阵顶点混合实现算法 135 4.9.7.2用户裁剪位面 136 4.9.7.3纹理坐标变换 136 4.9.7.4立方环境图支持 137 4.9.7.4.1环境映射和标准扩散照明的集成 138 4.9.7.5顶点和像素雾化 138 4.9.7.5.1顶点雾化 138 4.9.7.5.2像素雾化 139 4.9.7.5.3基于范围的雾化 140 4.10 Direct7.0发行事项 141 4.10.1 FVF 更新 141 4.10.2光栅更新 141 第5章 小型客户驱动程序 143 5.1 MCD头文件,示例代码及参考 143 5.2 MCD体系结构 143 5.3 MCD接口 144 5.3.1必需的MCD函数 144 5.3.2特定条件下必需的MCD函数 145 5.3.3可选的MCD函数 145 5.4 MCD初始化 146 5.4.1 Hdev句柄 146 5.4.2像素格式 147 5.5绘制设备环境 147 5.5.1设备环境的创建 147 5.5.2设备环境绑定 148 5.5.3设备环境删除 149 5.5.4状态 149 5.5.5视口 149 5.6内存管理 150 5.6.1 DMA支持 150 5.7帧缓冲区管理 150 5.7.1分配绘制缓冲区 150 5.7.2查询绘制缓冲区 151 5.7.3交换绘制缓冲区 151 5.7.4 MCD中的窗口跟踪 151 5.8绘制操作 152 5.8.1绘制OpenGL的原语 152 5.8.1.1每个最大值的考虑 153 5.8.2绘制变化范围 153 5.8.3清除缓冲区 154 5.8.4同步化 154 5.9纹理映像 154 5.9.1纹理环境状态 155 5.9.2纹理状态 155 5.10像素操作 156 5.10.1像素状态 156 5.11分层平面 157 5.11.1描述分层平面 157 5.11.2交换分层平面 158 5.11.3管理分层调色板 158 第6章 视频微端口驱动程序 158 6.1视频微端口头文件、示例代码及参考 159 6.2在图形体系结构中的视频微端口驱动程序 159 6.2.1视频微端口驱动程序特定平台的详细内容 160 6.2.1.1非VGA兼容、基于x86的微端口驱动程序 160 6.2.1.2VGA兼容的基于x86的微端口驱动程序 160 6.3视频微端口驱动程序接口 161 6.3.1必需的微端口驱动程序函数 161 6.3.2一定条件下必需的视频微端口驱动程序函数 161 6.3.3可选的视频微端口驱动程序 162 6.4视频微端口驱动程序初始化 162 6.4.1启动视频微端口驱动程序 163 6.4.1.1建立视频适配器存取区间 163 6.4.1.2在注册表中设置硬件信息 164 6.4.1.3改变适配器的状态 165 6.4.2声明继承资源 165 6.4.3初始化视频微端口与显示驱动程序的通讯 167 6.5视频微端口设备扩展 167 6.6视频微端口驱动的注册回调信号 168 6.7处理视频请求 168 6.7.1系统定义的IOCTL_VIDEO_XXX请求 169 6.7.2自定义的显示微端口IOCTL_VIDEO_XXX请求 170 6.7.3处理不支持的IOCTL_VIDEO_XXX请求 170 6.8在视频微端口中支持PnP及电源管理 170 6.9在视频微端口驱动程序中使用AGP 171 6.9.1通过AGP存取系统内存 171 6.9.2从AGP使用中释放内存 171 6.10图形适配器的子设备 171 6.10.1侦测子设备 172 6.10.2与子设备驱动程序通信 172 6.11视频微端口中的中断 172 6.11.1何时实现HwVidSynchronizeExecutionCallbak例程 173 6.12视频微端口的定时器 174 6.13在视频微端口中对适配器重置 174 6.14微端口驱动中的镜像驱动程序支持 174 6.15视频微端口的TV连接器及拷贝保护支持 175 6.15.1查询TV连接器及硬件的拷贝保护 175 6.15.2设置TV连接器及拷贝保护硬件 176 6.15.2.1设置拷贝保护硬件(Copy Protection Hardware) 176 6.15.2.2多会话拷贝保护 177 6.16VGA兼容的微端口SvgaHwIoPortXxx 177 6.16.1基于x86机器的窗口式VDM 177 6.16.2基于x86机器的全屏VDM 177 6.16.3VGA兼容的微端口的HwFindAdapter 178 6.16.4在SvgaHwIoPortXxx验证指令 179 6.16.5VGA兼容的HwVidStartIO 179 6.17将Windows NT 4.0的微端口驱动程序转换到Windows 2000 180 第三部分 打印驱动程序和假脱机组件 182 第1章 打印介绍 183 第2章 打印假脱机体系结构 183 2.1打印机组件 184 2.1.1假脱机组件介绍 184 2.1.2打印提供者 184 2.1.2.1打印提供者介绍 185 2.1.2.2打印提供者举例 186 2.1.2.3打印提供者能力 186 2.1.2.4打印提供者定义的函数 186 2.1.2.5本地打印提供者 190 第3章 打印机驱动程序体系结构 190 3.1打印机驱动程序组件 191 3.1.1打印机图形DLL 191 3.1.1.1打印机图形DLL介绍 191 3.1.1.2由打印机图形DLL定义的函数 192 3.1.1.3打印机图形DLL实例 193 3.1.1.4绘制一个打印作业 194 3.1.1.5支持设备字体 195 3.1.1.6返回专用的打印机信息 195 3.1.1.7选择用户模式或内核模式 196 3.1.1.8构建一个打印机图形DLL 197 3.1.2打印机接口DLL 198 3.1.2.1打印机接口DLL介绍 198 3.1.2.2由打印机接口DLL定义的函数 198 3.1.2.3打印机接口DLL实例 199 3.1.2.4为打印机创建原型表单页面 199 3.1.3打印机数据文件 199 第4章 微软统一打印机驱动程序 199 4.1统一打印机驱动程序介绍 200 4.1.1Unidrv能力 200 4.1.2Unidrv组件 201 4.1.3Unidrv小驱动程序 202 4.1.4Unidrv用户接口 202 4.1.5Unidrv绘制器 203 4.1.6GPD文件实例 203 4.1.7微软小驱动程序开发工具 203 4.2GPD文件介绍 204 4.2.1GPD文件条目 204 4.2.1.1GPD文件条目格式 204 4.2.1.2GPD值类型 205 4.2.1.3行连续 207 4.2.1.4注解及忽略块 208 4.2.1.5宏 209 4.2.1.6预处理器定向 211 4.2.1.7标准变量 213 4.2.2基本单位 215 4.2.3在小驱动程序中使用多个GPD文件 216 4.2.4在小驱动程序中使用资源DLL 216 4.3打印机属性 217 4.3.1打印机属性格式 217 4.3.2属性类型 218 4.3.3一般属性 218 4.3.3.1根层次属性 218 4.3.3.2一般打印机属性 220 4.3.3.3文本打印属性 223 4.3.3.4光栅打印属性 225 4.3.3.5向量打印属性 226 4.4打印机命令 227 4.4.1命令条目格式 227 4.4.2命令名称 227 4.4.2.1一般打印命令 228 4.4.2.2打印机配置命令 230 4.4.2.3选项选择命令 230 4.4.2.4文本打印命令 231 4.4.2.5光栅打印命令 232 4.4.2.6向量打印命令 234 4.4.3命令属性 235 4.4.4命令字符串格式 235 4.4.4.1命令字符串参数类型 236 4.4.4.2标准变量表达式 237 4.4.5命令执行顺序 237 4.5打印机特性 239 4.5.1标准特性 240 4.5.2定制特性 241 4.5.3特性条目格式 242 4.5.4特性属性(Feature Attributes) 243 4.5.5特性冲突优先级 244 4.6打印机选项 244 4.6.1标准选项 245 4.6.2定制选项 248 4.6.3选项条目格式 248 4.6.4选项属性 249 4.6.4.1所有特性的选项属性 249 4.6.4.2色彩模式特性的选项属性 251 4.6.4.3过渡调色特性的选项属性 252 4.6.4.4二进制输入特性的选项属性 252 4.6.4.5内存特性的选项属性 252 4.6.4.6二进制输出特性的选项属性 253 4.6.4.7纸张尺寸特性的选项属性 253 4.6.4.8精度特性的选项属性 255 4.6.5选项限制 256 4.6.5.1选择限制 257 4.6.5.2安装限制 258 4.6.5.3选择及安装之间的限制 258 4.7打印机字体描述 259 4.7.1硬件驻留字体 259 4.7.2Cartridge字体 259 4.7.2.1Cartridge字体属性 260 4.7.3字体替换 260 4.8条件语句 261 4.9压缩光栅数据 264 4.9.1使用Unidrv支持的压缩 265 4.9.2使用定制的压缩 265 4.10过滤光栅数据 266 4.11处理色彩格式 266 4.12用Unidrv进行中间色调整 267 4.12.1GDI提供的过渡调色 267 4.12.1.1小驱动程序提供的过渡调色 268 4.12.2驱动程序提供的过渡调色 268 4.12.3设备提供的过渡调色 270 4.13控制图像质量 270 4.14处理可安装的特性及选项 273 4.15指定特性和选项显示的顺序 275 4.16描述打印机内存配置 275 4.17指定纸张大小 277 4.17.1支持标准纸张尺寸 277 4.17.2支持厂商定义的纸张尺寸 278 4.17.3支持用户定义的纸张尺寸 278 4.18引用场所(Referencing Locales) 283 4.19安装一个Unidrv小驱动程序 284 第5章 微软PostScirpt打印机驱动程序 285 5.1 Pscript能力 285 5.2 Pscript组件 285 5.3 Pscript小驱动程序 286 5.3.1转换AFM文件到NTF文件 287 5.3.2安装Pscript小驱动程序 288 5.4 Pscript用户接口 288 5.5 Pscript绘制器 289 第6章 微软绘图仪驱动程序 289 6.1绘图仪驱动程序能力 289 6.2绘图仪驱动程序组件 290 6.3绘图仪驱动程序文件示例 290 6.4绘图仪驱动程序的小驱动程序 291 6.5绘图仪驱动程序用户接口 292 6.6绘图仪驱动程序绘制器 292 第7章 定制微软的打印机驱动程序 293 7.1用户接口插件 293 7.1.1用户接口插件介绍 293 7.1.2 UI插件的例子 294 7.1.3 UI插件的COM接口 294 7.1.3.1 IPrintOemUI COM 接口 294 7.1.3.2 IPrintOemDriverUI COM接口 295 7.1.4提供DEVMODE结构附加项 295 7.1.5从UI插件存取驱动程序设置 296 7.1.6修改一个驱动程序提供的属性表单页面 296 7.1.7增加新的属性表单页面 297 7.1.8定制其他的打印机接口操作 297 7.2绘制插件 297 7.2.1绘制插件介绍 298 7.2.2绘制插件实例 298 7.2.3绘制插件的COM接口 298 7.2.3.1 IPrintOemUni COM接口 298 7.2.3.2 IPrintOemDriver COM接口 299 7.2.3.3 IPrintOemPS COM接口 300 7.2.3.4 IPrintOemDriverPS COM接口 300 7.2.4定制的DDI函数 301 7.2.5定制的PDEV结构 301 7.2.6从绘制插件存取驱动程序设置 301 7.2.7特定Pscript的定制绘制 302 7.2.8特定Unidrv的定制绘制 302 7.2.8.1定制的色彩格式 302 7.2.8.2定制的过渡调色 303 7.2.8.3定制的数据流压缩 304 7.2.8.4定制的数据流过滤 304 7.2.8.5定制的字体管理 304 7.2.8.6动态产生的打印机命令 306 7.2.8.7处理设备管理的表面 307 7.3实现打印机驱动程序的COM接口 308 7.3.1 打印机驱动程序的接口标识符 308 7.3.2创建插件 309 7.3.3从打印机驱动程序存取插件接口 310 7.3.4从插件存取打印机驱动程序接口 310 7.4安装定制的驱动程序组件 310 7.4.1Unidrv的定制的字体安装程序 311 第8章 定制打印假脱机组件 312 8.1编写打印处理器 312 8.1.1对打印处理器的介绍 312 8.1.2打印处理器示例 314 8.1.3由打印处理器定义的函数 314 8.1.4处理一个打印作业 314 8.1.4.1为打印处理器使用GDI函数 315 8.1.5安装一个打印处理器 316 8.2编写打印监示器 316 8.2.1语言监示器 316 8.2.1.1语言监示器示例 317 8.2.2端口监示器 317 8.2.2.1端口监示器实例 318 8.2.3语言和端口监示器交互 318 8.2.3.1组合式语言和端口监示器 318 8.2.4由打印监示器定义的函数 318 8.2.4.1语言监示器函数 319 8.2.4.2端口监示器客户DLL函数 319 8.2.4.3端口监示器服务器DLL函数 319 8.2.5初始化打印监示器 320 8.2.6打开及关闭一个端口 320 8.2.7打印一个打印作业 321 8.2.8管理端口 321 8.2.8.1增加端口 321 8.2.8.2配置端口 322 8.2.8.3删除端口 322 8.2.8.4设定端口超时值 323 8.2.8.5存储端口配置信息 323 8.2.9为在集群打印服务器中使用转换打印监示器 323 8.2.10安装打印监示器 324 8.3编写网络打印提供者 324 8.3.1部分打印提供者概述 325 8.3.2支持打印机变化的通知 325 8.3.3安装一个网络打印提供者 326 第9章 Internet打印 327 9.1从应用程序打印到URL 327 9.2查看打印web页面 328 9.3定制打印web页面 329 9.3.1打印Web页面的ASP文件 329 9.3.1.1ASP文件实例 329 9.3.2定制打印机细节的Web页面 330 9.3.2.1替换默认的打印机细节页面 330 9.3.2.2哪一个打印机细节页面被显示 331 9.3.2.3打印机Web页面的ASP变量 331 9.3.2.4打印机Web页面的ActiveX对象 332 9.3.2.5修改web页面信息 332 9.3.3安装定制的打印Web页面 333 9.3.3.1为一个打印机类型安装页面 333 9.3.3.2为一个生产厂家安装页面 334 9.3.3.3为一个端口监示器安装页面 334 9.4从web页面安装打印驱动程序 334 第10章 安装及配置打印机驱动程序 335 10.1打印机的即插即用 335 10.2打印机的INF文件 335 10.2.1打印机DIRID 336 10.2.2打印机INF文件条目 336 10.2.2.1定制的打印机安装操作 338 10.2.3打印机INF文件数据部分 338 10.2.4打印机INF文件安装部分 339 10.2.5打印机INF文件拷贝文件部分 340 10.2.6打印机INF文件实例 340 10.3打印机的目录服务 341 10.3.1打印假脱机对打印机目录服务的支持 341 10.3.2打印机驱动程序对打印目录服务的支持 342 10.4支持点号及打印(Point and Print) 342 10.4.1点号及打印介绍 343 10.4.2在打印机安装过程中支持点号及打印 343 10.4.2.1安装驱动程序专用的文件 343 10.4.2.2安装队列专用的文件 343 10.4.2.3点号及打印DLL 344 10.4.3在打印机连接过程中支持点号及打印 345 10.4.3.1下载驱动程序专用的文件 345 10.4.3.2下载队列专用的文件 346 第11章 CPSUI 347 11.1 CPSUI介绍 348 11.2 CPSUI应用程序实例 348 11.3 CPSUI提供的函数 348 11.4应用程序提供的回调函数 349 11.4.1页面创建回调函数 349 11.4.2页面事件回调函数 349 11.4.2.1 CPSUI消息句柄 350 11.5创建属性表单页面 350 11.5.1属性表单选项 351 11.5.2 CPSUI支持的窗口控件 351 11.5.2.1定制CPSUI支持的窗口控件 351 11.5.3 CPSUI提供的页面和模板 352 11.5.4指定页面的方法 352 11.5.4.1父组 353 11.5.5打印机驱动程序中使用CPSUI 353 第12章 打印机色彩管理 354 12.1允许色彩管理 355 12.2控制色彩管理 355 12.2.1系统控制 355 12.2.2驱动程序控制及设备控制 356 12.2.3支持CMYK色彩空间 356 12.2.4对JPEG及PNG图像的色彩管理 357 12.3放置ICC的Profile文件 357 12.4安装ICC的Profiles文件 357