基于DM6446的Windows CE顯示驅動設計實現
WINCE 的顯示驅動程序如圖3 所示,由DDI(Display Device Interface) 和HAL(Hardware Abstraction Layer)兩部分組成。
HAL 主要為DirectDraw 服務,只需要在驅動中向GDI 導出HALinit()即可,因此本文研究的重點是DDI 部分,即通常的顯示驅動部分。由于在顯示中存在大量硬件無關操作,顯示驅動通常采用分層結構,采用分層結構有助于降低代碼復雜度提高代碼效率,其中MDD 層實現缺省的繪圖功能,由微軟提供的圖形原語引擎模塊(GPE , GraphicsPrimitive Engine)組成,如果要支持Directdraw,則要使用DDGPE模塊;而PDD層與硬件具體相關,則是顯示驅動的主要內容,一般由OEM 廠商或獨立硬件商實現。
WINCE 上層程序通過一組(約20 多個)顯示驅動接口函數同顯示驅動打交道,因此顯示設備驅動程序必須實現這些顯示驅動接口函數,GDI 通過調用這組函數初始化顯示設備驅動程序和將圖形輸出到顯示設備上。由于采用分層結構,顯示驅動由MDD 層負責對上層的GWES模塊提供函數接口,但是這些函數并不是直接提供出來的,實際上只是通過一個DrvEnabLEDriver( )函數來完成的。作為DDI部分的一個導出函數,DrvEnableDriver會在GDI初始化時被調用。
DrvEnableDriver 在MDD 層中沒有實現,所以需要在PDD層中定義,主要代碼如下:
BOOL APIENTRY DrvEnableDriver
(ULONG engineVersion,ULONG cj,DRVENABLEDATA *data,PENGCALLBACKS engineCallbacks)
{
BOOL fOk = FALSE;
if(gszBaseInstance[0] != 0)
{
fOk =
GPEEnableDriver(engineVersion, cj, data,engineCallbacks);
}
return fOk;
}
這里GPEEnableDriver 是微軟預先編寫的一個MDD層函數。該函數位于源文件ddi_if.cpp里, 因此我們只需簡單調用就可以了。
GPEEnableDriver 函數通過執行語句memcpy(pded, pDrvFn, cj) 將一個預先定義好的DRVENABLEDATA 結構體變量pDrvFn 的地址傳給一個上層結構體指針pded.而在結構體變量pDrvFn 中預先已包含了20 多個底層顯示驅動函數指針,這樣GWES 就可以通過這些指針操縱底層顯示硬件了。例如應用程序想創建一個到圖形設備的連接時可以通過GWES.exe 調用CreateDC(),而該函數會調用DrvEnablePDEV()函數,當應用程序需要從顯示設備上斷開時則會調用DeleteDC() , DeleteDC() 則會調用DrvDisablePDEV() .DrvEnablePDEV() 和DrvDisablePDEV()就屬于這20 多個被GWES 調用的底層顯示驅動函數。
以上這些底層顯示驅動函數大部分跟硬件密切相關,因此需要進一步調用PDD層函數。由于不同的顯示硬件特點都不盡相同,因此勢必造成PDD層暴露給MDD層的接口函數各不相同,這樣勢必會增加代碼的復雜性。為此微軟設計了一個GPE類,一個GPE類實例代表一個顯示設備硬件,其所有數據成員都對應于一個顯示設備的屬性數據,并設計了多個成員函數用以操縱這些數據成員。考慮到硬件的多樣性,GPE 類的有些函數并為全部實現,或為空函數或者虛函數,需要其子類實現或者覆蓋。因此不能直接定義GPE類型的變量,只能以先構造GPE類為父類的繼承類,然后才能定義實例。
MDD 層的底層顯示驅動函數通過實例化一個GPE 繼承類的實例就可以直接調用PDD 層代碼了,這一般是通過SafeGetGPE 函數來實現的。
SafeGetGPE 由微軟設計實現,位于MDD 層的ddi_if.cpp,一般無須改動。在SafeGetGPE 函數中調用了GetGPE 函數,這個函數MDD 層沒有,需要我們在PDD 層實現。GetGPE 函數可以簡單實現如下:
這里代碼利用了C++的多態性和繼承性。在C++中父類或更上一級的類的指針可以引用繼承類中相同的變量,并且對數據成員和成員函數的引用以繼承類的實現或定義優先。這樣在MDD 中使用指針gGPE 所指向的數據或函數時得到的都是類DM6446VPBE 的成員變量和成員函數。由此可以看出GetGPE 函數是顯示驅動中聯系MDD和PDD 的橋梁,通過它MDD 可以直接調用PDD的代碼。
3.3 GPE繼承類的實現
通過上面的分析可以看出,WINCE 的顯示驅動主要部分在于PDD 層,而PDD 層除了向MDD導出一些接口函數外如DrvEnableDriver,其余主要是構建一個GPE 或是DDGPE 的子類(如果要實現DirectDraw)。由于DDGPE 的父類是GPE,因此無論是DDGPE 還是GPE 的子類差別并不大。
構建一個GPE 的子類其實就是實現一個有具體數據和函數并且具體準確的反映了特定顯示設備硬件屬性的GPE 類的子類,并通過該子類去實例化一個對象。
一個GPE 子類通常需要重載GPE 類中的同名函數和實現GPE中的虛函數以及子類獨有的一些函數如初始化構造函數[3].子類構造函數主要是初始化硬件和子類成員變量,譬如視頻處理時鐘寄存器設置,OSD Window 的大小和坐標,VENC 的輸出模式,以及子類的成員變量如顯示寬度m_nScreenWidth 和顯示高度m_nScreenHeight 等等。子類要GPE 類中的函數包括GPE 的空函數和虛函數,這些函數實際上就是MDD 調用PDD 層驅動中需要實現的函數,主要函數包括:SetMode(),用于設置一個顯示設備能夠支持的顯示模式;GetPhysicalVideoMemory(),用于獲取顯示設備內存的系統基地址和內存大小; 以及AllocSurface() SetPointerShape()BltComplete() SetPalette()等。這些函數具體可以參考微軟提供的驅動示例代碼,它們位于Public CommonOAKDriversDisplay 目錄下[ 1].除了這些函數外PDD 還需實現一個MDD 層函數DrvGetMask,但比較簡單,只需要定義一個全局數組gBitMasks,該數組內容是代表RGB 的所占的位域,與具體的顯示硬件有關。
3.4 驅動程序與應用程序的通信
不同于其他流式驅動可以由應用程序直接調用,顯示驅動由操作系統調用,應用程序不能直接訪問。具體來說,應用程序不是通過CreateFile等這些文件系統API接口來訪問,而是通過GDI接口間接訪問。對于GDI調用而言,對應的后臺服務進程是GWES.exe,然后GWES.exe再進一步調用MDD和PDD函數,即WINCE底層顯示驅動。例如如果要畫一個矩形,則可以調用SetRect、GetDC和FillRect等函數在圖形界面上面進行顯示,而要在圖形界面上輸出一段文字只需調用DrawText函數就可以了,至于顯示驅動調用就可以交給GDI就可以了。
4 結束語
本文闡述和分析了DM6446 顯示硬件原理和Windows CE驅動模型,剖析了顯示驅動程序的工作原理和顯示工作流程。本文的創新點在于完整的闡述了WINCE顯示驅動程序在DM6446上的設計實現,而以往WINCE 的顯示驅動都是基于LCD,因此本文對編寫同類驅動程序的開發人員將有一定的參考價值。WINCE啟動運行后,圖形界面運行穩定,并可支持Windows CE下的應用軟件運行,表明驅動程序設計良好。
評論