1.4、Windows本地打印任务处理流程
1.4.1、本地打印机的架构
对Windows的本地打印任务处理的流程的说明,我想从打印机接入计算机或者操作系统说起。在这里我们仅仅针对本地计算机,因此,打印机和计算机之间,通过本地连接的话,他们之间就必须要有某一个介质和通信的协议来进行相互之间的数据传输。其中,介质归纳来说,合计有三种:USB、LPT(并口)、COM(串口)【当然了现在还有无线、蓝牙这样的介质打印机】。而在这里我所指的本地打印机,就是通过(计算机本地的LPT(并口)、COM(串口)或者USB口的连接)的打印机。本地打印机和计算机之间的通信协议就是LPT(并口)、COM(串口)或USB口决定的:是一种协议总线,即主机与设备之间的通信需要遵循一系列约定。比如USB协议、LPT(并口)以及COM(串口)协议,他们之间的控制语言就是ESC或ESC/P2。当然,这也并非放之四海而皆准,比如有一些打印机厂商,他在设计这个打印机的处理模块的时候,不同的打印机支持的协议都不完全相同。
USB生活中比较常见,大家对它都比较熟悉,这里我想简要提几句LPT(并口)和COM(串口)。LPT一般来说是提供给打印机专用的端口和协议,LPT并口是一种增强了的双向并行传输接口,在USB接口出现以前是扫描仪,打印机最常用的接口。一般有25针/孔和36PIN两种型号。并行接口是指数据的个位同时进行传送,其特点是传输速度快,但当传输距离较远、位数又多时,就导致通信线路复杂且成本提高。至于COM呢?COM接口也就是串口接口,一般是9针或者是9孔的接口,也称串行通信接口,是采用串行通信方式的扩展接口。串行通讯的特点是:数据位的传送,按位顺序进行,最少只需一根传输线即可完成;成本低但传送速度慢。串行通讯的距离可以从几米到几千米。老式的那种针式打印机或者现在部分的激光打印机还在使用COM口,部分激光打印机使用USB接口。COM最开始的作用仅仅是连接老式的鼠标,还有就是调试设备时使用,这个最广泛的例子就是交换机的调试端口Com口。现在来说:打印机采用USB接口的最多,因为USB接口的传输速度比LPT和COM快很多!
因此,本地打印系统中通常包含一台计算机(请求打印设备,当然不仅仅只有计算机)和一台打印机,此二者通过通信线缆(USB、LPT、COM)连接起来,计算机通过通信线缆向打印机发送打印数据,打印机完成打印数据的打印。目前,打印系统一般通过两种方式完成打印数据的打印,一种是在计算机上安装专用应用程序,比如针对某一行业或某一客户需求开发的应用程序,由专用应用程序直接向打印机发送可以被打印机识别的打印命令和打印数据,打印机接收到打印命令和打印数据后执行打印数据的打印;另一种是在打印请求设备上安装通用应用程序(比如Office办公软件),同时,在计算机上安装由打印机制造商提供的打印机驱动程序,应用程序生成图形数据后调用打印机驱动程序,使打印机驱动程序生成包含可被打印机识别的打印命令和打印数据的作业数据,计算机将作业数据发送到打印机,打印机接收到作业数据后执行打印数据打印。
下图显示了本地打印任务的流程图:
如图所示,应用程序通过GDI接口创建打印任务后,不管是否需要输出为EMF,本地的PrintProvider任务创建API都会创建一个Spool文件。然后,当任务被调度的时候,通过读取这个Spool文件,如果是EMF格式的话,就让EMF打印处理器配合打印机的渲染驱动,将打印任务发送回去给GDI转换成RAW格式,最后和没有使用EMF格式的任务一样,将数据流传递到端口监视器中执行最终打印。
下面我们来细细解析这一过程!
1.4.2、本地打印处理流程解析
流程1:应用程序枚举打印机
首先第一步,用户在编辑Word完成之后,想将这个Word打印出来,于是用户就点击了Word的功能按钮“打印”!当用户点击了这个按钮之后,Word应用程序通过打印API函数或打印相关的通用对话框枚举Windows打印池客户DLL,找到当前打印机的信息。
Windows打印池客户DLL首先接受到这个“打印”的请求之后,第一步就是初始化打印机下拉列表选项
Windows打印池客户DLL如何枚举打印机的信息呢?
对于Windows操作系统,操作系统的打印管理系统提供语言监视器(Language Monitor) 组件和端口监视器(Port Monitor) 组件,由语言监视器组件和端口监视器组件完成作业数据的发送和打印机的状态的获取。这两个组件我在打印假脱机程序(Print Spooler)已经提到过。语言监视器(Language Monitor) 组件和端口监视器(Port Monitor) 组件是由打印假脱机程序(Print Spooler)控制或调用的。因此,整个枚举(通过EnumeratePrinters函数)的过程是这样的:
1、枚举本地打印机。
Windows打印池客户DLL通过RPC将枚举打印机的请求提交给打印假脱机程序(Print Spooler),打印假脱机程序(Print Spooler)接受到该枚举打印机的请求之后,该程序首先会去枚举自己的注册表或者数据库中的打印机信息和打印作业状态。默认情况下,当我们在将打印机接入计算机时,都会在计算机上安装相应的打印机驱动程序并识别打印机。这个过程就是把打印机的信息预先填充到了打印系统的数据库中。识别打印机的这一过程和计算机识别一个U盘是一样的流程,在这里我就不做说明。
Windows打印池API提供了专门的枚举打印机的函数,打印假脱机程序(PrintSpooler)可以用它枚举打印机及查询打印机信息。这里面枚举的过程很复杂,有很多选项,返回很多不同类型的结构,可用于枚举本地打印机、打印提供者、域名及计算机域名内的所有打印机和打印服务器,然后将这些枚举出来的信息填充到打印假脱机程序(Print Spooler)的打印机信息结构数组。这个结构数组包含了服务器名、打印机名、共享名、驱动程序名、DEVMODE、单个文件、打印处理器、打印池数据类型、安全描述符等。这样的一个结构数组就提供了打印机的完整信息。
2、枚举打印机状态信息。
打印假脱机程序(Print Spooler)在获取到打印机信息结构数组的完整结构之后,根据结构数组里面的打印机列表,再分别枚举这些打印机的连接情况。怎么去枚举物理打印机的连接信息或者说状态信息呢?这也是一个比较难说明的过程。首先,我需要告诉大家的是,确定一台物理打印机的状态的前提条件是:必须尝试通过打印后台处理程序将打印作业发送到物理打印机才能够最终确定物理打印机的状态。但是在这个时候,我们明显的是没有打印任务给到打印机的(假设就一台打印机而且计算机是单用户操作系统,Word打印任务还没发布出来呢)。
Windows为这种情况下提供了两种方法来获取物理打印机的状态信息:
第一种方法,读取用于特定的打印作业的JOB_INFO结构的状态信息从而判断物理打印机的状态。JOB_INFO结构包含位于操作系统上的逻辑端口的状态信息和物理端口的状态信息。物理端口在计算机中又将其分为第一设备通道和第二设备通道,逻辑端口则具有第一逻辑通道和第二逻辑通道,计算机上的打印系统和端口监视器用于通过第一设备通道和第一逻辑通道向打印机发送打印数据,端口监视器用第二设备通道和第二逻辑通道监控打印机的状态,因此,通过这两个成员端口监视器就可以报告打印作业的状态信息。这两个成员在默认情况下包含预设的值,这些值是由Win32 SDK和WinSpool.h头文件记录。JOB_INFO结构有两个API函数︰GetJob和EnumJobs。这个状态信息里面记录的其实就是GetJob和EnumJobs的信息,也就是打印机的打印作业的记录和打印作业的状态信息,这些打印作业的状态信息会存入到JOB_INFO结构中。通过读取JOB_INFO结构的状态信息,如果不存在打印作业且打印作业队列为空,那么我们就认为打印机已就绪并处于闲置状态。
第二种方法,通过检查PRINTER_INFO结构的状态。PRINTER_INFO的结构是由GetPrinter函数返回的。GetPrinter函数的作作用是取得与指定打印机有关的信息。相应地,这个函数是在进行打印作业的时候从打印机获取到的打印机信息。当没有打印作业执行的时候,该函数返回的是默认的设定好的值。通过读取这个PRINTER_INFO结构的状态也可以大致得到物理打印机的链接状态信息。但是,这两组结构的状态可能与真实的物理打印机的状态并不严格的契合。如果这个时候,物理打印机处于错误状态如离线。操作系统也会认为打印机可以接受打印作业。这种情况下,端口监视器会讲端口断线的消息给到后台的打印系统,并且这个错误信息只会到用户的操作系统中,并在操作系统中显示错误状态而不会通知用户。因此,用户在这种情况下选择打印,枚举出来打印机之后,在发生打印数据的过程中就会出现错误。
3、在列表框中列出打印机。
完成上述两个步骤之后,打印假脱机系统就会将枚举出来的打印机的信息以及状态发送给到Windows打印池客户DLL。Windows打印池客户DLL第一步将这些打印机的名字加到列表框中,用户可用这个列表框来选择打印机。然后通过函数判断是否有选中值。
Windows API函数:【http://baike.baidu.com/link?url=JDDbqL_kzikpGQCJX8JqdT-gpDBLRbvu_94Ohv40d7eLvzvbyMqeAsfuE4KnUdTMl7pY7DW6Q_KAPWLoxy4wwa】