教你怎么使⽤打印机(api)
总的说来,效果也很好。在Windows程序中,⽤于视讯显⽰器的GDI函数⼀样可以在印表纸上打印⽂字和图形,在以前讨论的与设备⽆关的许多问题(多数都与平⾯显⽰的尺⼨、分辨率以及颜⾊数有关)都可以⽤相同的⽅法解决。当然,⼀台打印机不像使⽤阴极射线管的显⽰器那么简单,它们使⽤的是印表纸。它们之间有⼀些⽐较⼤的差异。例如,我们从来不必考虑视讯显⽰器没有与显⽰卡连结好,或者显⽰器出现「屏幕空间不够」的错误,但打印机off line和缺纸却是经常会遇到的问题。
我们也不必担⼼显⽰卡不能执⾏某些图形操作,更不⽤担⼼显⽰卡能否处理图形,因为,如果它不能处理图形,就根本不能使⽤Windows。但有些打印机不能打印图形(尽管它们能在Windows环境中使⽤)。绘图机尽管可以打印向量图形,却存在位图块的传输问题。
以下是其它⼀些需要考虑的问题:
打印机⽐视讯显⽰器慢。尽管我们没有机会将程序性能调整到最佳状态,却不必担⼼视讯显⽰器更新所需的时间。然⽽,没有⼈想在做其它⼯作前⼀直等待打印机完成打印任务。
程序可以⽤新的输出覆盖原有的显⽰输出,以重新使⽤视讯显⽰器表⾯。这对打印机是不可能的,打印机只能⽤完⼀整页纸,然后在新⼀页的纸上打印新的内容。
在视讯显⽰器上,不同的应⽤程序都被窗⼝化。⽽对于打印机,不同应⽤程序的输出必须分成不同的⽂件或打印作业。
为了在GDI的其余部分中加⼊打印机⽀持功能,Windows提供⼏个只⽤于打印机的函数。这些限⽤在打印机上的函数(StartDoc、EndDoc、StartPage和EndPage)负责将打印机的输出组织打印到纸页上。⽽⼀个程序呼叫普通的GDI函数在⼀张纸上显⽰⽂字和图形,和在屏幕上显⽰的⽅式⼀样。
在、和有打印位图、格式化的⽂字以及metafile的其它信息。
打印⼊门
当您在Windows下使⽤打印机时,实际上启动了⼀个包含GDI32动态链接库模块、打印驱动程序动态连结模块(带.DRV扩展名)、Windows后台打印程序,以及有⽤到的其它相关模块。在写打印机打印程序之前,让我们先看⼀看这个程序是如何进⾏的。
打印和背景处理
当应⽤程序要使⽤打印机时,它⾸先使⽤CreateDC或PrintDlg来取得指向打印机设备内容的句柄,于是使得打印机设备驱动程序动态链接库模块被加载到内存(如果还没有加载内存的话)并⾃⼰进⾏初始化。然后,程序呼叫StartDoc函数,通知说⼀个新⽂件开始了。StartDoc函数是由GDI模块来处理的,GDI模块呼叫打印机设备驱动程序中的Control函数告诉设备驱动程序准备进⾏打印。
打印⼀个⽂件的程序以StartDoc呼叫开始,以EndDoc呼叫结束。这两个呼叫对于在⽂件页⾯上书写⽂字或者绘制图形的GDI命令来说,其作⽤就像分隔页⾯的书挡⼀样。每页本⾝是这样来划清界限的:呼叫StartPage来开始⼀页,呼叫EndPage来结束该页。
例如,如果应⽤程序想在⼀页纸上画出⼀个椭圆,它⾸先呼叫StartDoc开始打印任务,然后再呼叫StartPage通知这是新的⼀页,接着呼叫Ellipse,正如同在屏幕上画⼀个椭圆⼀样。GDI模块通常将程序对打印机设备内容做出的GDI呼叫储存在磁盘上的metafile中,该⽂件名以字符串~EMF(代表「增强型metafile」)开始,且以.TMP为扩展名。然⽽,我在这⾥应该指出,打印机驱动程序可能会跳过这⼀步骤。
腾讯防沉迷当绘制第⼀页的GDI呼叫结束时,应⽤程序呼叫EndPage。现在,真正的⼯作开始了。打印机驱动程序必须把存放在metafile中的各种绘图命令翻译成打印机输出数据。绘制⼀页图形所需的打印机输出数
据量可能⾮常⼤,特别是当打印机没有⾼级页⾯制作语⾔时,更是如此。例如,⼀台每英⼨600点且使⽤8.5×11英⼨印表纸的激光打印机,如果要定义⼀个图形页,可能需要4百万以上字节的数据。
为此,打印机驱动程序经常使⽤⼀种称作「打印分带」的技术将⼀页分成若⼲称为「输出带」的矩形。GDI模块从打印机驱动程序取得每个输出带的⼤⼩,然后设定⼀个与⽬前要处理的输出带相等的剪裁区,并为metafile中的每个绘图函数呼叫打印机设备驱动程序的Output函数,这个程序叫做「将metafile输出到设备驱动程序」。对设备驱动程序所定义的页⾯上的每个输出带,GDI模块必须将整个metafile「输出到」设备驱动程序。这个程序完成以后,该metafile就可以删除了。
对每个输出带,设备驱动程序将这些绘图函数转换为在打印机上打印这些图形所需要的输出数据。这种输出数据的格式是依照打印机的特性⽽异的。对点阵打印机,它将是包括图形序列在内的⼀系列控制命令序列的集合(打印机驱动程序也能呼叫在GDI模块中的各种「helper」辅助例程,⽤来协助这种输出的构造)。对于带有⾼阶页⾯制作语⾔(如PostScript)的激光打印机,打印机将⽤这种语⾔进⾏输出。
打印驱动程序将打印输出的每个输出带传送到GDI模块。随后,GDI模块将该打印输出存⼊另⼀个临时⽂件中,该临时⽂件名以字符串~SPL 开始,带有.TMP扩展名。当处理好整页之后,GDI模块对后台打印程序进⾏⼀个程序间呼叫,通知它⼀个新的打印页已经准备好了。然后,应⽤程序就转向处理
二手房税费下⼀页。当应⽤程序处理完所有要打印的输出页后,它就呼叫EndDoc发出⼀个信号,表⽰打印作业已经完成。图13-1显⽰了应⽤程序、GDI模块和打印驱动程序的交互作⽤程序。
图13-1 应⽤程序、GDI模块、打印驱动程序和打印队列程序的交互作⽤过程
Windows后台打印程序实际上是⼏个组件的⼀种组合(见表13-1)。
表13-1
打印队列程序组件说明
打印请求队列程序将数据流传递给打印功能提供者
本地打印功能提供者为本地打印机建⽴背景⽂件
⽹络打印功能提供者为⽹络打印机建⽴背景⽂件
打印处理程序将打印队列中与设备⽆关的数据转换为针对⽬的打印机的格式
打印端⼝监视程序控件连结打印机的端⼝
打印语⾔监视程序控件可以双向通讯的打印机,设定设备设定并检测打印机状态
打印队列程序可以减轻应⽤程序的打印负担。Windows在启动时就加载打印队列程序,因此,当应⽤程序开始打印时,它已经是活动的了。当程序⾏印⼀个⽂件时,GDI模块会建⽴包含打印输出数据的⽂件。后台打印程序的任务是将这些⽂件发往打印机。GDI模块发出⼀个消息来通知它⼀个新的打印作业开始,然后它开始读⽂件并将⽂件直接传送到打印机。为了传送这些⽂件,打印队列程序依照打印机所连结的并列端⼝或串⾏埠使⽤各种不同的通信函数。在打印队列程序向打印机发送⽂件的操作完成后,它就将包含输出数据的临时⽂件删除。这个交互作⽤过程如图13-2所⽰。
图13-2 后台打印程序的操作程序
这个程序的⼤部分对应⽤程序来说是透明的。从应⽤程序的⾓度来看,「打印」只发⽣在GDI模块将所有打印输出数据储存到磁盘⽂件中的时候,在这之后(如果打印是由第⼆个线程来操作的,甚⾄可以在这之前)应⽤程序可以⾃由地进⾏其它操作。真正的⽂件打印操作成了后台打印程序的任务,⽽不是应⽤程序的任务。通过打印机⽂件夹,使⽤者可以暂停打印作业、改变作业的优先级或取消打印作业。这种管理⽅式使应⽤程序能更快地将打印数据以实时⽅式打印,况且这样必须等到打印完⼀页后才能处理下⼀页。
我们已经描述了⼀般的打印原理,但还有⼀些例外情况。其中之⼀是Windows程序要使⽤打印机时,并⾮⼀定需要后台打印程序。使⽤者可以在打印机属性表格的详细数据属性页中关闭打印机的背景操作。
为什么使⽤者希望不使⽤背景操作呢?因为使⽤者可能使⽤了⽐Windows打印队列程序更快的硬件或软件后台打印程序,也可能是打印机在⼀个⾃⾝带有打印队列器的⽹络上使⽤。⼀般的规则是,使⽤⼀个打印队列程序⽐使⽤两个打印队列程序更快。去掉Windows后台打印程序可以加快打印速度,因为打印输出数据不必储存在硬盘上,⽽可以直接输出到打印机,并被外部的硬件打印队列器或软件的后台打印程序所接收。
如果没有启⽤Windows打印队列程序,GDI模块就不把来⾃设备驱动程序的打印输出数据存⼊⽂件中,⽽是将这些输出数据直接输出到打印输出埠。与打印队列程序进⾏的打印不同,GDI进⾏的打印⼀定会让应⽤程序暂停执⾏⼀段时间(特别是进⾏打印中的程序)直到打印完成。
还有另⼀个例外。通常,GDI模块将定义⼀页所需的所有函数存⼊⼀个增强型metafile中,然后替驱动程序定义的每个打印输出带输出⼀遍该metafile到打印驱动程序中。然⽽,如果打印驱动程序不需要打印分带的话,就不会建⽴这个metafile;GDI只需简单地将绘图函数直接送往驱动程序。进⼀步的变化是,应⽤程序也可能得承担起对打印输出数据进⾏打印分带的责任,这就使得应⽤程序中的打印程序代码更加复杂了,但却免去了GDI模块建⽴metafile的⿇烦。这样,GDI只需简单地为每个输出带将函数传到打印驱动程序。
或许您现在已经发现了从⼀个Windows应⽤程序进⾏打印操作要⽐使⽤视讯显⽰器的负担更⼤,这样
可能出现⼀些问题-特别是,如果GDI 模块在建⽴metafile或打印输出⽂件时耗尽了磁盘空间。您可以更关切这些问题,并尝试着处理这些问题并告知使⽤者,或者您当然也可以置之不理。
对于⼀个应⽤程序,打印⽂件的第⼀步就是如何取得打印机设备的内容。
打印机设备内容
正如在视讯显⽰器上绘图前需要得到设备内容句柄⼀样,在打印之前,使⽤者必须取得⼀个打印机设备内容句柄。⼀旦有了这个句柄(并为建⽴⼀个新⽂件呼叫了StartDoc以及呼叫StartPage开始⼀页),就可以⽤与使⽤视讯显⽰设备内容句柄相同的⽅法来使⽤打印机设备内容句柄,该句柄即为各种GDI呼叫的第⼀个参数。
⼤多数应⽤程序经由呼叫PrintDlg函数打开⼀个标准的打印对话框(本章后⾯会展⽰该函数的⽤法)。这个函数还为使⽤者提供了⼀个在打印之前改变打印机或者指定其它特性的机会。然后,它将打印机设备内容句柄交给应⽤程序。该函数能够省下应⽤程序的⼀些⼯作。然⽽,某些应⽤程序(例如Notepad)仅需要取得打印机设备内容,⽽不需要那个对话框。要做到这⼀点,需要呼叫CreateDC函数。
在中,您已知道如何通过如下的呼叫来为整个视讯显⽰器取得指向设备内容的句柄:
非主流情侣个性签名hdc = CreateDC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
您也可以使⽤该函数来取得打印机设备内容句柄。然⽽,对打印机设备内容,CreateDC的⼀般语法为:
hdc = CreateDC (NULL, szDeviceName, NULL, pInitializationData) ;
pInitializationData参数⼀般被设为NULL。szDeviceName参数指向⼀个字符串,以告诉Windows打印机设备的名称。在设定设备名称之前,您必须知道有哪些打印机可⽤。
⼀个系统可能有不只⼀台连结着的打印机,甚⾄可以有其它程序,如传真软件,将⾃⼰伪装成打印机。不论连结的打印机有多少台,都只能有⼀台被认为是「⽬前的打印机」或者「内定打印机」,这是使⽤者最近⼀次选择的打印机。许多⼩型的Windows程序只使⽤内定打印机来进⾏打印。
取得内定打印机设备内容的⽅式不断在改变。⽬前,标准的⽅法是使⽤EnumPrinters函数来获得。该函数填⼊⼀个包含每个连结着的打印机信息的数组结构。根据所需的细节层次,您还可以选择⼏种结构之⼀作为该函数的参数。这些结构的名称为PRINTER_INFO_x,x是⼀个数字。
不幸的是,所使⽤的函数还取决于您的程序是在Windows 98上执⾏还是在Windows NT上执⾏。程序13-1展⽰了GetPrinterDC函数在两种操作系统上⼯作的⽤法。
程序13-1 GETPRNDC
星座查询是阴历还是阳历GETPRNDC.C
/*----------------------------------------------------------------------
GETPRNDC.C -- GetPrinterDC function
GETPRNDC.C -- GetPrinterDC function
-----------------------------------------------------------------------*/
#include <windows.h>
HDC GetPrinterDC (void)
{
DWORD dwNeeded, dwReturned ;
HDC hdc ;
PRINTER_INFO_4 * pinfo4 ;
PRINTER_INFO_5 * pinfo5 ;
if (GetVersion () & 0x80000000) // Windows 98
{
EnumPrinters (PRINTER_ENUM_DEFAULT, NULL, 5, NULL,
0, &dwNeeded, &dwReturned) ;
pinfo5 = malloc (dwNeeded) ;
EnumPrinters (PRINTER_ENUM_DEFAULT, NULL, 5, (PBYTE) pinfo5, dwNeeded, &dwNeeded, &dwReturned) ;
hdc = CreateDC (NULL, pinfo5->pPrinterName, NULL, NULL) ;
free (pinfo5) ;
}
else
//Windows NT
{
EnumPrinters (PRINTER_ENUM_LOCAL, NULL, 4, NULL,
0, &dwNeeded, &dwReturned) ;
pinfo4 = malloc (dwNeeded) ;
EnumPrinters (PRINTER_ENUM_LOCAL, NULL, 4, (PBYTE) pinfo4, dwNeeded, &dwNeeded, &dwReturned) ;
hdc = CreateDC (NULL, pinfo4->pPrinterName, NULL, NULL) ;
free (pinfo4) ;
}
return hdc ;
}
这些函数使⽤GetVersion函数来确定程序是执⾏在Windows 98上还是Windows NT上。不管是什么操作系统,函数呼叫EnumPrinters 两次:⼀次取得它所需结构的⼤⼩,⼀次填⼊结构。在Windows 98上,函数使⽤PRINTER_INFO_5结构;在Windows NT上,函数使⽤PRINTER_INFO_4结构。这些结构在EnumPrinters⽂件(/Platform SDK/Graphics and Multimedia Services/GDI/Printing and Print Spooler/Printing and Print Spooler Reference/Printing and Print Spooler Functions/EnumPrinters,范例⼩节的前⾯)中有说明,它们是「容易⽽快速」的。
修改后的DEVCAPS程序
只显⽰了从GetDeviceCaps函数获得的关于视讯显⽰的基本信息。程序13-2所⽰的新版本显⽰了关于视讯显⽰和连结到系统之所有打印机的更多信息。
程序13-2 DEVCAPS2
DEVCAPS2.C
/*--------------------------------------------------------------------------
DEVCAPS2.C -- Displays Device Capability Information (Version 2)
(c) Charles Petzold, 1998
---------------------------------------------------------------------------*/
#include <windows.h>
#include "resource.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
void DoBasicInfo (HDC, HDC, int, int) ;
void DoOtherInfo (HDC, HDC, int, int) ;
void DoBitCodedCaps (HDC, HDC, int, int, int) ;
图书管理制度typedef struct
{
int iMask ;
米热TCHAR * szDesc ;
}
BITS ;
#define IDM_DEVMODE 1000
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("DevCaps2") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
发布评论