显示驱动小节
理论:
本篇我们将介绍下如何写一个简单的显示驱动。显示驱动是一种特殊类型的驱动,必须要满足一个框架,它不像我们前面讲的那些驱动。
示例程序演示了如何写一个简单的显示驱动,这个驱动无需关联任何硬件。它能实现图形到内存,然后由一个应用程序来显示这些图形。
显示驱动的体系结构
首先介绍的是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