Смекни!
smekni.com

Ознакомление с приложениями Windows (стр. 5 из 18)

Win32

Здесь же надо еще раз отметить, что для Win32 API всегда применяется 32х разрядная flat–модель памяти. В этом случае модификаторы far и near не применяются. Кроме того хендл, соответствующий типу unsigned int, становится 32х разрядным. Это на самом деле приводит к изрядным сложностям при переходе с платформы на платформу. Дело в том, что в Windows API хендл часто объединяется с какими–либо дополнительными данными и размещается в одном двойном слове, передаваемом в качестве параметра функции или сообщения, а в Win32 такое уже не получится — хендл сам занимает все двойное слово.

Кроме того, в Win32 API для работы с файлами используется опять–таки хендл, но уже не типа HFILE, а HANDLE. При этом нулевое значение по–прежнему является допустимым и обозначает стандартное устройство вывода, а значение -1 — неверный хендл. Для обозначения неверного хендла файла определен символ INVALID_HANDLE_VALUE, равный -1. Для других хендлов, кроме хендлов файлов, этот символ не применяется, так как для индикации ошибки применяется значение 0. При этом тип HFILE и символ HFILE_ERROR определены также, как и в 16ти разрядных Windows — в виде 16ти разрядного целого числа. В принципе допустимо простое приведение типов, однако в будущих реализациях WindowsAPI ситуация может измениться, так как тип HANDLE соответствует 32х разрядному числу.

Венгерская нотация

При чтении текстов C—программ и документации Вы обратите внимание на несколько странное написание имен переменных и функций. Например:

lpszFileName, wNameLength

Разработчики Windows рекомендуют применять специфичные правила описания имен переменных, которые получили название “венгерская нотация” по национальности программиста Charles Simonyi из Microsoft, предложившего ее. Применение венгерской нотации улучшает читаемость программ и уменьшает вероятность ошибки. Хотя, конечно, это дается ценой увеличения длины имен переменных.

Хорошим программистским правилом является использование мнемонических имен переменных. Венгерская нотация предполагает не только применение мнемоники для определения смысла переменной (как, например, FileSize), но и включение ее типа в имя. Например lpszFileName обозначает дальний указатель на ASCIIZ[7] строку символов, содержащую имя файла.

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

обозначающий символ название обозначаемого типа пояснение
c char символ
by BYTE байт
n int целое число
x short координата или размер
y short координата или размер
i int целое число
f, b BOOL логическая величина
w WORD слово без знака
h HANDLE хендл
l LONG длинное целое со знаком
dw DWORD длинное целое без знака
e FLOAT число с плавающей запятой
*fn функция
s строка
sz строка, оканчивающаяся '\0' (ASCIIZ)
p * указатель на ...
lp far* дальний указатель на ...
np near* ближний указатель на ...

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

Так, в качестве примера можно привести название поля cbWndExtra в структуре WNDCLASS. В данном случае префикс cb расшифровывается как Count of Bytes.


Структура приложения Windows

Итак, еще раз посмотрим на приложение 1a.cpp и вспомним, что надо сделать для написания приложения:

· написать оконную функцию;

· зарегистрировать эту функцию в Windows;

· создать окно, принадлежащее данному классу;

· обеспечить работу приложения, обрабатывая поступающие окну сообщения.

В рассматриваемом нами примере выполняются все эти действия. Исходный код содержит следующие функции: WinProc, init_instance и WinMain. Здесь WinProc является оконной процедурой, init_instance регистрирует класс окон (оконную процедуру), а WinMain создает окно и обеспечивает работу всего приложения.

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

1. Операционная система загружает исполняемый файл в память и выделяет требуемые для первоначальной загрузки ресурсы.

2. Управление передается специально написанному разработчиками компиляторов startup–коду, который инициализирует приложение, получает необходимую информацию (как, например, командная строка, хендл копии приложения и пр.), запускает конструкторы статических объектов.

3. startup–код вызывает функцию WinMain, передавая ей полученные от операционной системы данные. Функция WinMain разрабатывается для каждого приложения.

4. WinMain обычно осуществляет регистрацию класса окон.

5. Далее WinMain создает и отображает главное окно приложения.

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

7. Оконная процедура выполняет обработку сообщений, направленных окну, обеспечивая реакцию задачи на действия пользователя.

8. Приложение работает, пока пользователь не закроет главное окно этого приложения. В момент закрытия этого окна оконная процедура принимает специальные меры для завершения цикла обработки сообщений, организованного WinMain.

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

10.Возврат управления из функции WinMain происходит опять–же в промежуточный exit–код, созданный разработчиками компиляторов. Он запускает деструкторы статических объектов, деинициализирует приложение и возвращает управление в систему.

11.Система освобождает оставшиеся занятыми ресурсы, закрепленные за этим приложением.

Теперь, перед тем как перейти к рассмотрению непосредственно кода приложения, надо сделать последнее замечание. Windows API разрабатывался тогда, когда систем виртуальной памяти на персональном компьютере еще не существовало. Первые версии Windows могли работать на XT с 640K оперативной памяти. Из–за очень ограниченного объема памяти приходилось идти на различные ухищрения. Так один из способов экономии памяти связан с тем, что код приложений обычно не изменяется. Если запустить две копии одного и того–же приложения (скажем, два Notepad’а для редактирования двух разных файлов), то код этих приложений будет одинаковым. В этом случае его можно загружать только один раз, но для необходимости как–то различать разные копии приложения и возникло понятие хендл копии приложения (instance handle, HINSTANCE).

Функция WinMain

Обычная программа на C (C++) содержит так называемую главную процедуру main. При создании программ для Windows тоже необходимо описать такую процедуру, правда она называется WinMain и имеет другие аргументы:

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
// ...
}

Описание главной функции ключевым словом PASCAL указывает на применение соглашений языка Pascal при передаче аргументов и вызове функции (так делается в большинстве функций Windows, потому что вызов pascal–декларированной функции осуществляется чуть быстрее и занимает меньше места, чем C–декларированной).

Рассмотрим ее аргументы:

HANDLE hInstance — этот параметр является хендлом, указывающим конкретную копию приложения. Знание этого хендла потребуется для связи тех или иных данных с конкретной копией.

HANDLE hPrevInstance — описывает хендл предыдущей копии приложения. Если данная копия является первой, то эта переменная содержит NULL. Использование этой информации несколько специфично:

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

Внимание: при использовании C++ иногда удобно описать статический объект. Однако в этом случае может потребоваться информация о hInstance для конструктора статического объекта. По странной причине мы ее не можем узнать до вызова WinMain — эта информация известна с самого начала (еще до вызова каких–либо конструкторов): startup–код в самых первых инструкциях обращается к процедуре INITTASK, которая возвращает системную информацию, в том числе hInstance. После этого hInstance копируется в статическую переменную, используемую startup– и exit– кодом, однако эта переменная является локальной (?!) и недоступна для остальных модулей приложения. Причина такого поступка со стороны разработчиков компиляторов остается непонятной.

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