Тестируем драйвер на практике

Автор: Иванов Д. В.

Год: 2008

Источник: pcports.ru

Итак, продолжим. Теперь у нас есть готовый драйвер, который мы сделали в прошлой статье. Теперь его надо как-то поиспользовать. Как мы будем это делать? Решим обычную задачу:

С помощью драйвера через пользовательскую программу хотим записывать данные в нужный порт и читать данные из порта под ОС Windows NT, 2000, XP.

Вот этим мы сейчас именно и займемся, но сначала, надо зарегестрировать наш драйвер, поскольку ОС про него пока ничего не знает. Для того чтобы зарегистровать драйвер в системе, надо выполнить следующие шаги:

  1. Скопируйте файл драйвера Port.sys в папку C:\Windows\system32\drivers\ если, конечно, система у Вас установлена на диск С: и Вы не меняли пути установки Windows.
  2. Среди файлов к этой статье, найдите файл install.reg и запустите его. При этом Вы увидете сообщение следующего вида:
    Нажимайте Да. При этом Вы получите сообщение об успешном внесении информации в реестр.
    Можно лично убедиться в этом. В программе редактора реестра regedit.exe в ветви HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Port можно увидеть информацию о нашем драйвере:
    Занесение информации о драйвере в рееестр - очень важный момент. Именно от туда ОС при загрузке драйверов будет черпать информацию, какие драйвера загружать и как ей надо это надо делать.
  3. Ну и наконец, самый важный элемент - ПЕРЕЗАГРУЗКА КОМПЬЮТЕРА, чтобы внесенные нами изменения вступили в силу. Теперь при загрузке, Windows увидит наш драйвер и загрузит его в память.

    Теперь создадим пользовательскую программу, которая сама прав доступа к портам ввода-вывода в Windows NT не имеет, но используя наш драйвер Port.sys сможет легко работать с портами компьютера. Итак, создаем пустой проект консольного приложения в VS++, создаем пустой файл *.cpp и копируем туда ниже следующий код:

    #include < windows.h>
    #include < iostream.h>
    #include < stdio.h>
    #include < conio.h>

    //определяем коды дейсвий для драйвера
    #define IOCTL_READ (0x800<<2)|(0x22<<16)
    #define IOCTL_WRITE (0x801<<2)|(0x22<<16)

    HANDLE hDrv;
    DWORD cbRet;

    int main()
    {
        //открываем драйвер
        hDrv = CreateFile ( "\\\\.\\MYDRIVER", GENERIC_READ | GENERIC_WRITE,
            0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
        // если не удалось, выходим из программы
        if ( hDrv == INVALID_HANDLE_VALUE )
        {
        cout<<"Error! Can`t open driver. Press any key to exit..."<     getch();
        return false;
        }
        else
        cout<<"Driver is open!"<
        //Тестируем запись данных в порт через драйвер

        USHORT DataToDriver[2];
            DataToDriver[0]=888; //адрес порта куда писать
            DataToDriver[1]=224; //что писать в порт
        DeviceIoControl(hDrv,IOCTL_WRITE,DataToDriver,4,NULL,0,&cbRet,NULL);


        //Тестируем чтение данных из порта через драйвер

        USHORT DataToDriver_[1];
        DataToDriver_[0]=888; //адрес порта откуда читать

        USHORT DataFromDriver; //куда записать результат чтения
        DeviceIoControl(hDrv,IOCTL_READ,DataToDriver_,2,&DataFromDriver,2,&cbRet,NULL);
        cout<
        return true;
    }

    Компилируем, запускаем. Если все в порядке: программа откомпилировалась, драйвер установился, то Вы должны увидеть следующее: строчку с сообщением об успешном открытии драйвера, затем в регистре Data LPT порта должно появиться число 224, далее это число отобразиться на экране. (Наблюдать факт появления числа в регистре лучше используя какой-либо блок светодиодов, подключенный к LPT порту.)

    "Что же здесь происходит?" - спросите Вы. Давайте разбираться. В самом начале программы, после подключения необходимых заголовочных файлов, идет определение кодов дейсвия драйвера, которые используются приложением для обращения к драйверу для выполнения определенного дейсвия. Они Вам ни чего не напоминают? Да, именно теже коды были заданы нами в прошлой статье в коде драйвере. Теперь используя их, мы можем требовать от драйвера делать то, что ему положено при поступлении того или иного кода дейсвия.

    Далее мы получаем доступ к драйверу (налаживаем канал связи) с помощью функции CreateFile(), используя в качестве имени драйвера его специально определенное символическое имя MYDRIVER в пространстве имен NT. Если результат открытия был неудачным (например, драйвер не установился), то мы улавливаем этот момент и выходим из программы, т.к. делать нам тогда нечего.

    Теперь начанается самое интересное - обращения к драйверу. Немного теории. Для общения с драйвером существует специальная функция DeviceIoControl(), принимающая целую кучу парпаметров. Ниже представлены пронуменрованные параметры этой функции:

    1. handle на открытый драйвер. Определяет к какому драйверу обращаться.
    2. Код действия, который необходимо выполнить драйверу.
    3. адрес на буфер, в котором находятся данные для отправки в драйвер
    4. размер отправляемых данных в байтах
    5. адрес на буфер, куда драйвер поместит данные, отправляемые программе
    6. число байт, которое мы расчитываем получить от драйвера
    7. реальное число переданных байт из драйвера
    8. не используем, и ставим NULL

    Сначала проводится тестирование записи данных в порт. Для этого создается массив DataToDriver из 2-х элементов. В первый элемент массива помещается адрес порта, куда необходимо записать данные. В этом примере используется адрес 888 - адрес регистра Data LPT порта в десятичной форме. Во второй элемент размещаем данные, которые надо переслать в порт. В данном случае это число 224. Потом идет вызов упомянутой выше функции DeviceIoControl(), которой мы передаем следующие параметры:

    Далее идет тестирование чтения данных из порта. Создаем массив из одного элемента (можно было просто переменную) DataToDriver_. В единственный элемент этого массива записываем адрес порта откуда нужно читать данные. В данном случае это все тотже регистр Data LPT порта. Определяем переменную DataFromDriver, в которую поместим результат чтения. Опять вызываем DeviceIoControl(), передавая ей параметры:

    После последнего вызова функции DeviceIoControl() в переменной DataFromDriver будет находиться число, прочитанное из порта. Его мы именно в конце программы и выводим на экран.

    Ну вот и все. Теперь Вы вполне можете сами написать простой драйвер и использовать его в своих целях. На этом статьи по драйверам не заканчиваются; в следующей статье мы поговорим про динамическую загрузку драйвера, что является гораздо более удобным способом установки драйвера нежели используя реестр с дальнейшей перезагрузкой компьютера.