作者:ARM-WinCE
好多天上不了优快云了,今天终于上来了。写篇Blog吧!!!这篇Blog介绍WinCE下的Display驱动开发。
在 WinCE 中, Display 驱动由 GWES 模块来管理。 WinCE 提供了两种架构的 Display 驱动模型,可以满足不同的硬件需求。一种是基于 WinCE DDI 的 Display 驱动模型,另一种是基于 DirectDraw 的 Display 驱动模型。下面将对两种架构作简单介绍。
1. Display 驱动模型
WinCE 下的 Display 驱动直接由 GWES 模块管理,它会直接被 GWES 模块管理和调用。 Display 驱动实际上也是分层的,其中包括 GPE 库,该库处理一些默认的绘图,相当于驱动的 MDD 层。用户只需要开发和硬件相关的 PDD 层驱动就可以了。在 WinCE 中,整个架构如图:
如图, Application 为一个应用程序,该程序会调用图形设备接口函数 (GDI) ,而 GDI 函数是由 Coredll.dll 模块导出的。 Coredll.dll 会将函数调用的参数打包,然后触发对另一个进程的本地过程调用 (LPC) ,所有的绘图和开窗口的工作被传给内核中 GWES 模块。 GWES 模块被称为图形,窗口和事件子系统,专门处理图形输出和用户输入等事件及相关的所有交互。 GWES 模块会调用 Display 驱动完成对显示硬件的操作。 Display 驱动由 GPE 和 DDL.dll 组成, GPE 完成基本的默认绘图工作,而 DDI.dll 实际上从 GPE 类上继承而来的,并实现了相关的显示硬件的操作。
2. DirectDraw Display 驱动模型
DirectDraw
提供了独立于硬件的直接访问显示设备的能力。它可以通过直接访问硬件抽象层
(HAL)
中的一些函数来达到直接操作显示设备的目的,在这个过程中,不再需要图形设备接口
(GDI)
的转换。这种直接的方法可以使图像更加连贯,也提高了显示的性能。
为了实现这样的功能,需要在显示驱动上扩展能够直接访问相关硬件的函数。这些函数会被
DirectDraw
模块调用,并形成
DirectDraw
的硬件抽象层
(DDHAL)
。
DirectDraw
显示驱动架构如图:
如图,
DirectDraw
的真正实现代码都驻留在
gwes.dll
模块中,应用程序只是连接了一个小的客户端,被称为
DDRAW.dll
代理,该代理主要负责用户进程与系统之间的远程
DirectDraw COM
接口连接。这样,用户请求会被传送到内核的
GWES
模块中。
针对
DirectDraw
,
WinCE
提供了一个名为
DirectDraw
的
GPE
库
(DDGPE)
,它是从
GPE
类上面继承而来的。实际上,
DirectDraw
显示驱动是由
DDGPE
和
DDHAL
组成,而
DDGPE
中已经包含了
DDHAL
的功能。用户需要从
DDGPE
类继承并实现相关函数即可。
GWES.dll
模块中包含
GDI
和
DDRAW
两个组件,这两个组件会调用驱动中的
DDGPE
的相关接口完成对硬件的操作。
在上述两种架构中,用户可以根据自己的硬件情况选择相应的架构。第一种架构是基于
GPE
类继承来实现的,第二种架构是基于
DDGPE
类继承来实现的,而第二种架构的
DDGPE
类又是从第一种架构的
GPE
类继承而来。关于两种类的具体定义,可参见
” /WINCE600/PUBLIC/COMMON/OAK/INC”
路径下的
gpe.h
和
ddgpe.h
文件。
本Blog将基于
Display
驱动模型来介绍,
DirectDraw Display
驱动模型不在这里介绍。
WinCE
下的
Display
驱动是基于
GPE
类来实现的,其中
GPE
中已经实现了基本的绘制工作,相当于
MDD
层。用户需要继承该类,并实现里面的其他一些函数,所以用户实现的相当于
PDD
层。
GPE
类是一个抽象类,其中包含很多纯虚函数,只能用于继承。用户在继承了
GPE
类以后,要对
GPE
类中的纯虚函数做相应的实现。开发
Display
驱动的大致步骤如下:
(1)
继承
GPE
类并定义一个该类的实例。
(2)
实现
GetGPE()
函数,把该类的实例返回给上层的
DDI
接口。
(3)
实现
DrvEnableDriver(..)
和
DisplayInit(..)
函数并导出这两个接口。
(4)
实现
GPE
类中的函数。
下面将具体介绍实现的步骤:
1
继承
GPE
类
首先,基于
GPE
类进行继承,如果想在
Display
驱动支持
Rotation
可以从
GPERotate
类上面继承。实际上,在
”gpe.h”
中有如下定义:
typedef
GPE
GPERotate;
可以看出
GPERotate
类就是
GPE
类。在这里,用户从
GPE
类上面继承就可以了,举个例子如下:
class
NewGPE: public
GPE
{
private
:
GPEMode
m_ModeInfo;
DWORD
m_colorDepth;
DWORD
m_VirtualFrameBuffer;
DWORD
m_FrameBufferSize;
BOOL
m_CursorDisabled;
BOOL
m_CursorVisible;
…
public
:
NewGPE(void
);
virtual
INT
NumModes(void
);
virtual
SCODE
SetMode(INT modeId,
HPALETTE *palette);
virtual
INT
InVBlank(void
);
virtual
SCODE
SetPalette(const
PALETTEENTRY *source, USHORT firstEntry, USHORT numEntries);
virtual
SCODE GetModeInfo(GPEMode *pMode, INT modeNumber);
virtual
SCODE
SetPointerShape(GPESurf *mask, GPESurf *colorSurface, INT xHot, INT yHot, INT cX, INT cY);
virtual
SCODE MovePointer(INT xPosition, INT yPosition);
virtual
void
WaitForNotBusy(void
);
virtual
INT
IsBusy(void
);
virtual
void
GetPhysicalVideoMemory(unsigned
long
*physicalMemoryBase, unsigned
long
*videoMemorySize);
virtual
SCODE AllocSurface(GPESurf **surface, INT width, INT height, EGPEFormat format, INT surfaceFlags);
virtual
SCODE
Line(GPELineParms *lineParameters, EGPEPhase phase);
virtual
SCODE
BltPrepare(GPEBltParms *blitParameters);
virtual
SCODE BltComplete(GPEBltParms *blitParameters);
virtual
ULONG GetGraphicsCaps();
virtual
ULONG DrvEscape(
SURFOBJ *pso,
ULONG
iEsc,
ULONG
cjIn,
PVOID
pvIn,
ULONG
cjOut,
PVOID
pvOut);
SCODE
WrappedEmulatedLine (GPELineParms *lineParameters);
void
CursorOn(void
);
void
CursorOff(void
);
#ifdef
ROTATE
void
SetRotateParms();
LONG DynRotate(int
angle);
#endif
};
类
NewGPE
从
GPE
类上面继承,其中包括一些属性,如下:
m_ModeInfo
:
显示模式,结构如下
struct GPEMode {
int modeId;
//
开发者定义的显示模式的索引号
int width;
//
显示宽度
int height;
//
显示高度
int Bpp;
//
显示深度
int frequency;
//
显示频率
EGPEFormat format;
// RGB
格式,各占多少
bit
};
m_colorDepth:
显示深度
m_VirtualFrameBuffer:
FrameBuffer的地址
m_FrameBufferSize:
FrameBuffer的大小
m_CursorDisabled:
光标使能标记
m_CursorVisible:
光标可视标记
用户可以根据需要定义相应的属性,在
NewGPE
类中,需要定义并实现基类中的纯虚函数,上面的
NewGPE
类中已经包含了这些函数的定义,还包括了其他一些函数,将在下面介绍。
2
实现
GetGPE
函数
在定义了
NewGPE
类之后,我们需要实现一个实例,首先定义一个该类的指针:
static
GPE *gGPE = (GPE*)NULL;
然后实现
GetGPE
函数,如下:
GPE *GetGPE(void
)
{
if
(!gGPE)
{
gGPE = new
NewGPE();
}
return
gGPE;
}
在该函数中,创建了一个
NewGPE
的实例。在这个时候
NewGPE
构造函数会被调用,一般我们会在这里面作一些与显示相关的初始化的工作。该函数返回
gGPE
指针给上层接口。
3
实现
DrvEnableDriver
和
DisplayInit
函数
Display
驱动对上层的
GWES
模块提供了
20
多个函数接口,但是这些函数并不是直接提供出来的,实际上只是通过一个
DrvEnableDriver(..)
函数来完成的。该函数在
Display
驱动的
MDD
层中没有实现,所以需要在
PDD
层中定义,如下:
BOOL APIENTRY DrvEnableDriver(ULONG engineVersion, ULONG cj, DRVENABLEDATA *data, PENGCALLBACKS
engineCallbacks)
{
BOOL fOk = FALSE;
// make sure we know where our registry configuration is
if
(gszBaseInstance[0] != 0) {
fOk = GPEEnableDriver(engineVersion, cj, data, engineCallbacks);
}
return
fOk;
}
engineVersion:
DDI版本号,目前为
DDI_DRIVER_VERSION。
cj
:
DRVENABLEDATA
结构的大小。
data
:
指向
DRVENABLEDATA
结构体。
engineCallbacks
:
指向一个回调函数结构体,传入一些
GDI
函数到
Display
驱动中。
其中,
DRVENABLEDATA
结构中包含了
Display
驱动中的设备接口函数的指针,在
DrvEnableDriver
函数中调用了
GPEEnableDriver
函数,该函数会导出
GWES
模块所需的所有
Display
驱动的接口函数。同时
GWES
模块通过第四个参数
engineCallbacks
提供回调函数供
Display
驱动调用。该函数在
”ddi_if”
中定义。
另一个重要的函数是
DisplayInit
函数,它是第一个被执行的
Display
驱动中的函数,该函数主要用于读取注册表中的一些信息并作判断。该函数是可选的,也可以不在驱动中实现它。
BOOL APIENTRY DisplayInit(LPCTSTR pszInstance, DWORD dwNumMonitors)
{
DWORD dwStatus;
HKEY hkDisplay;
BOOL fOk = FALSE;
if(pszInstance != NULL) {
_tcsncpy(gszBaseInstance, pszInstance, dim(gszBaseInstance));
}
// sanity check the path by making sure it exists
dwStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, gszBaseInstance, 0, 0, &hkDisplay);
if(dwStatus == ERROR_SUCCESS) {
RegCloseKey(hkDisplay);
fOk = TRUE;
}
else
{
RETAILMSG(0, (_T("SALCD2: DisplayInit: can't open '%s'/r/n"), gszBaseInstance));
}
return fOk;
}
pszInstance:
注册表中显示驱动的相关注册表值
dwNumMonitors:
支持的
Monitor的个数
在该函数中主要通过读取注册表信息判断显示驱动的存在,如果返回错误,则
GWES
会停止
Display
驱动的初始化。当然,用户可以根据自己的要求灵活掌握,也可以在这里初始化显示设备或做其他的初始化工作。
4
实现
GPE
类中的函数
由于
NewGPE
继承于
GPE
类,所以必须实现
GPE
类中的所有纯虚函数,这些函数实际上就是
PDD
层驱动中需要实现的函数,如下:
4.1
virtual
SCODE GetModeInfo(GPEMode *pMode, INT modeNumber)
获得显示模式。
pMode
:
输出显示模式结构
modeNumber
:
显示模式索引号
4.2
virtual
int NumModes(void)
获得当前驱动支持的显示模式的个数
4.3
virtual
SCODE SetMode(INT modeId, HPALETTE *palette)
设置显示模式。
modeId
:
显示模式索引号
palette
:
调色板指针,指向一个由
EngCreatePalette函数创建的调色板
4.4
virtual
SCODE AllocSurface(GPESurf **surface, INT width, INT height, EGPEFormat format, INT surfaceFlags)
在系统内存中创建一个绘图平面。
surface
:
指向被分配的内存的指针
width
:
宽度
height
:高度
format
:绘图平面格式
surfaceFlags
:标记位,标明在哪分配内存
4.5
virtual
SCODE SetPointerShape(GPESurf *pMask, GPESurf *pColorSurface, INT xHot, INT yHot, INT cX, INT cY);
设置光标形状。
pM
ask
:
指向一个包含光标形状的掩码
pColorSurface
:
指向被光标使用的颜色绘图平面
xHot
:光标热点的
X
坐标
yHot
:光标热点的
Y
坐标
cX
:光标宽度
cY
:光标高度
4.6
virtual
SCODE MovePointer(int x, int y)
移动光标到指定位置或者隐藏光标
x
:
光标移动位置的
x
坐标,若为
-1
表示隐藏光标。
y
:光标移动位置的
y
坐标
4.7
virtual
SCODE BltPrepare(GPEBltParms *blitParameters)
在做位块传输前会先执行该函数,用于确定执行
BLT
的函数
blitParameters
:
指向一个
GPE
的位块传输参数的结构体
4.8
virtual
SCODE BltComplete(GPEBltParms *blitParameters)
该函数用于释放在
BltPrepare
中申请的资源
blitParameters
:
指向一个
GPE
的位块传输参数的结构体
4.9
virtual
SCODE Line(GPELineParms *lineParameters, EGPEPhase phase)
画线函数
lineParameters
:
指向一个
GPE
的
Line
结构体,描述所画的线
phase
:画线所处的阶段,具体描述如下
gpeSingle
:画单根线
gpePrepare
:准备画线
gpeContinue
:画线过程中
gpeComplete
:画线完成
在这里要提一点,有时我们会看到在该函数中调用另一个函数WrappedEmulatedLine(..),
这个函数在
WinCE
的
PUBLIC
目录下的参考
Display
驱动中也可以找到,该函数是一个快速的画线函数,里面采用了
Bresenham
画线算法,通过采用运行速度快的加减和移位运算来完成画线。
4.10
virtual
SCODE SetPalette(const PALETTEENTRY *pSource, USHORT firstEntry, USHORT numEntries)
设置调色板
pSource
:
指向一个调色板入口信息的结构体
firstEntry
:第一个入口
numEntries
:入口的个数
4.11
virtual
int
InVBlank(void)
显示设备是否处于垂直消隐期间
上述函数在
GPE
类中均被定义为纯虚函数,需要在继承类中实现,也就是在我们的驱动程序中实现。这些函数是必须实现的。根据显示的需求,还可以在显示驱动中添加其他的函数,比如对光标的支持,对旋转的支持等,如下:
4.12
void
CursorOn(void)
使能光标显示。
4.13
void
CursorOff(void)
禁止光标显示。
4.14
void
SetRotateParms(void)
设置屏幕翻转参数。
4.15
void
DynRotate(int angel)
支持动态翻转。
angel
:翻转角度
4.16
ULONG *APIENTRY DrvGetMasks(DHPDEV dhpdev)
获得显示模式的
RGB
掩码
dhpdev
:指向掩码信息,比如
RGB565
模式为
(0xf800
,
0x07e0
,
0x001f)
NOTE
:该函数必须在驱动中被实现。
4.17
PowerHandler(BOOL bOff)
电源控制。
bOff
:
TRUE
表示关闭电源,
FALSE
表示打开电源
4.18
ULONG DrvEscape(DHPDEV dhpdev, SURFOBJ* pso, ULONG iEsc, ULONG cjIn, PVOID pvIn, ULONG cjOut, PVOID pvOut)
该函数提供给应用程序的一个直接访问显示驱动的接口,和流设备驱动中的
IoCtls
函数类似。应用程序通过调用
ExtEscape
函数传送操作码和数据给显示设备驱动,
DrvEscape
函数会接收到数据并进行处理,然后返回相应结果给
EstEscape
函数。用户也可以根据需要自己定义相应的操作码。
dhpdev
:设备句柄
pso
:指向一个绘图平面的结构
iEsc
:操作码
cjIn
:输入数据
buffer
的大小
pvIn
:指向输入数据
buffer
cjOut
:输出数据
buffer
的大小
pvOut
:指向输出数据
buffer
大
致就是这些内容,GPE类中的纯虚函数是肯定要实现的,其他的一些函数根据需要来实现。我在写这篇Blog的时候,有些地方有些犹豫,开始觉得自己语文水
平不够,不太会表达,但是也许是因为自己对Display驱动中的一些知识还是理解的不够彻底吧。如果有什么问题,请大家谅解,并请指点。