网语飘飘.Net/Delphi攻坚战

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::
本章解释怎样定制打印机假脱机组件,并提供了以下这些主题:

■编写打印处理器

■编写打印监示器

■编写网络打印提供者



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函数应当执行下面的操作:

1.调用打印假脱机的OpenPrinter函数(在平台的SDK文档中有描述),它引起端口监示器服务器DLL中的XcvOpenPort被调用。

2.多次调用打印假脱机的xcvData函数,以请求端口监示器服务器DLL增加端口并在UI DLL和服务器DLL之间传递配置信息。XcvData函数调用服务器DLL的XcvDataPort函数。AddPortUI函数通常获得从用户显示对话框栏显示配置信息。

3.调用打印假脱机的ClosePrinter函数(在平台的SDK文档中描述),它引起端口监示器服务器DLL中的XcvClosePort函数被调用。

关于这些操作的更多信息,参考AddPortUI函数的描述,也可以参考存储端口配置部分的内容。

端口监示器的EnumPorts函数必须枚举所有被增加的端口。假脱机可以调用每一个端口监示器的EnumPorts函数以确定在一个打印机服务器上可支持的端口集合。

8.2.8.2配置端口

配置一个端口是指修改一个端口监示器服务器的DLL的以前增加的端口的存储的配置信息。

当一个应用程序调用打印假脱机的ConfigPort函数(在平台的SDK文档中有描述)。ConfigurePort函数调用包含在适当的端口监示器UI DLL中的ConfiguePortUI函数。

端口监示器的UI DLL的ConfigPortUI函数应当执行下面的操作:

1.调用打印假脱机的OpenPrinter函数(在平台的SDK文档中有描述),它引起在端口监示器服务器DLL中的XcvOpenPort函数被调用。

2.一次或多次调用打印假脱机的XcvData函数,以在UI DLL及服务器DLL之间传输配置信息。XcvData函数调用服务器DLL的XcvDataPort函数,ConfigurePortUI函数通常通过从用户显示的对话框获得配置信息。

3.调用打印假脱机的ClosePrinter函数(在平台的SDK文档中有描述),它导致端口监示器服务器DLL中的XcvClosePort函数被调用。

关于这些操作的更多信息,可以参考ConifigurePortUI的描述。也可以参考存储端口配置信息部分的内容。



8.2.8.3删除端口

删除端口由删除端口的名称及删除用户可修改的端口监示器服务器DLL本地存储或注册表中的配置信息构成。

当一个应用程序调用打印假脱机的DeletePort函数(在平台的SDK文档中描述),DeletePort函数调用包含在适当的端口监示器UI DLL中的DeletePortUI函数。

端口监示器的UI DLL的DeletePortUI函数应当执行下面的操作:

1.调用打印假脱机的OpenPrinter函数(在平台的SDK文档中描述),它导致端口监示器服务器DLL中的XcvOpenPort函数被调用。

2.一次或多次调用打印假脱机的XcvData函数,以请求端口监示器服务器DLL删除端口。XcvData函数调用服务器DLL的XcvDataPort函数。

3.调用打印假脱机的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函数

假脱机调用这一函数以给打印提供者下面这些信息:

1.一套标识应用程序请求通知的打印机事件类型的标记。

2.一个消息请求的打印队列的句柄。

3.一个应用程序请求的当事件发生时被提供的信息类型的列表。

函数必须返回指明是否提供者应当被轮询,以决定已经有变化发生的一个标记值。(非轮询的提供者在发生变化时发送信号到客户端。一个必须被轮询的提供者在发生变化时不发送信号到客户端。相反,假脱机则以规则的间隔时间向客户端发送信号,无论是否有变化发生。)

(但要注意,在提供者层,这一函数与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函数必须能识别打印队列的名称。
posted on 2006-05-19 09:34  网语飘飘  阅读(3634)  评论(0)    收藏  举报