2.4 Разработка dll-библиотеки для взаимодействия с драйвером
2.4 Разработка 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-битных слов для чтения. Функция вернет число прочитанных слов.
int WriteMem(char *data, int len) — аналогична предыдущей; запись данных в память.
int GetMemSize(void) — получить объем доступной памяти устройства. Для того, чтобы функция стала экспортируемой, она должна быть скомпилирована со специальным объявлением типа:
extern "C" __declspec (dllexport)
Для того, чтобы при каждом объявлении функции не писать эту длинную малопонятную строку, определим ее, как директиву препроцессора:
#define EXPORT extern "C" __declspec (dllexport)
Теперь перед каждым объявлением функции просто следует писать слово EXPORT. Создадим заголовочный файл нашей dll-библиотеки, в котором перечислим все экспортируемые функции и директивы препроцессора:
#define EXPORT extern "C" __declspec (dllexport)
EXPORT int ReadMem(char *data, int len);
EXPORT int WriteMem(char *data, int len);
EXPORT int GetMemSize(void);
EXPORT bool IsDriverPresent(void);
Теперь рассмотрим текст исходного срр–файла библиотеки.
//В начале идут включения заголовочных файлов:
#include "stdafx.h" // Основной заголовочный файл MFC
#include "XDSPInter.h" //Наш заголовочный файл
//Определим IOCTL-код для нашего драйвера:
#define XDSPDRV_IOCTL_GETMEMSIZE 0x800
//Введем переменную, которая будет содержать HANDLE драйвера, возвращаемый
//вызовом API CreateFile.
HANDLE hDevice = INVALID_HANDLE_VALUE;
//Также введем строку со значением символической ссылки на наше устройство:
char *sLinkName = \\.\XDSPdrvDevice0;
//И зарезервируем переменную для хранения объема памяти карточки
UINT dwSize;
//Вспомогательная внутренняя функция OpenByName будет пытаться связаться с
//драйвером.
HANDLE OpenByName(void) {
// вызов API.
return CreateFile(sLinkName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
//Функция возвращает NULL, если не удалось подключится к драйверу и хэндл
//на него в противном случае.
}
//Далее – функция DllMain:
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
//Определяем, почему была вызвана функция:
switch (ul_reason_for_call) {
//Приложение подключает библиотеку. Ничего не делаем.
case DLL_PROCESS_ATTACH: {
break;
}
case DLL_THREAD_ATTACH: {
break;
}
//Приложение отключает библиотеку.
case DLL_THREAD_DETACH: {
//Закрыть хэндл драйвера
if (hDevice != INVALID_HANDLE_VALUE) CloseHandle(hDevice);
hDevice = INVALID_HANDLE_VALUE;
break;
}
case DLL_PROCESS_DETACH: {
//Закрыть хэндл драйвера
if (hDevice != INVALID_HANDLE_VALUE) CloseHandle(hDevice);
hDevice = INVALID_HANDLE_VALUE;
break;
}
} //Все операции завершились успешно. Вернем true.
return TRUE;
}
//Эта внешняя функция будет вызываться приложением, которое захочет установить
//связь с драйвером. Функция вернет true в случае успеха и false при неудаче.
EXPORT bool IsDriverPresent(void) {
//Попытаемся открыть хэндл драйвера
hDevice=OpenByName();
if (hDevice == INVALID_HANDLE_VALUE)
//неудача
return(false);
else
//Успех.
return(true);
};
//Внешняя функция, производящая чтение памяти устройства. Char* data – буфер для
//данных, int len – число 32-битных слов для чтения. Функция вернет число
//прочитанных слов.
EXPORT int ReadMem(char *data, int len) {
unsigned long rd=0; //Количество прочитанных слов
ReadFile(hDevice, data, len, &rd, NULL); //Системный вызов чтения данных из
//файла. В данном случае – из нашего устройства
//len – количество запрашиваемых слов
//rd – количество прочитанных слов.
data[len*4+1]=0; //Установить последний байт в 0 – признак конца строки.
return(rd); //Вернуть количество прочитанных слов.
}
//Внешняя функция, производящая запись в память. Практически аналогична
//предыдущей.
EXPORT int WriteMem(char *data, int len) {
unsigned long nWritten=0;
WriteFile(hDevice, data, len, &nWritten, NULL);
//len – количество запрашиваемых слов
//nWritten – количество прочитанных слов.
return nWritten;
}
//Эта функция возвращает количество памяти устройства, байт.
EXPORT int GetMemSize(void) {
CHAR bufInput[4]; // Это пустой входной буфер, который будет
//передан устройсву
unsigned long bufOutput; //Буфер, куда драйвер запишет результат.
ULONG nOutput; //Длина возвращенных драйвером данных, байт
// Вызвать функцию device IO Control с кодом XDSPDRV_IOCTL_GETMEMSIZE
if (!DeviceIoControl(hDevice, XDSPDRV_IOCTL_GETMEMSIZE, bufInput, 4, &bufOutput, 4, &nOutput, NULL)) return(0); //Неудача
else return(bufOutput); //Кол-во памяти
}
Таким образом, наша библиотека экпортирует всего четыре функции для работы с устройством. Все они имеют простой синтаксис и просты в использовании. Использование dll в нашем случае позволяет программисту не думать о сложных системных вызовах, необходимых для общения с драйвером, о формате передаваемых ему данных, а сосредоточится на решении прикладных задач.