July 15, 2003
作者:Thomas F. Divine 译:feikoo
微软的Windows驱动开发工具(DDK) 附带的NDIS中间层驱动PassThru源代码给我们提供了一个极好的示例,它向我们展示了实现NDIS 中间层过滤驱动框架一种方法。(感谢NDIS开发团队)。但是,PassThru中缺乏实际的功能。要想让它具备实际的用途,我们必须进行下一步的工作,就是向这个框架中添加自己的功能。如果你是Windows驱动开发或NDIS驱动开发的新手,接下来的工作可是一件苦差。
本文接下来以微软Windows DDK Build 3790(Windows Server 2003)附带的PassThru NDIS中间层驱动示例为起点,并在此基础上按下列步骤来添加自己的功能:
1. 基本的DeviceIoControl接口:提供Win32应用程序与PassThru驱动进行通信的一种基本方法。2.绑定枚举函数:允许Win32应用程序查询PassThru驱动的绑定信息;
2. ADAPT结构引用计数:添加对ADAPT结构的逻辑计数;
3. 适配器名柄(Adapter Handle)打开/关闭功能:建立与某一具体命名的PassThru绑定的用户模式句柄的方法。该句柄可以用来在某一适配器上请求,读写I/O和进行其它的操作。
4. 处理事件通知:在这节我们将处理已经打开的驱动句柄变为无效的情况(如Pnp)。
5. 在一个打开的适配器句柄上查询信息:增加一种用Win32初始化的NDIS请求在一个打开的适配器句柄上查询信息的方法。 接下来的一系列文章将介绍对PassThru的另一些扩展。
一 . 扩展PassThru驱动(PassThruEx)
我们由DDK(以下提及DDK均指Windows DDK Build 3790 Windows Server 2003)中的驱动程序源代码开始。PassThru包含以下所列的这些关键文件:
/PassThru - Windows DDK Build 3790 PassThru工程文件夹;
/Driver – PassThru驱动源程序
PassThru.c – DriveEntry函数和其它PassThru小端口驱动程序与协议驱动程序共用的代码部分。
PassThru.h – PassThru的头文件;
Miniport.c – PassThru中与Miniport相关的函数;
Protocol.c – PassThru中与Protocol相关的函数;
Precomp.h – 预编译的头文件;
Sources – 编译工具所用的源文件列表文件;
另外,在PassThru.htm中包含了下列重要信息:
1. 编译该示例的方法;2. 安装编译好的驱动程序的方法;3. 代码说明
文章由易渐难,一步一步地向PassThru中添加功能。每一步中都包含了所添加部分的功能描述和需要修改的代码部分的说明。并且,我们还开发一个Win32应用程序来演示所添加函数的功能。
我们将新添中的代码尽量放在新的.C或.H文件中,大部分的新增代码被放在了PTExtend.c文件中。其中PassThru 用户I/O(PTUUserIo)这个Win32控制台程序用来演示用户态下功能。整个完整的PassThru工程结构如下:
/PassThru - Windows DDK Build 3790 PassThru工程文件夹;
/Driver – PassThru驱动源程序
PassThru.c – DriveEntry函数和其它PassThru小端口驱动程序与协议驱动程序共用的代码部分。
PassThru.h – PassThru的头文件;
Miniport.c – PassThru中与Miniport相关的函数;
Protocol.c – PassThru中与Protocol相关的函数;
Precomp.h – 预编译的头文件;
Sources – 编译工具所用的源文件列表文件;
IOCommon.h – 驱动和用户态下程序所共用的头文件;
PTExtend.c – 包含新加代码的.c文件
/Test – PassThruEX Win32控制台测试程序;
PTUserIo.cpp – Win32控制台测试程序;
PTUtils.cpp – 支持文件(次重要)。
修改后的PassThru驱动程序和测试程序源代码均可以下载。在此感谢微软件公司提供了使用源代码的许可。
二.添加基本的DeviceIoControl接口
我们期望读者在看这篇文章之前已经熟悉基于IRP接口的用户/驱动程序编程。应用程序使用基本的为终端用户提供的Win32接口函数:CreateFile,DeviceIoControl,ReadFile,WriteFile和CloseHandle。
驱动程序创建一个设备对象和一个在Win32用户态下可用CreateFile打开并访问的符号链接名,并注册一些基于IRP的函数,通过这些函数来实现驱动程序内核态的终端用户接口。
1. 驱动程序代码:
设备I/O控制接口的代码在PassThru驱动示例中已经列出。其中NdisMRegisterDevice函数被PassThru.c中的PtRegisterDevice方法调用,通过该函数创建了设备对象和Win32用户态下可见的符号链接名字以及注册了用处理I/O请求的函数。
(1)原PassThru中的代码:PassThru.c中的PtRegisterDevice函数
以下的代码片断摘自PassThru驱动程序PassThru.c:
DispatchTable[IRP_MJ_CREATE] = PtDispatch;
DispatchTable[IRP_MJ_CLEANUP] = PtDispatch;
DispatchTable[IRP_MJ_CLOSE] = PtDispatch;
DispatchTable[IRP_MJ_DEVICE_CONTROL] = PtDispatch;
NdisInitUnicodeString(&DeviceName, NTDEVICE_STRING);
NdisInitUnicodeString(&DeviceLinkUnicodeString, LINKNAME_STRING);
//
// Create a device object and register our dispatch handlers
//
Status = NdisMRegisterDevice(
NdisWrapperHandle,
&DeviceName,
&DeviceLinkUnicodeString,
&DispatchTable[0],
&ControlDeviceObject,
&NdisDeviceHandle
);
(2)修改后的代码:PassThru.c中的PtRegisterDevice函数
在扩展后的PassThru驱动程序中,我们删除了PtDispatch 函数(在PassThru.c中删除PtDispatch 的代码并在PassThru.h中删除其原型),并在该处用分发函数DevOpen, DevCleanup, DevClose and DevIoControl代替。
// BEGIN_PTUSERIO
DispatchTable[IRP_MJ_CREATE] = DevOpen;
DispatchTable[IRP_MJ_CLEANUP] = DevCleanup;
DispatchTable[IRP_MJ_CLOSE] = DevClose;
DispatchTable[IRP_MJ_DEVICE_CONTROL] = DevIoControl;
// END_PTUSERIO
NdisInitUnicodeString(&DeviceName, NTDEVICE_STRING);
NdisInitUnicodeString(&DeviceLinkUnicodeString, LINKNAME_STRING);
//
// Create a device object and register our dispatch handlers
//
Status = NdisMRegisterDevice(
NdisWrapperHandle,
&DeviceName, // //Device//Passthru
&DeviceLinkUnicodeString, // //DosDevices//Passthru
&DispatchTable[0],
&ControlDeviceObject,
&NdisDeviceHandle
);
以上所用的函数在PTExtend.c中实现,在文件夹/PassThruEx/Driver中可找到。以下所列为添加的函数的全部代码:
These are the skeleton I/O dispatch handlers that are implemented in PTExtend.c. These are sufficient for a quick-and-dirty test of opening and closing a handle on the PassThru device. More functionality will be added.
NTSTATUS
DevOpen(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(pDeviceObject);
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
pIrpSp->FileObject->FsContext = NULL;
pIrpSp->FileObject->FsContext2 = NULL;
DBGPRINT(("==>Pt DevOpen: FileObject %p/n", pIrpSp->FileObject));
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
DBGPRINT(("<== Pt DevOpen/n"));
return NtStatus;
}
NTSTATUS
DevCleanup(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(pDeviceObject);
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
DBGPRINT(("==>Pt DevCleanup: FileObject %p/n", pIrpSp->FileObject ));
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
DBGPRINT(("<== Pt DevCleanup/n"));
return NtStatus;
}
NTSTATUS
DevClose(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(pDeviceObject);
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
DBGPRINT(("==>Pt DevClose: FileObject %p/n", pIrpSp->FileObject ));
pIrpSp->FileObject->FsContext = NULL;
pIrpSp->FileObject->FsContext2 = NULL;
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
DBGPRINT(("<== Pt DevClose/n"));
return NtStatus;
}
NTSTATUS
DevIoControl(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
ULONG BytesReturned = 0;
ULONG FunctionCode;
PUCHAR ioBuffer = NULL;
ULONG inputBufferLength;
ULONG outputBufferLength;
UNREFERENCED_PARAMETER(pDeviceObject);
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
ioBuffer = pIrp->AssociatedIrp.SystemBuffer;
inputBufferLength = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
outputBufferLength = pIrpSp->Parameters.DeviceIoControl.OutputBufferLength;
FunctionCode = pIrpSp->Parameters.DeviceIoControl.IoControlCode;
DBGPRINT(("==>Pt DevIoControl: FileObject %p/n", pIrpSp->FileObject ));
switch (FunctionCode)
{
case IOCTL_PTUSERIO_ENUMERATE:
case IOCTL_PTUSERIO_OPEN_LOWER_ADAPTER:
case IOCTL_PTUSERIO_OPEN_VIRTUAL_ADAPTER:
case IOCTL_PTUSERIO_QUERY_INFORMATION:
case IOCTL_PTUSERIO_SET_INFORMATION:
default:
NtStatus = STATUS_NOT_SUPPORTED;
break;
}
if (NtStatus != STATUS_PENDING)
{
pIrp->IoStatus.Information = BytesReturned;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
}
DBGPRINT(("<== Pt DevIoControl/n"));
return NtStatus;
}
2.测试用的应用程序代码:
尽管I/O分发处理函数较简单,但是它足以让我们开始编译并测试它。在此,我们添加代码打开并关闭在符号链接名上的句柄。
测试程序叫作“PassThru用户I/O”,它是一个