|
移植是一项实现应用程序跨平台运行的核心技术,本文介绍了一种在Linux平台上实现Windows打印机管理的移植方法及具体实现细节。
移植架构和移植概念不仅仅局限于打印机管理移植,这些概念和思想也是所有应用程序所通用的。应用程序跨平台移植已经在很多软件中被应用。实现应用程序在不同平台上无缝隙的运行操作也将是每位移植技术人员的共同目标。
Windows 平台提供了非常简单而且完善的打印机管理系统。在Windows编程中,打印功能被融入了GDI (Graphic Device Interface)模块。在GDI模块中,程序员只要调用EnumPrinters() 等API就可以轻松获取打印机信息。Windows的这种成熟打印管理机制很大程度上得益于打印机供应商所提供的完善的打印机驱动。Windows的打印机驱动屏蔽了打印机的具体打印实现细节,同时为上层调用提供了简单的API接口。
与Windows平台相反,打印机管理机制在Linux平台上从产生到成熟却经过了一个漫长的过程。Linux打印系统最早源于Unix打印系统,但Unix系统却一直缺乏统一的标准接口。由于历史原因,不同Unix平台使用着不同的打印系统。在各种Unix打印解决方案中,最流行的是Berkeley打印系统和 System V打印系统。一方面,不同打印系统需要不一样的打印驱动支持; 另一方面,Unix只拥有相对较小的客户群。这些因素使得很多打印机供应商完全放弃了对Unix平台的支持。统一打印接口的缺乏和底层驱动的不完善使打印在很长一段时间内成为了Linux平台的一大功能漏洞。
最终CUPS (Common Unix Printing System)的出现解决了上述窘境。CUPS是Unix/Linux上通用的打印系统。CUPS提供了一套CUPS API来完成Unix/Linux系统和打印机之间的交互。例如,用户可以通过CUPS获取打印机的信息,也可以通过CUPS设置打印机。CUPS提供了对Berkeley和System V打印命令的支持,这种兼容性使得之前的系统不用进行大规模修改就可被延续使用。同时,CUPS还提供一系列模块化的过滤接口。通过这些接口,打印机提供商只需要开发一个驱动程序就可以满足所有平台的需求。至今为止,CUPS已被所有Unix和Linux平台所支持。
CUPS 是Unix/Linux平台上的打印系统。CUPS的定义和实现是基于IPP(Internet Printing Protocol)协议的。IPP是通用的打印系统标准,它的功能和操作被一系列RFC(Request for Comments)所详细定义。这些具体功能和操作包括:建立IPP请求、应答IPP请求和设置IPP请求等等。和IPP相关的RFC包括 RFC1179、RFC2910、RFC2911、RFC3196等。在网络协议中,IPP位于HTTP(Hyper-Text Transport Protocol)协议之上。因此以下代码示例将涉及到很多IPP和HTTP的系统调用,例如ippAddString()和 httpConnectEncrypt()等。此外,在Unix/Linux平台上在使用CUPS之前要提前引入下列头文件:
#include < cups/cups.h>
#include < cups/language.h>
#include < cups/http.h>
#include < cups/ipp.h>
打印机管理移植架构
打印机管理移植是应用程序跨平台移植的重要组成部分。不同平台所支持的打印接口是不同的,因此移植的核心就是实现平台之间的打印机管理接口的转换。图1展示了打印机管理移植的架构。

Windows 提供了一系列API来获取打印机信息。这些信息被封装在预定义的Windows标准结构中,比如DEVMODE、PRINTER_INFO_2、 PRINTER_INFO_4等等。Linux使用CUPS来获取打印机信息,这些信息被封装在cups_dest_t、ipp_attribute_t 等数据结构中。只要正确获取Linux平台上打印机信息,并把它们转化成Windows打印机数据结构,就可以完成打印机管理。
获取打印机数量
Windows通过API EnumPrinters() 的返回参数pcReturned来获取系统的打印机数量。Windows程序的具体实现如下所示:
int n_PrinterCount;
EnumPrinters( , , , , , , &amp;n_PrinterCount);
在Linux中,CUPS函数cupsGetDests() 可实现同样的功能。需要注意的是,在调用结束后,调用者需要使用cupsFreeDests() 来释放内存。
cups_dest_t *dests;
int n_PrinterCount = cupsGetDests( &amp;dests );
cupsFreeDests(count, dests);
获取打印机名称、端口和型号
Windows使用API EnumPrinters() 来获取打印机名称,打印机端口和打印机型号。详情请参考Windows MSDN。在Linux平台上,CUPS可实现同样的功能。具体实现流程如图2所示。

建立HTTP连接
使用CUPS获取打印机名称,打印机端口和打印机型号信息首先需要开启IPP和HTTP服务。开启服务的第一步是建立一个HTTP连接来和CUPS服务器取得联系。在下面的代码中,cupsServer() 将返回指向默认CUPS服务器名称的指针; ippPort() 将返回IPP请求的默认端口号; cupsEncryption() 将返回当前CUPS请求的默认加密设置。将这些返回值作为参数传递给函数httpConnectEncrypt() 就可以建立一个HTTP连接。如果HTTP连接建立成功,即httpConnectEncrypt() 的返回值pHTTPConnection有效,那么就可以基于这个连接进行下一步IPP请求。
http_t *pHTTPConnection = httpConnectEncrypt( cupsServer(),ippPort(),cupsEncryption() );
if (!pHTTPConnection)
{
g_print(&quot;Cannot connect to CUPS server\n&quot;);
return 0;
}
建立IPP请求
建立一个新的IPP请求是通过IPP调用ippNew()来实现的。在此,operation_id被设置为CUPS_GET_PRINTERS,其语义是当前IPP请求要获取和打印机相关的信息。同时,request_id被设置为1,这是IPP协议所规定的。
ipp_t *pIPPReq = ippNew();
pIPPReq-&gt;request.op.operation_id = CUPS_GET_PRINTERS;
pIPPReq-&gt;request.op.request_id = 1;
设置IPP请求
以下是进一步设置当前IPP请求pIPPReq的细节。需要指出的是,在和CUPS服务器进行交互的过程中,很多信息是通过字符串来传递的。这就涉及到了文字语言编码表示的问题。函数cupsLangDefault() 就是用来获取CUPS服务器的默认语言设置。cupsLangDefault() 的返回值pDefLang还将作为参数传递给其它函数来完成对IPP请求的进一步设置。
根据IPP协议,对IPP请求的设置要从设置参数&quot;attributes-charset&quot;(字符集)和 &quot;attributes-natural-language&quot;(自然语言)开始。下列代码分别用系统默认字符集和CUPS默认语言来设置这两个参数。完成这两项规定设置后,用户就可以根据需求对需要的信息提出请求。此处需要获得的信息是打印机名称,端口号和打印机型号。在IPP协议中,这三项对应的IPP请求关键字分别是&quot;printer-name& quot;、 &quot;device-uri&quot;和&quot;printer-make-and- model&quot;。
下列代码定义了数组pReqAttrs来存储上述关键字,然后通过请求参数&quot;requested-attributes&quot;来设置这些IPP请求。
cups_lang_t *pDefLang = cupsLangDefault();
if (!pDefLang)
{
g_print(&quot;Cannot get default language\n&quot;);
return 0;
}
ippAddString(pIPPReq,IPP_TAG_OPERATION,IPP_TAG_CHARSET,&quot;attributes-charset&quot;,NULL, cupsLangEncoding(pDefLang));
ippAddString(pIPPReq,IPP_TAG_OPERATION,IPP_TAG_LANGUAGE,&quot;attributes-natural-language&quot;,NULL,pDefLang-&gt;language);
static const char *pReqAttrs[] = {&quot;printer-name&quot;, &quot;device-uri&quot;, &quot;printer-make-and-model&quot;};
ippAddStrings(pIPPReq, IPP_TAG_OPERATION,IPP_TAG_KEYWORD,&quot;requested-attributes&quot;,3, NULL, pReqAttrs);
发送IPP请求
设置好IPP请求之后,通过函数cupsDoRequest() 就可以把指定IPP请求发送到服务器端。如果请求发送成功,那么请求发送方将得到有效的IPP应答pIPPRes。需要指出的是,即使IPP应答有效,也并不意味着所有IPP请求的内容都得到了正确的回复。还需要进一步检查IPP应答的状态代码&ldquo; request.status.status_code&rdquo;来核实反馈信息的有效性。
ipp_t *pIPPRes = cupsDoRequest(pHTTPConnection, pIPPReq, &quot;/&quot;);
if (!pIPPRes)
{
g_print(&quot;No response from CUPS server\n&quot;);
return 0;
}
if (pIPPRes-&gt;request.status.status_code &gt; IPP_OK_CONFLICT)
{
printf(&quot;IPP Error: %s\n&quot;, ippErrorString(pIPPRes-&gt;request.status.status_code));
ippDelete(pIPPRes);
return 0;
}
获取IPP应答
如果上述操作都成功返回,就可以进一步从pIPPRes结构中提取感兴趣的信息。在下列代码中,变量pPrinterName,pPortName和 pPrinterModel 分别用来存储打印机名称,打印机端口号和打印机的类型信息。通过依次枚举IPP应答pIPPRes来寻找属性pAttr-&gt; name为&quot;printer-name&quot;或&quot;device- uri&quot;或&quot;printer-make-and-model&quot;的分量,就可以得到上述信息。
char *pPrinterName = NULL;
char *pPortName = NULL;
char *pPrinterModel = NULL;
for (ipp_attribute_t *pAttr = pIPPRes-&gt;attrs; pAttr != NULL; pAttr = pAttr-&gt;next)
{
if (pAttr-&gt;group_tag == IPP_TAG_PRINTER)
{
if (0 == strcmp(pAttr-&gt;name, &quot;printer-name&quot;))
pPrinterName = pAttr-&gt;values-&gt;string.text;
if (0 == strcmp(pAttr-&gt;name, &quot;device-uri&quot;))
pPortName = pAttr-&gt;values-&gt;string.text;
if (0 == strcmp(pAttr-&gt;name, &quot;printer-make-and-model&quot;))
pPrinterModel = pAttr-&gt;values-&gt;string.text;
}
}
释放内存
最后,需要释放相关内存以免内存泄露:
httpClose(pHTTPConnection);
ippDelete(pIPPRes);
字符编码转换
在实现打印机管理的移植过程中,还需要特别注意字符编码转换的问题。当然,字符编码问题不仅仅局限于本文所探讨的范畴,它同时还是所有应用程序移植都需要特别关注的技术细节。以本文为例,在Linux上获取的字符串,比如打印机名称,通常是UTF-8(Unicode Transformation Format)编码的。而Windows应用程序并不使用UTF-8编码。由于历史原因,Windows程序或使用ANSI编码方式,或使用UTF-16 编码方式。因此,从CUPS获取的字符串还需要根据程序运行环境进行编码转换,之后才能被Windows应用程序使用。字符编码转换可以使用IBM ICU(International Components for Unicode)来完成。
Linux联盟收集整理
 |
频道声明:本频道的文章除部分特别声明禁止转载的专稿外,可以自由转载.但请务必注明出出处和原始作者 文章版权归本频道与文章作者所有.对于被频道转载文章的个人和网站,我们表示深深的谢意。
| 原始作者:佚名 |
录入时间:2007-1-3 3:57:17 |
| 信息来源:不详 |
投稿信箱:itqoo@126.com |
|
|
 |
|