操作系統是如何控制印表機列印文件的?

我們知道印表機和顯示器對於計算機都是外設。顯示器通過暴露自己的VGA等一些介面給操作系統,操作系統寫視頻buffer來改變顯示器上的畫面顯示。那麼對於很多種印表機,在操作系統的層面是否有統一的一層抽象的介面,來供不同的軟體來調用,例如word,notepad,PS這類的?


終於看到一個自己專業範圍內的問題。不過苦惱於不知道應該說多深合適。

所以打算由淺慢慢談到深,題主看到自己希望了解的深度就好。

首先,我是開發Windows印表機驅動的,所以這裡只談Windows下的情況,Mac等其它系統不在此列。另外,我覺得提主對於顯示器的說法可能也不太正確,但不在我的範圍內,就不多談了。

---------------第一層---------------

要解釋操作系統如何控制印表機,就不得不談驅動。而談驅動之前,則要講一下操作系統提供給印表機驅動的介面。

對於現在的主流的Windows系統,是有統一的介面的。準確的說是有兩個統一的介面。

從Windows95開始至今(Windows10)都支持統一的通用圖形介面——GDI。

而從WindowsVista開始至今,MS提供了另一個統一的通用圖形介面——XPS。

但肩負著代替GDI使命的XPS過了多年依然沒達成這一任務,所以目前是GDI和XPS共存並互相兼容的一個狀態。而印表機驅動在這裡也分成兩個大的框架:GDI框架與XPS框架。這兩個框架的處理方式不同,下面分開來說。

GDI框架:

在GDI框架下,又分成2個模式——EMF模式(又稱後台列印模式)與Raw模式。這兩種模式的處理方式也是不同的。

在EMF模式中,軟體通過調用GDI提供的介面而生成用於印刷的通用中間文件(EMF文件)。再由列印假離線隊列服務調用印表機驅動的介面將這個EMF文件解析為印表機可以處理的文件(Raw文件),並通過埠服務發給印表機進行印刷。

在Raw模式中,軟體通過調用GDI提供的介面而直接調用驅動所提供的介面直接生成印表機所能處理的文件(Raw文件)並直接通過埠服務發給印表機進行印刷。

下圖是MSDN上對於GDI框架的說明。由於XP以後的印表機驅動都為用戶態,內核態的印表機驅動基本不再使用,就不另加說明了。

(圖片來自MSDN 網站)

XPS框架:

在XPS框架下,只有一種模式。軟體將不會直接調用驅動,其都會通過OS的XPS服務而生成一個用於印刷的通用XPS文件。再由列印假離線隊列服務調用印表機驅動的各種過濾器修改生成的XPS文件並最終生成印表機可以處理的文件(可以是XPS文件,也可以是Raw文件),再通過埠服務發給印表機進行印刷。

下圖是MSDN上對於XPS框架的說明。注意這個圖是XPS混和GDI的,對於上面的說明只看XPS路線即可。

(圖片來自MSDN 網站)

---------------第二層---------------

上面只是說明了一個框架和基本的流程。

這裡往後就要談的深一些了。要說明一下操作系統具體是怎麼進行操作的。

但在此之前需要先說明兩個重要的概念,列印假離線隊列和設備上下文

列印假離線隊列(Printer Spooler)

說實話,我第一次看這個詞的反應是——這是個什麼鬼。。

計算機的術語裡面有很多讓人很難懂的翻譯,這個絕對算的上是最難懂之一。我也很少去用這個詞,下面的說明也都將用Spooler代替。

什麼是Spooler呢?

前面已經提到現在的印表機驅動是工作在用戶態而不是內核態。要知道絕大部分驅動都是工作在內核態的。所以印表機是一個相對來說比較特殊的硬體。不但不同於顯卡這種機箱內的硬體,也不同於滑鼠鍵盤等外設。滑鼠鍵盤顯示器等都是即時反應的,比如移動一下滑鼠OS會立即處理並反應在顯示器上。但印表機不是,印刷一個文檔快則幾秒,慢則有數十分鐘。OS不可能一直等到印表機處理結束再進行返回。所以,OS提供了一個叫Spooler的服務來對各種印表機的任務進行管理。這就是Spooler的作用。

那麼Spooler是怎麼工作的呢?

說起來比較複雜,不過可以簡單的把Spooler想像成一個快遞公司。應用程序列印到印表機的過程就可以想像成下面這樣:首先,應用程序將數據打包好,並填好單子上的地址,再交到Spooler的快遞員手中,之後應用程序就可以不用管這個包裹繼續做其它事情。然後,快遞員將這個包裹運送到總部,按順序推疊起來等待分檢員分檢。分檢員通過單子上的地址交到不同的,分管這個地址的快遞員手中。而送快遞的一次只能送少量包裹,所以按照順序一件件的把包裹送到目的地印表機的手上。最後印表機拆開包裹按照包裹里的數據進行列印。這樣整個列印作業就完成了。在這裡運送的包裹被稱為Spool File。

設備上下文(DeviceContext)

又是一個比較頭痛的翻譯,不過這個詞和句柄一樣,算是一個比較常用的詞,也就是我們常說的DC。同樣下面的說明也都用DC代替。

DC可以理解為一個物理設備在OS中的抽像體現。比如一台印表機,OS不能實際的看到它也不能實際的模到它,但是OS還想使用它。怎麼辦呢?這就需要另一個人將這個印表機抽像出來並告訴OS。

怎麼抽像呢?

比如我旁邊有一個白板,你們看不到它。但是我可以告訴你,這個白板的大小是600mm x 900mm,備有一個板擦和黑色的記號筆。這個白板就在你的腦中抽像出來了。當你想使用它的時候,你可以告訴我:拿起黑色的記號筆,從(100mm, 200mm)開始劃條線到(200mm,300mm)的地方。我照做後,將與你期望的結果一致。這些信息就是白板抽像出來的DC。

接下來一個問題,誰來告訴OS這台印表機的抽像信息呢?

我想很多人應該已經知道答案了,就是這台印表機的驅動。

印表機驅動為OS提供了對應印表機的設備上下文(DC)。

OS和應用程序通過操作這個DC來操作印表機列印數據。

關於DC再多說一點,談談設備無關性

看完DC的解釋可能有些人會有疑問,為什麼OS不一開始就將印表機抽像好而非要驅動去告訴它呢?OS這麼做是為了實現設備無關性。

在早期的應用程序開發中,對印表機的使用是需要應用程序的開發者了解硬體的。這成了程序員的負擔,同時也使開發好的程序難以移植。在有了DC的概念後,各種圖形設備都可以抽像成一個DC。這個DC可以是一個顯示器,可以是一個噴默印表機,也可以是一個激光印表機。對各各DC的操作大體上並無不同。這就大大方便了開發者,也使應用程序不再局限於設備。應用程序開發者不再需要了解硬體的知識,只要知道如何使用DC就可以在各種不同的設備上實現想要的效果。

比如用GDI畫圓命令在顯示器的DC上畫了一個圓,同樣的處理只需要將DC換成各種廠家的印表機DC,就可以在印表機上列印同樣的一個圓。這就是設備無關性。

---------------第三層---------------

第三層以後的內容會比較專業了,對於大部分人來說可能意義不大。前面的像列印假離線這樣雖然難懂,但不管怎樣還算有個翻譯,後面這些很多是連翻譯都沒有了。

了解了上面的兩個概念後終於可以開始說說系統在控制印表機時具體做了什麼。OS在這裡的工作可以分成下面幾個大類:應用程序應答,列印設置,渲染,流程式控制制,以及事件觸發

寫完後覺得可能有些枯燥,為了方便理解,增加了括弧里的內容。對專業的部分不感興趣的也可以只看括弧里的內容。可以先將OS控制印表機的過程想像成一次新家裝修。OS是裝修公司。

應用程序應答

裝修公司OS今天從Word那裡接了個大活,半包300平米。OS把這個活交給了手下的驅動小組PrinterDriver。Word不太清楚這個小組的底細,跟OS問他們都能幹啥,能不能滿足我的要求。OS也只好去問PrinterDriver,小組中能說會道的小A出來說我們可以做水電、刮大白、貼瓷磚、熟練PS、會編C++、代寫暑假作業...... OS說行了行了,後面那些我不關心...Word還說要打個柜子,這個你會不?小A說不會。OS說這個柜子我來想辦法。

應答的時間點是這幾個工作中最靠前的。作用是讓應用程序了解正在使用的印表機,以進行對應的處理。應答的信息包括用紙信息、印刷能力等等。舉個最簡單的例子,比如當你打開記事本,在文件菜單中選頁面設置,你可以看到你的印表機驅動所支持的用紙和進紙方式被列在了紙張大小和來源里。換成其它驅動後這個列表也會變化。

所以應答信息是由驅動提供的。應答的方式在GDI框架下與XPS框架下是不同的。

GDI框架下應用程序想獲得信息是這樣的:

應用程序 -&> OS (GetDeviceCaps,DeviceCapabilities)-&> 驅動 (GDIINFO,DEVINFO,DrvDeviceCapabilities)

XPS框架下要簡單一些:

應用程序 -&> OS (PrintCapabilities)-&> 驅動 (PrintCapabilities)

應答信息的內容很多,在這裡就不進行羅列了,有興趣可自行去查MSDN。

想強調的一點是,應答除了列出可選擇的信息,也會影響應用程序的印刷行為。比如在使用Word的時候如果設置了很窄的邊距。如果設置的邊距低於驅動的應答值,在印刷時Word會彈出提示"印刷範圍超出了印表機可列印的範圍,是否繼續?"。繼續印刷的話Word會自行切掉超出範圍的數據部分。而有些驅動在應答中說明不能實現的部分也是OS代為實現的。

列印設置:

Word拿到了PrinterDriver小組給自已的報價單,在報價單的幾個選擇中選了自己想要的木板品牌,水管等等。不過有很多細節還是要和PrinterDriver小組當面談談。通過面談知道PrinterDriver居然還會代寫暑假作業,正好Word的作業沒寫完,就交給PrinterDriver寫了。

在應用程序取得了印表機能做什麼的列表後,應用程序將其提供給用戶並進行要做什麼的設置。比如選擇用紙(A4,B5等),設置解析度(600dpi,1200dpi等)這些。但應答列表是屬於OS標準定義的,並不能完全反應列印的能力。所以在標準定義之外的設置還是要打開驅動的界面進行設置,比如有些印表機支持的省墨模式。同樣,設置的方式在GDI框架下與XPS框架下是不同的。

GDI框架下使用一個名為Devmode結構體進行設置。這個結構體的信息存在註冊表中,並包括了目標印表機驅動的幾乎所有的設置。這個結構體包括兩個部分:標準設定部分和擴展設定部分(在MS Core的驅動下,擴展設定部分又分為MS保留部分和廠商自定義部分)。標準設定部分的定義是固定的,所有應用程序和驅動都遵守一樣的標準。標準Devmode部分是應用程序主要利用的地方,大多數應用程序在這裡是與驅動連動的。比如在記事本里設置了A4紙去驅動界面看的時候也是A4紙。也有像Word這樣比較霸道的應用程序跟本無視驅動的用紙設置,強制替換成Word中的設置。而擴展設定部分則由各個廠商去定義,但一般是不公開的。

Devmode是控制印表機驅動的核心,掌握了Devmode就可以控制印表機驅動幾乎所有的功能

另外,Devmode在註冊表中、應用程序中有多個副本,各各副本在不同許可權和環境下有著各自的優先順序,不過這個已經太過深入,在這裡就不展開了。

XPS框架下起著同樣作用的東西叫PrintTicket,同樣也是XPS驅動的核心。雖然其構造與Devmode完全不同,但作用基本一致。這裡就不再去詳細介紹了。不過需要提到的是PrintTicket/PrintCapbilities這對XPS框架下的應答與設置可以在理論上做到對驅動功能的完全應答 ,也就是說應用程序不用打開驅動的界面也可以有對印表機的完全控制。這是相對於Devmode進步的地方。但目前我還沒見到能去處理完全應答的應用程序。或許在下一代的Office上能見到。

渲染:

PrinterDriver小組確認了Word的需求後,派出了小G過來拿過Word的原材料,開始做水電,活水泥,貼磁磚,鋪地板等等,別看PrinterDriver小組那麼多人,其實真正在幹活的就小G一個人。

相對來說渲染是一個我比較喜歡的翻譯,這個詞用的也比較多,尤其是在3D領域。通常來說,我們提到渲染是指將一幅矢量圖形進行光柵化(有關矢量Vector和光柵Raster相關的知識量很大,請自行百度,這裡不做展開)。而在印表機這裡,情況要複雜一些。在這裡將渲染說成翻譯更準確一些。可以想像如果OS說英語而印表機只會說漢語,印表機自然聽不通OS在說什麼,所以就需要進行翻譯(渲染)。

所以,在談渲染之前,我們需要先了解印表機的語言。

如果我們說OS說的是GDI或者XPS語言的話,那麼印表機那裡也說著很多不同的語言。比如比較主流的有HP的PCL語言,Adobe的PS語言,另外各家大廠比如佳能、理光等也有著自己的語言。然後在MS推XPS之後,要求各家廠商要支持XPS語言。另外還包括蘋果的AirPrint等等。

經常我們裝上驅動之後,會發印表機名字後面有XXXXX(PCL)之類的字樣,這就是在說明這個驅動所使用的印表機語言。這有時會使我們比較困惑,為什麼有這麼多語言,各種語言之間有什麼區別?看打出來的結果都一樣啊。然而這只是表像,實際上區別非常之大,甚至會讓你覺得是不同的兩個印表機

作為背景知識,在這裡我只簡單談下主流的PCL和PS語言。(當然,其它大廠的語言就算想談,我這裡也沒有資料)

PCL語言是HP公司推出的一種矢量的印表機頁面描述語言。使用它完全免費,目前最高版本為PCL6。由於其免費,是目前使用最廣的一種印表機語言。其特點是簡單高效。

PS語言是Adobe公司推出的一種矢量的印表機頁面描述語言。是收費的,但對Adobe系的軟體支持的非常好。由於Adobe在平面領域的統治性地位,在專業領域PS語言也是不二選擇。但通常採用PS語言的印表機價格較高。

這兩種語言都是完備的矢量語言,而且OS方面也都提供了通用驅動(UniDrv)。其印刷文件生成及傳輸都非常快速,而光柵化的處理則在大多印表機端進行。也有出於印表機性能與成本的考慮將光柵化在驅動端進行的(民用的印表機多是如此)。下面只談光柵化處理在印表機端進行的情況。

好了,現在我們回來談一下渲染,印表機驅動渲染的實質就是將應用程序以GDI或XPS描畫的文檔翻譯為印表機能夠處理的語言

以OS的介面來說,首先渲染的描畫包括三個元素:文字(Text)、路徑(Path)、點陣圖(Bitmap)。驅動端需要實現OS的這三個介面,以供OS調用。比如,以PCL的驅動為例,當OS在文檔中碰到一個文字的GDI描畫(TextOut)時,它會調用驅動實現的介面(DrvTextOut),並根據應答向這個介面提供翻譯所需的必要信息,而驅動的介面將這段文字翻譯成印表機能夠處理的PCL指令,再輸出到印表機。對各個元素,下面稍微細說一下。

文字:主要提供的信息包括:位置、間距、字元、字體、字體字符集、顏色、裁切等。通常驅動會將字元對應的字體信息抽出,作為下載字體一起發送給印表機。

路徑:主要提供的信息包括:頂點、連線方式、畫筆、畫刷、線型、顏色、裁切等。路徑是相對好處理的部分,通常可以指令一對一的方式發送給印表機。不過麻煩在於優化。

點陣圖:主要提供的信息包括:位置、大小、圖片位深、疊加方式(ROP)、裁切等。一些特殊的圖片還會有透過度、色彩表之類的信息。點陣圖是驅動處理比較麻煩的部分,也是很見驅動功力的部分。在翻譯的過程中,驅動也會將圖片進行壓縮再發送給印表機。

在描畫之外,還有對於頁面的控制部分。如用紙大小、分辯率、複製份數等等。這部分是將印表機設置部分的設置翻譯為對應的頁面控制指令。同樣也是渲染的一部分。

流程式控制制/事件觸發

PrinterDriver小組最終完成裝修。OS將裝修後的新房交給客戶。其實在PrinterDriver小組工作時OS始終監控著整個流程,並在每個關鍵的時間點、比如水電完成,瓷磚完成等的時候通知PrinterDriver小組。

這部分的內容不多,合在一起說一下。流程式控制制其實就是第一層里說過的GDI和XPS的架構,這裡不重複了。著重說一下事件觸發。列印驅動相關的事件主要有三類:文檔事件(DocumentEvent)、印表機事件(PrinterEvent)和驅動事件(DriverEvent)。

印表機事件(PrinterEvent):

一般認為是以印表機圖標為單位觸發的事件,時間點包括印表機圖標初始化、印表機屬性變更等。一個印表機驅動可以對應多個印表機圖標。

驅動事件(DriverEvent):

包括驅動安裝、刪除、更新三個時間點的事件。

文檔事件(DocumentEvent):

這裡的事件比較多,主要包括創建DC(CreateDC)、刪除DC(DeleteDC)、開始文檔印刷(StartDoc)、結束文檔印刷(EndDoc)、開始一頁(StartPage)、結束一頁(EndPage)、特別處理(Escape)等。

OS在對應的時間點觸發事件,並調用驅動的介面處理事件。

---------------結語---------------

前面三層基本將OS對印表機控制的方方面面都介紹過了。

從第四層開始應該要詳細到每個介面的具體實現細節了,太過龐大而且意義不大。另外沒有提到的還有一些比如印表機共享、印表機服務、印表機組策略、印表機布署等等,這些內容與本問題關係不大,所以沒有提到。

最後想補充一點,以上回答都是基於Windows2000-現在的V3架構的印表機驅動而言。從Windows8開始MS提出並支持了全新的V4架構的印表機驅動,與V3有相當大的不同。大概三五年後,等V4普及的時候上面的內容就全過時了。

---------------更新日誌------------

8/24 初版 第一層完

8/25 增加第二層 Spooler與DC說明

8/25 晚 增加設備無關性說明。第二層完

8/27 增加第三層 應答與設置

9/6 增加渲染與印表機語言

9/16 增加流程式控制制與事件觸發,第三層完。回答完畢。

2016/04/16 過了半年,今天怱然多了幾十贊。沒想到還有這麼多人看,受寵若驚。回頭看了下以前的文字說明有點多,補了些圖片,方便理解。


樓上@盧雨的答案還是不錯的。我曾經在Windows Print組待過一段時間,所以對此也有一定了解,不過我掌握的知識可能是挺古老的了,那時候vista還在做到一半呢。

就補充幾點:

1. EMF文件裡面基本上就是一堆GDI的指令,比如在哪裡畫個圓啊,在哪裡輸出一段文字啊。對於app而言這個用GDI在屏幕上繪製是基本一樣的。有些app能直接打開emf文件查看,就像看圖形文件一樣。

2. Windows裡面有個Print Spooler服務用來管理印表機並接收列印服務,存儲列印任務的這個文件就稱為spool file。

3. 大部分公司用的印表機,都是先安裝在一台稱作print server的伺服器上,然後這些印表機被類似文件共享一樣地共享出來。客戶端電腦列印的時候其實是通過RPC連到那個伺服器上的spooler服務的,在這種情況下,實際的spool file會被傳到伺服器上。

4. 大部分中高端印表機都是通過網路連接到列印伺服器上的,其實印表機就是一個tcp server。這種情況下spooler會通過tcp協議連到印表機上發出真正的列印指令。當然也有其他連接,比如usb,或者更老的並口。Spooler裡面有不同的port monitor來處理不同的連接方式。

5. 印表機是不認EMF文件的,所以spool file需要被render一次,變成印表機能認識的指令,才能真正被送往印表機。以前基本上就兩種指令類型:postscript和PCL,前者是Adobe的,後者是HP的。印表機驅動的一個重要任務就是這個render過程。


DOS 也算操作系統對吧,不過估計有點跑題。

我小的時候還在用DOS的時候,向印表機(愛普生1600K)輸出數據非常簡單

copy con lpt1

解釋一下,con 是鍵盤的設備號, lpt1是第一個並口的設備號,這個動作在XP系統下仍然能夠執行。

那個年代,並口就是通信協議,你吧ASCII碼發過去,印表機就給你把東西打出來,自帶折行動作。

後來,USB的時代,早的時候,印表機連固件都不帶,需要驅動程序向 USB 端點發送一個firmware 然後印表機才能啟動。這個時候就需要設備驅動程序具體該幹啥幹啥了。

至於你題目說的東西,我猜你是說https://msdn.microsoft.com/zh-cn/library/sk61115a.aspx?f=255MSPPError=-2147217396


即使同一系統下,不同應用程序,列印驅動,不同介面不同語言不同廠商的印表機,需要被管理,需要共存,需要互動,統一的介面一定是有的。

如果是驅動這行,建議讀yuan feng的《Windows圖形編程》,把v3列印過程詮釋的比較清楚的,當年也是拜讀了此文後受益匪淺(記得作者當年也是在微軟)。


推薦閱讀:

如何挑選印表機耗材性價比最高?
求推薦一款性價比高的激光印表機?
家用印表機如何選購?如何根據列印量選購噴墨印表機還是激光印表機?
如何選擇列印文件和照片的家用印表機?學生黨 主要用來列印照片和論文。?
噴墨印表機的連供系統是不是可以使用類似醫院打點滴的方式?

TAG:印表機 | 操作系統 |