WDM驱动开发入门

    WDM,即Windows Driver Model,是Windows环境下开发驱动程序的有力工具。

必备知识

给出一个WDM一书中描述windows 2000系统结构的一个截图:

image

驱动程序是一个分层的结构,一个硬件设备并不是只由一个驱动程序来管理,在它相关联的物理设备驱动程序之上,还有很多过滤驱动程序。与这些过滤驱动程序相关联的,就是这个物理设备对象的过滤器设备对象。那么,一个用户模式的请求,必须通过上层的过滤器设备对象,一层一层的往下传,最终才能到达物理设备对象。这有点像TCP/IP分层结构模型,一个应用层的数据包必须通过传输层、网络层这样一层一层的往下传,最终才能达到物理层并传递到网络中。而设计这样的分层模型的目的,我想应该是为了方便扩展,比如如果想对某个设备加入新的管理操作,那么不需要修改其已有的物理设备驱动程序和过滤器驱动程序,而只需要加入新的过滤器设备对象以及相应的驱动程序,在这里加入新的操作就行了。

下面,还是用一个图来表示这种分层的结构模型:

image

这个图左边的PDO、FDO、FiDO等,就是指设备对象。而IRP——IO请求包,就是上一层设备对象向下一层设备对象发送的请求,也就是它们之间交互的信息。


另外,需要指出的一点是,在很多内核模式编程中,驱动程序并不一定要与某一个实际存在的物理设备相关联,它可以仅创建一个虚拟的设备对象,而这个设备对象不与任何实际的物理设备相关联。因为在很多情况下,用户编写驱动的目的仅仅是要让自己的代码执行在系统的内核态中。

 

编写WDM驱动程序

驱动程序入口点 - DriverEntry函数(相当于c语言的main函数), 它在驱动程序被加载进内存的时候调用。

DriverEntry函数有两个参数,其中第一个参数PDRIVER_OBJECT pDriverObj是指向该驱动程序对应的驱动程序对象的指针。

  • 一个重要的任务就是要设定驱动程序对象的几个函数指针,这样,该驱动程序对象关联的设备对象在接收到上层的IRP的时候,就会通过驱动程序对象中设置的函数指针,找到相应的函数来做处理:
           pDriverObj->DriverUnload = DriverUnload;
           pDriverObj->MajorFunction[IRP_MJ_CREATE] =
           pDriverObj->MajorFunction[IRP_MJ_CLOSE] =
           pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatch;
  • 还有一个重要的任务,就是要创建设备对象并为其建立符号连接。(这里说明一下,在规范的WDM程序中,创建设备对象的任务本来该由AddDevice函数来做,而这个函数也是通过驱动程序对象的一个函数指针来定位的。在这种规范的WDM程序中,一旦有新硬件加入,系统就会自动通过驱动程序对象的函数指针找到AddDevice函数,并调用它来创建设备对象。但是在这里,我并不是在为实际存在的硬件写驱动,而只是写一个内核模式下的程序,因此就只需要在DriverEntry函数中创建一个设备对象就行了。)
    IoCreateDevice( pDriverObj,
    0,
    &deviceName,
    FILE_DEVICE_UNKNOWN,
    FILE_DEVICE_SECURE_OPEN,
    true,
    &pDeviceObj ); //创建设备对象
    IoCreateSymbolicLink( &linkName, &deviceName );   //建立符号连接
    从上面调用IoCreateDevice函数的参数中还可以看出,设备对象和驱动程序对象是相关联的,这也就可以解释为什么是设备接收到IRP,而相应的处理函数却是由驱动程序对象中的函数指针定位的。至于建立符号连接,那是为了方便在用户模式中调用设备对象。


从前面设定的驱动程序对象中的函数指针可以看到,主要有两个函数:卸载函数DriverUnload和派遣函数DriverDispatch。DriverUnload函数应该很容易理解,它是在驱动程序被卸载出内存的时候调用,主要做一些释放内存之类的工作。而在我的这个程序中,所有的IRP都是在同一个函数里面进行处理的,这就是派遣函数DriverDispatch(实际上很多WDM程序都是这样做的)。下面就分别介绍一下这两个函数。

  • DriverUnload函数的主要任务是将创建的设备对象和符号连接删除掉,当然如果在程序中还分配了其他内存需要释放,也是在这里完成。
    IoDeleteSymbolicLink( &linkName );
    IoDeleteDevice( pDriverObj->DeviceObject );
  • 派遣函数DriverDispatch主要负责处理上层的IRP。这里先要提一下,每个IRP都与两个数据结构相关联,就是IRP本身和IRP Stack——IO_STACK_LOCATION结构。在这两个结构里面,包含了所有上层传递给本层设备对象的信息。最重要的一个信息就是:在IO_STACK_LOCATION结构中,包含了IRP的功能码MajorFunction和MinorFunction(IRP的功能码标识了该IRP具体是什么请求,比如读请求的MajorFunction值为IRP_MJ_READ)。
    DriverDispatch函数的处理流程一般是这样的:首先通过IRP获得IPR Stack;然后从IRP Stack中得到该IRP的主功能码MajorFunction,判断主功能码并做相应处理;处理完该请求后,根据具体情况选择完成该请求或者向下一层设备对象传递该IRP。获得IRP Stack很简单,只需要调用函数IoGetCurrentIrpStackLocation即可:
    PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp );
    判断主功能码并做相应的处理这一步一般是由一个switch-case语句实现的:
           switch( pIrpStack->MajorFunction )
           {
           case IRP_MJ_CREATE:
                  DbgPrint( "Info: Create!\n" );
                  break;
           case IRP_MJ_CLOSE:
                  DbgPrint( "Info: Close!\n" );
                  break;
           case IRP_MJ_DEVICE_CONTROL:
                  {
                         switch( pIrpStack->Parameters.DeviceIoControl.IoControlCode )
                         {
                                case IOCTL_GET_INFO:
                                {
                                       RtlCopyMemory( pIrp->UserBuffer, "This is a test driver!", 23 );
                                       information = 23;
                                       break;
                                }
                                default:
                                       break;
                         }
                  }
           default:
                  break;
           }


最后一步,如果需要完成该请求,那么应该先设置IRP结构中的IoStatus域,然后调用函数IoCompleteRequest:
       pIrp->IoStatus.Status = STATUS_SUCCESS;
       pIrp->IoStatus.Information = information;
       IoCompleteRequest(pIrp, IO_NO_INCREMENT);
如果需要向下一层设备对象传递该IRP,则要先初始化往下传递的IRP对应IRP Stack(可以直接将当前的IRP Stack复制给下层IRP Stack),然后调用IoCallDriver函数往下层传递该IRP:
IoCopyCurrentIrpStackLocationToNext(pIrp);
status = IoCallDriver(pLowerDeviceObj, pIrp);

 

那么,怎样从用户模式的程序中调用驱动?

    用户模式的程序要调用驱动,首先就要打开设备,也就是驱动程序中创建的设备对象。这可以通过调用CreateFile函数来实现。CreateFile函数本来是用于打开文件,它的第一个参数就是文件名。而这里,我们以设备名作为它的第一个参数传入,那么该函数打开的就是设备了。这里所说的设备名,实际上是驱动程序里面为设备对象建立的符号连接名。比如用户模式中给出的设备名为” \\.\MyDevice”,I/O管理器在执行名称搜索前先自动把”\\.\”转换成”\??\”,这样就成了” \??\MyDevice”,这就是驱动程序里面建立的符号连接名了。打开设备后,用户模式的程序就可以调用ReadFile、WriteFile和DeviceIoControl等函数向驱动程序发出请求了。
最后,给出我的试验程序的源码。

 

其他


WDM程序编译出来的并不是我们常见的.exe,而是.sys文件,在未经设置编译环境之前,是不能直接用VC来编译的(这就是为什么会有几百个错误了)。这种类型的文件你可以在WINNT\System32\Drivers里面找到很多。其实驱动程序也是一种PE文件,它同样由DOS MZ header开头,也有完整的DOS stub和PE header,同样拥有Import table和Export table——hoho……那跟普通的PE文件有什么不一样呢?

其实.sys跟.exe文件一样,都是一种PE文件来的。不同的是,.sys文件Import的通常是NTOSKRNL.EXE,而.exe文件Import的通常是KERNEL32.DLL和USER32.DLL。

知道了这些有什么用呢?实际上,由于.sys通常不调用KERNEL32.DLL和USER32.DLL,所以你是不能在设备驱动程序里面调用任何C、C++和Win32函数的,而且也不能用C++关键字new和delete等(可以用malloc和free来代替),而必须使用大量的内核函数。

为了读者的方便,下面我列出一些常见的驱动程序可用的内核函数:
Ex…        执行支持
Hal…        硬件抽象层(仅NT/Windows 2000)
Io…        I/O管理器(包括即插即用函数)
Ke…        内核
Ks…        内核流IRP管理函数
Mm…        内存管理器
Ob…        对象管理器
Po…        电源管理
Ps…        进程结构
Rtl…        运行时库
Se…        安全引用监视
Zw…        其他函数

 

//Driver部分:

#ifdef __cplusplus
extern "C" {
#endif

#include "ntddk.h"

#define DEVICE_NAME L"\\Device\\MyDevice"
#define LINK_NAME L"\\??\\MyDevice"
#define IOCTL_GET_INFO \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)

void DriverUnload( PDRIVER_OBJECT pDriverObj );
NTSTATUS DriverDispatch( PDEVICE_OBJECT pDeviceObj, PIRP pIrp );

// 驱动程序加载时调用DriverEntry例程:
NTSTATUS DriverEntry( PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegistryString )
{
DbgPrint( "DriverEntry!\n" );

NTSTATUS status;

PDEVICE_OBJECT pDeviceObj;
UNICODE_STRING deviceName;
RtlInitUnicodeString( &deviceName, DEVICE_NAME );
status = IoCreateDevice( pDriverObj, 0, &deviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, true, &pDeviceObj );
if( !NT_SUCCESS( status ) )
{
DbgPrint( "Error: Create device failed!\n" );
return status;
}

UNICODE_STRING linkName;
RtlInitUnicodeString( &linkName, LINK_NAME );
status = IoCreateSymbolicLink( &linkName, &deviceName );
if( !NT_SUCCESS( status ) )
{
DbgPrint( "Error: Create symbolic link failed!\n" );
IoDeleteDevice( pDeviceObj );
return status;
}

pDriverObj->DriverUnload = DriverUnload;
pDriverObj->MajorFunction[IRP_MJ_CREATE] =
pDriverObj->MajorFunction[IRP_MJ_CLOSE] =
pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatch;

return STATUS_SUCCESS;
}

void DriverUnload( PDRIVER_OBJECT pDriverObj )
{
DbgPrint( "DriverUnload!\n" );

if( pDriverObj->DeviceObject != NULL )
{
UNICODE_STRING linkName;
RtlInitUnicodeString( &linkName, LINK_NAME );
IoDeleteSymbolicLink( &linkName );

IoDeleteDevice( pDriverObj->DeviceObject );
}
return;
}

NTSTATUS DriverDispatch( PDEVICE_OBJECT pDeviceObj, PIRP pIrp )
{
ULONG information = 0;
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp );

switch( pIrpStack->MajorFunction )
{
case IRP_MJ_CREATE:
DbgPrint( "Info: Create!\n" );
break;
case IRP_MJ_CLOSE:
DbgPrint( "Info: Close!\n" );
break;
case IRP_MJ_DEVICE_CONTROL:
{
switch( pIrpStack->Parameters.DeviceIoControl.IoControlCode )
{
case IOCTL_GET_INFO:
{
RtlCopyMemory( pIrp->UserBuffer, "This is a test driver!", 23 );
information = 23;
break;
}
default:
break;
}
}
default:
break;
}

pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = information;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

#ifdef __cplusplus
}
#endif

//用户模式程序部分:

#include "stdafx.h"
#include "stdio.h"
#include "windows.h"

#define DEVICE_NAME "\\\\.\\MyDevice"
#define CTL_CODE( DeviceType, Function, Method, Access ) ( \
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
)
#define FILE_DEVICE_UNKNOWN 0x00000022
#define METHOD_NEITHER 3
#define FILE_ANY_ACCESS 0
#define IOCTL_GET_INFO \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)

int main(int argc, char* argv[])
{
HANDLE hDevice = CreateFile( DEVICE_NAME, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );

if( hDevice == INVALID_HANDLE_VALUE )
{
printf( "Error: Can't open the device!\n" );
return -1;
}

unsigned long numOfBytesReturned;
char info[32] = {0};
if( DeviceIoControl( hDevice, IOCTL_GET_INFO, NULL, 0, info, 32, &numOfBytesReturned,
NULL ) == true )
printf( "Information: %s \n", info );

CloseHandle( hDevice );
Sleep( 3000 );
return 0;
}

 

原文链接

 

ndis protocol driver send.c流程的简单描述

总结一下流程 
1.从封包池里申请封包 NdisAllocatePacket. 
2.设置IRP请求为未决状态 IoMarkIrpPending. 
3.利用NDIS_PACKET的保留域来保存上面哪个IRP请求的指针 
4.关联内存描符到我们申请的封包上 NdisChainBufferAtFront. 
  注意如果是win2000以下的系统不能直接使用pIrp->MdlAddress 
5.发送数据 NdisSendPackets. 
6.在ProtocolSendCompletel里取出上面保存的Irp,设置必要的数据 
7. 释放NdisAllocatePacket申请的资源NdisFreePacket. 
8.完成Irp请求 IoCompleteRequest

先看2个关键的列程指派 
pDriverObject->MajorFunction[IRP_MJ_WRITE] = NdisProtWrite; 
protocolChar.SendCompleteHandler = NdisProtSendComplete; 

msdn里定义的原型对应如下:
NTSTATUS 
  XxxDispatchWrite( 
  IN PDEVICE_OBJECT DeviceObject, 
  IN PIRP Irp 
  ); 
VOID ProtocolSendComplete( 
IN NDIS_HANDLE ProtocolBindingContext, 
IN PNDIS_PACKET Packet, 
IN NDIS_STATUS Status ); 
对于SendCompleteHandler的描述如下: 
This is a required function. ProtocolSendComplete is called for each packet transmitted with a call to NdisSend that returned NDIS_STATUS_PENDING as the status of the send operation. If an array of packets is sent, ProtocolSendComplete is called once for each packet passed to NdisSendPackets, whether or not it returned pending.


所以SendCompleteHandler必须被指派


先看xxDispatchWrite 
当我们的ring 3程序调用writefile时候,xxDispatchWrite就会被调用。 
然后在xxDispatchWrite里调用NdisSend或者NdisSendPackets就可以把数据发送出去了 
但是,在这之前我们需要对一些变量初始化,调用别的函数当这一切做好以后,才可以发送数据。 
我们先看这2个发送函数的原型: 
VOID 
  NdisSend( 
  OUT PNDIS_STATUS Status, 
  IN NDIS_HANDLE NdisBindingHandle, //对应NdisOpenAdapter返回的句柄,它在ndisbind.c里实现了 
  IN PNDIS_PACKET Packet  //一个PNDIS_PACKET结构的东东? 
  ); 

VOID 
  NdisSendPackets( 
  IN NDIS_HANDLE NdisBindingHandle, 
  IN PPNDIS_PACKET PacketArray, 
  IN UINT NumberOfPackets 
  ); 
通过比较,我们可以发现,这2个函数都有3个参数,不一样的ndissend有一个返回值,而ndissendpackets没有,这个结果对应了上面关于SendCompleteHandler的描述。 

在XxxDispatchWrite开始 
PNDIS_PACKET pNdisPacket=NULL; 
调用NdisAllocatePacket从包池里分配并初始化pNdisPacket这个包描述符 
NdisAllocatePacket(&Status,&pNdisPacket,pOpenContext->SendPacketPool); 
其中pOpenContext->SendPacketPool是在ndisbind.c里调用NdisAllocatePacketPool返回的句柄 

这里注意,msdn里有段关于NDIS_PACKET结构的描述 

Any buffers allocated by lower-level NDIS drivers must be mapped by buffer descriptors that were allocated from the buffer pool with NdisAllocateBuffer. Only highest-level Microsoft? Windows? 2000 protocols can use memory descriptor lists (MDLs) set up by still higher-level drivers as substitutes for NDIS_BUFFER descriptors. 

所以在send.c里,还有有段判断系统平台,然后是否调用NdisAllocateBuffer的代码 
如果是属于win2k以上的系统,代码就很简单的处理为 
pNdisBuffer = pIrp->MdlAddress; 
上面的描述解释了为什么可以这样写. 

然后需要调用NdisChainBufferAtFront函数,将缓冲区空间的描述符连接到包描述符里的缓冲空间头部 
NdisChainBufferAtFront(pNdisPacket, pNdisBuffer); 
当调用完成以后,在调用NdisSendPackets发送数据了。 
但是因为我们调用NdisSendPackets,而这个函数没有返回值,一但调用NdisSendPackets,那么系统都会调用ProtocolSendComplete,来完成这个Irp请求. 
所以,我们需要来在XxxDispatchWrite里来标识这个Irp处于未决状态。 
IoMarkIrpPending(pIrp); 
如果在列程里调用IoMarkIrpPending,那么列程必须返回STATUS_PENDING ,以表明这个Irp请求没有完成。系统会自动调用ProtocolSendComplete来完成剩下的工作。


我们在来看ProtocolSendComplete,这个函数的原型 
VOID ProtocolSendComplete( 
IN NDIS_HANDLE ProtocolBindingContext, 
IN PNDIS_PACKET Packet, 
IN NDIS_STATUS Status ); 
因为我们需要填充Irp结构里的一些变量,然后调用类似下面的代码 
  pIrpSp = IoGetCurrentIrpStackLocation(pIrp); 
  if (Status == NDIS_STATUS_SUCCESS) 
  { 
  pIrp->IoStatus.Information = pIrpSp->Parameters.Write.Length; 
  pIrp->IoStatus.Status = STATUS_SUCCESS; 
  } 
  else 
  { 
  pIrp->IoStatus.Information = 0; 
  pIrp->IoStatus.Status = STATUS_UNSUCCESSFUL; 
  } 
  IoCompleteRequest(pIrp, IO_NO_INCREMENT); 
来完成这个Irp请求 
那么就必须保存pIrp这个指针,但是我们看上面的函数原型里的3个参数,并没有和PIrp有关的参数 
事实上,ddk里的send.c。利用的Packet里的一个保留域来保存了着PIrp指针,我们可以看到 
Packet这个参数被传递过来了 
下面看NDIS_PACKET这个结构 
typedef struct _NDIS_PACKET { 
  NDIS_PACKET_PRIVATE Private; 
  union { 
  struct { 
  UCHAR MiniportReserved[2*sizeof(PVOID)]; 
  UCHAR WrapperReserved[2*sizeof(PVOID)]; 
  }; 
  struct { 
  UCHAR MiniportReservedEx[3*sizeof(PVOID)]; 
  UCHAR WrapperReservedEx[sizeof(PVOID)]; 
  }; 
  struct { 
  UCHAR MacReserved[4*sizeof(PVOID)]; 
  }; 
  }; 
  ULONG_PTR Reserved[2]; 
  UCHAR ProtocolReserved[1]; 
} NDIS_PACKET, *PNDIS_PACKET, **PPNDIS_PACKET; 
我们在看send.c里,是如何做的,用了一个宏 
NPROT_IRP_FROM_SEND_PKT(pNdisPacket) = pIrp; 
这个宏的定义 
#define NPROT_IRP_FROM_SEND_PKT(_pPkt) \ 
  (((PNPROT_SEND_PACKET_RSVD)&((_pPkt)->ProtocolReserved[0]))->pIrp) 
其实就是把PNDIS_PACKET->ProtocolReserved转换成PNPROT_SEND_PACKET_RSVD的指针,然后在指向pIrp 
PNPROT_SEND_PACKET_RSVD定义如下 
typedef struct _NPROT_SEND_PACKET_RSVD 

  PIRP pIrp; 
  ULONG RefCount; 
} NPROT_SEND_PACKET_RSVD, *PNPROT_SEND_PACKET_RSVD; 
其实就是把pIrp存放到了pNDIS_PACKET->ProtocolReserved 
看到这里,我就有点奇怪了ProtocolReserved不是字符数组吗?而且就1个字符,那么它就因该是1字节,但是pIrp是个指针 
4字节,怎么能这样存喃? 
其实不是的,msdn在非常隐秘的地方做了如下的说明.... 
NIC drivers and intermediate drivers allocate packet descriptors with four pointer's worth (4*sizeof(PVOID)) of ProtocolReserved space to be used by protocols for receive indications. On 32-bit systems, four pointer's worth of ProtocolReserved space equals 16 bytes. On 64-bit systems, four pointer's worth of ProtocolReserved space equals 32 bytes 
所以ProtocolReserved更本就不是什么UCHAR类型 
在看一下 
VOID ProtocolSendComplete( 
IN NDIS_HANDLE ProtocolBindingContext, 
IN PNDIS_PACKET pNdisPacket, 
IN NDIS_STATUS Status ); 
注意,Packet被传递进来了,而我们刚刚在XxxDispatchWrite将PENDING状态的Irp保存在Packet里了,所以也被传递进来了 
下面完成它 
  PIRP PIrp; 
  PIO_STACK_LOCATION pIrpSp; 
  PIrp = NPROT_IRP_FROM_SEND_PKT(pNdisPacket); 
  NdisFreePacket(pNdisPacket); 
  pIrpSp = IoGetCurrentIrpStackLocation(pIrp); 
  if (Status == NDIS_STATUS_SUCCESS) 
  { 
  pIrp->IoStatus.Information = pIrpSp->Parameters.Write.Length; 
  pIrp->IoStatus.Status = STATUS_SUCCESS; 
  } 
  else 
  { 
  pIrp->IoStatus.Information = 0; 
  pIrp->IoStatus.Status = STATUS_UNSUCCESSFUL; 
  } 
  IoCompleteRequest(pIrp, IO_NO_INCREMENT); 

最后完成这个Irp请求,发送完成 
ContractedBlock.gifExpandedBlockStart.gifCode
#include "ntddk.h"
#include 
"ndis.h"
#include 
"packet.h"

NTSTATUS
PacketWrite(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    POPEN_INSTANCE  open
=NULL;
    PNDIS_PACKET    Packet
=NULL;
    NDIS_STATUS     Status;
    
    open
=(POPEN_INSTANCE)DeviceObject->DeviceExtension;
    IoIncrement(open);
    
    
do
    {
        
if(!open->Bound)
        {
            Status 
= STATUS_UNSUCCESSFUL;
            
break;
        }
        
        NdisAllocatePacket(
&Status, &Packet, open->PacketPool);
        
if(Status != NDIS_STATUS_SUCCESS)
        {
            Status 
= STATUS_INSUFFICIENT_RESOURCES;
            
break;
        }
        
        ((PPACKET_RESERVED)
&(Packet->ProtocolReserved))->Irp=Irp;

        NdisChainBufferAtFront(Packet,(PNDIS_BUFFER)(Irp
->MdlAddress));
        
        IoMarkIrpPending(Irp);
        Status
=STATUS_PENDING;
        DebugPrint((
"PacketWrite() OK Sending\n"));
        NdisSendPackets(open
->AdapterHandle, &Packet, 1);

        
return Status;
    }
while(FALSE);
    
    DebugPrint((
"PacketWrite()  Failure!\n"));
    Irp
->IoStatus.Information = 0;
    Irp
->IoStatus.Status = Status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    IoDecrement(open);
    
return Status;
}

VOID
PacketSendComplete(
    IN NDIS_HANDLE   ProtocolBindingContext,
    IN PNDIS_PACKET  pPacket,
    IN NDIS_STATUS   Status
    )
{
    PIRP                irp;
    PIO_STACK_LOCATION  irpSp;
    
    irp
=RESERVED(pPacket)->Irp;
    irpSp 
= IoGetCurrentIrpStackLocation(irp);

    NdisFreePacket(pPacket);

    
if(Status == NDIS_STATUS_SUCCESS) {
        irp
->IoStatus.Information = irpSp->Parameters.Write.Length;
        irp
->IoStatus.Status = STATUS_SUCCESS;
        DebugPrint((
"PacketSendComplete() Ok! send byte %d", irpSp->Parameters.Write.Length)); 
    } 
else {
        irp
->IoStatus.Information = 0;
        irp
->IoStatus.Status = STATUS_UNSUCCESSFUL;
        DebugPrint((
"PacketSendComplete() Failure! send byte %d", irpSp->Parameters.Write.Length));
    }
    IoCompleteRequest(irp, IO_NO_INCREMENT);
    IoDecrement((POPEN_INSTANCE)ProtocolBindingContext);
    
return;
}

转载于:https://www.cnblogs.com/dubingsky/archive/2009/08/02/1536879.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值