Драйверы устройств в системе Windows
Драйвер - системная программа, предназначенная для управления каким-либо физическим или виртуальным устройством компьютера. Система ввода и вывода в Windows. Объекты для управления оборудованием. Разработка dll-библиотеки для взаимодействия с драйвером.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | дипломная работа |
Язык | русский |
Дата добавления | 07.12.2010 |
Размер файла | 3,8 M |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
Связь драйвера с приложением пользователя
Также остается неясным еще один вопрос, связанный с драйверами: как именно с нашим объектом устройства может связаться приложение или другой драйвер? Большинство из устройств в системе именованы, хотя теоретически допускается существование неименованных (anonymous) устройств. Связь с устройством можно установить двумя методами:
при помощи GUID.
при помощи символической ссылки.
1. GUID (Globally Unique Identifier, глобально уникальный идентификатор) - 16-байтное уникальное число. GUID используются для идентификации в системе драйверов, СОМ-объектов и т.п. В идеале, во всем мире не может быть двух одинаковых GUID, поэтому GUID может быть абсолютно уникальным идентификатором драйвера. GUID генерируется на основе текущей даты, времени и номера сетевого адаптера, если такой присутствует, и обычно указывается в заголовочном файле класса устройства и программы, которая хочет связаться с ним приблизительно таким образом:
#define MyDevice_CLASS_GUID \
{ 0xff779f4c, 0x8b57, 0x4a65, { 0x85, 0xc4, 0xc8, 0xad, 0x7a, 0x56, 0x64, 0xa6 } }
2. Символическая ссылка (symbloic link) похожа на путь к файлу и в тексте программы имеет вид:
char *sLinkName = "\\\\.\\MyDevice";
Если отбросить лишние символы бэкслэша, необходимые для соблюдения синтаксиса С++, то символическая ссылка оказывается строкой \\.\MyDevice. Чтобы понять принцип работы символической ссылки, следует знать, что в ОС есть системный каталог различных объектов, которые присутствуют в системе: драйверов, устройств, объектов событий, семафоров и т.п. Символическая ссылка - специфический тип объекта, который обеспечивает доступ к другим системным объектам. Специальный подкаталог системного каталога зарезервирован для символических ссылок на другие объекты ОС. Программа пользователя может обратиться к этим символическим ссылкам при помощи функций API.
Как же следует проектировать интерфейс с драйвером? Следует использовать GUID или символическую ссылку?
Идентификация драйвера при помощи GUID считается более правильной. Как было упомянуто выше, специальные алгоритмы гарантируют то, что GUID будет действительно уникальным. А кто мешает разработчику, находящемуся на другом конце света, также создать устройство с той же ссылкой на него \\.\MyDevice? Вообще-то, никто. Но с другой стороны, с написанной на понятном английском языке ссылкой гораздо проще обращаться, особенно на этапе разработки драйвера, чем с длинным и непонятным GUID. Так что, вероятно, на этапе разработки и отладки драйвера для интерфейса драйвера с приложением лучше использовать символическую ссылку, а для коммерческой версии драйвера - GUID.
5. Разработка драйвера в среде DriverStudio
5.1 Использование Driver Wizard
Процесс разработки драйвера при помощи DriverStudio во многом напоsминает разработку приложения в среде Visual C++. Создание проекта происходит при помощи мастера DriverWizard, похожего на мастер Visual C++. Мастер вызывается или из главного меню ( Пуск - Программы - DriverStudio - DriverWorks - DriverWizard) или из среды Visual C++ при помощи пункта меню DriverStudio - DriverWizard. Программе DriverWizard соответствует иконка
Далее при работе мастера появляется серия диалоговых окон, куда пользователь должен ввести данные, необходимые для формирования скелета драйвера.
Рис 6. Первый шаг DriverWizard
На первом шаге создания драйвера необходимо ввести имя проекта ( в нашем случае - XDSP) и директорию для проекта. После этого - нажать на кнопку Next, чтобы перейти к следующему шагу.
Рис 7. Второй шаг DriverWizard
На втором шаге следует выбрать архитектуру, по которой будет разрабатываться драйвер: Windows NT 4.0 (которая сейчас практически не используется) или WDM, которую нам и следует выбрать.
Рис 8. Третий шаг DriverWizard
На третьем шаге выберем шину, на которой располагается устройство, которое будет контролировать драйвер. Если это устройство будет подключаться к порту компьютера, например к параллельному - набо выбрать None - driver does not control any hardware. Если же устройство будет располагаться на одной из шин компьютера, например на PCI - надо задать дополнительные параметры. В случае PCI устройства надо указать следующие параметры:
Код производителя (PCI Vendor ID) - четырехзначное шестнадцатеричное число, которое однозначно идентифицирует производителя устройства. Пусть в нашем случае оно будет равно 1999.
Код устройства (PCI Device ID) - также четырехзначное шестнадцатеричное число, которое однозначно идентифицирует устройство нашего производителя. Пусть в нашем случае это будет 680C.
Номер подсистемы PCI. Обычно имеет вид код устройства + код производителя. В нашем случае - 680C1999.
Номер версии устройства (PCI Revision ID) - номер версии устройства. В нашем случае 01.
Эти коды весьма важны: по ним система будет находить драйвер для устройства. Эти же коды аппаратно прошиты в PCI-карточке. И если коды, заданные в драйвере (если быть точным, то они задаются не в самом файле драйвера, а в инсталляционном скрипте - inf-файле), не совпадут с кодами в PCI-устройстве, то драйвер не установится.
Рис 9. Четвертый шаг DriverWizard
На четвертом шаге мастера необходимо задать имена, которые DriverWizard присвоит файлу С++, который содержит класс драйвера, и самому классу драйвера (Driver Class).
Рис 13. Пятый шаг DriverWizard
На пятом шаге следует указать, какие функции должен выполнять драйвер. Это может быть:
чтение (read) - обработка запросов на чтение.
запись (write) - обработка запросов на запись.
сброс (flush) - обычно это сброс буфера обмена с устройством.
управление устройством (device control) - обработка других запросов.
внутреннее управление устройством (internal device control) - обработка запросов от других драйверов устройств.
Рис 14. Шестой шаг DriverWizard
На шестом шаге DriverWizard задает вопросы о способе обработки запросов. Опция Select queuing method выбирает, каким образом будут буферизироваться запросы на ввод-вывод:
None - запросы не буферизируются в очереди. Эту опцию лучше не выбирать.
DriverManaged - драйвер содержит одну или более одной очередей, в которой сохраняются запросы на ввод-вывод, пришедшие от других драйверов или системы.
SystemManaged - драйвер использует только одну очередь сообщений.
Также надо выбрать, будут ли буферизироваться запросы на чтение и запись. Как было сказано ранее, устройство может одновременно выполнять какую-то одну операцию, например, только чтение или только запись, или может выполнять несколько операций сразу. Чтобы гарантировать нормальную работу устройства в этом случае, следует буферизировать (Serialize) поступающие запросы на чтение и запись, помещая их в очередь. Установка флажков Seralize all Read requests и Serialize all Write requests позволяет буферизировать все запросы на чтение и запись, поступающие в объект устройства.
Рис. 15 - Седьмой шаг DriverWizard.
На седьмом шаге предлагается задать параметры, которые драйвер будет загружать из реестра Windows при старте, когда система загружается. При этом задается параметр реестра, имя переменной, куда сохраняется его значение, тип данного параметра и его значение по умолчанию. Если не менять настройки, то во время загрузки драйвер читает из реестра параметр BreakOnEntry типа boolean, сохраняет его значение в переменной m_BreakOnEntry. Значение по умолчанию для параметра - false. Обычно m_BreakOnEntry используется в отладочных целях.
Запись и считывание параметров из реестра позволяет драйверу задавать какие-либо конфигурационные параметры, сохранять данные, необходимые для его запуска или работы.
При помощи кнопок Add, Edit и Delete можно соответственно добавлять, редактировать и удалять параметры.
Рис. 16 - Восьмой шаг DriverWizard.
Восьмой шаг DriverWizard - один из самых важных моментов в разработке драйвера PCI - устройства при помощи DriverWorks. Поэтому окно мастера несет огромное количество информации и элементов управления.
На данном шаге предлагается изменить имена классов устройства для данного драйвера. В списке в верхней части окна следует выбирать класс устройства, который следует переименовать, и, нажав на кнопку Rename, можно задать новое имя класса устройства.
Окно DriverWizard также содержит несколько вкладок:
Рис.17 - вкладка Resource
Вкладка Resource. На ней определяются основные аппаратные ресурсы, которые есть в устройстве и которые будет контролировать этот драйвер. В их числе адреса памяти, диапазоны портов ввода-вывода, линии запроса на прерывание и линии прямого доступа к памяти (DMA), которые необходимы для работы драйвера. Задать ресурсы можно при помощи кнопок в нижней части вкладки.
Например, задать диапазон памяти, которую несет "на борту" устройство, можно, нажав на кнопку Add Memory Range. При этом выводится диалоговое окно, куда следует ввести сведения о новом диапазоне адресов памяти: имя объекта класса KMemoryRange, который будет контролировать этот диапазон адресов, адрес базового регистра в PCI - заголовке (PCI header) данного устройства, который определяет этот диапазон адресов, а также параметры доступа для данной памяти: только чтение (Read Only), только запись (Write Only) и полный доступ (Read/Write). Также можно еще задать опции разделения доступа (Share options). Эти опции позволяют разделять доступ к ресурсу: к нему можно обращаться только из класса данного устройства (Exclusive to this device), из любой части драйвера (Shareable within this driver) или из любого драйвера в системе (Shareable system wide). Впрочем, для разработки простых драйверов эти опции являются бесполезными и изменять их не стоит. В нашем случае мы создаем диапазон адресов памяти с именем m_MainMemoryRange, определяемый нулевым базовым регистром в PCI - header'e, с полным доступом.
Рис. 18 - задание диапазона адресов памяти.
По аналогичному принципу можно задать параметры портов ввода-вывода и линий DMA. Параметры линий запроса на прерывание посложнее: тут можно дать указание DriverWizard'у создать шаблоны для классов ISR, DPC и их функций (Make ISR/DPC class functions).
Если в процессе задания ресурсов задана ошибка или необходимо внести какие-либо изменения, то для этого надо щелкнуть по названию ресурса в окне правой клавишей мыши. Появится контекстное меню, в котором надо выбрать пункт Delete, чтобы удалить ресурс или Edit - редактировать его.
Рис. 19 - вкладка Interface.
На вкладке Interface задается способ, каким образом будет осуществляться связь программы или библиотеки DLL с драйвером.
Надежным способом является связь при помощи GUID класса. GUID - уникальный номер, который однозначно идентифицирует какой-либо объект системы. При помощи GUID идентифицируются не только драйвера, а и СОМ - интерфейсы и пр.
Другим способом реализации интерфейса является символическая ссылка. Это более естественный путь, т.к. просто указать имя класса устройства - гораздо проще, чем указывать непонятного вида GUID.
Рис. 20 - вкладка Buffers.
На вкладке Buffers определяется метод, каким образом буферизируются запросы к устройству.
Буферизированный (buffered) метод - пригоден для устройств типа мыши, клавиатуры, которые передают небольшие объемы данных за короткий промежуток времени. Прямой (direct) метод - используется при пересылке больших объемов информации за короткий промежуток времени, например, при обращении к дисководу.
Рис. 21 - вкладка Power.
При создании WDM - драйвера необходимо задать способ управления энергопотреблением. При помощи флажка Управлять энергопотреблением этого устройства (Manage power for this device) можно создать в драйвере методы управления энергопотреблением нашего устройства. В нашем простом случае мы не будем этого делать.
Рис. 22 - девятый шаг DriverWizard.
Естественно, для более-менее сложного драйвера устройства будет недостаточно двух запросов на чтение и запись. На девятом шаге можно задать коды управления драйвером устройства. Код управления (Device IO control code, IOCTL) просто представляет собой число, которое передается драйверу. Коды управления в драйвере обрабатываются специальной функцией. В ответ на каждый код драйвер выполняет какое-либо действие. Например, в нашем случае объект устройства будет возвращать количество памяти, которое имеет PCI-карточка. Для этого зададим код управления XDSP_GetMemSize. Для этого нажмем на кнопку Add, появится диалоговое окно Edit IO Control Code (редактирование кода управления).
Рис. 23 - задание кода управления драйвером.
драйвер системный компьютер библиотека
При задании кода управления устройством нужно указать имя кода в понятном программисту виде, метод общения с устройством (прямой или буферизированный). Также задается порядковый номер кода (Ordinal) - число, являющееся его уникальным номером. Числа, меньшие 0x800 используются для стандартных кодов, таких, как чтение, запись и т.п.
Запросы IOCTL также можно буферизировать, подобно запросам на чтение и запись. Для этого надо установить флажок Queue (serialize) this request code.
Внизу окна мастера указано имя заголовочного файла, в котором будут храниться коды управления устройством. В нашем случае этоXDSPioctl.h. Ненужные коды управления устройством можно удалить, нажав на кнопку Remove или редактировать, нажав кнопку Edit.
Рис. 24 - десятый шаг DriverWizard.
Одним из достоинств DriverWorks является то, что DriverWizard сразу создает консольное приложение для тестирования работоспособности драйвера. Конечно, такое тестирование бывает неполным и примитивным, но позволяет оценить, правильно ли работает драйвер и работает ли он вообще. Для того, чтобы DriverWizard создал такое приложение, нужно установить флажок Create test console application (создать консольное приложение для тестирования) и указать его имя. Также можно задать опции отладки. Они необходимы при отладке драйвера средствами DriverStudio. При написании простых драйверов эти опции, скорее всего, не понадобятся.
Пройдя все эти шаги, нажмите на кнопку Finish. В ответ появится окошко, которое содержит сведения о каталоге с файлами проекта нашего драйвера, для чего предназначен каждый файл. Нажимаем на кнопку ОК - DriverWizard сгенерирует все файлы нашего драйвера, приложения для тестирования и предложит открыть проект в Visual C++.
5.2 Компиляция и установка драйвера
Проект, сгенерированный DriverWizard, находится в каталоге XDSP. В этом каталоге расположены файлы рабочего пространства (Workspace) VC++: XDSP.dsw, XDSP.ncd и XDSP.opt и два каталога - sys и exe. Здесь же находится файл XDSPioctl.h. В нем описаны управляющие коды, используемые при обращении к драйверу с помощью функции DeviceIOControl.
В каталоге sys находится сгенерированный DriverWizard скелет драйвера и все необходимые для компиляции файлы. В нашем случае, имеем файлы:
Function.h
заголовочный файл, предназначенный для определения функций, входящих в драйвер;
Makefile, Sources
файлы с информацией, необходимой для компиляции проекта в VC++.
XDSP.h , XDSP.cpp
файлы, содержащие класс драйвера.
XDSP.plg, XDSP.dsp
проект VC++;
XDSP.inf
скрипт для инсталляции драйвера;
XDSP.rc
файл ресурсов проекта. В основном, содержит информацию о разработчике драйвера.
XDSPDevice.cpp, XDSPDevice.h
файлы, содержащие класс устройства.
В каталоге ехе находится исходный код консольного приложения TextXDSP, предназначенного для тестирования работы драйвера. При помощи него можно убедиться, правильно ли установлен драйвер в системе, а иногда даже проверить, как он работает. Хотя для более-менее сложного драйвера придется писать программу тестирования отдельно. В каталоге присутствуют файлы:
Makefile,Sources
файлы с информацией, необходимой для компиляции проекта в VC++.
Test_XDSP.plg, Test_XDSP.dsp
проект VC++;
Test_XDSP.cpp
исходный текст приложения.
Теперь самое время открыть проект драйвера в среде VC++ и посмотреть, что же мы имеем. Для этого надо запустить VC++ и открыть проект, используя команду File -> Open Workspace. В появившемся диалоговом окне открытия файла выберите файл XDSP.dsw. Если все вышеописанные действия выполнены правлиьно, то проект откроется в среде VC++. Для того, чтобы проект скомпилировался правильно, следует установить переменные среды DriverStudio. Для этого нужно выбрать пункт меню DriverStudio -> Driver Build Settings: На экране появится диалоговое окно установки переменных среды:
Рис.25 - установка значений переменных среды.
Для компиляции драйвера важны две переменные:
1. CPU - определяет архитектуру процессора, под которую компилируется драйвер. Не стоит забывать, что Win2000 может работать на платформах i386 (классические процессоры Intel), IA64 (64-разрядные процессоры Intel) и Alpha. В нашем случае надо установить значение i386.
2. BASEDIR - путь к пакету DDK, установленному в системе. Для того, чтобы изменить значение одной из этих переменных, надо нажать кнопку Edit: диалогового окна. Появится окно установки значений переменной.
Установив требуемое значение, нажмите кнопку Set. Чтобы закрыть окно - Exit. Задав переменные среды, нажмите кнопку Accept. Теперь можно компилировать проекты.
Драйвер может быть скомпилирован в двух конфигурациях: Checked и Free.
Checked - отладочный вариант драйвера. Такой драйвер несет в себе информацию для отладки. Естественно, что для отладки драйверов непригодны обыкновенные отладчики, входящие в комплект сред VC++, Delphi и т.п. Все они работают в 3-м кольце привилегий процессора и даже не догадываются, какие драйвера есть в системе. Для отладки драйверов применяются специальные отладчики, работающие в режиме ядра ОС. В качестве отладчика лучше всего использовать SoftIce, поставляемый с DriverStudio.
Free - драйвер не несет отладочную информацию.
Активную конфигурацию можно выставить при помощи пункта меню Build -> Set Active Configuration:
Особенность сгенерированного DriverWizard рабочего пространства состоит в том, что оно содержит два проекта: XDSP и Test_XDSP. Как нетрудно догадаться, XDSP - это проект драйвера, а Test_XDSP - приложения тестирования. Информация о проектах выводится в окне Workspace среды VC++.
В каждый отдельный момент времени можно компилировать только активный проект. Имя активного проекта выводится жирным шрифтом. Сделать активным другой проект просто: надо щелкнуть на его названии правой клавишей мыши и в выпавшем контекстном меню выбрать пункт Set as Active Project (Сделать активным проектом).
Теперь можно выполнять компиляцию проекта. Если в процессе компиляции появляются сообщения об ошибках - значит, вы не совсем точно следовали инструкциям, изложенным выше: или не скомпилировали библиотеки DriverWorks, или не установили переменные среды.
После компиляции драйвера следует скомпилировать тестовое приложение Test_XDSP. Оно должно скомпилироваться без каких-либо проблем.
Если все операции прошли гладко - то можете себя поздравить: драйвер готов к работе. Хотя, естественно, он не выполняет никаких разумных действий. Теперь можно протестировать наш драйвер.
После компиляции мы получили файл драйвера XDSP.sys. Он находится в каталоге .../XDSP/sys/obj/i386. В этом каталоге будут находится скомпилированные DriverStudio драйвера. Но для инсталляции кроме самого драйвера еще нужен скрипт XDSP.inf. Он обычно находится в самом каталоге XDSP.
Итак, для установки драйвера в системе предполагается наличие в системе PCI - карточки XDSP-680. После установки карточки (или перепрограммирования ее из среды Foundation) следует перезагрузить компьютер. При загрузке компьютер обнаружит новое устройство и потребует предоставить драйвер для него. Если же не потребует - значит, в системе есть более ранняя версия драйвера. Для этого надо открыть список устройств, установленных на компьютере и обновить драйвер для устройства. Для этого надо указать путь к скрипту xdsp.inf и к файлу драйвера xdsp.sys.
Если же Вы разрабатываете драйвер, который не управляет каким-либо устройством или это устройство не является PnP - необходимо просто установить драйвер стандартными средствами Windows: Пуск -> Настройка -> Панель управления -> Установка оборудования. Когда Windows выведет полный список типов устройств и спросит, какое устройство Вы хотите установить, выберите свой тип устройства.
Если разработанный Вами драйвер не подходит под какой-либо из известных классов устройств, то "Другие устройства" также являются неплохим вариантом. Такая ситуация тоже случается нередко - мне, например, приходилось разрабатывать драйвер для программатора микроконтроллеров, подключавшегося через параллельный порт. Конечно же, он не подходил под какой-либо из известных в Windows типов устройств.
После того, как драйвер будет установлен, нужно будет проверить его функционирование. Запустите скомпилированный файл test_xdsp.exe с параметрами test_xdsp r 32 (команда прочитать 32 байта из устройства). Должно появиться сообщение, похожее на это:
C:\XDSP\exe\objchk\i386>Test_XDSP.exe r 32
Test application Test_XDSP starting...
Device found, handle open.
Reading from device - 0 bytes read from device (32 requested).
-, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -, -,
-, -, -, -, -, -,
В данном случае приложение установило связь с драйвером и прочитало из него 32 байта. Функция чтения в драйвере не определена, поэтому, естественно, драйвер вернет абракадабру. Если же будет получено сообщение вида
C:\...Projects\XDSPdrv\exe\objchk\i386>Test_XDSP.exe r 32
Test application Test_XDSP starting...
ERROR opening device: (2) returned from CreateFile
Exiting...
- то приложение не смогло установить связь с драйвером. Следует попробовать переустановить драйвер.
5.3 Наращивание функциональных возможностей драйвера
Рассмотрим подробно текст драйвера, сгенерированного DriverWizard и внесем в него необходимые изменения.
В проекте присутствуют всего два класса:
XDSP
класс драйвера;
XDSPDevice
класс устройства.
Также есть несколько глобальных функций и переменных:
PNPMinorFunctionName - возвращает строку с текстовым названием кода функции IOCTL. Эта функция используется при отладке, когда надо перевести числовое значение кода IOCTL в строку с его названием.
POOLTAG DefaultPoolTag('PSDX') - используется совместно с BoundsChecker для отслеживания возможных переполнений буфера и утечек памяти.
KTrace t("XDSPdrv") - глобальный объект трассировки драйвера. Этот объект используется для вывода сообщений трассировки при работе драйвера. Использование объекта трассировки аналогично использованию класса iostream в С++. Вывод отладочных сообщений производится при помощи оператора <<. Примеры использования объекта трассировки неоднократно встречаются в тексте драйвера, например:
t << "m_bBreakOnEntry loaded from registry,
resulting value: [" << m_bBreakOnEntry << "]\n";
В данном примере объект трассировки используется для вывода строки "m_bBreakOnEntry loaded from registry, resulting value: [" и значения логической переменной m_bBreakOnEntry. Все сообщения трассировки можно прочитать в отладчике SoftIce.
Начнем анализ текста драйвера с класса XDSP (класс драйвера). В строке 31 при помощи макроса DECLARE_DRIVER_CLASS декларируется класс драйвера XDSP. Далее следует метод DriverEntry, который вызывается при инициализации драйвера:
NTSTATUS XDSPdrv::DriverEntry(PUNICODE_STRING RegistryPath)
//В строке RegistryPath содержится ключ реестра, в котором
// система хранит информацию о драйвере.
{
//Далее выводится трассировочное сообщение, информирующее
// о вызове метода DriverEntry:
t << "In DriverEntry\n";
//После этого драйвер создает объект Params класса
// KRegistryKey и считывает данные из
//реестра для этого драйвера:
KRegistryKey Params(RegistryPath, L"Parameters");
//Далее производится проверка на успех:
if ( NT_SUCCESS(Params.LastError()) )
{
//Текст, заключенный в марос препроцессора DBG будет откомпилирован
// только в отладочной версии
//драйвера.
#if DBG
ULONG bBreakOnEntry = FALSE;
// Читается значение переменной BreakOnEntry реестра:
Params.QueryValue(L"BreakOnEntry", &bBreakOnEntry);
// Если она принимает значение true,то инициировать
// точку останова в отладчике.
if (bBreakOnEntry) DbgBreakPoint();
#endif
//Загрузить остальные параметры реестра.
LoadRegistryParameters(Params);
}
m_Unit = 0;
//Вернуть успех
return STATUS_SUCCESS;
}
Метод LoadRegistryParameters зaгружает из реестра все остальные параметры, необходимые для драйвера. Впрочем, в нашем драйвере таковых нет, и поэтому функция не выполняет никаких полезных действий (просто загружает значение переменной m_bBreakOnEntry).
void XDSPdrv::LoadRegistryParameters(KRegistryKey &Params)
{
m_bBreakOnEntry = FALSE;
Params.QueryValue(L"BreakOnEntry", &m_bBreakOnEntry);
t << "m_bBreakOnEntry loaded from registry, resulting value:
[" << m_bBreakOnEntry << "]\n";
}
На этом заканчивается секция инициализации драйвера. Далее следует метод AddDevice. Он вызывается, когда система обнаруживает устройство, за которое отвечает драйвер (обычно это происходит при загрузке драйвера). В метод ситема передает указатель на физический объект устройства (Physical Device Object, PDO). Этот объект представляет собой некий блок информации о физическом устройстве, который используется ОС. Данный метод создает объект устройства XDSPDevice. С точки зрения системы, создается функциональный объект устройства (Functional Device Object, FDO).
NTSTATUS XDSPdrv::AddDevice(PDEVICE_OBJECT Pdo)
{
t << "AddDevice called\n";
//Здесь вызывается конструктор класса XDSPDevice.
XDSPdrvDevice * pDevice = new (
static_cast(KUnitizedName(L"XDSPdrvDevice", m_Unit)),
FILE_DEVICE_UNKNOWN,
static_cast(KUnitizedName(L"XDSPdrvDevice", m_Unit)),
0,
DO_DIRECT_IO
)
XDSPDevice(Pdo, m_Unit);
//m_Unit - количество таких устройств в системе.
if (pDevice == NULL)
//Не удалось создать объект устройства. Похоже, произошла какая-то ошибка.
{
t << "Error creating device XDSPdrvDevice"
<< (ULONG) m_Unit << EOL;
return STATUS_INSUFFICIENT_RESOURCES;
}
//Получить статус создания устройства.
NTSTATUS status = pDevice->ConstructorStatus();
if ( !NT_SUCCESS(status) )
//Похоже, устройство создано, но неудачно; произошла ошибка.
{
t << "Error constructing device XDSPdrvDevice"
<< (ULONG) m_Unit << " status " << (ULONG) status << EOL;
delete pDevice;
}
else
{
m_Unit++; //Устройство создано удачно
}
//Вернуть статус устройства.
return status;
}
Все. Работа объекта драйвера на этом окончена. Как мы можем видеть, объект драйвера практически не выполняет каких-либо функций управления аппаратурой, но он жизненно необходим для правильной инициализации драйвера. В нашем случае НЕ ТРЕБУЕТСЯ вносить какие-либо изменения в текст, сформированный DriverWizard.
Основным классом драйвера является класс устройства. Класс устройства XDSPdrvDevice является подклассом класса KpnpDevice. Конструктор получает два параметра: указатель на PDO и номер драйвера в системе.
XDSPdrvDevice::XDSPdrvDevice(PDEVICE_OBJECT Pdo, ULONG Unit) :
KPnpDevice(Pdo, NULL)
{
t << "Entering XDSPdrvDevice::XDSPdrvDevice (constructor)\n";
//Здесь проверяется код ошибки, которую вернул конструктор суперкласса. В случае
//успешного создания объекта базового класса значение переменной m_ConstructorStatus
//будет NT_SUCCESS.
if ( ! NT_SUCCESS(m_ConstructorStatus) )
{
//Ошибка в создании объекта устройства
return;
}
//Запомнить номер драйвера
m_Unit = Unit;
//Инициализация устройства нижнего уровня. В роли устройства нижнего уровня в нашем
//драйвере выступает PDO. Но в случае стека драйверов в качестве устройства нижнего
//уровня может выступать объект устройства другого драйвера.
m_Lower.Initialize(this, Pdo);
// Установить объект нижнего уровня для нашего драйвера.
SetLowerDevice(&m_Lower);
// Установить стандартную политику PnP для данного устройства.
SetPnpPolicy();
}
Порядок вызова методов m_Lower.Initialize(this, Pdo), SetLowerDevice(&m_Lower) и SetPnpPolicy() является жизненно важным. Его нарушение может вызвать серьезные сбои в работе драйвера. Не стоит редактировать текст конструктора, сгенерированный DriverWizard.
Деструктор объекта устройства не выполняет никаких действий. Но для сложных драйверов, когда создаются системные потоки, разнообразные объекты синхронизации и выделяется память, то все созданные объекты должны быть уничтожены в деструкторе. В нашем простейшем случае не стоит вносить изменения в текст деструктора.
XDSPdrvDevice::~XDSPdrvDevice()
{
t << "Entering XDSPdrvDevice::~XDSPdrvDevice() (destructor)\n";
}
Метод DefaultPnp - виртуальная функция, которая должна быть переопределена любым объектом устройства. Эта обработчик по умолчанию для IRP-пакета, у которого старший код функции (major function code) равен IRP_MJ_PNP. Драйвер обрабатывает некоторые из таких пакетов, у которых младший код функции равен IRP_MN_STOP_DEVICE, IRP_MN_START_DEVICE и т.п. (см. ниже) также при помощи виртуальных функций. Но те пакеты, которые не обрабатываются объектом устройства, передаются этой функции. Она ничего с ними не делает, а просто передает их устройству нижнего уровня (если такое есть, конечно). Не стоит изменять текст этой функции.
NTSTATUS XDSPdrvDevice::DefaultPnp(KIrp I)
{
t << "Entering XDSPdrvDevice::DefaultPnp with IRP minor function="
<< PNPMinorFunctionName(I.MinorFunction()) << EOL;
I.ForceReuseOfCurrentStackLocationInCalldown();
return m_Lower.PnpCall(this, I);
}
Метод SystemControl выполняет похожую функцию для IRP-пакетов, у которых старший код функции IRP_MJ_SYSTEM_CONTROL. Он также является виртуальной функцией и не выполняет никаких полезных действий, а просто передает IRP-пакет устройству нижнего уровня. Что-то менять в тексте этого метода надо только в том случае, если наше устройство является WMI-провайдером.
NTSTATUS XDSPdrvDevice::SystemControl(KIrp I)
{
t << "Entering XDSPdrvDevice::SystemControl\n";
I.ForceReuseOfCurrentStackLocationInCalldown();
return m_Lower.PnpCall(this, I);
}
Метод Invalidate вызывается, когда устройство тем или иным образом завершает свою работу: из функций OnStopDevice, OnRemoveDevice а также при всевозможных ошибках. Метод Invalidate объекта устройства также вызывается из деструктора. Его можно вызывать несколько раз - не произойдет ничего страшного; но в методах Invalidate нет никакой защиты от реентерабельности. Т.е. если при работе метода Invalidate возникает какая-либо ошибка и из-за этого Invalidate должен будет вызваться снова, то ни DriverWorks, ни ОС Windows не станут этому мешать. Разработчик должен сам предусмотреть такую возможность и принять меры, чтобы подобная ситуация не привела к нехорошим последствиям.
В методе Invalidate объекта устройства вызываются методы Invalidate всех ресурсов, которые использует драйвер: областей памяти, регистров, контроллеров DMA и т.п. При этом выполняется процедура, обратная процедуре инициализации: освобождаются все ресурсы, используемые объектом, закрываются все его хэндлы, но сам объект не уничтожается и может быть проинициализирован снова. В нашем простом случае нет смысла что-либо корректировать в тексте этого метода - DriverWizard все сделал за нас. Еще бы, ведь наше устройство имеет только один ресурс - диапазон адресов памяти. Но при проектировании более сложных драйверов следует обращать внимание на данный метод. Если разработчик добавляет какие-либо системные ресурсы вручную, то он должен включить соответствующий код в метод Invalidate.
VOID XDSPdrvDevice::Invalidate()
{
//Вызвать метод Invalidate для диапазона адресов памяти.
m_MainMem.Invalidate();
}
Далее следует виртуальная функция OnStartDevice. Она вызывается при приходе IRP- пакета со старшим кодом IRP_MJ_PNP и кодом подфункции IRP_MN_START_DEVICE. Обычно это происходит при старте драйвера после выполнения всех необходимых проверок и инициализаций. В этой функции драйвер инициализирует физическое устройство и приводит его в рабочее состояние. Также здесь драйвер получает список ресурсов, которые имеются в устройстве. На основе этого списка ресурсов выполняется их инициализация. Хотя мы не вносим изменений в данную функцию, но нельзя не отметить ее огромную важность. Именно в данной функции выполняется инициализация устройства и получение списка его ресурсов. По-другому мы их получить никак не можем, т.к. имеем дело с PnP устройством, для которого система распределяет ресурсы самостоятельно.
NTSTATUS XDSPdrvDevice::OnStartDevice(KIrp I)
{
t << "Entering XDSPdrvDevice::OnStartDevice\n";
NTSTATUS status = STATUS_SUCCESS;
I.Information() = 0;
//Здесь драйвер получает список ресурсов устройства
PCM_RESOURCE_LIST pResListRaw = I.AllocatedResources();
PCM_RESOURCE_LIST pResListTranslated = I.TranslatedResources();
// Наше устройство является PCI - карточкой и в своем конфигурационном поле содержит
//базовые адреса диапазонов памяти и портов ввода-вывода. Получаем эти данные
KPciConfiguration PciConfig(m_Lower.TopOfStack());
// Инициализируем каждый диапазон отображения адресов. Теперь, после инициализации,
//базовый адрес отображенного диапазона в виртуальном адресном пространстве
//процессора может быть получен при помощи вызова метода Base(). Физический адрес на
//шине адреса ЦП - при помощи CpuPhysicalAddress().
status = m_MainMem.Initialize(
pResListTranslated,
pResListRaw,
PciConfig.BaseAddressIndexToOrdinal(0)
);
if (!NT_SUCCESS(status))
{
//Неудача при инициализации области памяти
Invalidate();
return status;
}
//Сюда можно добавить код, выполняющий необходимую инициализацию, специфичную для
//этого устройства
return status;
}
Виртуальная функция OnStopDevice вызывается при остановке устройства. В этом случае система посылает драйверу IRP с старшим кодом IRP_MJ_PNP и кодом подфункции IRP_MN_STOP_DEVICE. Драйвер должен освободить все используемые ресурсы.
NTSTATUS XDSPdrvDevice::OnStopDevice(KIrp I)
{
NTSTATUS status = STATUS_SUCCESS;
t << "Entering XDSPdrvDevice::OnStopDevice\n";
//Освободить ресурсы устройства
Invalidate();
// Здесь добавляется код, специфичный для данного устройства.
return status;
}
Виртуальная функция OnRemoveDevice вызывается при извлечении устройства из системы. При этом системная политика PnP сама позаботится об удалении PDO.
NTSTATUS XDSPdrvDevice::OnRemoveDevice(KIrp I)
{
t << "Entering XDSPdrvDevice::OnRemoveDevice\n";
// Освободить ресурсы устройства
Invalidate();
// Здесь добавляется код, специфичный для данного устройства.
return STATUS_SUCCESS;
}
Иногда бывает необходимо отменить обработку IRP, уже поставленного в очередь (такие запросы иногда посылает диспетчер В/В). Когда такая ситуация может возникнуть?
Представим такую ситуацию: в приложении пользователя поток послал нашему драйверу запрос на ввод-вывод и завершил свою работу. А IRP-пакет попал в очередь запросов и терпеливо ждет своей очереди на обработку. Конечно же, обработка такого "бесхозного" IRP-пакета должна быть отменена. Для этого драйвером поддерживается целый механизм отмены обработки запросов. В Win2000 DDK подробно описано, почему ЛЮБОЙ драйвер должен поддерживать этот механизм. Это связано, в основном, с проблемами надежности и устойчивости работы системы. Ведь сбой в драйвере - это, практически, сбой в ядре ОС.
В классе KPnPDevice механизм отмены запроса реализован при помощи метода CancelQueuedIrp.
VOID XDSPdrvDevice::CancelQueuedIrp(KIrp I)
{
//Получаем очередь IRP-пакетов этого устройства.
KDeviceQueue dq(DeviceQueue());
//Проверить, является ли IRP, который должен быть отменен, тем пакетом, который должен
//быть обработан.
if ( (PIRP)I == CurrentIrp() )
{
//Уничтожить пакет.
CurrentIrp() = NULL;
//При вызове метода CancelQueuedIrp устанавливается глобальная системная
//защелка (SpinLock). Теперь следует ее сбросить.
CancelSpinLock::Release(I.CancelIrql());
t << "IRP canceled " << I << EOL;
I.Information() = 0;
I.Status() = STATUS_CANCELLED;
//Обработать следующий пакет.
PnpNextIrp(I);
}
//Удалить из очереди пакет. Если это удалось, то функция вернет true.
else if (dq.RemoveSpecificEntry(I))
{
// Это удалось. Теперь сбрасываем защелку.
CancelSpinLock::Release(I.CancelIrql());
t << "IRP canceled " << I << EOL;
I.Information() = 0;
I.PnpComplete(this, STATUS_CANCELLED);
}
else
{
//Неудача. Сбрасываем защелку.
CancelSpinLock::Release(I.CancelIrql());
}
}
Метод StartIo является виртуальной функцией. Она вызывается системой, когда драйвер готов обрабатывать следующий запрос в очереди. Это чрезвычайно важная функция: она является диспетчером всех запросов на ввод-вывод, поступаемых к нашему драйверу. В функции вызываются обработчики запросов на чтение, запись, а также обработчики вызовов IOCTL. К счастью, умный DriverWizard генерирует скелет функции автоматически, и вносить изменения в нее в нашем простом случае не требуется. В принципе, в эту функцию можно ввести какие-то дополнительные проверки IRP-пакетов.
VOID XDSPdrvDevice::StartIo(KIrp I)
{
t << "Entering StartIo, " << I << EOL;
//Здесь надо проверить, отменен этот запрос или нет. Это производится
// при помощи вызова метода TestAndSetCancelRoutine. Также этот метод
// устанавливает новую функцию отмены пакетов, если это необходимо.
// Адрес новой функции передается вторым параметром. Если он равен
// NULL,то вызывается старая функция. Если пакет должен быть отменен,
// функция вернет FALSE.
if ( !I.TestAndSetCancelRoutine(
LinkTo(CancelQueuedIrp),
NULL,
CurrentIrp()) )
{
//Пакет отменен.
return;
}
// Начать обработку запроса.
// Выбрать необходимую функцию
switch (I.MajorFunction())
{
case IRP_MJ_READ:
//Чтение
SerialRead(I);
break;
case IRP_MJ_WRITE:
//Запись
SerialWrite(I);
break;
case IRP_MJ_DEVICE_CONTROL:
//IOCTL
switch (I.IoctlCode())
{
default:
//Мы обрабатываем пакет, который не должен быть обработан.
//Поэтому просто выходим.
ASSERT(FALSE);
break;
}
break;
default:
// Драйвер занес в очередь какой-то непонятный пакет,
//он не должен быть обработан.
ASSERT(FALSE);
PnpNextIrp(I);
break;
}
}
Метод Create вызывается, когда пользовательское приложение пытается установить связь с драйвером при помощи вызова API CreateFile(). Обычно этот запрос обрабатывается в нашем объекте устройства, и нет смысла пересылать запрос устройству нижнего уровня.
NTSTATUS XDSPdrvDevice::Create(KIrp I)
{
NTSTATUS status;
t << "Entering XDSPdrvDevice::Create, " << I << EOL;
//Здесь можно вставить код пользователя, который должен быть вызван при установлении
//приложением связи с устройством.
status = I.PnpComplete(this, STATUS_SUCCESS, IO_NO_INCREMENT);
t << "XDSPdrvDevice::Create Status " << (ULONG)status << EOL;
return status;
}
Аналогично метод Close вызывается при разрыве связи приложения с драйвером.
NTSTATUS XDSPdrvDevice::Close(KIrp I)
{
NTSTATUS status;
t << "Entering XDSPdrvDevice::Close, " << I << EOL;
//Здесь можно вставить код пользователя, который должен быть вызван при разрыве
//приложением связи с устройством.
status = I.PnpComplete(this, STATUS_SUCCESS, IO_NO_INCREMENT);
t << "XDSPdrvDevice::Close Status " << (ULONG)status << EOL;
return status;
}
В этих методах можно ввести проверки каких-либо условий. Отвлечемся на секунду от нашей PCI-карточки и обратим внимание на другой хороший пример - тот же программатор микроконтроллеров. Предположим, пользователь подключил программатор к компьютеру и начинает записывать в память микроконтроллера, разработанную им программу. В принципе, ничто не помешает ему открыть еще одну копию программы и писать в ту же микросхему что-то совсем другое. В результате, в эту несчастную микросхему запишется невообразимая каша. Для того, чтобы избежать такой ситуации, в объекте драйвера надо установить флаг, который будет показывать, свободно ли это устройство, или оно уже кем-то используется. Это может выглядеть так:
NTSTATUS MyPrettyDevice::OnStartDevice(KIrp I)
{
NTSTATUS status = STATUS_SUCCESS;
I.Information() = 0;
. . . //Какая-то инициализация - может, PCI,
//может - какое-то другое оборудование...
//Устройство только что заработало - конечно, оно свободно...
m_AlreadyUsed = false;
return status;
}
NTSTATUS MyPrettyDevice::Create(KIrp I)
{
NTSTATUS status;
if (m_AlreadyUsed)
//Это устройство уже используется кем-то. Нельзя допустить его использование
//несколькими приложениями одновременно.
//Возвращаем ошибку.
status = I.PnpComplete(this, STATUS_INVALID_PARAMETER, IO_NO_INCREMENT);
else
{
//Это устройство свободно. Устанавливаем флаг и возвращаем успех.
m_AlreadyUsed = false;
status = I.PnpComplete(this, STATUS_SUCCESS, IO_NO_INCREMENT);
}
return status;
}
NTSTATUS MyPrettyDevice::Close(KIrp I)
{
NTSTATUS status;
//Пользователь закончил работу с устройством, теперь оно свободно.
//Сбрасываем флаг.
m_AlreadyUsed = false;
status = I.PnpComplete(this, STATUS_SUCCESS, IO_NO_INCREMENT);
return status;
}
Функция SerialRead вызывается, когда драйвер получает запрос на чтение. Это важная функция. Т.к. мы хотим, чтобы приложение пользователя могло читать и писать в память микросхемы, то именно сюда необходимо добавлять наш код. Все фрагменты кода, добавленные программистом, будут выделены жирным шрифтом:
//This code was added by the programmer
Фактически в данном методе мы должны прочитать содержимое памяти и передать его приложению пользователя. Но тут самое время вспомнить, что плата обменивается с памятью 4- байтными словами. Поэтому для операций с памятью следует применять метод ind/outd.
void XDSPdrvDevice::SerialRead(KIrp I)
{
t << "Entering XDSPdrvDevice::SerialRead, " << I << EOL;
NTSTATUS status = STATUS_SUCCESS;
//Здесь мы получаем буфер пользователя. Он передается через Irp.
KMemory Mem(I.Mdl());
PUCHAR pBuffer = (PUCHAR) Mem.MapToSystemSpace();
//Теперь pBuffer - указательна буфер пользователя.
//Здесь мы получаем число 4-байтных слов, которое должно быть прочитано. Оно также
//передается через Irp, как запрашиваемое количество байт для чтения.
ULONG dwTotalSize = I.ReadSize(CURRENT);
ULONG dwBytesRead = dwTotalSize;
//Здесь мы читаем заданное число байт из памяти устройства. Плата XDSP680 обменивается
//с памятью 4-байтными словами.Начальный адрес - 0, dwTotalSize 4-байтных слов будут
//прочитаны в буфер pBuffer.
m_MainMem.ind(0,(ULONG*)pBuffer,dwTotalSize);
//Возвращаем количество прочитанных слов
I.Information() = dwBytesRead;
I.Status() = status;
//Обработать следующий IRP-пакет.
PnpNextIrp(I);
}
Метод SerialWrite работает практически так же, только он записывает данные в память устройства, а не считывает их.
void XDSPdrvDevice::SerialWrite(KIrp I)
{
t << "Entering XDSPdrvDevice::SerialWrite, " << I << EOL;
NTSTATUS status = STATUS_SUCCESS;
KMemory Mem(I.Mdl());
PUCHAR pBuffer = (PUCHAR) Mem.MapToSystemSpace();
ULONG dwTotalSize = I.WriteSize(CURRENT);
ULONG dwBytesSent = dwTotalSize;
m_MainMem.outd(0,(ULONG*)pBuffer,dwTotalSize);
I.Information() = dwBytesSent;
I.Status() = status;
PnpNextIrp(I);
}
Как мы упоминали ранее, для большинства драйверов устройств недостаточно функций чтения и записи. Мало-мальски сложное устройство требует еще и множества других операций: получить состояние, получить информацию об устройстве, как-то отконфигурировать его. Для выполнения этих задач служат функции управления вводом-выводом, IO Control; сокращенно - IOCTL. IOCTL предоставляет программисту возможность разработать практически неограниченное количество различных функций управления устройством.
И драйвер, и приложение пользователя различают, какую функцию управления устройством вызвать, при помощи IOCTL-кодов. Такой код представляет собой обыкновенное 32-разрядное число. Для удобства ему директивой #define задают какое-то понятное имя. Например, в нашем случае зададим IOCTL-код, при получении которого драйвер будет возвращать количество памяти "на борту" PCI-устройства.
#define XDSPDRV_IOCTL_GETMEMSIZE 0x800
Если при чтении драйверу посылается IRP-пакет со старшим кодом функции IRP_MJ_READ, при записи - IRP_MJ_WRITE, то при вызове функции DeviceIOControl для нашего устройства драйвер получает пакет со старшим кодом IRP_MJ_IOCONTROL и младшим - код самой IOCTL-функции. Метод DeviceControl вызывается при получении драйвером IRP со старшим кодом IRP_MJ_DEVICE_CONTROL. Она действует подобно методу StartIo.В зависимости от кода IOCTL производится вызов соответствующей функции.
NTSTATUS XDSPdrvDevice::DeviceControl(KIrp I)
{
NTSTATUS status;
t << "Entering XDSPdrvDevice::Device Control, " << I << EOL;
switch (I.IoctlCode())
{
case XDSPDRV_IOCTL_GETMEMSIZE:
//Получен поределенный нами IOCTL-код XDSPDRV_IOCTL_GETMEMSIZE.
//Вызвать соответствующий обработчик.
status = XDSPDRV_IOCTL_GETMEMSIZE_Handler(I);
break;
default:
//Этот код не определен.
status = STATUS_INVALID_PARAMETER;
break;
}
if (status == STATUS_PENDING)
// Если драйвер по каким-то причинам отложил обработку запроса, переменной status
//присваивается значение STATUS_PENDING. Этот код будет возвращен методом
//DeviceControl.
{
return status;
}
else
//В противном случае завершаем обработку пакета.
{
return I.PnpComplete(this, status);
}
}
Метод XDSPDRV_IOCTL_GETMEMSIZE_Handler является обработчиком IOCTL - кода XDSPDRV_IOCTL_GETMEMSIZE. Получив этот код, драйвер возвращает общий объем памяти устройства. Шаблон метода сгенерирован DriverWizard, но программист должен написать практически весь его код.
NTSTATUS XDSPdrvDevice::XDSPDRV_IOCTL_GETMEMSIZE_Handler(KIrp I)
{
NTSTATUS status = STATUS_SUCCESS;
t << "Entering XDSPdrvDevice::XDSPDRV_IOCTL_GETMEMSIZE_Handler, " << I << EOL;
//Количество памяти будет возвращено как число unsigned long. Поэтому определяем
//указатель на unsigned long.
unsigned long *buf;
//Получаем указатель на буфер пользователя
buf=(unsigned long*) I.UserBuffer();
//Записываем туда количество памяти нашего устройства. Получаем его при помощи
//метода Count объекта m_MainMem.
*buf=m_MainMem.Count();
//Длина возвращаемых нами данных равна длине числа unsigned long.
I.Information() = sizeof(unsigned long);
//Возвращаем STATUS_SUCCESS
return status;
}
Написание драйвера завершено. Возможно, у Вас сложилось впечатление, что DriverWizard может практически все и написание драйвера - очень простая задача. Но не следует забывать, что наш драйвер - всего-то простейшая демонстрационная программа, которая практически не выполняет никаких полезных действий. Написание реальных драйверов является гораздо более сложной задачей.
Если бы драйвер был написан с использованием пакета DDK, то он бы имел практически ту же структуру и почти тот же код (правда, не объектно-ориентированный). Но в таком случае весь драйвер пришлось бы писать вручную, а DriverWizard генерирует скелет драйвера автоматически. Это сильно облегчает процесс разработки драйвера, позволяя программисту не заботиться о написании скелета драйвера и предохраняя его от возможных ошибок.
6. Разработка dll-библиотеки для взаимодействия с драйвером
dll-библиотека (Dynamic Link Library) - программный модуль, который может быть динамически подключен к выполняющемуся процессу. Dll - библиотека может содержать функции и данные. При подключении dll к процессу она отображается на адресное пространство этого процесса.
Если говорить по-русски, то это означает: в любой момент времени программа может загрузить dll-библиотеку, получить указатели на функции и данные этой библиотеки. Потом приложение как-то использует функции и данные библиотеки, и когда они больше не нужны - выгружает библиотеку.
Dll-библиотека содержит два вида функций: внешние (External) и внутренние (Internal). Внутренние функции могут вызываться только самой dll, а внешние может также вызывать приложение, подключившее библиотеку. В этом случае говорят, что dll-библиотека экспортирует функции и данные.
Как было упомянуть выше, в настоящее время для связи с драйвером используется схема Приложение -> Библиотека dll -> Драйвер. При использовании такой архитектуры запрос приложения на операцию ввода-вывода поступает в dll-библиотеку, проходит там предварительную обработку и передается драйверу. Результат, возвращенный драйвером библиотеке dll, также обрабатывается и передается приложению. Преимущества такого подхода очевидны:
Выпускается огромное количество различных периферийных устройств, и, соответственно, для каждого устройства разрабатывается свой драйвер. Программисту будет тяжело разбираться во всех тонкостях работы драйвера устройства: формат данных для чтения/записи, запоминать непонятные IOCTL-коды. Гораздо лучше - предоставить для него понятный интерфейс API-функций для работы с устройством. Еще лучше, если такой интерфейс будет унифицированным для всех устройств данного типа. Задача dll-библиотеки, поставляемой с драйвером - связать стандартные интерфейсы, предоставляемые прикладной программе, со специфическими алгоритмами работы драйвера.
Если в будущем измениться алгоритм взаимодействия приложения с драйвером, то пользователю для работы с новым драйвером будет необходимо обновить только библиотеку dll. Все ранее разработанные программы останутся прежними.
Естественно, такой подход имеет свои минусы. В данном случае за счет большего числа вызовов, через которые проходит запрос на ввод-вывод, снижается быстродействие работы системы.
В нашем случае нам необходимо разработать dll-библиотеку, которая будет предоставлять приложению три функции: чтение памяти, запись в память и получение общего количества памяти устройства. Естественно, dll - библиотеку мы также будем проектировать в среде Visual C++.
Запустите среду VC++ и создайте новый проект с названием XDSPInter. В качестве типа проекта выберите Win32 Dynamic-Link Library. Далее в качестве типа проекта выберите A Simple DLL (простая dll-библиотека). Среда VC++ создаст для Вас пустой проект с одной- единственной функцией DllMain().
Функция DllMain() вызывается при подключении и отключении dll процессом. DllMain() имеет возвращаемое значение BOOL APIENTRY (фактически, она возвращает значение типа BOOL) и три параметра - HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved.
Параметры:
HANDLE hModule - дескриптор (хэндл) нашей dll;
DWORD ul_reason_for_call - флаг, показывающий, почему была вызвана функция. Может принимать значения:
DLL_PROCESS_ATTACH или DLL_THREAD_ATTACH - библиотека подключается к процессу;
DLL_PROCESS_DETACH или DLL_THREAD_DETACH - библиотека отключается от процесса.
LPVOID lpReserved - зарезервировано.
Функция DllMain() - единственная функция, которая обязательно должна присутствовать в библиотеке. Остальные функции и переменные добавляет программист в соответствии с решаемой задачей.
В нашем случае dll - библиотека будет экспортировать следующие функции: bool IsDriverPresent(void). Функция будет определять, присутствует ли в системе необходимый драйвер и попытаться подключиться к нему. Если это удастся - функция вернет true, в противном случае - false.
int ReadMem(char data, int len) - чтение данных из памяти устройства. Char* data - буфер для данных, int len - число 32-битных слов для чтения. Функция вернет число прочитанных слов.
Подобные документы
Использование стандартных библиотек Windows. Установка и настройка дополнительных устройств ввода/вывода. Использование камеры, динамиков, сканера, дисков и портов ввода/вывода. Драйверы внешних устройств. Безопасность данных в операционных системах.
контрольная работа [1,8 M], добавлен 13.10.2022Разработка и практическая апробация действия драйвер-фильтра клавиатуры для операционной системы Windows, переопределяющего значение любых клавиш и отключающего клавиши клавиатуры. Составление программы для установки и управления данным драйвером.
курсовая работа [226,8 K], добавлен 18.06.2009Разработка драйверов ядра Windows. Драйвер виртуальных устройств Windows - компьютерная программа, с помощью которой другая программа получает доступ к аппаратному обеспечению стандартным образом. Доступ к драйверам из приложений пользовательского режима.
курсовая работа [436,1 K], добавлен 25.10.2012Архитектура ввода/вывода Windows NT. Внутренняя организация шины USB. Сущностная характеристика драйверной модели WDM. Точки входа разрабатываемого драйвера, размещение кода в памяти, установка драйвера в системе. Реализация кода драйвера на языке C.
курсовая работа [1,2 M], добавлен 27.09.2014Изучение подсистемы ввода-вывода и файловой системы ОС семейства Windows NT. Анализ особенностей работы приложения TotalCommander и его взаимодействия с файловой системой и подсистемой ввода-вывода. Взаимодействие TotalCommander с сетевыми адаптерами.
лабораторная работа [1,1 M], добавлен 12.06.2012Основные части персонального компьютера: системный блок, устройства ввода и вывода информации. Основные элементы системного блока: материнская плата, процессор, оперативная память, кэш-память, накопители. Операционная система, объекты Windows, окна.
реферат [135,0 K], добавлен 21.09.2009Архитектура Windows NT 5. Приоритеты выполнения программного кода. Описание формата MIDI-данных. Установка драйвера в системе. Выбор средств разработки программного обеспечения. Обработка запросов драйверной модели WDM. Использование библиотеки DirectKS.
курсовая работа [498,8 K], добавлен 24.06.2009Разработка и практическая апробация действия драйвер-фильтра USB-накопителя операционной системы Windows, предоставляющего возможности: установка на любой USB накопитель, перехват информации ввода/вывода, запись перехваченной информации в файл на диске.
курсовая работа [349,3 K], добавлен 18.06.2009Операционная система Windows - совокупность программных средств, обеспечивающих работу аппаратной части компьютера и прикладных программ, их взаимодействие между собой и пользователем. Основные объекты и приемы управления. Текстовый процессор Word.
методичка [1,4 M], добавлен 23.04.2012Операционная система как посредник в работе с устройствами компьютера: ДрайверЫ и 32-разрядность Windows 95 и программ. VFAT, DLL, DLE. Технические и программные средства для доступа и работы в Internet. TCP/IP, FTP, WWW. База данных в Excel.
реферат [55,0 K], добавлен 28.06.2008