一個簡單的顯示驅動

理論:


本篇我們將介紹下如何寫一個簡單的顯示驅動。顯示驅動是一種特殊類型的驅動,必須要滿足一個框架,它不像我們前面講的那些驅動。


示例程序演示瞭如何寫一個簡單的顯示驅動,這個驅動無需關聯任何硬件。它能實現圖形到內存,然後由一個應用程序來顯示這些圖形。

顯示驅動的體系結構

首先介紹的是windows NT下顯示驅動的體系結構。在這裏要特別說明的是windows vista使用了一種新的顯示驅動模型,叫做LDDM.它是vista最新桌面窗口管理器的核心部分。就兼容方面,vista仍然可以與老的窗口管理器一起協作支持老的顯示驅動。


顯示驅動模型包括兩部分,迷你小端口驅動和顯示驅動。迷你小端口驅動加載到系統空間,負責枚舉設備和管理設備資源。顯示驅動加載到會話空間,負責實現實際的GDI圖形調用。顯示驅動完全控制怎樣劃線或者怎樣實現透明效果。


下面的圖表顯示了windows顯示驅動的體系結構。

 

迷你小端口驅動

迷你小端口驅動加載到系統空間,負責管理顯示設備資源和枚舉設備。這個驅動使用其它的驅動(VIDEOPRT.SYS)作爲它的框架。你的驅動會使用VIDEOPRT.SYS導出的API. 很奇怪驅動可以導出API吧?驅動使用pe格式,也有導入表和導出表。你可以從你的驅動中導出api,並且允許其他驅動鏈接使用它們,就像調用一個dll一樣。實際上你使用的這些API連接着內核和其他驅動。


在這裏要注意連接內核模式驅動和用戶模式的驅動有些不同。如果一個驅動連接另一個驅動,而那個驅動當前沒有加載到內存。那麼那個驅動將會被加載進內存,然而那個驅動的DriverEntry不會被調用。DriverEntry自身不會被調用,只有使用ZwLoadDriver加載驅動,系統加載驅動或者使用我們前面展示過的服務api記載驅動纔會調用DriverEntry。任何時候,你都可以從一個驅動中導出API,然後在另一個驅動中使用這些api. 在內核中沒有類似於"GetProcAddress"的api,你需要自己寫一個。

你的迷你小端口驅動會調用VideoPrt.SYS導出的api。VideoPrt.SYS驅動做了很少的事情,其中之一就是實現了通用代碼。所以視頻驅動的開發者就不需要重新再寫同樣的代碼。這個代碼包括在win32子系統(win32k.sys)和你的迷你小端口驅動之間進行視頻設備枚舉。VideoPrt.SYS還創建了一個顯示用的設備對象,當你調用這個初始化例程時,他會使你的驅動對象入口點指向VideoPrt.SYS的。

所有的VideoPrt.SYS API都是"VideoPort"開頭的。你第一個調用的API應該是"VideoPortInitialize".如果你注意的話,可以看到開頭的兩個參數就是傳遞給你的DriverEntry例程的,他們被稱作"Context1"和"Context2",彷彿你的視頻迷你小端口驅動是特別的。不要被這些迷惑,第一參數"Context1"實際上就是你的驅動對象。一旦你傳遞你的驅動對象給VideoPortInitialize,所有你的驅動入口點都會指向VideoPrt.Sys的。除此之外,你還要傳遞給VIDEO_HW_INITIALIZATION_DATA結構不同的函數指針,VideoPrt.SYS在需要的時候會用到這些。

這意味着你不要直接處理在視頻迷你小端口驅動中的IRP. VideoPrt.SYS會處理他們。你需要處理的是"VRP"(視頻請求包),本質上講它其實是一種簡化的用不同數據結構的IRP版本。你需要簡單的返回,不需要像IRP那樣對這個數據結構進行處理。


在迷你小端口驅動中,你除了使用"VideoPort"開頭的api外,由於它是系統層的驅動,你還可以使用內核的api.

由於我們沒有任何硬件,因此我們的迷你小端口驅動會比較簡單,下面的代碼演示瞭如何編寫視頻迷你小端口驅動DriverEntry。

/**********************************************************************

*

* DriverEntry

*

*    This is the entry point for this video miniport driver

*

**********************************************************************/

ULONG DriverEntry(PVOID pContext1, PVOID pContext2)

{

    VIDEO_HW_INITIALIZATION_DATA hwInitData;

    VP_STATUS vpStatus;

  /*

     * The Video Miniport is "technically" restricted to calling

     * "Video*" APIs.

     * There is a driver that encapsulates this driver by setting your

     * driver's entry points to locations in itself. It will then

     * handle your IRP's for you and determine which of the entry

     * points (provided below) into your driver that should be called.

     * This driver however does run in the context of system memory

     * unlike the GDI component.

     */

    VideoPortZeroMemory(&hwInitData,

                                sizeof(VIDEO_HW_INITIALIZATION_DATA));

    hwInitData.HwInitDataSize = sizeof(VIDEO_HW_INITIALIZATION_DATA);

    hwInitData.HwFindAdapter             = FakeGfxCard_FindAdapter;

    hwInitData.HwInitialize             = FakeGfxCard_Initialize;

    hwInitData.HwStartIO                 = FakeGfxCard_StartIO;

    hwInitData.HwResetHw                 = FakeGfxCard_ResetHW;

    hwInitData.HwInterrupt               = FakeGfxCard_VidInterrupt;

    hwInitData.HwGetPowerState          = FakeGfxCard_GetPowerState;

    hwInitData.HwSetPowerState           = FakeGfxCard_SetPowerState;

    hwInitData.HwGetVideoChildDescriptor =

                                      FakeGfxCard_GetChildDescriptor;

    vpStatus = VideoPortInitialize(pContext1,

                                      pContext2, &hwInitData, NULL);

    return vpStatus;

}

將VideoPortInitialize的返回值作爲DriverEntry函數的返回值,返回調用者。

 

在你直接簡單的傳遞DriverObject給VideoPrt.SYS之前,你還需要填充一個數據結構,這個數據結構中包含了你驅動中的入口點,這些入口點會被VideoPrt.SYS驅動調用來執行不同的動作。"HwStartIO"是指你可以處理IOCTLs,在顯示驅動和視頻迷你小端口驅動之間,你可以使用IOCTLs。顯示驅動只需要簡單調用"EngDeviceIoControl",迷你小端口驅動中的HwStartIO就會處理IOCTL。代碼如下:


/*#pragma alloc_text(PAGE, FakeGfxCard_ResetHW)       Cannot be Paged*/
/*#pragma alloc_text(PAGE, FakeGfxCard_VidInterrupt) Cannot be Paged*/
#pragma alloc_text(PAGE, FakeGfxCard_GetPowerState)
#pragma alloc_text(PAGE, FakeGfxCard_SetPowerState)
#pragma alloc_text(PAGE, FakeGfxCard_GetChildDescriptor)
#pragma alloc_text(PAGE, FakeGfxCard_FindAdapter)
#pragma alloc_text(PAGE, FakeGfxCard_Initialize)
#pragma alloc_text(PAGE, FakeGfxCard_StartIO)
/**********************************************************************
* FakeGfxCard_ResetHW

*     This routine would reset the hardware when a soft reboot is
*     performed. Returning FALSE from this routine would force
*     the HAL to perform an INT 10h and set Mode 3 (Text).
*     We are not real hardware so we will just return TRUE so the HAL
*     does nothing.
**********************************************************************/
BOOLEAN FakeGfxCard_ResetHW(PVOID HwDeviceExtension,
                                 ULONG Columns, ULONG Rows)
{
   return TRUE;
}

/*********************************************************************
* FakeGfxCard_VidInterrupt

*     Checks if it's adapter generated an interrupt and dismisses it
*     or returns FALSE if it did not.

**********************************************************************/
BOOLEAN FakeGfxCard_VidInterrupt(PVOID HwDeviceExtension)
{
   return FALSE;
}

/*********************************************************************
* FakeGfxCard_GetPowerState
*         Queries if the device can support the requested power state.
**********************************************************************/
VP_STATUS FakeGfxCard_GetPowerState(PVOID HwDeviceExtension,
          ULONG HwId, PVIDEO_POWER_MANAGEMENT VideoPowerControl)
{            
   return NO_ERROR;
}

/**********************************************************************
* FakeGfxCard_SetPowerState
* Sets the power state.
**********************************************************************/

VP_STATUS FakeGfxCard_SetPowerState(PVOID HwDeviceExtension,
          ULONG HwId, PVIDEO_POWER_MANAGEMENT VideoPowerControl)
{
return NO_ERROR;

}

/**********************************************************************

*

* FakeGfxCard_GetChildDescriptor

*

*        Returns an identifer for any child device supported

*        by the miniport.

*

**********************************************************************/

ULONG FakeGfxCard_GetChildDescriptor (PVOID HwDeviceExtension,

      PVIDEO_CHILD_ENUM_INFO ChildEnumInfo, PVIDEO_CHILD_TYPE pChildType,

      PVOID pChildDescriptor, PULONG pUId, PULONG pUnused)

{

   return ERROR_NO_MORE_DEVICES;

}

/**********************************************************************

*

* FakeGfxCard_FindAdapter

*

*        This function performs initialization specific to devices

*        maintained by this miniport driver.

*

**********************************************************************/

VP_STATUS FakeGfxCard_FindAdapter(PVOID HwDeviceExtension,

            PVOID HwContext, PWSTR ArgumentString,

            PVIDEO_PORT_CONFIG_INFO ConfigInfo, PUCHAR Again)

{

   return NO_ERROR;

}

/**********************************************************************

*

* FakeGfxCard_Initialize

*

*      This initializes the device.

*

**********************************************************************/

BOOLEAN FakeGfxCard_Initialize(PVOID HwDeviceExtension)

{

   return TRUE;

}

/**********************************************************************

*

* FakeGfxCard_StartIO

*

*      This routine executes requests on behalf of the GDI Driver

*      and the system. The GDI driver is allowed to issue IOCTLs

*      which would then be sent to this routine to be performed

*      on it's behalf.

*

*      We can add our own proprietary IOCTLs here to be processed

*      from the GDI driver.

*

**********************************************************************/

BOOLEAN FakeGfxCard_StartIO(PVOID HwDeviceExtension,

                PVIDEO_REQUEST_PACKET RequestPacket)

{

   RequestPacket->StatusBlock->Status      = 0;

   RequestPacket->StatusBlock->Information = 0;

   return TRUE;

}


由於我沒有任何硬件,對於迷你小端口驅動,簡單實現就足夠了。如果我需要在系統上訪問或者執行一個操作,顯示驅動靠它自己有限的API函數集是不能做到的。我會用到的唯一的API就是"StartIO"。可是在這個實現中,我們不需要做什麼。記住,迷你小端口驅動的主要用途就是枚舉硬件設備/資源和管理他們。如果你沒有什麼硬件設備/資源,那麼爲了讓驅動模型高興,除了必需的外刪除其他的。

顯示驅動


顯示驅動連接在WIN32K.SYS,僅僅允許調用Eng* APIs,這些api實際上可以在內核模式和用戶模式下找到。Nt4之前,顯示驅動是在用戶模式。無論如何顯示驅動和打印驅動都使用同樣的api函數集。遵循這個API函數集,只需要很少的工作就能移植顯示驅動到用戶模式或者內核模式。


顯示驅動不是加載到系統內存空間而是在會話空間。會話空間是內核中類似於進程隔離的。如圖地址從OxA0000000開始擴展到0xA2FFFFFF是會話空間。在用戶模式下,進程有他們自己的虛擬內存地址空間,在內核模式下,會話有他們自己的虛擬內存地址空間。在內核內存中系統空間對所有會話是全局的。

 


一個會話是一個登錄用戶的實例,它包含了自己的窗口管理,桌面,shell和應用程序。最顯著的就是windows xp的快速用戶切換,即你可以在一臺機器上登錄多個用戶。每個用戶實際上有一個獨有的會話和一個獨有的內核內存範圍,我們稱爲會話空間。

當設計一個視頻驅動的時候,你會遇到一個問題。就是說,如果你的迷你小端口驅動可能處理的那個內存超出了當前會話上下文,你不能簡單地傳遞任意的內存到你的迷你小端口驅動。這裏有個例子傳遞這個內存給系統進程中的另外的線程來處理。

如果系統進程沒有關聯你的會話,你將會訪問一個不同的內存區域,而不是你想的那樣。當這個發生時,你會得到"A driver has not been correctly ported to Terminal Services"這樣的藍屏信息。


顯示驅動不象到目前爲止我們接觸過的驅動那樣。但它仍然是pe格式。它不象迷你小端口驅動那樣,是一個連接到一個不同框架的正規的內核驅動。它不能直接通過連接到內核而使用內核api,並且由於前面說明的原因也不能使用他們。如果你的api傳遞的內存超出了會話空間,那麼你就會藍屏,除非你保證你只是傳遞了系統內存。這是另一個爲什麼只能使用Eng* API函數集的原因。然而,你可以從迷你小端口驅動請求一個函數指針表,沒有什麼東西會阻止你這麼做。


任何時候,顯示驅動同普通驅動相比表現的更像一個dll,實際上它也是被看作是一個dll.這個驅動框架被綁定在WIN32K.SYS上,WIN32K.SYS實現了windows管理器和GDI.這個驅動編譯使用"-entry:DrvEnableDriver@12 /SUBSYSTEM:NATIVE",其中DrvEnableDriver作爲顯示驅動的入口點。

DrvEnableDriver

DrvEnableDriver是顯示驅動的初始入口點。它與DriverEntry沒有任何關係。這個API入口參數有一個DRVENABLEDATA結構,這個結構需要使用一個指向驅動入口的函數表來填充。這個表包含了一系列函數指針的索引值和函數指針。其中索引值表示了函數類型,就像"INDEX_DrvCompletePDEV"是指它對應的函數指針是指向驅動中一個DrvCompletePDEV型的處理函數。其中一些API是可選的,但有一些是必須的。


這個入口點爲了返回你的函數序列負責。你也可以在這裏做一些你需要的初始化動作。下面的代碼來自於本文的顯示驅動例子。


/*

* Display Drivers provide a list of function entry points for specific GDI

* tasks. These are identified by providing a pre-defined "INDEX" value (pre-

* defined

* by microsoft) followed by the function entry point. There are levels of

* flexibility

* on which ones you are REQUIRED and which ones are technically OPTIONAL.

*

*/

                                           

DRVFN g_DrvFunctions[] =

{

    {   INDEX_DrvAssertMode,         (PFN) GdiExample_DrvAssertMode         },

    {   INDEX_DrvCompletePDEV,       (PFN) GdiExample_DrvCompletePDEV       },

    {   INDEX_DrvCreateDeviceBitmap, (PFN) GdiExample_DrvCreateDeviceBitmap },

    {   INDEX_DrvDeleteDeviceBitmap, (PFN) GdiExample_DrvDeleteDeviceBitmap },

    {   INDEX_DrvDestroyFont,        (PFN) GdiExample_DrvDestroyFont        },

    {   INDEX_DrvDisablePDEV,        (PFN) GdiExample_DrvDisablePDEV        },

    {   INDEX_DrvDisableDriver,      (PFN) GdiExample_DrvDisableDriver      },

    {   INDEX_DrvDisableSurface,     (PFN) GdiExample_DrvDisableSurface     },

    {   INDEX_DrvSaveScreenBits,     (PFN) GdiExample_DrvSaveScreenBits     },

    {   INDEX_DrvEnablePDEV,         (PFN) GdiExample_DrvEnablePDEV         },

    {   INDEX_DrvEnableSurface,      (PFN) GdiExample_DrvEnableSurface      },

    {   INDEX_DrvEscape,             (PFN) GdiExample_DrvEscape             },

    {   INDEX_DrvGetModes,           (PFN) GdiExample_DrvGetModes           },

    {   INDEX_DrvMovePointer,        (PFN) GdiExample_DrvMovePointer        },

    {   INDEX_DrvNotify,             (PFN) GdiExample_DrvNotify             },

// {   INDEX_DrvRealizeBrush,     (PFN) GdiExample_DrvRealizeBrush       },

    {   INDEX_DrvResetPDEV,          (PFN) GdiExample_DrvResetPDEV          },

    {   INDEX_DrvSetPalette,         (PFN) GdiExample_DrvSetPalette         },

    {   INDEX_DrvSetPointerShape,    (PFN) GdiExample_DrvSetPointerShape    },

    {   INDEX_DrvStretchBlt,         (PFN) GdiExample_DrvStretchBlt         },

    {   INDEX_DrvSynchronizeSurface, (PFN) GdiExample_DrvSynchronizeSurface },

    {   INDEX_DrvAlphaBlend,         (PFN) GdiExample_DrvAlphaBlend         },

    {   INDEX_DrvBitBlt,             (PFN) GdiExample_DrvBitBlt             },

    {   INDEX_DrvCopyBits,           (PFN) GdiExample_DrvCopyBits           },

    {   INDEX_DrvFillPath,           (PFN) GdiExample_DrvFillPath           },

    {   INDEX_DrvGradientFill,       (PFN) GdiExample_DrvGradientFill       },

    {   INDEX_DrvLineTo,             (PFN) GdiExample_DrvLineTo             },

    {   INDEX_DrvStrokePath,         (PFN) GdiExample_DrvStrokePath         },

    {   INDEX_DrvTextOut,            (PFN) GdiExample_DrvTextOut            },

    {   INDEX_DrvTransparentBlt,     (PFN) GdiExample_DrvTransparentBlt     },

};

                                           

ULONG g_ulNumberOfFunctions = sizeof(g_DrvFunctions) / sizeof(DRVFN);

/*********************************************************************

* DrvEnableDriver

*

*   This is the initial driver entry point. This is the "DriverEntry"

*   equivlent for Display and Printer drivers. This function must

*   return a function table that represents all the supported entry

*   points into this driver.

*

*********************************************************************/

BOOL DrvEnableDriver(ULONG ulEngineVersion,

     ULONG ulDataSize, DRVENABLEDATA *pDrvEnableData)

{

    BOOL bDriverEnabled = FALSE;

    /*

     * We only want to support versions > NT 4

     *

     */

    if(HIWORD(ulEngineVersion) >= 0x3 &&

       ulDataSize >= sizeof(DRVENABLEDATA))

    {

       pDrvEnableData->iDriverVersion = DDI_DRIVER_VERSION;

       pDrvEnableData->pdrvfn         = g_DrvFunctions;

       pDrvEnableData->c              = g_ulNumberOfFunctions;

       bDriverEnabled                 = TRUE;

    }

    return bDriverEnabled;             

}

DrvDisableDriver

當顯示驅動卸載時調用這個函數。在這個函數中,你可以執行一些必要的清理工作,清理你在DrvEnableDriver調用中創建的東西。下面的代碼來自於例子。

/*********************************************************************

* GdiExample_DrvDisableDriver

*

*   This function is used to notify the driver when the driver is

*   getting ready to be unloaded.

*

*********************************************************************/

VOID GdiExample_DrvDisableDriver(VOID)

{

    /*

     * No Clean up To Do

     */

}

DrvGetModes

這個API在驅動被加載和使能後調用。他用來查詢設備支持的顯示模式。這些顯示模式式就是在顯示屬性對話框的設置欄中的使用的。這些顯示模式可以被緩存,所以操作系統不需要考慮它們的變化和改變。操作系統相信它是個靜態的列表。儘管這個api可能被以不同方式和不同次數來調用。但在極大程度上,他不應該被認爲是動態的。


通常這個api被調用兩次,第一次是詢問需要多大的空間來存儲這些顯示模式。第二次是使用正確的尺寸調用。下面的代碼片段來自於驅動示例,這個驅動只支持640 * 480 * 32。

/*********************************************************************

* GdiExample_DrvGetModes

*

*    This API is used to enumerate display modes.

*

*    This driver only supports 640x480x32

*

*********************************************************************/

ULONG GdiExample_DrvGetModes(HANDLE hDriver,

                               ULONG cjSize, DEVMODEW *pdm)

{

   ULONG ulBytesWritten = 0, ulBytesNeeded = sizeof(DEVMODEW);

   ULONG ulReturnValue;

   ENGDEBUGPRINT(0, "GdiExample_DrvGetModes/r/n", NULL);

   if(pdm == NULL)

   {

       ulReturnValue = ulBytesNeeded;

   }

   else

   {

      

       ulBytesWritten = sizeof(DEVMODEW);

       memset(pdm, 0, sizeof(DEVMODEW));

       memcpy(pdm->dmDeviceName, DLL_NAME, sizeof(DLL_NAME));

       pdm->dmSpecVersion   = DM_SPECVERSION;

       pdm->dmDriverVersion = DM_SPECVERSION;

       pdm->dmDriverExtra      = 0;

       pdm->dmSize             = sizeof(DEVMODEW);

       pdm->dmBitsPerPel       = 32;

       pdm->dmPelsWidth        = 640;

       pdm->dmPelsHeight       = 480;

       pdm->dmDisplayFrequency = 75;

       pdm->dmDisplayFlags     = 0;

      

       pdm->dmPanningWidth     = pdm->dmPelsWidth;

       pdm->dmPanningHeight    = pdm->dmPelsHeight;

       pdm->dmFields           = DM_BITSPERPEL | DM_PELSWIDTH |

                                 DM_PELSHEIGHT | DM_DISPLAYFLAGS |

                                 DM_DISPLAYFREQUENCY;

       ulReturnValue = ulBytesWritten;

   }

   return ulReturnValue;

}

 

DrvEnablePDEV

一旦選定了一種顯示模式,這個api就會被調用,它會允許驅動使能”物理設備”。這個API的用途是允許顯示驅動創建自己私有的上下文,這個上下文將會被傳遞給其他的顯示入口點。創建私有上下文的原因是一個顯示驅動可以管理多個顯示設備,使用它可以區分開各個顯示設備。這個api的返回值是一個上下文指針或者是顯示設備的實例。

選中的顯示設置通過DEVMODE參數傳遞給這個API,然而例子驅動中沒有使用這個方法,而是硬編碼設置爲800 * 600 * 32的顯示模式。

這個API除創建一個實例結構外,還必須初始化GDIINFO和DEVINFO這兩個數據結構。這些參數是重要的,如果你填寫支持某一特徵,但實際上並不支持。其副作用就是圖形扭曲,甚至是藍屏。其餘兩個參數我會在後面提及,他們是hDev和hDriver,參數hDriver實際上是顯示驅動的DEVICE_OBJECT,可以用於API,例如:EngDeviceIoControl用它來和迷你小端口驅動通訊。

hDev是GDI的句柄,然而由於hDev是在被創建進程中,實際上這個參數沒有用。在保存和使用這個句柄之前,建議等待,直到調用了DrvCompletePDEV。下面的代碼來自例子驅動的DrvEnablePDEV。

/*********************************************************************

* GdiExample_DrvEnablePDEV

*

*   This function will provide a description of the Physical Device.

*   The data returned is a user defined data context to be used as a

*   handle for this display device.

*

*   The hDriver is a handle to the miniport driver associated with

*   this display device. This handle can be used to communicate to

*   the miniport through APIs to send things like IOCTLs.

*

*********************************************************************/

DHPDEV GdiExample_DrvEnablePDEV(DEVMODEW *pdm, PWSTR pwszLogAddr,

       ULONG cPat, HSURF *phsurfPatterns, ULONG cjCaps,

       GDIINFO *pGdiInfo, ULONG cjDevInfo, DEVINFO *pDevInfo,

       HDEV hdev, PWSTR pwszDeviceName, HANDLE hDriver)

{

    PDEVICE_DATA pDeviceData = NULL;

  

    ENGDEBUGPRINT(0, "GdiExample_DrvEnablePDEV Enter /r/n", NULL);

    pDeviceData = (PDEVICE_DATA) EngAllocMem(0,

                               sizeof(DEVICE_DATA), FAKE_GFX_TAG);

    if(pDeviceData)

    {

        memset(pDeviceData, 0, sizeof(DEVICE_DATA));

        memset(pGdiInfo, 0, cjCaps);

        memset(pDevInfo, 0, cjDevInfo);

        {

            pGdiInfo->ulVersion    = 0x5000;

            pGdiInfo->ulTechnology = DT_RASDISPLAY;

            pGdiInfo->ulHorzSize   = 0;

            pGdiInfo->ulVertSize   = 0;

            pGdiInfo->ulHorzRes        = RESOLUTION_X;

            pGdiInfo->ulVertRes        = RESOLUTION_Y;

            pGdiInfo->ulPanningHorzRes = 0;

            pGdiInfo->ulPanningVertRes = 0;

            pGdiInfo->cBitsPixel       = 8;

            pGdiInfo->cPlanes          = 4;

            pGdiInfo->ulNumColors      = 20;

            pGdiInfo->ulVRefresh       = 1;     

            pGdiInfo->ulBltAlignment   = 1;   

            pGdiInfo->ulLogPixelsX = 96;

            pGdiInfo->ulLogPixelsY = 96;

            pGdiInfo->flTextCaps   = TC_RA_ABLE;

            pGdiInfo->flRaster     = 0;

            pGdiInfo->ulDACRed     = 8;

            pGdiInfo->ulDACGreen   = 8;

            pGdiInfo->ulDACBlue    = 8;

            pGdiInfo->ulAspectX    = 0x24;

            pGdiInfo->ulNumPalReg = 256;

            pGdiInfo->ulAspectY    = 0x24;

            pGdiInfo->ulAspectXY   = 0x33;

            pGdiInfo->xStyleStep   = 1;     

            pGdiInfo->yStyleStep   = 1;

            pGdiInfo->denStyleStep = 3;

            pGdiInfo->ptlPhysOffset.x = 0;

            pGdiInfo->ptlPhysOffset.y = 0;

            pGdiInfo->szlPhysSize.cx = 0;

            pGdiInfo->szlPhysSize.cy = 0;

            pGdiInfo->ciDevice.Red.x = 6700;

            pGdiInfo->ciDevice.Red.y = 3300;

            pGdiInfo->ciDevice.Red.Y = 0;

            pGdiInfo->ciDevice.Green.x = 2100;

            pGdiInfo->ciDevice.Green.y = 7100;

            pGdiInfo->ciDevice.Green.Y = 0;

            pGdiInfo->ciDevice.Blue.x = 1400;

            pGdiInfo->ciDevice.Blue.y = 800;

            pGdiInfo->ciDevice.Blue.Y = 0;

            pGdiInfo->ciDevice.AlignmentWhite.x = 3127;

            pGdiInfo->ciDevice.AlignmentWhite.y = 3290;

            pGdiInfo->ciDevice.AlignmentWhite.Y = 0;

            pGdiInfo->ciDevice.RedGamma = 20000;

            pGdiInfo->ciDevice.GreenGamma = 20000;

            pGdiInfo->ciDevice.BlueGamma = 20000;

            pGdiInfo->ciDevice.Cyan.x = 1750;

            pGdiInfo->ciDevice.Cyan.y = 3950;

            pGdiInfo->ciDevice.Cyan.Y = 0;

            pGdiInfo->ciDevice.Magenta.x = 4050;

            pGdiInfo->ciDevice.Magenta.y = 2050;

            pGdiInfo->ciDevice.Magenta.Y = 0;

            pGdiInfo->ciDevice.Yellow.x = 4400;

            pGdiInfo->ciDevice.Yellow.y = 5200;

            pGdiInfo->ciDevice.Yellow.Y = 0;

            pGdiInfo->ciDevice.MagentaInCyanDye = 0;

            pGdiInfo->ciDevice.YellowInCyanDye = 0;

            pGdiInfo->ciDevice.CyanInMagentaDye = 0;

            pGdiInfo->ciDevice.YellowInMagentaDye = 0;

            pGdiInfo->ciDevice.CyanInYellowDye = 0;

            pGdiInfo->ciDevice.MagentaInYellowDye = 0;

            pGdiInfo->ulDevicePelsDPI = 0;

            pGdiInfo->ulPrimaryOrder = PRIMARY_ORDER_CBA;

            pGdiInfo->ulHTPatternSize = HT_PATSIZE_4x4_M;

            pGdiInfo->flHTFlags = HT_FLAG_ADDITIVE_PRIMS;

            pGdiInfo->ulHTOutputFormat = HT_FORMAT_32BPP;

          

            *pDevInfo = gDevInfoFrameBuffer;

            pDevInfo->iDitherFormat = BMF_32BPP;

        }

        pDeviceData->pVideoMemory = EngMapFile(L"//??//c://video.dat",

              RESOLUTION_X*RESOLUTION_Y*4, &pDeviceData->pMappedFile);

        pDeviceData->hDriver = hDriver;

        pDevInfo->hpalDefault = EngCreatePalette(PAL_BITFIELDS,

               0, NULL, 0xFF0000, 0xFF00, 0xFF);

    }

    ENGDEBUGPRINT(0, "GdiExample_DrvEnablePDEV Exit /r/n", NULL);

    return (DHPDEV)pDeviceData;

}

 

DrvCompletePDEV

DrvCompletePDEV的調用,是用在DrvEnablePDEV調用之後,用來通知顯示驅動,設備對象現在已經完成。僅有的參數是一個私有的在DrvEnablePDEV調用中創建的數據結構和一個指向GDI設備的完成句柄。除非你有更多的初始化要做,否則通常的做法是保存這個gdi句柄,然後繼續。下面的代碼來自於例子驅動。

 

/*********************************************************************

* GdiExample_DrvCompletePDEV

*

*   This is called to complete the process of enabling the device.

*  

*

*********************************************************************/

void GdiExample_DrvCompletePDEV(DHPDEV dhpdev, HDEV hdev)

{

    PDEVICE_DATA pDeviceData = (PDEVICE_DATA)dhpdev;

    ENGDEBUGPRINT(0, "GdiExample_DrvCompletePDEV Enter /r/n", NULL);

    pDeviceData->hdev = hdev;

    ENGDEBUGPRINT(0, "GdiExample_DrvCompletePDEV Exit /r/n", NULL);

}

DrvDisablePDEV

當這個PDEV不再需要的時候,調用這個api消滅。如果還有一個表面使能,這個API調用之前,需要調用DrvDisableSurface。這個api我們實現得很簡單,僅僅是執行了一些清理工作,清理了在創建私有的PDEV結構期間創建的東西。

/*********************************************************************

* GdiExample_DrvDisablePDEV

*

*   This is called to disable the PDEV we created.

*  

*

*********************************************************************/

void GdiExample_DrvDisablePDEV(DHPDEV dhpdev)

{

    PDEVICE_DATA pDeviceData = (PDEVICE_DATA)dhpdev;

    UINT dwBytesReturned = 0;

    ENGDEBUGPRINT(0, "GdiExample_DrvDisablePDEV/r/n", NULL);

    if(pDeviceData->pMappedFile)

    {

       EngUnmapFile(pDeviceData->pMappedFile);

    }

    EngFreeMem(dhpdev);

}

DrvEnableSurface

這個api是用在PDEV完成以後,用來請求顯示驅動創建一個表面。當創建一個表面的時候,注意你可以有兩種選擇。你可以創建一個由顯示驅動管理的表面,也可以創建一個由GDI管理的表面。下面的代碼選擇一個選項來管理它自己的設備表面。

整個的用途就是定義有一個繪畫表面,GDI也能在上面繪製。顯示驅動有他們自己的設備表面,因此通常需要管理自己的表面。在做這個事情時,它需要以某種方式來描述這個表面,以使GDI能夠理解,並能夠在上面繪製。這意味着起始地址甚至是顯示程度的定義,作爲顯示驅動對所有的顯示模式一般都不能有線性緩衝。在我們這個例子中,我們使用我們創建的內存映像文件來作爲我們的視頻內存。

/*********************************************************************

* GdiExample_DrvEnableSurface

*

* This API is used to enable the physical device surface.

*

* You have two choices here.

*    

*     1. Driver Manages it's own surface

*          EngCreateDeviceSurface - Create the handle

*          EngModifySurface - Let GDI Know about the object.

*

*     2. GDI Manages the surface

*          EngCreateBitmap - Create a handle in a format that

*                            GDI Understands

*          EngAssociateSurface - Let GDI Know about the object.

*

*

*********************************************************************/

HSURF GdiExample_DrvEnableSurface(DHPDEV dhpdev)

{

    HSURF      hsurf;

    SIZEL       sizl;

    PDEVICE_DATA pDeviceData = (PDEVICE_DATA)dhpdev;

   

    ENGDEBUGPRINT(0, "GdiExample_DrvEnableSurface/r/n", NULL);

    pDeviceData->pDeviceSurface =

      (PDEVICE_SURFACE)EngAllocMem(FL_ZERO_MEMORY,

      sizeof(DEVICE_SURFACE), FAKE_GFX_TAG);

    sizl.cx = 800;

    sizl.cy = 600;

    hsurf = (HSURF)EngCreateDeviceSurface(

            (DHSURF)pDeviceData->pDeviceSurface, sizl, BMF_32BPP);

   

    EngModifySurface(hsurf, pDeviceData->hdev,

           HOOK_FILLPATH | HOOK_STROKEPATH | HOOK_LINETO |

           HOOK_TEXTOUT | HOOK_BITBLT | HOOK_COPYBITS,

           MS_NOTSYSTEMMEMORY, (DHSURF)pDeviceData->pDeviceSurface,

           pDeviceData->pVideoMemory, 800*4, NULL);


   

    return(hsurf);

}

DrvDisableSurface

調用這個api用來註銷在DrvEnableSurface調用中創建的繪畫表面。這個在註銷PDEV之前調用。下面的代碼來自於示例程序。

/*********************************************************************

* GdiExample_DrvDisableSurface

*

* This API is called to disable the GDI Surface.

*

*

*********************************************************************/

void GdiExample_DrvDisableSurface(DHPDEV dhpdev)

{

    PDEVICE_DATA pDeviceData = (PDEVICE_DATA)dhpdev;

    ENGDEBUGPRINT(0, "GdiExample_DrvDisableSurface/r/n", NULL);

    EngDeleteSurface(pDeviceData->hsurf);

    pDeviceData->hsurf = NULL;

    EngFreeMem(pDeviceData->pDeviceSurface);

    pDeviceData->pDeviceSurface = NULL;

}

 

先後順序

現在,讓我們清楚地回顧下的這個步驟:

DrvEnableDriver:加載驅動
DrvGetModes:獲取緩衝尺寸,來存放所有支持的顯示模式。
DrvGetModes: 得到顯示模式
DrvEnablePDEV: 通知顯示驅動在DEVMODE數據結構中初始化一個選中的模式,並且返回一個實例句柄。
DrvCompletePDEV: 通知驅動,設備初始化已經完成。  
DrvEnableSurface:得到驅動來提供一個繪畫表面。
<GDI 調用。。。>
DrvDisableSurface: 刪除繪畫表面
DrvDisablePDEV: 刪除實例結構
DrvDisableDriver: 卸載顯示驅動
怎樣進行繪製?

像"BitBlt"這樣的“GDI調用”在你的顯示驅動中實際上使用DrvBitBlt。你可能注意到,我們的驅動自身沒有實現任何繪畫命令。這是因爲我們沒有硬件來加速繪畫特性,所以我決定只是調用WINDOWS提供的,已經在軟件中實現的例程。示例中,DrvBitBlt簡單的轉向EngBitBlt。這些將直接渲染到我們的視頻緩衝,這裏的視頻緩衝,我們使用的是內存映像文件。

你可能對“我怎樣從Drv*的調用中得到我的PDEV或者我的表面對象”產生疑惑。好的,傳遞給這些API的SURFOBJ包含了一個指向表面對象的指針。Dhsurf成員是一個設備創建的提供給SURFOBJ的句柄,用來代表一個設備管理的表面。這個可以通過檢查設置於SURFOBJ 上的STYPE_DEVICE標誌位來決定。

顯示驅動escape codes

前面的設備驅動中,我們知道了使用"DeviceIoControl"可以實現從用戶模式應用與驅動程序之間的通訊。對於顯示驅動,這也是可以的。然而這裏有一點不同,他們不再被叫作"IOCTLs",他們被稱作"Escape Codes"

在用戶模式下,你可以使用兩種方法中的一種發送"Escape Codes"到顯示驅動。第一種是ExtEscape,它可以簡單的發送你提供的數據給驅動。你的顯示驅動隨後將在DrvEscape例程中處理它。

第二種辦法是DrawEscape,它可以在你驅動的DrvDrawEscape中處理。不同的是,DrawEscape允許你提供一個包含你的數據的窗口DC和這個窗口的裁剪區域,提供給你的驅動。這允許你簡單的實現擴展的繪畫命令。你的驅動將通知正確的裁剪區域使這些命令在windows環境中運轉正常。

OpenGL支持

在Windows中,通過使用一個可安裝用戶驅動包(ICD:Installable Client Driver)來實現OpenGL支持,這個概念最初由SGI提出,通過讓製造商完全地實現圖形管道來提高OpenGL的性能。當OpenGL32.DLL被加載時,它會向視頻驅動詢問它的ICD,如果有,就會被加載到進程空間,OpenGL的API就是由ICD提供。ICD完全控制着圖形管道,因而每個製造商和驅動版本都有不同的實現。

通常的做法是緩衝OpenGL命令,然後使用ExtEscape這個api將他們從緩衝刷新到圖形加速卡。ICD包現在是由微軟維護的,如果你想爲它開發,但它不是免費的。


另有一種支持OpenGL辦法是通過小型用戶驅動包(MCD:Mini Client Driver)實現的。這是微軟最早爲提供OpenGL 支持而使用的辦法,類似於ICD,但是MCD位於內核中。據我所知這種辦法非常慢,沒有被任何的驅動製造商使用。

DirectX支持


在XPDM下, GDI驅動通過DrvEnableDirectDraw接口提供了DirectDraw的支持。由微軟公司提供的系統元件實現了DirectX圖形管道的用戶模式部分和一些內核部分。這些API簡單的返回一系列回調接口,內核中的DirectDraw層將用來在硬件中執行特殊的動作。


Direct3D通過DrvGetDirectDrawInfo來完成初始化,在DrvGetDirectDrawInfo 中GDI驅動要求支持Direct3D。提供的回調將被調用幾次來得到恰當的進入驅動的接口,這些接口實現了Direct3D不同的特徵。這些在MSDN中作了描寫。

什麼是鏡像驅動?

鏡像驅動並不像字面意思那樣,你可以加載一個視頻驅動,這個視頻驅動鏡像另一個顯示驅動。他們會收到同他們鏡像的顯示驅動一樣的調用。鏡像驅動不支持DrvGetModes,然而如果你實現它,返回的顯示模式將會被緩存,你不能動態的改變這些模式。儘管我聽說在模式切換上,實現DrvGetModes能幫助加載和卸載顯示驅動,但是我沒有能得到證實。


爲了加載鏡像驅動,這個設備的註冊表鍵需要設置"Attach.ToDesktop"的值爲1。然後你可以在鏡像驅動上使用參數"CDS_UPDATEREGISTRY"調用ChangeDisplaySettingsEx。然後設置你希望切換的顯示模式,再在鏡像驅動上調用一次ChangeDisplaySettingsEx。

鏡像驅動在模式切換時不會完全地卸載。通常如果有繪製表面的引用,驅動將不會卸載。所以,從我的經驗來看,得到鏡像驅動,爲了模式切換,你需要一個應用程序來檢測WM_DISPLAYCHANGE消息。你也可以在加載顯示驅動後,設置"Attach.ToDesktop“爲0。這將有助於你卸載顯示驅動,對於WM_DISPLAYCHANGE,你可以仔細檢查這個卸載鏡像驅動的過程。

如果你希望立即卸載鏡像驅動,而不需要顯示驅動改變,你需要遵循同加載一樣的步驟。設置"Attach.ToDesktop"爲0,然後執行"CDS_UPDATEREGISTRY"。然後不帶參數再調用"ChangeDisplaySettingsEx"一次來強制卸載。儘管這看上去像是再運行一遍,所有的事情都通過引用顯示錶面完成。所以如果有對於顯示錶面顯著的引用,驅動將不會被卸載。DDK中鏡像驅動的例子沒有全部實現這些,有一些缺少部分,正如在加載鏡像驅動後,沒有實現WM_DISPLAYCHANGE,也沒有重新設置"Attach.ToDesktop"爲0。


例子


本篇示例驅動在應用程序和顯示驅動之間簡單共享了一個內存映像文件。顯示驅動寫圖形命令給內存映像文件,應用程序作爲監視器執行,每秒鐘大約刷新自己70次。儘管這不是是非常有效,但這僅僅是一個例子。顯示驅動象一個常規硬件驅動一樣的安裝,看上去類似於ATI或者NVIDIA驅動。

爲了安裝這個示例,你需要使用控制面板中的"Add New Hardware" 。你必須選擇"Hardware is already installed"和"Manually select hardware from a list"。下面的圖片顯示了設備列表,你需要滾動到最後,選擇"Add a new hardware device"

 


然後,你選擇"Have Disk"找這個工程提供的.INF文件。然後,向下滾動到新的列表,找出如下圖所示的"Toby Opferman Sample Video Display"

 


除非你不想安裝這個驅動,不然你會看到下面的對話框,你選擇"Continue Anyway"來安裝。下一步要做的就是使用顯示設置和第三個選項卡激活第二個監視器。運行本篇提供的鏡像程序,你會在應用窗口中看到第二個監視器。

 


 


結論:

本篇展示瞭如何創建一個簡單的顯示驅動來處理GDI命令。本篇提及的顯示驅動體系結構僅僅適用於XPDM,不適用於windows vista中新的LDDM.

 

 

http://hi.baidu.com/combojiang/blog/item/aa6be0f3fcc74255352accd6.html

http://hi.baidu.com/combojiang/blog/category/%C7%FD%B6%AF%BF%AA%B7%A2/index/2

 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/immcss/archive/2008/12/08/3474332.aspx

發佈了14 篇原創文章 · 獲贊 10 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章