Архитектура "клиент-сервер"
Для тех из вас, кто никогда не имел дело с локальными сетями персональных компьютеров и распределенной обработкой данных, мы немного расскажем об архитектуре "клиент-_ -сервер", имеющей самое непосредственное отношение к DDE.
Прежде всего, определимся с терминологией.
Клиентом и сервером мы будем называть процессы (программы или приложения), работающие одновременно на одном или разных компьютерах (объединенных в сеть), и взаимодействующих между собой определенным образом.
Клиент посылает серверу запрос на получение данных или выполнение какой-либо работы (рис. 3.1). Это может быть запрос к серверу базы данных, задание серверу резервного копирования дисков или что-то аналогичное.
Рис. 3.1. Взаимодействие между клиентом и сервером
Сервер, получив от клиента запрос, выполняет соответствующие действия и посылает клиенту ответ. Например, сервер базы данных находит нужные записи и посылает их клиенту. Сервер резервного копирования вводит в действие механизм выгрузки данных на магнитную ленту или магнитооптическое дисковое устройство и сообщает клиенту о запуске процедуры.
Процесс передачи запроса серверу мы будем называть транзакцией.
Строго говоря, транзакция - это совокупность трех действий: посылка запроса, выполнение запроса, прием ответа.
Транзакция называется завершенной, если выполнены все три действия. Если же на одном из трех этапов произошел сбой, транзакция останется незавершенной. После восстановления незавершенная транзакция откатывается, что необходимо в ряде случаев для сохранения целостности данных (разумеется, за правильное выполнение отката транзакции отвечает сервер).
Например, если сервер базы данных получил от клиента запрос на обновление записей в нескольких файлах и перед аварийным отключением электропитания успел выполнить только часть работы, после восстановления ему необходимо восстановить состояние базы данных на момент начала незавершенной транзакции.
Топология системы, имеющей архитектуру "клиент-сервер", может быть различной.
На рис. 3. 1 показана система, состоящая из одного клиента и одного сервера. Однако в системе может быть несколько клиентов, работающих с одним сервером (рис. 3.2), или несколько клиентов, работающих одновременно с несколькими серверами (рис. 3.3).
Рис. 3.2. Система с одним сервером и несколькими клиентами
Обратите внимание на пунктирную линию, соединяющую серверы на рис.3.3. Между серверами тоже может идти обмен транзакциями. Таким образом, любой процесс может выступать одновременно и клиентом, и сервером.
Рис. 3.3. Система с двумя серверами и несколькими клиентами
Очень хорошо, скажете вы, но причем тут DDE?
А вот причем. Приложения, использующие технологию динамического обмена данных DDE, выступают как клиенты или серверы (или одновременно как клиенты и серверы). При этом взаимодействие между ними - это ни что иное, как транзакции.
Библиотека DDEML позволяет создавать системы, имеющие различные топологии. В среде Windows версии 3.1 в качестве клиентов и серверов могут выступать приложения, работающие на одном компьютере. В локальной сети, созданной на базе операционных систем Windows for Workgroups и Windows NT, клиентами и серверами могут быть приложения, работающие на разных компьютерах.
В нашей книге мы не будем рассматривать сетевую передачу данных через Network DDE, так как этот материал заслуживает отдельного и более глубокого изучения. Из-за ограниченного объема книги мы подробно рассмотрим только простейший, но часто встречающийся случай - когда в системе есть только один клиент и один сервер. Более того, мы ограничимся одним вариантом взаимодействия клиента и сервера, при котором инициатором передачи данных является клиент. Полное описание библиотеки DDEML вы сможете найти в документации, которая поставляется вместе с SDK for Windows 3.1.
Атрибуты разделов
Перечислим атрибуты, которые может иметь раздел справочной системы. Только один из них должен быть указан всегда (контекст), остальные являются необязательными.
Атрибут раздела | Описание |
Контекст | Текстовая строка, однозначно идентифицирующая раздел. Используется для ссылки на раздел. Это обязательный атрибут |
Заголовок | Заголовок, под которым раздел появляется при поиске с помощью кнопки "Search" приложения winhelp.exe. Этот атрибут необязательный |
Список ключевых слов | Пользователь может искать разделы по ключевым словам. Для каждого раздела можно задать несколько ключевых слов (но можно не задавать ни одного, в этом случае раздел не будет иметь доступ по ключевым словам). Справочная система может содержать несколько списков ключевых слов. Необязательный атрибут |
Номер в последовательности просмотра | Некоторые разделы могут иметь логическую последовательную связь. Данный атрибут позволяет задать положение раздела в последовательности просмотра. О последовательностях просмотра мы расскажем позже. Необязательный атрибут |
Макрокоманда | Для каждого раздела можно указать одну или несколько макрокоманд, запускаемых при отображении раздела. Макрокоманды мы рассмотрим позже |
Тег компиляции | Атрибут, который позволяет включать или не включать раздел в справочную систему в зависимости от параметров компиляции. Необязательный атрибут |
В справочной системе не может быть двух разделов с одинаковым контекстом. Если же по ошибке вы сделаете такие разделы, компилятор Help Compiler выведет сообщение об ошибке.
Для строки контекста (context string) вы можете использовать алфавитно-цифровые символы от A до Z, числа от 0 до 9, точку и знак подчеркивания (но не символы пробела). Максимальная длина строки - 255 символов, но не увлекайтесь длинными названиями, так как они создают дополнительные трудности при вводе текста.
Заголовок раздела (title of the topic) появляется в окне поиска и просмотра закладок, поэтому в хорошей справочной системе все разделы имеют заголовок.
Максимальная длина текстовой строки заголовка составляет 128 символов.
Что же касается ключевых слов (keyword), то здесь вы можете использовать для каждого ключевого слова максимально 255 символов. Если для раздела используется несколько ключевых слов, их нужно разделить символом точка с запятой ";".
Теперь займемся последовательностями просмотра (browse sequences).
Для того чтобы вам было понятнее, что такое последовательность просмотра, рассмотрим пример справочной системы, описывающей меню приложения. Главное меню приложения состоит из нескольких временных меню, в каждом из которых, в свою очередь, имеется несколько строк. Для главного меню, для каждого временного меню и для каждой строки нужно создать отдельный раздел в справочной системе. Организуем эти разделы следующим образом (рис. 4.13).
Рис. 4.13. Номера последовательностей просмотра
На рис. 4.13 определены четыре последовательности просмотра. Первая имеет отношение к главному меню. В ней находятся три раздела, которые описывают, соответственно, временные меню "File", "Edit" и "Help". Нажимая кнопки со значками и , пользователь сможет последовательно переходить к просмотру описаний этих меню.
Для каждого временного меню определена своя последовательность просмотра, так как, изучая меню, пользователь, скорее всего, захочет последовательно ознакомится со всеми строками этого меню.
Обозначение номера последовательности меню состоит из имени и числа, разделенного символом двоеточия ":". Каждому имени соответствует отдельная последовательность просмотра. Что же касается чисел, то можно использовать любые целые значения, причем они не обязательно должны идти подряд. Более того, для облегчения вставки в последовательность новых разделов рекомендуется нумеровать их с некоторым интервалом, как когда-то нумеровались строки в программе на языке Бейсик.
Вы можете не использовать последовательности просмотра вовсе или создать одну последовательность, разместив в нее все разделы.Здесь все зависит от информационного содержания справочной системы и вашего вкуса.
Тегом компиляции (build tag) отмечаются разделы, которые включаются или не включаются в справочную систему на этапе компиляции в зависимости от содержимого одного из разделов файла проекта справочной системы (файл проекта будет рассмотрен позже). Тег компиляции удобно использовать при отладке.
Back()
Отображение предыдущего раздела (в списке просмотренных разделов).
Безопасное уничтожение окон Document Window
Так как MDI-приложения могут работать одновременно с несколькими документами, следует соблюдать осторожность при уничтожении окон Document Window, которые используются для редактирования документов, а также при завершении работы приложения. Необходимо убедиться, что пользователь сохранил все измененные документы в соответствующих файлах.
Обычный способ заключается в использовании сообщения WM_QUERYENDSESSION, о котором мы уже рассказывали в предыдущих томах "Библиотеки системного программиста". Напомним, что это сообщение посылается всем приложениям при завершении работы операционной системы Windows. Если приложение может завершить свою работу, в ответ на это сообщение оно должно возвратить значение TRUE. Если же будет возвращено значение FALSE, завершение работы Windows будет отменено.
Большинство приложений, редактирующих документы, реагируют на сообщение WM_QUERYENDSESSION следующим образом. Если пользователь не загружал документы или не изменил ни один из загруженных документов, обработчик сообщения WM_QUERYENDSESSION (который находится в функции окна Frame Window) возвращает значение TRUE, разрешая завершение Windows. Если же один или несколько из загруженных документов был изменен, но не сохранен, обработчик предлагает пользователю сохранить документ, или отказаться от сохранения документа. Аналогичные действия выполняются и при завершении работы приложения в обработчике сообщения WM_CLOSE.
Для проверки возможности уничтожения окна Document Window лучше всего послать ему сообщение WM_QUERYENDSESSION, предусмотрев в функции окна Document Window соответствующий обработчик. Этот обработчик проверяет, нужно ли сохранять документ, связанный с данным окном Document Window, и при необходимости предлагает пользователю сохранить документ или отказаться от сохранения. Если функция SendMessage, с помощью которой было послано сообщение WM_QUERYENDSESSION, возвратила значение TRUE, данное окно можно уничтожать.
Аналогичные проверки требуется выполнять в обработчике сообщения для строки "Close All" меню "Window", так как в противном случае могут быть потеряны результаты работы по редактированию одного или нескольких документов.
Библиотека HELPMORE.DLL
В этом разделе мы приведем исходные тексты DLL-библиотеки, предназначенной для работы совместно со справочной системой helpmore.hlp, описанной нами ранее.
Основной файл исходного текста приведен в листинге 4.7.
BookmarkDefine()
Отображение диалоговой панели "Bookmark Define".
BookmarkMore()
Отображение диалоговой панели "More", отображаемой при выборе строки "More..." из меню "Bookmark" приложения winhelp.exe.
BrowseButtons()
Добавление в окно Toolbar приложения winhelp.exe кнопок последовательного просмотра разделов с обозначениями
и (по умолчанию эти кнопки не создаются).ChangeButtonBinding("buttonid", "buttonmacro")
С помощью этой макрокоманды можно закрепить за кнопкой "Help" указанную макрокоманду.
Алиас: CBB.
Параметры:
Параметр | Описание |
buttonid | Идентификатор кнопки, использованный при ее создании макрокомандой CreateButton, или один из встроенных идентификаторов стандартных кнопок: btn_contents, btn_search, btn_back, btn_history, btn_previous, btn_next |
buttonmacro | Имя макрокоманды, которая будет выполнена, когда пользователь нажмет на кнопку |
ChangeItemBinding("itemid", "itemmacro")
Эта команда предназначена для закрепления макрокоманды за заданной строкой меню.
Алиас: CIB.
Параметры:
Параметр | Описание |
itemid | Идентификатор строки меню, присвоенный макрокомандой AppendItem |
itemmacro | Имя макрокоманды, которая будет выполнена, когда пользователь выберет указанную строку |
Чтение данных из Clipboard
Рассмотрим теперь процедуру чтения данных из Clipboard. Приложение должно сделать следующее.
Открыть Clipboard функцией OpenClipboard
Вызвать функцию GetClipboardData, передав ей через единственный параметр требуемый формат данных. Если Clipboard содержит данные в указанном формате, функция GetClipboardData возвратит идентификатор незафиксированного блока памяти, содержащего нужные данные. Если в Clipboard нет данных в указанном формате, будет возвращено значение NULL
Зафиксировать блок памяти, идентификатор которого получен от функции GetClipboardData, функцией GlobalLock
Переписать данные из зафиксированного буфера данных Clipboard в буфер, заказанный специально для этого приложением
Расфиксировать блок памяти, идентификатор которого получен от функции GetClipboardData, функцией GlobalUnlock
Закрыть Clipboard функцией CloseClipboard
Чтение содержимого Clipboard, как и запись в Clipboard, нужно выполнять в обработчике одного сообщения, руководствуясь рекомендациями, приведенными в предыдущем разделе.
Отметим также, что приложение должно переписать данные из блока памяти Clipboard в свой, созданный специально, а не пользоваться блоком памяти, идентификатор которого был получен от функции GetClipboardData. Причина очевидна - в любой момент времени пользователь может уничтожить этот блок памяти, перезаписав содержимое Clipboard новыми данными.
Чтение DDB
Для чтения данных из Clipboard в формате CF_BITMAP (который соответствует битовому изображению DDB) ваше приложение должно выполнить следующие действия:
Открыть Clipboard функцией OpenClipboard
Вызвать функцию GetClipboardData, передав ей через единственный параметр константу CF_BITMAP, и проверить возвращенное значение. Если функция вернула NULL, Clipboard не содержит данных в формате CF_BITMAP. Ненулевое значение является идентификатором битового изображения DDB, который можно использовать для рисования
Закрыть Clipboard функцией CloseClipboard
Приведем фрагмент кода, читающий данные из Clipboard в формате CF_BITMAP:
OpenClipboard(hwnd); hBitmap = (HBITMAP)GetClipboardData(CF_BITMAP); if(hBitmap != NULL) DrawBitmap(hdc, 0, 0, hBitmap); CloseClipboard();
Для рисования DDB используется функция DrawBitmap, описанная нами ранее в 14 томе "Библиотеки системного программиста". Исходный текст этой функции вы сможете также найти в проекте приложения CLIPSHOW, которое будет описано ниже.
Чтение метафайла
Рассмотрим теперь процедуру чтения из Clipboard и отображения в окне метафайла. Эта процедура усложняется необходимостью анализа режима отображения, установленного для метафайла, и необходимостью учета трех способов задания размеров изображения при рисовании (напомним, что размеры могут быть совсем не заданы, заданы в виде ширины и высоты, заданы в виде отношения ширины и высоты).
Открыть Clipboard функцией OpenClipboard (если Clipboard не был открыт ранее при чтении данных в других форматах)
Вызвать функцию GetClipboardData, передав ей через единственный параметр константу CF_METAFILEPICT, и проверить возвращенное значение. Если функция вернула NULL, Clipboard не содержит данных в формате CF_METAFILEPICT. Ненулевое значение является идентификатором блока памяти заголовка метафайла. Этот метафайл можно проиграть в контексте отображения для рисования содержимого Clipboard
Зафиксировать блок памяти заголовка метафайла
Выполнить проигрывание метафайла в контексте отображения
Расфиксировать блок памяти заголовка метафайла
Закрыть Clipboard функцией CloseClipboard
В приложении CLIPSHOW указанная последовательность действий выполняется следующим образом:
hmf = (HMETAFILE)GetClipboardData(CF_METAFILEPICT); if(hmf != NULL) { lpmfp = (LPMETAFILEPICT)GlobalLock(hmf); if(lpmfp != NULL) { SaveDC(hdc); PrepareMetaFile(hdc, lpmfp, cxClient, cyClient); PlayMetaFile(hdc, lpmfp->hMF); RestoreDC(hdc, -1); GlobalUnlock(hmf); } }
После фиксирования заголовка метафайла приложение сохраняет текущий контекст отображения функцией SaveDC, так как перед проигрыванием метафайла этот контекст будет изменен в соответствии с содержимым полей заголовка метафайла. После проигрывания контекст отображения восстанавливается при помощи функции RestoreDC.
Проигрывание метафайла выполняется функцией PlayMetaFile из программного интерфейса Windows, причем идентификатор метафайла берется из заголовка. Однако перед проигрыванием необходимо выполнить некоторые подготовительные действия, для чего в нашем приложении вызывается функция PrepareMetaFile.
Прежде всего, эта функция устанавливает контекст отображения, указанный в заголовке метафайла, вызывая функцию SetMapMode:
void PrepareMetaFile(HDC hdc, LPMETAFILEPICT lpmfp, int cxClient, int cyClient) { int x, y;
SetMapMode(hdc, lpmfp->mm);
if(lpmfp->mm == MM_ISOTROPIC lpmfp->mm == MM_ANISOTROPIC) { if(lpmfp->xExt == 0) SetViewportExtEx(hdc, cxClient, cyClient, NULL);
else if(lpmfp->xExt > 0) { x = lpmfp->xExt; y = lpmfp->yExt; HiMetricToSize(&x, &y); SetViewportExtEx(hdc, x, y, NULL); }
else if(lpmfp->xExt < 0) { x = -lpmfp->xExt; y = -lpmfp->yExt; HiMetricToSizeScaled(&x, &y, cxClient, cyClient); SetViewportExtEx(hdc, x, y, NULL); } } else SetViewportExtEx(hdc, lpmfp->xExt, lpmfp->yExt, NULL); }
Далее для изотропного и анизотропного режимов отображения анализируется содержимое полей xExt и yExt заголовка метафайла с целью выбора размеров изображения в контексте отображения.
Если в заголовке метафайла размеры изображения не указаны (равны нулю), функция PrepareMetaFile устанавливает размер изображения, равным размеру внутренней области окна (размеры окна передаются функции через параметры).
Если размеры изображения указаны и являются положительными значениями, функция PrepareMetaFile пересчитывает эти размеры из сотых долей миллиметра в пикселы (вызывая функцию HiMetricToSize) и устанавливает размеры изображения, равными значениям, переданным через заголовок метафайла.
Если же размеры изображения, указанные в заголовке метафайла, меньше нуля, функция PrepareMetaFile изменяет их знак на положительный. Затем она выполняет преобразование с учетом размеров внутренней области окна, используемого для отображения. Функция HiMetricToSizeScaled масштабирует изображение таким образом, чтобы при любом размере окна изображение помещалось в него целиком и чтобы при этом сохранялось правильное отношение ширины и высоты изображения (указанное в заголовке метафайла).
Исходные тексты функций HiMetricToSize и HiMetricToSizeScaled приведены ниже в разделе "Приложение CLIPSHOW".
Если же в заголовке метафайла указан режим отображения, отличный от изотропного или анизотропного, размеры изображения устанавливаются равными значениям, указанным в заголовке.
Чтение палитры
Записывая изображение DDB в формате CF_BITMAP в Clipboard, приложения обычно сохраняют там же и цветовую палитру в формате CF_PALETTE. Если ваше приложение работает с многоцветными изображениями, оно должно извлекать из Clipboard не только битовое изображение, но и палитру.
Действия, необходимые для чтения палитры, аналогичны действиям, выполняемым при чтении DDB:
Открыть Clipboard функцией OpenClipboard
Вызвать функцию GetClipboardData, передав ей через единственный параметр константу CF_PALETTE, и проверить возвращенное значение. Если функция вернула NULL, Clipboard не содержит данных в формате CF_PALETTE. Ненулевое значение является идентификатором палитры, который должен использоваться для выбора и реализации палитры
Закрыть Clipboard функцией CloseClipboard
Следующий фрагмент кода демонстрирует извлечение палитры, ее выбор и реализацию после извлечения изображения DDB:
hBitmap = (HBITMAP)GetClipboardData(CF_BITMAP); if(hBitmap != NULL) { hPal = (HPALETTE)GetClipboardData(CF_PALETTE); if(hPal) { hOldPal = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc); } DrawBitmap(hdc, 0, 0, hBitmap); }
Clipboard и редактор текста EDIT
Орган управления, созданный на базе предопределенного класса окна "EDIT" (редактор текста), способен обмениваться данными с Clipboard. В 12 томе "Библиотеки системного программиста" мы перечисляли сообщения, которые можно посылать редактору текста с помощью функции SendMessage. Для обмена данных между редактором текста и Clipboard приложение может посылать редактору сообщения WM_COPY, WM_CUT и WM_PASTE:
SendMessage(hEdit, WM_PASTE, 0, 0L);
Перечисленные выше сообщения не имеют параметров и используются, соответственно, для копирования выделенного фрагмента текста в Clipboard, для переноса выделенного фрагмента текста в Clipboard с последующим удалением и для вставки текста из Clipboard в редактор текста.
Пример организации взаимодействия органа управления EDIT и буфера обмена Clipboard приведен в 13 томе "Библиотеки системного программиста" в разделе "Приложение SMARTPAD".
CloseWindow("windowname")
Макрокоманда закрывает окно с именем windowname, которое может быть как основным окном приложения winhelp.exe, так и вторичным.
CONFIG
Эта секция нужна в том случае, если разработчик справочной системы использовал дополнительные возможности приложения winhelp.exe, такие как дополнительные кнопки, макрокоманды, вызовы функций из внешних DLL-библиотек и т. п.
В частности, для того чтобы в окне Toolbar появились кнопки просмотра последовательностей разделов со значками
и , необходимо добавить в строку CONFIG имя макрокоманды BrowseButtons, создающей указанные кнопки:BrowseButtons()
Contents()
Переход к разделу, выполняющему функции оглавления справочной системы.
CopyDialog()
Отображение на экране диалоговой панели "Copy", предназначенной для копирования всего раздела или его фрагмента в Clipboard.
CopyTopic()
Копирование текущего (отображаемого в главном окне) раздела в Clipboard.
CreateButton("buttonid", "name", "macro")
Создание новой кнопки в окне Toolbar приложения winhelp.exe.
Алиас: CB.
Параметры:
Параметр | Описание |
buttonid | Идентификатор создаваемой кнопки |
name | Текстовая строка, которая появится на кнопке |
macro | Имя макрокоманды, которая будет выполнена, когда пользователь выберет созданную кнопку |
DeleteMark("marktext")
Удаление отметки в тексте, созданной макрокомандой SaveMark.
Динамическое изменение главного меню приложения
Окна Document Window MDI-приложения могут содержать документы различных типов либо документы одного и того же типа, но находящиеся в различном состоянии. Поэтому в зависимости от того, какое окно Document Window активно, внешний вид главного меню приложения (и органов управления окна Toolbar, если такое предусмотрено в приложении) должно изменяться. О том, как динамически изменять меню, мы рассказывали в первой главе 13 тома "Библиотеки системного программиста". Теперь нам надо научиться выполнять такие изменения при активизации окон Document Window.
Функция окна Document Window может обнаружить, когда окно становится активным (получает фокус ввода) или неактивным (теряет фокус ввода), отслеживая сообщение WM_MDIACTIVATE. Параметр wParam этого сообщения принимает значение TRUE, если окно Document Window становится активным, или FALSE, если оно становится неактивным.
Если окно становится активным, младшее слово параметра lParam сообщения WM_MDIACTIVATE содержит идентификатор активного окна Document Window. Если же окно теряет фокус ввода и становится неактивным, старшее слово параметра lParam сообщения WM_MDIACTIVATE содержит идентификатор окна Document Window, которое становится неактивным.
Предусмотрев в функции окна Document Window обработчик сообщения WM_MDIACTIVATE, приложение может изменять главное меню приложения и выполнять другие действия в зависимости от того, какое окно Document Window стало активным.
DisableButton("buttonid")
Блокирование кнопки с идентификатором buttonid.
Алиас: DB.
DisableItem("itemid")
Блокирование строки меню с идентификатором itemid.
Алиас: DI.
DLL-библиотека D2W.DLL
DLL-библиотека d2w.dll (листинг 5.9) взаимодействует, с одной стороны, с приложением dos2win.exe, с другой - с виртуальным драйвером vxdsrv.386.
Добавление окон Toolbar и Statusbar
Большинство профессиональных MDI-приложений создает в своем главном окне инструментальные средства, облегчающие работу с меню - окна Toolbar и Statusbar. Операционная система Windows версии 3.1 не содержит поддержки для этих окон, поэтому вы должны создавать их самостоятельно.
Окно Toolbar содержит кнопки с пиктограммами, дублирующие функции меню. Это окно может располагаться горизонтально (в верхней или нижней части внутренней области окна Frame Window) или вертикально (слева или справа).
Окно Statusbar обычно находится в нижней части окна Frame Window и используется для отображения справочной информации. Например, когда пользователь перемещает курсор мыши по кнопкам окна Toolbar, в окне Statusbar могут появляться текстовые строки, поясняющие назначение кнопок, на которые указывает курсор. Это окно также часто используется для отображения текущего состояния переключающих клавиш, таких как <Insert>, <Scroll Lock>, <Caps Lock> и <Num Lock>.
MDI-приложение создает окна Toolbar, Statusbar и аналогичные как дочерние для окна Frame Window (рис. 1.10).
Рис. 1.10. Иерархия окон MDI-приложения с окнами Toolbar и Statusbar
Основная проблема, возникающая при этом, заключается в необходимости уменьшения размера окна Client Window. В стандартном MDI-приложении это окно занимает всю внутреннюю область окна Frame Window, причем его размеры автоматически изменяются при изменении размеров окна Frame Window.
Как устанавливаются размеры окна Client Window?
Очень просто. При создании окна Frame Window или изменении его размеров функция окна Frame Window получает сообщение WM_SIZE. Если вы помните, параметр lParam содержит новые размеры внутренней области окна (LOWORD(lParam) - ширина внутренней области окна, HIWORD(lParam) - высота). Функция окна Frame Window обычно передает это сообщение функции DefFrameProc, которая и устанавливает размеры окна Client Window равными размерам внутренней области окна Frame Window.
Следовательно, для того чтобы изменить размеры окна Client Window, функция окна Frame Window должна обрабатывать сообщение WM_SIZE самостоятельно, не передавая его функции DefFrameProc. Обработка в этом случае заключается в установке новых размеров окна Client Window, а также окон Toolbar и Statusbar. Последнее необходимо для того, чтобы при горизонтальном расположении окон Toolbar и Statusbar их ширина была всегда равна ширине внутренней области окна Frame Window, а при вертикальном расположении - высоте внутренней области окна Frame Window.
Что же касается создания окон Toolbar и Statusbar, то приложение может создавать их одновременно с созданием окна Client Window. Так как все эти окна являются дочерними для окна Frame Window, их удобно создавать при обработке сообщения WM_CREATE функции окна Frame Window.
Описанный способ изменения размера окна Client Window для размещения окон Toolbar и Statusbar демонстрируется в приложении MDITB, к описанию которого мы и перейдем.
Драйвер клавиатуры
В стандартном режиме работы Windows используется драйвер клавиатуры в виде обычной DLL-библиотеки. Основное назначение драйвера клавиатуры, как нетрудно догадаться, заключается в определении скан-кода нажатой или отпущенной клавиши, преобразования его в код ANSI и передаче этих кодов Windows для создания соответствующего сообщения WM_KEYDOWN или WM_KEYUP. Клавиатурное сообщение будет затем записано в очередь приложения, имеющего фокус ввода.
Процедура передачи кодов заключается в вызове процедуры, адрес которой Windows передает драйверу при инициализации драйвера. Эта процедура называется процедурой события (Event Procedure).
Менее очевидно второе назначение драйвера клавиатуры, которое состоит в преобразовании кодировки символов из стандарта OEM в стандарт ANSI и обратно.
Почему задача преобразования кодов возложена именно на драйвер клавиатуры? Ответ заключается в том, что при разработке этого драйвера приходится решать проблемы использования символов национального языка: внешний вид или, как говорят, раскладка клавиатуры зависит от национальных особенностей и состава алфавита.
Поэтому в комплекте с драйвером клавиатуры поставляются библиотеки перекодировки и таблицы перекодировки, по одной для каждого национального языка. Таблица перекодировки предназначена для преобразования кодировки символов из стандарта OEM в стандарт ANSI и обратно. Библиотеки перекодировки - это обычные DLL-библиотеки, которые содержат табличную информацию о раскладке клавиатуры.
После всего сказанного выше для вас не будет удивительно, что драйвер клавиатуры экспортирует среди прочих такие функции, как VkKeyScan, AnsiToOem и OemToAnsi.
В расширенном режиме работы Windows в дело включается виртуальный драйвер клавиатуры, задачей которого является виртуализация клавиатуры для ее совместного использования одновременно работающими виртуальными машинами MS-DOS и системной виртуальной машиной Windows.
Дополнительно виртуальный драйвер клавиатуры обеспечивает симулирование клавиатурного ввода при выполнении операции вставки содержимого Clipboard в окно виртуальной машины MS-DOS (эта возможность есть только в расширенном режиме работы Windows).
При этом виртуальный драйвер клавиатуры определяет метод, используемый программой MS-DOS для ввода с клавиатуры - вызов прерывания INT 16h или непосредственное сканирование клавиатурного буфера в области данных BIOS.
Еще одно назначение виртуального драйвера клавиатуры - обработка клавиш активизации или ускоренного запуска (Hot Key). Если пользователь нажмет такую клавишу, будет запущено соответствующее приложение или выполнены другие действия.
Виртуальный драйвер клавиатуры может также обеспечить доступ к клавиатуре другим виртуальным драйверам, войдя в так называемый режим сообщений (Message Mode).
Драйвер клавиатуры подключается в секции [boot] файла system.ini:
keyboard.drv=keyboard.drv
Кроме этого, используется секция [keyboard]:
[keyboard] subtype= type=4 keyboard.dll= oemansi.bin=xlat866.bin
С помощью строки type задается тип клавиатуры. Значение 1 соответствует клавиатуре XT, значение 4 - клавиатуре AT.
Строка subtype нужна только для драйвера клавиатуры Olivetti и описывает разновидность клавиатуры внутри одного типа.
При помощи строки keyboard.dll задается библиотека перекодировки, определяющая раскладку клавиатуры. По умолчанию используется американский вариант расположения клавиш.
Строка oemansi.bin определяет таблицу перекодировки символов из стандарта OEM в стандарт ANSI и обратно. Для корректной перекодировки текстов, содержащих символы кириллицы, необходимо использовать специальную таблицу, которая поставляется в составе таких средств русификации Windows, как CyrWin или ParaWin.
Еще одно небольшое замечание относительно использования символов кириллицы.
Есть два подхода в решении этой проблемы. Первый заключается в использовании русифицированных версий MS-DOS, Windows и других продуктов. Русификация выполнена А.О. Microsoft, поэтому можно считать, что она сделана профессионально. При втором подходе вы отдельно приобретаете оригинальные версии MS-DOS, Windows и т. п., и отдельно средства русификации, такие, как CyrWin, ParaWin, WinOrfo, Hameleon и т.
п.
Первый подход обычно удобен для начинающих пользователей, не привыкших к англоязычным версиям программ. Вы просто устанавливаете им русские версии программных продуктов, и они сразу начинают работать.
Второй подход обеспечивает возможность работы с символами кириллицы без перевода на русский язык меню и сообщений. Он используется профессиональными программистами и теми, для кого языковый барьер не является проблемой. Такие пользователи тяжело воспринимают переход к русским версиям, так как все строки меню и термины изменились до неузнаваемости и им трудной найти то, что нужно. Кроме того, работая с русскими версиями, пользователи будут вынуждены ждать появления русифицированного варианта новой версии, в то время как оригинальный вариант уже доступен.
Поэтому в настоящий момент, несмотря на то что А.О. Microsoft своевременно выпускает русские версии программных продуктов, по-прежнему существует необходимость в средствах русификации оригинальных операционных систем и прикладных программ.
Для того чтобы средства русификации Windows работали правильно, желательна правильная русификация MS-DOS. В частности, необходимо обеспечить в MS-DOS кодовую страницу с номером 866.
Самый простой способ корректной русификации оригинальной версии MS-DOS заключается в замене файлов ega.cpi и country.sys на аналогичные файлы из русифицированной MS-DOS с внесением соответствующих изменений в файлы config.sys и autoexec.bat.
В файле config.sys следует указать код страны 7 и подключить драйвер дисплея display.sys, указав кодовую страницу 866:
COUNTRY=7,,C:\CYR\COUNTRY.SYS DEVICE=C:\DOS\DISPLAY.SYS CON=(EGA,866,1)
В файле autoexec.bat нужно подготовить и загрузить шрифт, соответствующий 866-й кодовой странице:
C:\DOS\MODE CON CP PREP=((866) C:\CYR\EGA.CPI) C:\DOS\MODE CON CP SEL=866
Кроме этого, можно подключить любую резидентную программу русификации клавиатуры, не загружающую экранные шрифты или допускающую отключение такой загрузки, например, keyrus:
C:\KEYR\KEYRUS /KEYS=C:\KEYR\KBDMAIN /ROM /GRAPH=16
Далее следует русифицировать Windows, запустив CyrWin или аналогичное изделие.
При выполнении русификации описанным выше способом обеспечивается согласованное использование кодовой страницы 866 как в MS-DOS, так и в Windows. Большинство обычных русификаторов MS-DOS ограничиваются лишь загрузкой экранных шрифтов и переключением раскладки клавиатуры, нисколько не беспокоясь об изменении номера кодовой страницы в MS-DOS.
До сих пор нам не встречалось ни одно комплексное средство русификации MS-DOS и Windows, которое бы с одной стороны, было бы удобно в работе, а с другой - выполняло бы корректную русификацию MS-DOS и Windows с заменой всех необходимых шрифтов (в частности, часто используемого шрифта MS Sans Serif). Возможно, что вы сможете решить эту проблему. Исходные тексты драйвера клавиатуры, библиотек перекодировки и таблиц перекодировки есть в DDK, так что можно попробовать!
Драйвер мыши
Едва ли вам потребуется создавать собственный драйвер мыши, разве что вам придет в голову идея подключить к Windows одновременно две мыши для работы вдвоем. Тем не менее, мы скажем пару слов о драйвере этого устройства.
Назначение драйвера мыши очевидно - подготовка данных для генерации сообщений мыши. Так же, как и драйвер клавиатуры, драйвер мыши вызывает для этого процедуру, адрес которой передается драйверу при инициализации. Процедура вызывается тогда, когда пользователь перемещает мышь, либо нажимает на одну из ее клавиш.
Самое интересное в драйвере мыши заключается в том, что он работает напрямую с аппаратурой, не пользуясь услугами драйвера последовательного адаптера.
Драйвер последовательного адаптера
Драйвер последовательного адаптера обслуживает не только последовательный, но и параллельный адаптер. Это странно и неудобно, так как если вы разрабатываете драйвер для нестандартного последовательного адаптера, то вам необходимо реализовать в нем все функции параллельного адаптера и наоборот, разработчик нестандартного параллельного адаптера вынужден иметь дело с последовательным адаптером. Частично такой подход оправдывается тем, что использованный протокол взаимодействия с этими типами устройства одинаков и ориентирован на посимвольную передачу данных.
В расширенном режиме работы Windows дополнительно используется виртуальный коммуникационный драйвер VCD и виртуальный коммуникационный буфер COMMBUFF (который есть ни что иное, как еще один виртуальный драйвер). Первый из них отвечает за виртуализацию аппаратуры, второй - за буферизацию данных.
Виртуальный коммуникационный драйвер может зарезервировать порт отдельной виртуальной машине. Если другая виртуальная машина попытается получить доступ (обратиться) к зарезервированному порту, пользователь получит предупреждающее сообщение о такой попытке.
Драйвер принтера
В 14 томе "Библиотеки системного программиста" мы рассказали вам о том, как приложения Windows работают с принтером. Мы сказали тогда, что для печати используются те же функции графического интерфейса GDI, что и для рисования на экране, плюс некоторые дополнительные. Можно было бы ожидать, что в процессе печати функции GDI выводят данные на принтер, обращаясь к аппаратуре соответствующего параллельного или последовательного порта, однако это не так.
Прежде всего, данные, которые выводятся на печать, попадают на вход драйвера принтера. После обработки драйвером данные направляются снова в интерфейс GDI, который далее может направить их либо в систему спулинга печати, либо сразу передать драйверу параллельного или последовательного порта, к которому подключен принтер. Система спулинга печати, которая управляется через приложение Print Manager, способна накапливать данные и передавать их драйверу параллельного или последовательного порта в фоновом режиме, почти не влияя на работу других приложений.
Драйвер принтера может не делать практически ничего, просто возвращая полученные от GDI данные в нужном формате без какой-либо сложной обработки. Однако в некоторых случаях драйвер принтера может ускорять процесс печати, например, выполняя самостоятельно операции по преобразованию битовых изображений.
Так же, как и драйвер видеоконтроллера, драйвер принтера экспортирует функции BitBlt, ExtTextOut, SetPalette, StretchDIBits и другие.
Для облегчения задачи создания собственного драйвера принтера DLL-библиотека gdi.exe экспортирует "грубые" функции (Brute Functions), которые выполняют основные операции с монохромными битовыми изображениями. Имена этих функций начинаются с префикса dm, например, dmBitBlt.
Другая возможность, которая есть в распоряжении разработчика драйвера принтера - функции для работы с приоритетными очередями, также экспортируемые GDI. С помощью этих функций можно создавать и уничтожать приоритетные очереди печати, добавлять в очередь или удалять из нее задания на печать, устанавливать размер очереди и определять параметры самого приоритетного задания.
Драйвер видеоконтроллера
Драйвер видеоконтроллера оказывает решающее влияние на производительность Windows как операционной системы с графическим интерфейсом пользователя. Поэтому он составляется, как правило, на языке ассемблера.
В состав дистрибутива Windows входят драйверы лишь для ограниченного набора видеоконтроллеров. В частности, имеется драйвер для видеоконтроллера VGA, с которым совместимы практически все современные видеоконтроллеры. Для реализации режимов SVGA производители видеоконтроллеров создают собственные драйверы и поставляют их в комплекте со своими изделиями.
Как мы уже говорили в 14 томе "Библиотеки системного программиста", приложения Windows работают с видеоконтроллером и принтером через интерфейс графических устройств GDI, реализованный как DLL-библиотека gdi.exe. Этот интерфейс обеспечивает независимость приложений от особенностей физической аппаратуры.
Модули интерфейса GDI не работают с регистрами видеоадаптера или принтера, а обращаются к соответствующим драйверам. Заметим, что драйвер видеоадаптера вызывается для выполнения операций достаточно высокого уровня, например, рисования линии или закрашивания области. Если драйвер не поддерживает нужную операцию, GDI выполняет ее самостоятельно. Однако только драйвер может выполнять операции наиболее быстрым образом, так как GDI "не знает" особенностей аппаратуры.
Если Windows работает в расширенном режиме, в дело включается виртуальный драйвер видеоконтроллера, который выполняет операции ввода/вывода над регистрами видеоконтроллера и осуществляет запись данных в видеопамять. В составе DDK есть исходные тексты виртуальных драйверов для некоторых типов видеоконтроллеров, таких как CGA, EGA и VGA. Основная задача виртуального драйвера видеоконтроллера заключается в координации коллективного доступа к аппаратуре видеоконтроллера со стороны виртуальных машин MS-DOS и системной виртуальной машины, в которой работают приложения Windows.
В последнее время в качестве видеоконтроллера обычно используется так называемый ускоритель Windows (Windows Accelerator), способный выполнять многие графические операции на аппаратном уровне без загрузки центрального процессора компьютера.
При этом значительно увеличивается скорость вывода изображения в среде Windows. Разумеется, для того чтобы задействовать новые возможности акселераторов Windows, необходимо подключить соответствующие драйверы, которые всегда поставляются вместе с видеоконтроллером.
Драйверы экспортируют многие функции, которые по выполняемым действиям можно отнести к интерфейсу GDI, например, BitBlt, ExtTextOut, SetPalette, StretchDIBits и многие другие. При этом достигается максимальная производительность, так как только разработчик видеоконтроллера знает все его аппаратные особенности.
Так как драйвер видеоконтроллера выполняет многие критические функции, возможные ошибки, допущенные при создании драйвера, могут сказаться на работе любых приложений Windows. Авторам известна ошибка в одной из версий драйвера Cirrus Logic, влияющая на режим предварительного просмотра документа текстового процессора Word for Windows. Поэтому если вы обнаружили, что ваше приложение работает неправильно (рисует не то, что нужно или даже завершается с сообщением о нарушении защиты памяти), проверьте его работу с другим драйвером или при другом разрешении.
Помимо экспортирования функций, связанных с рисованием на экране, драйвер "экспортирует" многие ресурсы, такие как шрифты, битовые изображения, пиктограммы и другие. При изображении таких стандартных органов управления, как системные меню, движки полос просмотра, заголовки окон и курсоры, Windows пользуется ресурсами, определенными в драйвере видеоконтроллера. При установке видеоконтроллера в системный каталог Windows копируются файлы шрифтов, соответствующие текущему разрешению.
Последнее обстоятельство приводит в замешательство некоторых пользователей, работающих с символами кириллицы. Установив Windows и драйвер видеоконтроллера VGA для разрешения 640х480 пикселов, такие пользователи выполняют русификацию Windows при помощи таких продуктов типа CyrWin, ParaWin ит. п. В ходе этой процедуры многие шрифты из системного каталога Windows заменяются на другие, сходные по начертанию, но имеющие в своем составе символы кириллицы.
Если теперь установить другой драйвер видеоконтроллера, например, для разрешения 800х600 пикселов, опять произойдет замена шрифтов. На этот раз в системный каталог Windows будут скопированы шрифты, которые поставляются вместе с драйвером видеоконтроллера. Как правило, эти шрифты не содержат символов кириллицы, поэтому русские буквы исчезнут с экрана.
Следовательно, всякий раз, когда вы меняете драйвер видеоконтроллера, необходимо заново устанавливать приложение, отвечающее за использование символов кириллицы.
Еще одна важная задача, выполняемая драйвером видеоконтроллера (вернее, отдельным модулем, который устанавливается вместе с драйвером) заключается в сохранении и восстановлении содержимого экрана при переключении от виртуальной машины MS-DOS к системной виртуальной машине, в рамках которой работают все приложения Windows.
Сохранение и восстановление содержимого экрана виртуальной машины MS-DOS выполняется модулем, который называется Display Grabber. Мы будем называть его модулем сохранения экрана.
В реальном и стандартном режиме работы Windows модуль сохранения экрана представляет собой односегментную программу реального режима, составленную на языке ассемблера. По своей структуре она больше всего напоминает com-программу для MS-DOS.
В расширенном режиме работы Windows виртуальные машины MS-DOS могут работать как в полноэкранном, так и в оконном режиме. При этом используется другой модуль сохранения экрана, выполненный в виде обычной DLL-библиотеки. Его основная задача заключается в преобразовании формата изображения к виду, пригодному для отображения в окне. Само же отображение выполняется драйвером видеоконтроллера.
Другая важная задача, выполняемая модулем сохранения экрана расширенного режима - копирование содержимого окна виртуальной машины MS-DOS в универсальный буфер обмена Clipboard. Можно также скопировать произвольный фрагмент окна, выделенный пользователем. Функции выделения фрагмента окна в расширенном режиме работы Windows также выполняются модулем сохранения экрана.
Драйверам видеоконтроллера можно было бы посвятить отдельную книгу, так как решаемые этим драйвером задачи очень сложны. Однако, как мы уже говорили, вы едва ли будете составлять такие драйверы самостоятельно. Всю необходимую информацию при необходимости вы сможете найти в документации, которая поставляется в составе DDK.
Драйвер VXDSRV
После такого краткого обзора сервиса, доступного виртуальным драйверам, перейдем к практике. Мы предлагаем вам познакомиться с разработанным нами виртуальным драйвером VXDSRV, предназначенным для запуска приложений Windows из командной строки виртуальной машины MS-DOS или из среды таких программ, как Norton Commander и Xtree, работающих в виртуальной машине MS-DOS.
Поясним подробнее, для чего же предназначен наш драйвер.
Запустите любое приложение Windows из командной строки виртуальной машины MS-DOS. Вы увидите беспомощное сообщение о том, что требуется наличие Microsoft Windows:
This program requires Microsoft Windows.
Но Microsoft Windows уже работает! Не лучше ли было бы, вместо того чтобы выдавать подобное сообщение, попытаться запустить приложение в системной виртуальной машине?
Именно это и делает наш драйвер. После его активизации вы никогда не увидите сообщения о невозможности запуска приложения из виртуальной машины MS-DOS. Драйвер, работающий вместе с DLL-библиотекой d2w.dll и обычным приложением Windows dos2win.exe (исходные тексты также будут описаны), передает приложению dos2win.exe параметры запускаемого приложения. Получив эти параметры, dos2win.exe обеспечивает запуск приложения с помощью функции LoadModule из программного интерфейса Windows.
Для того чтобы обнаружить попытку запуска программы MS-DOS или приложения Windows, мы перехватываем функцию 4B00h прерывания INT21h и анализируем передаваемый ей файл. Перехват выполняется при помощи сервиса VMM, поэтому свободное адресное пространство виртуальных машин MS-DOS не уменьшается (это было бы не так при использовании обычной резидентной программы MS-DOS).
После обнаружения попытки запуска, виртуальный драйвер анализирует заголовок соответствующего exe-файла, для того чтобы определить, что запускается - программа MS-DOS или приложение Windows.
Если запускается программа MS-DOS, виртуальный драйвер не вмешивается в процесс запуска, позволяя функции 4B00h прерывания INT 21h спокойно делать свое дело.
Если же пользователь сделал попытку запустить приложение Windows, виртуальный драйвер отменяет выполнение указанной выше функции прерывания INT 21h. Затем он сохраняет путь к запускаемому файлу, параметры и путь к текущему на момент запуска каталогу в буфере. Этот буфер расположен в фиксированном сегменте данных DLL-библиотеки d2w.dll.
Сохранив параметры в буфере, виртуальный драйвер вызывает функцию обратного вызова, которая находится в фиксированном сегменте кода той же DLL-библиотеки d2w.dll.
В свою очередь, функция обратного вызова записывает специальное сообщение в очередь сообщений главного окна приложения dos2win.exe, пользуясь для этого обычной функцией PostMessage.
Получив это сообщение, функция главного окна приложения dos2win.exe выполняет запуск приложения, параметры которого получены от виртуального драйвера.
Описанная схема не лишена недостатков. В частности, в составе системы разработки программного обеспечения Microsoft Visual C++ for Windows поставляются достаточно странные программы, для которых наш алгоритм не подходит. Эти программы являются, строго говоря, приложениями Windows, так как содержащие их exe-файлы имеют два заголовка - старый и новый. Однако их необходимо запускать из среды MS-DOS или из среды виртуальной машины MS-DOS - иначе они не работают.
Наш драйвер совершенно справедливо относит такие программы к приложениям Windows, так как он принимает решение на основе анализа заголовка exe-файла. Попытка запуска такого "приложения" функцией WinExec приводит к зацикливанию Windows. Другая функция, с помощью которой можно запустить приложение Windows, это LoadModule. Эта функция в данном случае просто ничего не делает.
В результате драйвер VXDSRV блокирует выполнение странных программ (что все же лучше, чем зацикливание Windows!).
Вам такое решение может не понравиться, что будет совершенно справедливо. Однако выход все-таки есть. Драйвер может считывать список имен нестандартных программ, и принимать решение о принадлежности программы, входящих в этот список, не на основе анализа заголовка exe-файла, а на основе данных, хранящихся в списке (вспомните драйвер setver.exe, который из благородных побуждений вводит программы MS-DOS в заблуждение относительно текущей версии MS-DOS).Реализацию этого способа мы оставляем вам в качестве упражнения.
Обратимся к исходному тексту виртуального драйвера (листинг 5.1).
Драйвер WASTDRV.DLL
Загружаемый драйвер часто используется в качестве альтернативы DLL-библиотеки. Мы подготовили исходный текст загружаемого драйвера wastdrv.dll (листинг 5.12), который предназначен для совместной работы с описанным ранее виртуальным драйвером vxdsrv.386 и приложением wast.exe, которое будет описано позже.
Драйвер wastdrv.dll решает те же задачи, что и DLL-библиотека d2w.dll, однако теперь появилась возможность удобной установки всего комплекса и его включения/отключения с помощью стандартных средств Control Panel.
Итак, обратимся к исходному тексту драйвера.
Драйверы для Windows
; Lasciate ogni speranza, voi ch' entrata! ; (Leave behind every hope, you who enter!) ; ; -- Dante Оставь надежду, всяк сюда входящий! Данте ddk\286\keyboard\xlat.asm, строка 53 |
5.1.
5.2.
5.3.
В этой главе мы затронем одну из сложнейших областей программирования для операционной системы Windows - создание драйверов.
Традиционно считается, что разработка драйвера для Windows доступна лишь немногим программистам, и отчасти это так и есть. Особенно если речь заходит о драйвере для видеоадаптера, принтера или другого стандартного оборудования компьютера. Если вы сомневаетесь - посмотрите исходные тексты драйверов, которые есть в составе DDK. Они содержат тысячи строк ассемблерного "бреда"! Посмотрев на все это, вы, возможно, с тоской вспомните старые времена, когда казалось, что драйверы для MS-DOS - это очень сложные программы, которые не так просто составить и отладить.
В чем же сложность? Только ли в объеме листингов? Увы, нет.
Для начала сообщим, что в операционной системе Windows существуют драйверы трех типов. Причем два их них используются как в стандартном, так и в расширенном режиме работы Windows, а один - только в расширенном (к счастью, реальный режим Windows ушел в прошлое, вслед за ним идет и стандартный: Windows for Workgroups версии 3.11 работает только в расширенном режиме).
Драйверы первого типа (мы будем называть их стандартными драйверами) представляют собой обычные DLL-библиотеки и обслуживают стандартные устройства компьютера "с первых дней жизни" Windows. Когда-то, когда Windows еще не работал в расширенном режиме, это был единственный тип драйверов.
Стандартные драйверы, как и полагается драйверам, в стандартном режиме Windows работают непосредственно с регистрами аппаратуры (кроме драйвера принтера). Соответствующие DLL-библиотеки экспортируют функции, доступные как ядру Windows, так и обычным приложениям.
Несмотря на то, что мы называем эти драйверы стандартными, не существует никакого стандартного набора экспортируемых такими драйверами функций.
Так как устройства компьютера достаточно сильно отличаются друг от друга, сложно придумать единый интерфейс, удовлетворяющий требованиям, например, таких разных устройств, как дисплейный адаптер и принтерный порт.
Поэтому можно считать, что стандартные драйверы для стандартных устройств компьютера - это программы с разнотипным интерфейсом, которые объединяет только то, что все они реализованы как DLL-библиотеки, что приложения вызывают их для доступа к аппаратуре, и что все они загружаются в память в процессе загрузки Windows.
С возникновением версии 3.0 операционной системы Windows на свет появился новый тип драйверов - виртуальные драйверы.
Виртуальные драйверы не похожи ни на что другое в Windows. Это 32-разрядные DLL-библиотеки, работающие в так называемой FLAT-модели памяти. Они - чужестранцы в 16-разрядном мире Windows версий 3.0 и 3.1.
FLAT-модель является сплошной (несегментированной) моделью памяти. Однако название модели памяти не должно вводить вас в заблуждение. На самом деле сегменты памяти существуют всегда (так как даже в процессоре Pentium есть сегментные регистры!), но размер этих сегментов для FLAT-модели составляет 4 Гбайт.
Базовый адрес гигантских сегментов, адресуемых виртуальными драйверами, равен нулю, т. е. соответствует началу памяти. В результате этого, а также из-за того, что виртуальные драйверы работают в нулевом кольце защиты, они могут адресовать любой участок физической или виртуальной памяти системы. В частности, они имеют доступ к памяти любой запущенной виртуальной машины.
Основное назначение виртуальных драйверов - обеспечение безконфликтного коллективного доступа к физической аппаратуре для всех одновременно работающих виртуальных машин. Устройства компьютера как бы виртуализируются для каждой виртуальной машины (отсюда и название "виртуальные драйверы"). Другая задача, решаемая виртуальными драйверами, заключается в организации взаимодействия между виртуальными машинами MS-DOS и системной виртуальной машиной, в которой работают все приложения Windows.
Когда операционная система Windows запущена в расширенном режиме, стандартные драйверы не обращаются напрямую к аппаратуре, как это происходит в стандартном режиме. Вместо этого они обмениваются данными с виртуальными устройствами, вызывая функции виртуальных драйверов.
Так как виртуальные драйверы работают в нулевом кольце защиты, они имеют доступ к любым участкам памяти, к командам ввода/вывода в любые порты и к привилегированным машинным командам процессора (а также арифметического сопроцессора). Поэтому виртуальный драйвер, в отличие от обычного приложения Windows и стандартного драйвера, может сделать в системе все, что ему вздумается.
К сожалению, для того чтобы создать виртуальный драйвер, придется вспомнить язык ассемблера. И это еще одна сложность, далеко не последняя.
Когда мы рассказывали вам в первом томе "Библиотеки системного программиста" о драйверах MS-DOS, мы упоминали, что драйверу недоступно большинство функций MS-DOS. Виртуальные драйверы находятся в намного лучшем положении. Они могут пользоваться обширным набором функций, предоставляемых, как правило, системными виртуальными драйверами (сервисом виртуальных драйверов). Это сотни функций! И вы должны овладеть этим сервисом, или хотя бы основной его частью.
Третий тип драйверов появился в Windows одновременно с системой мультимедиа. Это так называемые загружаемые драйверы.
Загружаемые драйверы не представляют из себя ничего особенного - это простые DLL-библиотеки, экспортирующие среди прочих функцию DriverProc. Функция DriverProc используется для организации стандартизованного интерфейса, основанного на передаче сообщений. Загружаемые драйверы имеют также стандартный интерфейс для установки и конфигурирования.
Как правило, в расширенном режиме работы Windows загружаемые драйверы не обращаются к аппаратуре непосредственно. Они вызывают для этого виртуальные драйверы. Например, вместе с широко известным звуковым адаптером Sound Blaster поставляется не только загружаемый, но и виртуальный драйвер.
Безусловно, в одной книге, и тем более, в одной главе, невозможно привести всю информацию, необходимую для разработки драйверов перечисленных выше трех типов. Однако в этом нет необходимости.
Что касается стандартных драйверов, то вы едва ли будете создавать их самостоятельно (если только вы не занимаетесь разработкой стандартной периферии компьютеров, такой как видеоконтроллеры или принтеры). В любом случае для разработки драйверов вам потребуется приобрести программный продукт Microsoft Driver Development Kit for Windows 3.1, содержащий полный комплект документации, примеры драйверов и все необходимые утилиты. Относительно подробное описание стандартных драйверов вы сможете также найти в книге "Writing Windows Device Drivers", написанной Д. Нортоном (имеется перевод этой книги на русский язык).
Лучший способ создания собственного стандартного драйвера заключается в модификации исходного текста наиболее подходящего драйвера из комплекта примеров DDK.
В нашей книге мы приведем только краткий обзор стандартных драйверов, уделив больше внимания виртуальным и загружаемым драйверам.
Когда у вас может возникнуть необходимость создания собственного виртуального драйвера?
Прежде всего, тогда, когда вам нужно использовать в расширенном режиме работы Windows нестандартное устройство ввода/вывода, работающее с прерываниями и каналами прямого доступа к памяти, особенно в реальном времени.
Вам не обойтись без виртуального драйвера и в том случае, если ваше приложение Windows должно взаимодействовать с виртуальными машинами MS-DOS, резидентными программами и драйверами MS-DOS, загруженными до или после запуска Windows.
Так как виртуальные драйверы имеют полный контроль над аппаратным обеспечением компьютера, с их помощью можно создавать эффективно работающие средства защиты от несанкционированного копирования программных продуктов.
Приложения Windows могут использовать тот факт, что виртуальные драйверы работают в нулевом кольце защиты, для выполнения задач, не решаемых в рамках стандартного программного интерфейса Windows.
Мы, разумеется, не призываем вас к созданию приложений, нарушающих защитный механизм Windows, однако иногда поставленную задачу невозможно решить иными способами.
Для чего вам может потребоваться загружаемый драйвер?
Загружаемый драйвер не имеет никаких привилегий перед обычными приложениями и DLL-библиотеками, однако для него предусмотрены стандартные методы инсталляции, конфигурирования и взаимодействия с приложениями. Это удобно для пользователя и программиста, поэтому в некоторых случаях, когда в паре с приложением необходимо использовать DLL-библиотеку, имеет смысл оформить последнюю в виде загружаемого драйвера.
Ваше приложение может использовать загружаемый драйвер как интерфейс с виртуальным драйвером. При этом приложение взаимодействует с загружаемым драйвером с помощью механизма передачи сообщений, а вся работа по вызову виртуального драйвера возлагается на загружаемый драйвер. Так достигается полная функциональная независимость между приложением и драйверами: вы можете заменять версии драйверов на новые, не изменяя самого приложения.
Другие функции для работы с Clipboard
В заключение этой главы кратко рассмотрим еще несколько функций, предназначенных для работы с Clipboard.
Функция IsClipboardFormatAvailable позволяет проверить доступность формата данных, указанного в ее единственном параметре:
BOOL WINAPI IsClipboardFormatAvailable(UINT uFormat);
Если указанный формат доступен, функция возвращает значение TRUE, в противном случае - FALSE.
Для того чтобы получить идентификаторы всех доступных форматов данных Clipboard, воспользуйтесь функцией EnumClipboardFormats:
UINT WINAPI EnumClipboardFormats(UINT uFormat);
Эту функцию следует вызывать в цикле, передав ей в качестве параметра в первый раз нулевое значение (в этом случае она вернет идентификатор первого доступного формата данных). Затем ей нужно каждый раз передавать значение, полученное от функции в результате предыдущего вызова и так до тех пор, пока функция не вернет нулевое значение. Заметим, что перед использованием функции EnumClipboardFormats ваше приложение должно открыть Clipboard функцией OpenClipboard.
С помощью функции CountClipboardFormats можно определить количество различных форматов данных Clipboard, доступных на момент вызова:
int WINAPI CountClipboardFormats(void);
Функция GetOpenClipboardWindow возвращает идентификатор окна, открывшего на момент вызова Clipboard, или NULL при ошибке:
HWND WINAPI GetOpenClipboardWindow(void);
Функция GetClipboardOwner возвращает идентификатор окна, которое на момент вызова владеет Clipboard:
HWND WINAPI GetClipboardOwner(void);
Если вы знаете идентификатор формата данных Clipboard, то с помощью функции GetClipboardFormatName сможете получить текстовую строку имени этого формата:
int WINAPI GetClipboardFormatName( UINT uFormat, LPSTR lpszFormatName, int cbMax);
При вызове функции вы должны передать код формата через первый параметр, а адрес буфера, в который будет записана строка - через второй. Через третий параметр необходимо передать функции размер буфера.
Другие возможности
Возможности приложения winhelp.exe слишком велики, чтобы их можно было описать полностью, к тому же они не все документированы. Авторы этой книги знают о существовании полного описания системы Windows Help, предназначенной только для внутреннего пользования в Microsoft, состоящего из сотен страниц. В этом разделе мы очень кратко расскажем еще о нескольких возможностях.
EnableButton("buttonid")
Разблокирование кнопки с идентификатором buttonid.
Алиас: EB.
EnableItem("itemid")
Разблокирование строки меню с идентификатором itemid.
Алиас: EI.
ExecProgram("cmd", "show")
Запуск приложения Windows.
Алиас: EP.
Параметры:
Параметр | Описание |
cmd | Командная строка для запуска (с параметрами запуска) |
show | Способ отображения окна запускаемого приложения:0 нормальное;1 минимизированное;2 максимизированное |
Файл определения модуля виртуального драйвера
Файл определения модуля виртуального драйвера напоминает аналогичный файл для DLL-библиотек (листинг 5.2). В этом нет ничего удивительного, так как виртуальный драйвер и есть DLL-библиотека, только 32-разрядная.
FileOpen()
Отображение диалоговой панели "Open", которая обычно появляется при выборе из меню "File" приложения winhelp.exe строки "Open...".
FILES
В этой секции перечисляются файлы разделов *.rtf, из которых создается справочная система. В простейшем случае все разделы могут находиться в одном rtf-файле, однако для сложных и разветвленных справочных систем такой подход затрудняет процесс отладки.
Фильтр прерывания INT21h
Следующая процедура - фильтр прерывания INT 21h. Имя этой процедуры - V86_Int21_Handler, она вставляется в цепочку фильтров на этапе критической системной инициализации.
Эта процедура, прежде всего, проверяет номер функции. Если он не равен 4B00h, процедура ничего не делает, передавая управление дальше по цепочке фильтров.
После проверки факта регистрации вызывается процедура IsWindowsApp, выполняющая проверку заголовка exe-файла. Если запускается программа MS-DOS, фильтр передает управление дальше по цепочке.
Если же запускается приложение Windows, процедура фильтра выполняет ожидание семафора, созданного на этапе регистрации.
Ожидание выполняется с помощью сервиса Wait_Semaphore. Перед вызовом в регистр EAX записывается идентификатор семафора, полученный при его создании, а в регистр ECX - флаги, влияющие на обработку прерываний во время ожидания. В нашем случае разрешается обработка прерываний в виртуальной машине, даже если они запрещены.
Когда фильтр вызывается в первый раз, начальное значение семафора равно 1. При этом никакого ожидания не будет. Если же до завершения обработки прерывания INT 21h произойдет новое прерывание, сервис Wait_Semaphore переведет драйвер в состояние ожидания до тех пор, пока не будет вызван сервис Signal_Semaphore, восстанавливающий состояние семафора. Этот сервис будет вызван после окончания обработки прерывания INT 21h.
Далее драйвер вызывает процедуру GetCurDir, определяющую текущий диск для виртуальной машины, выполняющий запуск приложения. Номер диска сохраняется в буфере CallbackBuf.
Исходя из содержимого регистров DS:DX виртуальной машины MS-DOS при вызове функции 4B00h прерывания INT 21h, драйвер определяет FLAT-адрес командной строки, сохраняя его в глобальной переменной flatpCmdLine. Аналогично определяется адрес блока параметров, в котором находится нужный нам адрес строки параметров, указанных пользователем при запуске приложения.
FocusWindow("windowname")
Назначение фокуса ввода окну, указанному параметром windowname.
Функции для работы с Clipboard
В программном интерфейсе Windows предусмотрено несколько функций для работы с Clipboard. В этом разделе мы рассмотрим только основные функции, с помощью которых вы сможете открывать и закрывать Clipboard, а также выполнять запись и чтение.
Перед тем как выполнить запись или чтение данных, приложение должно получить доступ к Clipboard, открыв ее при помощи функции OpenClipboard:
BOOL WINAPI OpenClipboard(HWND hwnd);
В качестве параметра этой функции передается идентификатор окна, которое будет "владеть" Clipboard. При необходимости функция этого окна будет получать сообщения, извещающие о необходимости выполнения определенных операций с Clipboard. Мы рассмотрим эти сообщения позже.
Если доступ к Clipboard получен, функция OpenClipboard вернет значение TRUE. Если же Clipboard уже открыт другим приложением, эта функция вернет значение FALSE.
После использования приложение должно закрыть Clipboard, вызвав функцию CloseClipboard:
BOOL WINAPI CloseClipboard(void);
Эта функция в случае успеха возвращает значение TRUE, а в случае ошибки - FALSE.
Содержимое открытого Clipboard может быть сброшено функцией EmptyClipboard:
BOOL WINAPI EmptyClipboard(void);
Функция EmptyClipboard возвращает значение TRUE при нормальном завершении или FALSE при ошибке.
Для выполнения записи данных в Clipboard приложение должно использовать функцию SetClipboardData:
HANDLE WINAPI SetClipboardData(UINT uFormat, HANDLE hData);
Функция SetClipboardData имеет два параметра. Первый параметр определяет формат запоминаемых в Clipboard данных, через второй параметр передается идентификатор незафиксированного глобального блока памяти, содержащего запоминаемые данные.
Для параметра uFormat вы можете использовать константу, соответствующую одному из предопределенных форматов, или полученную от функции RegisterClipboardFormat.
Функция RegisterClipboardFormat позволяет зарегистрировать собственные форматы данных для Clipboard, мы расскажем о ней позже.
Приведем список предопределенных форматов данных для Clipboard.
Соответствующие константы определены в файле windows.h.
Константа | Описание формата данных |
CF_TEXT | Текстовые данные в виде массив символов. Каждая строка завершается комбинацией символов "\r\n", весь массив закрыт двоичным нулем |
CF_OEMTEXT | Аналогично предыдущему, однако буфер содержит символы в кодировке OEM |
CF_BITMAP | Битовое изображение в формате, который зависит от устройства отображения (DDB) |
CF_METAFILEPICT | Метафайл |
CF_DIB | Битовое изображение в формате, который не зависит от устройства отображения (DIB) |
CF_PALETTE | Палитра цветов. Используется при записи в Clipboard битовых изображений DIB |
CF_SYLK | Формат Microsoft Symbolic Link. Этот формат предназначен для передачи текстовых данных, причем каждая строка заканчивается комбинацией символов "\r\n". Используется в некоторых программных продуктах Microsoft |
CF_DIF | Формат Data Interchange Format. Аналогично формату CF_SYLK, формат CF_DIF предназначен для передачи текстовых данных |
CF_TIFF | Графическое изображение в формате Tag Image File Format, который был разработан Microsoft, Aldus Corporation и Hewlett-Packard |
CF_PENDATA | Этот формат данных используется в расширении операционной системы Windows, педназначенном для работы с перьевым вводом |
CF_RIFF | Формат Resource Interchange File Format. Мы рассказывали о нем в томе "Библиотеки системного программиста", посвященном системам мультимедиа |
CF_WAVE | Звуковые данные. Подмножество формата Resource Interchange File Format |
CF_OWNERDISPLAY | Формат данных определяется приложением, записавшим такие данные в Clipboard. Это же приложение отвечает за отображение данных |
CF_DSPTEXT | Текстовое представление данных, формат которых определен приложением |
CF_DSPBITMAP | Представление данных, формат которых определен приложением, в виде битового изображения |
CF_DSPMETAFILEPICT | Представление данных, формат которых определен приложением, в виде метафайла |
HANDLE WINAPI GetClipboardData(UINT uFormat);
Единственный параметр функции задает требуемый формат данных. Если Clipboard содержит данные в указанном формате, функция GetClipboardData возвращает идентификатор незафиксированного глобального блока памяти, содержащего требуемые данные.
Итак, основных функций, предназначенных для работы с Clipboard, немного. После того как мы их перечислили, расскажем об особенностях использования этих функций для выполнения операций записи данных в Clipboard и чтения данных из Clipboard.
Функции для работы с загружаемыми драйверами
Перед обращением к загружаемому драйверу его необходимо открыть функцией OpenDriver:
HDRVR OpenDriver( LPCSTR lpDriverName, // имя драйвера LPCSTR lpSectionName, // имя секции ini-файла LPARAM lParam); // адрес дополнительных данных
Займемся параметрами этой функции.
В процессе инсталляции устанавливаемого драйвера в секции [drivers] файла system.ini для каждого драйвера создается отдельная строка:
[drivers] Wave=galaxy.drv MIDI=galaxy.drv AUX=sgaux.drv WAST=wastdrv.dll
Эти строки устанавливают соответствие между именем загружаемого драйвера и именем файла, содержащего загрузочный модуль драйвера. Дополнительно после имени файла могут указываться параметры, отделенные символом пробела. Драйвер записывается в системный каталог Windows.
В качестве параметра lpDriverName для функции OpenDriver следует указывать имя драйвера, как оно определено в секции [drivers], или имя файла, содержащего драйвер.
Параметр lpSectionName позволяет загрузить драйвер, описанный в произвольной секции файла system.ini. Если же используется стандартная секция [drivers], этот параметр нужно указать как NULL.
В процессе открытия драйвера функция DriverProc вызывается несколько раз для обработки инициализирующих сообщений. При этом она через один из своих параметров получает значение, указанное в параметре lParam функции OpenDriver.
При нормальном завершении функция OpenDriver возвращает идентификатор драйвера, который следует сохранить для последующих обращений к драйверу.
После завершения работы с драйвером его необходимо закрыть функцией CloseDriver:
LRESULT CloseDriver( HDRVR hdrvr, // идентификатор драйвера LPARAM lParam1, // дополнительные данные 1 LPARAM lParam2); // дополнительные данные 2
В процессе закрытия драйвера, как и при его открытии, функция DriverProc вызывается несколько раз для обработки завершающих сообщений. При этом она через свои параметры получает значения, указанные в параметрах lParam1 и lParam2 функции CloseDriver.
Для взаимодействия с загружаемым драйвером приложения Windows и DLL-библиотеки используют функцию SendDriverMessage, которая посылает сообщение функции DriverProc. Приведем прототип функции SendDriverMessage:
LRESULT SendDriverMessage( HDRVR hdrvr, // идентификатор драйвера UINT msg, // код сообщения LPARAM lParam1, // первый параметр LPARAM lParam2); // второй параметр
Функции окон MDI-приложения
Как мы уже говорили, MDI-приложение должно определить как минимум две функции окна. Одну функцию окна необходимо указать при регистрации класса окна Frame Window, другую - при регистрации класса окна Document Window. Если приложение создает окна Document Window для отображения разнотипной информации, может потребоваться определить несколько разных функций (по одной для окна Document Window каждого типа). Для окна Client Window, создаваемого на базе предопределенного класса "MDICLIENT" функция окна находится внутри Windows, поэтому вам не нужно о ней беспокоиться.
Функция DriverProc
Приведем прототип функции DriverProc, обрабатывающей сообщения, посылаемые загружаемому драйверу:
LRESULT CALLBACK _export DriverProc( DWORD dwDiverId, // идентификатор драйвера HDRVR hDriver, // идентификатор, который // используется для обращения к драйверу UINT msg, // код сообщения LPARAM lParam1, // первый параметр сообщения LPARAM lParam2) // второй параметр сообщения
Функция DriverProc напоминает другую хорошо известную вам функцию обратного вызова, а именно, функцию окна WndProc. Функция DriverProc обрабатывает ряд сообщений, которые мы опишем в следующем разделе.
Все необработанные сообщения должны посылаться функции DefDriverProc. Назначение параметров этой функции аналогично назначению параметров функции DriverProc.