備註:
1.在下面的討論中,"輸入"表示資料從使用者模式的應用程式到驅動程式,"輸出"表示資料從驅動程式到應用程式。
2.IRP 中的 SystemBuffer 欄位包含系統地址。UserBuffer 欄位包含初始的使用者緩衝區地址。
參考文章連結:
https://www.cnblogs.com/endenvor/p/9057856.html/ ,個人覺得原文有些亂,並且程式碼不完整,在此做了整理和補充。
一、I/O裝置控制操作
R3通過DeviceControl函式來寫入和讀取R0的資料。R0層通過一下幾種方式讀取R3傳送的資料和向R3傳送資料。
(1)"緩衝"記憶體IOCTL -- METHOD_BUFFERED
對於 IOCTL 請求,會分配一個容量大小足以包含輸入緩衝區或輸出緩衝區的系統緩衝區,並將 SystemBuffer 設定為分配的緩衝區地址。輸入緩衝區中的資料複製到系統緩衝區。UserBuffer 欄位設定為使用者模式輸出緩衝區地址。核心模式驅動程式應當只使用系統緩衝區,且不應使用 UserBuffer 中儲存的地址。
對於 IOCTL,驅動程式應當從系統緩衝區獲取輸入並將輸出寫入到系統緩衝區。當完成請求時,I/O 系統將輸出資料從系統緩衝區複製到使用者緩衝區。(ioctl是裝置驅動程式中對裝置的I/O通道進行管理的函式。所謂對I/O通道進行管理,就是對裝置的一些特性進行控制,例如串列埠的傳輸波特率、馬達的轉速等等。)
輸入緩衝區大小:stack->Parameters.DeviceIoControl.InputBufferLength
輸出緩衝區大小:stack->Parameters.DeviceIoControl.OutputBufferLength
輸入緩衝區:pIrp->AssociatedIrp.SystemBuffer
輸出緩衝區:pIrp->AssociatedIrp.SystemBuffer
(2)"直接"方法IOCTL -- METHOD_IN_DIRECT/METHOD_OUT_DIRECT
對於讀取和寫入請求,使用者模式緩衝區會被鎖定,並且會重新對映一個記憶體描述符列表 (MDL)。MDL 地址會儲存在 IRP 的 MdlAddress 欄位中。根據 Irp->MdlAddress 重新為這片實體地址對映一份虛擬地址。通過這份虛擬地址向R3層傳送資料。
SystemBuffer 和 UserBuffer 均沒有任何含義。但是,驅動程式不應當更改這些欄位的值。
輸入緩衝區大小:stack->Parameters.DeviceIoControl.InputBufferLength
輸出緩衝區大小:stack->Parameters.DeviceIoControl.OutputBufferLength
輸入緩衝區:MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority)
輸出緩衝區:pIrp->AssociatedIrp.SystemBuffer
METHOD_IN_DIRECT/METHOD_OUT_DIRECT區別:
1) 只讀許可權開啟裝置,METHOD_IN_DIRECT的IOCTL操作成功,而METHOD_OUT_DIRECT的IOCTL操作失敗
2) 讀寫許可權開啟裝置,METHOD_IN_DIRECT與METHOD_OUT_DIRECT的IOCTL操作都成功
(3)"兩種都不"方法IOCTL(其他記憶體模式) -- MEHTOD_NEITHER
對於 IOCTL 請求,I/O 管理器將 UserBuffer 設定為初始的使用者輸出緩衝區,而且,它將當前 I/O 棧位置的 Parameters.DeviceIoControl.Type3InputBuffer 設定為使用者輸入緩衝區。利用該 I/O 方法,由驅動程式來確定如何處理緩衝區:分配系統緩衝區或建立 MDL。
輸入緩衝區大小:stack->Parameters.DeviceIoControl.InputBufferLength
輸出緩衝區大小:stack->Parameters.DeviceIoControl.OutputBufferLength
輸入緩衝區:ProbeForRead(stack->Parameters.DeviceIoControl.Type3InputBuffer)
輸出緩衝區:ProbeForWrite(pIrp->UserBuffer)
二、讀寫操作
分為一下三種,ReadFile,WirteFile方式的緩衝區裝置讀寫,直接方式讀寫,和其他方式讀寫(DO_BUFFERED_IO、DO_DIRECT_IO、METHOD_NEITHER)。
在R3層通過 ReadFile,WriteFile 函式進行讀寫資料,在R0層通過 IRP_MJ_READ 與 IRP_MJ_WRITE 對應的派遣函式中處理以下三種快取區來進行資料讀寫。
(1)緩衝區方式讀寫 -- DO_BUFFERED_IO
在建立 Device 後,須要指定方式為 Device 的 Flags 有 DO_BUFFERED_IO。通過應用層 Api 函式 ReadFile,WriteFile 等函式。ntoskrnl.exe建立Irp後,ReadFile和WriteFile引數的緩衝區就在irp->AssociatedIrp.Systembuffer。
同時要求讀寫的偏移量,和長度都在 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp) 資料型別中的stack->Parameters.Read.Length,stack->Parameters.Read.ByteOffset(該型別為Large_Interge型別)。
對於讀取請求,I/O 管理器分配一個與使用者模式的緩衝區大小相同的系統緩衝區 SystemBuffer。當完成請求時,I/O 管理器將驅動程式已經提供的資料從系統緩衝區複製到使用者緩衝區。
對於寫入請求,會分配一個系統緩衝區並將 SystemBuffer 設定為地址。使用者緩衝區的內容會被複制到系統緩衝區,但是不設定 UserBuffer。
ReadFile /WriteFile
讀取/寫入位元組數:stack->Parameters.Read.Length
偏移:stack->Parameters.Read.ByteOffset.QuadPart
輸出/寫入緩衝區:pIrp->AssociatedIrp.SystemBuffer
輸出/返回位元組數:pIrp->IoStatus.Information
IRP_MJ_QUERY_INFORMATION
FILE_INFORMATION_CLASS: stack->Parameters.QueryFile.FileInformation
輸入輸出緩衝區:pIrp->AssociatedIrp.SystemBuffer
返回位元組數:pIrp->IoStatus.Information=stack->Parameters.QueryFile.Length
(2)直接方式讀寫 -- DO_DIRECT_IO
在建立 Device 後,須要指定方式為 Device 的 Flags 有 DO_DIRECT_IO 。通過應用層 APi 函式 ReadFile,WriteFile 等函式,ntoskrnl.exe建立的Irp後,ReadFile 和 WriteFile 引數的緩衝區將被鎖住,然後作業系統將這段緩衝區在核心模式地址再次對映一遍,這樣應用層的緩衝區和記憶體層的就指向同一個實體記憶體。而核心模式用MDL資料結構記錄這段記憶體,這個虛擬記憶體大小在MmGetByteCount(pIrp->MdlAddress),首地址在MmGetMdlVirtualAddress(pIrp->MdlAddress);
偏移量為MmGetMdlByteOffset(pIrp->MdlAddress)(這裡的偏移量不是檔案讀寫的偏移量,而是在MDL中的偏移量),然後檔案的長度還是stack->Parameters.Read.Length,這個值和MmGetByteCount(pIrp->MdlAddress)是一樣的,要不然就出錯了,而真正的讀寫偏移量還是在stack->Parameters.Read.ByteOffset。
這裡無論是讀還是寫,都要得到MDL在核心模式下的對映,因此還要用MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority)在核心中根據 pIrp->MdlAddress 重新為其對應的實體地址對映一份虛擬地址,然後可以讀寫該地址,就會轉化到應用層相應的記憶體。
讀取/寫入位元組數:stack->Parameters.Read.Length
偏移:stack->Parameters.Read.ByteOffset.QuadPart
輸出/寫入緩衝區:MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority)
輸出/返回位元組數:pIrp->IoStatus.Information
(3)其他方式讀寫 -- METHOD_NEITHER
在建立 Device 後,Flags 既不標誌 DO_BUFFERED_IO 也不標誌 DO_DIRECT_IO,ReadFile 和 WriteFile 提供的緩衝區記憶體地址,可以再 IRP 的 pIrp->UserBuffer 欄位得到,而長度和偏移量還是在 stack->Paameters.Read 中。
但是用這種方法須要注意的是 ReadFile 可能把空指標地址或者非法地址傳遞給驅動程式,因此驅動程式使用使用者模式地址錢須要檢查是否可讀或者可寫,可以用 ProbeForWrite 或者 ProbeForWrite 函式和try模組。
對於寫請求,UserBuffer 欄位被設定為指向輸出緩衝區。
對於讀請求,Type3InputBuffer 欄位被設定為輸入緩衝區。不執行任何其他操作。
SystemAddress 和 MdlAddress 沒有任何含義。
讀取/寫入位元組數:stack->Parameters.Read.Length
偏移:stack->Parameters.Read.ByteOffset.QuadPart
輸出/寫入緩衝區:ProbeForWrite(pIrp->UserBuffer)
輸出/返回位元組數:pIrp->IoStatus.Information
R0層程式碼:
#include <ntddk.h> #define CTRL_CODE1 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_IN_DIRECT, FILE_ANY_ACCESS) // "直接"方法 #define CTRL_CODE2 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS) // "緩衝"方法 #define CTRL_CODE3 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS) // "兩者都不"方法 // 建立一個裝置物件 NTSTATUS CreateDevice(PDRIVER_OBJECT DriverObject, PUNICODE_STRING DeviceName, PUNICODE_STRING SymbolName, UINT32 DO) { PDEVICE_OBJECT DeviceObject = NULL; // 1. 建立一個裝置物件 NTSTATUS Status = IoCreateDevice(DriverObject, 0, DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject); if (NT_SUCCESS(Status)) { // 2. 新增符號連結名 Status = IoCreateSymbolicLink(SymbolName, DeviceName); // 3. 設定裝置的通訊方式 DeviceObject->Flags |= DO; } // 4. 設定返回值 return Status; } NTSTATUS DefaultProc(struct _DEVICE_OBJECT *DeviceObject, struct _IRP *Irp) { UNREFERENCED_PARAMETER(DeviceObject); // 1. 設定處理的位元組數量 Irp->IoStatus.Information = 0; // 2. 設定狀態 Irp->IoStatus.Status = STATUS_SUCCESS; // 3. 表示已完成所有IRP處理 IoCompleteRequest(Irp, IO_NO_INCREMENT); // 4. 設定返回值 return STATUS_SUCCESS; } // 不設定這個函式,則Ring3呼叫CreateFile會返回1 // IRP_MJ_CREATE 處理函式 NTSTATUS CreateProc(struct _DEVICE_OBJECT *DeviceObject, struct _IRP *Irp) { UNREFERENCED_PARAMETER(DeviceObject); // 1. 設定處理的位元組數量 Irp->IoStatus.Information = 0; // 2. 設定狀態 Irp->IoStatus.Status = STATUS_SUCCESS; // 3. 表示已完成所有IRP處理 IoCompleteRequest(Irp, IO_NO_INCREMENT); // 4. 設定返回值 return Irp->IoStatus.Status; } /*******************************緩衝區方式讀寫操作*********************************/ NTSTATUS WriteProc(struct _DEVICE_OBJECT *DeviceObject, struct _IRP *Irp) { UNREFERENCED_PARAMETER(DeviceObject); PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(Irp); ULONG WriteSize = pstack->Parameters.Write.Length; char *Buffer = NULL; // 判斷讀寫方式 if (Irp->AssociatedIrp.SystemBuffer) // DO_BUFFERED_IO 緩衝方法 Buffer = Irp->AssociatedIrp.SystemBuffer; else if (Irp->MdlAddress) // DO_DIRECT_IO 直接方法 Buffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); else if (Irp->UserBuffer) // METHOD_NEITHER 方法 Buffer = Irp->UserBuffer; // 對該地址區域寫入一些資料,讓程式讀出去 KdPrint(("緩衝區方式讀寫操作 - 來自R3的資料:%S\n", Buffer)); Irp->IoStatus.Information = WriteSize; Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS ReadProc(struct _DEVICE_OBJECT *DeviceObject, struct _IRP *Irp) { UNREFERENCED_PARAMETER(DeviceObject); char *Buffer = NULL; // 判斷讀寫方式 if (Irp->AssociatedIrp.SystemBuffer) // DO_BUFFERED_IO 緩衝方法 Buffer = Irp->AssociatedIrp.SystemBuffer; else if (Irp->MdlAddress) // DO_DIRECT_IO 直接方法 Buffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); else if (Irp->UserBuffer) // METHOD_NEITHER 方法 Buffer = Irp->UserBuffer; // 對該地址區域寫入一些資料,讓程式讀出去 RtlCopyMemory(Buffer, L"Hello 15PB", 22); Irp->IoStatus.Information = 22; Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } /*******************************IO裝置控制操作**********************************/ NTSTATUS DeviceIoCtrlProc(struct _DEVICE_OBJECT *DeviceObject, struct _IRP *Irp) { UNREFERENCED_PARAMETER(DeviceObject); PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(Irp); ULONG CtrlCode = pStack->Parameters.DeviceIoControl.IoControlCode; ULONG OutputLength = pStack->Parameters.DeviceIoControl.OutputBufferLength; ULONG Information = 0; switch (CtrlCode) { // "直接"方法 case CTRL_CODE1: { // 輸入緩衝I/O方法,來自R3的資料列印 char *SystemBuffer = Irp->AssociatedIrp.SystemBuffer; KdPrint(("IO裝置控制操作 - 緩衝方式 - 來自R3的資料:%S\n", SystemBuffer)); Information = 64; // 直接輸出 // MDL 地址會儲存在 IRP 的 MdlAddress 欄位中。根據 Irp->MdlAddress 重新為這片實體地址對映一份虛擬地址。 // MmGetSystemAddressForMdlSafe是一個巨集,它為MDL描述的緩衝區返回一個非分頁的系統空間虛擬地址。 char *MdlBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); // Irp->MdlAddress:0xfffffa80`1afe6010 RtlCopyMemory(MdlBuffer, L"CTRL_CODE1 - METHOD_IN_DIRECT\n", Information); // MdlBuffer:0xfffff880`0bf968f0 break; } // 輸入輸出緩衝I/O方法 case CTRL_CODE2: { // IRP 中的 SystemBuffer 欄位包含系統地址。UserBuffer 欄位包含初始的使用者緩衝區地址。 // 對於讀取請求,I/O 管理器分配一個與使用者模式的緩衝區大小相同的系統緩衝區。 // 當完成請求時,I/O 管理器將驅動程式已經提供的資料從系統緩衝區複製到使用者緩衝區。 // 對於寫入請求,會分配一個系統緩衝區並將 SystemBuffer 設定為地址。 // 使用者緩衝區的內容會被複制到系統緩衝區,但是不設定 UserBuffer。 char *Buffer = Irp->AssociatedIrp.SystemBuffer; KdPrint(("IO裝置控制操作 - 緩衝方式 - 來自R3的資料:%S\n", Buffer)); // 輸入緩衝I/O方法,來自R3的資料列印 Information = 62; RtlCopyMemory(Buffer, L"CTRL_CODE2 - METHOD_BUFFERED\n", Information); // 輸出緩衝I/O方法,R0傳送資料 break; } // "兩種都不"方法 case CTRL_CODE3: { // 對於寫請求,UserBuffer 欄位被設定為指向輸出緩衝區。 // 對於讀請求,Type3InputBuffer 欄位被設定為輸入緩衝區。 // 不執行任何其他操作。SystemAddress 和 MdlAddress 沒有任何含義。 char *InBuffer = pStack->Parameters.DeviceIoControl.Type3InputBuffer; char *OutBuffer = Irp->UserBuffer; KdPrint(("IO裝置控制操作 - 兩種都不方式 - 來自R3的資料:%S\n", InBuffer)); // "兩種都不"方法,來自R3的資料列印 Information = 60; RtlCopyMemory(OutBuffer, L"CTRL_CODE3 - METHOD_NEITHER\n", Information); break; } default: KdPrint(("failed!\n")); break; } // 1. 設定處理的位元組數量 Irp->IoStatus.Information = OutputLength; // 2. 設定狀態 Irp->IoStatus.Status = STATUS_SUCCESS; // 3. 表示已完成所有IRP處理 IoCompleteRequest(Irp, IO_NO_INCREMENT); // 4. 設定返回值 return STATUS_SUCCESS; } // 解除安裝函式 void DriverUnload(PDRIVER_OBJECT DriverObject) { // 獲取裝置物件連結串列 PDEVICE_OBJECT DeviceObject = DriverObject->DeviceObject; for (int i = 0; i < 3; ++i) { IoDeleteDevice(DriverObject->DeviceObject); DeviceObject = DeviceObject->NextDevice; } // 解除安裝裝置符號名 UNICODE_STRING SymbolLinkName1 = RTL_CONSTANT_STRING(L"\\DosDevices\\devicelink1"); UNICODE_STRING SymbolLinkName2 = RTL_CONSTANT_STRING(L"\\DosDevices\\devicelink2"); UNICODE_STRING SymbolLinkName3 = RTL_CONSTANT_STRING(L"\\DosDevices\\devicelink3"); IoDeleteSymbolicLink(&SymbolLinkName1); IoDeleteSymbolicLink(&SymbolLinkName2); IoDeleteSymbolicLink(&SymbolLinkName3); } NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING UnicodeString) { //DbgBreakPoint(); UNREFERENCED_PARAMETER(UnicodeString); // 建立不同讀寫方式的裝置 UNICODE_STRING DeviceName1 = RTL_CONSTANT_STRING(L"\\Device\\device1"); UNICODE_STRING SymbolName1 = RTL_CONSTANT_STRING(L"\\DosDevices\\devicelink1"); if (NT_SUCCESS(CreateDevice(DriverObject, &DeviceName1, &SymbolName1, DO_BUFFERED_IO))) KdPrint(("Create device success: \\Device\\device1\n")); UNICODE_STRING DeviceName2 = RTL_CONSTANT_STRING(L"\\Device\\device2"); UNICODE_STRING SymbolName2 = RTL_CONSTANT_STRING(L"\\DosDevices\\devicelink2"); if (NT_SUCCESS(CreateDevice(DriverObject, &DeviceName2, &SymbolName2, DO_DIRECT_IO))) KdPrint(("Create device success: \\Device\\device2\n")); UNICODE_STRING DeviceName3 = RTL_CONSTANT_STRING(L"\\Device\\device3"); UNICODE_STRING SymbolName3 = RTL_CONSTANT_STRING(L"\\DosDevices\\devicelink3"); if (NT_SUCCESS(CreateDevice(DriverObject, &DeviceName3, &SymbolName3, 0))) KdPrint(("Create device success: \\Device\\device3\n")); // 設定解除安裝函式 DriverObject->DriverUnload = DriverUnload; // 初始化IRP派遣函式 for (UINT32 i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) DriverObject->MajorFunction[i] = DefaultProc; DriverObject->MajorFunction[IRP_MJ_CREATE] = CreateProc; DriverObject->MajorFunction[IRP_MJ_READ] = ReadProc; DriverObject->MajorFunction[IRP_MJ_WRITE] = WriteProc; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceIoCtrlProc; return STATUS_SUCCESS; }
R3層程式碼:
#include <cstdio> #include <windows.h> #include <tchar.h> #define CTRL_CODE1 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_IN_DIRECT, FILE_ANY_ACCESS) // "直接"方法 #define CTRL_CODE2 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS) // "緩衝"方法 #define CTRL_CODE3 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS) // "兩者都不"方法 int main() { _tsetlocale(0, _T("Chinese-simplified")); //支援中文 // 建立核心物件的裝置物件連線 --- 對應驅動層中 IRP_MJ_WRITE 請求的過濾函式 HANDLE DeviceHandle1 = CreateFile(L"\\\\.\\devicelink1", FILE_ALL_ACCESS, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); HANDLE DeviceHandle2 = CreateFile(L"\\\\.\\devicelink2", FILE_ALL_ACCESS, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); HANDLE DeviceHandle3 = CreateFile(L"\\\\.\\devicelink3", FILE_ALL_ACCESS, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); WCHAR ReadBuffer[100] = { 0 }; DWORD ReadByte = 0; DWORD WriteByte = 0; /**********************************************************************************************/ // 使用WriteFile、ReadFile函式傳送資料 --- 對應驅動層中 IRP_MJ_CREATE 與 IRP_MJ_READ 請求的過濾函式 // 根據驅動中Device建立時所選擇的通訊緩衝方法,進行資料傳送 wprintf(L"緩衝區方式讀寫操作:\n"); printf("(1)device1 - DO_BUFFERED_IO:\n"); WriteFile(DeviceHandle1, L"device1 - DO_BUFFERED_IO", 52, &WriteByte, NULL); // DeviceHandle1 建立時選擇了 DO_BUFFERED_IO 緩衝方法 printf("\tWriteBytes: %d\n", WriteByte); ReadFile(DeviceHandle1, ReadBuffer, 100, &ReadByte, NULL); wprintf(L"\tRead: %d(%s)\n\n", ReadByte, ReadBuffer); // 列印出22<Hello 15PB>,這是對的 printf("(2)device2 - DO_DIRECT_IO:\n"); WriteFile(DeviceHandle2, L"device2 - DO_DIRECT_IO", 48, &WriteByte, NULL); // DeviceHandle2 建立時選擇了 DO_DIRECT_IO 直接方法 printf("\tWriteBytes: %d\n", WriteByte); ReadFile(DeviceHandle2, ReadBuffer, 100, &ReadByte, NULL); wprintf(L"\tRead: %d(%s)\n\n", ReadByte, ReadBuffer); // 列印出22<Hello 15PB>,這是對的 printf("這個有BUG\n"); printf("(3)device3 - METHOD_NEITHER:\n"); WriteFile(DeviceHandle3, L"device3 - METHOD_NEITHER", 32, &WriteByte, NULL); // DeviceHandle3 建立時選擇了 METHOD_NEITHER 方法 printf("\tdevice3 - Write: %d\n", WriteByte); ReadFile(DeviceHandle3, ReadBuffer, 100, &ReadByte, NULL); printf("\tdevice3 - Read: %d(%Ls)\n\t", ReadByte, ReadBuffer); // 列印出22<H>,有bug?? /**********************************************************************************************/ // 使用DeviceIoControl函式傳送資料 --- 對應驅動層中Write與Read的過濾函式 wprintf(L"\n\nIO裝置控制操作:\n"); DeviceIoControl(DeviceHandle1, CTRL_CODE1, (LPVOID)L"CTRL_CODE1", 24, ReadBuffer, 100, &ReadByte, NULL); // "直接"方法 wprintf(L"(1)直接方法: \n\t ReadByte - ReadBuffer: %d - %s\n", ReadByte, ReadBuffer); DeviceIoControl(DeviceHandle1, CTRL_CODE2, (LPVOID)L"CTRL_CODE2", 24, ReadBuffer, 100, &ReadByte, NULL); // 輸入輸出緩衝I/O方法 wprintf(L"(2)輸入輸出緩衝I/O方法: \n\t ReadByte - ReadBuffer: %d - %s\n", ReadByte, ReadBuffer); DeviceIoControl(DeviceHandle1, CTRL_CODE3, (LPVOID)L"CTRL_CODE3", 24, ReadBuffer, 100, &ReadByte, NULL); // 上面方法都不是(METHOD_NEITHER) wprintf(L"(3)METHOD_NEITHER方法: \n\t ReadByte - ReadBuffer: %d - %s\n", ReadByte, ReadBuffer); system("pause"); return 0; }