WinCE 5.0边做边学(7)(8)

发 布 时 间 : 2008-11-19 来 源 : 来自网络 作 者 : 匿名 浏 览 :
上次写过关于CE的中断处理机制以后,一直有人要求能以实际的程序体现出来,但是出于各种因素,一直未能成文。在此表示歉意,此次我们就一起来分析驱动程序中的一种,然后顺便看一下中断在驱动程序中的体现,对WIN32底层的内核对象不熟的话,可要先补一补这方面的知识哦。
还是先从驱动程序说起。CE的驱动程序架构与桌面WINDOWS有很大的不同,因此,桌面WINDOWS下的设备驱动程序无法在CE中使用。通常桌面WINDOWS驱动程序的体现形式是VXD,SYS等,在CE中,则统一体现为DLL,也就是说,只要符合要求的DLL都可以成为CE下的设备驱动程序,前提是只要在注册表中注册过。
从驱动程序的结构上来看,CE下的驱动程序结构有两种,单体结构和分层结构。单体结构即将通用的驱动程序接口和特定的硬件接口在同一层面上实现,以减少两层之间的调用和协调,从而可以提高效率,通常用于非常关键的系统特性上。分层结构则被广泛应用,将同一设备的驱动程序分成两层:平台相关驱动PDD和模型设备驱动MDD。前者和具体的设备绑定在一起,是针对特定设备特定型号的,后者是同一类设备驱动中公用的部分,主要由CE的系统支持提供,通常不需要修改。
在类别上来看,主要的类别有两种,一种是内置的驱动程序,一种是流接口的驱动程序。我平此次先来看内置的驱动程序。
内置的驱动程序是由CE系统定义了接口函数,开发人员只要实现这些函数即可,当然这个实现必然要对应你自己用到的硬件设备。常见的比如键盘驱动,鼠标驱动,显示卡驱动,电池驱动等都属于这一类,拿键盘驱动来说,我们可以通过DEF文件来得到系统规定的接口函数如下:
KeybdDriverInitializeEx
KeybdDriverPowerHandler
KeybdDriverGetInfo
KeybdDriverSetMode
KeybdDriverInitStates
KeybdDriverVKeyToUnicode
KeybdDriverMapVirtualKey

LayoutMgrGetKeyboardType
LayoutMgrGetKeyboardLayout
LayoutMgrGetKeyboardLayoutName
LayoutMgrGetKeyboardLayoutList
LayoutMgrLoadKeyboardLayout
LayoutMgrActivateKeyboardLayout

IL_00000409
PS2_AT_00000409


对于我们开发人员而言,如果你要写键盘驱动,那你的DLL中必须按上述函数的原型导出,系统在使用键盘驱动的时候就是在适当的时机调用这些你提供的函数,从而按你的实现来驱动特定的键盘设备。
我们一起来看一下键盘的驱动,它位于PUBLIC\COMMON\OAK\DRIVERS\KEYBD文件夹下。在这里有很多个源程序文件,它们所包含的头文件位于PUBLIC\COMMON\OAK\INC文件夹下,如果你需要自己实现键盘驱动程序,那就要仔细分析微软提供的这个样例。
在LAYMGR文件夹下的laymgr.cpp程序中我们可以找到上述接口函数中的大部分,这就是系统需要的,因为键盘驱动很复杂,所以这里采用了分层机制,即在laymgr.cpp中并未直接实现这些函数的操作,而是调用了其他源程序文件中的函数功能。在IST文件夹中的keybdist.cpp文件则是上次我们说的IST的实现,里面典型的等待和循环是很值得学会的。真正和硬件密切相关的源程序是在PS@_8042文件夹下的ps2keybd.cpp源程序中,在这里可以找到直接操作硬件的语句,也可以发现IST的安装方法。这里面涉及到了很多event,thread等WIN32的内核对象,有需要了解的请看《Windows高级编程》那本非常厚的经典书籍吧。
其他的驱动程序也是如些,每个文件夹下都有很多源程序文件,只要慢慢分析就会明白其中的流程。有人问说你是怎么知道如何操作硬件的呢?这当然需要硬件手册,对于每一类设备都有它自己的硬件标准,其中会定义描述出不同的地址的功能,不同的指令需要处理的数据结构,有的还会包含中断和时间序列等,对于专用的设备来说,这可是必不可少的,否则你是无法操作硬件的。
除了实现出驱动程序的DLL以外,还需要在注册表中对其进行注册,以方便CE的设备管理机制通过枚举注册表来加载设备驱动程序。对于键盘的注册典型的如下:
[HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\KEYBD]
"DriverName"="kbdmouse.dll"
"SysIntr"=dword:11
"IoBase"=dword:60
"IoLen"=dword:5
"BusNumber"=dword:0
"BusType"=dword:5
"EnableWake"=dword:1
对于其中各项的含义,帮助文档中说得非常详细,请参考。
由于各种内置的驱动程序都有各自的接口定义,所以需要开发人员按需来用。实现上也是比较复杂。下次我们一起来看一下流式驱动,它的接口就相对比较简单了,我们也可以自己安装一个流式驱动来看一下这种驱动程序是如何开发的,下次见吧。

 

在上次的边做边学中,我们了解到了内置的设备驱动程序的架构,因为各种不同类别的内置驱动程序要实现不同的接口,因此在实现起来就非常有针对性,也不利于演示。这次我们继续了解另一种驱动程序——流接口驱动程序的特点及其实现方法。
流接口驱动程序是在应用程序层采用标准的文件操作APIS来调用设备的,最常见的就是串口,我们可以使用CreateFile()函数来打开一个串口,然后通过ReadFile(),WriteFile()函数来读写串口,很显然,串口就是一种流接口的驱动程序。这类驱动程序的特点就是有着统一的接口函数,设备端一般都较简单,以数据提供者为主。
标准的流接口是如下的一组函数,我们先来看一下这组函数的基本外观:
1、HANDLE XXX_Init(LPCTSTR pContext,LPCVOID lpvBusContext);
这个函数是在设备管理程序通过ActiveDeviceEx()函数激活设备时被系统调用的。它的主要功能是初始化驱动程序中用到的资源,对I/O地址空间和内存进行映射等。
2、BOOL XXX_Deinit(DWORD hDeviceContext);
这个函数是在设备管理程序通过DeactivateDevice()函数卸载设备时被系统调用的,它的主要功能是回收驱动程序中用到的资源。
3、DWORD XXX_Open(DWORD hDeviceContext,DWORD AccessCode,DWORD ShareMode);
这个函数是在应用程序通过CreateFile()函数打开设备时调用的。
4、BOOL XXX_Close(DWORD hOpenContext);
这个函数是在应用程序通过CloseHandle()函数关闭句柄时调用的。
5、DWORD XXX_Read(DWORD hOpenContext,LPVOID pBuffer,DWORD Count);
这个函数是在应用程序通过ReadFile()函数读取设备时调用的。
6、DWORD XXX_Write(DWORD hOpenContext,LPCVOID pBuffer,DWORD Count);
这个函数是在应用程序通过WriteFile()函数向设备写入数据时调用的。
7、DWORD XXX_IOControl(DWORD hOpenContext,DWORD dwCode,PBYTE pBufIn,DWORD dwLenIn,PBYTE pBufOut,DWORD dwLenOut,PDWORD pdwActualOut);
这个函数是在应用程序通过DeviceIOControl()函数向设备发送控制字时调用的。
8、void XXX_PowerUp(DWORD hDeviceContext);
这个函数是在向设备恢复供电的时候调用的。
9、void XXX_PowerDown(DWORD hDeviceContext);
这个函数是在设备断电的时候调用的。

上述函数中的XXX部分是在注册表中注册此设备驱动程序的时候Perfix注册表项的值。例如串口,则用Com,并口则用LTP等等。只要一个DLL中针对特定设备实现了上述接口并且正确在注册表中注册,那么就可以在程序中通过那些文件API函数来访问此设备。下面我们就一起在PB5中按上述接口模拟一个设备。设备的名字就叫做TTT吧。
运行PB5,打开上次做的平台,在文件菜单中执行"New Project or File"命令,新建一个"WCE Dynamic-Link Library"项目,输入项目名字为"Test"。在向导的第一步中输入必要的信息(此处不输也可以,这些信息可供向导自动生成一个README.TXT文件,不过还是写上好,以免以后忘了),在下一步中选择一个空项目,完成。
在新的源程序文件中输入以下程序:
// test.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include "memory.h"
#include "windows.h"

HANDLE* hDevice;
#define BUFSIZE 256
WCHAR buffer[BUFSIZE];

BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}

HANDLE TTT_Init(LPCTSTR pContext,LPCVOID lpvBusContext)
{
hDevice=(HANDLE*)LocalAlloc(LPTR,sizeof(HANDLE));
memset(buffer,0,sizeof(WCHAR)*BUFSIZE);
return hDevice;
}

BOOL TTT_Deinit(DWORD hDeviceContext)
{
LocalFree(hDevice);
return TRUE;
}

DWORD TTT_Open(DWORD hDeviceContext,DWORD AccessCode,DWORD ShareMode)
{
if (!hDeviceContext)
return FALSE;
return TRUE;
}

BOOL TTT_Close(DWORD hOpenContext)
{
if (!hOpenContext)
return FALSE;
return TRUE;
}

DWORD TTT_Read(DWORD hOpenContext,LPVOID pBuffer,DWORD Count)
{
DWORD dwret=min(BUFSIZE,Count);
wcsncpy((LPWSTR)pBuffer,buffer,dwret);
return dwret;
};

DWORD TTT_Write(DWORD hOpenContext,LPCVOID pBuffer,DWORD Count)
{
DWORD dwret=min(BUFSIZE,Count);
wcsncpy(buffer,(LPWSTR)pBuffer,dwret);
return dwret;
}

DWORD TTT_IOControl(DWORD hOpenContext,DWORD dwCode,PBYTE pBufIn,
DWORD dwLenIn,PBYTE pBufOut,DWORD dwLenOut,PDWORD pdwActualOut)
{
return TRUE;
}

void TTT_PowerUp(DWORD hDeviceContext)
{
return;
}

void TTT_PowerDown(DWORD hDeviceContext)
{
return;
}


编译,然后编译该test文件夹下的test.def文件,将这些实现的接口函数导出:
LIBRARY TEST.DLL

EXPORTS

TTT_Init
TTT_Deinit
TTT_Open
TTT_Close
TTT_PowerUp
TTT_PowerDown
TTT_IOControl
TTT_Read
TTT_Write


同时编辑此文件夹下的test.reg文件,加入必要的注册表项:
[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\test]
"DeviceArrayIndex"=dword:0
"Prefix"="TTT"
"Dll"="test.Dll"
"Order"=dword:20

好了,一个TTT设备的流接口驱动就写好了,从中可以看到,在写驱动的时候比PB4要更加清晰,在源程序文件夹中的DEF文件,REG文件,BIB文件,DB文件,DAT文件和编译链接时需要的BAT文件都被PB生成好了,开发人员只要作些必要的修改即可以,非常方便。
接下来,为了测试我们的驱动程序是否可以正常工作,还需要写一个测试程序,再次为平台新建一个WCE应用程序项目TestTTT,写入如下代码并编译为EXE文件:
#include "stdafx.h"
#include "windows.h"

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.

HANDLE handle=CreateFile(_T("TTT1:"),GENERIC_READ|GENERIC_WRITE,0,
NULL,OPEN_EXISTING,0,NULL);
ASSERT(handle);
DWORD ret=0;
TCHAR* pstr=_T("This is a TEST of TTT Driver");
WriteFile(handle,pstr,(_tcslen(pstr) 1)*sizeof(TCHAR),&ret,NULL);
TCHAR ReadStr[256];
memset(ReadStr,0,sizeof(TCHAR)*256);
ret=0;
ReadFile(handle,ReadStr,sizeof(ReadStr),&ret,NULL);
MessageBox(NULL,ReadStr,_T("Test"),MB_OK);
CloseHandle(handle);
return 0;
}


好了,一切准备就绪,重新编译平台并下载到设备上运行,在Windows文件夹(修改文件夹选项,把所有的复选框全去掉才能看到隐藏的和系统文件)下可以找到test.dll文件和TestTTT.exe文件,运行后者,结果如图:


很简单吧,你只要在上述的接口中访问你的设备端口读写数据就可以了,我只是举个例子,希望能对你有所启发,对于驱动程序部分的内容,就先告一段落吧,继续关注后续文章吧!



上一篇:WinCE 5.0边做边学(5)(6) 下一篇:Linux 脚本编写基础