Страница 4 из 5 Вернемся ненадолго к тексту приложения. Функция isr(), которую мы вызываем из драйвера, имеет следующий вид: void isr(int segment,int dt){ ... } Поскольку мы в драйвере протолкнули в стек сначала данные Data, а затем селектор DSseg, они расположились в стеке приложения в правильном с точки зрения этой функции порядке, поэтому она может обращаться к своим локальным переменным segment и dt, как если бы была вызвана обычным образом оператором: isr(DSseg,Data); После завершения функции isr() управление возвращается в VMM, а из него в драйвер на команду, следующую за вызовом Resume_Exec. Этот переход может потребовать пары сотен команд и нескольких десятков микросекунд. Отложенная процедура драйвера завершается очевидными вызовами End_Nest_Exec - окончания вложенного блока выполнения и Pop_Client_State - восстановления структуры клиента. Описанная методика организации взаимодействия обработчика аппаратных прерываний, включенного в состав драйвера, и самого приложения относительно сложна и тем не менее не обеспечивает необходимые функциональные возможности обработчику прерываний приложения, в котором запрещается вызов функций Windows. Для того чтобы по сигналу прерывания вывести на экран результаты измерений, нам пришлось создавать специальный цикл обработки сообщений с постоянным опросом флага request. Установка флага обработчиком прерываний приложения приводила к выполнению функции PostMessage() и посылке в приложение сообщения WM_USER, в ответ на которое мы уже могли выполнять любые программные действия безо всяких ограничений. Заметного упрощения программы можно добиться, организовав посылку в приложение сообщения WM_USER непосредственно из обработчика прерываний драйвера, точнее с уровня отложенных прерываний. В этом случае отпадает необходимость в передаче драйверу каких-либо данных, кроме дескриптора того окна приложения, в которое посылается сообщение WM_USER, в данном случае - дескриптора главного окна. Сокращается и текст процедуры отложенных прерываний Reflect_Int. Приложение Windows также упрощается: отпадает необходимость в разделении функций обработки прерываний между функцией Isr(), работающей, по существу, на уровне отложенных прерываний, и функцией OnUser(), выполняемой уже на обычном уровне задачи. Поскольку результат измерений легко передать из драйвера в приложение в качестве параметра сообщения WM_USER, исчезает необходимость в пункте <Чтение> в меню приложения. Рассмотрим изменения, вносимые при использовании такого метода. Приложение Windows, обрабатывающее аппаратные прерывания //Операторы препроцессора #define и #include ... #define HANDLE_WM_USER(hwnd, wParam, lParam, fn) \//Макрос для обработки ((fn)(hwnd,wParam), 0L) // сообщения WM_USER //Прототипы функций ... void OnUser(HWND,WPARAM); //Сигнатура функции изменилась //Глобальные переменные HWND hMainWnd; //Дескриптор главного окна ... //Главная функция WinMain int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int){ ... while(GetMessage(&msg,NULL,0,0))//Обычный цикл DispatchMessage(&msg);//обработки сообщений return 0; }//Конец WinMain //Функция Register регистрации класса окна ... //Функция Create создания и показа окна void Create(HINSTANCE hInst){ hMainWnd=CreateWindow(szClassName,szTitle,WS_OVERLAPPEDWINDOW, 10,10,200,100,HWND_DESKTOP,NULL,hInst,NULL); ShowWindow(hMainWnd,SW_SHOWNORMAL); } //Оконная функция WndProc главного окна LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam){ switch(msg){ HANDLE_MSG(hwnd,WM_COMMAND,OnCommand); HANDLE_MSG(hwnd,WM_DESTROY,OnDestroy); HANDLE_MSG(hwnd,WM_USER,OnUser); default: return(DefWindowProc(hwnd,msg,wParam,lParam)); } } //Функция OnCommand обработки сообщений WM_COMMAND от команд меню void OnCommand(HWND hwnd,int id,HWND,UINT){ switch(id){ case MI_START://Инициализация платы InitCard(); break; case MI_EXIT://Завершить приложение DestroyWindow(hwnd); } } //Функция OnUser обработки сообщения WM_USER void OnUser(HWND,WPARAM wParam){ char txt [80]; wsprintf(txt,"Измерения окончены\nНакоплено %d событий",(UINT)wParam); MessageBox(hMainWnd,txt,"Контроль",MB_ICONINFORMATION); } //Функция OnDestroy обработки сообщения WM_DESTROY ... //Функция InitCard инициализации (через драйвер) платы void InitCard (){ GetAPIEntry(); //Получение адреса точки входа в драйвер _BX=55; //Канал 0 _CX=20000; //Канал 1; BX*CX=Длительность интервала _DX=1000; //Канал 2, внутренний генератор _DI=(UINT)hMainWnd; //Дескриптор главного окна VxDEntry(); //Вызов драйвера } //Функция GetAPIEntry получения адреса точки входа в драйвер void GetAPIEntry() ... В приведенном выше тексте файла .CPP детально показаны только измененные участки программы. Изменилось определение макроса HANDLE_WM_USER для обработки сообщения WM_USER, которое мы пошлем в приложение из драйвера: функция обработки этого сообщения fn (в приложении она носит название OnUser()) принимает два параметра - hwnd и wParam. Через параметр сообщения wParam в приложение будет передан результат измерений. При необходимости передавать в приложение больший объем данных можно было расширить состав параметров функции третьим параметром lParam. Цикл обработки сообщений существенно упростился и принял форму, обычную для простых приложений Windows. В функции OnCommand() удален фрагмент, связанный с пунктом меню <Чтение> (идентификатор MI_READ), поскольку в этом пункте уже нет необходимости. В функции OnUser() параметр wParam приводится к типу целого без знака, преобразуется в символьную форму и выводится на экран в окно сообщения с соответствующим текстом. Как будет видно из программы драйвера (и как, впрочем, должно быть очевидно для читателя), при посылке из драйвера сообщения WM_USER необходимо указать системе, какому окну адресовано это сообщение. Однако драйверу, естественно, ничего не известно об окнах приложений; дескриптор нашего главного окна следует передать драйверу на этапе инициализации платы. Эта операция выполняется в функции InitCard(), где перед вызовом драйвера в регистры BX, CX и DX засылаются константы настройки таймера, а регистр DI является приведенным к целому типу дескриптором главного окна hMainWnd. |