Windows打印管理解决方案
需求
从需求出发,我们的目的是在电脑上提供一个虚拟打印机,然后让用户选择这个虚拟机打印时产生的中间文件被拦截下来,之后进行进一步处理后在执行真实的打印。
Windows打印体系
首先附上查找Windows打印相关内容的链接,这个分类下包含了Windows打印的方方面面
https://msdn.microsoft.com/en-us/library/windows/hardware/ff561035(v=vs.85).aspx
Windows2000以后的打印体系结构都是由一个打印机假脱机程序(Spooler)和一系列的打印驱动组成。应用程序通过调用设备无关的函数,就能创建打印任务,并发送到打印设备中。包括激光打印机、矢量绘图机、光栅打印机和传真机。
其中打印驱动包括一个渲染组件和一个配置组件。
渲染组件负责将应用程序传来的每一页的绘制命令(GDI命令)转换成打印机用来渲染的命令数据(打印机才能识别的命令)发送到打印机中。
配置组件又包含一个可以让用户进行打选项配置的用户接口组件和一个将打印机的配置和特征传递给应用程序的程序接口。
当GDI程序执行打印时,通过调用API来传递GDI绘图指令到绘图引擎,绘图引擎要么和打印驱动一起合作来缓存这些绘制指定到一个EMF文件中,要么直接渲染成一个可打印的图片发送到spooler中。Spooler解释EMF文件,并将页面布局和作业控制指令信息插入到数据流中,然后发送这些数据里到序列化、并行化或者网络形式的打印机关联的端口上。(XPS设备会有一点不同,这里不进行介绍)。
由于Spooler和打印驱动都是可以被单独取代,所以硬件厂商们可以很容易的增加对新硬件的支持。当需要增加对新款打印机的支持时,通常只需要创建根据微软所提供的打印驱动类型中相关联的数据类型就可以了。
下图是Windows提供的内置打印驱动程序:
大致了解了Windows打印体系组成之后,来分别看一下Spooler和打印驱动。
Spooler
从Windows2000开始,打印假脱机程序由一系列的微软提供的和可选的渲染组件组成,他们的作用包括:
1、检测是否打印任务是在本地处理还是跨网络处理。
2、接受GDI和打印驱动为特定类型的打印机所提供的数据流。
3、缓冲绘制数据到文件中。
4、从逻辑打印队列中选出第一个有效的物理打印机。
5、将缓冲的数据流(如EMF)转换成能呗打印机硬件所识别的格式(如PCL)。
6、发送打印数据流到打印机硬件中。
7、为假脱机组件和打印机的相关信息维护一个基于注册表的数据库
Spooler主要组成结构如下图所示:
Application通过调用GDI函数来创建打印任务,通过调用Winspool.drv提供的API接口,将打印内容路由到PrintProvider中。
PrintProvider负责管理本地打印和远程打印,同时要管理打印任务堆里的启动、停止和枚举打印队列。
我们这里只讨论本地打印流程,它提供了下面的能力:
1、打印任务缓冲和解析到打印队列
2、为Win2000以后的操作系统的打印驱动体系提供支持。
3、为厂商提供的打印处理器的提供支持
4、为场上提供的打印监视器的提供支持
下图提供了本地打印任务处理流程:
如图所示,应用程序通过GDI接口创建打印任务后,不管是否需要输出为EMF,本地的PrintProvider任务创建API都会创建一个spool文件。然后,当任务被调度的时候,通过读取这个spool文件,如果是EMF格式的话,就让EMF打印处理器配合打印机的渲染驱动,将打印任务发送回去给GDI转换成RAW格式,最后和没有使用EMF格式的任务一样,将数据流传递到端口监视器中执行最终打印。
我们通过定制自己的打印机,让整个打印流程走如上图中红线描述的路径,在打印处理器这一层拦截spool文件及其相关打印信息,来保留整个打印任务的相关数据,等待后续进行处理。
Printer Driver
Windows提供了三种类型的打印驱动,分别为:Universal Printer Driver、PostScript Printer Driver、Plotter Driver。原则上来说这三种类型的驱动已经能支持大多数打印机了,我们只需要简单的为新的打印机提供对应驱动的DataFile即可。我们这里只讨论Universal Printer Driver。
Universal Printer Driver由三部分组成:
1、Printer Graphics DLL: 负责和GDI一起渲染打印任务,并发送渲染数据流到打印假脱机程序中。
2、Printer Interface DLL: 提供打印机参数配置接口和假脱机能调用的用于通知打印系统事件的接口。
3、Printer Data Files:对于Universal Printer Driver而言,这个数据文件就是GPD文件,它用于创建UnidrvMiniDrivers,主要用于描述打印机的可选项配置。包括打印机属性、相关命令、特征、可选项、字体描述、环境状态等。
上面三个模块对应的就是下图中红色矩形框住的部分。
由于我们不需要对打印渲染和用户接口做过多的定制,只需要使用标准的Windows打印首选项对话框,所以无需自定义渲染插件和用户接口插件。当然如果需要的话,也是能在https://msdn.microsoft.com/zh-cn/library/windows/hardware/ff547298(v=vs.85).aspx中找到这些插件重定义的方式。
根据需求,我们只需要自己定制GPD文件,来实现对系统标准的打印机首选项对话框相关设置的定制。(这种方式应该就是UnidrvMiniDrivers)。
解决方案
综上所述,我们的虚拟打印机要自己定制的模块就只有打印处理器和GPD文件。
打印处理器负责拦截打印生成的中间文件,GPD文件负责定制打印可选项。
打印处理器定制文档:https://msdn.microsoft.com/zh-cn/library/windows/hardware/ff563807(v=vs.85).aspx
GPD文件说明文档:https://msdn.microsoft.com/zh-cn/library/windows/hardware/ff551750(v=vs.85).aspx
打印处理器
WDK提供了支持相关示例,我们在这个示例的基础上直接更改,主要要更改的地方是在winprint.cpp中,PrintDocumentOnPrintProcessor函数的实现中。我们可以看到打印处理器对一个打印任务的处理流程如下:
由于我们这里的目的是拦截打印任务产生的中间文件保存下来,所以只需要调用GDI Functions in Print Processors,缓存好spool文件后直接返回(阻止传递数据流到spooler中去)。
GPD文件
前面介绍了,GPD文件是使用GPD语言去描述一台打印机,也就是说GPD文件有自己固定的格式,对应GPD语言也有固定的语法,其中主要包括下列信息:
1、Printer attributes: 描述打印机特征
2、Printer commands: 用于控制打印机的操作
3、Printer features: 描述能被通用打印驱动所控制的能力
4、Printer options: 呈现能用来设置Printer features值的状态。
5、Printer font descriptions :描述和硬件相关连的字体
6、Conditional statements: 描述Printer attributes 和 打印机的配置之间的依赖关系。
此外,GPD语言也定义了一些用来控制一些操作的GPD文件设置,这种操作我们暂不需要。
根据上面的描述,要完全自定义一个GPD文件相对来说是比较困难的,这里我们可以通过参考其他打印机中有使用GPD文件的部分,或者参考WDK目录下\src\print\mini中的GPD文件示例。然后根据需要去修改相应的部分更加简单。
我这里只对自己所理解的部分进行阐述,更详细的信息请参考MSDN。
首先GPD文件中最基本的值设置格式Entries,其格式为:
其中的EntryName都是GPD解析器预定义好的关键字(不然GPD解析器无法识别你写这么个关键字是要做什么),而EntryValue则只能是GPD所支持的值类型中的一种(不然GPD解析器无法判断你这个关键字对应的内容正不正确)。关于类型,请自行参考https://msdn.microsoft.com/zh-cn/library/windows/hardware/ff550568(v=vs.85).aspx
下面拿我们的GPD文件作为参考进行描述。
如图所示,对于这种*AttributeName:AttributeValue格式的文本,都是前面描述的Printer Attribute,其中放在文件头部,又更细分为Root-Level-Only Attributes,其各部分含义可参见https://msdn.microsoft.com/zh-cn/library/windows/hardware/ff561989(v=vs.85).aspx
如图所示,上面*%的部分都是注释,后面的Attribute,则是细分在Printer Capability Attributes下的一些属性,详细描述参见https://msdn.microsoft.com/zh-cn/library/windows/hardware/ff560780(v=vs.85).aspx
如图所示,形如*Command:CommandName{CommandAttributes}的内容,则属于Printer Commands。前面描述过这类型是用于控制打印机的操作,其中CommandName都是预定义的命令名,具体这些命令名称的含义,参见https://msdn.microsoft.com/zh-cn/library/windows/hardware/ff546117(v=vs.85).aspx中各类别的描述。
如图所示,形如*Feature:FeatureName{FeatureAttributes}的内容,描述的便是我们打印机所提供的打印选项功能。Feature又分为标准类型和自定义类型,当然我们只需要提供标准的特征,所支持的标准特种参见https://msdn.microsoft.com/zh-cn/library/windows/hardware/ff562697(v=vs.85).aspx,如截图中所示即为标准特征中允许选择纸张方向的特征,这里不仅仅描述了这个特征的说明,而且描述了这个特征是不是必须的和支不支持自定义选项。
需要说明的是*rcNameID这个属性段,表述的是对应特征(或者可选项)显示在界面上的内容的ID,MSDN上也描述了可以使用*Name来直接指定内容。但是我们不需要自己指定。Unidrv驱动提供的stdname.gpd中包含了标准特征显示的文本的ID,我们只需要引用其中的就可以的。
另外补充说明一下上面Feature中的Option,前面描述了Option对应的其实是Feature可以设置的选项的描述,对应的每一个选项也有固定的格式,可以参见https://msdn.microsoft.com/zh-cn/library/windows/hardware/ff559622(v=vs.85).aspx
对应上面Feature描述的界面效果如下:
类似的对应的*Feature:PaperSize产生的界面效果如下:
至此,关于GPD文件的描述就结束了。具体需要添加某项特征选项时,可在MSDN上找到对应选项的关键字段,然后根据描述设置相应的属性即可。
参考资料
打印处理器缓冲spool文件相关资料http://www.undocprint.org/winspool/spool_files