Статья
Версия для печати
Обсудить на форуме (9)
Драйвер с нуля.



В один прекрасный день меня попросили написать Драйвер. На тот день мои познания в 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 скопируйте следующее:
Код:
TARGETNAME=myDrv
TARGETPATH=obj
TARGETTYPE=DRIVER

SOURCES=myDrv.c MyDrv.rc
  В MAKEFILE скопируйте следующее:
Код:
!INCLUDE $(NTMAKEENV)makefile.def
 В myDrv.rc скопируйте следующее:
Код:
#include <windows.h>
#include <ntverp.h>
#define VER_FILETYPE VFT_DRV
#define VER_FILESUBTYPE VFT2_DRV_SYSTEM
#define VER_FILEDESCRIPTION_STR     "Generic Port I/O"
#define VER_INTERNALNAME_STR        "myDrv.sys"
#include "common.ver"

А вот так должен выглядеть myDrv.h:
Код:
#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)

Теперь обратимся к тексту самого драйвера myDrv.c:
Код:
#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;
  
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

//прототипы функций
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;
 
  RtlInitUnicodeString(&deviceNameUnicodeString, NT_DEVICE_NAME);
  
  ntStatus = IoCreateDevice(DriverObject,
                            sizeof (DEVICE_EXTENSION),
                            &deviceNameUnicodeString,
                            FILE_DEVICE_UNKNOWN,
                            0,
                            FALSE,
                            &deviceObject);

  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;
}

 Немного поясню содержимое файла myDrv.c. Итак, по порядку:
Код:
#define NT_DEVICE_NAME	    L"\Device\myDrv"
#define DOS_DEVICE_NAME            L"\DosDevices\myDrv"- Эти строки служат для задания символических имен текстовыми строками с именами объекта устройства, который будет создан нашим драйвером.
Далее приведены прототипы используемых функций. Этим функциям можно дать произвольные имена, НО их сигнатура (состав параметров с типом возвращаемого значения) жестко заданы системой. [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/~gorinov/texts/irp99.html.

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 и со следующим содержанием:
Код:
 
%SystemRoot%system32cmd.exe /c "cd C:NTDDKbin&&setenv.bat C:NTDDK&&cd    c:myDriver &&build -ceZ"
pause

Замечание:

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 <conio.h>
#include "LoadDRV.h"
#include <string.h>
#include <stdio.h>


void main()
 {
  LPTSTR m_name = new char[20];
  strcpy(m_name, "myDrv.sys");

  if (drvLoad(m_name)) TestSmth();
  
  drvUnLoad(m_name);
  delete m_name;  
  }

//-----------------------------------------------------------------------------
//создаем и посылаем драйверу  IRP-запрос
int TestSmth(void)//0x800 + 101
{
  int test = 0;
  DWORD ReturetLength = 0;

  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;

status=FALSE;


  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);

return status;
}

//---------------------------------------------------------------------

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;

return status;
}

//---------------------------------------------------------------------
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 drvOpenService(PCHAR name)
{

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;
}

//-------------------------------------------------------------------
void CloseService()
{
CloseServiceHandle (hService);
hService=NULL;
}

//-------------------------------------------------------------------
int drvUnLoad(PCHAR name)
{
int status;
status=FALSE;

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;

}
}

И содержимое h-файла этого приложения:
Код:
#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-порта.
                                                                                                                         
Артем.
Версия для печати
Обсудить на форуме (9)