Александр Тарво - Использование NuMega DriverStudio для написания WDM-драйверов
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 в нашем случае позволяет программисту не думать о сложных системных вызовах, необходимых для общения с драйвером, о формате передаваемых ему данных, а сосредоточится на решении прикладных задач.
2.5 Подключение dll-библиотеки к приложению.
После того, как написан драйвер и dll-библиотека для работы с ним, пришло время написать приложение пользоваеля, работающее с устройством. Оно будет взаимодействовать с драйвером через dll-библиотеку. Естественно, написано оно также будет в среде Visual C++. В принципе, его можно было бы реализовать в среде Visual Basic, Delphi или CВuilder, но это приведет к некоторым трудностям, прежде всего в использовании системных вызовов и структур данных. В данном разделе, в отличие от предыдущих, не рассматривается какое-либо конкретное приложение, а даются общие рекомендации по написанию такой программы.
Подключение библиотеки к приложению не требует особых усилий. Библиотека подключается при помощи системного вызова HMODULE LoadLibrary(char* LibraryName), где LibraryName — строка с именем файла dll-библиотеки. Возвращаемое значение — хендл (дескриптор) бибилиотеки. Если функция возвратила NULL, то произошла ошибка при подключении библиотеки.
После подключения библиотеки можно из нее импортировать функции. Импорт функции производится при помощи системного вызова
FARPROC GetProcAdress(HMODULE hModule, char * ProcName)
• hModule — хэндл библиотеки, возвращенный LoadLibrary;
• ProcName — строка с именем импортируемой функции. Вызов GetProcAdress возвращает адрес функции с заданным именем и NULL, если такой функции нет в библиотеке.
Так как из библиотеки импортируется не само тело функции, а ее адрес, то вызов такой функции превращается в непростое дело. Прежде всего, в программе объявляется не сама функция, а переменная, содержащая указатель на нее. Естественно, работа с таким указателем сильно отличается от работы с указателем на число или строку. Ведь функция в отличие от просто переменной возвращает значение и принимает некоторые параметры, поэтому указатель на нее должен быть объявлен специальным образом.
Указатель на функцию, ипортируемую из dll-библиотеки должен также быть скомпилирован со специальным объявлением типа — __declspec(dllimport). Эту строку также удобно представить в виде директивы #define.
#define XDSPINTER_API __declspec(dllimport).
Мы импортируем из библиотеки четыре функции, поэтому необходимо определить их типы: параметры, передаваемые в функцию, возвращаемое значение. Это можно сделать при помощи директивы typedef:
//Объявить тип - указатель на функцию, возвращающую значение типа int и принимающую два
//параметра – массив типа char и число int. В библиотеке ей будет соответствовать функция
// EXPORT int ReadMem(char *data, int len)
typedef XDSPINTER_API int (*MemReadFun)(char *data, int len);
// EXPORT int WriteMem(char *data, int len)
typedef XDSPINTER_API int (*MemWrtFun)(char *data, int len);
// EXPORT int GetMemSize(void)
typedef XDSPINTER_API int (*MemSizeFun)();
//EXPORT bool IsDriverPresent(void)
typedef XDSPINTER_API bool (*IsDrivFun)();
Теперь пришло время создать сами указатели на функции:
MemReadFun ReadMem;
MemWrtFun WriteMem;
MemSizeFun GetMemSize;
IsDrivFun IsDriverPresent;
Теперь рассмотрим функцию, подключающую dll-библиотеку к приложению. Она будет подключать dll-библиотеку к приложению и пытаться установить связь с драйвером. Функция вернет true в случае успеха и false при неудаче. Т.к. VC++ — объектно-ориентированная среда, то эта функция будет методом одного из классов приложения (в нашем случае — класса представления).
bool CXDSPView::ConnectToDriver() {
//Переменная, в которой будет храниться возвращаемое значение.
success=true;
//HMODULE InterDll – переменная экземпляра, где хранится хэндл библиотеки.
InterDll=::LoadLibrary("XDSPInter");
if (InterDll==NULL) {
//Не удалось подключиться к библиотеке
AfxMessageBox("Couldn't load a library XDSPInter.dll",MB_ICONERROR | MB_OK);
//Вернем неудачу.
success=false;
} else {
//Библиотека подключена успешно. Импортируем функции.
ReadMem=(MemReadFun)::GetProcAddress(InterDll,"ReadMem");
if (ReadMem==NULL) {
//Не удалось импортировать функцию
AfxMessageBox("Couldn't get adress for ReadMem function from library XDSPInter.dll", MB_ICONERROR | MB_OK);
success=false;
}
WriteMem=(MemReadFun)::GetProcAddress(InterDll,"WriteMem");
if (WriteMem==NULL) {
//Не удалось импортировать функцию
AfxMessageBox("Couldn't get an adress for WriteMem function from library XDSPInter.dll", MB_ICONERROR | MB_OK);
success=false;
}
GetMemSize=(MemSizeFun)::GetProcAddress(InterDll,"GetMemSize");
if (GetMemSize==NULL) {
//Не удалось импортировать функцию AfxMessageBox("Couldn't get an adress for GetMemSize function from library XDSPInter.dll", MB_ICONERROR | MB_OK);
success=false;
}
IsDriverPresent=(IsDrivFun)::GetProcAddress(InterDll,"IsDriverPresent");
if (IsDriverPresent==NULL) {
//Не удалось импортировать функцию
AfxMessageBox("Couldn't get an adress for IsDriverPresent function from library XDSPInter.dll", MB_ICONERROR | MB_OK);
success=false;
}
}
return(success);
}
Вызов метода ConnectToDriver() целесообразно сделать в конструкторе класса. Там же надо реализовать и проверку, присутствует ли в системе драйвер. Тогда вся необходимая инициализация будет проведена еще при запуске приложения.
CXDSPView::CXDSPView() : CFormView(CXDSPView::IDD) {
//{{AFX_DATA_INIT(CXDSPView)
//}}AFX_DATA_INIT
//Здесь мы добавляем свой код. Success – переменная экземпляра. Если она
//равна true – то ошибок нет, иначе произошла какая-то ошибка.
success=true;
//Пробуем подключить dll:
if (ConnectToDriver()) {
//Удалось подключить библиотеку. Теперь пытаемся установить связь с
//драйвером – вызываем функцию в dll:
if (!IsDrvPresent()) {
//Неудача
success=false;
AfxMessageBox("Necessary driver isn't present in the system",MB_ICONERROR | MB_OK);
}
} else
//Не удалось подключиться к dll.
success=false;
}
Метод, производящий чтение памяти устройства может выглядеть следующим образом: