IBM中國軟體開發實驗室 龔奕平
移植是一項實現應用程序跨平台運行的核心技術,本文介紹了一種在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)來完成。
詳情請參考http://www-306.ibm.com/software/globalization/icu/index.jsp。
[火星人 ] Windows印表機管理向Linux移植已經有549次圍觀