Рефераты
 

Ознакомление с приложениями Windows

p align="left">После завершения цикла надо сделать совсем немногое -- освободить память от тех объектов, которые создавались во время работы приложения (если они еще существуют). Некоторые объекты, которые уничтожаются автоматически, можно не освобождать -- это сделает Windows. Таков, например, зарегистрированный нами класс окон.

И остается еще одно дело: так как WinMain возвращает результат, то мы должны вернуть какое-либо значение. В Windows принято, что возвращаемое значение является параметром wParam сообщения WM_QUIT, завершившего цикл обработки сообщений. Таким образом мы пишем:

return msg.wParam;

Оконная процедура

Пока мы рассмотрели только функцию WinMain, причем она будет без существенных изменений сохраняться и в последующих примерах. Теперь мы должны написать оконную функцию. Строго говоря, описать ее лучше перед WinMain -- тогда не надо описывать ее прототип.

И еще одно замечание: после вызова функции RegisterClass, регистрирующей данную оконную процедуру, вы не должны вызывать ее напрямую -- это приведет к ошибке. Вызывать эту функцию может только Windows. Позже мы узнаем, почему это так и как можно ее вызвать самим.

Оконная функция должна быть декларирована следующим образом (в случае Win32 API ключевое слово _export может быть пропущено, подробнее об описании оконных функций см. в разделе, посвященном диспетчеру памяти):

LRESULT WINAPI _export proc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
// ...
}

В качестве аргументов мы получаем параметры переданного сообщения. Обычно оконные функции оформляются примерно по такой схеме:

LRESULT WINAPI _export proc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
// описание внутренних переменных

switch ( uMsg ) {
case WM_...:
// обработка нужного сообщения
break;

// обработка других сообщений...

default:
return DefWindowProc( hWnd, uMsg, wParam, lParam );
}
return 0L;
}

Главным элементом является конструкция switch, которая позволяет написать обработку каждого отдельного сообщения, полученного окном. В объектных библиотеках эти функции берет на себя базовый интерактивный объект, позволяющий связать определенные методы класса с получаемыми сообщениями.

Для упрощения написания оконной функции Windows предлагает специальную функцию

LRESULT DefWindowProc( hWnd, uMsg, wParam, lParam );

Эта функция реализует стандартную обработку сообщений, что позволяет описать окно, имеющее заданные нами стили и свойства и чистую внутреннюю область. Поэтому вы должны определять обработку только тех сообщений, которые вы хотите обрабатывать нестандартным образом, а все остальные передавать этой процедуре.

Сообщения, которые получает окно, информируют его о самых разных событиях, происходящих с этим окном, с приложением в целом, с самой системой и так далее. Сейчас мы рассмотрим четыре сообщения, которые применяются в рассмотренном примере, и их обработку.

Сообщение WM_CREATE

Самым первым мы рассмотрим сообщение WM_CREATE. Это сообщение посылается окну в тот момент, когда оно создается. Реальным созданием окна ведает функция CreateWindow, а не обработчик сообщения WM_CREATE. Вы в этот момент должны инициализировать свои переменные, выполнить необходимые настройки, создать требуемые объекты и прочее. При создании окно еще невидимо -- поэтому Вы можете менять его размеры, устанавливать в нужное положение, менять цвета не опасаясь мелькания на экране. Часто здесь создаются необходимые структуры и выделяются требуемые окном ресурсы.

Стандартная обработка этого сообщения необязательна -- функция DefWindowProc просто возвращает 0 в ответ на это сообщение.

Параметр wParam не используется, а параметр lParam содержит указатель В Windows все указатели, которые получает или передает Вам система являются 32х разрядными. В случае Windows API это будут дальние (far) указатели, а в случае Win32 это ближние (near), так как они уже являются 32х разрядными. на структуру CREATESTRUCT. В этой структуре передается основная информация о создаваемом окне.

typedef struct tagCREATESTRUCT {
void FAR* lpCreateParams; // указатель на дополнительные данные,
// переданный как параметр lpParam в вызове
// функции CreateWindow или CreateWindowEx
HINSTANCE hInstance; // хендл копии приложения, создавшей окно
HMENU hMenu; // хендл меню (или NULL, если нет)
HWND hwndParent; // хендл родительского окна (или NULL)
int cy, cx; // размеры окна
int y, x; // положение окна
LONG style; // стили окна
LPCSTR lpszName; // заголовок окна
LPCSTR lpszClass; // имя класса, к которому принадлежит окно
DWORD dwExStyle; // расширенные стили окна (см. CreateWindowEx)
} CREATESTRUCT;

Поля x, y, cx и cy в момент обработки сообщения WM_CREATE могут быть еще не определены. При необходимости получить информацию о размере или положении окна надо пользоваться функциями GetWindowRect или GetClientRect, которые возвращают корректный результат

Возвращаемое обработчиком значение:

не 0 -- возникла ошибка, окно надо уничтожить (далее, при уничтожении, будет получено сообщение WM_DESTROY), функция CreateWindow или CreateWindowEx вернет NULL.

0 -- окно успешно создано; функция CreateWindow или CreateWindowEx вернет хендл окна.

Сообщения WM_DESTROY и WM_QUIT

Еще одно сообщение, интересующее нас -- WM_DESTROY. Это сообщение посылается окну в момент его уничтожения. Это одно из последних сообщений, которое получает окно -- можно считать, что после этого оно уже не существует, причем в момент получения этого сообщения окно уже невидимо. В этот момент вы можете выполнить необходимые операции по освобождению выделенных при создании окна ресурсов. Оба параметра сообщения WM_DESTROY не используются.

Как и WM_CREATE сообщение WM_DESTROY является информационным -- реальное уничтожение окна осуществляется функцией DestroyWindow, а не обработчиком сообщения WM_DESTROY. Независимо от способа обработки этого сообщения окно будет уничтожено.

Если ваша оконная функция обслуживает главное окно приложения, то в момент уничтожения окна вы должны принять меры для завершения работы всего приложения в целом. Для этого вы должны послать сообщение WM_QUIT, при извлечении которого из очереди закончится цикл обработки сообщений. Если этого сообщения не послать, то цикл обработки сообщений продолжит работу дальше после закрытия всех имеющихся окон. На практике это означает что приложение вообще перестанет получать сообщения и “зависнет” на функции GetMessage, которая будет ждать, пока не придет новое сообщение. В случае Windows 3.x есть единственный способ удалить такое приложение -- перезапуск всего Windows (в Windows-95 или Windows NT такое приложение можно снять самому с помощью менеджера задач). Сообщение WM_QUIT посылается с помощью функции:

void PostQuitMessage( nExitCode );

Параметр nExitCode будет передан как wParam сообщения WM_QUIT и позже возвращен функцией WinMain в качестве параметра завершения.

Обработчиков для сообщения WM_QUIT писать не надо, так как:

Во-первых, функция GetMessage, получив это сообщение просто вернет FALSE, так что цикл обработки сообщений будет закончен без трансляции и диспетчеризации этого сообщения.

Во-вторых, это сообщение адресовано не окну, а приложению. То есть, даже если воспользоваться функцией PeekMessage для извлечения сообщения WM_QUIT из очереди (в отличие от GetMessage это получится), оно не будет отправлено никакому окну, так как хендл окна-получателя равен NULL.

Сообщение WM_PAINT

Последнее рассматриваемое нами сообщение -- WM_PAINT. Оба параметра сообщения WM_PAINT не используются. С этим сообщением нам придется разбираться подробнее. Раньше, когда мы обсуждали разделение экрана между разными задачами, говорилось о том, что в Windows невозможно полностью виртуализовать всю работу с экраном -- то есть содержимое невидимой в данной момент части окна для Windows остается неизвестным.

Представим себе, что невидимая часть окна стала видимой (например, вследствие перемещения другого окна) -- тогда возникает необходимость восстановить ее образ. Windows не знает, что там должно быть отображено, следовательно отображением может заниматься только само приложение.

Для этого вводится специальное сообщение WM_PAINT, которое посылается окну тогда, когда часть окна (или все окно) нуждается в перерисовке. Часто перерисовка окна занимает значительное время, а обработку сообщений рекомендуется выполнять как можно быстрее; поэтому Windows особым образом обрабатывает WM_PAINT, что бы задержки в обработке этого сообщения не оказывали такого влияния на работу системы.

Получив это сообщение, процедура обработки сообщений должна узнать, какую часть окна надо перерисовать, выполнить перерисовку, и сообщить Windows, что данная часть окна теперь корректна. Вы можете перерисовывать не только эту часть окна, а все окно сразу -- Windows проследит, что бы реальные изменения происходили только в той части, которая нуждается в перерисовке.

Приложение должно быть построено таким образом, что бы, получив сообщение WM_PAINT, оно могло перерисовать требуемую часть окна. То есть приложение должно знать, что, где и как должно быть нарисовано в окне, так как в любой момент может потребоваться многократная перерисовка какой-либо части окна.

Основы рисования в окне

Еще раз вспомним, каким образом появляется сообщение WM_PAINT: это происходит когда все окно, или только его часть становятся видимыми. Та часть окна, которая нуждается в перерисовке, является неверной -- ее содержимое не соответствует требуемому. Эта часть получила название неверный прямоугольник (invalid rectangle).

Соответственно, после перерисовки она перестает быть неверной -- то есть становится верным прямоугольником (valid rectangle).

Теперь стоит выяснить при каких условиях возникают неверные прямоугольники и как они используются. Для этого посмотрим на те ситуации, когда неверный прямоугольник может возникнуть, а когда нет:

Неверный прямоугольник возникает, если:

скрытая область окна (например, закрытая другим окном) становиться видимой.

область окна “прокручивается” (с помощью функций ScrollWindow или ScrollDC). В этом случае та часть окна, в которой должен появиться новый, невидимый ранее, текст, объявляется неверным прямоугольником.

окно изменяет размер. Обычно это приводит к появлению неверных прямоугольников только при увеличении размера окна и только на дополнительно появившейся поверхности. Однако, если при регистрации класса окна Вы указали стиль CS_HREDRAW или CS_VREDRAW, то все окно целиком будет рассматриваться как неверное.

Вами вызвана одна из функций:

void InvalidateRect( hWnd, lpRect, fErase );
void InvalidateRgn( hWnd, hRgn, fErase );

Параметры этих функций: hWnd -- хендл окна, содержащего прямоугольник (или регион), нуждающийся в перерисовке. lpRect -- указатель на структуру типа RECT, описывающую прямоугольник, или hRgn -- хендл нужного региона. fErase -- логическая (TRUE,FALSE) величина, указывающая, нужно ли восстанавливать фон неверной области, или нет.

Неверный прямоугольник не появляется при перемещении курсора мыши или пиктограмм через область окна -- Windows самостоятельно сохраняет и восстанавливает окно под курсором (или пиктограммой).

В некоторых случаях неверные прямоугольники могут создаваться, а могут и не создаваться. Это может быть, если часть окна становиться видимой после использования диалогов, выпадающих меню или после вызова функции MessageBox, являющейся частным случаем стандартного диалога. В этих случаях Windows может сохранять текст под появляющимся окном и восстанавливать его позже. Однако это делается не всегда и зависит от разных факторов -- размера диалога или меню, режима его создания и других факторов.

Еще о сообщении WM_PAINT

Допустим, что в результате какого-либо события появился неверный прямоугольник. Что с ним происходит дальше?

Windows, обнаружив неверный прямоугольник, принадлежащий какому-либо окну, ставит в очередь приложения сообщение WM_PAINT, адресованное этому окну. Оконная процедура, получив это сообщение, перерисовывает требуемую часть окна (или большую), после чего сообщает Windows о том, что неверный прямоугольник исправлен.

Если этого не сообщить Windows, то будет считаться, что прямоугольник остался неверным, и снова будут генерироваться сообщения WM_PAINT этому окну. Поэтому очень важно не оставлять после WM_PAINT неверных прямоугольников.

Таким образом осуществляется поддержание окна в требуемом состоянии. Однако перерисовка является медленным процессом (к тому же многие приложения перерисовывают большую часть окна, чем надо), а неверные прямоугольники могут появляться в нескольких экземплярах, иногда перекрывающихся, до того, как приложение начнет обрабатывать WM_PAINT.

Поэтому в Windows сообщение WM_PAINT трактуется не как сообщение, а скорее как состояние: если в очереди приложения есть сообщение WM_PAINT, то окно надо перерисовать. А если в это время появляются новые неверные прямоугольники, то новых сообщений WM_PAINT в очередь не попадает, а просто новый неверный прямоугольник объединяется со старым. Обычно в результате объединения возникает некоторая неверная область сложной формы В первых версиях Windows в результате объединения формировался новый неверный прямоугольник. В современных версиях вместо неверного прямоугольника реально формируется неверный регион, который может иметь сложную форму..

Сообщение WM_PAINT является низкоприоритетным. Чем отличается низкоприоритетное сообщение от нормального? Тем, что, если в очереди приложения есть только сообщения с низким приоритетом, то Windows может передать управление другому приложению, имеющему в очереди сообщения нормального приоритета (в Windows только два сообщения являются низкоприоритетными: WM_PAINT и WM_TIMER).

Эти две особенности позволяют Windows сравнительно легко переносить продолжительное рисование окна, хотя при этом перерисовываемая поверхность зачастую приближается к размерам всего экрана. Специфичная обработка сообщения WM_PAINT является причиной того, что в Windows рекомендуется сосредоточивать все операции по выводу в окно в обработчике сообщения WM_PAINT.

Если Вам надо самим осуществить какой-либо вывод в окно (понятно, что такая необходимость возникает и при обработке других сообщений), то настоятельно рекомендуется следующий метод:

все операции по выводу сосредотачиваются в обработчике сообщения WM_PAINT.

когда у Вас возникает необходимость осуществить вывод в окно, Вы вызываете функцию InvalidateRect, которая маркирует нужную часть окна как неверную, и, следовательно, в очередь приложения попадает сообщение WM_PAINT.

при этом сообщение только лишь оказывается в очереди, но оно не попадает на обработку немедленно, реальный вывод произойдет несколько позже. Если Вам надо именно сейчас осуществить вывод в окно, то добавьте вызов функции UpdateWindow. Эта функция немедленно передаст оконной процедуре сообщение WM_PAINT для его обработки.

Конечно, осуществление вывода при обработке других сообщений не является ошибкой, хотя делать этого не стоит, особенно если рисование сколько-нибудь сложное. К сожалению, всегда следовать этому правилу почти невозможно, однако нужны веские причины для его нарушения.

При обработке сообщения WM_PAINT стоит придерживаться нескольких правил, специфичных для этого сообщения:

Первое: Никогда не передавайте сообщение WM_PAINT непосредственно окну. Для этого существует функция UpdateWindow, которая генерирует это сообщение, если неверный прямоугольник существует.

Второе: Рекомендуется начинать обработку сообщения WM_PAINT с функции:

HDC BeginPaint( hWnd, lpPaintstruct );

и заканчивать функцией

void EndPaint( hWnd, lpPaintstruct );

где lpPaintstruct -- указатель на структуру типа PAINTSTRUCT. Эти функции выполняют несколько нужных дополнительных действий для обработки сообщения WM_PAINT, в том числе объявляют внутреннюю область окна корректной.

Наконец: Если все же Вы не используете BeginPaint...EndPaint, то обязательно объявляйте перерисованную область верной с помощью функции ValidateRect или ValidateRgn.

void ValidateRect( hWnd, lpRect );
void ValidateRgn( hWnd, hRgn );

Параметры этих функций: hWnd -- хендл окна, содержащего прямоугольник (или регион), нуждающийся в перерисовке. lpRect -- указатель на структуру типа RECT, описывающую прямоугольник, или hRgn -- хендл нужного региона.

Контекст устройства

Рассматривая сообщение WM_PAINT и неверные прямоугольники мы обсудили основные правила осуществления вывода в Windows. Теперь обзорно познакомимся с правилами осуществления вывода графической информации в окно.

В первом же написанном приложении 1a.cpp мы увидели, что функция вывода текста TextOut использовала не хендл окна, а так называемый хендл контекста устройства (HDC, device context). Зачем он понадобился? и почему бы не использовать вместо него хендл окна?

Дело в том, что все средства вывода в Windows относятся не к менеджеру окон, а к графическому интерфейсу устройств (GDI). GDI представляет собой библиотеку функций для выполнения графического вывода на различных устройствах, не только на дисплее. При создании GDI стремились сделать работу с устройствами независимой от самих устройств. Так, одни и те же функции, осуществляющие вывод текста, рисование линий и т.д. могут применяться для работы с дисплеем, принтером, плоттером и другими устройствами.

Для этого пришлось ввести дополнительное понятие -- контекст устройства, идентифицируемый его хендлом. Все функции вывода взаимодействуют с этим контекстом. Часто даже говорят, что функции GDI рисуют на контексте, а не непосредственно на устройстве. Контекст устройства описывает так называемые атрибуты контекста и непосредственно характеристики устройства, на котором осуществляется вывод.

Атрибуты контекста устройства независимы от самого устройства. Они характеризуют то изображение, которое будет рисоваться. В число атрибутов входят кисти, перья, цвет текста, цвет фона и многое другое. Так, назначив контексту устройства текущее перо вы определяете толщину, цвет и стиль (сплошная или прерывистая) тех линий, которые будут отображаться последующими вызовами функций, рисующих эти линии. При необходимости нарисовать линию другого стиля вы должны поменять текущее перо. Аналогично определяются кисть, используемая для закраски фона фигур, шрифт, применяемый при выводе текста, и много других атрибутов контекста устройства.

Информация об устройстве описывает непосредственно возможности самого графического устройства. Функции GDI взаимодействуют с устройством опосредованно -- через контекст и через драйвер этого устройства. Благодаря наличию информации об устройстве одна и та же функция GDI способна осуществлять вывод на любое устройство, для которого существует необходимый драйвер. Так, вы можете выбирать перо или кисть любого цвета, а GDI примет меры, что бы необходимое изображение было получено как на цветном дисплее, так и на черно-белом принтере или плоттере.

Как правило вы можете не заботиться о характеристиках устройств, на которых реально будет работать приложение. Однако, при разработке сложных приложений, которые могут широко распространяться, вы должны все-таки позаботиться о некоторых вопросах совместимости -- например, при назначении цветов стоит их подбирать так, что бы при переходе на черно-белое оборудование изображение осталось бы различимым. Часто лучшим решением является возможность легкой настройки программы пользователем под его конкретную аппаратуру.

Обычно надо предусматривать следующие варианты:

если приложение осуществляет вывод только в окно, то надо учитывать возможность работы:

с разным разрешением -- от 640x400, 640x480 и до часто встречающихся 1024x768, 1280x1024. Следует подчеркнуть, что в некоторых случаях возможны режимы работы монитора только с 400 строками развертки, а не с 480, как считают обычно. Было бы очень желательно, что бы даже в таком режиме все диалоги и окна умещались на экране.

с разным числом цветов -- от 8 и до более чем 16 миллионов цветов (16 777 216). Чисто монохроматические дисплеи (черный и белый) уже практически не встречаются, а вот дисплеи дешевых переносных компьютеров часто дают только 8-16 градаций серого; причем различимость цветов может быть невелика.

с разными настройками системной палитры; включая контрастные и энергосберегающие режимы (иногда применяются для переносных компьютеров).

если приложение способно выводить на принтер, то надо иметь в виду, что вместо принтера может оказаться плоттер, который хорошо рисует линии, но совершенно не может выводить растровых изображений, либо АЦПУ, которое способно только печатать текст. Перед выводом рисунков следует проверять возможности данного устройства Для проверки возможностей аппаратуры следует использовать функцию GetDeviceCaps..

Работа с контекстом устройства

Так как для рисования на каком-либо устройстве необходимо получить хендл контекста этого устройства, то естественно надо рассмотреть основные правила работы с контекстом и средства для его получения. Здесь будут обзорно рассмотрены основные правила работы с контекстом, для получения более подробной информации следует обратиться к разделу, посвященному работе с графикой.

Собственно существует две группы методов получения контекста устройства -- создание и получение контекста устройства. Разница связана с тем, что создание и, позже, уничтожение контекста устройства занимает некоторое время. Если вы собираетесь осуществлять вывод на принтер, то эти затраты времени ничтожно малы по сравнению со всем временем печати. Однако, если вы собираетесь только осуществлять рисование в окне (которое может обновляться очень часто), то даже сравнительно быстрая операция создания контекста, повторенная многократно, займет значительное время. Поэтому в Windows существует несколько заранее созданных контекстов, соответствующих дисплею. При выводе в окно контекст создавать не надо, надо воспользоваться одной из функций, возвращающих такой заранее заготовленный контекст устройства.

Более того, в Windows методы, создающие контекст, предназначены для работы с устройством целиком, а методы, возвращающие уже существующий -- с окном. Разница заключается в применении системы координат, связанной с контекстом. В первом случае система координат связана с верхним левым углом устройства, а во втором случае -- с верхним левым углом внутренней области окна В частном случае -- с верхним левым углом самого окна, включая его обрамление (см. описание функции GetWindowDC)..

Сейчас мы сосредоточимся только на двух способах получения контекста устройства и на некоторых общих правилах применения этого контекста. С первым способом мы уже познакомились -- он основан на функциях BeginPaint и EndPaint, а второй на функциях GetDC и ReleaseDC:

HDC GetDC( hWnd );
void ReleaseDC( hWnd, hDC );

Оба способа возвращают заранее заготовленный контекст устройства, однако делают это по разному. Функции BeginPaint и EndPaint предназначены для обработки сообщения WM_PAINT. В других случаях пользоваться этими функциями не рекомендуется. Это связано с тем, что:

эти функции объявляют окно корректным

возвращаемый контекст устройства соответствует даже не внутренней области окна, а только неверной области. То есть система координат контекста будет связана с верхним левым углом окна, а вот область рисования будет соответствовать только неверной области. При попытке рисовать на таком контексте все, что не попадает в неверную область, не будет рисоваться. Это сделано для некоторого ускорения перерисовки окна.

функция BeginPaint дополнительно принимает меры к закраске фона той кисточкой, которая была задана при регистрации класса окна. Это позволяет при разработке обработчика сообщения WM_PAINT не заботиться о закраске фона окна.

Вторая рассматриваемая пара функций (GetDC, ReleaseDC) этих операций не делает, но зато она возвращает контекст для всей внутренней области окна, а не только для неверной области. При необходимости использовать именно эти функции в обработчике сообщения WM_PAINT необходимо самостоятельно принять меры к закраске фона и к объявлению окна корректным.

Все рассматриваемые нами функции для получения контекста устройства приводились в паре -- функция для получения и функция для освобождения. Это связано с тем, что применение полученного контекста устройства должно быть ограничено обработкой только текущего сообщения. Оставлять такой контекст занятым нельзя, так как в системе зарезервировано только 8 таких контекстов; если контекст не освободить, то несколько одновременно отображаемых окон (а в Windows почти всегда одновременно работает несколько приложений), могут занять все контексты и при попытке что-то нарисовать в следующем окне возникнет ошибка.

В процессе рисования вы будете постоянно изменять атрибуты контекста -- выбирать новые кисти, перья, изменять цвета и режимы рисования и так далее. Все эти изменения действуют только в то время, пока контекст существует. Как только контекст освобождается (или уничтожается, если он был создан), то все изменения, сделанные в его атрибутах, пропадают. Контекст, который вы получаете, практически всегда Кроме случаев применения сохраняемых контекстов. См. стили класса окон CS_OWNDC и CS_CLASSDC. настроен стандартным образом.

Системы координат Windows

При рисовании на контексте устройства вам придется задавать координаты выводимого текста и изображаемых фигур в определенной системе координат. Система координат, связанная с контекстом устройства -- система координат GDI, определяет правила преобразования координат x и y всеми функциями GDI. Вы можете сами определять различные масштабные коэффициенты по обеим осям, задавать положение начала отсчета этой системы координат, либо использовать одну из стандартных систем координат GDI. В главе, посвященной выводу графических изображений на контексте устройства, мы подробнее познакомимся с этими системами координат.

Сейчас же надо выделить несколько основных систем координат, применяемых Windows, и уточнить области применения этих систем координат.

Первая рассматриваемая система координат -- система координат менеджера окон. В этой системе ось x направлена по горизонтали направо, ось y -- по вертикали вниз. Начало отсчета (0,0) связана либо с верхним левым углом дисплея, либо с верхним левым углом внутренней области родительского окна. Цена одной единицы этой системы координат равна одной единице устройства (пикселю). Для пересчета координат из системы отсчета, связанной с внутренней областью окна в систему отсчета, связанную с экраном (и наоборот) используются функции ClientToScreen и ScreenToClient.

Очевидно, что такая система отсчета удобна далеко не во всех случаях. Например, если окно содержит текст, либо отдельные элементы с текстом, то размеры текста (или элементов) будут зависеть от используемого шрифта. В такой ситуации было бы удобно для задания размеров и положения текста применять не единицы устройства, а величины, производные от размера символов. Пример таких окон -- панели диалога. На таких панелях обычно располагается значительное количество кнопок, флажков, списков, просто статического текста и пр., а, так как одно и то же приложение и одними и теми же панелями диалогов может работать на самых разных компьютерах с различными видеоадаптерами, мониторами, системными шрифтами и версиями Windows, то и размеры панелей должны определяться пропорционально размерам шрифта, используемого этими панелями.

При описании панелей диалогов используется своя собственная система координат -- система координат панели диалога. В этом случае начало отсчета помещается в верхний левый угол внутренней области панели диалога Панель диалога является родительским окном для всех элементов управления, размещенных на этой панели, включая статические -- рамки, текст и прочее., ориентация осей координат сохраняется прежней, а в качестве единиц отсчета применяют по оси x -- одну четвертую от средней ширины символа шрифта, а по оси y -- одну восьмую от высоты шрифта. Обычно эти величины примерно соответствуют одному пикселю. Дополнительную информацию можно получить, например, из описания функции MapDialogUnits.

Сложность в применении этой системы координат связана с понятием средней ширины символа. Дело в том, что подавляющее большинство шрифтов является пропорциональными -- то есть каждый символ шрифта имеет свою ширину. Для вычисления «средней ширины» применяют ширины символов алфавита, взвешенные с учетом частотности встречи символов в общелитературном тексте. Как правило -- английском. Все это может привести к некоторым ошибкам в задании положения и размеров при использовании иного языка, чем английский, особенно если при этом используются нестандартные шрифты. В Windows-95 легко наблюдать этот эффект, изменяя с помощью панели управления (control panel) размеры используемых шрифтов и наблюдая за отображением стандартных диалогов.

Как построить приложение

После разработки исходного текста приложения возникает необходимость его скомпилировать и построить файл образа задачи (выполняемый файл, exe-файл). В книге не будет даваться подробное описание конкретных методов построения приложений -- эту информацию стоит почерпнуть из руководств по вашему компилятору. В зависимости от применяемой среды разработки приложений и даже от способа ее применения процесс построения приложений будет изменяться.

В данном разделе будут рассмотрены только основные шаги, из которых состоит построение приложения для Windows и исходные файлы, которые могут для этого потребоваться. Все среды построения приложений будут так или иначе реализовывать эти шаги и разработчику надо будет ориентироваться в тех исходных файлах, которые будут применяться или создаваться в процессе генерации выполняемого файла.

Как строили Windows-приложения в самом начале

Предварительно начнем с истории, времен ранних версий Windows (до 3.x). В те времена, когда Windows только начинал развиваться, компиляторы не содержали практически никаких средств поддержки процесса создания приложений для Windows. В этом случае практически вся работа сваливалась на программистов, которые должны были разрабатывать дополнительные текстовые файлы, необходимые для построения приложений.

Основные сложности были связаны с двумя особенностями приложений для Windows:

приложение поддерживало динамическую загрузку библиотек. В случае DOS все необходимое для работы приложения находилось в файле образа задачи, а в случае Windows большое количество функций, необходимых приложению, содержится в динамически загружаемых библиотеках (часто это компоненты операционной системы). В таких случаях говорят, что приложение импортирует (import) функции. В то же время приложение должно предоставлять часть своих функций для того, что бы их вызывала операционная система (как, скажем, оконная процедура). В этих случаях говорят, что приложение экспортирует (export) функции.

приложение содержало дополнительные данные, называемые ресурсами приложения (не путать с ресурсами компьютера или операционной системы). В виде ресурсов приложения могли выступать пиктограммы, курсоры, меню, диалоги и пр., использовавшиеся приложением. Эти ресурсы включались в специальном формате в файл образа задачи.

Обычная DOS-задача не могла делать таких вещей. Поэтому в Windows был принят новый формат выполняемого файла, а для нормального построения образа задачи пришлось изменить стандартный компоновщик (linker) так, чтобы он мог использовать информацию об экспортируемых и импортируемых функциях и собирать выполняемые файлы в новом формате. Соответственно, такой компоновщик нуждался в информации об экспортируемых и импортируемых функциях, а также о некоторых дополнительных параметрах Windows-приложения. Чтобы предоставить эту информацию компоновщику надо было написать специальный текстовой файл -- файл описания модуля (def-файл).

В файле описания модуля перечислялись имена экспортируемых функций, имена импортируемых и библиотеки, содержащие функции, подлежащие импорту, задавался размер стека, давалось короткое описание приложения и пр. Это было не слишком удобно, так как при вызове какой-либо новой функции из Windows API (а их более 1000), необходимо было добавлять ее имя в файл описания модуля.

В тех случаях, когда приложение нуждалось в собственных ресурсах, необходимо было отдельно описать эти ресурсы в специальном текстовом файле описания ресурсов (rc-файле). Вместе с компиляторами до сих поставляется специальный компилятор ресурсов RC.EXE, который воспринимает файл описания ресурсов, компилирует его и создает файл ресурсов приложения (res-файл). Затем тот-же компилятор ресурсов может объединить уже построенный выполняемый файл с файлом ресурсов приложения.

Таким образом построение приложения состояло из следующих этапов:

разработка исходных текстов -- .c, .cpp, .h, .hpp, .rc и .def файлы;

компиляция исходного текста программы -- из .c и .cpp получаем .obj (иногда .lib);

компиляция ресурсов приложения -- из .rc получаем .res;

компоновка приложения -- из .obj и .lib получаем .exe;

встраивание ресурсов приложения в выполняемый файл -- из .exe и .res получаем рабочий .exe.

Конечно, если собственных ресурсов приложение не имело, то построение задачи несколько упрощалось. Но и при этом необходимость перечислять все экспортируемые и импортируемые функции в файле описания приложения была крайне неудобной.

Следующий этап

Очевидно, что необходимость перечислять кучу имен функций в файле описания приложения никого не приводила в восторг. Поэтому на следующем этапе была включена поддержка Windows-приложений непосредственно в компиляторы Ориентировочно, начиная с компиляторов для Windows 3.0. Для этого было добавлено ключевое слово _export (иногда __export), которое применяется при описании экспортируемых функций непосредственно в тексте C-программы. Для таких функций компилятор включает в объектный файл специальную информацию, так что компоновщик может правильно собрать выполняемый файл. Так, например, было сделано в первом примере для оконной процедуры:

LRESULT WINAPI _export proc( ...

Помимо экспортированных функций, в файле описания приложения было необходимо перечислять все импортированные функции. А этот список был самым большим (Windows экспортирует более 1000 функций, которые могут импортироваться приложениями). Для решения этой задачи был изменен формат библиотек объектных файлов, так что появилась возможность описать имена функций, экспортированных другими модулями. Так компоновщик задачи может определить, какие функции импортируются и из каких модулей.

В этом случае составление файла описания модуля стало тривиальным -- длинные списки имен экспортированных и импортированных функций исчезли, а остальная информация заполнялась, как правило, однообразно для разных приложений. Компоновщики задач получили возможность обходиться вообще без файлов описания модуля -- при его отсутствии подставлялись стандартные значения. С этого момента простейшее приложение можно было создать, написав только один файл с исходным текстом программы.

Библиотеки со списками функций, которые можно импортировать из Windows (то есть экспортированных компонентами Windows) входят в состав всех компиляторов. Иногда может возникнуть необходимость использования нестандартного компонента (или собственной динамически загружаемой библиотеки), для которых соответствующей библиотеки с ссылками нет. В таком случае можно воспользоваться специальным приложением -- implib.exe -- которое входит в состав большинства компиляторов (если его нет в составе компилятора, то значит его возможности реализованы в каком-либо другом инструменте, как, например, wlib.exe в Watcom C/C++). Implib позволяет по имеющемуся файлу динамически загружаемой библиотеки (.dll) или файлу описания проекта модуля библиотеки (.def), содержащему список экспортированных функций, построить библиотечный файл (.lib), с ссылками на функции библиотеки.

Первоначально, стремясь максимально уменьшить время загрузки модулей в память, при экспортировании функций им присваивались уникальные номера (назначаются разработчиком, либо просто по порядку). Конкретная функция однозначно определяется именем экспортирующей библиотеки динамической загрузки и идентификатором функции. Для задания идентификаторов экспортируемых функций используется файл описания модуля. Однако использование номеров вместо имен является не слишком удобным для человека, поэтому в Windows используются оба метода -- функции доступны как по их идентификаторам, так и по их именам. Для Windows API общепринятым методом считается использование идентификаторов -- Microsoft следит за тем, что бы все документированные функции сохраняли свои идентификаторы. А в Win32 API предполагается использование имен; более того, Microsoft не гарантирует, что идентификаторы документированных функций не будут изменяться.

Построение приложения сохранило в себе все шаги, однако создание файла описания приложения стало необязательным. При разработке приложений для Win32 API с файлами описания модуля практически не приходится иметь дело, он используется в очень редких случаях для построения динамически загружаемых библиотек. В настоящее время и в случае Windows API, и в случае Win32 API этот файл создается только если необходимо обеспечить нестандартную компоновку модуля, имеющего, например, разделяемые сегменты данных.

Современные компиляторы и системы поддержки проектов фактически остались в рамках рассмотренного порядка построения приложения. Небольшие изменения реализованы в разных компиляторах независимо друг от друга. Так, иногда включение файла ресурсов приложения в выполняемый модуль выполняется не компилятором ресурсов, а непосредственно компоновщиком; в других случаях специальные редакторы ресурсов позволяют обойтись без построения файла описания ресурсов (.rc), а создать непосредственно файл ресурсов приложения (.res). Особенно часто это делается в системах визуального программирования.

Если вы используете какую-либо систему поддержки проектов (Watcom IDE, Microsoft Developer Studio, Borland IDE, Symantec IDE и пр.) -- а скорее всего это именно так -- то вы должны только проследить за тем, что бы необходимые файлы были включены в проект. Система сама отследит, как и когда должен использоваться тот или иной исходный файл.

Страницы: 1, 2, 3, 4


© 2010 BANKS OF РЕФЕРАТ