27、Windows内核编程,IRP的同步(1)

对设备的任何操作都会转化为IRP请求,而IRP一般都是由操作系统异步发送的。但是有时需要同步来避免逻辑错误。同步方法有:StartIO例程,使用中断服务例程等。

1、应用程序对设备的同步异步操作

1)同步操作原理

大部分IRP是由应用程序的Win32 API发起。这些函数本身就支持同步异步操作。如ReadFile,WriteFile,DeviceIoControl等。

ScreenShot007

图示 IRP同步操作示意图P250

2)同步操作设备

CreateFile,其参数dwFlagsAndAttributes 是同步异步的关键。没有设置FILE_FLAG_OVERLAPPED为同步,否则为异步。

再如ReadFile,其参数lpOverlapped 如果设为NULL,则为同步。

27、Windows内核编程,IRP的同步(1) 代码
   
     
1 #include < windows.h >
2 #include < stdio.h >
3
4 #define BUFFER_SIZE 512
5 int main()
6 {
7 HANDLE hDevice =
8 CreateFile( " test.dat " ,
9 GENERIC_READ | GENERIC_WRITE,
10 0 ,
11 NULL,
12 OPEN_EXISTING,
13 FILE_ATTRIBUTE_NORMAL, // 此处没有设置FILE_FLAG_OVERLAPPED
14 NULL );
15
16 if (hDevice == INVALID_HANDLE_VALUE)
17 {
18 printf( " Read Error\n " );
19 return 1 ;
20 }
21
22 UCHAR buffer[BUFFER_SIZE];
23 DWORD dwRead;
24 ReadFile(hDevice,buffer,BUFFER_SIZE, & dwRead,NULL); // 这里没有设置OVERLAP参数
25
26 CloseHandle(hDevice);
27
28 return 0 ;
29 }
示例代码 P252

3)异步操作设备

法一:通过OVERLAPPED 结构体。

typedef struct _OVERLAPPED {

ULONG_PTR Internal;

ULONG_PTR InternalHigh;

DWORD Offset;

DWORD OffsetHigh;

HANDLE hEvent;

} OVERLAPPED;

第三个参数:操作设备会指定一个偏移量,从设备的偏移量进行读取。该偏移量用一个64位整形表示。Offset为该偏移量的低32位整形,OffsetHigh为高32位整形。

hEvent用于操作完成后通知应用程序。我们可以初始化其为未激发状态,当操作设备结束后,即在驱动中调用IoCompleteRequest后,设备该事件激发态。

OVERLAPPED 结构使用前要清0,并为其创建事件。

27、Windows内核编程,IRP的同步(1) 代码
   
     
1 #include < windows.h >
2 #include < stdio.h >
3
4 #define BUFFER_SIZE 512
5 // 假设该文件大于或等于BUFFER_SIZE
6
7 #define DEVICE_NAME "test.dat"
8 int main()
9 {
10 HANDLE hDevice =
11 CreateFile( " test.dat " ,
12 GENERIC_READ | GENERIC_WRITE,
13 0 ,
14 NULL,
15 OPEN_EXISTING,
16 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // 此处设置FILE_FLAG_OVERLAPPED
17 NULL );
18
19 if (hDevice == INVALID_HANDLE_VALUE)
20 {
21 printf( " Read Error\n " );
22 return 1 ;
23 }
24
25 UCHAR buffer[BUFFER_SIZE];
26 DWORD dwRead;
27
28 // 初始化overlap使其内部全部为零
29 OVERLAPPED overlap = { 0 };
30
31 // 创建overlap事件
32 overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
33
34 // 这里没有设置OVERLAP参数,因此是异步操作
35 ReadFile(hDevice,buffer,BUFFER_SIZE, & dwRead, & overlap);
36
37 // 做一些其他操作,这些操作会与读设备并行执行
38
39 // 等待读设备结束
40 WaitForSingleObject(overlap.hEvent,INFINITE);
41
42 CloseHandle(hDevice);
43
44 return 0 ;
45 }
46
47
示例代码 P254

法二:ReadFileExWriteFileEx 专门被用来异步操作。

BOOL WriteFileEx(

HANDLE hFile, // handle to output file

LPCVOID lpBuffer, // data buffer

DWORD nNumberOfBytesToWrite, // number of bytes to write

LPOVERLAPPED lpOverlapped, // overlapped buffer

LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // completion routine

);

lpOverlapped不需要提供事件句柄。

ReadFileEx 将读请求传递到驱动程序后立即返回。驱动程序在结束读操作后,会通过调用ReadFileEx提供的回调函数(Call Back Function)lpCompletionRoutine 。类似于一个软中断。Windows将这种机制称为异步过程调用APC(asynchronous procedure call )。只有线程于Alert状态时,回调函数才能被调用。多个函数使系统进行Alert状态:SleepExWaitForMultipleObjectsExWaitForSingleObjectExetc

OS一旦结束读取操作,就会把相应的completion routine插入到APC队列中。当OS进入Alert状态后,会枚举当前线程的APC队列。

27、Windows内核编程,IRP的同步(1) 代码
   
     
1 #include < windows.h >
2 #include < stdio.h >
3
4 #define DEVICE_NAME "test.dat"
5 #define BUFFER_SIZE 512
6 // 假设该文件大于或等于BUFFER_SIZE
7
8 VOID CALLBACK MyFileIOCompletionRoutine(
9 DWORD dwErrorCode, // 对于此次操作返回的状态
10 DWORD dwNumberOfBytesTransfered, // 告诉已经操作了多少字节,也就是在IRP里的Infomation
11 LPOVERLAPPED lpOverlapped // 这个数据结构
12 )
13 {
14 printf( " IO operation end!\n " );
15 }
16
17 int main()
18 {
19 HANDLE hDevice =
20 CreateFile( " test.dat " ,
21 GENERIC_READ | GENERIC_WRITE,
22 0 ,
23 NULL,
24 OPEN_EXISTING,
25 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // 此处设置FILE_FLAG_OVERLAPPED
26 NULL );
27
28 if (hDevice == INVALID_HANDLE_VALUE)
29 {
30 printf( " Read Error\n " );
31 return 1 ;
32 }
33
34 UCHAR buffer[BUFFER_SIZE];
35
36 // 初始化overlap使其内部全部为零
37 // 不用初始化事件!!
38 OVERLAPPED overlap = { 0 };
39
40 // 这里没有设置OVERLAP参数,因此是异步操作
41 ReadFileEx(hDevice, buffer, BUFFER_SIZE, & overlap,MyFileIOCompletionRoutine);
42
43 // 做一些其他操作,这些操作会与读设备并行执行
44
45 // 进入alterable
46 SleepEx( 0 ,TRUE);
47
48 CloseHandle(hDevice);
49
50 return 0 ;
51 }
52
53
示例代码 P255

2IRP的同步完成和异步完成

两种方法,一种是在派遣函数中直接结束IRP,称为同步方法。另外一种是在派遣函数中不结束IRP,而让派遣函数直接返回,IRP在以后某个时候再进行处理,也称为异步方法。

比如在ReadFile中同步时,派遣函数在调用IoCompleteRequest时,IoCompleteRequest内部会设置IRPUserEvent事件。

如果在ReadFile异步时,ReadFile不创建事件,但是接收overlap参数,IoCompleteRequest内部会设置overlap提供的事件。

ReadFileEx异步中,IoCompleteRequestReadFileEx提供的回调函数插入到APC队列中。

异步时,通过GetLastError()得到ERROR_IO_INCOMPLETE。如果派遣函数不调用IoCompleteRequest,此时IRP处于挂起状态。处理方法见示例代码中。

同时,应用程序关闭设置时产生IRP_MJ_CLEANUP类型的IRP。可以在IRP_MJ_CLEANUP的派遣函数中调用IoCompleteRequest来结束那些挂起的IRP请求。

27、Windows内核编程,IRP的同步(1) 代码
   
     
// .h
#pragma once

#ifdef __cplusplus
extern " C "
{
#endif
#include
< NTDDK.h >
#ifdef __cplusplus
}
#endif

#define PAGEDCODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")

#define PAGEDDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")

#define arraysize(p) (sizeof(p)/sizeof((p)[0]))

typedef
struct _DEVICE_EXTENSION {
PDEVICE_OBJECT pDevice;
UNICODE_STRING ustrDeviceName;
// 设备名称
UNICODE_STRING ustrSymLinkName; // 符号链接名
PLIST_ENTRY pIRPLinkListHead;
} DEVICE_EXTENSION,
* PDEVICE_EXTENSION;

typedef
struct _MY_IRP_ENTRY
{
PIRP pIRP;
LIST_ENTRY ListEntry;
} MY_IRP_ENTRY,
* PMY_IRP_ENTRY;

// 函数声明

NTSTATUS CreateDevice (IN PDRIVER_OBJECT pDriverObject);
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject);
NTSTATUS HelloDDKDispatchRoutin(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp);
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp);
NTSTATUS HelloDDKCleanUp(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp);
// .cpp
#include " Driver.h "

/* ***********************************************************************
* 函数名称:DriverEntry
* 功能描述:初始化驱动程序,定位和申请硬件资源,创建内核对象
* 参数列表:
pDriverObject:从I/O管理器中传进来的驱动对象
pRegistryPath:驱动程序在注册表的中的路径
* 返回 值:返回初始化驱动状态
************************************************************************
*/
#pragma INITCODE
extern " C " NTSTATUS DriverEntry (
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath )
{
NTSTATUS status;
KdPrint((
" Enter DriverEntry\n " ));

// 设置卸载函数
pDriverObject -> DriverUnload = HelloDDKUnload;

// 设置派遣函数
pDriverObject -> MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutin;
pDriverObject
-> MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutin;
pDriverObject
-> MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutin;
pDriverObject
-> MajorFunction[IRP_MJ_READ] = HelloDDKRead;
pDriverObject
-> MajorFunction[IRP_MJ_CLEANUP] = HelloDDKCleanUp;
pDriverObject
-> MajorFunction[IRP_MJ_DEVICE_CONTROL] = HelloDDKDispatchRoutin;
pDriverObject
-> MajorFunction[IRP_MJ_SET_INFORMATION] = HelloDDKDispatchRoutin;
pDriverObject
-> MajorFunction[IRP_MJ_SHUTDOWN] = HelloDDKDispatchRoutin;
pDriverObject
-> MajorFunction[IRP_MJ_SYSTEM_CONTROL] = HelloDDKDispatchRoutin;

// 创建驱动设备对象
status = CreateDevice(pDriverObject);

KdPrint((
" Leave DriverEntry\n " ));
return status;
}

/* ***********************************************************************
* 函数名称:CreateDevice
* 功能描述:初始化设备对象
* 参数列表:
pDriverObject:从I/O管理器中传进来的驱动对象
* 返回 值:返回初始化状态
************************************************************************
*/
#pragma INITCODE
NTSTATUS CreateDevice (
IN PDRIVER_OBJECT pDriverObject)
{
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;

// 创建设备名称
UNICODE_STRING devName;
RtlInitUnicodeString(
& devName,L " \\Device\\MyDDKDevice " );

// 创建设备
status = IoCreateDevice( pDriverObject,
sizeof (DEVICE_EXTENSION),
& (UNICODE_STRING)devName,
FILE_DEVICE_UNKNOWN,
0 , TRUE,
& pDevObj );
if ( ! NT_SUCCESS(status))
return status;

pDevObj
-> Flags |= DO_BUFFERED_IO;
pDevExt
= (PDEVICE_EXTENSION)pDevObj -> DeviceExtension;
pDevExt
-> pDevice = pDevObj;
pDevExt
-> ustrDeviceName = devName;

pDevExt
-> pIRPLinkListHead = (PLIST_ENTRY)ExAllocatePool(PagedPool, sizeof (LIST_ENTRY));
InitializeListHead(pDevExt
-> pIRPLinkListHead);

// 创建符号链接
UNICODE_STRING symLinkName;
RtlInitUnicodeString(
& symLinkName,L " \\??\\HelloDDK " );
pDevExt
-> ustrSymLinkName = symLinkName;
status
= IoCreateSymbolicLink( & symLinkName, & devName );
if ( ! NT_SUCCESS(status))
{
IoDeleteDevice( pDevObj );
return status;
}
return STATUS_SUCCESS;
}

/* ***********************************************************************
* 函数名称:HelloDDKUnload
* 功能描述:负责驱动程序的卸载操作
* 参数列表:
pDriverObject:驱动对象
* 返回 值:返回状态
************************************************************************
*/
#pragma PAGEDCODE
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject)
{
PDEVICE_OBJECT pNextObj;
KdPrint((
" Enter DriverUnload\n " ));
pNextObj
= pDriverObject -> DeviceObject;
while (pNextObj != NULL)
{
PDEVICE_EXTENSION pDevExt
= (PDEVICE_EXTENSION)
pNextObj
-> DeviceExtension;

// 删除符号链接
UNICODE_STRING pLinkName = pDevExt -> ustrSymLinkName;
IoDeleteSymbolicLink(
& pLinkName);

ExFreePool(pDevExt
-> pIRPLinkListHead);

pNextObj
= pNextObj -> NextDevice;
IoDeleteDevice( pDevExt
-> pDevice );
}
}

/* ***********************************************************************
* 函数名称:HelloDDKDispatchRoutin
* 功能描述:对读IRP进行处理
* 参数列表:
pDevObj:功能设备对象
pIrp:从IO请求包
* 返回 值:返回状态
************************************************************************
*/
#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutin(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint((
" Enter HelloDDKDispatchRoutin\n " ));

PIO_STACK_LOCATION stack
= IoGetCurrentIrpStackLocation(pIrp);
// 建立一个字符串数组与IRP类型对应起来
static char * irpname[] =
{
" IRP_MJ_CREATE " ,
" IRP_MJ_CREATE_NAMED_PIPE " ,
" IRP_MJ_CLOSE " ,
" IRP_MJ_READ " ,
" IRP_MJ_WRITE " ,
" IRP_MJ_QUERY_INFORMATION " ,
" IRP_MJ_SET_INFORMATION " ,
" IRP_MJ_QUERY_EA " ,
" IRP_MJ_SET_EA " ,
" IRP_MJ_FLUSH_BUFFERS " ,
" IRP_MJ_QUERY_VOLUME_INFORMATION " ,
" IRP_MJ_SET_VOLUME_INFORMATION " ,
" IRP_MJ_DIRECTORY_CONTROL " ,
" IRP_MJ_FILE_SYSTEM_CONTROL " ,
" IRP_MJ_DEVICE_CONTROL " ,
" IRP_MJ_INTERNAL_DEVICE_CONTROL " ,
" IRP_MJ_SHUTDOWN " ,
" IRP_MJ_LOCK_CONTROL " ,
" IRP_MJ_CLEANUP " ,
" IRP_MJ_CREATE_MAILSLOT " ,
" IRP_MJ_QUERY_SECURITY " ,
" IRP_MJ_SET_SECURITY " ,
" IRP_MJ_POWER " ,
" IRP_MJ_SYSTEM_CONTROL " ,
" IRP_MJ_DEVICE_CHANGE " ,
" IRP_MJ_QUERY_QUOTA " ,
" IRP_MJ_SET_QUOTA " ,
" IRP_MJ_PNP " ,
};

UCHAR type
= stack -> MajorFunction;
if (type >= arraysize(irpname))
KdPrint((
" - Unknown IRP, major type %X\n " , type));
else
KdPrint((
" \t%s\n " , irpname[type]));


// 对一般IRP的简单操作,后面会介绍对IRP更复杂的操作
NTSTATUS status = STATUS_SUCCESS;
// 完成IRP
pIrp -> IoStatus.Status = status;
pIrp
-> IoStatus.Information = 0 ; // bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );

KdPrint((
" Leave HelloDDKDispatchRoutin\n " ));

return status;
}

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint((
" Enter HelloDDKRead\n " ));

PDEVICE_EXTENSION pDevExt
= (PDEVICE_EXTENSION)
pDevObj
-> DeviceExtension;

PMY_IRP_ENTRY pIrp_entry
= (PMY_IRP_ENTRY)ExAllocatePool(PagedPool, sizeof (MY_IRP_ENTRY));

pIrp_entry
-> pIRP = pIrp;

// 插入队列
InsertHeadList(pDevExt -> pIRPLinkListHead, & pIrp_entry -> ListEntry);

// 将IRP设置为挂起
IoMarkIrpPending(pIrp);

KdPrint((
" Leave HelloDDKRead\n " ));

// 返回pending状态
return STATUS_PENDING;
}

NTSTATUS HelloDDKCleanUp(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint((
" Enter HelloDDKCleanUp\n " ));

PDEVICE_EXTENSION pDevExt
= (PDEVICE_EXTENSION)
pDevObj
-> DeviceExtension;

// (1)将存在队列中的IRP逐个出队列,并处理

PMY_IRP_ENTRY my_irp_entry;
while ( ! IsListEmpty(pDevExt -> pIRPLinkListHead))
{
PLIST_ENTRY pEntry
= RemoveHeadList(pDevExt -> pIRPLinkListHead);
my_irp_entry
= CONTAINING_RECORD(pEntry,
MY_IRP_ENTRY,
ListEntry);
my_irp_entry
-> pIRP -> IoStatus.Status = STATUS_SUCCESS;
my_irp_entry
-> pIRP -> IoStatus.Information = 0 ; // bytes xfered
IoCompleteRequest( my_irp_entry -> pIRP, IO_NO_INCREMENT );

ExFreePool(my_irp_entry);
}

// (2)处理IRP_MJ_CLEANUP的IRP
NTSTATUS status = STATUS_SUCCESS;
// 完成IRP
pIrp -> IoStatus.Status = status;
pIrp
-> IoStatus.Information = 0 ; // bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );

KdPrint((
" Leave HelloDDKCleanUp\n " ));
return STATUS_SUCCESS;
}
// 应用程序
#include < windows.h >
#include
< stdio.h >

int main()
{
HANDLE hDevice
=
CreateFile(
" \\\\.\\HelloDDK " ,
GENERIC_READ
| GENERIC_WRITE,
0 ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_OVERLAPPED, // 此处设置FILE_FLAG_OVERLAPPED
NULL );

if (hDevice == INVALID_HANDLE_VALUE)
{
printf(
" Open Device failed! " );
return 1 ;
}

OVERLAPPED overlap1
= { 0 };
OVERLAPPED overlap2
= { 0 };

UCHAR buffer[
10 ];
ULONG ulRead;

BOOL bRead
= ReadFile(hDevice,buffer, 10 , & ulRead, & overlap1);
if ( ! bRead && GetLastError() == ERROR_IO_PENDING)
{
printf(
" The operation is pending\n " );
}
bRead
= ReadFile(hDevice,buffer, 10 , & ulRead, & overlap2);
if ( ! bRead && GetLastError() == ERROR_IO_PENDING)
{
printf(
" The operation is pending\n " );
}

// 迫使程序中止2秒
Sleep( 2000 );

// 创建IRP_MJ_CLEANUP IRP
CloseHandle(hDevice);

return 0 ;
}
示例代码 P260

取消IRP

PDRIVER_CANCEL

  IoSetCancelRoutine(

    IN PIRP  Irp,

    IN PDRIVER_CANCEL  CancelRoutine

    );

来设置取消IRP请求的回调函数。也可以用来删除取消例程,当CancelRoutine为空指针时,则删除原来设置的取消例程。

       IoCancelIrp函数指定取消IRP请求。在IoCancelIrp内部,通过cancel lock来进行同步。

IoReleaseCancelSpinLock

IoAcquireCancelSpinLock

       可以用CancelIo API 来取消IRP请求。在CancelIo内部会枚举所有没有被完成的IRP,依次调用IoCancelIrp。如果应用程序没有调用CancelIo,在应用程序关闭时也会自动调用CancelIo

       注意:cancel lock是全局自旋锁,占用时间不宜太长。

27、Windows内核编程,IRP的同步(1) 代码
   
     
1 VOID
2 CancelReadIRP(
3 IN PDEVICE_OBJECT DeviceObject,
4 IN PIRP Irp
5 )
6 {
7 KdPrint(( " Enter CancelReadIRP\n " ));
8
9 PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
10 DeviceObject -> DeviceExtension;
11
12 // 设置完成状态为STATUS_CANCELLED
13 Irp -> IoStatus.Status = STATUS_CANCELLED;
14 Irp -> IoStatus.Information = 0 ; // bytes xfered
15 IoCompleteRequest( Irp, IO_NO_INCREMENT );
16
17 // 释放Cancel自旋锁
18 IoReleaseCancelSpinLock(Irp -> CancelIrql);
19
20 KdPrint(( " Leave CancelReadIRP\n " ));
21 }
22
23
24 NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
25 IN PIRP pIrp)
26 {
27 KdPrint(( " Enter HelloDDKRead\n " ));
28
29 PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
30 pDevObj -> DeviceExtension;
31
32 IoSetCancelRoutine(pIrp,CancelReadIRP);
33
34 // 将IRP设置为挂起
35 IoMarkIrpPending(pIrp);
36
37 KdPrint(( " Leave HelloDDKRead\n " ));
38
39 // 返回pending状态
40 return STATUS_PENDING;
41 }
42
43

27、Windows内核编程,IRP的同步(1) 代码
   
     
1 #include < windows.h >
2 #include < stdio.h >
3
4 int main()
5 {
6 HANDLE hDevice =
7 CreateFile( " \\\\.\\HelloDDK " ,
8 GENERIC_READ | GENERIC_WRITE,
9 0 ,
10 NULL,
11 OPEN_EXISTING,
12 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // 此处设置FILE_FLAG_OVERLAPPED
13 NULL );
14
15 if (hDevice == INVALID_HANDLE_VALUE)
16 {
17 printf( " Open Device failed! " );
18 return 1 ;
19 }
20
21 OVERLAPPED overlap1 = { 0 };
22 OVERLAPPED overlap2 = { 0 };
23
24 UCHAR buffer[ 10 ];
25 ULONG ulRead;
26
27 BOOL bRead = ReadFile(hDevice,buffer, 10 , & ulRead, & overlap1);
28 if ( ! bRead && GetLastError() == ERROR_IO_PENDING)
29 {
30 printf( " The operation is pending\n " );
31 }
32 bRead = ReadFile(hDevice,buffer, 10 , & ulRead, & overlap2);
33 if ( ! bRead && GetLastError() == ERROR_IO_PENDING)
34 {
35 printf( " The operation is pending\n " );
36 }
37
38 // 迫使程序中止2秒
39 Sleep( 2000 );
40
41 // 显式的调用CancelIo,其实在关闭设备时会自动运行CancelIo
42 CancelIo(hDevice);
43
44 // 创建IRP_MJ_CLEANUP IRP
45 CloseHandle(hDevice);
46
47 return 0 ;
48 }
示例代码 P264

 

 

 

你可能感兴趣的:(windows)