摘 要:本文介绍了一种将数据不经过任何改变传输给打印机的方法,来实现自己对输出的精确控制,并利用VC作为开发环境介绍了详细实现的过程。
关键字:假脱机 打印 VC编程
一、前言
在Windows系统中,若要将一个文件中的数据传输给打印机,一般的方法为将该文件采用拖放功能,利用鼠标将该文件的图标拖到打印机的图标上,系统将会直接调用和该文件扩展名相关联的应用程序,利用该应用程序的打印功能将文件中的数据采用Windows设备描述表的方式转换为打印机能识别的数据放到打印队列中,这种方式要求该种格式的文件在Windows系统中一定要有与它相关联的应用程序来完成这种转换工作,并且输送给打印机的数据已经不是原来文件中的原始数据。在很多情况下我们希望文件中的数据要在不被修改的情况下直接传输给打印机(即文件中的数据和打印机实际接收到的数据一致),不管传输的是文本文件还时控制代码都不能被改变。要实现该方式,可以通过暂时退到DOS状态下的方式来勉强实现,但这种方式没有利用到Windows的后台打印功能,降低了系统的使用效率,另外该方式没有一个友好的用户界面,现在软件没有友好的用户界面将是用户难以接受的,更重要的是这种方式在工作时,如果有其他的应用程序使用打印机,或通过网络共享你的打印机进行打印时系统可能会工作不正常。为此本文提供了一种利用Windows的打印队列将数据不修改地传输给打印机的实现方法,这种方法充分利用了Windows系统的脱机打印功能,去除了上述的各种问题。
二、程序设计的原理
在Windows系统中,如果能将自己的原始数据不被改变的直接放入打印队列中,然后通知打印服务器系统将此文档输出,这样服务器会将文档不变的传输给打印机。从而实现了原始数据的透明输出。
Windows系统在安装打印机驱动程序的时候为每一个打印机建立了一个对应的打印队列。系统通过打印服务器来对其进行管理,控制其中的文档的输出。每一个需要输出的文档被作为一个作业任务存放在打印队列中,实际上每一个作业(JOB)对应磁盘中的一个临时文件,要被直接输出到打印机中的数据就存放在该临时文件中,该临时文件由打印服务器来控制,对于一般的用户来说是不透明的。如果程序设计者能建立这样一个被打印服务器所认同能管理的临时文件,将自己的数据放入该文件中,然后通知打印服务器作业准备就绪,该作业可以被输出了,系统打印服务器就会自动将该文件直接输出。这样一来就实现了原始数据和打印机接受到的数据完全一致的目的,同时又利用系统打印服务器对输出的控制,避免了应用程序直接和硬件端口交换数据,大大简化了程序设计的复杂程度。这样一来,无论是文本文件还是控制代码都可以输出了。
三、程序设计步骤
下面以Visual C++作为开发工具来具体实现该功能,步骤如下:
1. 判断系统是否安装了打印机驱动程序,若安装了驱动程序将获得对应的驱动程序所表示的打印机的名称。具体利用系统提供的函数 EnumPrinters 来实现。
2. 利用函数OpenPrinter根据打印机的名称来打开打印机,并获得打印机的句柄将其保存在变量m_hPrinter中
3. 如果前面的过程都成功则利用系统提供的函数AddJob来在该打印机的队列种添加一个新的打印作业(Spool Job)。通过该函数的调用,系统将获得一个可以用来存放数据的文件名和一个标志号,该结果将被保存在变量pJob所指的内存中。此时用户可以通过一般的文件操作功能来建立并打开该文件,然后将自己的所有需要输出的数据放入该文件中,此时你可以高兴的说:“我的任务完成大部分了”。剩下来的事情就是告诉系统,“我的数据已经准备完毕,你可以管理输出了”。
4. 打开通过函数AddJob所获得的临时文件,将自己要输出的数据写入其中,关闭该文件。
5. 通过系统函数ScheduleJob通知打印服务器,该作业已经准备完毕,可以输出了。系统将根据当前的使用情况来安排合适的时间输出该作业。最后用户别忘了关闭(函数ClosePrinter)打印句柄。
四、程序设计和解释
根据上面的步骤,具体的程序和注释如下:
// Filename: OutPrn.cpp
#include "stdafx.h"
#include <ctype.h>
#include <string.h>
#include <winspool.h>
// *******定义变量********
// 定义实际上指向ADDJOB_INFO_1
// 结构的指针
LPBYTE pJob=0;
//打印机句柄
HANDLE m_hPrinter=NULL;
//********函数定义****************
//获得打印作业的临时文件名和ID号
//并保存在变量pJob所在的空间,成功返
//回true,失败返回false
bool GetSpoolFileName( );
//通知系统数据准备就绪,可以输出,
//同时释放函数运行中占用的内存
void EndPrint( ) ;
//主函数演示怎样调用上面的函数来完成将一个
//文件完整的不变的传给外部设备
void Demo( );
// *******获得当前缺省打印机的设备名称*******
bool GetDefaultPrinterName(CString &name)
{
CPrintDialog pd(TRUE);
if(pd.GetDefaults()==FALSE)
{
AfxMessageBox("Windows系统没有安装缺省打印机");
return false;
}
name=pd.GetDeviceName();
if (pd.m_pd.hDevNames)
{
::GlobalUnlock(pd.m_pd.hDevNames);
::GlobalFree(pd.m_pd.hDevNames);
pd.m_pd.hDevNames=NULL;
}
if (pd.m_pd.hDevMode)
{
::GlobalFree(pd.m_pd.hDevMode);
pd.m_pd.hDevMode=NULL;
}
return true;
}
//********* 函数具体实现 ***********
bool GetSpoolFileName()
{
//定义一些临时变量
DWORD dwNeeded=0;
DWORD dwReturned=0;
LPBYTE pPrinterEnum=0;
BOOL nRet=FALSE;
CString name;
if(GetDefaultPrinterName(name)==false) return false;
//获得系统缺省打印机名称首先调用EnumPrinters获得需要
//多大的存储空间来放获得的信息,
//该大小写入变量dwNeeded 中
::EnumPrinters(PRINTER_ENUM_NAME,NULL,2,NULL,0
,&dwNeeded,&dwReturned);
if(dwNeeded<=0) return false;
//根据前面结果来分配存储空间
pPrinterEnum=new BYTE[dwNeeded];
//再一次调用函数EnumPrinters,
//将获得系统缺省打印机
//信息放入pPrinterEnum中。
nRet=::EnumPrinters(PRINTER_ENUM_NAME,NULL,2
,pPrinterEnum,dwNeeded,&dwNeeded,&dwReturned);
if(nRet==FALSE ||dwReturned==0)
{
//没有找到所需要的缺省打印机,函数返回
delete pPrinterEnum;
return false;
}
// 将pPrinterEnum转换为结构 PRINTER_INFO_2的指针
PRINTER_INFO_2 *pInfo=(PRINTER_INFO_2 *)pPrinterEnum;
for(DWORD num=0L;num<dwReturned;num++)
{
if(lstrcmp((LPTSTR)(&(pInfo[num].pDevMode->dmDeviceName[0])),name)==0){ break;}
}
if(num>=dwReturned) return false;
//根据结构PRINTER_INFO_2中包含的打印机名称来打
//开该打印机并将获得的句柄保存在变量 m_hPrinter中
if(!::OpenPrinter(pInfo[num].pPrinterName
,&m_hPrinter,NULL))
{
//打开打印机失败,函数返回
AfxMessageBox("打开打印机失败");
//释放内存
delete pPrinterEnum;
m_hPrinter=NULL;
return false;
}
//下面不再需要,释放所占用的内存
delete pPrinterEnum;
//使用函数AddJob 来获得新添加的打印作业的
//临时文件名和对应的ID号
dwNeeded=0;
ASSERT(pJob==NULL);
//分配空间用来存放结构 ADDJOB_INFO_1所包
//含的信息注意不要利用AddJob函数自动检
//测需要多大的空间来存
pJob=new BYTE[2048];
//放ADDJOB_INFO_1所包含的信息,该函数
//的返回值本人测试了几次
//都不正确自己给它分配2K的内存足够了。
BOOL flag=::AddJob( m_hPrinter,1,pJob,2048,&dwNeeded);
if(!flag)
{//函数不成功返回
delete []pJob;//释放内存
pJob=0;
AfxMessageBox("分配内存失败");
::ClosePrinter( m_hPrinter);
m_hPrinter=NULL;
return false;
}
return true;
}
// ***************************
void EndPrint( )
{
ASSERT(pJob);
//发送消息给打印管理服务器,当前的作业可以输出了
::ScheduleJob(m_hPrinter,((ADDJOB_INFO_1 *)pJob)->JobId );
//释放打印句柄
ClosePrinter( m_hPrinter);
m_hPrinter=0;
delete []pJob;//释放内存
pJob=0;
m_hPrinter=0;
}
// ****************************
void Demo( )
{
//调用文件对话框选择一个文件
CFileDialog aDlg(TRUE,NULL,"*.*");
if(aDlg.DoModal()==IDCANCEL ) return;
//调用函数生成临时文件和JOB
if(!GetSpoolFileName()) return;
//将原始数据放入临时文件中
CopyFile(aDlg.GetPathName(),((ADDJOB_INFO_1 *)pJob)->Path,FALSE);
//通知服务器作业准备就绪
EndPrint( );
}
上面的程序实现了任何文件不经过改变地传输给打印机的功能。用户可以使用Visual C++自动生成一个程序框架,在程序中调用 Demo()函数将打开一个对话框让用户选择一个文件,然后程序将将该文件中的内容直接输出个缺省得打印机。读者可以选择一个文本文件进行测试。
上面的程序采用的编程环境为Visual C++6.0,Windows98,在Windows NT4.0(Service Pack 5)上测试通过。用户必须在Windows系统中至少配置一个支持后台打印的打印机。
参考文献
1.Microsoft MSDN Library Visual Studio6.0
技术文档
|