АКЦИЯ от www.R3.ru - хостинг сайтов 72р. в месяц. Домен в подарок!

Процедура DriverEntry и предварительные объявления

Все приведенные ниже отрывки кода
следует последовательно поместить в
один файл (обычно, файл, содержащий
описание DriverEntry, разработчики называют
Init.c). Редактирование, разумеется,
удобнее всего выполнять в каком-нибудь
редакторе интегрированной среды.
Рекомендуется использовать редактор
из среды Visual Studio, поскольку в
нем производится динамический контроль
синтаксиса и типов данных языка С. В
главе 2 приводится содержимое файлов
настройки проекта для драйвера Example,
соблюдение которых позволит воспользоваться
динамическими подсказками среды во время
редактирования и позволит также выполнять
контрольную компиляцию кода. Последнее
весьма удобно, поскольку в интегрированной
среде легко перейти к месту возникновения
ошибки по диагностическому сообщению.

Окончательную компиляцию драйвера (как
чистовую, так и отладочную) категорически
рекомендуется выполнять утилитой Build из
среды DDK, поскольку иные способы компиляции
могут быть источником необъяснимых странностей
в поведении драйвера.

/////////////////////////////////////
// init.cpp: Инициализация драйвера
// Замечание. Рабочая версия данного драйвера
// должна быть скомпилирована как не-WDM версия.
// В противном случае - драйвер не сможет корректно
// загружаться и выгружаться с использованием
// программы monitor (пакет Numega Driver Studio)
// и сервисов SCM Менеджера.//
////////////////////////////////////////////////
// DriverEntry Главная точка входа в драйвер
// UnloadRoutine Процедура выгрузки драйвера
// DeviceControlRoutine Обработчик DeviceIoControl IRP пакетов
///////////////////////////////////////////////////
#include "Driver.h"

// Предварительные объявления функций:
NTSTATUS DeviceControlRoutine( IN PDEVICE_OBJECT fdo,
IN PIRP Irp );
VOID UnloadRoutine(IN PDRIVER_OBJECT DriverObject);
NTSTATUS ReadWrite_IRPhandler( IN PDEVICE_OBJECT fdo, IN PIRP Irp );
NTSTATUS Create_File_IRPprocessing(IN PDEVICE_OBJECT fdo,
IN PIRP Irp);
NTSTATUS Close_HandleIRPprocessing(IN PDEVICE_OBJECT fdo,
IN PIRP Irp);

// Хотя и нехорошо делать глобальные переменные
в драйвере...
KSPIN_LOCK MySpinLock;
#pragma code_seg("INIT") // начало секции INIT
//////////////////////////////////////////////
// (Файл init.cpp)
// DriverEntry - инициализация драйвера и необходимых
// объектов
// Аргументы: указатель на объект драйвера
// раздел реестра (driver service key) в UNICODE
// Возвращает: STATUS_Xxx

extern "C"
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_OBJECT fdo;
UNICODE_STRING devName;

#if DBG
DbgPrint("=Example= In DriverEntry.");
DbgPrint("=Example= RegistryPath = %ws.",
RegistryPath->Buffer);
#endif

//Экспорт точек входа в драйвер (AddDevice объявлять не будем) // DriverObject->DriverExtension->AddDevice=OurAddDeviceRoutine;
DriverObject->DriverUnload = UnloadRoutine;
DriverObject->MajorFunction[IRP_MJ_CREATE]=
Create_File_IRPprocessing;
DriverObject->MajorFunction[IRP_MJ_CLOSE] =
Close_HandleIRPprocessing;
DriverObject->MajorFunction[IRP_MJ_READ]=
ReadWrite_IRPhandler;
DriverObject->MajorFunction[IRP_MJ_WRITE]=
ReadWrite_IRPhandler;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=
DeviceControlRoutine;
//=================================================
// Действия по созданию символьной ссылки
// (их нужно было бы делать в OurAddDeviceRoutine,
// но у нас очень простой драйвер и эта процедура
отсутствует):
RtlInitUnicodeString(&devName, L"\\Device\\EXAMPLE");

// Создаем наш Functional Device Object (FDO)
и получаем указатель на созданный FDO в нашей
переменной fdo. (В WDM драйвере эту работу также
следовало бы выполнять в процедуре OurAddDeviceRoutine.)
При создании FD будет выделено место и под структуру
расширения устройства EXAMPLE_DEVICE_EXTENSION
(для этого мы передаем в вызов ее размер, вычисляемый
оператом sizeof):
status = IoCreateDevice(DriverObject,
sizeof(EXAMPLE_DEVICE_EXTENSION),
&devName, // может быть и NULL
FILE_DEVICE_UNKNOWN,
0,
FALSE, // без эксклюзивного доступа
&fdo);
if(!NT_SUCCESS(status)) return status;

// Получаем указатель на область, предназначенную под
// структуру расширение устройства
PEXAMPLE_DEVICE_EXTENSION dx =
(PEXAMPLE_DEVICE_EXTENSION)fdo->DeviceExtension;

dx->fdo = fdo; // Сохраняем обратный указатель

//Применяя прием условной компиляции, вводим функцию DbgPrint,
//сообщения которой мы сможем увидеть в окне DebugView, если
//выполним сборку нашего драйвера как checked (отладочную)
// версию:
#if DBG
DbgPrint("=Example= FDO %X, DevExt=%X.",fdo,dx);
#endif

//=======================================
// Действия по созданию символьной ссылки
// (их нужно было бы делать в OurAddDeviceRoutine, но у нас
// очень простой драйвер):
UNICODE_STRING symLinkName; // Сформировать символьное имя:
// #define SYM_LINK_NAME L"\\??\\Example"
// Такого типа символьные ссылки ^^ проходят только в NT.
// (То есть, если перенести бинарный файл драйвера в
// Windows 98, то пользовательские приложения заведомо
// не смогут открыть файл по такой символьной ссылке.)
// Для того, чтобы ссылка работала в и Windows 98 и в NT,
// необходимо поступать следующим образом:
#define SYM_LINK_NAME L"\\DosDevices\\Example"
RtlInitUnicodeString( &symLinkName, SYM_LINK_NAME );
dx->ustrSymLinkName = symLinkName;

// Создаем символьную ссылку
status = IoCreateSymbolicLink( &symLinkName,&devName);
if (!NT_SUCCESS(status))
{ // при неудаче – удалить Device Object и вернуть управление
IoDeleteDevice( fdo );
return status;
} // Теперь можно вызывать CreateFile("\\\\.\\Example",...);
// в пользовательских приложениях

//Объект спин-блокировки, который будем использовать для
//разнесения во времени выполнения кода обработчика
// IOCTL запросов. Инициализируем его:
KeInitializeSpinLock(&MySpinLock);

// Снова используем условную компиляцию, чтобы выделить код,
// компилируемый в отладочной версии и не компилируемый в
// версии free (релизной):
#if DBG
DbgPrint("=Example= DriverEntry successfully completed.");
#endif
return status;
}
#pragma code_seg() // end INIT section

Функция CompleteIrp


Вспомогательная функция CompleteIrp реализует действия по завершению обработки
IRP пакета с кодом завершения status. Данная функция предназначена для внутренних
нужд драйвера и нигде не регистрируется. Параметр info, если он не равен нулю,
чаще всего содержит число байт, переданных клиенту (полученных от клиента) драйвера.



//
// (Файл init.cpp)
// CompleteIrp: Устанавливает IoStatus и завершает обработку IRP
// Первый аргумент - указатель на объект нашего FDO.
//
NTSTATUS CompleteIrp( PIRP Irp, NTSTATUS status, ULONG info)
{
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = info;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return status;
}

Рабочая процедура обработки запросов read/write


Процедура ReadWrite_IRPhandler предназначена для обработки запросов Диспетчера
ввода/вывода, которые он формирует в виде IRP пакетов с кодами IRP_MJ_READ/IRP_MJ_WRITE
по результатам обращения к драйверу из пользовательских приложений с вызовами
read/write или из кода режима ядра с вызовами
ZwReadFile или ZwWriteFile. В данном примере
наша функция обработки запросов чтения/записи ничего полезного не делает, и
ее регистрация выполнена только для демонстрации, как это могло бы быть в более "развитом" драйвере.

Описание прототипов рабочих процедура драйвера (параметров их вызова) можно найти
в документации DDK (2000, ХР, 2003), если в режиме указателя задать ключевые слова Dispatch..., например,
DispatchRead. Если ваша программа просмотра файлов справки не поддерживает переходов между разными .chm файлами
(представляющими полную документацию по DDK), то можно сразу обратиться к файлу kmarch.chm (который, собственно,
и содержит информацию по рабочим процедурам). Там же можно узнать, на каком уровне IRQL происходит вызов
конкретной функции.

//
// (Файл init.cpp)
// ReadWrite_IRPhandler: Берет на себя обработку запросов
// чтения/записи и завершает обработку IRP вызовом CompleteIrp
// с числом переданных/полученных байт (BytesTxd) равным нулю.
// Аргументы:
// Указатель на объект нашего FDO
// Указатель на структуру IRP, поступившего от Диспетчера ввода/вывода
NTSTATUS ReadWrite_IRPhandler( IN PDEVICE_OBJECT fdo, IN PIRP Irp )
{
ULONG BytesTxd = 0;
NTSTATUS status = STATUS_SUCCESS; //Завершение с кодом status
// Задаем печать отладочных сообщений – если сборка отладочная
#if DBG
DbgPrint("-Example- in ReadWrite_IRPhandler.");
#endif
return CompleteIrp(Irp,status,BytesTxd);
}

Рабочая процедура обработки запросов открытия драйвера


Процедура Create_File_IRPprocessing предназначена для обработки запросов Диспетчера
ввода/вывода, которые он формирует в виде IRP пакетов с кодами IRP_MJ_CREATE
по результатам обращения к драйверу из пользовательских приложений с вызовами
CreateFile или из кода режима ядра с вызовами ZwCreateFile.
В нашем примере эта функция не выполняет никаких особых действий (хотя можно
было бы завести счетчик открытых дескрипторов и т.п.), однако без регистрации
данной процедуры система просто не позволила бы клиенту "открыть" драйвер

для работы с ним (хотя сам драйвер мог бы успешно загружаться и стартовать).


//
// (Файл init.cpp)
// Create_File_IRPprocessing: Берет на себя обработку запросов с
// кодом IRP_MJ_CREATE.
// Аргументы:
// Указатель на объект нашего FDO
// Указатель на структуру IRP, поступившего от Диспетчера ВВ
//
NTSTATUS Create_File_IRPprocessing(IN PDEVICE_OBJECT fdo,IN PIRP Irp)
{
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
// Задаем печать отладочных сообщений - если сборка отладочная
#if DBG
DbgPrint("-Example- Create File is %ws",
&(IrpStack->FileObject->FileName.Buffer));
#endif
return CompleteIrp(Irp,STATUS_SUCCESS,0); // Успешное завершение
}

Рабочая процедура обработки запросов закрытия драйвера


Процедура Close_File_IRPprocessing предназначена для обработки запросов Диспетчера
ввода/вывода, которые он формирует в виде IRP пакетов с кодом IRP_MJ_CLOSE по
результатам обращения к драйверу из пользовательских приложений с вызовами CloseHandle
или из кода режима ядра с вызовами ZwClose. В нашем примере
эта функция не выполняет никаких особых действий, однако, выполнив регистрацию
процедуры открытия файла, мы теперь просто обязаны зарегистрировать процедуру
завершения работы клиента с открытым дескриптором. Заметим, что если клиент
пользовательского режима забывает закрыть полученный при открытии доступа к
драйверу дескриптор, то за него эти запросы выполняет операционная система (впрочем,
как и в отношении всех открытых приложениями файлов, когда приложения завершаются
без явного закрытия открытых файлов).



// (Файл init.cpp)
// Close_File_IRPprocessing: Берет на себя обработку запросов с
// кодом IRP_MJ_CLOSE.
// Аргументы:
// Указатель на объект нашего FDO
// Указатель на структуру IRP, поступившего от Диспетчера ввода/вывода
NTSTATUS Close_HandleIRPprocessing(IN PDEVICE_OBJECT fdo,IN PIRP Irp)
{
#if DBG
// Задаем печать отладочных сообщений - если сборка отладочная
DbgPrint("-Example- In Close handler.");
#endif
return CompleteIrp(Irp,STATUS_SUCCESS,0);// Успешное завершение
}

Рабочая процедура обработки IOCTL запросов


Процедура DeviceControlRoutine предназначена для обработки запросов Диспетчера
ввода/вывода, которые он формирует в виде IRP пакетов с кодом IRP_MJ_DEVICE_CONTROL
по результатам обращения к драйверу из пользовательских приложений с вызовами
DeviceIoControl.


В нашем примере это самая важная функция. Она реализует обработку пяти IOCTL
запросов:



IOCTL_PRINT_DEBUG_MESS — выводим отладочное сообщение в окно DebugView.

IOCTL_CHANGE_IRQL — проводим эксперимент, насколько высоко можно
искусственно поднять уровень IRQL в коде драйвера.
IOCTL_MAKE_SYSTEM_CRASH — проводим эксперимент по "обрушению" операционной
системы и пытаемся его предотвратить.
IOCTL_TOUCH_PORT_378H — проводим эксперимент по обращению к аппаратным
ресурсам системы.
IOCTL_SEND_BYTE_TO_USER — отправляем байт данных в пользовательское
приложение.

Эти IOCTL коды являются пользовательскими — они определены с помощью
макроса CTL_CODE в файле Driver.h, который является частью данного проекта,
и речь о котором пойдет ниже.



Определения используемых ниже непривычных для программистов Win32 типов данных
(например, UCHAR или PUCHAR) можно найти в DDK в файле Windef.h .


// (Файл init.cpp)
// DeviceControlRoutine: обработчик IRP_MJ_DEVICE_CONTROL запросов
// Аргументы:
// Указатель на объект нашего FDO
// Указатель на структуру IRP, поступившего от Диспетчера ВВ
// Возвращает: STATUS_XXX
// #define SMALL_VERSION
// В том случае, если не закомментировать верхнюю строчку – будет
// выполнена компиляция версии, в которой будет обрабатываться только
// один тип IOCTL запросов -- IOCTL_MAKE_SYSTEM_CRASH

NTSTATUS DeviceControlRoutine( IN PDEVICE_OBJECT fdo, IN PIRP Irp )
{
NTSTATUS status = STATUS_SUCCESS;
ULONG BytesTxd =0; // Число переданных/полученных байт (пока 0)
PIO_STACK_LOCATION IrpStack=IoGetCurrentIrpStackLocation(Irp);

// Получаем указатель на расширение устройства
PEXAMPLE_DEVICE_EXTENSION dx =
(PEXAMPLE_DEVICE_EXTENSION)fdo->DeviceExtension;
//-------------------------------
// Выделяем из IRP собственно значение IOCTL кода, по поводу
// которого случился вызов:
ULONG ControlCode =
IrpStack->Parameters.DeviceIoControl.IoControlCode;
ULONG method = ControlCode & 0x03;

// Получаем текущее значение уровня IRQL – приоритета,
// на котором выполняется поток (вообще говоря, целое число):
KIRQL irql,
currentIrql = KeGetCurrentIrql();

#if DBG
DbgPrint("-Example- In DeviceControlRoutine (fdo= %X)\n",fdo);
DbgPrint("-Example- DeviceIoControl: IOCTL %x.", ControlCode );
if(currentIrql==PASSIVE_LEVEL)
DbgPrint("-Example- PASSIVE_LEVEL (val=%d)",currentIrql);
#endif
// Запрашиваем владение объектом спин-блокировки. В данном
// примере не выполняется никаких критичных действий, но,
// вообще говоря, этот прием может быть полезен и даже
// незаменим, если в приведенном ниже коде должны будут
// выполнены манипуляции, которые можно делать только
// эксклюзивно. Пока потоку выделен объект спин-блокировки –
// никакой другой поток не сможет войти в оператор switch:
KeAcquireSpinLock(&MySpinLock,&irql);

// Диспетчеризация по IOCTL кодам:
switch( ControlCode) {

#ifndef SMALL_VERSION
case IOCTL_PRINT_DEBUG_MESS:
{ // Только вводим сообщение и только в отладочной версии
#if DBG
DbgPrint("-Example- IOCTL_PRINT_DEBUG_MESS.");
#endif
break;
}
case IOCTL_CHANGE_IRQL:
{
#if DBG
// Эксперименты по искусственному повышению
// IRQL – только в отладочной версии!
DbgPrint("-Example- IOCTL_CHANGE_IRQL.");
KIRQL dl = DISPATCH_LEVEL, // только для распечатки (2)
oldIrql,
newIrql=25; // Новый уровень IRQL (например, 25)
// Устанавливаем newIrql, сохраняя текущий в oldIrql:
KeRaiseIrql(newIrql,&oldIrql);
newIrql=KeGetCurrentIrql(); // Что реально получили?

DbgPrint("-Example- DISPATCH_LEVEL value =%d",dl);
DbgPrint("-Example- IRQLs are old=%d new=%d",
oldIrql,newIrql);
KeLowerIrql(oldIrql); // Возвращаем старое значение
#endif
break;
}
#endif // SMALL_VERSION


case IOCTL_MAKE_SYSTEM_CRASH:
{
int errDetected=0;
char x = (char)0xFF;

#if DBG // Вообще говоря, под NT мы этого уже не увидим:
DbgPrint("-Example- IOCTL_MAKE_SYSTEM_CRASH.");
#endif
// Вызываем системный сбой обращением по нулевому адресу
__try {
x = *(char*)0x0L; // ошибочная ситуация
//^^^^^^^^^^^^ здесь случится сбой NT, но не Win98
}
__except(EXCEPTION_EXECUTE_HANDLER)
{ // Перехват исключения не работает!
// Эта занимательная ситуация объяснена в 10.2.6,
// при рассмотрении объектов спин-блокировок.
errDetected=1;
};
#if DBG
DbgPrint("-Example- Value of x is %X.",x);
if(errDetected)
DbgPrint("-Example- Except detected in Example driver.");
#endif
break;
}

#ifndef SMALL_VERSION
case IOCTL_TOUCH_PORT_378H:
{
unsigned short ECRegister = 0x378+0x402;
#if DBG
DbgPrint("-Example- IOCTL_TOUCH_PORT_378H.");
#endif
// Пробуем программно перевести параллельный порт 378,
// сконфигурированный средствами BIOS как ECP+EPP, в
// режим EPP.
_asm {
mov dx,ECRegister ;
xor al,al ;
out dx,al ; Установить EPP mode 000
mov al,095h ; Биты 7:5 = 100
out dx,al ; Установить EPP mode 100
}
// Подобные действия в приложении пользовательского
// режима под NT обязательно привело бы к аварийной
// выгрузке приложения с сообщением об ошибке!
// Практически эти пять строк демонстрируют, что можно
// работать с LPT портом под Windows NT !
break;
}

//////////////////////////////////////////////////////////////////////////////
// * Buffered I/O. Метод буферизации данных. Ядро операционной системы определяет максимальный размер данных
//(входные/выходные) и выделяет буфер в пуле. Входной буфер копируется ядром при формировании запроса. Выходные данные
//формирует драйвер по тому же адресу. Указатель на буфер располагается в в поле AssociatedIrp.SystemBuffer IRP'а.
//Используется для передачи небольших объемов данных.
// * Direct I/O. Метод прямого доступа к страницам памяти буфера. Доступ осуществляется через механизмы MDL. Адрес
//структуры MDL располагается в поле MdlAddress IRP'а. Используется для передачи больших объемов данных.
// * Neither Buffered Nor Direct I/O. «Третий» (не buffered и не direct) метод доступа к данным. В этом случае,
//указатель //на пользовательский буфер с данными передается без изменений. Адрес входной буфера располагается в поле
//Parameters.DeviceIoControl.Type3InputBuffer текущей ячейки стека IRP'а. Адрес выходного буфера помещается
// менеджером I/O //поле UserBuffer IRP'а. При обработке такого рода запросов, драйвер должен сам вызывать Probe-функции,
//следить за текущим //контекстом пользовательского процесса и обращаться данным внутри блока структурной обработки
//исключений (__try/__except).
//////////////////////////////////////////////////////////////////////////////
case IOCTL_SEND_BYTE_TO_USER:
{
// Размер данных, поступивших от пользователя:
ULONG InputLength = //только лишь для примера
IrpStack->Parameters.DeviceIoControl.InputBufferLength;
// Размер буфера для данных, ожидаемых пользователем
ULONG OutputLength =
IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
#if DBG
DbgPrint("-Example- Buffer outlength %d",OutputLength);
#endif

if( OutputLength<1 )
{// Если не предоставлен буфер – завершить IRP с ошибкой
status = STATUS_INVALID_PARAMETER;
break;
}
UCHAR *buff; // unsigned char, привыкаем к новой нотации
if(method==METHOD_BUFFERED)
{
buff = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;
#if DBG
DbgPrint("-Example- Method : BUFFERED.");
#endif
}
else
if (method==METHOD_NEITHER)
{
buff=(unsigned char*)Irp->UserBuffer;
#if DBG
DbgPrint("-Example- Method : NEITHER.");
#endif
}
else
{
#if DBG
DbgPrint("-Example- Method : unsupported.");
#endif
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
#if DBG
DbgPrint("-Example- Buffer address is %08X",buff);
#endif
*buff=33; // Любимое число Штирлица
BytesTxd = 1; // Передали 1 байт
break;
}
#endif // SMALL_VERSION
// Ошибочный запрос (код IOCTL, который не обрабатывается):
default: status = STATUS_INVALID_DEVICE_REQUEST;
}
// Освобождение спин-блокировки
KeReleaseSpinLock(&MySpinLock,irql);

#if DBG
DbgPrint("-Example- DeviceIoControl: %d bytes written.", (int)BytesTxd);
#endif

return CompleteIrp(Irp,status,BytesTxd); // Завершение IRP
}
Корректность фрагмента
кода, посвященного обработке IOCTL запроса IOCTL_TOUCH_PORT_378H, может
вызвать споры, поскольку действует "напролом", не обращая внимания на
то, что в системе могут быть устройства и драйвера, работающие с этим
портом, существование которых следует учитывать. Однако цель данного примера
— показать, что само по себе обращение к аппаратным ресурсам в режиме
ядра является делом тривиальным, не имеющим ограничений со стороны операционной
системы.
Рабочая процедура выгрузки драйвера
Процедура UnloadRoutine выполняет завершающую работу перед тем как драйвер
растворится в небытии.

При следовании WDM
модели, драйвер должен был бы зарегистрировать обработчик PnP запросов
(то есть IRP_MJ_PNP) и перед вызовом UnloadRoutine получал бы IRP пакеты
с кодом IRP_MJ_PNP и суб-кодом IRP_MN_STOP_DEVICE (например, когда пользователь
решил отключить устройство, воспользовавшись окном Диспетчера Устройств
в Настройках системы). В этом обработчике и следует выполнять действия,
предшествующие удалению WDM драйвера.

// (Файл init.cpp)
// UnloadRoutine: Выгружает драйвер, освобождая оставшиеся объекты
// Вызывается системой, когда необходимо выгрузить драйвер.
// Как и процедура AddDevice, регистрируется иначе чем
// все остальные рабочие процедуры и не получает никаких IRP.
// Arguments: указатель на объект драйвера
//

#pragma code_seg("PAGE")
// Допускает размещение в странично организованной памяти
//
VOID UnloadRoutine(IN PDRIVER_OBJECT pDriverObject)
{
PDEVICE_OBJECT pNextDevObj;
int i;

// Задаем печать отладочных сообщений – если сборка отладочная
#if DBG
DbgPrint("-Example- In Unload Routine.");
#endif
//==========================================================
// Нижеприведенные операции в полномасштабном WDM драйвере
// следовало бы поместить в обработчике IRP_MJ_PNP запросов
// с субкодом IRP_MN_REMOVE_DEVICE, но в силу простоты
// драйвера, сделаем это здесь.
// Проходим по всем объектам устройств, контролируемым
// драйвером
pNextDevObj = pDriverObject->DeviceObject;

for(i=0; pNextDevObj!=NULL; i++)
{
PEXAMPLE_DEVICE_EXTENSION dx =
(PEXAMPLE_DEVICE_EXTENSION)pNextDevObj->DeviceExtension;
// Удаляем символьную ссылку и уничтожаем FDO:
UNICODE_STRING *pLinkName = & (dx->ustrSymLinkName);
// !!! сохраняем указатель:
pNextDevObj = pNextDevObj->NextDevice;

#if DBG
DbgPrint("-Example- Deleted device (%d) : pointer to FDO = %X.",
i,dx->fdo);
DbgPrint("-Example- Deleted symlink = %ws.", pLinkName->Buffer);
#endif

IoDeleteSymbolicLink(pLinkName);
IoDeleteDevice( dx->fdo);
}
}
#pragma code_seg() // end PAGE section

Заголовочный файл Driver.h


Ниже приводится полный текст файла Driver.h, содержащий объявления, необходимые
для компиляции драйвера Example.sys.



#ifndef _DRIVER_H_04802_BASHBD_1UIWQ1_8239_1NJKDH832_901_
#define _DRIVER_H_04802_BASHBD_1UIWQ1_8239_1NJKDH832_901_
// Выше приведены две строки (в конце файла имеется еще #endif),
// которые в больших проектах запрещают повторные проходы по тексту,
// который находится внутри h-файла (что весьма удобно для повышения
// скорости компиляции).
======================
// (Файл Driver.h)

#ifdef __cplusplus
extern "C"
{
#endif

#include "ntddk.h"

//#include "wdm.h"
// ^^^^^^^^^^^^^^ если выбрать эту строку и закомментировать
// предыдущую, то компиляция в среде DDK (при помощи утилиты Build)
// также пройдет успешно, однако драйвер Example не станет от этого
// настоящим WDM драйвером.

#ifdef __cplusplus
}
#endif
// Определяем структуру расширения устройства. Включим в нее
// указатель на FDO (для удобства последующей работы UnloadRoutine) и
// имя символьной ссылки в формате UNOCODE_STRING.

typedef struct _EXAMPLE_DEVICE_EXTENSION
{
PDEVICE_OBJECT fdo;
UNICODE_STRING ustrSymLinkName; // L"\\DosDevices\\Example"
} EXAMPLE_DEVICE_EXTENSION, *PEXAMPLE_DEVICE_EXTENSION;

// Определяем собственные коды IOCTL, с которыми можно будет
// обращаться к драйверу при помощи вызова DeviceIoControl.
// Определение макроса CTL_CODE можно найти в файле DDK Winioctl.h.
// Там же можно найти и численные значения, скрывающиеся под именами
// METHOD_BUFFERED и METHOD_NEITHER.

// Внимание! Текст приведенный ниже должен войти в файл Ioctl.h,
// который будет необходим для компиляции тестового приложения.
// (Разумеется, за исключением последней строки с "#endif".)

#define IOCTL_PRINT_DEBUG_MESS CTL_CODE( \
FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define IOCTL_CHANGE_IRQL CTL_CODE(\
FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define IOCTL_MAKE_SYSTEM_CRASH CTL_CODE( \
FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define IOCTL_TOUCH_PORT_378H CTL_CODE( \
FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define IOCTL_SEND_BYTE_TO_USER CTL_CODE( \
FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS)

// Вариант :
//#define IOCTL_SEND_BYTE_TO_USER CTL_CODE( \
// FILE_DEVICE_UNKNOWN, 0x805, METHOD_NEITHER, FILE_ANY_ACCESS)
#endif

Третий параметр CTL_CODE называется Function и при составлении собственных
(пользовательских) IOCTL кодов его значение не должно быть менее 0x800. Пересечение
пользовательских IOCTL кодов со значениями IOCTL кодов других драйверов не имеет
никакого значения, поскольку они действуют только в пределах конкретного драйвера.


===================================================
Файл (Win32) Test.cpp

#include "stdafx.h"
#include
#include
#include
#include "Ioctls.h"
#define Device "\\\\.\\test"

int main(int argc, char* argv[])
{

HANDLE hHandle = CreateFile( Device,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL );

if (hHandle == INVALID_HANDLE_VALUE)
{

printf("**Error open Device**-> %s\n",Device);
printf("ERROR!-> %d\n", GetLastError());
getchar();
return 1;
}

DWORD BytesReturned;
unsigned long ioctlCode;
unsigned char xdata = 0x88;

// Последовательно выполняем обращения к драйверу
// с различными кодами IOCTL:
ioctlCode=IOCTL_PRINT_DEBUG_MESS;
if( !DeviceIoControl( hHandle,
ioctlCode,
NULL, 0, // Input
NULL, 0, // Output
&BytesReturned,
NULL ) )
{
printf( "Error in IOCTL_PRINT_DEBUG_MESS!" );
return(-1);
}

ioctlCode=IOCTL_CHANGE_IRQL;
if( !DeviceIoControl( hHandle,
ioctlCode,
NULL, 0, // Input
NULL, 0, // Output
&BytesReturned,
NULL ) )
{
printf( "Error in IOCTL_CHANGE_IRQL!" );
return(-1);
}

ioctlCode=IOCTL_TOUCH_PORT_378H;
if( !DeviceIoControl( hHandle,
ioctlCode,
NULL, 0, // Input
NULL, 0, // Output
&BytesReturned,
NULL ) )
{
printf( "Error in IOCTL_TOUCH_PORT_378H!" );
return(-1);
}

// Следующий тест. Получаем 1 байт данных из драйвера.
// По окончании данного вызова переменная xdata должна
// содержать значение 33:
xdata = 0x88;
ioctlCode=IOCTL_SEND_BYTE_TO_USER;
if( !DeviceIoControl( hHandle,
ioctlCode,
NULL, 0, // Input
&xdata, sizeof(xdata),// Output
&BytesReturned,
NULL ) )
{
printf( "Error in IOCTL_SEND_BYTE_TO_USER!" );
return(-1);
}

// Вывод диагностического сообщения в консольном окне:
printf("IOCTL_SEND_BYTE_TO_USER: BytesReturned=%d xdata=%d",
BytesReturned, xdata);

// Выполнение следующего теста в Windows NT приведет к
// фатальному сбою операционной системы (намеренно выполненное
// падение ОС может быть полезно при изучении, например,
// организации crash dump файла и работы с отладчиком).

// ioctlCode=IOCTL_MAKE_SYSTEM_CRASH;
// if( !DeviceIoControl( hHandle,
// ioctlCode,
// NULL, 0, // Input
// NULL, 0, // Output
// &BytesReturned,
// NULL ) )
// {
// printf( "Error in IOCTL_MAKE_SYSTEM_CRASH!" );
// return(-1);
// }

// Закрываем дескриптор доступа к драйверу:

CloseHandle(hHandle);
getchar();
return 0;
}