//=====================================================================
//TITLE:
// WinCE驱动的动态加载
//AUTHOR:
// norains
//DATE:
// Monday 22- February-2010
//Environment:
// WINDOWS CE 5.0
//=====================================================================
WinCE驱动的调试,很多人的第一感觉就是:编写好DLL文件,接着在PB中添加相关注册表信息,然后将DLL文件包含进系统,最后生成系统,下载,调试。如果有误,那么依次按步骤重来。
其实这种繁琐的操作完全可以不必要,因为在WinCE下驱动是可以动态加载和卸载的。
驱动的加载和卸载非常简单,我们只需要如下两个函数:
HANDLE ActivateDeviceEx(
LPCWSTR lpszDevKey,
LPCVOID lpRegEnts,
DWORD cRegEnts,
LPVOID lpvParam
);
BOOL DeactivateDevice(
HANDLE hDevice
);
前者是加载,后者是卸载。
我们首先来看一下加载函数。这函数很简单,lpszDevKey指向的是驱动信息在注册表的位置。比如,我之前文章所提到的虚拟串口的驱动的注册表信息如下:
[HKEY_LOCAL_MACHINE/Drivers/Builtin/VirtualSerial]
"Prefix"="VSP"
"Dll"="VirtualSerial.dll"
"Order"=dword:0
"Index"=dword:1
"Map_Port"="COM1:"
那么对于这个信息而言,lpszDevkey的取值为TEXT("Drivers//Builtin//VirtualSerial")。对于系统而言,驱动的Root Key为HKEY_LOCAL_MACHINE,故这里并不需要特别指出。换而言之,驱动的信息只能放置于HKEY_LOCAL_MACHINE,因为我们无法另外指定Root Key。
接下来再看看别的参数。lpRegEnts和cRegEnts是和BUS有关的,但我们接下来的例子并没有用上,所以这里直接可以忽略,直接赋值NULL即可。其实,如果不使用这两个形参的话,我们还可以选择ActivateDevice。
lpvParam指向的是传给驱动XXX_Init函数的形参,如果有特别需求,我们可以通过该指针进行传递。
函数功能很简单。我们写一个功能简单的驱动,来测试该函数是否有效。
驱动代码如下:
DWORD g_dwParam = 0;
std::wstring g_strContext;
BOOL WINAPI DllEntry(HANDLE hInstDll, DWORD dwReason, LPVOID lpvReserved)
{
switch ( dwReason )
{
case DLL_PROCESS_ATTACH:
break;
}
return TRUE;
}
DWORD FKE_Init(
LPCTSTR pContext,
LPCVOID lpvBusContext
)
{
printf("FKE_Init !/r/n");
if(pContext != NULL)
{
g_strContext = pContext;
}
if(lpvBusContext != NULL)
{
g_dwParam = *(reinterpret_cast(lpvBusContext));
}
return TRUE;
}
BOOL FKE_Deinit(
DWORD dwContext
)
{
printf("FKE_Deinit !/r/n");
return TRUE;
}
DWORD FKE_Open(
DWORD dwData,
DWORD dwAccess,
DWORD dwShareMode
)
{
return TRUE;
}
BOOL FKE_Close(DWORD dwHandle)
{
return TRUE;
}
BOOL FKE_IOControl(
DWORD dwHandle,
DWORD dwIoControlCode,
PBYTE pBufIn,
DWORD dwBufInSize,
PBYTE pBufOut,
DWORD dwBufOutSize,
PDWORD pBytesReturned
)
{
return FALSE;
}
DWORD FKE_Read(DWORD dwHandle, LPVOID pBuffer, DWORD dwNumBytes)
{
std::vector vtVal(MAX_PATH,0);
_stprintf(&vtVal[0],TEXT("Context:%s/r/n Parameter:%d/r/n"),g_strContext.c_str(),g_dwParam);
int iLen = _tcslen(&vtVal[0]);
memcpy(pBuffer,&vtVal[0],iLen * 2);
return iLen;
}
DWORD FKE_Write(DWORD dwHandle, LPCVOID pBuffer, DWORD dwNumBytes)
{
return 0;
}
DWORD FKE_Seek(DWORD dwHandle, long lDistance, DWORD dwMoveMethod)
{
return FALSE;
}
void FKE_PowerUp(void)
{
return;
}
void FKE_PowerDown(void)
{
return;
}
代码意思很简单,就是在加载和卸载的时候,分别打印信息。然后还有两个全局变量,一个是g_strContext,用来保存成功加载时的注册表位置;另一个是g_dwParam,用来保存通过ActivateDeviceEx函数传递的第4个形参。而这两个全局变量的数值,之后我们可以通过ReadFile函数获得。只不过FKE_Read函数健壮性不高,没有判断缓冲区是否为空。但作为测试,还是够了。
驱动方面大致如此,我们再来实例看看如何加载驱动。因为驱动的加载涉及到注册表的写入,所以我这里直接采用了CReg类。关于该类的代码,详情可参见:(http://blog.csdn.net/norains/archive/2007/06/20/1659925.aspx)
我们先写注册表信息:
#define DEV_KEY TEXT("Drivers//Builtin//FakeDriver")
CReg reg;
reg.Create(HKEY_LOCAL_MACHINE,DEV_KEY);
reg.SetDW(TEXT("Order"),0);
reg.SetDW(TEXT("Index"),1);
reg.SetSZ(TEXT("Prefix"),TEXT("FKE"));
reg.SetSZ(TEXT("Dll"),TEXT("//Windows//Driver.dll"));
稍微说一下注册表写入数值的意思。Order是加载的顺序,其实手工加载的话可以无视该字段。Index是打开时的序号,与此相关的还有Prefix,为驱动名。Dll则简单了,则是我们编译好的驱动的存放路径。
接下来就简单多了,我们加载驱动,然后传递一个DWORD的数值作为形参:
DWORD dwParam = 89;
HANDLE hd = ActivateDeviceEx(DEV_KEY,NULL,0,&dwParam);
HANDLE hDriver = CreateFile(TEXT("FKE1:"),
GENERIC_READ | GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
std::vector vtBuf(MAX_PATH);
DWORD dwRead = 0;
ReadFile(hDriver,&vtBuf[0],vtBuf.size(),&dwRead,NULL);
CloseHandle(hDriver);
如果运行这段代码,你则会看到ReadFile之后,vtBuf则会存储到相应的数值。在我的平台上,某次运行时的数值如下:
Context:Driver/Active/37 Parameter:89
最后,就是卸载驱动。这个最简单,直接传递ActivateDeviceEx执行成功后返回的数值即可:
DeactivateDevice(hd);