Пишем драйвер под windows
Ну вот мы и добрались до самого интересного — сейчас будем писать драйвер. Итак, приступим. Для удобства выполнения, всю последовательность действий по написанию драйвера я распишу по пунктам.
1. Создание директории проекта
Установив DDK, на Вашем компьютере в директории C:\WINDDK\2600.1106\ должны появиться файлы DDK. В этой директории создадим папку, в которой будут храниться наши проекты. Назовем ее, например, MyDrivers.
В папке MyDrivers создадим папку FirstDriver — тут будет находится наш первый проект драйвера.
2. Подготовка файлов проекта
В папке FirstDriver создайте пустой текстовый файл и переименуйте его под именем FirstDriver.c
При попытке переименовки со сменой расширения файла, появляется следующее предупреждение:
Не обращаем внимания на это предупреждение, и нажимаем Да. При этом наш файл примет вид:
Если же никакого предупреждения не было и переименованный файл так и остался текстовым с именем FirstDriver.c и расширением .txt, то в настройках своийств папки, которые можно найти в Пуск-> Настройка-> Панель управления-> Свойства паки уберите галочку напротив пункта «Скрывать расширения для зарегестрированных типов файлов». Попробуйте еще раз и все должно быть в порядке.
Теперь нам надо добавить еще два очень важных файла в наш проект, без которых драйвер нам не сделать. Они называются makefile и sources (обратите внимание, у них нет расширения). Их можно создать самим, но мы сделаем проще: скопируем готовые из какого либо примера проекта драйвера из DDK. Например, возьмем их из C:\WINDDK\2600.1106\src\general\cancel\sys\. Итак, копируем из указанной директории эти два файла и вставляем их в нашу папку проекта FirstDriver. Эти файлы управляют процессрм компиляции драйвера. Файл makefile оставляем без изменений, а вот sources надо подредактировать.
Открываем этот файл с помощью блокнота, и удаляем из него все что там есть и вставляем туда текст, представленный ниже. Сохранияем файл.
Первым параметром идет TARGETNAME, которому мы присвоили Port. Это значит, что когда DDK откомпилирует наш код и создаст драйвер, имя этого файла будет Port.sys Следующем параметром идет TARGETPATH, которому мы указали путь к папке нашего проекта. Если Вы устанавливали DDK в другое место, или создали пупку проекта в другой директории, здесь Вам надо это поправить на тот путь, который у Вас. Параметр TARGETTYPE пока оставлю без комментариев. В параметре SOURCES указываем, из каких файлов будет компилироваться драйвер. У нас это файл FirstDriver.c, вот мы его и указали.
Всю подготовительную работу мы сделали. Можно приступать к самой содержательной части — коду драйвера. Писать мы будем его на Си.
Еще раз напомню решаемую нами задачу: надо написать драйвер под Windows 2000, XP с помощью которого можно будет работать с портами компьютера (читать и писать данные) .
Рассмотрим код, представленный ниже. Серым цветом обозначены те участки кода, которые являются как бы шаблонными для большинства драйверов, а зеленым — код, который относится именно к нашему текущему драйверу.
Представленный код — есть код простейшего драйвера, который может только записать данные в порт и прочесть их от туда. Выглядит страшновато? Ни чего, в следующей статье будем разбираться с тем что в нем понаписано.
Пишем драйвер под windows
В один прекрасный день меня попросили написать Драйвер. На тот день мои познания в C/C++ ограничивались программой Hellow Word, и поэтому на вопрос: «Напишешь?» я самоуверенно ответил: «Конечно». На следующий день я узнал, что на свете существуют MSDN и DDK. Вскоре я понял, что не все Windows одинаковые, оказалось, что мой драйвер должен работать под Win2000/NT. У меня ушло больше месяца на то, чтобы скомпилировать и запустить свой первый Драйвер. По правде сказать, это был не совсем мой Драйвер, а точнее — это был genport из NTDDK. Но радовался я так, как будто минимум написал свою ОС.
Недавно мне пришлось опять вернуться к Драйверу. И вот вчера я, наконец-то, сдал работающий драйвер и решил написать эту небольшую статью, для того, чтобы как-то систематизировать то, что я узнал, и, чтобы когда мне снова придется взяться за драйвер, было от чего отталкиваться.
Люди, знающие что такое IOCTL, DEVICE_EXTENSION, MajorFunction и DriverEntry не найдут здесь ничего нового. Эта статья для тех, кто, возможно, никогда не слышал слово ДДК, и кто до сего дня никогда не заглядывал в исходники драйверов. И еще, я буду довольно-таки подробно описывать многие, даже очевидные вещи, поэтому напомню о том, что данная статья рассчитана на людей с очень малым опытом программирования, какой был у меня, когда я занялся написанием драйверов.
Первое, с чем сталкивается программист, решивший заняться драйверами, это почти полное отсутствие русскоязычной литературы по данной теме. За год поисков мне удалось найти всего три книги на русском, в которых хотя бы немного говорится о драйверах:
————————
1. Свен Шрайбер «Недокументированные возможности Windows 2000». Издательство «Питер» 2002 год.
Здесь очень хорошо описан механизм динамической загрузки драйвера.
——————
2. П. И. Рудаков, К. Г. Финогенов «Язык ассемблера: уроки программирования» Диалог МИФИ 2001 год.
Очень полезная книга для того, что бы понять, как писать драйвера без всякий Wizard-ов.
——————————
3. Светлана Сорокина, Андрей Тихонов, Андрей Щербаков «Программирование драйверов и систем безопасности». Издательство «БХВ-Петербург» 2002 год.
Здесь хорошо описывается многоуровневая модель драйверов.
Я ни в коей мере не претендую как на полноту освещения темы написания драйверов, так и на 100% правильность и достоверность того, что здесь написано (но все приведенные здесь исходники проверены и являются работоспособными). Буду благодарен всем, приславшим мне (01_artem@mail.ru) или высказавшим в форуме свои замечания.
Итак, я обращаюсь к человеку, решившему написать Драйвер уровня ядра под Win2000/NT. Надеюсь, эти заметки помогут сэкономить кучу времени и сил.
Прежде всего, я бы не рекомендовал (исходя из собственного опыта) пользоваться различными библиотеками (типа NuMega и всякими другими визардами). В основном из-за того, что даже для написания простейшего драйвера необходимо хотя бы поверхностное представление о том, как он функционирует. И самый простой способ получить представление об этом — написать драйвер самому. Мне, например, не хватило терпения разобраться с NuMega, и даже оболочки функций динамической загрузки/выгрузки драйвера, предложенные Свен Шрайбером в своей книге, я предпочел переписать.
Итак, начнем. Для начала надо установить на компьютер Visul C++ 6.0, MSDN и NTDDK установку проводить желательно именно в этом порядке. Лично я пользуюсь редактором UltraEdit для работы с текстами драйверов, но, в принципе, исходный код драйвера можно набирать в любом текстовом редакторе, хоть в NotePad.
Создадим папку, в которой мы будем работать с драйвером (пусть это будет C:myDriver). В этой папке создадим 5 файлов:
1. myDrv.c
2. myDrv.h
3. myDrv.rc
4. MAKEFILE
5. SOURCES
Начнем с последнего файла. В SOURCES скопируйте следующее:
#define FIRST_IOCTL_INDEX 0x800
#define FILE_DEVICE_myDRV 0x00008000
#define TEST_SMTH CTL_CODE(FILE_DEVICE_myDRV, \
FIRST_IOCTL_INDEX + 101, \
METHOD_BUFFERED, \
FILE_ANY_ACCESS)
#include «ntddk.h»
#include «myDrv.h»
#include «parallel.h»
#define NT_DEVICE_NAME L»\\Device\\myDrv»
#define DOS_DEVICE_NAME L»\\DosDevices\\myDrv»
//структура расширения устройства
typedef struct _DEVICE_EXTENSION
<
PDRIVER_OBJECT DriverObject;
PDEVICE_OBJECT DeviceObject;
PFILE_OBJECT FileObject;
HANDLE Handle;
//прототипы функций
NTSTATUS
DriverDeviceControl(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp);
VOID
DriverUnload(IN PDRIVER_OBJECT DriverObject);
NTSTATUS
DriverOpen(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp);
NTSTATUS
DriverClose(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp);
NTSTATUS
DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
<
PDEVICE_OBJECT deviceObject;
UNICODE_STRING deviceNameUnicodeString;
UNICODE_STRING deviceLinkUnicodeString;
PDEVICE_EXTENSION extension;
NTSTATUS ntStatus;
if (!NT_SUCCESS(ntStatus)) return ntStatus;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDeviceControl;
DriverObject->DriverUnload = DriverUnload;
DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverOpen;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverClose;
extension = (PDEVICE_EXTENSION) deviceObject->DeviceExtension;
extension->DeviceObject = deviceObject;
extension->DriverObject = DriverObject;
// Create counted string version of our Win32 device name.
RtlInitUnicodeString(&deviceLinkUnicodeString, DOS_DEVICE_NAME);
// Create a link from our device name to a name in the Win32 namespace.
ntStatus = IoCreateSymbolicLink(&deviceLinkUnicodeString, &deviceNameUnicodeString);
if (!NT_SUCCESS(ntStatus))
<
IoDeleteDevice(deviceObject);
return ntStatus;
>
return STATUS_SUCCESS;
>
//———————————————————————————————-
VOID
DriverUnload(IN PDRIVER_OBJECT DriverObject)
<
UNICODE_STRING deviceLinkUnicodeString;
PDEVICE_EXTENSION extension;
PIRP pNewIrp = NULL;
ULONG m_size;
NTSTATUS ntStatus;
extension = DriverObject->DeviceObject->DeviceExtension;
// Create counted string version of our Win32 device name.
RtlInitUnicodeString(&deviceLinkUnicodeString, DOS_DEVICE_NAME);
// Delete the link from our device name to a name in the Win32 namespace.
IoDeleteSymbolicLink(&deviceLinkUnicodeString);
// Finally delete our device object
IoDeleteDevice(DriverObject->DeviceObject);
>
//————————————————————————————————
NTSTATUS
DriverOpen(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
<
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
>
//————————————————————————————————
NTSTATUS
DriverClose(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
<
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
>
//————————————————————————————————
NTSTATUS
DriverDeviceControl(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
<
NTSTATUS ntStatus;
PIO_STACK_LOCATION irpStack;
PDEVICE_EXTENSION extension;
PULONG ioBuffer;
ULONG ioControlCode;
ULONG port = 0;
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
irpStack = IoGetCurrentIrpStackLocation(Irp);
extension = DeviceObject->DeviceExtension;
ioBuffer = Irp->AssociatedIrp.SystemBuffer;
ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
switch (ioControlCode)
<
case TEST_SMTH:
ioBuffer[0] =(ULONG)DriverEntry;//В буфер обмена адрес функции DriverEntry
Irp->IoStatus.Information = 4;
break;
default:
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
break;
>
ntStatus = Irp->IoStatus.Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return ntStatus;
>
Далее приведены прототипы используемых функций. Этим функциям можно дать произвольные имена, НО их сигнатура (состав параметров с типом возвращаемого значения) жестко заданы системой. [2]:Программная часть драйвера начинается с обязательной функции с именем DriverEntry(), которая автоматически вызывается системой на этапе загрузки драйвера. Эта функция должна содержать все действия по его инициализации. В качестве первого параметра наша функция получает указатель на объект драйвера типа PDRIVER_OBJECT.
При загрузке драйвера системы создается объект драйвера (driver object), олицетворяющий образ драйвера в памяти. С другой стороны, объект драйвера представляет собой структуру, содержащую необходимые для функционирования драйвера данные и адреса функций. В процессе инициализации драйвера (в DriverEntry()) создаются один или несколько объектов устройств (device object), олицетворяющие те устройства, с которыми будет работать драйвер. Этот самый device object необходим для правильного функционирования драйвера и создается (как и в нашем случае) даже тогда, когда драйвер не имеет отношения к какому-либо реальному устройству.
Далее, в первых строках DriverEntry мы определяем используемые в ней данные, в т.ч. указатель на device object, и две символьные строки UNICODE_STRING с именами устройств. Системные программы взаимодействуют с объектом устройства, созданным драйвером, посредством указателя на него. Однако для прикладных программ объект устройства представляется одним из файловых объектов, и обращение к нему осуществляется по имени (в приложении мы будем использовать функцию CreateFile()).
Надо иметь в виду, что объект устройства должен иметь два имени, одно — в пространстве имен NT, другое — в пространстве имен Win32. Эти имена должны представлять собой структуры UNICODE_STRING. Имена объектов устройств составляются по определенным правилам. NT-имя предваряется префиксом Device, а Win32-имя — префиксом ?? (или DosDevice). При указании имен в Си-программе знак обратной косой черты удваивается. Для того, чтобы указанное в программе драйвера имя можно было использовать в приложении для открытия устройства, следует создать символическую связь между обоими заданными именами устройств. Для этого используем функцию IoCreateSymbolicLink(). Следующая обязательная операция — создание объекта устройства — осуществляется вызовом функции IoCreateDevice(). Первый параметр этой функции — это указатель на объект драйвера, поступающий в DriverEntry(). Второй параметр определяет размер расширения устройства — структуры, которая служит для передачи данных между функциями драйвера (состав этой структуры произволен и полностью определяется разработчиком). Третий параметр — созданное ранее NT-имя устройства. Далее идут: тип устройства (FILE_DEVICE_UNKNOWN), специальные характеристики (0), FALSE означает, что у нас однопоточное устройство. Наконец, последний параметр является выходным — через него функция возвращает указатель на созданный объект устройства.
Далее необходимо занести в объект драйвера адреса основных функций, включенных программистом в текст драйвера. Массив MajorFunction является одним из элементов структурной переменной. В этот массив мы и заносим адреса основных функций (т.е. функций, которые вызываются системой автоматически в ответ на определенные действия приложения или устройства). Завершается функция оператором return с указанием кода успешного завершения.
Функция DriverUnload() вызывается при выгрузке драйвера. Здесь мы должны выполнить действия по удалению объекта устройства, созданного в DriverEntry().
Функции DriverOpen и DriverClose в нашем случае ничего не делают и возвращают просто STATUSS_SUCCESS. Кстати, все эти функции тоже могут иметь произвольные имена, но передаваемые им параметры строго фиксированы.
[2]:Вот мы и добрались до самой содержательной, с точки зрения прикладного программирования, функции DriverDeviceControl(). Эта функция вызывается каждый раз, когда драйверу приходит IRP-пакет с каким либо IOCTL_ кодом. Грубо говоря, IRP-пакет — это структура, передавая указатель на которую, приложение может общаться с драйвером (как, впрочем, и драйвер может общаться с другим драйвером). Более подробное описание того, что такое IRP-пакет, можно найти здесь http://www.lcard.ru/
IRP-пакет содержит так называемый системный буфер, служащий для обмена информацией (переменная SystemBuffer). Таким образом, нам надо получить доступ к IRP-пакету, а через него к SystemBuffer. Для этого объявляем переменную irpStack типа указателя на стековую область ввода-вывода PIO_STACK_LOCATION, и, кроме того, переменная ioBuffer, в которую будет помещен адрес системного буфера обмена. В нашем случае тип этой переменной — PULONG, в действительности тип передаваемых данных может быть каким угодно. С помощью функции IoGetCurrentIrpStackLocation() в переменную irpStack помещается адрес стековой области ввода-вывода, а в переменную ioBuffer заносится адрес системного буфера из структуры IRP. Системный буфер входит в объединение (union) с именем AssociatedIrp, поэтому мы используем конструкцию Irp->AssociatedIrp.SystemBuffer. Конструкция switch-case анализирует содержимое ячейки IoControlCode и в зависимости от значения кода выполняет те или иные действия. В нашей программе только один код действия TEST_SMTH. Засылка в буфер обмена адреса функции DriverEntry() осуществляется через указатель на этот буфер. В переменную Irp->IoStatus.Information заносим количество (4) пересылаемых байт. Для завершения IRP-пакета вызываем IoCompleteRequest().
Итак, драйвер мы написали. Теперь надо его скомпилировать. Т.к. процесс компиляции идет из командной строки, то для этой цели гораздо удобнее пользоваться bat-файлом. Создадим небольшой bat-файл с именем, допустим, Crt.bat и со следующим содержанием:
NTDDK у меня установлено в корень С:, если у Вас по-другому, то вместо C:NTDDKbin и C:NTDDK пропишите полные пути к соответствующим папкам.
Итак, теперь запустим наш Crt.bat. После окончания компиляции в папке C:myDriverobjfrei386 находим готовый драйвер myDrv.sys. Наш драйвер пока умеет только лишь загружаться/выгружаться и по специальному запросу посылает приложению адрес одной из своих процедур.
Теперь займемся написанием приложения, работающего с нашим драйвером. Еще раз напоминаю, что мы работаем под Win2000. Эта ОС позволяет реализовать динамическую загрузку/выгрузку драйвера.
Точнее, динамическую загрузку/выгрузку служб (service), но т.к. в Win2000 в качестве службы можно рассматривать и драйвер, я буду использовать оба эти термина, подразумевая, в данной статье, наш драйвер.
Для загрузки и выгрузки драйверов используется диспетчер управления службами SC Manager (Service Control Manager). Прежде чем вы сможете работать с интерфейсом SC, вы должны получить дескриптор диспетчера служб. Для этого необходимо обратиться к функции OpenSCManager(). Дескриптор диспетчера служб необходимо использовать при обращении к функциям CreateServise() и OpenService(). Дескрипторы, возвращаемые этими функциями, необходимо использовать при обращении к вызовам, имеющим отношение к конкретной службе. К подобным вызовам относятся функции ControlService(), DeleteService() и StartService(). Для освобождения дескрипторов обоих типов используется вызов CloseServiceHandle().
Загрузка и запуск службы подразумевает выполнение следующих действий:
- Обращение к функции OpenSCManager() для получения дескриптора диспетчера
- Обращение к CreateServise() для того, чтобы добавить службу в систему. Если такой сервис уже существует, то CreateServise() выдаст ошибку с кодом 1073 (код ошибки можно прочитать GetLastError()) Данная ошибка означает, что сервис уже существует и надо вместо CreateServise() использовать OpenService().
- Обращение к StartService() для того, чтобы перевести службу в состояние функционирования.
- Если служба запустилась успешно, то можно вызвать CreateFile() для получения дескриптора, который мы будем использовать уже непосредственно при обращении к драйверу.
- И по окончании работы не забудьте дважды обратиться к CloseServiceHandle() для того, чтобы освободить дескрипторы диспетчера и службы.
Если на каком-то шаге этой последовательности возникла ошибка, нужно выполнить действия, обратные тем, которые вы успели выполнить до ошибки. Надо помнить о том, что при обращении к функциям, подобным CreateServise(), необходимо указывать полное имя исполняемого файла службы (в нашем случае полный путь и имя myDrv.sys).
Посмотрим на исходный текст простого консольного приложения, написанного на Visual C++ 6.0:
#include
#include «LoadDRV.h»
#include
#include
void main()
<
LPTSTR m_name = new char[20];
strcpy(m_name, «myDrv.sys»);
if (drvLoad(m_name)) TestSmth();
drvUnLoad(m_name);
delete m_name;
>
DeviceIoControl(hDevice, IOCTL_TEST_SMTH, NULL, 0,
&test, 4, &ReturetLength, NULL);
printf(«TestSmth= %in»,test);
return test;
>
///**************Функции динамической загрузки************************
bool drvLoad(char* name)
<
printf (name);
hSCManager=NULL;
hService=NULL;
bool status;
if(OpenManager())
<
if(drvCreateService(name))
<
if(drvStartService(name))
<
status=TRUE;
printf(«n Driver is now load. n»);
>
>
>
hDevice = CreateFile («//./myDrv», GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
bool OpenManager()
<
bool status;
status=FALSE;
if(hSCManager!=NULL)
<
CloseServiceHandle (hSCManager);
hSCManager=NULL;
>
hSCManager=OpenSCManager (NULL,NULL,SC_MANAGER_ALL_ACCESS);
if (hSCManager == NULL)
<
error(_OpenSCManager);
> else status=TRUE;
//———————————————————————
bool drvCreateService(PCHAR pDrvName)
<
LPTSTR lpBuffer;
lpBuffer = new char[256];
bool status = FALSE;
LPTSTR awPath; // путь к драйверу с именем pDrvName
// формируем путь к pDrvName, драйвер должен лежать рядом с exe-шником
GetCurrentDirectory(256, lpBuffer);
strcat(lpBuffer,»\»);
strcat(lpBuffer,pDrvName);
awPath = lpBuffer;
hService = CreateService(hSCManager,pDrvName,pDrvName,SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,SERVICE_DEMAND_START,SERVICE_ERROR_NORMAL,awPath,NULL,NULL,NULL,NULL,NULL);
if(!hService)
<
error(_CreateService);
status = drvOpenService(pDrvName);//Пытаемся открыть службу
>
else status=TRUE;
delete lpBuffer;
return status;
>
bool status;
status=FALSE;
if(hService!=NULL) CloseService();
hService=OpenService(hSCManager,name,SERVICE_ALL_ACCESS);
if (!hService) error(_OpenService);
else status=TRUE;
return status;
>
//———————————————————————
bool drvStartService(PCHAR name)
<
bool status;
status=FALSE;
if(!StartService(hService,0,NULL))
<
error(_StartService);
printf(«Deleting service. «);
drvDeleteService(name)
>
else status=TRUE;
return status;
>
//———————————————————————
bool drvDeleteService(PCHAR name)
<
bool status;
status=FALSE;
CloseService();
if(!DeleteService(hService)) error(_DeleteService);
else status=TRUE;
return status;
>
if (hDevice!=INVALID_HANDLE_VALUE)
<
if(!CloseHandle(hDevice)) error(_CloseHandle);
hDevice=INVALID_HANDLE_VALUE;
>
if (hService)
<
status = ControlService(hService,SERVICE_CONTROL_STOP,&ServiceStatus);
if(!status) error(_SERVICE_CONTROL_STOP);
status = DeleteService(hService);
if(!status) error(_DeleteService);
status = CloseServiceHandle(hService);
if(!status) error(_CloseServiceHandle);
>
if(!CloseServiceHandle(hSCManager)) error(_CloseServiceHandle);
if (status) printf(«Driver Unload. SUCCESSn»);
return status;
>
//———————————————————————
void error(error_index erIndex)
< DWORD err;
err=GetLastError();
switch(erIndex)
<
case _OpenSCManager:
printf(«OpenSCManager failed with Error=%in»,err);
break;
case _GetFullPathName:
printf(«GetFullPathName failed with Error=%in»,err);
break;
case _CreateService:
switch (err)
<
case 1073:
printf(«The specified service already exists.n»);
printf(«opening this service. «);
break;
default:
printf(«CreateService failed with Error=%in»,err);
>
break;
case _OpenService:
printf(«OpenService failed with Error=%in»,err);
break;
case _StartService:
printf(«StartService failed with Error=%in»,err);
break;
case _DeleteService:
printf(«DeleteService failed with Error=%in»,err);
break;
case _SERVICE_CONTROL_STOP:
printf(«SERVICE_CONTROL_STOP failed with Error=%in»,err);
break;
case _CreateFile:
printf(«CreateFile failed with Error=%in»,err);
break;
case _CloseHandle:
printf(«CloseHandle failed with Error=%in»,err);
break;
case _CloseServiceHandle:
printf(«CloseServiceHandle failed with Error=%in»,err);
break;
#define FIRST_IOCTL_INDEX 0x800
#define FILE_DEVICE_myDrv 0x00008000
#define TEST_SMTH CTL_CODE(FILE_DEVICE_myDrv, \
0x800 + 101, \
METHOD_BUFFERED, \
FILE_ANY_ACCESS)
Это приложение загружает драйвер, файл которого лежит в одной папке, что и exe-файл данного приложения.
Посмотрим на исходный текст:
В main() мы создаем переменную с именем драйвера (myDrv.sys) и передаем это имя в функцию динамической загрузки драйвера drvLoad(), которая выполняет все необходимые действия по работе с менеджером служб, и в конце вызывает CreateFile(), которая возвращает дескриптор, нужный для работы с драйвером как файловым объектом. Этот дескриптор, в частности, используется при вызове функции DeviceIoControl.
Если драйвер загружен успешно, то вызываем функцию TestSmth(), внутри которой мы создаем и посылаем драйверу IRP-пакет (с помощью вызова DeviceIoControl()). Приняв этот пакет, наш драйвер возвращает адрес своей процедуры DriverEntry. После этого выгружаем драйвер. Все.
Итак, мы написали простейший (дальше некуда) драйвер и приложение, работающее с ним. В этой статье я активно цитировал материалы из книги П. И. Рудаков, К. Г. Финогенов, по возможности делая ссылки на нее в виде: [2].
Через некоторое время напишу о том, как на базе этого драйвера написать драйвер-фильтр для LPT-порта.