第十四章 Windows CE 驱动程序
Platform Builder是Windows CE驱动程序开发环境中,一个很重要的工具。Platform Builder和Microsoft Visual Studio .Net被设备制造商或者B S P(硬件平台支持套件)厂商、外围装置和芯片开发商、应用程序开发人员所广泛使用。本章将讲述Platform Builder的使用方法,以及如何在此环境下的设计驱动程序。
使用Platform Builder开发驱动程序所带来的好处:
• 可以使用核心侦错器来侦错驱动程序。
• 可以开发并测试任何类型的驱动程序。
• 可以使用Windows CE Test Kit (CETK)来测试驱动程序。
• 可以把一个驱动程序包装或汇出成 .msi档案。
• 可以把一个驱动程序包装或者汇出成 .cab档案提供给目标装置的最终使用者。
• 可以验证驱动程序并把它发送给Microsoft公司。
• 可以在多种硬件平台上使用并测试驱动程序。
相对的,使用Platform Builder 开发驱动程序的不足之处:
• 许多芯片开发商对此开发环境并不熟悉。
• 不能侦错基于Microsoft Pocket PC的装置。
• 硬件的使用或者测试平台支持可能会很昂贵或难以找到。
• 很难把一个驱动程序移到Visual Studio里去。
14.1 Platform Builder 4.0介绍
微软的Platform Builder 4.0是一个基于Windows CE操作系统的嵌入式平台的整合开发环境( I D E )。Platform Builder同时附带了设计、建立、产生、测试并侦错一个Windows CE 平台所必须的开发工具。I D E提供了一个单独整合工作区,在工作区内可以在平台和工程中进行开发。下面的列表给出了在Platform Builder 中包括的开发工具:
• 新建平台指导手册帮助建立一个新的平台。
• BSP指导手册使建立一个B S P更简单有效。
• 目录列出了可以用来制定平台的操作系统特性。
• 自动的相依性检查,保证支持一个平台的所需特性都被包括到操作系统映像档案中。
• 汇出指导手册来向其它Platform Builder使用者汇出使用者的目录特性。
• 基本的配置提供了建立使用者操作系统的开始点。
• CETK提供了一个驱动程序测试的整合。
• 核心侦错器侦错一个使用者操作系统映像档,并且提供了测试这个映像文件性能的信息。
• 应用程序侦错器用于在一个使用者操作系统映像文件上侦错一个应用程序。
• 远程工具可用于执行在Windows CE 目标装置上的各种侦错和信息收集任务。
• 仿真器通过模拟硬件加速并简化了开发工作,它允许使用者在开发工作区上测试平台和应用程序。
• 汇出S D K指导手册使使用者可以汇出一个制定的软件开发套件。
14.2 使用Platform Builder整合开发环境
开发者可以将自己编写的驱动程序整合到Platform Builder的I D E环境中,这使得开发者在以后建立的任何项目中都可以包含这个驱动程序。将驱动程序整合到I D E目录下有两种方法,开发者可以使用Platform Builder提供的B S P指导手册建立一个新的c e c档案,或者对一个现成的c e c档案进行修改。第一种方法相对简单,也是通常所推荐的。
B S P指导手册将会引导开发者建立一个基于Windows CE的B S P,重要步骤解释如下。首先自动为所建立的B S P产生一个c e c档案,将B S P相关档案拷贝到相应的目录并将c e c档案汇入到该目录下。指导手册会根据使用者的选择,决定所建立的B S P中包含哪些Windows CE系统核心特性、装置驱动程序以及与微处理器相关的驱动程序,使用者建立的系统可以小到仅仅支持少数几种硬件装置,也可以大到包含完整的系统特性和全面的硬件支持。下面是详细的步骤:
1) 打开B S P指导手册,选择将驱动程序加入到已有的B S P,可以在列表中选择任意一个B S P作为加入的对象。如果不想将驱动程序和某一个特定的B S P系结,可以选择任一个B S P稍后再更新这个信息。
2) 为所产生的c e c档案命名并指定一个路径。
3) 加入一个驱动程序并填写相关的信息。需要注意的是,其中必须正确地指定驱动程序的类型,I D E将从类型信息中判断驱动程序对应的装置的某些特性。填写完成并确定选择后所加入的驱动程序就会出现在这个B S P中。
4) 重复步骤3,直至加入完所有的驱动程序。
完成上述步骤以后,指导手册就会为B S P含入所有已加入驱动程序的c e c档案。需要指出的是,按照上述方法建立的驱动程序,通常与某个实体的B S P和处理器相关联,如果需要建立一个关联度小一些的c e c档案,可以使用Platform Builder提供的C E C编辑器对产生的c e c档案进行修改,下面是详细步骤:
1) 从C E C编辑器中打开一个c e c档案,指定一个驱动程序,选择A d v a n c e d标签。
2) 在这个页面中可以改变B S P目录的选择,以表示该驱动程序支持哪些B S P,如果需要支持任意一种B S P,则须将B S P目录置为空。
3) 同样的,使用者可以指定驱动程序支持哪些操作系统特性和C P U特性,通常推荐选择支持所有或者全都不支持。
4) 最后将所有的变更储存到档案即可。
14.3 驱动程序的汇出
将驱动程序汇出为m s i档案,步骤如下:
1) 在P l a t f o r m菜单下选择汇出指导手册。
2) 在对应目录下选择需要汇出的驱动程序。
3) 填写一些描述信息并为汇出的档案命名。
4) 点选完成按钮即可产生m s i档案,该档案可以直接在目标环境下安装使用。
将驱动程序汇出为c a b档案,步骤如下:
1) 在B u i l d菜单中选择Open Build Release Directory。
2) 在驱动程序的目录下利用C a b a r c . e x e工具进行操作。
C a b a r c . e x e是一个用于建立c a b档案的命令列程序,这个程序位于/ c e p b / b i n目录下,下面是一些常见用法。
• cabarc n MyDriver. c a b—以M y D r i v e r为名称为指定的驱动程序产生一个c a b安装檔。
• cabarc l MyDriver. c a b—查看名为M y D r i v e r的c a b档案中内容。
具体操作可以使用cabarc /?来查看帮助。
14.4 驱动程序的程序代码结构
大多数基于Windows CE的装置驱动程序都采取多层次结构。在大多数情况下,这种结构可以使得使用者能仅针对需要修改的层次进行二次开发。通常上层结构被称为模型装置驱动层,而底层的程序代码被称为平台相关的驱动程序层。
开发装置驱动程序可以采取以下三种方式:
• 使用Platform Builder提供的驱动程序模型,这是推荐的方法。
• 借助层次模型开发驱动程序。通常Windows CE的装置驱动程序有多层结构和单层结构两种。
• 从另一个系统中移植驱动程序,一般不建议开发者使用这种方法。
多层结构正如上面介绍的那样分为模型装置驱动( M D D)和平台相关的驱动( P D D)两层。模型装置驱动也可以称为类别驱动程序,一个模型装置驱动包含了一类别驱动程序所共有的程序代码,而与实体装置相关的部分则包含在P D D层中,操作系统核心通过M D D来呼叫某个特定的P D D例程,从而达到控制和存取硬件信息的目的。使用多层结构开发驱动程序时,开发者可以重复使用由微软提供的M D D层程序代码,其它的工作仅仅是编写与特定硬件相关的程序代码。而在进行驱动程序移植时M D D代码也可以直接拷贝到目标机器上,仅对P D D层进行修改即可。
多层结构并不适合所有的驱动程序开发,因为在程序代码层之间必须编写额外的呼叫操作,在一些对性能要求较高的应用中单层结构可能更符合要求。单层结构中包含了所有必须的操作,既有中断服务例程也有与实体平台相关的程序代码,这种结构避免了层次之间的互相呼叫所造成的系统资源的浪费。也就是说单层结构与多层结构相比更为简单和高效,但缺点是移植性和可读性相对较差。实际上无论采取何种结构,开发者都可以借助Windows CE所提供的模型程序代码来完成一些最基本和最常用的工作。
14.5 驱动程序的侦错
侦错Windows CE的驱动程序有很多方法,例如使用Debug Zones或者核心侦错工具,也可以使用Microsoft Visual Studio.NET提供的侦错工具。Debug Zones和Windows CE控制台S h e l l侦错工具( C e s h . e x e)提供了以下一些功能。
• 可以通过宏开关控制驱动程序的讯息输出,这使得开发者能在不中断操作系统运行的情况下进行侦错,同时能追踪驱动程序的运行状态而不干扰系统核心的运行。
• 提供处理程序,执行绪等级别的侦错状态信息。
• 可以动态控制I D E环境的配置。
核心侦错工具采取的侦错方式是关闭所有的硬件中断,将整个操作系统核心挂上,这种方式可以提供更强大的功能:
• 开发者进行单步侦错,并进入操作系统核心程序代码进行分析。
• 可以取得stack trace信息。
有关Microsoft Visual Studio.NET提供的侦错工具的信息可以参看I D E提供的辅助文件 (document)。另外还有一些不同的侦错方法,如P C卡扩展工具、逻辑分析器、J TAG Ports、DEBUG LED等,它们针对不同的侦错环境和具体的侦错目标发挥各自的优势。
14.6 驱动程序实例
在Windows CE中,装置驱动程序本质上就是一些动态链接库,这些D L L向核心提供一些接口函式,这样装置管理模块就可以通过这些函式与具体的硬件装置进行通信。在这里将要介绍一种简单的装置驱动类型—串流装置驱动,这类驱动程序接收驱动程序管理器的命令,应用程序也可以通过档案操作的系统呼叫来控制这些串流装置,驱动程序的工作是将这些命令转换为实体的装置操作,并封装了这一过程中传递的所有信息。
表 14.1是串流驱动程序需要实作的D L L接口。
表 14.1 串流驱动程序需要实作的D L L接口函式名描述
X X X _ C l o s e 在驱动程序关闭时由应用程序通过C l o s e h a n d l e函式呼叫
X X X _ D e i n i t 装置管理器在卸载一个实体装置时呼叫
X X X _ I n i t 装置管理器在初始化一个实体装置时呼叫
X X X _ I O C o n t r o l 在发送命令时由应用程序通过D e v i c e I o C o n t r o l函式呼叫
X X X _ O p e n 在打开一个装置驱动程序时由应用程序通过C r e a t e F i l e函式呼叫
X X X _ P o w e r D o w n 在系统挂上之前呼叫
X X X _ P o w e r U p 在系统重新启动之前呼叫
X X X _ R e a d 在一个装置驱动程序处于执行中状态时由应用程序通过R e a d F i l e函式呼叫
X X X _ S e e k 对装置的数据指针进行操作,由应用程序通过S e t F i l e P o i n t e r函式呼叫
X X X _ Wr i t e 在一个装置驱动程序处于执行中状态时由应用程序通过Wr i t e F i l e函式呼叫
其中电源管理的部分是可选的,在实际开发中,接口名称中的X X X三个字母由装置驱动的前缀所代替,同时在宣告这些接口时,需要在函式原型的前面加上_ _ d e c l s p e c ( d l l e x p o r t ),表示这些函数是这个D L L汇出的界面函式,例如:
_declspec(dllexport) DWORD GEN_Init (DWORD dwContext);
如果是用C + +开发,则需要加上extern “C”。
下面看一个串流装置驱动程序的例子,这个例子建立了一个名为G e n D r i v e r的驱动程序,尽管并不是针对某个特定的硬件来实作,但它实作了1 0个接口函式,任何Windows CE系统都可以加载这个驱动程序,并通过函式指标来呼叫它们。可以在系统注册表的[ H K E Y _ L O C A L_ M A C H I N E ] / D r i v e r s / B u i l t i n目录下增加一个主机码,这样当系统启动时会自动加载这个驱动程序。
G e n D r i v e r. h的程序代码限于篇幅在这里没有列出,其中主要是D L L的接口宣告和一些重要的结构定义,如装置上下文等。G e n D r i v e r. c的程序代码主要是接口的实作以及一个测试例程。
程序代码 14.1
G e n D r i v e r . c
#include <windows.h> // For all that Windows stuff
#include "GenDriver.h" // Local program includes
HINSTANCE hInst; // DLL instance handle
long g_DllCnt = 0; // Global DLL reference count
// Debug zone structure
#ifdef DEBUG
DBGPARAM dpCurSettings = {
TEXT("GenDriver"), {
T E X T ( " E r r o r s " ) , T E X T ( " W a r n i n g s " ) , T E X T ( " F u n c t i o n s " ) ,
TEXT("Init"),TEXT("Driver Calls"),TEXT("Undefined"),
TEXT("Undefined"),TEXT("Undefined"), TEXT("Undefined"),
T E X T ( " U n d e f i n e d " ) , T E X T ( " U n d e f i n e d " ) , T E X T ( " U n d e f i n e d " ) ,
T E X T ( " U n d e f i n e d " ) , T E X T ( " U n d e f i n e d " ) , T E X T ( " U n d e f i n e d " ) ,
TEXT("Undefined") },
0 x 0 0 0 3
} ;
#endif //DEBUG
// DllMain - DLL initialization entry point
/ /
BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD dwReason,
LPVOID lpvReserved) {
hInst = hinstDLL;
switch (dwReason) {
case DLL_PROCESS_ATTACH:
D E B U G R E G I S T E R ( h i n s t D L L ) ;
DEBUGMSG(ZONE_INIT, (DTAG TEXT("DLL_PROCESS_ATTACH/r/n")));
b r e a k ;
case DLL_PROCESS_DETACH:
DEBUGMSG(ZONE_INIT, (DTAG TEXT("DLL_PROCESS_DETACH/r/n")));
b r e a k ;
}
DEBUGMSG (ZONE_FUNC, (DTAG TEXT("DllMain--/r/n")));
return TRUE;
}
// GEN_Init - Driver initialization function
/ /
DWORD GEN_Init (DWORD dwContext) {
PDRVCONTEXT pDrv;
DEBUGMSG (ZONE_INIT | ZONE_FUNC | ZONE_DRVCALLS,
(DTAG TEXT("GEN_Init++ dwContex:%x/r/n"), dwContext));
// Allocate a drive instance structure.
pDrv = (PDRVCONTEXT)LocalAlloc (LPTR, sizeof (DRVCONTEXT));
if (!pDrv) {
DEBUGMSG (ZONE_INIT | ZONE_FUNC | ZONE_ERROR,
(DTAG TEXT("GEN_Init failure. Out of memory/r/n")));
return 0; // Fail init
}
// Initialize structure.
memset ((PBYTE) pDrv, 0, sizeof (DRVCONTEXT));
pDrv->dwSize = sizeof (DRVCONTEXT);
// Read registry to determine the size of the disk.
GetConfigData (dwContext);
DEBUGMSG (ZONE_FUNC, (DTAG TEXT("GEN_Init-- pDrv: %x/r/n"), pDrv));
return (DWORD)pDrv;
}
// GEN_Deinit - Driver de-initialization function
/ /
BOOL GEN_Deinit (DWORD dwContext) {
PDRVCONTEXT pDrv = (PDRVCONTEXT) dwContext;
DEBUGMSG (ZONE_FUNC | ZONE_DRVCALLS,
(DTAG TEXT("GEN_Deinit++ dwContex:%x/r/n"), dwContext));
if (pDrv && (pDrv->dwSize == sizeof (DRVCONTEXT))) {
// Free the driver state buffer.
LocalFree ((PBYTE)pDrv);
}
DEBUGMSG (ZONE_FUNC, (DTAG TEXT("GEN_Deinit--/r/n")));
return TRUE;
}
// GEN_Open - Called when driver opened
/ /
DWORD GEN_Open (DWORD dwContext, DWORD dwAccess, DWORD dwShare) {
PDRVCONTEXT pDrv = (PDRVCONTEXT) dwContext;
DEBUGMSG (ZONE_FUNC | ZONE_DRVCALLS,
(DTAG TEXT("GEN_Open++ dwContext: %x/r/n"), dwContext));
// Verify that the context handle is valid.
if (pDrv && (pDrv->dwSize != sizeof (DRVCONTEXT))) {
DEBUGMSG (ZONE_ERROR, (DTAG TEXT("GEN_Open failed/r/n")));
return 0;
}
// Count the number of opens.
InterlockedIncrement (&pDrv->nNumOpens);
DEBUGMSG (ZONE_FUNC, (DTAG TEXT("GEN_Open--/r/n")));
return (DWORD)pDrv;
}
// GEN_Close - Called when driver closed
/ /
BOOL GEN_Close (DWORD dwOpen) {
PDRVCONTEXT pDrv = (PDRVCONTEXT) dwOpen;
DEBUGMSG (ZONE_FUNC | ZONE_DRVCALLS,
(DTAG TEXT("GEN_Close++ dwOpen: %x/r/n"), dwOpen));
if (pDrv && (pDrv->dwSize != sizeof (DRVCONTEXT))) {
DEBUGMSG (ZONE_FUNC | ZONE_ERROR,
(DTAG TEXT("GEN_Close failed/r/n")));
return 0;
}
if (pDrv->nNumOpens)
p D r v - > n N u m O p e n s - - ;
DEBUGMSG (ZONE_FUNC, (DTAG TEXT("GEN_Close--/r/n")));
return TRUE;
}
// GEN_Read - Called when driver read
/ /
DWORD GEN_Read (DWORD dwOpen, LPVOID pBuffer, DWORD dwCount) {
DWORD dwBytesRead = 0;
DEBUGMSG (ZONE_FUNC | ZONE_DRVCALLS,
(DTAG TEXT("GEN_Read++ dwOpen: %x/r/n"), dwOpen));
DEBUGMSG (ZONE_FUNC, (DTAG TEXT("GEN_Read--/r/n")));
return dwBytesRead;
}
// GEN_Write - Called when driver written
/ /
DWORD GEN_Write (DWORD dwOpen, LPVOID pBuffer, DWORD dwCount) {
DWORD dwBytesWritten = 0;
DEBUGMSG (ZONE_FUNC | ZONE_DRVCALLS,
(DTAG TEXT("GEN_Write++ dwOpen: %x/r/n"), dwOpen));
DEBUGMSG (ZONE_FUNC, (DTAG TEXT("GEN_Write--/r/n")));
return dwBytesWritten;
}
// GEN_Seek - Called when SetFilePtr called
/ /
DWORD GEN_Seek (DWORD dwOpen, long lDelta, WORD wType) {
DEBUGMSG (ZONE_FUNC | ZONE_DRVCALLS,
(DTAG TEXT("GEN_Seek++ dwOpen: %x/r/n"), dwOpen));
DEBUGMSG (ZONE_FUNC | ZONE_DRVCALLS, (DTAG TEXT("GEN_Seek--/r/n")));
return 0;
}
// GEN_IOControl - Called when DeviceIOControl called
/ /
DWORD GEN_IOControl (DWORD dwOpen, DWORD dwCode, PBYTE pIn, DWORD dwIn,
PBYTE pOut, DWORD dwOut, DWORD *pdwBytesWritten) {
PDRVCONTEXT pState;
DWORD err = ERROR_INVALID_PARAMETER;
DEBUGMSG (ZONE_FUNC | ZONE_DRVCALLS,
(DTAG TEXT("GEN_IOControl++ dwOpen: %x dwCode: %x/r/n"),
dwOpen, dwCode));
pState = (PDRVCONTEXT) dwOpen;
switch (dwCode) {
// Insert IOCTL codes here.
d e f a u l t :
DEBUGMSG (ZONE_ERROR,
(DTAG TEXT("GEN_IOControl: unknown code/r/n")));
return FALSE;
}
SetLastError (err);
DEBUGMSG (ZONE_FUNC, (DTAG TEXT("GEN_IOControl--/r/n")));
return TRUE;
}
// GEN_PowerDown - Called when system suspends
/ /
void GEN_PowerDown (DWORD dwContext) {
r e t u r n ;
}
// GEN_PowerUp - Called when resumes
void GEN_PowerUp (DWORD dwContext) {
r e t u r n ;
}
// GetConfigData - Get the configuration data from the registry.
/ /
DWORD GetConfigData (DWORD dwContext) {
int nLen, status;
DWORD dwLen, dwType, dwSize = 0;
HKEY hKey;
TCHAR szKeyName[256], szPrefix[8];
DEBUGMSG (ZONE_FUNC, (DTAG TEXT("GetConfigData++/r/n")));
nLen = 0;
// If ptr < 65K, its a value, not a pointer.
if (dwContext < 0x10000) {
return -1;
} else {
__try {
nLen = lstrlen ((LPTSTR)dwContext);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
nLen = 0;
}
}
if (!nLen) {
DEBUGMSG (ZONE_ERROR,
(DTAG TEXT("No active registry key ptr/r/n")));
return -2;
}
// Open the Active key for the driver.
status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPTSTR) dwContext,
0, 0, &hKey);
if (status == ERROR_SUCCESS) {
// Read the key value.
dwLen = sizeof(szKeyName);
status = RegQueryValueEx (hKey, TEXT("Key"), NULL, &dwType,
(PBYTE)szKeyName, &dwLen);
R e g C l o s e K e y ( h K e y ) ;
if (status == ERROR_SUCCESS)
status = RegOpenKeyEx (HKEY_LOCAL_MACHINE, (LPTSTR)
dwContext, 0, 0, &hKey);
if (status == ERROR_SUCCESS) {
// This driver doesn't need any data from the key, so as
// an example, it just reads the Prefix value, which
// identifies the three-char prefix (GEN) of this driver.
dwLen = sizeof (szPrefix);
status = RegQueryValueEx (hKey, TEXT("Prefix"), NULL,
&dwType, (PBYTE)szPrefix, &dwLen);
R e g C l o s e K e y ( h K e y ) ;
} else
DEBUGMSG (ZONE_ERROR,
(TEXT("Error opening driver key/r/n")));
} else
DEBUGMSG (ZONE_ERROR, (TEXT("Error opening Active key/r/n")));
DEBUGMSG (ZONE_FUNC, (DTAG TEXT("GetConfigData--/r/n")));
return 0;
}
在上面的程序代码中可以看出,各个接口函式并没有实作任何实际的操作,只是输出了一些侦错信息,从中不难了解这些接口函式在何时何地被呼叫,结尾部分的G e t C o n f i g D a t a例程主要是测试传递给I n i t函式的参数指向的是一个字符串还是简单的数值。
这个例子为开发者提供了一个很好的开发串流装置驱动的模板,实际上只需要改写这些函式体中的实体实作就可以很方便地编写出适合各种实体装置的驱动程序。