Not(...)
Эта макрокоманда инвертирует результат проверки, выполняемой макрокомандой IsMark:
Not(IsMark("markertext"))
Новая версия приложения BMPINFO
В 14 томе "Библиотеки системного программиста" мы привели исходные тексты приложения BMPINFO, с помощью которого можно просматривать битовые изображения DIB. Новая версия этого приложения позволяет сохранять загруженное изображение в Clipboard, пользуясь форматами CF_BITMAP, CF_PALETTE и CF_METAFILE.
Так как полные листинги приложения BMPINFO занимают много места, мы решили опубликовать только те функции, которые были изменены или добавлены к старой версии приложения BMPINFO, описанной в 14 томе. Вы можете приобрести вместе с книгой дискету, на которой записаны исходные тексты всех приложений, описанных в этой книге, в том числе полные исходные тексты новой версии приложения BMPINFO.
Основной файл исходных текстов приложения (листинг 2.5) претерпел небольшие изменения, связанные с добавлением нового временного меню "Edit". Это меню содержит строку "Copy", предназначенную для копирования загруженного изображения в Clipboard.
Обмен данными через Clipboard
2.1.
2.2.
2.3.
2.4.
2.5.
2.6.
В этой главе мы расскажем вам о важной компоненте операционной системы Windows - об универсальном буфере обмена Clipboard. Как пользователь Windows вы, безусловно, знаете, что Clipboard предназначен для обмена информацией между различными приложениями Windows. Например, вы можете "вырезать" из графического изображения, редактируемого приложением Paintbrush, произвольный фрагмент и вставить его в документ, созданный в текстовом процессоре Microsoft Word for Windows или в ячейку электронной таблицы, созданной с помощью Microsoft Excel.
Мы называем Clipboard универсальным буфером обмена, так как пользователь может записать в него самую различную информацию.
Методика работы с Clipboard одинакова для всех приложений и обычно заключается в том, что пользователь выделяет нужную часть документа или изображения, а затем выбирает из меню "Edit" строки "Copy" или "Cut". В первом случае выделенный фрагмент копируется в Clipboard, во втором - также копируется, но после копирования фрагмент удаляется из документа.
Для вставки фрагмента из Clipboard в документ пользователь выбирает строки "Paste" или "Paste Special..." из меню "Edit". Назначение строки "Paste" вроде бы понятно - она используется для вставки фрагмента из Clipboard в документ. Чтобы понять для чего нужна загадочная строка "Paste Special...", а заодно и уточнить действие строки "Paste", проведем небольшой эксперимент.
Запустите приложение Paintbrush и загрузите в него любое изображение (либо нарисуйте что-нибудь сами). Выделите фрагмент изображения и скопируйте его в Clipboard, выбрав из меню "Edit" строку "Copy". Затем запустите приложение Clipboard или ClipBook Viewer (если вы работаете в Windows for Workgroups). Это приложение предназначено для просмотра содержимого универсального буфера обмена Clipboard.
В нижней части меню "View" вы увидите несколько строк, позволяющих выбрать формат отображения фрагмента, записанного в Clipboard (рис. 2.1).
Рис. 2.1. Выбор формата отображения
В нашем случае вы сможете выбрать либо формат по умолчанию "Default Format", формат "Bitmap" или "Picture".
Выполнив аналогичный эксперимент с другим приложением, вы можете убедиться, что набор доступных форматов просмотра меняется в зависимости от того, с помощью какого приложения вы записали фрагмент в Clipboard, а также от того, что этот фрагмент из себя представляет (графическое изображение, текст, звук, видео и т. д.).
Фактически приложения записывают данные в Clipboard одновременно в нескольких форматах. Например, Paintbrush записывает в Clipboard изображение в виде объекта, предназначенного для вставки в документ по технологии OLE, в виде битового изображения DDB, а также в виде метафайла.
Когда пользователь вставляет содержимое Clipboard в документ, приложение может выбирать наиболее подходящий формат. В некоторых случаях выбор оставляется за пользователем. Для того чтобы при вставке фрагмента указать формат данных явным образом, пользователь должен выбрать из меню "Edit" строку "Paste Special...". В результате на экране появляется диалоговая панель, с помощью которой можно сделать выбор (рис.2.2). Заметим, что не каждое приложение предоставляет пользователю возможность выбора формата данных при вставке из Clipboard, поэтому, работая с некоторыми приложениями, вы можете и не найти строку "Paste Special..." в меню "Edit".
Рис. 2.2. Диалоговая панель "Paste Special" в приложении Microsoft Word for Windows версии 2.2.
Таким образом, Clipboard может содержать данные одновременно в нескольких форматах. Что это за форматы?
Во-первых, приложение может записать в Clipboard данные в одном из форматов, предопределенных для Windows. Можно записать данные в текстовом формате, битовое изображение в формате, зависящем от устройства отображения (DDB), цветовую палитру, битовое изображение в формате, независящем от устройства отображения (DIB), в виде метафайла, а также в нескольких других форматах, созданных на базе текстового формата данных.
Во-вторых, приложение может использовать свой собственный, уникальный формат данных, зарегистрировав его в Windows при помощи специальной функции. Например, текстовый редактор Microsoft Write, который входит в дистрибутив Windows, хранит в Clipboard текст вместе с атрибутами форматирования, используя для этого свой собственный формат данных.
Сразу возникает вопрос: а сможет ли приложение Clipboard или ClipBook из Windows for Workgroups (либо другое приложение, предназначенное для просмотра содержимого Clipboard) отображать данные, которые находятся в нестандартном формате?
Самостоятельно - нет, так как откуда это приложение узнает способ отображения? Но приложение, зарегистрировавшее нестандартный формат данных для Clipboard может помочь в этом приложению, показывающему содержимое Clipboard. Когда приложение Clipboard или ClipBook приступит к рисованию нестандартных данных, оно пошлет сообщение тому приложению, которое зарегистрировало этот формат данных. После этого приложение, владеющее нестандартным форматом, должно само нарисовать данные в окне приложения Clipboard или ClipBook (рис. 2.3).
Рис. 2.3. Содержимое Clipboard нарисовано приложением Microsoft Write
Словом, если ваше приложение использует для Clipboard нестандартный формат данных, будьте готовы изобразить эти данные после прихода соответствующего сообщения от приложения, предназначенного для просмотра Clipboard.
Прежде чем приступить к подробному описанию методов работы с Clipboard, расскажем еще об одной интересной возможности - отложенной записи данных в Clipboard.
Чтобы понять, что такое отложенная запись, разберемся вначале с обычной записью данных.
Как вы думаете, где хранятся данные, записанные в Clipboard?
Очевидно, в памяти. Но в какой? Эта память должна быть доступна для всех приложений. Кроме того, содержимое памяти не должно исчезать при завершении работы отдельных приложений.
Например, вы можете запустить Paintbrush, скопировать из него фрагмент изображения в Clipboard, затем завершить работу приложения Paintbrush и запустить Word for Windows.
Несмотря на то, что приложение Paintbrush не запущено, Clipboard по-прежнему содержит фрагмент изображения, который можно вставить в документ, редактируемый с помощью Word for Windows.
Всем перечисленным выше требованиям удовлетворяет глобальная память, принадлежащая операционной системе Windows. Такая память должна иметь атрибут GMEM_DDESHARE.
Следовательно, когда приложение записывает данные в Clipboard, размер оперативной памяти, доступной другим приложениям, уменьшается. Причем уменьшается достаточно сильно, если выполняется запись графического изображения, так как данные сохраняются одновременно в нескольких форматах. Для каждого формата может потребоваться несколько сотен Кбайт памяти.
Когда какое-либо приложение вставляет данные из Clipboard, оно использует только один из имеющихся там форматов. Остальные форматы данных могут так и не потребоваться.
Механизм отложенной записи данных экономит оперативную память. В момент, когда пользователь копирует данные в Clipboard, при отложенной записи никакой передачи данных не происходит, хотя Clipboard отмечается как содержащий эти данные в одном или нескольких форматах. Когда какому-либо приложению потребуется тот или иной формат данных, приложение, записавшее в Clipboard данные, получает сообщение о необходимости выполнения фактической записи данных и предоставляет данные в нужном формате "потребителю".
Таким образом, данные сохраняются в глобальной памяти только тогда, когда в этом появилась необходимость, и только в том формате, который нужен приложению, читающему содержимое Clipboard.
Если же приложение, выполнившее отложенную запись, завершает свою работу, оно должно записать данные в Clipboard во всех возможных форматах. Иначе как их потом оттуда взять?
И еще одно замечание.
Содержимым Clipboard должен управлять пользователь и только пользователь.
Все действия с Clipboard должны выполняться только тогда, когда пользователь выбирает соответствующие строки из меню "Edit", нажимает на клавиши-акселераторы, кнопки органа управления Toolbar, дублирующие эти строки, или пользуется другими органами управления, специально предназначенными для работы с Clipboard.Ни в коем случае не следует очищать Clipboard при запуске вашего приложения или выполнять с Clipboard другие, нестандартные и неожиданные для пользователя действия.
Обмен данными через DDE
3.1.
3.2.
3.3.
3.4.
3.5.
3.6.
Операционная система Windows предоставляет пользователю возможность запустить одновременно несколько приложений. Как правило, пользователь именно так и поступает. Однако сама по себе эта достаточно важная особенность Windows не имела бы столь большого значения, если бы не существовали удобные механизмы обмена данными между приложениями.
В предыдущей главе мы рассмотрели возможность передачи данных из одного приложения в другое через универсальный буфер обмена Clipboard. При этом обмен данными носит эпизодический характер и находится под контролем пользователя. Пользователь выделяет фрагмент документа, копирует его в Clipboard и затем вставляет в другой или тот же самый документ.
Однако существуют и другие способы передачи данных между приложениями.
Предметом нашего изучения в этой главе будет механизм динамического обмена данными DDE (Dynamic Data Exchange), позволяющий создать постоянно действующие каналы между несколькими одновременно работающими приложениями Windows. Эти каналы могут создаваться автоматически при запуске приложения или при необходимости, а также по явному запросу пользователя. После того как каналы созданы, они будут работать без вмешательства пользователя.
В операционной системе Windows версии 3.0 механизм DDE был реализован через передачу сообщений с помощью хорошо известной вам функции SendMessage с использованием глобальных блоков памяти, доступных всем приложениям. Фактически при создании DDE-приложений программист был вынужден вникать во все детали процесса создания канала связи, придерживаясь определенного в SDK протокола. Поэтому использованию динамического обмена данных DDE сопутствовали многочисленные затруднения.
Начиная с версии 3.1 в составе Windows появилось расширение - управляющая библиотека динамического обмена данными DDEML, выполненная как обычная DLL-библиотека. При создании новых DDE-приложений в SDK рекомендуется пользоваться именно этой библиотекой, а не старым механизмом, основанном на передаче сообщений.
Библиотека DDEML хороша сама по себе, так как она упрощает DDE-приложения и избавляет программиста от учета утомительных деталей протокола обмена сообщениями. Однако есть еще две причины, по которой следует пользоваться этой библиотекой.
Во-первых, в новых версиях Windows, а также в операционной системе Windows NT динамический обмен данных организуется исключительно с помощью библиотеки DDEML. Поэтому если в будущем вы собираетесь переносить свое приложение в среду Windows NT или Windows-95, имеет смысл сразу ориентироваться на работу с DDEML.
Во-вторых, вспомним о существовании такой операционной системы, как Windows for Workgroups. Сетевые возможности Windows for Workgroups базируются на сетевом динамическом обмене данными Network DDE (операционная система Windows NT также поддерживает Network DDE).
Механизм Network DDE позволяет организовать каналы обмена данными между приложениями, которые запущены на разных рабочих станциях сети. Поэтому освоение "обычной" библиотеки DDEML можно считать первым шагом на пути к изучению сетевых возможностей различных версий Windows.
Исходя из сказанного выше, мы считаем нецелесообразным изучение устаревшего протокола обмена сообщениями DDE. Вместо этого мы сразу займемся библиотекой DDEML, ставшей стандартным средством организации динамического обмена данными в операционной системе Windows.
Оглавление
Каждая книга имеет оглавление, пользуясь которым читатель без труда находит нужный ему раздел. Справочная система также должна иметь оглавление.
Как сделать оглавление?
Оглавление создается как отдельный раздел, который ничем не отличается от других разделов, за исключением того, что он описывается специальным образом при создании справочной системы и содержит прямые или косвенные ссылки на все остальные разделы.
Оглавление книги может занимать несколько страниц. Вы, конечно можете сделать подробное оглавление, содержащее прямые ссылки на все существующие разделы справочной системы. Однако это не лучший способ - такое оглавление будет занимать несколько экранов и в нем будет трудно ориентироваться. Поэтому в большинстве справочных систем оглавление создается в виде дерева.
В древовидном оглавлении один раздел выполняет роль корневого раздела. Он содержит ссылки на несколько разделов, отвечающих за различные темы (рис. 4.4).
Рис. 4.4. Корневой раздел справочной системы приложения Microsoft Anti Virus
Если в корневом разделе справочной системы выбрать строку "Commands", на экране появится одноименный раздел, представляющий собой следующий иерархический уровень оглавления (рис. 4.5).
Рис. 4.5. Раздел "Commands"
Иногда в оглавлении используются пиктограммы (рис. 4.6).
Рис. 4.6. Пиктограммы в оглавлении
Пользователь может нажать на пиктограмму левой клавишей мыши, при этом на экране появится оглавление следующего уровня или раздел, на который сделана ссылка. Пиктограммы улучшают внешний вид и привлекательность справочной системы.
Окна MDI-приложения
На первый взгляд может показаться, что MDI-приложение состоит из окон двух типов: главного окна приложения (которое можно отнести к перекрывающимся окнам) и дочерних окон Document Window, в которых отображаются документы или другая информация. Это не совсем верно. На самом деле в MDI-приложении имеется больше окон, чем кажется. Кроме того, окна Document Window ведут себя немного не так, как обычные дочерние окна.
Запустите приложение WSTYLE, загрузочный модуль которого есть на дискете, прилагаемой к 11 тому "Библиотеки системного программиста" (приложение WSTYLE описано в 3 главе 11 тома, оно демонстрирует поведение окон, имеющих различные стили). Внешний вид окон, создаваемых этим приложением, показан на рис. 1.7.
Рис. 1.7. Окна, создаваемые приложением WSTYLE
Проведя эксперимент, вы сможете легко убедиться в том, что дочерние окна Document Window ведут себя не так, как временные (pop up), перекрывающиеся (overlapped) или "настоящие" дочерние (child) окна. В отличие от перекрывающихся и временных окон, окна Document Window не могут выходить за границы создавшего их окна. В этом окна Document Window похожи на дочерние окна. Однако дочернее окно не может стать активным, и его заголовок (если он есть) никогда не будет выделен цветом, сколько бы вы не щелкали по нему левой клавишей мыши. Если же выбрать окно Document Window, оно становится активным и цвет его заголовка изменяется, становясь таким же, как и у активного перекрывающегося или временного окна.
Секрет такого странного поведения окон Document Window заключается в том, что Windows создает заголовок окна Document Window в виде отдельного окна и для активного окна Document Window изменяет цвет фона окна заголовка. Таким образом, для Windows окно Document Window состоит как бы из двух отдельных дочерних окон - окна заголовка и окна, отображающего документ.
Но это еще не все. Оказывается, MDI-приложение создает еще одно, невидимое окно, которое является родительским для окон Document Window.
Это окно называется Client Window и оно является дочерним по отношению к главному окну приложения.
Как правило, окно Client Window занимает всю внутреннюю область главного окна приложения (client area). Поэтому окна Document Window располагаются внутри внутреннего пространства главного окна приложения. Если же в приложении используется орган управления Toolbar (набор кнопок с изображением пиктограмм, дублирующих функции меню) или Statusbar (строка состояния в нижней части главного окна приложения), необходимо искусственно уменьшить размер окна Client Window. Позже мы расскажем вам, как это сделать.
Иерархия окон, создаваемых MDI-приложением, показана на рис. 1.8.
Рис. 1.8. Иерархия окон MDI-приложения
Прежде всего, приложение создает главное окно Frame Window, вызывая функцию CreateWindow. Это окно создается аналогично обычному главному окну приложения и является перекрывающимся. Оно может иметь меню, системное меню, кнопки и рамку для изменения размера.
Перед созданием окна Frame Window приложение должно зарегистрировать класс окна обычным образом, назначив стиль окна, а также при необходимости меню и пиктограмму. Разумеется, в приложении должна быть определена соответствующая функция окна. Она похожа на обычную, но в ней вместо функции DefWindowProc вызывается функция DefFrameProc, выполняющая дополнительную обработку сообщений.
После создания окна Frame Window следует создать окно Client Window, во внутренней области которого будут располагаться окна Document Window. Окно Client Window создается на базе предопределенного класса окна "MDICLIENT" с помощью функции CreateWindow. Для окна Client Window не нужно определять функцию окна, так как она уже определена в Windows.
Внутренняя область окна Client Window называется рабочим пространством приложения (application workspace). Внутри этой области создаются дочерние окна Document Window.
И, наконец, по мере необходимости MDI-приложение создает окна Document Window, посылая окну Client Window при помощи функции SendMessage сообщение WM_MDICREATE.
В ответ на это сообщение функция окна Client Window, определенная в Windows, создает новое окно Document Window. Отметим, что для создания окон Document Window нельзя использовать функцию CreateWindow.
Перед тем как приступить к созданию окон Document Window, приложение должно зарегистрировать класс окна Document Window и определить соответствующую функцию окна. Функция окна Document Window определяет реакцию окна на сообщения, предназначенные окну Document Window, и выполняет рисование во внутренней области окна Document Window. В этой функции вместо функции DefWindowProc вызывается функция DefMDIChildProc.
Таким образом, MDI-приложение при инициализации создает окно Frame Window и Client Window, определяя для окна Frame Window класс окна и специальную функцию окна. В процессе работы приложение создает окна Document Window, посылая окну Client Window сообщение WM_MDICREATE.
При необходимости упорядочить расположение окон Document Window или представляющих их пиктограмм приложение посылает окну Client Window соответствующие сообщения. Например, для каскадного расположения окон Document Window нужно послать сообщение WM_MDICASCADE. Функция окна Client Window сама выполнит необходимое перемещение окон и изменит их размеры, избавляя программиста от рутинной работы.
Окно просмотра Clipboard
В программном интерфейсе Windows есть функция SetClipboardViewer, специально предназначенная для создания приложений, которые динамически отображают содержимое Clipboard в своем окне.
Одновременно можно создать несколько окон, предназначенных для просмотра Clipboard (окон просмотра Clipboard). Операционная система Windows поддерживает список окон просмотра Clipboard, посылая им специальные сообщения при изменении состояния Clipboard. Обрабатывая эти сообщения, функции окон просмотра Clipboard отображают содержимое Clipboard и выполняют другие необходимые действия.
Если вы создаете приложение, главное окно которого должно выполнять функции просмотра Clipboard, при создании этого окна (во время обработки сообщения WM_CREATE) следует вызывать функцию SetClipboardViewer, передав ей идентификатор окна:
hwndNextViewer = SetClipboardViewer(hwnd);
Функция SetClipboardViewer включит окно, идентификатор которого передается ей через единственный параметр, в список окон просмотра Clipboard, вернув идентификатор предыдущего окна просмотра Clipboard hwndNextViewer. Вы должны сохранить возвращенное значение, так как функция вашего окна просмотра Clipboard будет передавать по цепочке сообщения для других окон просмотра Clipboard, пользуясь значением hwndNextViewer.
Если окно вашего приложения является единственным окном просмотра Clipboard, функция SetClipboardViewer вернет значение NULL.
Перед завершением работы приложение должно восстановить список окон просмотра Clipboard, удалив из него свое окно. Эту процедуру следует выполнить при обработке сообщения WM_DESTROY при помощи функции ChangeClipboardChain:
ChangeClipboardChain(hwnd, hwndNextViewer);
В качестве первого параметра функции передается идентификатор окна, удаляемого из списка окон просмотра Clipboard, в качестве второго - идентификатор окна, полученный ранее от функции SetClipboardViewer.
Самое последнее окно, для которого была вызвана функция SetClipboardViewer, становится текущим окном просмотра Clipboard.
Текущее окно просмотра получает сообщения, связанные с изменением состояния Clipboard, и передает их по цепочке.
Когда содержимое Clipboard изменяется, текущее окно просмотра получает сообщение WM_DRAWCLIPBOARD. Функция окна просмотра предает это сообщение по цепочке с помощью функции SendMessage и перерисовывает внутреннюю область окна, вызывая функцию InvalidateRect:
case WM_DRAWCLIPBOARD: { if(hwndNextViewer) SendMessage(hwndNextViewer, msg, wParam, lParam); InvalidateRect(hwnd, NULL, TRUE); return 0; }
Чтение и рисование нового содержимого Clipboard выполняется при обработке сообщения WM_PAINT, которое записывается в очередь приложения в результате вызова функции InvalidateRect.
В любой момент времени любое из существующих окон просмотра Clipboard может быть уничтожено пользователем или другим приложением. При этом если будет уничтожено следующее в цепочке окно просмотра, идентификатор которого вы сохранили в переменной hwndNextViewer, следует обновить содержимое переменной hwndNextViewer.
При любых изменениях в списке окон просмотра функция окна просмотра получит от Windows сообщение WM_CHANGECBCHAIN. Параметр wParam этого сообщения будет содержать идентификатор окна, удаляемого из списка окон просмотра. Младшее слово параметра lParam будет содержать идентификатор следующего окна просмотра в цепочке.
Обработка сообщения WM_CHANGECBCHAIN может быть выполнена следующим образом:
case WM_CHANGECBCHAIN: { if(wParam == (WPARAM)hwndNextViewer) hwndNextViewer = (HWND)LOWORD(lParam); else if(hwndNextViewer) SendMessage(hwndNextViewer, msg, wParam, lParam); return 0; }
Если удаляется следующее (по отношению к вашему) окно просмотра, значение параметра wParam равно значению, которое хранится в переменной hwndNextViewer. В этом случае мы записываем в переменную hwndNextViewer новое значение для идентификатора следующего в цепочке окна просмотра.
Если же удаляется другое окно, сообщение WM_CHANGECBCHAIN передается без изменений следующему окну. В том случае, когда следующего окна нет (наше окно является последним в цепочке окон просмотра Clipboard), содержимое переменной hwndNextViewer равно NULL, и нам не надо передавать это сообщение дальше.
Определение текущего диска и каталога
Функция GetCurDir определяет текущий диск и каталог в момент вызова фильтра прерывания INT21h.
Для выполнения этой задачи требуется вызвать функцию 1900h прерывания INT 21h (определение текущего диска) и функцию 4700h того же прерывания (определение текущего каталога).
В данном случае текущей является виртуальная машина MS-DOS, выполняющая запуск приложения Windows. Для вызова прерываний из этой виртуальной машины мы используем только что описанную методику, но вместо сервиса Simulate_Far_Call вызываем сервис Exec_Int, предназначенный для вызова программного прерывания. Перед вызовом Exec_Int драйвер должен записать в регистр EAX номер вектора прерывания.
Если номер текущего диска определить относительно просто, так как он возвращается соответствующей функцией прерывания INT 21h в регистре AL, то для получения текущего пути придется заняться эквилибристикой.
Перед вызовом функции 4700h прерывания INT 21h, определяющей текущий каталог, в регистры DS:SI необходимо записать адрес блока памяти, размером 64 байта, в который и будет записан этот путь. Сложность заключается в том, что мы не можем зарезервировать этот блок памяти в сегменте данных виртуального драйвера, а вынуждены заказывать его в адресном пространстве виртуальной машины MS-DOS.
Наш драйвер заказывает этот блок памяти при помощи функции 4800h прерывания INT 21h, преобразует адрес блока во FLAT-адрес, вызывает функцию 4700h, заполняя полученный буфер строкой пути к текущему каталогу. Затем, пользуясь FLAT-адресом буфера, драйвер копирует эту строку в буфер, расположенный в DLL-библиотеке d2w.dll. После этого буфер, заказанный в адресном пространстве виртуальной машины MS-DOS, освобождается функцией 4900h прерывания INT 21h.
Аналогичная техника используется и в процедуре IsWindowsApp, анализирующей заголовок exe-файла.
Эта процедура заставляет виртуальную машину MS-DOS, осмелившуюся запустить приложение Windows, открыть файл, считать его заголовок в память для анализа. Разумеется, что для чтения используется буфер, заказанный динамически в адресном пространстве виртуальной машины MS-DOS.
Определение виртуального драйвера
Исходный текст каждого виртуального драйвера должен содержать так называемое определение драйвера. Для того чтобы определить виртуальный драйвер, в начале исходного текста следует разместить вызов макрокоманды Declare_Virtual_Device, имеющей несколько параметров (до девяти).
Параметры разделяются запятыми, причем можно указывать не все параметры:
Declare_Virtual_Device VXDSRV, HiVers, LoVers, \ VXDSRV_Control, VXDSRV_Id, Undefined_Init_Order, \ VXDSRV_V86API_Handler, VXDSRV_PMAPI_Handler,
Макрокоманда Declare_Virtual_Device создает блок описания устройства DDB.
Первый параметр макрокоманды определяет имя виртуального устройства (т. е. имя виртуального драйвера). В приведенном выше примере виртуальный драйвер имеет имя VXDSRV.
Второй и третий параметры указывают старший и младший номер версии драйвера, соответственно. Здесь вы можете использовать целые числа или символические константы, определенные оператором equ. В нашем случае мы использовали символические константы HiVers и LoVers, которые соответствуют версии 1.1:
HiVers equ 1 ; верхний номер версии драйвера LoVers equ 1 ; нижний номер версии драйвера
Четвертый параметр представляет собой метку управляющей точки входа виртуального драйвера, т. е. FLAT-адрес управляющей процедуры. Управляющая процедура вызывается при возникновении ряда событий в системе. В частности, эта точка получает управление несколько раз на различных стадиях инициализации драйвера. В нашем примере указана процедура VXDSRV_Control, которая будет описана позже:
BeginProc VXDSRV_Control Control_Dispatch Sys_Critical_Init, VXDSRV_Sys_Crit_Init clc ret EndProc VXDSRV_Control
Пятый параметр - идентификатор виртуального драйвера. Это число вы должны получить от фирмы Microsoft, послав запрос в произвольной форме через электронную почту по адресу vxdid@microsoft.com. В ответ на этот запрос вам придет бланк, который нужно заполнить и отправить обратно по тому же адресу. После этого через пару недель вы получите идентификатор.
Каждый виртуальный драйвер, предоставляющий сервис приложениям Windows и программам, работающим в среде виртуальных машин MS-DOS, должен иметь свой собственный идентификатор, отличный от идентификаторов других виртуальных драйверов.
В противном случае возможен конфликт идентификаторов, который приведет к ошибке при загрузке виртуального драйвера. Использование идентификатора, полученного у Microsoft, гарантирует отсутствие конфликта с другими зарегистрированными виртуальными драйверами.
Если ваш драйвер не предоставляет никакого сервиса, ему не нужен идентификатор. В этом случае в качестве пятого параметра можно указать значение Undefined_Device_ID, определенное в файле vmm.inc.
С помощью шестого параметра можно указать порядок выполнения инициализации данного драйвера по отношению к другим виртуальным драйверам. Дело в том, что на этапе инициализации ваш виртуальный драйвер может пользоваться сервисом, предоставляемым другими виртуальными драйверами. Последние на момент инициализации вашего драйвера должны быть уже проинициализированы.
В нашем примере порядок инициализации не имеет значения, поэтому мы указали константу Undefined_Init_Order, определенную в файле vmm.inc.
В том случае, когда ваш драйвер предоставляет сервис приложениям Windows или программам, запущенным в среде виртуальной машины MS-DOS, необходимо указывать седьмой и восьмой параметры. Это точки входа процедур, получающих управление при вызове драйвера для получения сервиса.
Седьмой параметр определяет адрес процедуры, которая вызывается при обращении к драйверу из виртуальной машины MS-DOS, восьмой - из приложения Windows. В нашем примере указаны адреса процедур VXDSRV_V86API_Handler и VXDSRV_PMAPI_Handler (седьмой и восьмой параметры, соответственно).
И, наконец, девятый параметр предназначен для создания структуры, обеспечивающей предоставление вашим драйвером сервиса для других виртуальных драйверов. В нашей книге из-за недостатка места мы не будем рассматривать такую возможность, оставив ее вам на самостоятельное изучение.
После макрокоманды определения виртуального драйвера в исходном тексте следует расположить все необходимые сегменты кода и данных, к описанию которых мы и переходим.
OPTIONS
Параметры, определенные в этой секции, управляют процессом создания справочной системы на этапе компиляции. Секция OPTIONS должна быть определена в файле проекта первой.
Перечислим некоторые параметры, значения которых задаются в секции OPTIONS:
Параметр | Описание |
CONTENTS | Строка контекста, соответствующая разделу справочной системы, выполняющей функции оглавления |
TITLE | Текст заголовка окна приложения winhelp.exe при работе с данной справочной системой. Можно использовать только латинские буквы |
COPYRIGHT | Строка, содержащая сведения о разработчиках данной справочной системы. Будет добавлена в диалоговую панель приложения winhelp.exe, которая выводится на экран при выборе строки "About..." меню "Help" |
BMROOT | Путь к каталогу, содержащему графические bmp-файлы, включаемые в проект справочной системы |
BUILD | Параметр позволяет исключать из справочной системы разделы, имеющие заданные атрибуты тега компиляции (build tag) |
COMPRESS | Определяет степень компрессии данных при создании hlp-файла. Возможны следующие значения:NO, FALSE или 0 -компрессия не используется;MEDIUM - средняя степень компрессии;YES, TRUE, HIGH или 1 - максимальная компрессия |
ERRORLOG | Путь к файлу, в который будут записаны сообщения о найденных в исходном тексте ошибках |
FORCEFONT | При использовании этого параметра для отображения hlp-файла будет использован только один шрифт, имя которого указано в параметре FORCEFONT |
ICON | Этот параметр позволяет указать пиктограмму, которая будет отображаться при сворачивании главного окна приложения winhelp.exe, если отображается данный hlp-файл |
OPTCDROM | Оптимизация hlp-файла, предназначенного для записи на компакт-диск, заключается в выравнивании границ разделов на границу блока данных.Указывается следующим образом:OPTCDROM=YESВместо "YES" можно указать также TRUE, ON и 1 |
REPORT | Включение режима отображения сообщений во время компиляции. Указывается следующим образом:REPORT=ON |
ROOT | Путь к каталогу, содержащему файлы, входящие в проект. Эта секция может отсутствовать |
WARNING | Уровень сообщений об ошибках:1 - выводятся сообщения только о самых серьезных ошибках;2 - выводится среднее количество сообщений об ошибках;3 - выводятся все сообщения и предупреждения |
Органы управления
Приложение winhelp.exe имеет в своем главном окне меню и окно Toolbar, содержащее кнопки с различными надписями и обозначениями (рис. 4.7).
Рис. 4.7. Органы управления приложения winhelp.exe
С помощью меню "File" пользователь может открыть новый hlp-файл (т.е. приступить к работе с новой справочной системой), выбрать принтер и распечатать содержимое текущего раздела, отображаемого в главном окне приложения winhelp.exe. К сожалению, возможность полной распечатки содержимого всей справочной системы отсутствует, что является серьезным недостатком приложения winhelp.exe.
Меню "Edit" предназначено для копирования всего текущего раздела или любого его фрагмента в Clipboard (рис. 4.8). При этом копируется только неформатированный текст без графических изображений (хотя можно было бы предоставить пользователю возможность скопировать все как есть в формате, например, текстового редактора Write, входящего в поставку Windows).
Рис. 4.8. Копирование содержимого раздела в Clipboard
Тем не менее, возможность копирования информации из справочной системы в Clipboard даже в неформатированном виде является весьма ценной, так как позволяет, например, вставлять в исходный текст программы готовые фрагменты, взятые из примеров применения функций.
Строка "Annotate..." из меню "Edit" предназначена для добавления комментария к разделу (рис. 4.9).
Рис. 4.9. Добавление комментария к разделу справочной системы
Меню "Bookmark" позволяет "вставить" в справочную систему закладку, как в обычную книгу (рис. 4.10). Такая закладка иногда называется маркером.
Рис. 4.10. Создание закладок
По умолчанию для имени закладки выбирается заголовок раздела, однако пользователь может указать любое другое имя.
И, наконец, с помощью меню "Help" пользователь может узнать о том, как работать с приложением winhelp.exe, а также сделать главное окно этого приложения "непотопляемым", т. е. расположенным всегда над другими окнами (выбрав строку "Always on Top").
Окно Toolbar содержит несколько кнопок, предназначенных для выполнения самых нужных функций.
С помощью кнопки "Contents" можно отобразить раздел, содержащий оглавление загруженного hlp-файла.
Нажав на кнопку "Search", пользователь получит возможность выполнить поиск информации по ключевому слову (рис. 4.11).
Рис. 4.11. Поиск информации по ключевому слову
Напомним, что список ключевых слов определяется для тех разделов, к которым необходимо предоставить доступ по ключевым словам. Например, справочная система приложения Borland C++ for Windows позволяет найти описание функции по ключевому слову - имени функции.
Кнопка "Back" позволяет вернуться к просмотру раздела, который отображался в окне приложения winhelp.exe в прошлый раз.
При помощи кнопки "History" пользователь может получить доступ к списку названий просмотренных ранее разделов (рис. 4.12). Он может выбрать из этого списка любой раздел и вызвать его на экран двойным щелчком левой клавиши мыши.
Рис. 4.12. Список названий просмотренных ранее разделов
Как правило, окно Toolbar содержит кнопки со значками и , причем одна или обе такие кнопки могут быть заблокированы. Эти кнопки предназначены для последовательного просмотра логически следующих друг за другом разделов, соответственно, в прямом и обратном направлении.
Разработчик справочной системы (то есть исходного файла справочной системы, попадающего на вход компилятора Microsoft Help Compiler) с помощью специальных макрокоманд может изменить состав органов управления приложения winhelp.exe при отображении данного файла. Например, он может добавить меню или новую кнопку.
Однако при выборе состава органов управления для вашей справочной системы следует учитывать, что непривычные названия и органы управления иногда затрудняют работу пользователя и, как следствие, ухудшают общее впечатление от приложения.
Отладка DDEML-приложений
Библиотека DDEML позволяет создавать приложения, предназначенные для отладки DDEML-приложений. В частности, такие приложения могут перехватывать вызовы функций обратного вызова DDEML (как для сервера, так и для клиента), следить за использованием идентификаторов строк и данных, за регистрацией сервиса и так далее. Из-за ограниченного объема книги мы не сможем рассказать вам о том, как создавать такие приложения, однако вся необходимая информация есть в документации, которая входит в состав Microsoft SDK for Windows 3.1.
Тем не менее, мы научим вас пользоваться готовым приложением DDESpy, которое поставляется в составе SDK и специально предназначено для отладки DDEML-приложений.
Запустите приложение DDESpy и раскройте меню "Output" (рис. 3.10).
Рис. 3.10. Меню "Output" приложения DDESpy
С помощью этого меню вы можете направить поток отладочной информации в файл (строка "File..."), на отладочный терминал (строка "Debug Terminal" или в окно приложения DDESpy (строка "Screen"). Кроме того, с помощью строки "Clear Screen" вы можете очистить содержимое окна приложения DDESpy от отладочной информации (если она там есть).
В меню "Output" вам надо выбрать строку "File..." и с помощью появившейся на экране диалоговой панели задать путь к файлу, в который будет записана отладочная информация.
Затем раскройте меню "Monitor" (рис. 3.11).
Рис. 3.11. Меню "Monitor" приложения DDESpy
С помощью этого меню вы можете определить состав отладочной информации, отображаемой в окне приложения и сохраняемой в только что указанном вами файле. Отметьте все строки, как на рис. 3.11. Теперь вы сможете получить полную отладочную информацию.
Затем раскройте меню "Track" (рис. 3.12).
Рис. 3.12. Меню "Track" приложения DDESpy
С помощью этого меню вы сможете задать информацию о системе DDEML, отображаемую в отдельных окнах.
Отметьте все строки в меню "Track".
При этом в нижней части экрана монитора появится несколько новых пиктограмм, соответствующих отдельным окнам.
Теперь все готово к отладке.
Запустите приложение DDEMLSR, описанное нами раньше. В главном окне приложения DDESpy появятся текстовые строки описания происходящих событий. Эти строки одновременно записываются в файл, указанный нами ранее. Мы к ним еще вернемся, а пока давайте раскроем пиктограмму "Registered Service". Появится окно, в котором вы сможете увидеть имя сервиса "BMPServer", а также название и идентификатор копии приложения (рис. 3.13).
Рис. 3.13. Список серверов DDEML
Раскройте окно "Active Conversation", в котором отображается информация об активных каналах связи.
В этот момент времени ни один канал связи еще не создан, поэтому окно пустое. Запустите приложение DDEMLCL, предназначенное для совместной работы с приложением DDEMLSR. Оно создаст канал связи, используя сервис "BMPServer" и раздел "BMPFile". Теперь в окне "Active Conversation" есть информация о сервисе, разделе и идентификаторах копий приложений клиента и сервера, создавшего канал связи (рис. 3.14).
Рис. 3.14. Список активных каналов
Итак, канал связи установлен. Раскройте окно "String Handles", отображающее список созданных идентификаторов строк (рис. 3.15).
Рис. 3.15. Список идентификаторов строк
И сервер, и клиент создали каждый по три идентификатора, соответствующих строкам "BMPService, "BMPFile" и "DDEData". В столбце "Count" отображается счетчик использования идентификаторов. В нашем случае он равен 2, так как каждая строка была использована по 2 раза - сервером и клиентом. Для одинаковых строк не создается отдельных идентификаторов, а просто увеличивается счетчик использования.
Сделайте активным окно клиента DDEMLCL и выполните пересылку данных, выбрав из меню "Action" строки "Send Filename" и затем "Get Server Version". Будет выполнена передача данных по каналу связи, причем информация о результате будет записана в отладочный файл.
Закройте приложение DDEMLCL и сделайте активным окно "String Handles". Теперь счетчик использования всех трех идентификаторов строк будет равен 1, так как клиент освободил эти идентификаторы. Однако сервер продолжает их использовать, поэтому идентификаторы не уничтожаются.
Окно "Active Conversation" очистится, так как теперь в системе нет активных каналов связи (если только их не создали какие-либо другие приложения).
Теперь завершите приложение DDEMLSR. Окна "String Handles" и "Registered Service" теперь тоже очистятся, так как все строки уничтожены, а сервис больше не доступен.
Завершите приложение DDESpy и загрузите в любой текстовый редактор файл, содержащий протокол отладки. Мы приведем фрагменты такого файла, полученного в результате выполнения описанных выше манипуляций с приложениями DDEMLSR и DDEMLCL.
Первые три строки появились в результате запуска сервера DDEMLSR. Они содержат сведения о том, что были созданы три идентификатора строк для копии приложения с идентификатором 0x458f. Указано время события (от момента запуска Windows), идентификаторы созданных строк (c0d0, c0d5, c2d8) и содержимое строк.
Task:0x458f,Time:7766779,String Handle Created:c0d0(BMPServer) Task:0x458f,Time:7766779,String Handle Created:c0d5(BMPFile) Task:0x458f,Time:7766834,String Handle Created:c2d8(DDEData)
Далее идут строки, описывающие вызов функции обратного вызова сервера:
Task:0x458f Time:7766834 Callback: Type=Register, fmt=0x0("?"), hConv=0x0, hsz1=0xc0d0("BMPServer") hsz2=0x5c40c0d0("BMPServer:(5c40)"), hData=0x0, dwData1=0x0, dwData2=0x0 return=0x0
Здесь функция обратного вызова была вызвана для выполнения регистрации сервиса "BMPServer". Вы можете определить содержимое всех параметров функции на момент вызова.
В следующем фрагмента отражен факт посылки данных серверу:
Task:0x458f Time:7769470 hwndTo=0x5f24 Message(Posted)=Poke: hwndFrom=0x5e44, lParam=0xc2d82aaf status=2000(fRelease ) fmt=0x1("CF_TEXT") Data= "c:\nicebmp\sky.bmp" Item=0xc2d8("DDEData")
Следом идет обращение к функции обратного вызова:
Task:0x458f Time:7770624 Callback: Type=Poke, fmt=0x1("CF_TEXT"), hConv=0x75f24, hsz1=0xc0d5("BMPFile") hsz2=0xc2d8("DDEData"), hData=0x4537232c, dwData1=0x0, dwData2=0x0 return=0x8000 Input data= "c:\nicebmp\sky.bmp"
А вот запрос данных от сервера:
Task:0x458f Time:7772052 hwndTo=0x5f24 Message(Posted)=Request: hwndFrom=0x5e44, lParam=0xc2d80001 fmt=0x1("CF_TEXT") Item=0xc2d8("DDEData") Task:0x458f Time:7772052 Callback: Type=Request, fmt=0x1("CF_TEXT"), hConv=0x75f24, hsz1=0xc0d5("BMPFile") hsz2=0xc2d8("DDEData"), hData=0x0, dwData1=0x0, dwData2=0x0 return=0x2daf02fc Output data= "DDEML Server v.1.0, (C) Frolov A.V."
В следующем фрагменте листинга выполняется запрос на уничтожение канала связи, после чего управление получает функция обратного вызова:
Task:0x458f Time:7774578 hwndTo=0x5f24 Message(Posted)=Terminate: hwndFrom=0x5e44, lParam=0x0 dFrom=0x5e44, lParam=0x0 Task:0x458f Time:7774688 Callback: Type=Disconnect, fmt=0x0("?"), hConv=0x75f24, hsz1=0x0("") hsz2=0x0(""), hData=0x0, dwData1=0x0, dwData2=0x0 return=0x0
Перед завершением работы сервер должен сообщить системе DDEML, что обеспечиваемый им сервис больше не доступен. Следующий фрагмент листинга отражает процедуру "изъятия" сервиса :
Task:0x458f Time:7776281 Callback: Type=Unregister, fmt=0x0("?"), hConv=0x0, hsz1=0xc0d0("BMPServer") hsz2=0x5c40c0d0("BMPServer:(5c40)"), hData=0x0, dwData1=0x0, dwData2=0x0 return=0x0
Анализируя содержимое протокола отладки, вы можете проследить за тем, чтобы при завершении сервера освобождались все занимаемые им ресурсы, такие как идентификаторы строк и сервис. Вы можете проверить значения параметров функции обратного вызова для всех или отдельных транзакций, проследить формат передаваемых данных и многое другое.
Отложенная запись
Мы уже говорили о том, что приложения, как правило, записывают данные в Clipboard одновременно в нескольких форматах. В некоторых случаях это может привести к неэкономному расходованию оперативной памяти. Например, если приложение копирует битовое изображение в форматах CF_DIB, CF_BITMAP вместе с CF_PALETTE, CF_METAFILEPICT и еще в нескольких собственных форматах (о собственных форматах читайте ниже), общий объем израсходованной для записи памяти может оказаться значительным. В то же время, если пользователь скопировал изображение в Clipboard только для того чтобы, например, вставить его в Paintbrush, может оказаться достаточным использование формата метафайла.
В этом разделе мы расскажем о том, как организовать отложенную запись (delayed rendering) в Clipboard и приведем исходные тексты приложения, выполняющего такую операцию с текстом.
Передача данных через канал DDEML
Итак, мы создали канал связи между сервером и клиентом. И сервер, и клиент получили и сохранили идентификаторы созданного канала связи. Теперь все готово для того чтобы приступить к передаче данных.
Передача и прием данных может выполняться в трех режимах: по явному запросу, через "теплый" канал, или через "горячий" канал.
В первом случае клиент посылает серверу запрос, указав нужный элемент данных. Сервер, получив такой запрос, предоставляет клиенту нужные данные. "Теплый" и "горячий" каналы связи создаются в тех случаях, когда клиент должен постоянно следить за изменениями данных, хранящихся в памяти сервера.
В "теплом" режиме при изменении данных сервер посылает клиенту соответствующее извещение. Получив такое извещение, клиент запрашивает у сервера новые данные.
В "горячем" режиме при изменении данных сервер самостоятельно посылает клиенту данные без дополнительного запроса.
Предметом нашего рассмотрения будет самый простой режим - по явному запросу клиента.
Процесс передачи данных заключается в посылке транзакций. Отметим, что транзакции бывают синхронные и асинхронные.
Клиент, пославший синхронную транзакцию, дожидается ее завершения в течение заданного интервала времени. Если по истечении этого интервала времени транзакция не завершилась, клиент получает код ошибки.
После посылки асинхронной транзакции клиент не ждет завершения транзакции. Когда транзакция будет завершена, клиент получит от системы DDEML транзакцию XTYP_XACT_COMPLETE.
В наших примерах мы будем работать с синхронными транзакциями.
Передача данных серверу
Рассмотрим обратную процедуру - передачу данных от клиента серверу.
Процедура передачи данных состоит из двух шагов. Вначале надо создать блок глобальной памяти и записать в него передаваемые данные. Для этого следует воспользоваться только что рассмотренной нами функцией DdeCreateDataHandle:
hData = DdeCreateDataHandle (idInst, szString, lstrlen(szString) + 1, 0L, hszItem, CF_TEXT, 0);
Затем клиент должен передать серверу транзакцию XTYP_POKE, вызвав для этого функцию DdeClientTransaction:
if(hData != NULL) hData = DdeClientTransaction((LPBYTE)hData, -1, hConv, hszItem, CF_TEXT, XTYP_POKE, 1000, &dwResult);
В качестве первого параметра функции DdeClientTransaction передается идентификатор созданного блока памяти. Обратите внимание, что размер блока задан во втором параметре функции как -1. Так и должно быть, если через первый параметр передается не указатель на область памяти, а идентификатор блока памяти.
Приведем описание параметров для транзакции XTYP_POKE:
Параметр | Значение |
hsz1 | Идентификатор строки, содержащей имя раздела |
hsz2 | Идентификатор строки, содержащей имя сервиса |
hData | Идентификатор данных, передаваемых серверу |
Обработчик транзакции XTYP_POKE в приложении DDEMLSR выглядит следующим образом:
case XTYP_POKE: { // Проверяем элемент данных if(hsz1 == hszTopic) { // Получаем данные DdeGetData(hData, (LPBYTE) szDDEData, 200L, 0L);
// Отображаем принятые данные на экране if(szDDEData != NULL) { MessageBox(NULL, szDDEData, "DDEML Server", MB_OK | MB_SYSTEMMODAL | MB_ICONINFORMATION);
// Признак успешного завершения транзакции return((HDDEDATA)DDE_FACK); } } else return((HDDEDATA)NULL); break; }
Этот обработчик сначала проверяет идентификатор элемента данных, затем получает данные с помощью рассмотренной нами ранее функции DdeGetData. Полученные данные отображаются на экране при помощи функции MessageBox.
Обратите внимание, что в качестве признака успешного завершения транзакции возвращается значение DDE_FACK, определенное в файле ddeml.h.
Перекрестные ссылки
Для навигации по справочной системе отдельные разделы связаны между собой при помощи ссылок. Для пользователя ссылки представляются в виде выделенного цветом и подчеркиванием текста или в виде графических пиктограмм.
Разработчик справочной системы может создать ссылку на другой раздел, либо на временное (pop-up) окно. Можно также создать ссылку на раздел, отобразив его во вторичном окне.
В первом и третьем случае в окне приложения winhelp.exe ссылка выглядит как фрагмент текста, подчеркнутый сплошной линией, во втором - пунктирной. Например, на рис. 4.1 строка "Control menu commands" подчеркнута сплошной линией и представляет собой ссылку на другой раздел. Если выбрать мышью эту строку, в окне появится содержимое раздела "Control Menus" (рис. 4.2).
Рис. 4.2. Раздел "Control Menus"
Обратите внимание на то, что в этом разделе есть ссылки на другие разделы, например, "Restore", "Move" и т. д.
Если выбрать мышью ссылку в виде строки, подчеркнутой пунктирной линией, на экране появится временное окно (рис. 4.3).
Рис. 4.3. Временное окно
Временное окно обычно используется для пояснения термина. В нашем случае во временном окне объясняется понятие "application window".
Подготовка разделов
Как мы уже говорили, исходный текст справочной системы, поступающий на вход компилятора Help Compiler, должен быть сохранен в формате RTF (Rich Text Format). Этот формат описан в документации, которая поставляется вместе с SDK, но мы не знаем пока еще никого, подготовившего исходный текст справочной системы в этом формате. Приведем здесь небольшой фрагмент текста в формате RTF. Просто взгляните на него, и вам станет понятно, почему этим форматом трудно пользоваться:
{\rtf1\ansi \deff0\deflang1024{\fonttbl{\f0\froman Times New Roman;}{\f1\froman Symbol;}{\f2\fswiss Arial;}{\f3\fswiss Sans Serif PS;}{\f4\fmodern Courier;}{\f5\fmodern Sans Serif 20cpi;}{\f6\fmodern Roman Cyrillic;} {\f7\fmodern Courier Cyrillic;}{\f8\fswiss Helv Cyrillic;}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255;
К счастью, текстовый процессор Microsoft Word for Windows позволит вам создать справочную систему в режиме "почти WYSIWYG", когда внешний вид редактируемых разделов почти совпадает с тем, что увидит пользователь, запустивший справочную систему.
Структурная единица справочной системы - раздел. Вы должны создать нужное количество разделов справочной системы и снабдить каждый раздел необходимыми атрибутами, к описанию которых мы и перейдем.
Подключение виртуального драйвера
Для подключения драйвера VXDSRV необходимо добавить строку в раздел [386Enh] файла system.ini с указанием пути к файлу виртуального драйвера:
[386Enh] device=f:\wadvance\src\vxdsrv\vxdsrv.386
Если скопировать драйвер в системный каталог Windows, путь можно не указывать, ограничившись только именем файла vxdsrv.386.
После перезапуска Windows драйвер будет активен.
PopupContext("filename", contextnumber)
Отображение раздела, номер контекста которого равен contextnumber. Расположение раздела (имя hlp-файла) определяется параметром filename.
Алиас: PC.
Параметры:
Параметр | Описание |
filename | Имя hlp-файла |
contextnumber | Номер раздела в указанном hlp-файле. Этот номер должен быть определен в разделе MAP файла проекта *.hpj справочной системы |
PopupId("filename", "contextstring")
Отображение раздела с контекстом contextstring. Расположение раздела (имя hlp-файла) определяется параметром filename.
Алиас: PI.
Параметры:
Параметр | Описание |
filename | Имя hlp-файла |
contextstring | Строка контекста отображаемого раздела |
PositionWindow(x, y, w, h, wndstate, "windowname")
Изменение размеров и расположения главного или вторичного окон приложения winhelp.exe.
Алиас: PW.
Параметры:
Параметр | Описание |
x | X-координата верхнего левого угла окна (значение параметров x, y, w, h может находиться в диапазоне от 0 до 1024) |
y | Y-координата верхнего левого угла окна |
w | Высота окна |
h | Ширина окна |
wndstate | Состояние окна:0 нормальное;1 максимизированное |
windowname | Имя окна (главному окну приложения winhelp.exe соответствует имя "main") |
Постоянные сегменты
Все процедуры и данные, не имеющие отношения к инициализации, располагаются, соответственно, в сегментах VXD_CODE и VXD_DATA.
Эти сегменты постоянно присутствуют в памяти и используются при выполнении работы, для которой предназначен виртуальный драйвер. Постоянный сегмент кода содержит процедуры, а постоянный сегмент данных - блок описания устройства DDB, созданный макрокомандой Declare_Virtual_Device, таблицы и глобальные переменные.
Начало постоянного сегмента кода отмечается макрокомандой VXD_CODE_SEG или VXD_LOCKED_CODE_SEG. Обе эти макрокоманды в текущей версии DDK для Windows 3.1 полностью эквивалентны, в чем можно убедиться, посмотрев на их определения в файле vmm.inc.
Конец постоянного сегмента кода отмечается макрокомандой VXD_CODE_ENDS или VXD_LOCKED_CODE_ENDS.
Начало постоянного сегмента данных отмечается макрокомандой VXD_DATA_SEG или полностью эквивалентной ей макрокомандой VXD_LOCKED_DATA_SEG.
Для обозначения конца постоянного сегмента данных используются макрокоманды VXD_DATA_ENDS или VXD_LOCKED_DATA_ENDS.
Prev()
Переход к предыдущему разделу в разделах, расположенных последовательно.
Приложение CLIPRNDR
Для иллюстрации метода отложенной записи мы изменили приложение CLIPTXT, создав на его основе приложение CLIPRNDR. Внешний вид главного окна приложения и выполняемые им функции не изменились, однако теперь это приложение способно выполнять более экономную отложенную запись данных в Clipboard.
Основной файл исходных текстов приведен в листинге 2.11.
Приложение CLIPSHOW
Приложение CLIPSHOW является упрощенным аналогом стандартного приложения Clipboard, предназначенного для динамического просмотра содержимого Clipboard (рис. 2.5).
Рис. 2.5. Главное окно приложения CLIPSHOW
Основной файл приложения приведен в листинге 2.7. В нем определены описанные нами ранее функции, предназначенные для работы с Clipboard и метафайлами, а также все остальные функции приложения.
Приложение CLIPTXT
Работу описанных выше функций вы можете проверить при помощи приложения CLIPTXT (рис. 2.4). Главное меню этого приложения содержит временное меню "Edit". Пользуясь строками "Copy" и "Paste" этого меню вы сможете записать в Clipboard текстовую строку, заданную в приложении, а также просмотреть в главном окне приложения содержимое Clipboard в текстовом формате.
Рис. 2.4. Главное окно приложения CLIPTXT
Файл cliptxt.cpp содержит все функции, определенные в приложении (листинг 2.1).
Приложение DDEMLCL
Приложение DDEMLCL создано нами специально для работы с сервером DDEMLSR, описанном в предыдущем разделе. Вы можете запустить сервер перед запуском клиента DDEMLCL, либо не делать этого. В последнем случае на экране появится предупреждающее сообщение о том, что сервер не запущен (рис. 3.7).
Рис. 3.7. Запрос на запуск сервера DDEML
Вам будет предложено запустить сервер, для чего следует нажать на клавишу "Yes". Приложение DDEMLCL предпримет попытку запустить приложение DDEMLSR из текущего каталога или из каталогов, указанных в переменной среды PATH операционной системы MS-DOS.
Главное окно приложения представлено на рис. 3.8.
Рис. 3.8. Приложение DDEMLCL
С помощью меню "Action" вы можете послать серверу текстовую строку "c:\\nicebmp\\sky.bmp" (строка "Send Filename") или запросить версию сервера (строка "Get Server Version"). В последнем случае принятая строка будет отображена на экране (рис. 3.9).
Рис. 3.9. Клиент отображает текстовую строку, полученную от сервера
Функция WinMain и функция главного окна приложения DDEMLCL определены в файле ddemlcl.cpp (листинг 3.6).
Приложение DDEMLSR
Теперь, когда вы познакомились с основными возможностями библиотеки DDEML, самое время приступить к практике. Мы подготовили для вас два приложения - DDEMLSR и DDEMLCL, которые, как нетрудно догадаться, являются сервером и клиентом DDEML.
Приложение DDEMLSR (рис. 3.5) регистрирует сервис "BMPServer". Клиент может установить канал связи с разделом "BMPFile" и работать с элементом данных "DDEData".
Рис. 3.5. Приложение DDEMLSR
Функции, выполняемые сервером, предельно просты.
После запуска и регистрации сервиса сервер находится в состоянии ожидания запросов от клиента. Предусмотрены два вида запросов - запрос данных от сервера (транзакция XTYP_REQUEST) и передача данных серверу (транзакция XTYP_POKE).
Когда сервер получает запрос на передачу данных клиенту, он в ответ передает текстовую строку, в которой находится описание версии приложения DDEMLSR.
Если клиент посылает серверу данные (в виде текстовой строки), сервер отображает данные на экране при помощи функции MessageBox (рис. 3.6).
Рис. 3.6. Сервер отображает текстовую строку, полученную от клиента по каналу DDE
Функция WinMain и функция главного окна приложения определены в файле ddemlsr.cpp (листинг 3.1).
Приложение DOS2WIN
Приложение DOS2WIN (листинг 5.4) работает в кооперации с DLL-библиотекой d2w.dll и виртуальным драйвером vxdsrv.386, описанном в предыдущем разделе. Его основная задача - обработка сообщения WM_STARTWINAPP, которое посылается из библиотеки d2w.dll, когда любая виртуальная машина MS-DOS пытается запустить приложение Windows.
Приложение HELPMWH
Приложение HELPMWH демонстрирует приемы работы с функцией WinHelp и один из возможных способов организации получения контекстно-зависимой справки. Меню "Help" этого приложения (рис. 4.28) открывает доступ к содержимому справочной базы данных hlpmore.hlp, описанной в предыдущем разделе.
Рис. 4.28. Меню 'Help" приложения HELPMWH
Если, запустив приложение HELPMWH, нажать комбинацию клавиш <Shift+F1>, запустится приложение winhelp.exe и в его главном окне отобразится раздел оглавления справочной системы hlpmore.hlp.
До тех пор, пока не выделена какая-нибудь строка меню, клавиша <F1> не действует. Если же выделить любую строку любого меню, кроме "Help", с помощью клавиши <F1> можно будет получить контекстно-зависимую подсказку. Например, при выделении любой строки в меню "File" с последующим нажатием на клавишу <F1> на экране появится справка об использовании меню "File". Аналогичная операция в меню "Edit" или 'View" приведет к отображению справочной информации об использовании этих меню.
Нажав на клавишу <F1> при выделенном меню "Help", вы увидите оглавление справочной системы.
Таким образом, доступ к справочной системе организован таким образом, что в зависимости от того, какое меню выделено, с помощью клавиши <F1> можно вызвать тот или иной раздел справочной системы.
Клавиша <F1> действует и во время отображения диалоговой панели, которая появляется на экране при выборе из меню "Help" нашего приложения строки "About..." (рис. 4.29).
Рис. 4.29. Диалоговая панель, которая появляется при выборе из меню "Help" строки "About..."
Мы организовали контекстно-чувствительную подсказку таким образом, что нажав клавишу <F1> во время отображения диалоговой панели, мы попадаем в раздел оглавления, однако вы можете выбрать любой другой раздел по вашему усмотрению, немного изменив исходные тексты приложения.
Основной файл исходных текстов приведен в листинге 4.3.
Приложение MDIAPP
Итак, теперь мы готовы приступить к созданию MDI-приложения. В этом разделе мы приведем исходные тексты простейшего MDI-приложения MDIAPP (рис. 1.9).
Это приложение имеет все основные свойства стандартного MDI-приложения, в частности, оно имеет стандартное меню "Window", с помощью которого можно управлять расположением окон Document Window.
Рис. 1.9. Простейшее MDI-приложение MDIAPP
Все функции приложения определены в файле mdiapp.cpp (листинг 1.1). В частности, в этом файле определена функция WinMain, функция окна Frame Window (которая имеет имя FrameWndProc) и функция окна Document Window (с именем ChildWndProc).
Приложение MDITB
Приложение MDITB отличается от только что рассмотренного нами приложения MDIAPP наличием дополнительных окон Toolbar и Statusbar (рис. 1.11).
Рис. 1.11. Приложение MDITB
Для сокращения объема исходного текста мы не стали полностью реализовывать стандартные для окон Toolbar и Statusbar функции, ограничившись демонстрацией способов создания этих окон в MDI-приложении. Один из возможных способов реализации функций окна Toolbar мы описали в 13 томе "Библиотеки системного программиста" (см. разделы "Орган управления TOOLBAR" и "Приложение SMARTPAD" главы "Меню"). Реализация функций окна Statusbar не отнимет у вас много сил, поэтому вы справитесь с этим окном самостоятельно.
Итак, обратимся к листингу 1.5, содержащему определения всех функций приложения MDITB. В книге приведен сокращенный вариант листинга (без комментариев), полный вариант вы найдете на дискете, которая продается вместе с книгой.
Приложение WAST
Приложение WAST (листинг 5.18) не создает главного окна и не знает ничего о виртуальном драйвере vxdsrv.386, хотя обращается к нему косвенно через загружаемый драйвер wastdrv.dll.
Print()
Печать содержимого текущего (отображаемого в главном окне) раздела на принтере.
PrinterSetup()
Установка параметров принтера с помощью диалоговой панели "Print Setup".
Процесс инициализации
Рассмотрим процесс инициализации виртуального драйвера более подробно.
Вначале в память загружается сегмент инициализации реального режима VXD_REAL_INIT. После загрузки система управления виртуальными машинами VMM (Virtual Machine Manager) вызывает процедуру инициализации реального режима. Она выполняет все необходимые действия перед переключением Windows в защищенный режим, например, сбрасывает обслуживаемое драйвером устройство в исходное состояние или блокирует его. На этапе инициализации в реальном режиме можно выполнить проверку файлов инициализации system.ini и win.ini, резервирование физических страниц памяти для использования устройством. Можно также зарезервировать блок памяти, который имеет отношение к устройству и создается для каждой вновь запускаемой виртуальной машины.
Имя процедуры инициализации реального режима не имеет значения, однако эта процедура должна располагаться в самом начале сегмента инициализации реального режима.
Перед вызовом процедуры регистры процессора устанавливаются следующим образом.
Регистр | Описание содержимого регистра |
CS, DS, ES | Адрес сегмента инициализации реального режима |
AH | Старший номер версии системы управления виртуальными машинамиVMM |
AL | Младший номер версии системы управления виртуальными машинами VMM |
BX | Флаги загрузки:Duplicate_Device_ID повторная загрузка драйвера с тем же идентификатором;Duplicate_From_INT2F повторная загрузка драйвера с тем же идентификатором из списка драйверов прерывания INT 2Fh;Loading_From_INT2F драйвер был определен в списке драйверов прерывания INT 2Fh |
ECX | Дальний адрес в формате <сегмент:смещение> точки входа сервиса инициализации реального режима |
EDX | Указатель на данные, полученные из прерывания INT 2Fh. Если данные не передаются, регистр содержит нулевое значение |
SI | Сегментный адрес блока памяти, содержащего среду (environment) операционной системы MS-DOS |
SS:SP | Адрес стека |
Процедура инициализации реального режима может определять конфигурацию компьютера с помощью прерываний BIOS и соответствующих функций MS-DOS, однако она не должна вызывать функции MS-DOS, предназначенные для завершения работы программ.
Возврат из процедуры инициализации реального режима должен выполняться ближней версией команды ret. Перед возвратом необходимо установить регистры процессора:
Регистр | Описание содержимого регистра |
AX | Код возврата:Abort_Device_Load не загружать виртуальный драйвер;Abort_Win386_Load не загружать Windows;Device_Load_Ok можно продолжать процедуру инициализации и загрузки драйвера в память;No_Fail_Message это значение можно комбинировать со значениями Abort_Device_Load и Abort_Win386_Load, в этом случае на экран не выдается сообщение об аварийном завершении загрузки драйвера или операционной системы Windows |
BX | Указатель на массив, содержащий номера физических страниц памяти, зарезервированных для исключительного использования виртуальным драйвером. Последний элемент массива должен содержать нулевое значение.Под указателем здесь понимается смещение в сегменте инициализации реального режимаЕсли страницы физической памяти не резервируются, регистр BX должен содержать нулевое значение |
EDX | 32-разрядное значение, которое будет передано через регистр EDX процедуре инициализации защищенного режима, вызываемой по сообщению Sys_Critical_Init (инициализация в защищенном режиме будет рассмотрена позже) |
SI | Указатель на массив структур данных, состоящих из двойного слова и слова. Двойное слово содержит указатель на блок памяти, слово - размер этого блока памяти. Последний элемент массива должен содержать нулевое значение.Описанные таким образом блоки памяти создаются для каждой запускаемой виртуальной машины.Если блоки памяти не резервируются, в регистр SI следует записать нулевое значение |
Вызов сервиса выполняется следующим образом:
mov ax, Service call cs:[ecx]
где Service - код сервиса.
Для кода сервиса возможны следующие значения:
Код сервиса | Описание |
0000hGet_Profile_String | Получение строки из файла system.ini.В DS:SI должен находиться указатель на строку имени секции или 0 для секции [386Enh].В DS:DI необходимо записать адрес имени, а в ES:DX - адрес строки, которая будет использована по умолчанию.На выходе ES:DX будет содержать адрес строки, прочитанной из файла конфигурации или адрес строки по умолчанию, если указанные раздел или имя не найдены |
0001hGet_Next_Profile_String | Получение следующего значения, используется в том случае, если для одного имени указано несколько разных значений |
0003hGet_Profile_Boolean | Получение значения типа BOOL из файла system.ini.В ECX нужно записать значение по умолчанию (0 или 0FFFFFFFFh). В DS:SI должен находиться указатель на строку имени секции или 0 для секции [386Enh].В DS:DI необходимо записать адрес имени.Прочитанное из файла конфигурации значение записывается в регистр ECX |
0004hGet_Profile_Decimal_Int | Аналогично предыдущему, но возвращается целое значение, преобразованное из десятичного формата |
0005hGet_Profile_Hex_Int | Аналогично предыдущему, но возвращается целое значение, преобразованное из шестнадцатиричного формата |
Более подробное описание сервиса, доступного на этапе инициализации в реальном режиме, вы найдете в документации, которая поставляется вместе с DDK.
Теперь мы займемся вторым этапом инициализации, который выполняется в защищенном режиме.
Как мы уже говорили, макрокоманде Declare_Virtual_Device, расположенной в самом начале исходного текста виртуального драйвера, среди прочих параметров необходимо указать адрес управляющей точки входа. Соответствующая процедура (управляющая процедура) вызывается системой управления виртуальными машинами VMM при возникновении в системе различных событий, имеющих отношение как к данному драйверу, так и ко всей системе в целом.
При вызове управляющей процедуре передается системное управляющее сообщение (system control message). В драйвере необходимо предусмотреть обработку некоторых или всех таких сообщений (в зависимости от назначения драйвера).
На втором этапе инициализации виртуальному драйверу по очереди передаются три инициализирующих системных управляющих сообщения: Sys_Critical_Init, Device_Init и Init_Complete.
Сообщение Sys_Critical_Init приходит первым, причем в момент прихода этого сообщения прерывания запрещены. Во время обработки этого сообщения драйвер может устанавливать вектора прерываний и процедуры обработки прерываний, захватывать страницы физической памяти и выполнять другие инициализирующие действия.
Далее в процессе второго этапа инициализации драйвер получает сообщение Device_Init. На этот раз прерывания разрешены. На момент прихода этого сообщения создана системная виртуальная машина (ее идентификатор передается в регистре EBX). Ваш драйвер может пользоваться сервисом, предоставляемым другими виртуальными драйверами. О том, что это за сервис, вы узнаете позже.
На последней стадии инициализации драйвер получает сообщение Init_Complete. После того как обработчик этого сообщения вернет управление, система удалит из памяти сегменты инициализации в защищенном режиме.
Как организовать обработку этих и других сообщений?
Прежде всего, необходимо создать управляющую процедуру и указать ее имя в четвертом параметре макрокоманды Declare_Virtual_Device.
Вот пример управляющей процедуры нашего драйвера VXDSRV, который мы рассмотрим позже:
BeginProc VXDSRV_Control ; Процедура системной критической инициализации Control_Dispatch Sys_Critical_Init, VXDSRV_Sys_Crit_Init clc ; признак успешного завершения ret EndProc VXDSRV_Control
В начале процедуры вызывается макрокоманда Control_Dispatch, с помощью которой организуется вызов процедуры VXDSRV_Sys_Crit_Init (определенной в исходном тексте драйвера) при поступлении сообщения Sys_Critical_Init. Если нужно организовать обработку других сообщений, следует поместить в начало процедуры несколько макрокоманд Control_Dispatch (по одной на сообщение), указав коды сообщений и имена процедур обработки.
Заметим, что ваш драйвер может обрабатывать одно-два сообщения или не обрабатывать их совсем - все зависит от того, для чего предназначен драйвер. Обычно ограничиваются одной из трех процедур инициализации.
Приведем исходный текст процедуры системной критической инициализации драйвера VXDSRV:
VXD_ICODE_SEG BeginProc VXDSRV_Sys_Crit_Init
; Устанавливаем фильтр для прерывания int 21h mov eax, 21h mov esi, offset32 V86_Int21_Handler VMMcall Hook_V86_Int_Chain
clc ; признак успешного завершения ret EndProc VXDSRV_Sys_Crit_Init VXD_ICODE_ENDS
Эта процедура расположена в сегменте инициализации защищенного режима, который будет удален из памяти после завершения второго этапа инициализации. Она добавляет обработчик в цепочку обработчиков прерывания INT 21h с помощью сервиса Hook_V86_Int_Chain. При этом указывается адрес добавляемого обработчика - смещение функции V86_Int21_Handler.
Макрокоманда VMMcall служит для вызова сервиса и будет описана позже.
Итак, подведем небольшой итог.
Исходный текст виртуального драйвера должен начинаться с определения заголовка устройства, в котором следует среди прочих параметров задать имя управляющей процедуры, предназначенной для обработки системных управляющих сообщений.
Необходимо объявить один сегмент инициализации реального режима, один сегмент кода для инициализации защищенного режима и при необходимости один сегмент данных для инициализации защищенного режима.
Инициализация выполняется в два этапа.
На первом этапе вызывается процедура инициализации реального режима, после чего соответствующий сегмент удаляется из памяти.
Второй этап заключается в обработке трех системных управляющих сообщений. Ваш драйвер может использовать только некоторые из этих трех сообщений (обработка остальных сообщений будет выполнена системой автоматически). Для установки соответствия между системным управляющим сообщением и процедурой обработки этого сообщения следует использовать макрокоманду Control_Dispatch.
Программный интерфейс драйвера
Далее, вслед за управляющей процедурой VXDSRV_Control, обеспечивающей обработку системного сообщения Sys_Crit_Init, в исходном тексте драйвера находится таблица адресов функций программного интерфейса драйвера VXDSRV_API_Call.
В этой таблице находятся адреса трех функций, предназначенных для вызова из виртуальных машин MS-DOS и системной виртуальной машины. Эти функции являются интерфейсом между драйвером с одной стороны, и виртуальными машинами, с другой стороны.
Функция vxdapiGetVersion позволяет определить версию нашего виртуального драйвера. Абсолютно любой виртуальный драйвер должен иметь в составе своего интерфейса эту функцию, причем она должна иметь номер, равный 0 (т. е. при ее вызове в регистр AX необходимо загрузить нулевое значение).
Функции vxdapiRegisterWnd и vxdapiUnregisterWnd предназначены, соответственно, для регистрации и отмены регистрации главного окна приложения dos2win.exe, а также буфера и функции обратного вызова DLL-библиотеки d2w.dll в виртуальном драйвере. Регистрация заключается в том, что драйвер сохраняет адрес функции обратного вызова и буфера в своих глобальных переменных.
Имена процедур VXDSRV_V86API_Handler и VXDSRV_PMAPI_Handler описаны в макрокоманде определения драйвера. Эти процедуры получают управление при вызове драйвера, соответственно, из виртуальной машины MS-DOS и системной виртуальной машины. Их задачей является установка флага V86CallFlag, который может быть проанализирован при необходимости определения типа виртуальной машины, из которой сделан вызов - системной или виртуальной машины MS-DOS.
После установки флага обе процедуры вызывают процедуру VXDSRV_API_Handler. Ее задачей является анализ содержимого регистра AX вызывающей виртуальной машины и передача управления на соответствующую процедуру по таблице адресов функций программного интерфейса драйвера VXDSRV_API_Call.
Обратите внимание, что анализируется не текущее содержимое регистра AX, а содержимое регистра AX виртуальной машины, взятое из структуры, адрес которой передается через регистр EBP.
Напомним, что в этой структуре сохраняется текущий контекст виртуальной машины на момент вызова виртуального драйвера.
Займемся теперь программным интерфейсом драйвера VXDSRV.
Процедура vxdapiGetVersion - самая простая. Она возвращает версию драйвера в регистре AX вызывающей виртуальной машины. Заметьте, что процедура не просто записывает номер версии в регистр AX, а изменяет соответствующее поле структуры контекста вызывающей виртуальной машины.
Процедура vxdapiRegisterWnd регистрирует окно приложения dos2win.exe. Эту процедуру можно вызывать только в защищенном режиме, поэтому анализируется флаг V86CallFlag.
Селектор и смещение функции обратного вызова, расположенной в DLL-библиотеке, передается этой процедуре через регистры DX и CX. Виртуальный драйвер сохраняет эти значения в глобальных переменных CallbackSel и CallbackOff.
Помимо адреса функции обратного вызова, виртуальный драйвер сохраняет адрес буфера, предназначенного для записи параметров запускаемого приложения Windows. Этот адрес передается в регистрах SI:DI.
Но драйвер работает в модели памяти FLAT, поэтому для обеспечения доступа к буферу со стороны драйвера необходимо преобразовать адрес из формата <селектор:смещение> во FLAT-адрес. Проще всего сделать это с помощью сервиса Map_Flat.
Перед вызовом этого сервиса в регистр AH необходимо загрузить смещение поля структуры контекста виртуальной машины, содержащего селекторную (или сегментную) компоненту адреса, а в регистр AL - смещение поля, содержащего компоненту смещения. Сервис Map_Flat выполнит все необходимые преобразования, причем будет приниматься во внимание тип виртуальной машины. Для виртуальной машины MS-DOS будет выполнено преобразование из формата <сегмент:смещение>, а для системной виртуальной машины - из формата <селектор:смещение>.
Результат преобразования будет записан в регистр EAX.
При регистрации виртуальный драйвер создает семафор с начальным значением, равным 1. Этот семафор будет использован для исключения реентерабельных вызовов одной из процедур драйвера.
Семафоры создаются сервисом Create_Semaphore. Начальное значение семафора задается содержимым регистра ECX перед вызовом сервиса. Идентификатор созданного семафора возвращается в регистре EAX. Его необходимо сохранить для выполнения операций над семафором.
Процедура vxdapiUnregisterWnd отменяет регистрацию. Она записывает нулевые значения в глобальные переменные, содержащие адреса функции обратного вызова и буфера, которые находятся в DLL-библиотеке d2w.dll, а также уничтожает созданный при регистрации семафор, вызывая сервис Destroy_Semaphore. Идентификатор уничтожаемого семафора передается через регистр EAX.
Просмотр содержимого Clipboard
В этом разделе мы расскажем о том, как создать приложение, аналогичное стандартному приложению Clipboard операционной системы Windows 3.1. На примере исходных текстов приложения CLIPSHOW вы научитесь создавать окна, предназначенные для динамического просмотра содержимого Clipboard, научитесь извлекать и рисовать данные в формате битовых изображений с цветовыми палитрами, а также в формате метафайла.
Простейшие приемы использования Clipboard
В этой главе мы опишем основные функции программного интерфейса Windows, предназначенные для работы с Clipboard, рассмотрим простейшие приемы использования Clipboard и приведем пример приложения, выполняющего запись текстовых данных в Clipboard и чтение их из Clipboard в буфер приложения.
Прототип функции WinHelp
Прототип функции WinHelp определен в файле windows.h:
BOOL WINAPI WinHelp( HWND hwndMain, // идентификатор окна LPCSTR lpszHelp, // путь к hlp-файлу UINT usCommand, // код операции DWORD ulData); // дополнительные данные
Параметр hwndMain перед вызовом функции должен содержать идентификатор окна, для которого вызывается справочная система.
Через параметр lpszHelp передается указатель на текстовую строку, закрытую двоичным нулем, в которой должен быть записан путь к hlp-файлу, содержащему нужную справочную систему.
Функция WinHelp может выполнять одну из нескольких операций в зависимости от значения параметра usCommand:
Команда | Описание |
HELP_COMMAND0x0102 | Выполнение макрокоманды, заданной параметром ulData. Этот параметр должен содержать дальний указатель на текстовую строку, содержащую макрокоманду. Перед использованием команды HELP_COMMAND необходимо, чтобы было запущено приложение winhelp.exe и чтобы нужный hlp-файл был открыт |
HELP_CONTENTS0x0003 | Отображение раздела, выполняющего роль оглавления справочной системы. Параметр ulData для этой команды должен быть равен нулю |
HELP_CONTEXT0x0001 | Отображение содержимого раздела, заданного номером контекста, определенным в разделе MAP файла проекта справочной системы. Через параметр ulData передается номер контекста отображаемого раздела |
HELP_CONTEXTPOPUP0x0008 | Отображение содержимого раздела, заданного номером контекста, во временном окне. Номер контекста должен быть определен в секции MAP файла проекта справочной системы и указан в параметре ulData |
HELP_FORCEFILE0x0009 | Если в момент вызова функции WinHelp с этим кодом операции отображается правильный hlp-файл, функция отрабатывает вхолостую. В противном случае отображается раздел оглавления, заданный в секции CONTENTS файла проекта справочной системы. Параметр ulData для этой команды должен быть равен нулю |
HELP_HELPONHELP0x0004 | Отображение раздела оглавления справочной системы, содержащей информацию об использовании приложения winhelp.exe. Параметр ulData должен быть равен нулю |
HELP_INDEX0x0003 | Синоним HELP_CONTEXT, использовался раньше в функции WinHelp для Windows версии 3.0 |
HELP_KEY0x0101 | Отображение раздела справочной системы в соответствии с ключевым словом, передаваемым через параметр ulData. Этот параметр должен содержать дальний указатель на текстовую строку, содержащую ключевое слово |
HELP_MULTIKEY0x0201 | Аналогично предыдущему, но с использованием альтернативной таблицы ключей. Параметр ulData должен содержать дальний указатель на структуру MULTIKEYHELP, определяющую символ сноски для альтернативного ключа и ключевое слово |
HELP_POPUPID0x0104 | Отображение содержимого раздела, заданного номером контекста, во временном окне. Через параметр ulData передается номер контекста отображаемого раздела, определенный в разделе MAP файла проекта справочной системы. |
HELP_PARTIALKEY0x0105 | Аналогично HELP_KEY, однако отображаются разделы, для которых имеется неполное соответствие (несколько начальных символов ключевого слова) |
HELP_QUIT0x0002 | Завершение работы с hlp-файлом. Если ни одно другое приложение не выполняет никаких операций со справочной системой, приложение winhelp.exe завершает свою работу. Параметр ulData должен быть равен нулю |
HELP_SETCONTENTS0x0005 | Раздел, номер контекста которого указан в параметре ulData, будет выполнять функции оглавления справочной системы. Номер контекста раздела должен быть определен в секции MAP файла проекта справочной системы |
HELP_SETINDEX0x0005 | Синоним HELP_SETCONTEXT, использовался раньше в функции WinHelp для Windows версии 3.0 |
HELP_SETWINPOS0x0203 | Изменение размеров и расположения окна приложения winhelp.exe в соответствии со значениями, определенными в структуре HELPWININFO, указатель на которую передается через параметр ulData |
Структура MULTIKEYHELP определена в файле windows.h следующим образом:
typedef struct tagMULTIKEYHELP { UINT mkSize; // размер структуры в байтах BYTE mkKeylist; // символ сноски BYTE szKeyphrase[1]; // текстовая строка, содержащая // ключевое слово } MULTIKEYHELP;
Текстовая строка szKeyphrase должна быть закрыта двоичным нулем.
Структура HELPWININFO (и указатели на нее) определена также в файле windows.h:
typedef struct { int wStructSize; // размер структуры в байтах int x; // X-координата верхнего левого угла окна int y; // Y-координата верхнего левого угла окна int dx; // ширина окна int dy; // высота окна int wMax; // стиль отображения окна char rgchMember[2]; // имя окна } HELPWININFO; typedef HELPWININFO NEAR* PHELPWININFO; typedef HELPWININFO FAR* LPHELPWININFO;
Для стиля отображения окна вы можете использовать константы с префиксом имени SW:
Константа | Описание |
SW_HIDE | Скрыть окно |
SW_SHOWNORMAL | Активизировать окно и отобразить его в нормальном состоянии (не минимизированном или максимизированном) |
SW_SHOWMINIMIZED | Минимизировать окно |
SW_SHOWMAXIMIZED | Максимизировать окно |
SW_SHOWNOACTIVE | Использовать для окна старые размеры и расположение. Те окна, которые были активными на момент вызова функции, по-прежнему остаются активными |
SW_SHOW | Активизировать и отобразить окно, используя текущие размеры и расположение |
SW_MINIMIZE | Минимизировать окно, активизировав другое, расположенное в самом низу (т. е. окно нижнего уровня вдоль оси Z) |
SW_SHOWMINNOACTIVE | Отобразить окно как пиктограмму. Те окна, которые были активными на момент вызова функции, по-прежнему остаются активными |
SW_SHOWNA | Отобразить окно в текущем состоянии. Те окна, которые были активными на момент вызова функции, по-прежнему остаются активными |
SW_RESTORE | Синоним SW_SHOWNORMAL |
Работа с окнами Document Window
В этом разделе мы кратко рассмотрим некоторые дополнительные вопросы, возникающие при создании MDI-приложений. Мы расскажем о безопасном способе уничтожения окон Document Window, о динамическом изменении главного меню приложения и о том, как идентифицировать окна Document Window.
Разделы
Основной "атомарный" элемент справочной системы - раздел (topic). Раздел представляет собой фрагмент справочной системы, отображаемый в окне приложения winhelp.exe. Он может содержать как текст, так и графические изображения (рис. 4.1).
Рис. 4.1. Раздел справочной системы Microsoft Word for Windows
Если размер окна недостаточен для отображения раздела целиком, у окна появляется горизонтальная или вертикальная полоса просмотра (либо сразу и горизонтальная, и вертикальная полоса просмотра).
Помимо основного окна, приложение winhelp.exe способно создавать вторичные перекрывающиеся окна и временные окна. В этих окнах также отображается содержимое разделов справочной системы.
Можно сказать, что справочная система состоит из многих разделов, связанных между собой многочисленными перекрестными ссылками и имеет структуру гипертекста. Каждый раздел обычно имеет заголовок, отображаемый в верхней части окна просмотра, идентификатор, набор ключевых слов, по которым можно найти раздел, а также ссылки на другие разделы.
Создавая исходный текст справочной системы в текстовом процессоре Microsoft Word for Windows, вы создаете разделы в виде групп обычных параграфов текста. Каждая группа должна начинаться с новой страницы и содержать в первом параграфе заголовок, идентификатор, ключевые поля и другие атрибуты в виде подстрочных сносок. Как мы уже говорили, вы можете включить в текст графические изображения или таблицы.
RegisterRoutine("DLLname", "functionname", "format")
Макрокоманда RegisterRoutine позволяет вам расширить набор макрокоманд, определив собственные макрокоманды. Вы можете создать макрокоманду в виде функции, находящейся в разработанной вами DLL-библиотеке. Перед использованием такую функцию необходимо зарегистрировать, вызвав макрокоманду RegisterRoutine. Лучшее место для вызова макрокоманды регистрации - раздел CONFIG файла проекта справочной системы, хотя вы можете зарегистрировать свою функцию в любое время до ее вызова.
Алиас: RR.
Параметры:
Параметр | Описание |
DLLname | Имя DLL-библиотеки, из которой выполняется вызов функции |
functionname | Имя функции |
format | Описание формата параметров. |
Строка описания параметров состоит из отдельных букв, соответствующих типу параметров функции:
Буква в строке формата | Тип параметра |
u | unsigned short |
U | unsigned long |
i | short int |
I | long int |
s | near char * |
S | far char * |
v | void |
Регистрация сервиса
Следующий этап в инициализации сервера DDEML заключается в регистрации предоставляемого им сервиса.
Библиотека DDEML использует трехступенчатую схему адресации данных, передаваемых по каналу связи - сервис (service), раздел (topic) и элемент данных (data item). Приложение задает элементы адреса в виде текстовых строк размером не более 255 байт. Это ограничение возникло в результате использования для реализации DDEML атомов, которые представляют собой идентификаторы текстовых строк, хранящихся в специальной системной таблице (для Windows версии 3.1). Размер таких строк не должен превышать 255 байт.
Сервер DDEML может предоставлять сервис одного или нескольких видов. Как правило, один сервер предоставляет только один сервис, причем текстовая строка, идентифицирующая сервис, часто совпадает с именем приложения. Но можно выбрать любую другую строку. Например, наше приложение DDEMLSR предоставляет сервис "BMPService". Как можно догадаться из названия, этот сервис связан с bmp-файлами (в действительности мы привели сильно упрощенную версию сервера bmp-файлов, в которой для сокращения объема листингов изъяты функции обслуживания bmp-файлов).
Второй элемент адреса - раздел. В рамках одного сервиса можно определить несколько разделов. Когда клиент DDEML создает канал с сервером, он указывает сервис и раздел. Раздел объединяет группу элементов данных или выполняемых функций. В приложении DDEMLSR определен один раздел "BMPFile".
Канал DDEML служит для передачи блоков данных. В рамках одного раздела сервер может обмениваться с клиентом разными блоками данных, каждый из которых идентифицируется при передаче именем элемента данных. В процессе создания канала связи не требуется указывать элементы данных.
Для иллюстрации сказанного выше предположим, что мы создаем сервер BMPSERV.EXE, предназначенный для отображения битовых изображений DIB, причем путь к соответствующему bmp-файлу и управляющая информация должны передаваться серверу через канал связи DDE.
При регистрации сервер BMPSERV.EXE регистрирует один сервис "BMPServer" и два раздела: "BMPFile" и "Control" (рис. 3.4).
Рис. 3.4. Сервис, разделы и элементы данных для сервера BMPSERV
В разделе "BMPFile" определены элементы данных "Filename" (имя отображаемого bmp-файла) и "Title" (заголовок изображения или подпись под изображением). В разделе "Control" определен один элемент данных "Mode", определяющий режим отображения (размеры и расположение окна, органы управления для работы с изображением и т. п.).
Клиент может создавать два канала с сервером. Первый канал можно обозначить как BMPServer/BMPFile, второй - BMPServer/Control. Через канал BMPServer/BMPFile передается путь к отображаемому файлу и заголовок изображения, а через канал BMPServer/Control клиент может управлять режимом отображения.
Разумеется, предложенная схема не единственно возможная и даже не самая простая. В нашем случае можно было ограничиться одним каналом, передавая по нему либо путь к файлу, либо управляющую информацию, имеющую отношение к отображению содержимого файла.
Регистрация сервиса выполняется сервером DDEML обычно сразу после вызова функции DdeInitialize и выполняется в два этапа.
На первом этапе текстовая строка имени сервиса сохраняется в специальной системной таблице (таблице атомов), для чего вызывается функция DdeCreateStringHandle:
HSZ WINAPI DdeCreateStringHandle( DWORD idInst, // идентификатор приложения LPCSTR psz, // адрес текстовой строки int iCodePage); // кодовая страница
Через параметр idInst приложение должно передать идентификатор, полученный на этапе регистрации приложения в библиотеке DDEML функцией DdeInitialize.
Параметр psz представляет собой указатель на текстовую строку, закрытую двоичным нулем. Размер этой строки не должен превышать 255 байт.
В качестве значения для параметра iCodePage можно указать CP_WINANSI (эта константа равна нулю). Можно также использовать значение, полученное от функции GetKBCodePage. Функция GetKBCodePage не имеет параметров и возвращает номер текущей кодовой страницы.
Идентификатор текстовой строки, возвращенный функцией DdeCreateStringHandle и соответствующий регистрируемому сервису, следует передать функции DdeNameService:
HDDEDATA WINAPI DdeNameService( DWORD idInst, // идентификатор приложения HSZ hsz1, // идентификатор строки имени сервиса HSZ hsz2, // зарезервировано UINT afCmd); // флаги
Через параметр idInst приложение должно передать идентификатор, полученный на этапе регистрации приложения в библиотеке DDEML функцией DdeInitialize.
Параметр hsz1 предназначен для передачи имени сервиса через идентификатор текстовой строки, возвращенной функцией DdeCreateStringHandle.
Параметр hsz2 зарезервирован, для него следует использовать нулевое значение.
При регистрации сервиса через параметр afCmd следует передать значение DNS_REGISTER (регистрация сервиса). Сервер DDEML в процессе своей работы может динамически регистрировать и отменять виды предоставляемого сервиса. Для отмены сервиса через параметр afCmd передается значение DNS_UNREGISTER.
Перед завершением работы сервер DDEML должен отменить весь зарегистрированный им ранее сервис, вызвав функцию DdeInitialize с параметром afCmd, имеющим значение DNS_UNREGISTER.
Если регистрация сервиса выполнена успешно, функция DdeNameService возвращает ненулевое значение, а при ошибке - нулевое.
Приведем фрагмент кода, выполняющего регистрацию сервиса "BMPServer":
hszService = DdeCreateStringHandle(idInst, "BMPServer", CP_WINANSI); DdeNameService(idInst, hszService, (HSZ)NULL, DNS_REGISTER);
Одновременно с регистрацией сервиса сервер обычно создает идентификаторы текстовых строк, содержащих имена используемых разделов и элементов данных. Для этого вызывается все та же функция DdeCreateStringHandle:
hszTopic = DdeCreateStringHandle(idInst, szTopic, CP_WINANSI); hszItem = DdeCreateStringHandle(idInst, szItem, CP_WINANSI);
Отметим, что регистрацию сервиса выполняет только сервер DDEML. Что же касается создания идентификаторов текстовых строк функцией DdeCreateStringHandle, то эта операция выполняется как сервером, так и клиентом. Полученные идентификаторы используются при создании канала и в процессе передачи данных.
Зная идентификатор строки, приложение может получить строку, вызвав функцию DdeQueryString:
DWORD WINAPI DdeQueryString( DWORD idInst, // идентификатор приложения HSZ hsz, // идентификатор строки LPSTR psz, // адрес буфера для записи строки DWORD cchMax, // размер буфера int iCodePage); // кодовая страница
Назначение параметров понятно из комментариев в прототипе функции.
Если идентификатор созданной текстовой строки используется в функции обратного вызова (которую мы рассмотрим чуть позже), за освобождение ресурсов, связанных с текстовой строкой, отвечает система DDEML. В противном случае приложение должно самостоятельно уничтожать созданные им идентификаторы, вызывая функцию DdeFreeStringHandle:
BOOL WINAPI DdeFreeStringHandle( DWORD idInst, // идентификатор приложения HSZ hsz); // идентификатор уничтожаемой строки
В случае успеха функция DdeFreeStringHandle возвращает ненулевое значение, при ошибке - нулевое.
Регистрация в библиотеке DDEML
Библиотека DDEML используется одновременно многими приложениями, однако, подобно всем DLL-библиотекам, находится в оперативной памяти в единственном экземпляре. Функция LibMain, выполняющая инициализацию DLL-библиотеки, вызывается только один раз при первой загрузке библиотеки в память (для Windows версии 3.1), поэтому LibMain не может использоваться для регистрации приложений.
Если приложение собирается использовать DDEML, оно должно зарегистрировать себя в библиотеке DDEML, вызвав специально предназначенную для этого функцию с именем DdeInitialize.
Прототип функции DdeInitialize определен в файле ddeml.h (который должен быть включен в исходный текст DDEML-приложения наряду с файлом windows.h):
UINT WINAPI DdeInitialize(
DWORD FAR* pidInst, // адрес идентификатора приложения
PFNCALLBACK pfnCallback, // адрес функции обратного вызова
DWORD afCmd, // флаги DWORD ulRes); // зарезервировано
Функция DdeInitialize используется в процессе инициализации и серверов, и клиентов DDEML. Сама по себе она не создает никаких каналов передачи данных между приложениями, однако процедура регистрации приложения, выполняемая этой функцией, должна быть проведена до вызова любых других функций, имеющих отношение к DDEML.
Займемся параметрами функции DdeInitialize.
Параметр pidInst представляет собой указатель на двойное слово, в которое после регистрации будет записан идентификатор, присвоенный копии приложения библиотекой DDEML (одновременно могут работать несколько копий одного и того же DDEML-приложения). Иными словами, в процессе регистрации библиотека DDEML присваивает копии приложения некоторый идентификатор, под которым она его "знает". Вы должны указывать полученный от функции идентификатор при вызове всех остальных функций библиотеки DDEML.
Перед вызовом функции DdeInitialize ваше приложение должно записать в двойное слово, адрес которого передается через первый параметр, нулевое значение.
Заметим, что идентификатор копии приложения, присвоенный в процессе регистрации, и идентификатор копии приложения, полученный через параметр функции WinMain - разные по смыслу (и по значению) идентификаторы.
Параметр pfnCallback представляет собой указатель на функцию обратного вызова, определенную приложением для обработки транзакций. Как сервер, так и клиент должны определить такую функцию. Функция обратного вызова вызывается системой DDEML и содержит в себе всю логику обработки транзакций, определенную вами при разработке приложения.
Если приложение вызывает функцию DdeInitialize несколько раз для многократной регистрации, каждый раз следует указывать отдельную функцию обратного вызова. Многократная регистрация вполне допустима, так как каждый раз библиотека DDEML будет создавать для себя новый идентификатор приложения. Такая методика используется при создании DLL-библиотек, работающих с DDEML. Обычным приложениям достаточно зарегистрировать себя один раз и, соответственно, определить одну функцию обратного вызова.
Через параметр afCmd передается двойное слово, каждый бит которого является флагом, определяющим режимы работы канала связи, а также влияющие на действия, выполняемые функцией DdeInitialize.
Последний параметр с именем ulRes зарезервирован и должен иметь нулевое значение.
Приведем фрагмент кода, выполняющего регистрацию сервера в библиотеке DDEML:
idInst = 0L; lpDdeSrProc = MakeProcInstance((FARPROC)DDEServerCallback, hInst); if(DdeInitialize((LPDWORD)&idInst, (PFNCALLBACK)lpDdeSrProc, APPCLASS_STANDARD, 0L)) { return FALSE; }
В этом фрагменте вначале создается переходник для функции обратного вызова, затем адрес этого переходника указывается во втором параметре функции DdeInitialize.
В случае успеха функция DdeInitialize возвращает нулевое значение. Для проверки можно также использовать константу DMLERR_NO_ERROR, определенную в файле ddeml.h. Если произошла ошибка, возвращается ненулевой код ошибки. Соответствующие константы определены в файле ddeml.h и имеют префикс имени DMLERR.
Немного о флагах, передаваемых через параметр afCmd.
Символические константы с префиксом имени APPCLASS позволяют задать класс приложения с точки зрения использования DDEML.
Класс APPCLASS_STANDARD предназначен для регистрации стандартного DDEML-приложения. Этот класс использован в приведенном выше фрагменте кода и в приложении DDEMLSR, исходные тексты которого вы увидите позже.
Класс APPCLASS_MONITOR предназначен для отладчиков и других приложений, управляющих работой системы DDEML. В качестве примера можно привести приложение DDESpy. Это приложение поставляется в составе Microsoft SDK for Windows 3.1 и предназначено для отладки DDE-приложений (и, разумеется, DDEML-приложений). В конце данной главы мы научим вас использовать приложение DDESpy для отладки созданных вами DDE-приложений.
Символические константы с префиксом имени APPCMD позволяют конкретизировать функции, выполняемые приложением, и экономить системные ресурсы. Если DDEML-приложение выполняет только функции клиента, следует указать флаг APPCMD_CLIENTONLY:
if(DdeInitialize((LPDWORD)&idInst, (PFNCALLBACK)lpDdeClProc, APPCMD_CLIENTONLY, 0L)) { return NULL; }
В простейших случаях можно ограничиться использованием класса APPCLASS_STANDARD при создании сервера DDEML и флага APPCMD_CLIENTONLY при создании клиента DDEML. Мы так и поступили в наших приложениях DDEMLSR и DDEMLCL, выполняющих, соответственно, функции сервера и клиента DDEML. Остальные флаги влияют на то, когда и зачем будет вызываться функция обратного вызова.
Если приложение больше не собирается работать с библиотекой DDEML, оно должно вызвать функцию DdeUninitialize, передав ей в качестве единственного параметра идентификатор копии приложения, полученный от функции DdeInitialize:
BOOL WINAPI DdeUninitialize(DWORD idInst);
SaveMark("marktext")
Эта макрокоманда сохраняет расположение текущего отображаемого раздела и файла в виде закладки или маркера, имеющего имя marktext.
Search()
Отображает диалоговую панель "Search", которая обычно появляется на экране, если нажать кнопку "Search" в окне Toolbar приложения winhelp.exe.
Сегменты инициализации
Инициализация виртуального драйвера выполняется в два этапа, причем на первом этапе она выполняется в реальном режиме работы процессора. Инициализация в реальном режиме выполняется до того, как создаются виртуальные машины.
Выполнив инициализацию в реальном режиме, виртуальный драйвер может разрешить или отменить свою дальнейшую загрузку в память, вернув то или иное значение.
Сегмент инициализации реального режима VXD_REAL_INIT объявляется с помощью макрокоманды VXD_REAL_INIT_SEG. Этот сегмент имеет имя _RTEXT и содержит процедуру инициализации виртуального драйвера в реальном режиме работы процессора, а также, возможно, данные, необходимые для инициализации.
Конец сегмента отмечается макрокомандой VXD_REAL_INIT_ENDS:
VXD_REAL_INIT_SEG RealInit proc near mov ax, Device_Load_Ok xor bx, bx xor si, si xor edx, edx ret RealInit endp VXD_REAL_INIT_ENDS
После того как процедура инициализации реального режима возвратит управление, сегмент VXD_REAL_INIT_SEG будет удален из памяти. Это логично, так как инициализация выполняется только один раз и соответствующая процедура (а также данные) больше не потребуются.
Если инициализация в реальном режиме выполнена успешно, начинается второй этап инициализации виртуального драйвера, который выполняется в защищенном режиме. На этом этапе вызываются процедуры инициализации защищенного режима, расположенные в сегменте VXD_ICODE с именем _ITEXT. Этот сегмент объявляется макрокомандой VXD_ICODE_SEG. Конец сегмента отмечается макрокомандой VXD_ICODE_ENDS:
VXD_ICODE_SEG BeginProc VXDSRV_Sys_Crit_Init clc ret EndProc VXDSRV_Sys_Crit_Init VXD_ICODE_ENDS
На этапе инициализации в защищенном режиме может также использоваться сегмент данных VXD_IDATA (называется также _IDATA). Начало этого сегмента отмечается макрокомандой VXD_IDATA_SEG, конец - макрокомандой VXD_IDATA_ENDS.
После полного завершения инициализации сегменты _ITEXT и _IDATA удаляются из памяти, так как они больше не потребуются.
Секция BAGGAGE
Разработчик справочной системы может добавить в hlp-файл (в его внутреннюю файловую систему) произвольные файлы данных, описав их в секции BAGGAGE файла проекта справочной системы. Это могут быть, например, видео- или звуковые данные, графические изображения и т. п.
Сервис для виртуального драйвера
Операционная система Windows обеспечивает мощную поддержку для виртуальных драйверов в виде функций (сервиса). Ваш виртуальный драйвер может пользоваться сервисом, предоставляемым другими виртуальными драйверами, в частности, драйверами ядра Windows.
Пользоваться сервисом достаточно просто - не сложнее, чем вызывать процедуры или программные прерывания.
Система управления виртуальными машинами VMM предоставляет сервис, вызываемый с помощью макрокоманды VMMcall:
mov ax, (Client_SI shl 8) + Client_DI VMMcall Map_Flat mov [CallbackBuf], eax
Все, что вам нужно сделать перед вызовом сервиса, это записать нужные значения в регистры или в стек, а затем вызвать сервис. Результат работы обычно передается в регистрах.
Если после имени сервиса указать параметры, они будут занесены макрокомандой в стек.
Для того чтобы воспользоваться сервисом, предоставляемым другими виртуальными драйверами, нужно использовать макрокоманду VXDcall, которая используется точно таким же образом, что и макрокоманда VMMcall.
Сервис, доступный виртуальным драйверам, слишком обширный, для того чтобы описать его в нашей книге достаточно подробно. За более детальным описанием вы можете обратиться к документации, поставляемой вместе с DDK, или к книге Д. Нортона "Writing Windows Device Drivers". Мы же ограничимся кратким перечислением основных возможностей сервиса. Впоследствии, при описании исходного текста драйвера VXDSRV, мы рассмотрим более подробно некоторые функции сервиса, использованные в этом драйвере.
Сервис, предоставляемый виртуальным драйвером
Приложения Windows, работающие в системной виртуальной машине, а также программы MS-DOS, запущенные в среде виртуальной машины MS-DOS, могут вызывать виртуальные драйверы для выполнения тех или иных операций.
Однако, прежде всего, необходимо получить адрес точки входа для вызова драйвера. Способ получения этого адреса одинаков как для приложений Windows, работающих в защищенном режиме, так и для программ MS-DOS, работающих на виртуальных машинах MS-DOS в виртуальном режиме процессора V86.
Приведем пример функции, возвращающей адрес точки входа для виртуального драйвера, идентификатор которого задан через параметр vxd_id:
VXDAPI vxdGetDeviceAPI(unsigned short vxd_id) { unsigned axreg, dxreg; asm push di asm push es asm mov ax, 0x1684 asm mov bx, vxd_id asm xor di, di asm mov es, di asm int 0x2f asm mov ax, di asm mov dx, es asm pop es asm pop di asm mov axreg, ax asm mov dxreg, dx return((VXDAPI)MAKELP(dxreg, axreg)); }
Как видно из примера, получение адреса точки входа сводится к вызову функции 1684h прерывания INT2Fh. Искомый адрес возвращается в регистрах ES:DI. Если драйвер с указанным идентификатором отсутствует в системе, возвращается нулевой адрес.
Учтите, что если адрес точки входа определяется из приложения Windows, когда процессор находится в защищенном режиме, адрес возвращается в формате <селектор:смещение>. Если же этот адрес определяется в виртуальной машине MS-DOS, он будет получен в формате <сегмент:смещение>.
После того как адрес точки входа определен, вызов виртуального драйвера выполняется следующим образом:
asm mov dx, sel asm mov cx, off asm mov si, bsel asm mov di, boff asm mov ax, vxdapiRegisterWnd (*vxdApi)();
Перед вызовом необходимо загрузить все нужные для выполнения данной функции регистры. Номер выполняемой функции следует указать в регистре AX.
Что происходит в виртуальном драйвере, когда его вызывают подобным образом?
Если вы помните, в седьмом и восьмом параметрах макрокоманды определения драйвера Declare_Virtual_Device указываются адреса процедур, соответственно, для виртуального и защищенного режима, которые вызываются при обращении к драйверу. Именно эти процедуры и будут вызваны в нашем случае. Они могут проверить содержимое регистра AX на момент вызова виртуального драйвера и в соответствии с ним сделать, например, переход по таблице адресов функций, определенных внутри виртуального драйвера.
Однако не следует думать, что соответствующая функция просто проверяет текущее содержимое регистра AX. Дело в том, что в мультизадачной среде одновременно работают несколько виртуальных машин, каждая из которых имеет свой виртуальный процессор и свой набор регистров. При вызове виртуального драйвера из виртуальной машины регистры процессора не соответствуют состоянию виртуальной машины на момент вызова. Тем не менее, виртуальный драйвер может легко получить доступ к области памяти, в которую автоматически переписывается содержимое всех регистров при переключении виртуальных машин (а также при вызове виртуального драйвера).
Расскажем об этом подробнее.
Сервис системы управления виртуальными машинами
Система управления виртуальными машинами VMM предоставляет наиболее мощный сервис. Простое перечисление функций, доступных в рамках этого сервиса займет больше дюжины страниц. Рассмотрим основные группы функций.
Функции управления памятью
Функции управления памятью позволяют работать с оперативной памятью на уровне страниц. Драйвер может работать как с виртуальной, так и с физической памятью, выполняя отображение памяти, заказ страниц памяти и т. д.
Предусмотрены функции для работы с глобальной и локальной таблицей дескрипторов. Виртуальный драйвер может создать и удалить дескриптор как в глобальной таблице дескрипторов GDT, так и в локальной таблице дескрипторов LDT.
Есть функции, предназначенные для получения блоков памяти из кучи, освобождения таких блоков памяти и изменения их размера.
Предусмотрены многочисленные средства преобразования адресов для обеспечения возможности адресации памяти, принадлежащей виртуальной машине, из процедур виртуального драйвера.
Функции прослеживания команд ввода/вывода
Как известно, процессоры модели 80386 и более старших моделей позволяют прослеживать обращение программ к портам ввода/вывода. Для этого необходимо создать карту доступа, в которой каждый бит отвечает за разрешение или запрещение доступа к своему порту.
Виртуальный драйвер с помощью специального набора функций из сервиса VMM может управлять этим процессом, разрешая или запрещая доступ к портам ввода/вывода приложениям и программам MS-DOS, работающим на виртуальных машинах MS-DOS.
Возможно также симулирование выполнения команды ввода/вывода. При симулировании драйвер вызывает функцию Simulate_IO, в результате чего виртуальная машина "принудительно" выполняет операцию ввода/вывода.
Управление прерываниями и вызовом функций обратного вызова
Функции этой группы позволяют запрещать или разрешать прерывания для виртуальной машины, устанавливать обработчики векторов прерываний, добавлять обработчики прерываний в уже имеющуюся цепочку обработчиков.
Виртуальный драйвер может вызвать функцию или обработчик прерывания, расположенные в сегменте кода виртуальной машины.
Для обеспечения возможности передачи параметров предусмотрены функции, записывающие параметры в стек виртуальной машины и извлекающие их оттуда.
Мы использовали некоторые функции из этой группы в драйвере VXDSRV, так что вы сможете познакомиться с ними на практике.
Функции первичного планировщика
Функции первичного планировщика позволяют управлять выполнением виртуальных машин, изменяя их приоритет, а также синхронизируя работу виртуальных машин, запущенных одновременно.
Пользуясь этим сервисом, виртуальный драйвер может планировать вызов функции обратного вызова для конкретной виртуальной машины, указанной своим идентификатором. Благодаря этому возможно, например, инициировать из виртуальной машины MS-DOS вызов функции, расположенной в системной виртуальной машине.
Есть функции для работы с критической секцией. Виртуальная машина может войти в критическую секцию и выполнять какую-либо работу в монопольном режиме, с гарантией того, что эта работа не будет прервана другими виртуальными машинами.
Функции вторичного планировщика
Функции этой группы позволяют изменять параметры выполнения виртуальной машины, имеющие отношение к приоритету использования времени центрального процессора.
Функции планирования событий
С помощью функций, входящих в данную группу, можно запланировать вызов функции, расположенной в памяти любой заданной виртуальной машины, либо независимой от конкретной виртуальной машины.
Функции таймера
Эти функции, как нетрудно догадаться, предназначены для работы со временем. Виртуальный драйвер может задать интервал времени для любой виртуальной машины, по истечению которого будет вызвана указанная функция обратного вызова.
Есть также средства определения текущего системного времени и времени работы заданной виртуальной машины.
Обработка сбоев и прерываний
Виртуальный драйвер может взять на себя обработку немаскируемого прерывания, нарушения защиты, сбоев во время работы виртуальной машины MS-DOS, системной виртуальной машины, а также системы управления виртуальными машинами VMM.
Получение справочной информации
Набор справочных функций позволяет виртуальному драйверу узнать идентификатор виртуальной машины, из которой его вызвали, идентификатор системной виртуальной машины. Можно также получить список идентификаторов всех активных виртуальных машин и другую информацию о виртуальных машинах.
Большой набор функций предназначен для определения конфигурации системы и анализа файлов конфигурации Windows. Эти функции предназначены для использования на стадии инициализации виртуального драйвера.
Работа со связанными списками
Так как виртуальные драйверы составляются на языке ассемблера, работа со сложными структурами данных, такими, как связанные списки, может отнять много времени у программиста. К счастью, имеется набор функций, позволяющих легко выполнять всю работу по созданию, изменению, просмотру и удалению списков
Обработка ошибок
В этой группе есть функции, с помощью которых можно вызвать аварийное завершение работы текущей виртуальной машины и всей системы в целом.