


Windows 打印管理在Linux 平臺實現
1.理解 Windows 打印機管理和 Linux 打印機管理
Windows 平臺提供了非常簡單而且完善的打印機管理系統。在 Windows 編程中,打印功能被融入了 GDI (Graphic Device Interface)模塊。在 GDI 模塊中,程序員只要調用 EnumPrinters() 等 APIs 就可以輕松獲取打印機信息。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 平臺所支持。
2.打印機管理移植架構
打印機管理移植是應用程序跨平臺移植的重要組成部分。不同平臺所支持的打印接口是不同的,因此移植的核心就是實現平臺之間的打印機管理接口的轉換。下圖展示了打印機管理移植的架構。
Windows 提供了一系列 API 來獲取打印機信息。這些信息被封裝在預定義的Windows 標準結構中,比如 DEVMODE,PRINTER_INFO_2,PRINTER_INFO_4 等等。Linux 使用 CUPS 來獲取打印機信息,這些信息被封裝在 cups_dest_t,ipp_attribute_t 等數據結構中。只要正確獲取 Linux 平臺上打印機信息,并把它們轉化成 Windows 打印機數據結構,就可以完成打印機管理。
3.CUPS 基礎知識
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> |
有了上述 CUPS 基礎知識,下文將舉例說明使用 CUPS 實現打印機管理移植的技術細節。
4.獲取打印機數量
Windows 通過 API EnumPrinters() 的返回參數 pcReturned 來獲取系統的打印機數量。Windows 程序的具體實現如下所示:
int n_PrinterCount;EnumPrinters( , , , , , , &n_PrinterCount); |
在 Linux 中,CUPS 函數 cupsGetDests() 可實現同樣的功能。需要注意的是,在調用結束后,調用者需要使用 cupsFreeDests() 來釋放內存。
cups_dest_t *dests; int n_PrinterCount = cupsGetDests( &dests ); cupsFreeDests(count, dests); |
5.獲取打印機名稱,打印機端口和打印機型號
Windows使用API EnumPrinters() 來獲取打印機名稱,打印機端口和打印機型號。詳情請參考Windows MSDN。在Linux平臺上,CUPS可實現同樣的功能。具體實現流程如下圖所示:
圖2 獲取打印機信息(名稱,端口,型號)流程
5.1建立 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("Cannot connect to CUPS server\n"); return 0; } |
5.2建立 IPP 請求
建立一個新的 IPP 請求是通過 IPP 調用 ippNew() 來實現的。在此,operation_id 被設置為 CUPS_GET_PRINTERS,其語義是當前 IPP 請求要獲取和打印機相關的信息。同時,request_id 被設置為 1,這是 IPP 協議所規定的。
ipp_t *pIPPReq = ippNew(); pIPPReq->request.op.operation_id = CUPS_GET_PRINTERS; pIPPReq->request.op.request_id = 1; |
5.3設置 IPP 請求
以下是進一步設置當前 IPP 請求 pIPPReq 的細節。需要指出的是,在和 CUPS 服務器進行交互的過程中,很多信息是通過字符串來傳遞的。這就涉及到了文字語言編碼表示的問題。函數 cupsLangDefault() 就是用來獲取 CUPS 服務器的默認語言設置。cupsLangDefault() 的返回值 pDefLang 還將作為參數傳遞給其它函數來完成對 IPP 請求的進一步設置。
根據 IPP 協議,對 IPP 請求的設置要從設置參數 "attributes-charset"(字符集)和"attributes-natural-language"(自然語言)開始。下列代碼分別用系統默認字符集和CUPS 默認語言來設置這兩個參數。完成這兩項規定設置后,用戶就可以根據需求對需要的信息提出請求。此處需要獲得的信息是打印機名稱,端口號和打印機型號。在 IPP 協議中,這三項對應的IPP請求關鍵字分別是 "printer-name", "device-uri" 和 "printer-make-and-model"。下列代碼定義了數組 pReqAttrs 來存儲上述關鍵字,然后通過請求參數 "requested-attributes" 來設置這些 IPP 請求。
cups_lang_t *pDefLang = cupsLangDefault(); if (!pDefLang) { g_print("Cannot get default language\n"); return 0; } ippAddString(pIPPReq, IPP_TAG_OPERATION, IPP_TAG_CHARSET, "attributes-charset", NULL, cupsLangEncoding(pDefLang)); ippAddString(pIPPReq, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "attributes-natural-language", NULL, pDefLang->language);static const char *pReqAttrs[] = {"printer-name", "device-uri","printer-make-and-model"}; ippAddStrings(pIPPReq, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", 3, NULL, pReqAttrs); |
5.4發送 IPP 請求
設置好 IPP 請求之后,通過函數 cupsDoRequest() 就可以把指定IPP請求發送到服務器端。如果請求發送成功,那么請求發送方將得到有效的IPP應答pIPPRes。需要指出的是,即使 IPP 應答有效,也并不意味著所有 IPP 請求的內容都得到了正確的回復。還需要進一步檢查 IPP 應答的狀態代碼 "request.status.status_code" 來核實反饋信息的有效性。
ipp_t *pIPPRes = cupsDoRequest(pHTTPConnection, pIPPReq, "/"); if (!pIPPRes) { g_print("No response from CUPS server\n"); return 0; } if (pIPPRes->request.status.status_code > IPP_OK_CONFLICT) { printf("IPP Error: %s\n", ippErrorString(pIPPRes->request.status.status_code)); ippDelete(pIPPRes); return 0; } |
5.5獲取 IPP 應答
如果上述操作都成功返回,就可以進一步從 pIPPRes 結構中提取感興趣的信息。在下列代碼中,變量 pPrinterName,pPortName 和 pPrinterModel 分別用來存儲打印機名稱,打印機端口號和打印機的類型信息。通過依次枚舉 IPP 應答 pIPPRes 來尋找屬性 pAttr->name 為 "printer-name" 或 "device-uri" 或 "printer-make-and-model" 的分量,就可以得到上述信息。
char *pPrinterName = NULL; char *pPortName = NULL; char *pPrinterModel = NULL; for (ipp_attribute_t *pAttr = pIPPRes->attrs; pAttr != NULL; pAttr =pAttr->next) { if (pAttr->group_tag == IPP_TAG_PRINTER) { if (0 == strcmp(pAttr->name, "printer-name")) pPrinterName = pAttr->values->string.text; if (0 == strcmp(pAttr->name, "device-uri")) pPortName = pAttr->values->string.text; if (0 == strcmp(pAttr->name, "printer-make-and-model")) pPrinterModel = pAttr->values->string.text; } } |
5.6釋放內存
最后,需要釋放相關內存以免內存泄露:
httpClose(pHTTPConnection); ippDelete(pIPPRes); |
6字符編碼轉換
在實現打印機管理的移植過程中,還需要特別注意字符編碼轉換的問題。當然,字符編碼問題不僅僅局限于本文所探討的范疇,它同時還是所有應用程序移植都需要特別關注的技術細節。以本文為例,在 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
7.結束語
移植是一項實現應用程序跨平臺運行的核心技術。本文重點闡述了 Windows 打印機管理系統在 Linux 平臺上的移植技術。通過具體舉例,本文詳細分析了打印機管理在 Linux 平臺上的移植細節。本章涉及的移植架構和移植概念不僅僅局限于打印機管理移植。這些概念和思想也是所有應用程序所通用的。應用程序跨平臺移植已經在很多軟件中被應用。實現應用程序在不同平臺上無縫隙的運行操作也將是每位移植技術人員的共同目標。
參考資料
- "CUPS Software Programmers Manual", Easy Software Products, Copyright 1997-2004
- "CUPS Implementation of IPP", Easy Software Products, Copyright 1997-2004
- RFC2910 "Internet Printing Protocol/1.1: Encoding and Transport", 2000
- RFC2911 "Internet Printing Protocol/1.1: Model and Semantics", 2000
- RFC3196 "Internet Printing Protocol/1.1: Implementor's Guide", 2001
- MSDN: Microsoft Developer Network
- http://www-306.ibm.com/software/globalization/icu/index.jsp
關鍵字:管理、平臺、打印
新文章:
- CentOS7下圖形配置網絡的方法
- CentOS 7如何添加刪除用戶
- 如何解決centos7雙系統后丟失windows啟動項
- CentOS單網卡如何批量添加不同IP段
- CentOS下iconv命令的介紹
- Centos7 SSH密鑰登陸及密碼密鑰雙重驗證詳解
- CentOS 7.1添加刪除用戶的方法
- CentOS查找/掃描局域網打印機IP講解
- CentOS7使用hostapd實現無AP模式的詳解
- su命令不能切換root的解決方法
- 解決VMware下CentOS7網絡重啟出錯
- 解決Centos7雙系統后丟失windows啟動項
- CentOS下如何避免文件覆蓋
- CentOS7和CentOS6系統有什么不同呢
- Centos 6.6默認iptable規則詳解