Создание драйверов. Часть 4. Обмен данными. В данной статье рассмотрены способы обмена данными между приложением и драйвером, при помощи рабочих процедур драйвера с IRP кодом IRP_MJ_READ, IRP_MJ_WRITE и IRP_MJ_DEVICE_CONTROL. Из второй части цикла статей и примеров по созданию и работе с драйверами, мы узнали, как отправлять драйверу IOCTL запросы, в зависимости от которых драйвер будет предпринимать те или иные действия.
В обмене данных между приложением и драйвером активное участие принимают IRP(I/o Request Packet) пакеты, в которых содержится фиксированная часть (заголовок) и изменяющаяся часть (стек), IRP пакет представляет собой структуру, более наглядно представленной на рисунке
Таблица: Заголовок IRP пакета
Для получения указателя на стек IRP пакета используется функция:
PIO_STACK_LOCATION IoGetCurrentIrpStackLocation(IN PIRP Irp);
аргументом, которой является указатель на IRP пакет.
При работе с IRP необходимо указать диспетчеру ввода/вывода (IOManager), каким способом будет происходить управление буферами, существуют три вида:
— буферизованный ввод-вывод (buffered I/O);
— прямой ввод-вывод (direct I/O);
— ввод-вывод без управления (neither I/O).
Установка одного из видов управления осуществляется сразу после создания объекта устройства, то есть после вызова функции IoCreateDevice:
PDEVICE_OBJECT fdo;
status = IoCreateDevice(..,..,..,..,..,..,&fdo);
fdo->Flags |= DO_BUFFERED_IO;
или:
fdo->Flags |= DO_DIRECT_IO;
или:
fdo->Flags |= 0;
Следующим шагом будет добавление рабочих процедур драйвера, для того чтобы он смог обрабатывать запросы из пользовательского приложения, для этого сначала необходимо глобально объявить процедуры:
NTSTATUS TestControl (IN PDEVICE_OBJECT fdo, IN PIRP irp);
NTSTATUS TestWrite (IN PDEVICE_OBJECT fdo, IN PIRP irp);
NTSTATUS TestRead (IN PDEVICE_OBJECT fdo, IN PIRP irp);
и в функции DriverEntry добавить точки входа в драйвер:
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = TestControl;
DriverObject->MajorFunction[IRP_MJ_READ] = TestRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = TestWrite;
Более наглядно взаимодействие приложения и драйвера показано на рисунке:
Рассмотрим обмен данными между приложение и драйвером при помощи функции DeviceIoControl, которая позволяет одновременно передавать в драйвер данные и принимать, для этого служат 3, 4, 5 и 6 аргументы, то есть 3 аргумент, это указатель на данные которые буду передаваться в драйвер, 4 аргумент размер передаваемых данных, соответственно 5 аргумент функции, буфер куда будет происходить прием данных и 6 аргумент размер принятых данных. Более подробное описание функции можно найти в MSDN`е. В прилагаемом примере мною был создан собственный IOCTL запрос при помощи макроса CTL_CODE:
#define IOCTL_FROM_CORE CTL_CODE(0x00008000,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS)
при помощи которого указаны тип устройства, управляющий код, способ получения доступа к буферу и тип доступа, более наглядно вы можете это видеть на следующем рисунке:
char szoutBuff[] = "DeviceIoControl+IOCTL_FROM_CORE"; char szinBuff[80]; ZeroMemory(&szinBuff,sizeof(szinBuff)); if(DeviceIoControl(hHandl,ioctl, &szinBuff,sizeof(szinBuff),//in &szoutBuff,sizeof(szoutBuff),//out &dwReturnBytes,NULL)){/*ok*/}else{/*error*/ }
Рабочая процедура драйвера по обработке IOCTL запросов выглядит следующим образом:
NTSTATUS TestControl (IN PDEVICE_OBJECT fdo, IN PIRP irp) { PVOID pBuffer = NULL; UCHAR usString[] = "www.code.hut1.ru"; PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(irp); ULONG ControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode; ULONG InputLength = IrpStack->Parameters.DeviceIoControl.InputBufferLength; ULONG OutputLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength; pBuffer = irp->UserBuffer; if(ControlCode == IOCTL_FROM_CORE) { #if DBG DbgPrint("IoControlCode IOCTL_FROM_CORE: %d",ControlCode); DbgPrint("TestControl(InputLength): %d",InputLength); DbgPrint("TestControl(OutputLength): %d",OutputLength); DbgPrint("TestControl(pBuffer): %s",pBuffer); #endif RtlCopyMemory(irp->AssociatedIrp.SystemBuffer,&usString,InputLength); } irp->IoStatus.Status = STATUS_SUCCESS; irp->IoStatus.Information = InputLength; IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
Теперь подробнее о коде:
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(irp); — получение указателя на стек пакета;
ULONG ControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode; — получение управляющего IOCTL кода;
ULONG InputLength = IrpStack->Parameters.DeviceIoControl.InputBufferLength; — получение размера передаваемого из приложения буфера;
ULONG OutputLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength; — получение размера буфера которое ожидает приложение;
pBuffer = irp->UserBuffer; — получение переданной строки, в данном случае «DeviceIoControl+IOCTL_FROM_CORE»;
RtlCopyMemory(irp->AssociatedIrp.SystemBuffer,&usString,InputLength); — передача строки приложению, в данном случае «www.code.hut1.ru»;
irp->IoStatus.Information = InputLength; — количество переданных байт.
Следующим шагом будет, запись в драйвер при помощи функции WriteFile:
char outBuffer[] = "This is a test string"; DWORD outCount = sizeof(outBuffer); DWORD bW; if(WriteFile(hHandl,outBuffer,outCount,&bW,NULL)){/*ok*/}else{/*error*/}
Код рабочей процедуры драйвера по обработке IRP с кодом IRP_MJ_WRITE:
NTSTATUS TestWrite(IN PDEVICE_OBJECT fdo, IN PIRP irp) { PVOID pBuffer = NULL; ULONG ulSize = 0; PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(irp); ulSize = IrpStack->Parameters.Write.Length; pBuffer = irp->AssociatedIrp.SystemBuffer; #if DBG DbgPrint("Run TestWrite"); DbgPrint("ulSize: %d", ulSize); DbgPrint("pBuffer: %s",pBuffer); #endif irp->IoStatus.Status = STATUS_SUCCESS; irp->IoStatus.Information = ulSize; IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
Здесь драйвер просто выводит полученный буфер при помощи функции DbgPrint().
ulSize = IrpStack->Parameters.Write.Length; — получение количества прочитанных байт;
pBuffer = irp->AssociatedIrp.SystemBuffer; — получение переданного буфера.
Чтение данных из драйвера при помощи функции ReadFile:
char inBuffer[80];DWORD inCount = sizeof(inBuffer);DWORD bR; if(ReadFile(hHandl,inBuffer, inCount, &bR, NULL)){/*ok*/}else{/*error*/ }
Код рабочей процедуры драйвера по обработке IRP с кодом IRP_MJ_READ:
NTSTATUS TestRead(IN PDEVICE_OBJECT fdo, IN PIRP irp) { PVOID pBuffer = NULL; UCHAR usString[] = "Code by Lazy_elf"; ULONG ulSize = 0; PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(irp); ulSize = IrpStack->Parameters.Read.Length; pBuffer = irp->AssociatedIrp.SystemBuffer; RtlCopyMemory(pBuffer,&usString,ulSize); #if DBG DbgPrint("Run TestRead"); DbgPrint("ulSize: %d", ulSize); DbgPrint("pBuffer: %s",pBuffer); #endif irp->IoStatus.Status = STATUS_SUCCESS; irp->IoStatus.Information = ulSize; IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
Самый важный момент в это участке кода: RtlCopyMemory(pBuffer,&usString,ulSize); — где происходит копирование внутреннего буфера содержавшего строку «Code by Lazy_elf» в буфер который будет передан приложению.
Приложение и драйвер тестировались при помощи программы Dbgview(Mark Russinovich), результат которой представлен на рисунке:
Отличная статья. Большое спасибо автору за труды.
Приятно читать такие комментарии, статья писалась очень давно, даже не подозреваю актуальна ли она до сих пор.
Очень познавательно. Спасибо большое. Требуем продолжения банкета!
Благодарю за положительный комментарий. К сожалению, продолжений не будет, так как в данный момент занимаюсь программированием 1С.
Обсуждение закрыто.