Сервис виртуального драйвера контроллера прерываний
В составе виртуальных драйверов Windows имеется виртуальный драйвер контроллера прерываний VPICD, виртуализирующий аппаратуру контроллера для использования виртуальными машинами.
Сервис драйвера VPICD позволяет выполнять операции с контроллером прерываний, такие как маскирование и размаскирование прерываний, обработка конца прерывания, обработка аппаратного прерывания и т. п.
Если ваш виртуальный драйвер должен обрабатывать прерывания от нестандартной аппаратуры, вам следует воспользоваться сервисом драйвера VPICD.
Сервис виртуального драйвера контроллера прямого доступа к памяти
Виртуальный драйвер контроллера прямого доступа к памяти VDMAD виртуализует аппаратуру контроллера для ее совместного использования виртуальными машинами.
Никакая виртуальная машина не может программировать регистры контроллера прямого доступа к памяти (ПДП), так как соответствующие порты ввода/вывода зарезервированы виртуальным драйвером VDMAD.
Сервис, предоставляемый драйвером VDMAD, позволяет виртуальным машинам использовать каналы ПДП. В распоряжение виртуальной машины предоставляется виртуальный контроллер ПДП, который программируется с помощью сервиса VDMAD аналогично физическому контроллеру ПДП.
Сервис виртуального драйвера SHELL
Виртуальный драйвер Shell экспортирует две очень полезные функции, с помощью которых можно вывести на экран компьютера обычную и системную модальную диалоговую панель с сообщением. Эти функции напоминают функцию MessageBox из программного интерфейса Windows и называются SHELL_Message и SHELL_SYSMODAL_Message.
Виртуальный драйвер может использовать эти диалоговые панели для отображения сообщений об ошибках или критических ситуациях, а также для получения ответа от пользователя.
Сервис виртуального драйвера жесткого диска
Сервис виртуального драйвера жесткого диска позволяет виртуальным драйверам выполнять чтение и запись одного или нескольких секторов логического диска. Для соответствующих функций необходимо задать номер начального сектора и количество секторов, участвующих в операции чтения или записи.
SetContents("filename", "contextnumber")
С помощью этой макрокоманды вы можете указать, что раздел, имеющий номер контекста contextnumber, должен использоваться в качестве оглавления справочной системы, расположенной в hlp-файле с именем filename.
Параметры:
Параметр | Описание |
filename | Имя hlp-файла |
contextnumber | Номер раздела в указанном hlp-файле. Этот номер должен быть определен в разделе MAP файла проекта *.hpj справочной системы |
SetHelpOnFile("filename")
Вы можете заместить справочную систему, содержащую сведения об использовании приложения winhelp.exe (расположенную в файле winhelp.hlp) на свою собственную. Для этого в качестве параметра макрокоманде SetHelpOnFile следует указать имя hlp-файла, замещающего файл winhelp.hlp.
Системная критическая инициализация
На этапе системной критической инициализации процедура VXDSRV_Sys_Crit_Init (которая вызывается для обработки системного сообщения Sys_Crit_Init) устанавливает фильтр для прерывания INT 21h. Для этого используется функция Hook_V86_Int_Chain, входящая в сервис VMM.
Перед вызовом функции Hook_V86_Int_Chain следует записать в регистр EAX номер прерывания, для которого устанавливается фильтр, а в регистр ESI - FLAT-адрес процедуры фильтра.
В нашем драйвере определена процедура V86_Int21_Handler, которая встраивается в начало цепочки других фильтров прерывания INT 21h. Мы рассмотрим эту процедуру немного позже.
Сообщение EWM_ASKPALETTE
Приложение winhelp.exe посылает сообщение EWM_ASKPALETTE (с кодом 0x706c) функции встроенного окна для того чтобы получить информацию об используемой цветовой палитре.
Обработчик сообщения EWM_ASKPALETTE должен вернуть идентификатор палитры, используемой встроенным окном.
Сообщение EWM_FINDNEWPALETTE
Сообщение EWM_FINDNEWPALETTE (с кодом 0x706d) нигде не документировано, однако оно встречается в *.h файлах примеров, поставляемых в составе средств разработки справочных систем на базе Microsoft Windows Help.
Сообщение EWM_QUERYSIZE
Функция встроенного окна получает сообщение EWM_QUERYSIZE (с кодом 0x706b) от приложения winhelp.exe.
Параметр wParam содержит идентификатор контекста отображения для окна. Через параметр lParam передается указатель на структуру POINT.
Основная задача обработчика сообщения EWM_QUERYSIZE заключается в том, чтобы записать в поля x и y структуры POINT, соответственно, ширину и высоту встроенного окна. Таким образом, встроенное окно должно сообщить приложению winhelp.exe свои размеры.
Сообщение EWM_RENDER
Содержимое раздела справочной системы может быть скопировано в Clipboard или распечатано на принтере. В первом случае приложению winhelp.exe требуется получить текстовое представление содержимого встроенного окна, во втором - графическое в виде битового изображения.
Для получения текстового и графического представления приложение winhelp.exe посылает в функцию встроенного окна сообщение EWM_RENDER (с кодом 0x706a). Если параметр wParam этого сообщения равен константе CF_TEXT, обработчик должен вернуть текстовое представление, а если CF_BITMAP - графическое в виде битового изображения.
Процедура возврата текстового представления достаточно проста. Она заключается в том, что обработчик сообщения EWM_RENDER создает и фиксирует глобальный блок памяти. Затем он записывает в этот блок памяти текстовую строку представления содержимого встроенного окна, расфиксирует блок памяти и возвращает идентификатор блока памяти. Освобождение блока памяти выполняет приложение winhelp.exe, вам не нужно беспокоиться по этому поводу.
Если требуется вернуть графическое представление, параметр lParam сообщения EWM_RENDER содержит указатель на структуру следующего вида (не описана в файле windows.h):
typedef struct tagRenderInfo { RECT rc; HDC hdc; } RENDERINFO;
Поле hdc этой структуры нужно использовать для создания битового изображения функцией CreateCompatibleBitmap.
Что же касается размеров битового изображения, их следует получить при помощи сообщения EWM_QUERYSIZE.
Сообщение WM_CREATE
Сообщение WM_CREATE может попадать в функцию встроенного окна несколько раз за время одного сеанса работы со справочной системой. Если пользователь отображает раздел, содержащий встроенное окно, оно создается. Затем это окно может быть уничтожено при переходе к другому разделу и создано заново при повторном отображении того же раздела.
Когда функция встроенного окна получает сообщение WM_CREATE, параметр lParam содержит указатель на структуру CREATESTRUCT. Поле lpCreateParams этой структуры, в свою очередь, содержит указатель на структуру EWDATA следующего вида (не описана в файле windows.h):
typedef struct tagCreateInfo { short idMajVersion; // ст. версия winhelp.exe short idMinVersion; // мл. версия winhelp.exe LPSTR szFileName; // путь к hlp-файлу LPSTR szAuthorData; // строка параметров команды ewl HANDLE hfs; // идентификатор файловой системы DWORD coFore; // основной цвет главного окна winhelp.exe DWORD coBack; // фоновый цвет главного окна winhelp.exe } EWDATA;
Microsoft рекомендует проверить поля idMajVersion и idMinVersion (старший и младший номер версии справочной системы winhelp.exe) на равенство нулю. Если содержимое этих полей не равно нулю, работоспособность вашей DLL-библиотеки с этой версией winhelp.exe не гарантируется.
Поле szFileName содержит указатель на текстовую строку, содержащую путь к hlp-файлу. Если эта строка вам нужна, ее надо скопировать в отдельный буфер на этапе обработки сообщения WM_CREATE.
Через поле szAuthorData передается указатель на текстовую строку, указанную при вставке встроенного окна в раздел, т. е. строка param в команде ewl, ewc или ewr. Если эта строка нужна, ее также следует сохранить на этапе обработки сообщения WM_CREATE.
Через параметр hfs передается идентификатор файловой системы hlp-файла, который может пригодиться при выполнении операций с данными, описанными в секции BAGGAGE файла проекта справочной системы. Подробное рассмотрение этих операций выходит за рамки нашей книге, однако несколько слов по этому поводу мы скажем в конце текущей главы.
И, наконец, через параметры coFore и coBack передается основной и фоновый цвет главного окна winhelp.exe, соответственно. Вы можете использовать эти значения для раскраски создаваемого встроенного окна.
Так как отображение встроенного окна выполняется приложением winhelp.exe, ваше приложение не должно вызывать для этого окна функцию ShowWindow или назначать для него стиль WS_VISIBLE.
Сообщения для драйвера
При инициализации загружаемый драйвер получает последовательно три сообщения: DRV_LOAD, DRV_ENABLE и DRV_OPEN (именно в этом порядке).
Сообщение DRV_LOAD, как и можно было бы ожидать, посылается тогда, когда драйвер загружен в память. Для продолжения загрузки драйвера в память соответствующий обработчик должен вернуть значение1L.
Сообщение DRV_ENABLE в расширенном режиме работы Windows посылается один раз в процессе инициализации. В стандартном режиме пользователь может переключаться время от времени на работу с программами MS-DOS, при этом драйвер получает сообщение DRV_DISABLE. Когда же пользователь вновь возвращается к работе с приложениями Windows, драйвер получает сообщение DRV_ENABLE.
Последним при открытии драйвер получает сообщение DRV_OPEN, в ответ на которое обработчик должен вернуть значение 1L (в противном случае операция открытия драйвера не будет выполнена).
При закрытии драйвера функция DriverProc получит последовательно сообщения DRV_CLOSE, DRV_DISABLE и DRV_FREE. Обработчики этих сообщений должны освободить ресурсы, занимаемые драйвером.
Когда пользователь выполняет установку загружаемого драйвера с помощью приложения Control Panel, функция DriverProc получает сообщение DRV_INSTALL. Обработчик этого сообщения может выполнить необходимые инициализирующие действия, например, создание разделов в файле system.ini или в других конфигурационных файлах.
Далее драйвер получает сообщение DRV_QUERYCONFIGURE. С помощью этого сообщения приложение Control Panel определяет, поддерживает ли данный драйвер функцию настройки конфигурации. Если такая функция поддерживается, обработчик сообщения DRV_QUERYCONFIGURE должен вернуть значение 1L, в противном случае - значение 0L.
При необходимости изменения конфигурации (и если драйвер поддерживает такую возможность) драйвер получает сообщение DRV_CONFIGURE. Это сообщение посылается при установке драйвера, а также когда пользователь изменяет конфигурацию драйвера с помощью приложения Control Panel.
В ответ на сообщение DRV_CONFIGURE драйвер обычно выводит диалоговую панель, с помощью которой пользователь может выполнить настройку параметров драйвера.
В любой момент времени загружаемый драйвер может быть удален из системы. Такая возможность предусмотрена в приложении Control Panel. В этом случае драйвер получит сообщение DRV_REMOVE, в ответ на которое он может внести соответствующие изменения в файлы конфигурации.
Загружаемый драйвер может получать и другие сообщения, которые мы не будем рассматривать для экономии места. Полное описание вы можете найти в документации, которая поставляется вместе с SDK (именно SDK, а не DDK, как это можно было бы ожидать).
Для организации взаимодействия между приложением и загружаемым драйвером можно использовать пользовательские сообщения, код которых должен быть больше DRV_RESERVED, но меньше DRV_USER. Эти две константы описаны в файле windows.h.
Сообщения для встроенного окна
Функция встроенного окна ведет себя как дочернее окно главного окна приложения winhelp.exe (или вторичного окна). В частности, при создании функция окна получает сообщение WM_CREATE, а при уничтожении - WM_DESTROY. Есть также ряд сообщений, специфических для встроенного окна.
Создание файла проекта справочной системы
Итак, на данный момент вы создали (или, что лучше и быстрее, переписали с дискеты) файл hlpfile.rtf, содержащий исходный текст разделов справочной системы. Следующим шагом будет подготовка файла проекта hlpfile.hpj, необходимого для работы компилятора Help Compiler.
Готовый файл hlpfile.hpj вы можете найти на дискете, которая продается вместе с книгой. Если дискеты нет, наберите файл самостоятельно, пользуясь листингом 4.1.
Для набора файла проекта вы можете выбрать любой текстовый редактор, сохраняющий файл без шрифтового оформления. Подойдет редактор Notepad или редактор, входящий в систему разработки Borland C++ for Windows. Можно использовать даже встроенный в Norton Commander текстовый редактор или аналогичное средство MS-DOS. И пусть вас не смущает, что вы не сможете использовать символы кириллицы из-за различий в кодировке для MS-DOS и Windows, так как... вы не сможете их использовать в любом случае. В файле проекта символы кириллицы недопустимы.
Создание и уничтожение канала
Последнее, что нужно сделать перед началом передачи данных по каналу DDEML, - создать канал связи (conversation). Теперь мы к этому вполне готовы.
Кто является инициатором создания канала?
Канал связи между клиентом и сервером создается всегда по инициативе клиента. После регистрации в библиотеке DDEML клиент вызывает функцию DdeConnect, создающую канал связи:
HCONV WINAPI DdeConnect( DWORD idInst, // идентификатор приложения HSZ hszService, // идентификатор строки сервиса HSZ hszTopic, // идентификатор строки раздела CONVCONTEXT FAR* pCC); // адрес данных контекста
Через параметр idInst приложение должно передать идентификатор, полученный на этапе регистрации приложения в библиотеки DDEML функцией DdeInitialize.
Параметры hszService и hszTopic предназначены для передачи идентификаторов строк, содержащих, соответственно, имена сервиса и раздела. Эти идентификаторы были получены нами ранее при помощи функции DdeCreateStringHandle.
Последний параметр - указатель на структуру типа CONVCONTEXT. Эта структура используется для указания информации о национальном языке и кодовой странице, соответствующей передаваемым данным. В простейшем случае для данного параметра можно указать значение NULL, при этом будет использована кодовая страница CP_WINANSI (что приемлемо в подавляющем большинстве случаев).
В нашем приложении DDEMLCL для создания канала с сервером используется следующий фрагмент кода:
hConv = DdeConnect(idInst, hszService, hszTopic, (PCONVCONTEXT)NULL);
Идентификатор канала, полученный от функции DdeConnect, следует сохранить для обеспечения возможности получения данных от сервера.
Что же происходит, когда клиент создает канал, вызывая функцию DdeConnect?
Прежде всего, библиотека DDEML посылает транзакцию с кодом XTYP_CONNECT всем активным серверам, которые зарегистрировали сервис, указанный во втором параметре функции DdeConnect.
Для транзакции XTYP_CONNECT параметры функции обратного вызова принимают следующие значения:
Параметр | Значение |
hsz1 | Идентификатор строки, содержащей имя раздела |
hsz2 | Идентификатор строки, содержащей имя сервиса |
dwData1 | Адрес структуры CONVCONTEXT |
dwData2 | Если значение равно TRUE, данная копия приложения является одновременно и клиентом, и сервером. Если же значение равно FALSE, клиент и сервер являются разными приложениями или разными копиями приложения |
Обработчик транзакции XTYP_CONNECT, расположенный в функции обратного вызова сервера, должен проверить сервис и раздел, идентификаторы которых переданы через параметры функции. Если сервер поддерживает этот сервис и раздел, можно создавать канал. В таком случае функция обратного вызова должна вернуть значение TRUE. Иначе следует вернуть FALSE.
Приведенный ниже фрагмент кода, взятый из нашего приложения DDEMLSR, проверяет только сервис (так как в нашем приложении определен только один сервис и один раздел):
case XTYP_CONNECT: { if((HSZ)hsz2==(HSZ)hszService) return((HDDEDATA)TRUE); else return((HDDEDATA)FALSE); }
Заметим, что мы сравниваем не строки, содержащие имя сервиса, а идентификаторы, так как для одинаковых строк в данном случае будут созданы одинаковые идентификаторы.
В случае успешного создания канала сервер получает от системы DDEML транзакцию с кодом XTYP_CONNECT_CONFIRM. При обработке этой транзакции сервер может сохранить идентификатор созданного канала (который передается функции обратного вызова через параметр hConv) для дальнейшего использования.
Приведем назначение параметров функции обратного вызова для транзакции XTYP_CONNECT_CONFIRM:
Параметр | Значение |
hsz1 | Идентификатор строки, содержащей имя раздела |
hsz2 | Идентификатор строки, содержащей имя сервиса |
dwData2 | Если значение равно TRUE, данная копия приложения является одновременно и клиентом, и сервером. Если же значение равно FALSE, клиент и сервер являются разными приложениями или разными копиями приложения |
BOOL WINAPI DdeDisconnect(HCONV hConv);
В качестве единственного параметра этой функции передается идентификатор уничтожаемого канала.
В процессе удаления канала "партнер" приложения, выступившего инициатором удаления канала, получает транзакцию XTYP_DISCONNECT. Соответствующий обработчик может при необходимости выполнить действия по освобождению ресурсов, заказанных приложением для работы с данным каналом связи.
Приложение DDEMLSR обрабатывает транзакцию XTYP_DISCONNECT следующим образом:
case XTYP_DISCONNECT: { hConvApp = NULL; break; }
Все, что делает этот фрагмент кода - это запись нулевого значения в идентификатор не существующего больше канала связи.
Создание и уничтожение окна Document Window
Последнее, о чем мы вам расскажем, перед тем как перейти к рассмотрению исходных текстов готового MDI-приложения, это процесс создания окна Document Window. В отличие от всех других рассмотренных нами окон, окно Document Window не создается функцией CreateWindow. Более того, окно Document Window нельзя создать этой функцией.
Для того чтобы создать окно Document Window, следует послать сообщение WM_MDICREATE окну Client Window при помощи функции SendMessage:
MDICREATESTRUCT mdics;
mdics.szClass = szChildClassName; // класс окна mdics.szTitle = "MDI Child Window"; // заголовок окна mdics.hOwner = hInst; // идентификатор приложения mdics.x = CW_USEDEFAULT; // размеры окна Document Window mdics.y = CW_USEDEFAULT; mdics.cx = CW_USEDEFAULT; mdics.cy = CW_USEDEFAULT; mdics.style = 0; // дополнительные стили mdics.lParam = NULL; // 32-битное значение
hwndChild = (HWND)SendMessage(hwndClient, WM_MDICREATE, 0, (LPARAM)&mdics);
Через последний параметр функции SendMessage передается указатель на заполненную структуру MDICREATESTRUCT:
typedef struct tagMDICREATESTRUCT { LPCSTR szClass; LPCSTR szTitle; HINSTANCE hOwner; int x; int y; int cx; int cy; DWORD style; LPARAM lParam; } MDICREATESTRUCT;
Поля этой структуры определяют такие характеристики окна Document Window, как класс окна (поле szClass), заголовок окна (szTitle), размеры и расположение (x, y, cx, cy), стиль окна (style) и произвольное 32-битовое значение lParam, которое может быть проанализировано функцией окна Document Window при получении сообщения WM_CREATE. В поле hOwner при инициализации структуры нужно также указать идентификатор приложения.
В целом назначение этих полей аналогично назначению параметров функции CreateWindow, так как, в конечном счете, именно с помощью этой функции Windows (но не приложение) создает окно Document Window.
Остановимся на поле style, которое используется для определения стиля окна Document Window. Если при создании окна Client Window был использован стиль MDIS_ALLCHILDSTYLES, в этом поле вы можете указать любой стиль, разрешенный для функции CreateWindow.
В противном случае вы должны ограничиться следующими стилями:
WS_MINIMIZE (окно Document Window будет создано в минимизированном состоянии, т. е. в виде пиктограммы);
WS_MAXIMIZE (размеры окна Document Window сразу после создания будут увеличены до максимальных пределов);
WS_HSCROLL (окно Document Window будет иметь горизонтальную полосу просмотра), WS_VSCROLL (окно Document Window будет иметь вертикальную полосу просмотра).
В любом случае для окна Document Window следует указать стили WS_CHILD и WS_CLIPSIBLINGS.
Для уничтожения окна Document Window приложение должно послать окну Client Window сообщение:
SendMessage(hwndClient, WM_MDIDESTROY, hwndDoc, 0l);
Через третий параметр следует передать функции SendMessage идентификатор уничтожаемого окна Document Window.
Создание исходного текста для файла hlpfile.hlp
Закрепим полученные знания на практике. Нашей задачей будет создание разделов простейшей справочной системы.
Запустите текстовый процессор Microsoft Word for Windows. Вы можете использовать версию 2.0 или 6.0, но мы будем рассматривать приемы использования Microsoft Word for Windows версии 2.0.
Если вы приобрели дискету, которая продается вместе с книгой, загрузите из каталога hlpfile файл hlpfile.rtf, содержащий готовый исходный текст справочной системы (рис. 4.17). Если у вас нет дискеты, вам придется набрать исходный текст самостоятельно. Рассмотрим этот процесс.
Вначале создайте новый документ. Сразу после создания документ будет содержать одну строку, имеющую стиль Normal. Наберите в этой строке заголовок первого раздела, а именно, раздела оглавления справочной системы. На рис. 4.17 это строка "Текстовый редактор".
Выберите шрифтовое оформление заголовка. Учтите, что приложение winhelp.exe при отображении текста в системе пользователя подберет наиболее подходящий шрифт из числа установленных шрифтов. Для достижения максимального соответствия старайтесь выбирать только те шрифты, которые входят в стандартную поставку Windows. При этом следует также учесть, что не во всех шрифтах есть символы кириллицы.
После завершения оформления выделите весь первый параграф и в списке стилей выберите стиль "heading 1", изменив его по вашему образцу. Теперь вы сможете использовать этот стиль для оформления всех заголовков справочной системы и тогда все заголовки будут выглядеть одинаково.
Если вам нужно, чтобы в процессе просмотра раздела заголовок всегда оставался на видном месте, задайте для стиля "heading 1" атрибут оформления параграфа "Keep With Next" (располагать строки параграфа на одной странице со следующей строкой). В этом случае в верхней части окна просмотра раздела образуется дополнительное окно, содержащее заголовок.
Рис. 4.17. Первые два раздела справочной системы
Для каждого раздела справочной системы необходимо определить несколько атрибутов, перечисленных выше.
Для того чтобы задать контекст, выполните следующие шаги:
расположите текстовый курсор в начале строки заголовка (в нашем случае курсор должен находится в начале первой строки)
вставьте подстрочную сноску (footnote) с идентификатором #
наберите в качестве текста сноски строку контекста
Затем, после определения контекста, укажем заголовок раздела, под которым он будет появляться при поиске (для поиска используется кнопка "Search", расположенная в окне Toolbar приложения winhelp.exe).
Заголовок раздела задается точно также, как и строка контекста, но в качестве идентификатора сноски следует использовать символ $.
Задайте список ключевых слов, по которым пользователь сможет найти раздел. Список ключевых слов задается как сноска с идентификатором K. Вы можете задать несколько ключевых слов, разделив их символом точка с запятой.
В заключение задайте номер последовательности просмотра как сноску с идентификатором +, указав строку "cont:010".
Окно сносок для первого и второго разделов должно выглядеть следующим образом:
$ Редактор текста Small Edit # cont + cont:010 K оглавление $ Описание команд # cmd + cont:020 K команды
Аналогичным образом подготовьте остальные разделы справочной системы (рис. 4.18 и 4.19).
Рис. 4.18. Третий, четвертый и пятый разделы справочной системы
Рис. 4.19. Шестой, седьмой, восьмой и девятый разделы справочной системы
В окне редактирования сносок для разделов с третьего по девятый должны быть определены следующие сноски:
$ Использование клавиатуры # key + cont:030 K клавиатура $ Работа с мышью # mouse + cont:040 K мышь $ Графические изображения # graphics K графика;битовые изображения $ Меню File # file + cmd:010 K File; загрузка документа $ Меню Edit # edit + cmd:020 K edit; редактирование $ Меню View # view + cmd:030 K view $ Отсутствующий раздел # missing
Подготовив текст, сохраните его в формате RTF. Для этого из меню "File" выберите строку "Save as..." и в появившейся диалоговой панели в списке "Save File as Type" выберите строку "Rich Text Format (*.rtf)".
Затем вы можете сохранить файл и в естественном формате Word for Windows, хотя это и не обязательно.
Создание перекрестных ссылок
Справочная система, созданная с использованием компилятора Help Compiler, имеет вид гипертекста, состоящего из разделов, связанных между собой ссылками. Ссылки создаются непосредственно в тексте раздела, для чего используется соответствующее шрифтовое оформление - перечеркнутый или двукратно подчеркнутый текст, однократно подчеркнутый текст и скрытый текст.
Любая ссылка состоит из двух частей. Первая часть - это текст, который виден пользователю и отображается, как правило, зеленым цветом с подчеркиванием сплошной или пунктирной линией. Для выполнения перехода пользователь должен сделать щелчок левой клавишей мыши по выделенному таким образом тексту. Вторая часть не видна пользователю. Это строка контекста раздела, на который выполняется переход.
Если выполняется переход на обычный раздел, в исходном тексте справочной системы первая часть ссылки оформляется как перечеркнутая или подчеркнутая двойной чертой строка текста. Если же выполняется переход во временное окно (рис. 4.3), используется однократное подчеркивание.
Строка контекста всегда оформляется скрытым текстом.
На рис. 4.16 показан фрагмент окна текстового процессора Word for Windows, в котором видны две ссылки.
Рис. 4.16. Ссылки на временное окно и обычный раздел
Строка "графических изображений" содержит ссылку на раздел с контекстом "graphics". Обратите внимание, что первая часть ссылки подчеркнута одной чертой, а вторая оформлена скрытым текстом (показан как подчеркнутый линией, состоящей из точек). Так как строка "графических изображений" подчеркнута один раз, это ссылка на временное окно.
Вторая ссылка состоит из строк "Описание команд" и "cmd". Здесь мы видим ссылку на обычный раздел, имеющий контекст "cmd".
Подготавливая ссылку, следите за тем, чтобы между строкой и контекстом не было символов пробела или каких-либо других посторонних символов. Символ конца параграфа не следует оформлять как невидимый, в противном случае такая строка сольется со следующей.
Для того чтобы выделить строку текста подчеркиванием или оформить как скрытый текст, используйте строку "Character" меню "Format" текстового процессора Word for Windows.
Создание справочной системы
Для начала вы вместе с нами сделаете простейшую справочную систему (вернее, ее заготовку) для гипотетического текстового редактора. При этом нашей основной целью будет научить вас создавать исходный текст справочной системы, файл проекта справочной системы и запускать компилятор Help Compiler.
Создание встроенного окна
Процедура вставки встроенного окна в исходный текст раздела справочной системы напоминает аналогичную процедуру для битовых изображений, хранящихся в bmp-файлайх. Все, что для этого нужно, это включить в исходный текст ссылку следующего вида:
{ewl dllname, wndclass, param}
Как и при вставке bmp-файлов, в зависимости от выбранной команды будет использован один из трех способов взаимного расположения текста и встроенного окна в окне раздела:
Команда | Способ расположения |
ewc | Встроенное окно будет вставлено в строку как символ |
ewl | Встроенное окно выравнивается по левой границе, текст располагается справа |
ewr | Встроенное окно выравнивается по правой границе, текст располагается слева |
Параметр dllname - это имя DLL-библиотеки, которую требуется разработать специально для обслуживания встроенного окна. При инициализации функция LibMain этой библиотеки должна зарегистрировать класс окна wndclass, указанный во втором операторе команды вставки встроенного окна.
Функция окна, соответствующая классу wndclass и определенная в библиотеке dllname будет получать и обрабатывать сообщения, предназначенные для встроенного окна.
Через параметр param можно передать любую текстовую строку, которая будет доступна на этапе создания встроенного окна при обработке сообщения WM_CREATE.
Создание вторичного окна
При создании ссылки на раздел можно указать, что его содержимое должно отображаться во вторичном окне. Для этого после строки контекста в тексте ссылки необходимо расположить символ > и имя окна:
SmartPadedit_wnd>edwindow
Список системных управляющих сообщений
В этом разделе мы приведем список основных системных управляющих сообщений с кратким описанием, сгруппированных по выполняемым функциям.
Список встроенных макрокоманд
В этом разделе мы приведем краткий справочник по встроенным макрокомандам справочной системы Windows.
Справочная система HELPMORE.HLP
Для иллюстрации применения стандартных и дополнительных макрокоманд, функции WinHelp, а также малоизвестных и редко используемых, но полезных встроенных окон (Embedded Windows), мы создали справочную систему helpmore.hlp (рис. 4.21). При этом мы взяли за основу подробно описанную нами ранее справочную систему hlpfile.hlp.
Рис. 4.21. Справочная система helpmore.hlp
Обратите внимание на окно Toolbar. В нем появилась новая кнопка "Calc". Если нажать на нее, будет запущено приложение calc.exe. Эта возможность реализована с помощью стандартных макрокоманд.
Выберите мышью строку "Вызов функции MsgBox из DLL". На экране появится сообщение (рис. 4.22).
Рис. 4.22. Сообщение, которое выводится при помощи специально созданной макрокоманды
Для вывода такого сообщения мы расширили стандартный набор макрокоманд приложения winhelp.exe, добавив собственную макрокоманду MsgBox, реализованную как функцию библиотеки helpmore.dll.
Если выбрать строку "Вторичное окно", на экране появится еще одно окно, которое живет самостоятельно, вне зависимости от состояния главного окна приложения winhelp.exe (рис. 4.23).
Рис. 4.23. Вторичное окно
Однако наибольший интерес представляет собой небольшое прямоугольное окно, внутри которого в цифровом виде отображается текущее время! Это так называемое встроенное окно. Мы использовали его для часов, однако в таком окне можно отображать все что угодно, например, анимацию или даже видеофильмы.
Вы можете сделать щелчок левой клавишей мыши внутри встроенного окна, при этом на экране появится диалоговая панель с предложением запустить "настоящие" часы clock.exe (рис. 4.24).
Рис. 4.24. Пользователю предлагается запустить приложение clock.exe
В случае утвердительного ответа приложение clock.exe будет запущено, вслед за чем на экране появится раздел справочной системы с информацией о часах. Этот раздел будет показан во временном окне, и в нем также отображается текущее время (рис. 4.25). Таким образом, мы использовали временное окно еще и как нестандартное средство для организации гипертекстовой ссылки.
Рис. 4.25. Гипертекстовая ссылка из встроенного окна
Для экспериментов со справочной системой hlpmore.hlp лучше всего использовать ее исходные тексты и другие файлы, расположенные на дискете, продаваемой вместе с книгой в каталоге hlpmore. Для тех, кто не смог приобрести дискету, мы приведем содержимое первого раздела справочной системы (раздела оглавления) и еще одного дополнительного раздела, которого нет в предыдущей описанной нами справочной системе helpfile.hlp.
На рис. 4.26 изображено оформление раздела оглавления.
Рис. 4.26. Раздел содержания справочной системы hlpmore.hlp
Вызов созданной нами макрокоманды MsgBox выполняется следующим образом (в исходном *.rtf файле правая часть строки оформлена скрытым текстом):
Вызов функции MsgBox из DLL!MsgBox(hwndApp,"Сообщение из DLL")
Макрокоманда получает два параметра, первый из которых является внутренней глобальной переменной hwndApp (идентификатор главного окна приложения winhelp.exe), а второй представляет собой произвольную текстовую строку.
Для отображения содержимого во вторичном окне использована следующая строка:
Вторичное окноgraphics>grwnd
Здесь graphics - контекст раздела, а grwnd - название вторичного окна, определенного в файле проекта справочной системы hlpmore.hpj (будет описан позже).
Для создания встроенного окна использована следующая строка:
{ewl HLPMORE, EWHlpMoreClass, TestString}
В этой строке оператор ewl указывает, что необходимо встроить окно и расположить его в левой части основного окна (аналогично bml). Существуют также операторы ewc и ewr, с помощью которых можно задать центрирование встроенного окна и выравнивание вправо, соответственно.
Параметр HLPMORE - имя DLL-библиотеки, содержащей функцию окна для обработки сообщений встроенного окна. В данном случае используется библиотека hlpmore.dll, исходным текстом которой мы займемся чуть позже.
Параметр EWHlpMoreClass - это имя класса окна, зарегистрированное DLL-библиотекой для обработки сообщений от встроенного окна.
Строка TestString - произвольная строка, которая передается в функцию встроенного окна на этапе его создания.
Встроенными окнами мы займемся ниже в отдельном разделе.
Если вы создаете исходный текст справочной системы hlpmore.hlp на базе файла hlpfile.rtf, описанного нами ранее, добавьте в него раздел с контекстом clock (рис. 4.27). Этот раздел будет появляться во временном окне в том случае, если вы щелкните левой клавишей мыши внутри встроенного окна.
Рис. 4.27. Раздел с контекстом clock
Приведем полный список подстрочных ссылок, определенных в файле hlpmore.rtf:
$ Пример справочной системы # cont + cont:010 K оглавление $ Описание команд # cmd + cont:020 K команды $ Использование клавиатуры # key + cont:030 K клавиатура $ Графические изображения # graphics K графика;битовые изображения $ Меню File # file + cmd:010 K File; загрузка документа $ Меню Edit # edit + cmd:020 K edit; редактирование $ Меню View # view + cmd:030 K view $ часы # clock $ Отсутствующий раздел # missing
В файле hlpmore.hpj находится файл проекта справочной системы (листинг 4.2).
Справочная система Windows Help
4.1.
4.2.
4.3.
4.4.
4.5.
4.6.
4.7.
Ни одно приложение Windows, созданное на профессиональном уровне, не обходится без справочной системы, предназначенной для пользователя. До сих пор меню "Help" наших приложений в лучшем случае содержало строку "About...", выбрав которую вы могли полюбоваться на номер версии приложения и фамилию разработчиков. Однако пользователь вправе ожидать большего. И вы не должны разочаровать его, обеспечив свое приложение удобной справочной системой, содержащей всю информацию, необходимую как для начинающих, так и для опытных операторов персонального компьютера.
Какими же возможностями должна обладать справочная система?
Как минимум, она должна содержать информацию о том, для чего предназначено приложение, как использовать меню и диалоговые панели. Необходимо описать методику выполнения различных процедур, таких как копирование в Clipboard, редактирование документов или аналогичных. Хорошо спроектированная справочная система должна содержать глоссарий, систему поиска информации по контексту и графические иллюстрации.
Вся необходимая информация должна быть всегда под руками. Например, если пользователь просматривает текст, описывающий функцию, ему может потребоваться пример использования этой функции или перечень имен других функций, так или иначе связанных с данным текстом.
Впрочем, не будем утомлять вас перечнем всего того, что должно содержаться в справочной системе, ибо вам доступно множество различных готовых справочных систем, встроенных в стандартные приложения Windows, а также в такие приложения, как Microsoft Word for Windows или Borland C++ for Windows. Просто посмотрите на них внимательно - это и есть примеры хорошо спроектированных справочных систем.
Даже не очень пристальный взгляд на то, что появляется на экране при работе с меню "Help" текстового процессора Microsoft Word for Windows наводит на размышления о необычайной сложности справочной системы. Однако не думайте, что создание таких систем доступно только избранным.
Очень скоро вы научитесь делать подобные вещи или даже более мощные.
Схожесть внешнего вида справочных систем различных приложений наводит на мысль, что для их создания было использовано одно и то же средство. Это так и есть на самом деле.
Для создания справочных систем используется специальный help-компилятор, входящий в состав SDK и других продуктов Microsoft, предназначенных для разработки приложений, например, Microsoft FoxPro for Windows или Microsoft Visual Basic.
Пользователь работает со справочной системой при помощи приложения winhelp.exe, которое поставляется вместе с Windows. Программист также имеет доступ к средствам отображения справочной информации. В его распоряжении имеется функция WinHelp, которая входит в программный интерфейс Windows.
Можно сказать, что средства создания и просмотра справочных систем встроены в операционную систему Windows. И это очень хорошо, так как пользователю достаточно лишь один раз научиться работать со справочной системой какого-либо одного стандартного приложения, и он без труда сможет работать со справочными системами всех других приложений Windows.
К сожалению, некоторые приложения имеют нестандартные справочные системы, выполненные без помощи специально предназначенных для этого средств. Для примера можно привести картографическую систему MapInfo for Windows. "Самодельные" справочные системы дезориентируют пользователя, лишая его привычного способа "добывания" нужной информации. К тому же, внешний вид таких систем, информационное содержание и удобство использования обычно оставляют желать лучшего. И это не смотря на то, что разработка такой справочной системы может отнять много времени и сил.
Что же предлагает Microsoft для тех, кому нужно снабдить свое приложение справочной системой?
Прежде всего, такое удобное средство, как текстовый процессор Microsoft Word for Windows версий 2.0 и 6.0. Именно с помощью этого текстового процессора выполняется начальная подготовка текстовых и графических данных, которые будут содержаться в справочной системе.
Вы создаете справочную систему как обычный текстовый документ, включая в него специальные элементы, такие как скрытый текст, подстрочные сноски и т. п. Документ может содержать графические изображения и таблицы, стилевое и шрифтовое оформление.
Созданный документ сохраняется в формате RTF. Это универсальный текстовый формат, который обычно используется как промежуточный для конвертирования между форматами различных текстовых процессоров.
Строго говоря, для создания заготовки будущей справочной системы в формате RTF вы можете использовать помимо Word for Windows и другие текстовые процессоры. Однако в этом случае Microsoft не гарантирует, что все будет хорошо. Поэтому если вы собираетесь создавать справочные системы для Windows, лучше сразу обзавестись текстовым процессором Microsoft Word for Windows.
На следующем этапе исходный текст справочной системы преобразуется из формата RTF в hlp-файл. Для выполнения этой процедуры вам потребуется компилятор Microsoft Help Compiler. Он есть в составе SDK и поставляется в составе практически всех систем разработки приложений Windows, таких как Borland C++ for Windows и Microsoft VisualC++.
После завершения процесса компиляции справочная система готова к использованию. Вы можете работать с ней при помощи приложения winhelp.exe или при помощи функции WinHelp, вызываемой из вашего приложения.
Ссылка на другой hlp-файл
Можно создать ссылку на раздел, расположенный в другом hlp-файле. Для этого после строки контекста, оформленной скрытым текстом, надо расположить символ @ и сразу вслед за ним путь к файлу:
SmartPadcontents@c:\windows\smartpad.hlp
В этой строке все символы, расположенные после строки "SmartPad" необходимо оформить скрытым текстом.
Раздел, отображаемый при помощи ссылки на другой файл, можно показать во вторичном окне:
SmartPadcontents@c:\windows\smartpad.hlp>contwindow
Стандартные драйверы
В этом разделе мы очень кратко расскажем о стандартных драйверах Windows, обслуживающих такие устройства компьютера, как видеоконтроллер, принтер и плоттер, клавиатуру, мышь и асинхронный последовательный адаптер.
Структура виртуального драйвера
Виртуальный драйвер может содержать сегменты нескольких типов, которые разделяются на существующие только в момент инициализации драйвера и загруженные в память постоянно.
Для описания сегментов необходимо использовать специальные макрокоманды, включив в исходный текст драйвера файл vmm.inc оператором include. Приведем первые две строки исходного текста виртуального драйвера:
.386p include vmm.inc
Так как виртуальный драйвер пользуется обычными и привилегированными командами процессора 80386, необходима директива ".386p", позволяющая вызывать такие команды.
Помимо файла vmm.inc в исходный текст виртуального драйвера могут включаться и другие inc-файлы, определяющие различные макрокоманды и константы.
Трансляция и сборка виртуального драйвера
Для трансляции и сборки виртуального драйвера вам не нужно переписывать на свой диск все файлы, входящие в комплект поставки DDK. Вам потребуется только содержимое каталога ddk\386\include и несколько утилит из каталога ddk\386\tools, а именно:
специальная версия MASM masm5.exe;
редактор связей link386.exe;
утилита добавления заголовка addhdr.exe
Далее вам нужно подготовить пакетный файл, предназначенный для запуска из среды MS-DOS. Мы использовали файл m.bat, представленный в листинге 5.3.
Виртуальные драйверы
В этом разделе мы займемся такими экзотическими вещами, как 32-разрядное программирование на языке ассемблера в защищенном режиме (и в нулевом кольце защиты). К тому же, мы будем работать в мультизадачной среде (имеется в виду настоящая вытесняющая мультизадачность). И все это, как ни странно, будет происходить в среде старой знакомой операционной системы Windows версии 3.1, запущенной в расширенном режиме.
Сейчас самое время вспомнить систему команд процессора Intel 80386, сегментную и страничную организацию памяти, семафоры и другие аспекты, связанные со спецификой программирования для мультизадачной среды.
Мы рекомендуем вам обратиться к 6 тому "Библиотеки системного программиста", в котором описаны особенности работы процессора в защищенном режиме, а также рассмотрены основы мультизадачных операционных систем. Минимальные сведения об адресации памяти в защищенном режиме вы также можете получить из 13 тома "Библиотеки системного программиста" (глава "Управление памятью").
Виртуальные машины в Windows
Стандартный режим работы Windows позволял пользователю запустить несколько сеансов DOS. Когда пользователь переключался на такой сеанс, процессор переходил в реальный режим работы и занимался выполнением соответствующей программы MS-DOS. Пока работала программа MS-DOS, все приложения Windows находились в "замороженном" состоянии.
Пользователь мог переключиться снова на одно из приложений Windows, при этом все запущенные ранее сеансы DOS переводились в неактивное состояние, а приложения Windows продолжали работать в псевдо-мультизадачном режиме (с использованием "добровольной" невытесняющей мультизадачности). При переключении на приложение Windows процессор переводился в защищенный режим.
Когда Windows запускается в расширенном режиме, создается одна системная виртуальная машина, в рамках которой работают все приложения Windows. Системная виртуальная машина работает в защищенном режиме. Для каждого запускаемого вновь сеанса DOS создается отдельная виртуальная машина, работающая в режиме виртуального процессора V86.
Каждая виртуальная машина имеет виртуальный процессор, набор регистров и собственное адресное пространство (свою локальную таблицу дескрипторов LDT).
Системная виртуальная машина (в рамках которой работают все приложения Windows), так же имеет свою таблицу LDT, причем только одну. Последнее означает, что все приложения Windows работают в одном адресном пространстве. Это обстоятельство облегчает обмен данными между приложениями, так как можно создать глобальный блок памяти, доступный любому приложению, однако сильно ухудшает защищенность приложений Windows друг от друга. И в самом деле, большинство ошибок в приложениях при своем проявлении приводят к необходимости перезапуска всей системы.
Виртуальные машины MS-DOS лучше защищены друг от друга, так их адресные пространства разделены. Создавая новую виртуальную машину MS-DOS, Windows сначала копирует в ее адресное пространство все драйверы и резидентные программы, загруженные до запуска Windows, а затем копирует туда запускаемую программу (рис. 5.1).
Рис. 5.1. Структура памяти в виртуальной машине MS-DOS
Если запускается резидентная программа, то она удаляется из памяти при завершении работы данной виртуальной машины. Этот факт удобно использовать для отладки резидентных программ MS-DOS.
Для виртуальной машины MS-DOS эмулируются все необходимые ей прерывания DOS и BIOS, которые доступны в "чистом" MS-DOS. Дополнительно программа, запущенная в виртуальной машине MS-DOS, может использовать функции DPMI (DOS Protected Mode Interface). С помощью функций интерфейса DPMI программа может переключить процессор из режима виртуального процессора V86 в защищенный режим. При этом ей будет доступна вся виртуальная память, имеющаяся в системе.
Интерфейс DPMI был нами описан в 6 томе "Библиотеки системного программиста". Там же был приведен пример программы, переключающей процессор в защищенный режим из режима виртуального процессора V86.
Перед запуском системной виртуальной машины (т. е. перед запуском Windows) в младшую область памяти этой машины копируются драйверы и резидентные программы, загруженные в память до запуска Windows. Приложения Windows размещаются в области старших адресов выше границы 1 Мбайт (рис. 5.2).
Рис. 5.2. Структура памяти системной виртуальной машины
Существует малоизвестная возможность загрузить резидентную программу MS-DOS только в системную виртуальную машину, не копируя ее в адресные пространства виртуальных машин MS-DOS. Для этого имя такой программы необходимо указать в файле с именем winstart.bat, который запускается перед стартом системной виртуальной машины.
Загруженная таким образом резидентная программа MS-DOS может впоследствии взаимодействовать косвенно или непосредственно с приложениями Windows или виртуальными драйверами.
С учетом всего сказанного выше, нетрудно сделать вывод, что организация взаимодействия и передачи данных между отдельными виртуальными машинами, работающими в различных адресных пространствах - далеко не простая задача. Программы MS-DOS или приложения Windows не могут просто заказать области памяти, доступные всем виртуальным машинам, так как использованная схема адресации памяти полностью исключает такую возможность.
В режиме виртуального процессора V86 программой MS-DOS непосредственно адресуется только первый мегабайт памяти. Это не вызывает затруднений при запуске нескольких виртуальных машин MS-DOS, так как размещение в памяти их адресных пространств выполняется на уровне страниц. Поэтому, хотя программа, работающая в такой виртуальной машине, адресуется к первому мегабайту своего адресного пространства, фактически происходит обращение к старшим адресам общей виртуальной памяти.
При переключении виртуальных машин в процессе мультизадачной работы происходит переключение адресных пространств, не связанное, однако, с копированием образа памяти виртуальной машины в область нижних адресов (как это происходит в стандартном режиме работы Windows). Следовательно, такое переключение выполняется быстро.
Что касается обработки прерываний, то она выполняется всегда в защищенном режиме. Соответствующая таблица прерываний IDT (Interrupt Descriptor Table) создается ядром Windows и никогда не изменяется непосредственно ни программами DOS, ни приложениями Windows.
Когда происходит аппаратное или программное прерывание, управление получает обработчик защищенного режима, расположенный в ядре Windows. В зависимости от номера прерывания и от того, какой виртуальной машиной вызвано программное прерывание, оно отображается либо на соответствующий виртуальный драйвер, либо на обработчик виртуальной машины MS-DOS. В последнем случае процессор переводится в режим виртуального процессора V86.
Часть пресловутого "ядра операционной системы Windows" в расширенном режиме работы загружается из файла win386.exe, который представляет из себя ни что иное, как набор виртуальных драйверов, работающих в нулевом кольце защиты в модели памяти FLAT (которой мы скоро займемся). Одной из важнейших функций win386.exe является запуск виртуальных машин и планирование распределения времени, выделяемого для их работы.
Система планирования состоит из двух компонент - первичного планировщика (primary scheduler) и планировщика времени (time slicer).С помощью приложения Control Panel пользователь может назначить приоритет от 1 до 10000, который учитывается планировщиком времени. Можно установить как фоновый, так и основной приоритеты. Приоритеты можно изменять динамически для любой запущенной виртуальной машины MS-DOS или определить в соответствующем pif-файле. С помощью пиктограммы "Enchanced" приложения Control Panel вы можете также установить фоновый и основной приоритеты для системной виртуальной машины (т. е. одновременно для всех запущенных приложений Windows).
Не существует никакого способа увеличить или уменьшить приоритет отдельного приложения Windows, так как все приложения работают в рамках одной виртуальной машины.
Вставка с помощью ссылки
Чаще для вставки графических файлов в исходный текст раздела справочной системы используется косвенная ссылка следующего вида:
{bmc check1.bmp} {bml check2.bmp} {bmr check3.bmp}
Здесь приведены три ссылки на файлы check1.bmp, check2.bmp и check3.bmp, содержащие графические изображения. Перед именем файла необходимо указать одну из трех команд вставки (есть две разновидности каждой команды, см. ниже). В зависимости от выбранной команды будет использован один из трех способов взаимного расположения текста и графического изображения в окне раздела:
Команда | Способ расположения |
bmc,bmcwd | Графическое изображение будет вставлено в строку как символ. При этом оформление параграфа (например, высота строки) будет применяться и к изображению |
bml,bmlwd | Графическое изображение выравнивается по левой границе, текст располагается справа от графического изображения |
bmr,bmrwd | Графическое изображение выравнивается по правой границе, текст располагается слева от графического изображения |
Если вы создаете справочную систему, предназначенную для записи на компакт-диск, в некоторых случаях удобнее использовать команды bmcwd, bmlwd, bmrwd. При использовании этих команд графические данные располагаются на диске рядом с окружающим их текстом, что значительно ускоряет процесс отображения.
Если на один и тот же графический файл будет сделано несколько ссылок, при использовании команд bmc, bml и bmr в hlp-файле будет храниться только один экземпляр графических данных. Если же изображения вставлены командами bmcwd, bmlwd и bmrwd, для одного и того же изображения графические данные будут вставлены в hlp-файл столько раз, сколько имеется ссылок.
Встроенное окно
При создании справочной базы данных с использованием приложения winhelp.exe существует малоизвестная возможность создания встроенных окон, внутри которых разработчик справочной системы может делать практически все что угодно, за исключением ввода с клавиатуры (так как встроенное окно не может получить фокус ввода).
Процедура создания встроенных окон не описана в документации, поставляющейся вместе с SDK, однако необходимая информация есть в руководстве к утилите "Windows Help Authoring Guide for Word for Windows 2.0", которое поставляется на компакт-диске MSDN CD ("Microsoft Developer Network. Development Library").
Для чего можно использовать встроенное окно?
Можно придумать различные применения, например, следующие.
Отображение анимации и видеофильмов. Для увеличения привлекательности и наглядности справочной системы можно дополнить ее не только статическими графическими изображениями, но и видеофрагментами, например, в формате avi-файлов. Встроенное окно может содержать органы управления и окно просмотра видеофрагментов.
Изображение стандартных или нестандартных органов управления непосредственно в тексте раздела или в несворачиваемой области. Такие органы управления можно использовать как для организации гипертекстовых ссылок, так и для выполнения нестандартных функций, таких, как копирование файлов из справочной системы на диск и т. п.
Этой книгой мы завершаем рассказ
Этой книгой мы завершаем рассказ о программном интерфейсе Windows, начатый в 11 томе "Библиотеки системного программиста". Многие вопросы, затронутые здесь, актуальны не только при создании приложений для Windows версии 3.1, но и при создании приложений для Microsoft Windows NT, а также Microsoft Windows-95, известной во время бета-тестирования под названием Chicago.
Напомним, что тома 11, 12 и 13 ("Microsoft Windows 3.1 для программиста", часть 1, 2 и 3) были посвящены основам программирования для Windows. В них мы объяснили многие базовые понятия, без знания которых невозможно создать ни одно приложение Windows.
В 14 томе ("Интерфейс графических устройств GDI") мы научили вас пользоваться интерфейсом графических устройств GDI, который необходим для реализации в полном объеме графических возможностей Windows.
Для тех из вас, кто интересуется быстро развивающейся технологией мультимедиа, предназначен том 15 ("Мультимедиа для Windows. Руководство программиста"). В нем мы рассказали о том, как создавать мультимедиа-приложения, ориентированные на работу со звуком и видео.
В 16 томе ("Программирование модемов и факс-модемов в MS-DOS и Windows") отдельная глава посвящена программированию модемов и факс-модемов в среде Windows.
Надеемся, что после чтения наших книг вы смогли ощутить силу операционной системы Windows и научились создавать приложения, которые делают что-то полезное или просто интересны сами по себе. В этом случае вы много работали и возможно, устали от бесчисленных функций, сообщений и структур данных. Но потерпите, это еще далеко не все! Мы оставили без внимания многие важнейшие вопросы, в которых должен уметь ориентироваться профессиональный программист.
Например, мы до сих пор не рассказали вам о многооконном интерфейсе MDI, который используется в знакомых вам приложениях (например, в приложении Program Manager или Microsoft Word). Интерфейс MDI позволяет организовать работу пользователя с несколькими окнами, содержащими разные документы или разные представления одного документа.
Мы расскажем о работе с интерфейсом MDI в первой главе нашей книги.
Так как приложения Windows работают не в гордом одиночестве (как программы MS-DOS), а в компании других приложений, у пользователя должна быть возможность передавать данные из одного приложения в другое или даже организовать постоянно действующий канал передачи данных между различными приложениями.
Для однократной или эпизодической передачи данных можно использовать универсальный буфер обмена Clipboard и соответствующие функции программного интерфейса Windows, которые мы опишем во второй главе.
Вы можете также организовать канал передачи данных между любыми приложениями, пользуясь механизмом динамической передачи данных DDE. Этот механизм, а также интерфейсная dll-библиотека DDEML будет описана в третьей главе.
Посмотрев на любое стандартное приложение Windows, вы без труда найдете в главном меню строку "Help", открывающую доступ к справочной системе приложения, выполненной с использованием средств Windows. Для того чтобы стиль вашего приложения мог претендовать на стандартный, приложение обязано иметь собственную справочную систему. Нужно создать hlp-файл (один или несколько) и обеспечить доступ к его содержимому через строку "Help" главного меню приложения. Нужно также обеспечить получение контекстно-зависимой справки при помощи клавиши <F1>. Четвертая глава книги поможет вам создать справочную систему и организовать работу с ней.
Помимо обычных сведений об использовании компилятора Help Compiler, мы рассказали о том, как создать в справочной системе встроенные окна (Embedded Windows). Вы можете использовать такие окна для отображения анимации, видеофильмов или других произвольных изображений. Отметим, что стандартная документация, которая поставляется вместе с SDK, умалчивает о такой возможности.
Пятая глава нашей книги посвящена драйверам Windows. Если что и есть в Windows по-настоящему сложное, так это драйверы, особенно стандартные и виртуальные. Сложнее драйверов разве лишь система привязки и вставки объектов OLE, для описания программного интерфейса которой требуются многие сотни страниц текста (описание OLE выходит за рамки этой книги).
Создание драйвера для стандартного устройства компьютера под силу только разработчику, имеющему доступ к полному описанию аппаратного интерфейса устройства и знающего все "потайные" уголки Windows. Однако к счастью, у обычных программистов практически никогда не возникает необходимость разработки собственного драйвера для стандартных устройств компьютера, таких как видеоконтроллер, принтер или контроллер диска - нужные драйверы поставляются разработчиком устройства или Microsoft. В то же время иногда возникают задачи, которые принципиально невозможно разрешить в рамках стандартного программного интерфейса Windows, рассчитанного на обычные приложения.
Например, для обеспечения работы нестандартного устройства в режиме реального времени вам потребуются средства, доступные только драйверам Windows. Иногда нужно "преодолеть" защиту Windows, основанную на использовании колец защиты. Приложения Windows работают в третьем, наименее привилегированном кольце защиты, а виртуальные драйверы - в нулевом. Поэтому только виртуальные драйверы могут обращаться к любым областям оперативной памяти, к любым регистрам процессора и портам ввода/вывода, а также обрабатывать прерывания в реальном времени. Другая важная задача, которая под силу только виртуальным драйверам, - организация взаимодействия между различными виртуальными машинами, работающими в среде Windows.
В пятой главе мы расскажем о трех различных типах драйверов Windows, уделив основное внимание виртуальным и загружаемым драйверам. Тех же, кому требуется разрабатывать драйверы стандартных устройств компьютера, мы отсылаем к документации, поставляющейся вместе с DDK - системой разработки драйверов Microsoft Driver Development Kit for Windows 3.1, а также к книге Д. Нортона "Writing Windows Device Drivers" (имеется перевод этой книги на русский язык).
При этом вы можете использовать пятую главу нашей книги с одной стороны, как введение в книгу Д. Нортона, с другой - как дополнение этой книги, в которой нет ни одного примера виртуального драйвера, и ничего не сказано про загружаемые драйверы.
Что нужно для работы с книгой?
Вам, прежде всего, потребуется система разработки Borland Turbo C++ версии 3.1 или Borland C++ версий 3.1, 4.0 или 4.02, а также система Microsoft Driver Development Kit for Windows 3.1 (для создания виртуальных драйверов).
Никаких особых требований к аппаратному обеспечению компьютера не предъявляется, достаточно иметь компьютер с процессором i386 и объемом оперативной памяти 4 Мбайт. Однако если вы используете систему разработки Borland C++ версии 4.0 или 4.02, для ускорения процесса сборки проекта желательно установить, по крайней мере, 8 Мбайт оперативной памяти и использовать процессор i486. Для нормальной работы систем Borland C++ и Windows Help нужен видеоконтроллер VGA или SVGA (но не EGA и тем более не CGA).
Исходные тексты приложений поставляются на дискете, которая продается вместе с книгой, поэтому вам не нужно набирать эти тексты вручную.
В следующих томах серии "Библиотека системного программиста" мы планируем рассказать вам об объектно-ориентированном программировании для Windows. Объектно-ориентированное программирование предполагает использование готовых библиотек классов, скрывающих многие (но не все) детали реализации программного интерфейса Windows. Наиболее известны библиотеки классов, разработанные Borland (OWL - Borland Object Windows Library) и Microsoft (MFC - Microsoft Foundation Classes). Каждая из этих библиотек имеет свои преимущества и недостатки, поэтому окончательный выбор будет за вами. Мы рассмотрим обе эти библиотеки в самое ближайшее время.
Несмотря на то, что библиотеки классов сильно упрощают и ускоряют процесс создания приложений Windows, вы едва ли сможете ими воспользоваться, не владея стандартным интерфейсом Windows и не понимая принципов, положенных в основу Windows. Поэтому перед тем как приступить к объектно-ориентированному программированию, мы рекомендуем вам изучить программный интерфейс Windows, проработав тома с 11 по 17 нашей серии книг.
Присылайте ваши отзывы в издательство или непосредственно нам через электронную почту по адресу:
frolov@glas.apc.org
Все ваши замечания будут учтены при подготовке новой редакции книги.
Авторы выражают благодарность сотрудникам издательского отдела АО "Диалог-МИФИ" Елене Виноградовой, Олегу Александровичу Голубеву, Наталье Дмитриевой и Оксане Кузьминовой. Мы благодарим корректора Виктора Кустова, а также системных программистов фирмы Interactive Products Inc. Максима Синева и Сергея Ноженко.
Сергей Ноженко принял самое активное участие в отладке виртуального драйвера VXDSRV, а также сопутствующих этому драйверу приложений. Драйвер VXDSRV описан в нашей книге и предназначен для запуска приложений Windows из командного приглашения виртуальной машины MS-DOS. Идея создания такого драйвера принадлежит Максиму Синеву.
Выполнение команды
Помимо передачи данных возможно такое взаимодействие между клиентом и сервером, когда клиент передает серверу команды в виде текстовой строки, а сервер их исполняет. Вообще говоря, этот механизм можно реализовать с помощью рассмотренных нами ранее транзакций XTYP_POKE и XTYP_REQUEST, однако существует специально предназначенная для передачи команд транзакция XTYP_EXECUTE. Вот соответствующие параметры, передаваемые функции обратного вызова:
Параметр | Значение |
hsz1 | Идентификатор строки, содержащей имя раздела |
hsz2 | Идентификатор строки, содержащей имя сервиса |
Процесс передачи команды очень напоминает процесс передачи данных серверу через транзакцию XTYP_POKE.
Вначале необходимо при помощи функции DdeCreateDataHandle создать блок памяти, содержащей текстовую строку команды. Отличие заключается в том, что параметр hszItem должен быть указан как NULL:
hData = DdeCreateDataHandle (idInst, szCmdString, lstrlen(szCmdString) + 1, 0L, NULL, wFmt, 0);
Затем с помощью функции DdeClientTransaction серверу посылается транзакция XTYP_EXECUTE:
if(hData != NULL) hData = DdeClientTransaction((LPBYTE)hData, -1, hConv, hszItem, wFmt, XTYP_EXECUTE, 1000, &dwResult);
Для того чтобы получить доступ к командной строке, обработчик транзакции XTYP_EXECUTE, расположенный в функции обратного вызова сервера, должен использовать функцию DdeAccessData:
BYTE FAR* WINAPI DdeAccessData( HDDEDATA hData, // идентификатор блока памяти DWORD FAR* pcbDataSize); // указатель на переменную, // в которую будет записан размер блока памяти
Эта функция возвращает указатель на начало области памяти, содержащей команду.
После успешного выполнения команды функция обратного вызова сервера должна вернуть значение DDE_FACK. Если команда не поддерживается, нужно вернуть значение DDE_FNOTPROCESSED. В том случае, когда сервер может выполнить команду позже (потому что занят, например, выполнением другой команды), функция обратного вызова должна вернуть значение DDE_FBUSY.
Клиент может проверить результат выполнения передачи команды, если проанализирует содержимое двойного слова, на которое указывал параметр lpdwResult перед вызовом функции DdeClientTransaction. Например, если в этом слове установлен бит DDE_FBUSY, можно попробовать повторить посылку команды позже.
Выполнение отложенной записи
Сам по себе процесс выполнения отложенной записи предельно прост: достаточно открыть и очистить Clipboard, вызвать функцию SetClipboardData, указав в первом параметре формат данных, а во втором - значение NULL, и закрыть Clipboard. Так, отложенная запись текстовых данных выглядит следующим образом:
OpenClipboard(hwnd); EmptyClipboard(); SetClipboardData(CF_TEXT, NULL); CloseClipboard();
Выполняя отложенную запись данных в Clipboard, приложение как бы объявляет, что оно способно "по первому требованию" других приложений предоставить данные в указанном формате. Сами данные на момент объявления могут не существовать.
Как только какое-либо приложение попытается прочитать записанные подобным образом данные, Windows обнаружит, что данных нет в памяти, так как для идентификатора соответствующего блока памяти указано значение NULL. В этом случае приложение, выполнившее отложенную запись, получит сообщение WM_RENDERFORMAT, в ответ на которое оно должно выполнить реальную запись данных.
Параметр wParam сообщения WM_RENDERFORMAT содержит код формата данных, который потребовался приложению, выполняющему чтение данных из Clipboard.
Обработчик сообщения WM_RENDERFORMAT должен, не открывая и не очищая Clipboard, предоставить данные в нужном формате, вызвав функцию SetClipboardData. На этот раз необходимо указать реальный идентификатор незафиксированного блока памяти, содержащего копируемые данные:
SetClipboardData(wParam, hglbTextCopyBuf);
После выполнения операции не следует закрывать Clipboard.
Теперь рассмотрим следующую ситуацию. Пусть пользователь скопировал данные из приложения в Clipboard с использованием отложенной записи, а затем попытался закрыть это приложение, и пусть в момент копирования не было активно ни одно окно динамического просмотра Clipboard.
В данной ситуации данные объявлены, но реально их нет в памяти. Следовательно, перед завершением приложения требуется выполнить запись данных в Clipboard во всех возможных форматах.
Если приложение объявило форматы данных в Clipboard, "взяв на себя обязательство" обеспечить их в случае необходимости, оно не может уйти со сцены, не выполнив реальной записи данных, так как в противном случае записывать объявленные данные будет некому.
Эта проблема имеет простое решение. Перед завершением работы приложение, объявившее в Clipboard данные, но не предоставившее их, получит от Windows сообщение WM_RENDERALLFORMATS. В ответ на это сообщение оно должно выполнить реальную запись данных.
Вот возможная реализация обработчика сообщения WM_RENDERALLFORMATS:
case WM_RENDERALLFORMATS: { OpenClipboard(hwnd); EmptyClipboard(); SendMessage(hwnd, WM_RENDERFORMAT, CF_TEXT, 0L); SendMessage(hwnd, WM_RENDERFORMAT, CF_BITMAP, 0L); SendMessage(hwnd, WM_RENDERFORMAT, CF_PALETTE, 0L); CloseClipboard(); return 0; }
Приложение несколько раз посылает само себе сообщение WM_RENDERFORMAT, указывая все возможные для него форматы данных. При этом обработчик сообщения WM_RENDERFORMAT каждый раз будет выполнять реальную запись данных в Clipboard.
Возможна такая ситуация, когда приложение выполнило отложенную запись и затем предоставила реальные данные, заказав для их хранения блок глобальной памяти, а другое приложение вскоре после этого очистило Clipboard и записало туда свои данные. Теперь первое приложение хранит в глобальной памяти никому не нужные данные, которые могут занимать много места.
В указанной ситуации ваше приложение получит сообщение WM_DESTROYCLIPBOARD. Обработчик этого сообщения может уничтожить ненужный блок памяти.
Вызов функции обратного вызова
После окончательного формирования буфера, расположенного в DLL-библиотеке d2w.dll, драйвер может вызвать функцию обратного вызова из d2w.dll, адрес которой был зарегистрирован ранее. Однако драйвер не может просто вызвать эту функцию оператором call или другим аналогичным способом.
Дело в том, что задачей функции обратного вызова, расположенной в d2w.dll, является запись сообщения в очередь приложения dos2win.exe. Для этого надо сделать так, чтобы в момент вызова была активна системная виртуальная машина, в рамках которой работают приложения Windows.
В то же время на момент вызова процедуры фильтра прерывания INT21h активна виртуальная машина MS-DOS. Поэтому нам нужно обеспечить переключение на системную виртуальную машину и вызвать функцию обратного вызова из среды системной виртуальной машины.
Эта задача решается сервисом Schedule_VM_Event. Перед вызовом сервиса в регистр EBX необходимо записать идентификатор виртуальной машины, для которой нужно сделать вызов функции обратного вызова, в регистр ESI - адрес процедуры обратного вызова, расположенной внутри виртуального драйвера, а в регистр EDX - произвольное значение, которое будет передано функции обратного вызова.
Заметьте, что для данного сервиса в регистре ESI необходимо указать адрес процедуры обратного вызова, расположенной в нулевом кольце защиты, т. е. в виртуальном драйвере, но не в DLL-библиотеке d2w.dll. В этом нет ничего страшного, нужная нам функция обратного вызова будет вызвана той процедурой, адрес которой передается сервису Schedule_VM_Event в регистре ESI.
Для определения идентификатора системной виртуальной машины мы использовали сервис Get_Sys_VM_Handle, возвращающий искомый идентификатор в регистре EBX.
Через регистр EDX мы передаем идентификатор текущей виртуальной машины (т. е. виртуальной машины MS-DOS, запустившей приложение Windows). Этот идентификатор возвращается в регистре EBX сервисом Get_Cur_VM_Handle.
Что происходит после вызова сервиса Schedule_VM_Event?
Через некоторое, достаточно малое время, происходит переключение на системную виртуальную машину, после чего управление передается процедуре CallbackProc, расположенной в нашем виртуальном драйвере.
Эта процедура, прежде всего, сохраняет контекст (состояние) системной виртуальной машины, вызывая специально предназначенную для этого макрокоманду Push_Client_State.
Затем она вызывает функцию обратного вызова, расположенную в d2w.dll, используя специально предназначенный для этого сервис. Опять это нельзя сделать прямым вызовом, так как функция обратного вызова находится в памяти системной виртуальной машины в третьем кольце защиты, а процедура CallbackProc - в памяти виртуального драйвера в нулевом кольце защиты. При этом в частности, используются разные стеки.
Вызов макрокоманды
Как вызвать макрокоманду?
Для этого существует несколько способов.
Во-первых, можно вызвать одну или несколько макрокоманд при загрузке hlp-файла. Такие макрокоманды могут выполнять инициализирующие функции, нужные для работы со всеми разделами справочной системы. Примером может послужить вызов макрокоманды BrowseButtons в разделе CONFIG файла проекта справочной системы hlpfile.hpj:
[CONFIG] BrowseButtons()
В разделе CONFIG можно указывать сразу несколько макрокоманд.
Во-вторых, можно запускать одну или несколько макрокоманд при отображении раздела справочной системы. Для этого имя макрокоманды необходимо оформить как подстрочную ссылку с идентификатором "!":
! BrowseButtons()
Для запуска нескольких макрокоманд их надо разделить символом ";":
! BrowseButtons();SaveMark("Key Assignment")
В-третьих, можно сделать так, чтобы макрокоманда запускалась при выборе чувствительной точки, созданной при помощи подчеркнутого текста или ссылки на битовое изображение:
Здесь при выборе строки "Запустить часы" будет исполнена макрокоманда ExecProgram, предназначенная для запуска приложений. В данном случае будет запущено приложение clock.exe.
Вызов процедуры в среде виртуальной машины
Для вызова процедуры в среде виртуальной машины виртуальный драйвер должен использовать сервис Simulate_Far_Call, загрузив в регистры CX:EDX адрес функции в формате <селектор:смещение> (или <сегмент:смещение для режима V86>).
Если функции через стек передаются параметры, их следует загрузить при помощи сервиса Simulate_Push, записав параметр в регистр AX.
Подготовив параметры в стеке при помощи сервиса Simulate_Push и вызвав сервис Simulate_Far_Call, драйвер может передать управление виртуальной машине для выполнения вызова при помощи сервиса Resume_Exec.
Описанная выше методика вызова процедуры в виртуальной машине предполагает также использование функций сервиса VMM с именами Begin_Nest_Exec и End_Nest_Exec. Первая из них открывает блок вложенного выполнения процедур в виртуальной машине, а вторая - закрывает.
После выполнения всех действий в системной виртуальной машине процедура CallbackProc восстанавливает контекст этой виртуальной машины, вызывая сервис Pop_Client_State.
На данном этапе обработку прерывания INT 21h можно считать законченной, поэтому перед возвращением управления процедура CallbackProc открывает семафор, вызывая сервис Signal_Semaphore.
Вызов внутренних функций
Разработчик справочной системы может использовать 16 внутренних функций, экспортируемых приложением winhelp.exe. Эти функции позволяют работать с внутренней файловой системой hlp-файла. Их можно использовать для доступа к данным, описанным в секции BAGGAGE.
Определив функцию LDLLHandler, разработчик DLL-библиотеки может получать извещения от приложения winhelp.exe о таких действиях пользователя, как выбор чувствительной точки или передача фокуса ввода другим приложениям.
WINDOWS
Параметры секции определяют внешний вид окон приложения winhelp.exe.
Характеристики окна определяются следующим образом:
wndname="caption",(horizposition,vertposition,width,height), sizing,(clientRGB),(nonscrollingRGB),topmost
Для определения характеристик главного окна приложения winhelp.exe в качестве wndname следует указать строку "main":
main = "Text Editor Help",,,, (255,255,192 )
Разработчик справочной системы может создавать дополнительные (вторичные) окна, имеющие уникальные имена. Для определения внешнего вида такого вторичного окна вместо wndname следует указывать имя этого окна.
Параметр caption задает заголовок окна.
Параметры horizposition, vertposition, width и height предназначены, соответственно, для определения горизонтального положения, вертикального положения, ширины и высоты окна. При этом следует использовать значения в диапазоне от 0 до 1024. Действительные размеры окна будут пересчитаны в соответствии с текущим разрешением экрана.
С помощью параметра sizing можно указать начальные размеры вторичного окна при его появлении на экране. Если этот параметр указан как1, устанавливаются максимальные размеры окна. Если же значение параметра равно 0, размеры окна определяются параметрами horizposition, vertposition, width и height.
Параметры (clientRGB) и (nonscrollingRGB) задаются как тройки чисел, разделенных запятыми. Эти числа определяют, соответственно, красную, зеленую и голубую компоненту цвета для внутренней области главного окна приложения winhelp.exe и для несворачиваемого окна заголовка раздела, располагающегося в верхней части главного окна.
Если для вторичного окна указать параметр topmost, равный 1, это окно будет всегда располагаться над другими окнами, превратившись в "непотопляемое".
Неиспользуемые параметры можно не указывать, однако в строке параметров должны присутствовать все запятые до последнего указанного параметра.
Загружаемые драйверы
В этом разделе мы расскажем вам о последнем, третьем типе драйверов Windows. Это так называемые загружаемые или инсталлируемые драйверы (installable drivers).
Как мы уже говорили, загружаемые драйверы - это обычные DLL-библиотеки, которые экспортируют функцию с именем DriverProc.
Обычные приложения Windows и обычные DLL-библиотеки могут выполнять различные операции с загружаемыми драйверами, такие как открытие и закрытие, активизация и перевод в неактивное состояние.
В программном интерфейсе Windows определены несколько функций, с помощью которых можно обращаться к загружаемым драйверам. Кроме того, предусмотрен стандартный механизм инсталляции и конфигурирования загружаемых драйверов с помощью пиктограммы Drivers приложения Control Panel.
Запись данных в Clipboard
Для того чтобы записать данные в Clipboard, вы должны выполнить следующую последовательность действий.
Открыть Clipboard функцией OpenClipboard
Сбросить содержимое Clipboard функцией EmptyClipboard
Заказать функцией GlobalAlloc глобальный блок памяти имеющий размер, достаточный для размещения записываемых в Clipboard данных
Зафиксировать полученный блок памяти функцией GlobalLock
Записать в зафиксированный блок памяти данные
Расфиксировать блок памяти функцией GlobalUnlock
Вызвать функцию SetClipboardData, передав ей через первый параметр формат данных, а через второй - идентификатор расфиксированного блока памяти, содержащего данные
Закрыть Clipboard функцией CloseClipboard
На первый взгляд, указанная последовательность действий понятна, однако при ее внимательном изучении возникает ряд вопросов.
Например, почему блок памяти, полученный для данных, не уничтожается функцией GlobalFree, после того как его идентификатор был использован в вызове функции SetClipboardData?
Дело в том, что при записи в Clipboard никакого перемещения данных не происходит. Функция SetClipboardData изменяет атрибуты передаваемого ей блока памяти таким образом, что этот блок памяти изменяет своего "хозяина", переходя в собственность операционной системы Windows и приобретая атрибут GMEM_DDESHARE.
Если приложение заказало глобальный блок памяти без атрибута GMEM_DDESHARE, этот блок памяти принадлежит приложению. Оно отвечает за уничтожение такого блока памяти. Если же приложение "забыло" уничтожить заказанный ей глобальный блок памяти, Windows уничтожает блок памяти сама при завершении приложения.
Напомним, что одно из требований к блоку памяти, предназначенного для хранения данных Clipboard, заключается в том, чтобы этот блок не уничтожался при завершении работы приложения, создавшего его.
Если приложение снабдит блок памяти атрибутом GMEM_DDESHARE, Windows не будет автоматически уничтожать его при завершении работы приложения. Приложение обязано уничтожить его самостоятельно, как только этот блок памяти перестанет быть нужным.
Обычно блоки памяти с атрибутом GMEM_DDESHARE используются в Windows версии 3. 1 для организации общего поля памяти, доступного всем приложениям. Так как в Windows версии 3.0 и 3.1 для адресации памяти все приложения обращаются к одной локальной таблице дескрипторов LDT, они все могут адресоваться к одному блоку памяти, если будут знать его селектор.
Память Clipboard в Windows версий 3.0 и 3.1 устроена как раз в виде набора таких блоков памяти "общего пользования" (по одному на каждый формат данных). Функция SetClipboardData получает у приложения его "личный" глобальный блок памяти с данными, и отдает его в "коллективное пользование", добавляя атрибут GMEM_DDESHARE.
Из всего сказанного выше следует правило:
Если вы записали данные в Clipboard, отдав идентификатор глобального блока памяти функции SetClipboardData, больше не используйте этот идентификатор для адресации данных
Почему нельзя использовать идентификатор блока памяти, переданный функции SetClipboardData?
Дело в том, что в любой момент времени пользователь может уничтожить такой блок памяти записью в Clipboard новых данных. В процессе записи новых данных приложение вызовет функцию EmptyClipboard, которая уничтожит все блоки памяти, распределенные Clipboard раньше.
Разумеется, если операция записи данных в Clipboard выполняется при обработке одного сообщения (а именно так и рекомендуется поступать), никакое другое приложение не будет работать с Clipboard во время записи данных. Однако если этот обработчик попутно создает диалоговые панели, пользователь может записать в Clipboard содержимое, например, однострочного текстового редактора, реализованного в виде органа управления диалоговой панели.
Для исключения конфликтных ситуаций, связанных с попыткой одновременного доступа к Clipboard со стороны нескольких приложений, используйте следующее правило:
Все операции с Clipboard от его открытия и до закрытия должны выполняться в одном обработчике сообщения. Нельзя открывать Clipboard в обработчике одного сообщения, а закрывать в обработчике другого сообщения.
Между открытием и закрытием Clipboard нельзя создавать диалоговые панели или выполнять другие действия, которые могут повлечь за собой создание диалоговых панелей (например, вызов функций SendMessage или PeekMessage)
Еще одно замечание относительно атрибута блока памяти GMEM_DDESHARE.
Узнав о том, что с помощью этого атрибута можно создавать блоки памяти, адресуемые одновременно несколькими приложениями, вы можете впасть в соблазн организовать передачу данных между приложениями с использованием общих областей памяти.
Такой метод будет превосходно работать в Windows версии 3.1, и... не будет работать в Windows NT. Дело в том, что 32-разрядная операционная система Windows NT создает для каждого 32-разрядного приложения отдельную локальную таблицу дескрипторов LDT и, следовательно, отдельное адресное пространство. Поэтому никакое приложение ни при каких обстоятельствах не может иметь доступа к адресному пространству другого приложения.
Если же для передачи данных между приложениями вы будете использовать функции Clipboard, вам гарантирована совместимость на уровне исходных текстов с операционной системой Windows NT. И это несмотря на то, что в Windows NT работа Clipboard основана на других принципах, нежели в Windows версии 3.1.
Запись DDB
Запись DDB в Clipboard выполняется достаточно просто.
Пусть мы создали DDB (например, при помощи функции CreateCompatibleBitmap) и сохранили идентификатор созданного DDB в переменной hBitmap. В этом случае нам необходимо выполнить следующую последовательность действий.
Открыть Clipboard функцией OpenClipboard
Сбросить содержимое Clipboard функцией EmptyClipboard
Вызвать функцию SetClipboardData, передав ей через первый параметр константу CF_BITMAP, а через второй - идентификатор DDB
Закрыть Clipboard функцией CloseClipboard
Все перечисленные выше действия можно проделать, например, так:
OpenClipboard(hwnd); EmptyClipboard(); if(hBitmap) SetClipboardData(CF_BITMAP, hBitmap); CloseClipboard();
Отметим, что ваше приложение не должно удалять DDB, идентификатор которого был использован при вызове функции SetClipboardData. После записи в Clipboard память, занимаемая DDB, переходит в распоряжение Windows, и ваше приложение не должно ее использовать.
Запись и чтение графических изображений
Передача через Clipboard графических изображений выглядит сложнее, чем передача текста, однако и эта задача разрешима. Вы можете записать в Clipboard битовое изображение DDB в формате, который зависит от устройства отображения, цветовую палитру, метафайл, а также битовое изображение в формате DIB.
Какой из перечисленных форматов выбрать для передачи данных в вашем приложении? Это зависит в первую очередь от того, что и каким приложениям вы собираетесь передавать.
Мы рассмотрим широко распространенную задачу передачи через Clipboard битовых изображений DIB и DDB.
Создавая приложения, способные сохранять в Clipboard изображения DIB и DDB, вам придется иметь дело с метафайлами. Такие приложения, как Paintbrush, при записи графических изображений в Clipboard сохраняют данные одновременно в нескольких форматах, добавляя к ним еще и цветовую палитру. В этом случае то приложение, которое будет извлекать изображение из Clipboard, может выбрать для себя наиболее удобный формат.
Например, графический редактор Micrografx Designer версии 3.02 при записи изображений в Clipboard помимо своих нестандартных форматов использует форматы CF_BITMAP и CF_METAFILEPICT. Приложение Paintbrush сохраняет данные аналогичным образом, добавляя к своим собственным форматам форматы CF_BITMAP, CF_PALETTE и CF_METAFILEPICT. А вот графический редактор Microsoft Draw, который поставляется вместе с текстовым процессором Microsoft Word for Windows версии 2.0, сохраняет данные в Clipboard используя свои собственные форматы данных и формат CF_METAFILEPICT.
По возможности ваше приложение должно быть способно сохранять и читать изображения в перечисленных выше форматах. В этом случае пользователь сможет вставить сохраненное вашим приложением изображение из Clipboard в какой-либо стандартный графический редактор.
Несмотря на то, что для записи DIB в Clipboard предусмотрен специальный формат данных CF_DIB (блок памяти, который содержит биты изображения и начинается со структуры BITMAPINFO), многие приложения записывают DIB в виде DDB и цветовой палитры, созданных на основе DIB. Это потому, что не все приложения умеют читать из Clipboard данные в формате CF_DIB. Поэтому запись DIB сводится к записи DDB и палитры. Как мы уже говорили, следует также записать DIB в формате метафайла, к чему мы еще вернемся.
Рассмотрим по отдельности способы записи DDB, палитры и метафайла в Clipboard.
Запись метафайла
Практически все графические редакторы, такие как Paintbrush, Micrografix Designer, Photo Finish и т. п., при записи фрагмента изображения в Clipboard сохраняют данные не только в своих собственных форматах, но и в формате метафайла. В этом разделе мы рассмотрим процесс записи метафайла в Clipboard, а немного позже расскажем об особенностях чтения метафайла из Clipboard и проигрывания его в контексте отображения.
Сначала перечислим шаги, которые ваше приложение должно выполнить для записи метафайла в Clipboard.
Создать метафайл в оперативной памяти (но не в виде файла на диске)
С помощью функций SetWindowOrgEx и SetWindowExtEx установить в контексте метафайла размер и начало логической системы координат и размеры изображения
Выполнить рисование сохраняемого в Clipboard изображения с помощью графического интерфейса GDI, пользуясь контекстом метафайла
Закрыть метафайл
Заказать глобальный блок памяти для заголовка метафайла (структура типа METAFILEPICT) и зафиксировать этот блок памяти
Заполнить поля структуры METAFILEPICT, указав режим отображения, размеры изображения и идентификатор метафайла
Расфиксировать блок памяти, содержащий заголовок метафайла METAFILEPICT
Открыть Clipboard функцией OpenClipboard (если Clipboard не был открыт ранее при записи данных в других форматах)
Сбросить содержимое Clipboard функцией EmptyClipboard (если Clipboard не был очищен ранее при записи данных в других форматах)
Вызвать функцию SetClipboardData, передав ей через первый параметр константу CF_METAFILEPICT, а через второй - идентификатор расфиксированного блока памяти, содержащего заполненный заголовок метафайла METAFILEPICT
Закрыть Clipboard функцией CloseClipboard (если в дальнейшем не предполагается сохранять в Clipboard данные, пользуясь другими форматами)
Как видите, процедура записи метафайла в Clipboard достаточно громоздкая, однако использование метафайлов для передачи данных через Clipboard имеет свои преимущества. В частности, если в контексте метафайла установлены режимы отображения MM_ISOTROPIC или MM_ANISOTROPIC, в процессе вставки изображения легко выполнить масштабирование, соответственно, с сохранением отношения высоты к ширине или без сохранения этого отношения.
Первые шаги процедуры записи метафайла в Clipboard (создание метафайла и настройка контекста метафайла) несложны.
Для создания метафайла в оперативной памяти следует воспользоваться функцией CreateMetaFile, передав ей в качестве параметра значение NULL:
hdcMeta = CreateMetaFile((LPSTR)NULL);
Далее необходимо выбрать начало логической системы координат и размеры изображения. Как правило, для метафайла, сохраняемого в Clipboard, используются режимы отображения MM_ISOTROPIC или MM_ANISOTROPIC. В этом случае при записи графического изображения размером (ptSize.x, ptSize.y) вы можете определить начало логической системы координат и размеры изображены следующим образом:
SetWindowOrgEx(hdcMeta, 0, 0, NULL); SetWindowExtEx(hdcMeta, ptSize.x, ptSize.y, NULL);
Отметим, что на данном этапе не следует устанавливать режим отображения в контексте метафайла явным вызовом функции SetMapMode. Режим отображения устанавливается позже при заполнении заголовка метафайла.
На следующем шаге приложение должно нарисовать в контексте метафайла изображение, которое будет сохранено в Clipboard. Приведем фрагмент приложения BMPINFO, рассмотренного ниже, который рисует в контексте метафайла битовое изображение DIB:
// Создаем палитру из DIB hPal = DIBCreatePalette(hDib); if(hPal) { // Выбираем и реализуем палитру в контекст // метафайла hOldPal = SelectPalette(hdcMeta, hPal, FALSE); RealizePalette(hdcMeta); } // Рисуем DIB в контексте метафайла DIBPaint(hdcMeta, 0, 0, hDib);
// Выбираем старую палитру if(hPal) SelectPalette(hdcMeta, hOldPal, FALSE);
Процедура рисования DIB в контексте метафайла ничем не отличается от аналогичной процедуры для контекста отображения. Эта процедура, а также функции DIBCreatePalette и DIBPaint были описаны в 14 томе "Библиотеки системного программиста" (см. разделы "Рисование изображений DIB" и "Приложение BMPINFO").
После того как изображение нарисовано, следует закрыть метафайл:
hMF = CloseMetaFile(hdcMeta);
Теперь нужно создать и заполнить заголовок метафайла.
Прежде всего, заказываем и фиксируем глобальный блок памяти нужного размера:
hMeta = GlobalAlloc(GHND, sizeof(METAFILEPICT)); lpMeta = (LPMETAFILEPICT)GlobalLock(hMeta);
Структура METAFILEPICT и указатель на нее описаны в файле windows.h следующим образом:
typedef struct tagMETAFILEPICT
{
int mm;
int xExt;
int yExt;
HMETAFILE hMF;
} METAFILEPICT;
typedef METAFILEPICT FAR* LPMETAFILEPICT;
При заполнении заголовка метафайла в поле mm необходимо записать нужный режим отображения:
lpMeta->mm = MM_ANISOTROPIC;
Поле hMF должно содержать идентификатор метафайла, полученный от функции CloseMetaFile:
lpMeta->hMF = hMF;
Наибольшую трудность вызывает заполнение полей xExt и yExt. Эти поля заполняются по-разному в зависимости от выбранного режима отображения.
Если используются режимы отображения, отличные от MM_ISOTROPIC или MM_ANISOTROPIC, в поля xExt и yExt следует записать размеры изображения в тех единицах измерения, которые соответствуют режиму отображения, указанному в поле mm.
Однако, как мы уже говорили, для обеспечения возможности масштабирования изображения после вставки из Clipboard практически все приложения устанавливают в заголовке метафайла режимы отображения MM_ISOTROPIC или MM_ANISOTROPIC. Для этих режимов возможны несколько вариантов заполнения полей xExt и yExt.
Во-первых, приложение может записать в эти поля нулевые значения, не передавая через Clipboard никакой информации о размерах изображения или об отношении высоты к ширине изображения. При рисовании вставленного изображения из Clipboard, не имеющего информации о размерах, "принимающее" приложение может выбрать размеры и отношение высоты к ширине по собственному усмотрению.
Во-вторых, приложение может записать в поля xExt и yExt положительные значения - предпочтительный размер изображения в сотых долях миллиметра (такая единица измерения используется в режиме отображения MM_HIMETRIC). Если приложение прочитало из Clipboard изображение, для которого установлены предпочтительные размеры, оно может использовать эти размеры для рисования.Оно также может проигнорировать предпочтительные размеры и нарисовать изображение по-своему, что, однако, приведет в некоторых случаях к искажению изображения (например, при уменьшении размеров битового изображения).
В-третьих, приложение может записать в поля xExt и yExt отрицательные значения. Отношение этих отрицательных значений передает информацию об отношении ширины к высоте изображения. При этом информация об абсолютных размерах изображения не передается.
Итак, выбрав один из перечисленных выше вариантов, необходимо заполнить поля xExt и yExt, завершив таким образом формирование заголовка метафайла:
lpMeta->xExt = xPicSize; lpMeta->yExt = yPicSize;
Перед записью данных в Clipboard нужно расфиксировать блок памяти, содержащий заголовок метафайла и передать его функции SetClipboardData:
GlobalUnlock(hMeta); if(hMeta) SetClipboardData(CF_METAFILEPICT, hMeta);
Запись палитры
Если приложение должно уметь записывать в Clipboard многоцветное изображение DIB, вместе с DDB необходимо записать в Clipboard цветовую палитру, используемую этим изображением. Напомним, что файл в формате DIB может содержать наряду с битами изображения специальную таблицу цветов, на базе которой создается цветовая палитра. Вы можете получить дополнительную информацию о работе с DIB и цветовыми палитрами из 14 тома "Библиотеки системного программиста".
Для записи цветовой палитры в Clipboard вам достаточно передать идентификатор палитры функции SetClipboardData, указав этой функции в качестве первого параметра константу CF_PALETTE:
if(hPal) SetClipboardData(CF_PALETTE, hPal);
Разумеется, перед записью палитры необходимо открыть Clipboard.
Если палитра и DDB создаются на базе DIB, и ваше приложение записывает и то, и другое в Clipboard, нужно выполнить следующие действия:
Открыть Clipboard функцией OpenClipboard
Сбросить содержимое Clipboard функцией EmptyClipboard
Вызвать функцию SetClipboardData, передав ей через первый параметр константу CF_BITMAP, а через второй - идентификатор DDB
Вызвать функцию SetClipboardData, передав ей через первый параметр константу CF_PALETTE, а через второй - идентификатор цветовой палитры
Закрыть Clipboard функцией CloseClipboard
Палитра "сопровождает" DDB, обеспечивая приложения таблицей цветов, необходимой для правильного отображения DDB.
Передав идентификатор палитры функции SetClipboardData, вы должны "забыть" про него, так как он уже не принадлежит вашему приложению.
Запрос данных от сервера
Для того чтобы получить данные от сервера, клиент должен послать серверу транзакцию XTYP_REQUEST. Задача посылки серверу транзакции решается с помощью функции DdeClientTransaction:
HDDEDATA WINAPI DdeClientTransaction( void FAR* pData, // адрес данных, передаваемых серверу DWORD cbData, // размер передаваемых данных HCONV hConv, // идентификатор канала HSZ hszItem, // идентификатор элемента данных UINT uFmt, // формат данных UINT uType, // код транзакции DWORD dwTimeout, // продолжительность периода ожидания DWORD FAR* pdwResult); // указатель на двойное слово, // в которое будет записан результат выполнения транзакции
Если приложение запрашивает данные у сервера, для первых двух параметров следует указать нулевые значения (так как клиент не передает данные серверу, а наоборот, запрашивает их).
Через параметр hConv следует передать идентификатор созданного ранее канала связи.
Так как по одному каналу связи можно передавать различные элементы данных, следует указать нужный элемент данных с помощью параметра hszItem.
Формат данных передается через параметр uFmt. Здесь вы можете использовать один из идентификаторов формата Clipboard, такой как CF_TEXT или CF_BITMAP, в зависимости от того, что собой представляют передаваемые данные.
Через параметр uType следует передать код транзакции, посылаемой серверу. Для запроса данных следует послать транзакцию XTYP_REQUEST.
Параметр dwTimeout задает для синхронных транзакций время ожидания завершения транзакции (в миллисекундах). Вы можете указать для этого параметра значение TIMEOUT_ASYNC, в этом случае будет запущена асинхронная транзакция.
Параметр pdwResult должен содержать указатель на двойное слово. В это слово будет записан код результата выполнения транзакции. Если проверка не используется, через этот параметр можно передать нулевое значение.
Приведем фрагмент кода приложения DDEMLCL, в котором выполняется запрос данных от сервера:
hData = DdeClientTransaction(NULL, 0, hConv, hszItem, CF_TEXT, XTYP_REQUEST, 1000, &dwResult); if(hData != NULL) { DdeGetData(hData, szBuf, nBufSize, 0L); return TRUE; }
В случае успешного завершения транзакции XTYP_REQUEST функция DdeClientTransaction возвращает идентификатор области глобальной памяти, в которой расположены полученные данные.
Приложение должно переписать эти данные в свой буфер, вызвав для этого функцию DdeGetData:
DWORD WINAPI DdeGetData( HDDEDATA hData, // идентификатор области памяти void FAR* pDst, // адрес буфера DWORD cbMax, // размер буфера DWORD cbOff); // смещение начала данных
Последний параметр указывает смещение внутри области глобальной памяти, начиная с которого выполняется копирование. В нашем примере смещение равно нулю.
Теперь посмотрим на сервер. Получив от клиента транзакцию XTYP_REQUEST, он должен передать данные клиенту.
Приведем назначение параметров функции обратного вызова (сторона сервера) для транзакции XTYP_REQUEST:
Параметр | Значение |
hsz1 | Идентификатор строки, содержащей имя раздела |
hsz2 | Идентификатор строки, содержащей имя сервиса |
case XTYP_REQUEST: { // Создаем идентификатор данных hData = DdeCreateDataHandle(idInst, szDDEServerVersion, lstrlen(szDDEServerVersion) + 1, 0L, hszItem, CF_TEXT, 0);
// В случае успеха возвращаем созданный идентификатор if(hData != NULL) return(hData); else return(NULL); }
Этот фрагмент передает клиенту текстовую строку szDDEServerVersion.
Однако вначале сервер должен заказать блок глобальной памяти, записав туда передаваемые данные. Это необходимо сделать при помощи функции DdeCreateDataHandle:
HDDEDATA WINAPI DdeCreateDataHandle( DWORD idInst, // идентификатор приложения void FAR* pSrc, // адрес буфера DWORD cb, // размер буфера DWORD cbOff, // смещение начала данных HSZ hszItem, // идентификатор элемента данных UINT wFmt, // формат данных UINT afCmd); // флаги
Если с помощью функции DdeCreateDataHandle вы заказываете блок памяти, идентификатор которого будет возвращен функцией обратного вызова, последний параметр следует указать как NULL.В этом случае система DDEML освободит блок памяти самостоятельно, как только в нем отпадет потребность.
Итак, при обработке транзакции XTYP_REQUEST функция обратного вызова сервера создала блок глобальной памяти и записала туда передаваемые данные. Затем она должна возвратить идентификатор созданного блока памяти или NULL, если блок памяти создать невозможно.
Завершение работы драйвера
System_Exit
Первое сообщение, которое получает драйвер при завершении работы системы. Следом за этим посылается сообщение Sys_VM_Terminate
Sys_Critical_Exit
Последнее сообщение, которое посылается виртуальному драйверу. Прерывания запрещены. Обработчик этого сообщения обычно используется для подготовки аппаратного обеспечения к переводу процессора в реальный режим работы
Завершение работы виртуальной машины
Query_Destroy
Это сообщение поступает в драйвер, когда виртуальный драйвер Shell (входящий в состав ядра Windows) желает узнать, можно ли уничтожить виртуальную машину. Если обработчик установит флаг переноса в единицу, виртуальная машина не будет уничтожена
VM_Terminate
Первое сообщение, которое посылается при уничтожении виртуальной машины. Оно может быть использовано для подготовки виртуального драйвера к завершению работы виртуальной машины
Sys_VM_Terminate
Первое сообщение, которое посылается драйверу при завершении работы системной виртуальной машины. Эта машина всегда завершает свою работу последней
VM_Not_Executable
Второе сообщение, которое посылается при уничтожении виртуальной машины
Destroy_VM
Третье сообщение, которое посылается при уничтожении виртуальной машины