ЭКЗАМЕНАЦИОННЫЙ БИЛЕТ № 2
1. Основы программирования в операционной системе Windows.
Программирование в Windows основывается на использовании функций API. Их количество достигает двух тысяч. Ваша программа в значительной степени будет состоять из таких вызовов. Все взаимодействие с внешними устройствами и ресурсами операционной системы будет происходить посредством таких функций.
· Список функций API и их описание лучше всего брать из файла WIN32.HLP, который поставляется, например, с пакетом Borland C++.
· Главным элементом программы в среде Windows является окно. Для каждого окна определяется своя процедура обработки сообщений (см. ниже).
· Окно может содержать элементы управления: кнопки, списки, окна редактирования и др. Эти элементы, по сути, также являются окнами, но обладающими особыми свойствами. События, происходящие с этими элементами (и самим окном), приводят к приходу сообщений в процедуру окна.
· Операционная система Windows использует линейную модель памяти. Другими словами, всю память можно рассматривать как один сегмент. Для программиста на языке ассемблера это означает, что адрес любой ячейки памяти будет определяться содержимым одного 32-битного регистра, например EBX.
· Мы фактически не ограничены в объеме данных, кода или стека (объеме локальных переменных). Выделение в тексте программы сегмента кода и сегмента данных является теперь простой формальностью, улучшающей читаемость программы.
· Операционная система Windows является многозадачной средой. Каждая задача имеет свое адресное пространство и свою очередь сообщений. Более того, даже в рамках одной программы может быть осуществлена многозадачность - любая процедура может быть запущена как самостоятельная задача.
Начнем с того, как можно вызвать функции API, например, MessageBox. Это ее синтаксис:
int MessageBox ( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType );
Данная функция выводит на экран окно с сообщением и кнопкой (или кнопками) выхода. Для каждого аргумента определены тип (32-битные целые числа) и значение:
· hWnd -дескриптор окна, в котором будет появляться окно-сообщение, тип HWND — 32-битное целое.
· lpText - текст, который будет появляться в окне, тип LPCTSTR — 32-битный указатель на строку.
· lpCaption - текст в заголовке окна, тип LPCTSTR — 32-битный указатель на строку.
· uType - тип окна, в частности можно определить количество кнопок выхода. Тип UINT — 32-битное целое.
К имени функций нужно добавлять суффикс "А" при использовании кодировки символов ANSI, кроме того, при использовании MASM необходимо также в конце имени добавить @16. Вызов указанной функции будет выглядеть так:
CALL MessageBoxA@16.
Аргументы будут извлекаться из стека в порядке их упоминанию в функции. Чтобы это было возможно надлежит ввести в стек до вызова функции в обратномпорядке.
Пример для MessageBox.
; Создать аргументы
МВ_ОК equ 0 ; uType создать
STR1 DB "Неверный ввод! ",0 ; lpText создать
STR2 DB "Сообщение об ошибке.",0 ; lpCaption создать
HW DWORD ? ; hwind создать
; Поместить аргументы в стек
PUSH МВ_ОК ; uType в стек
PUSH OFFSET STR2 ; lpCaption в стек
PUSH OFFSET STR1 ; lpText в стек
PUSH HW ; hwind в стек
CALL MessageBoxA@16 ; вызов функции
2. Создание, закрытие объекта ядра.
Создание, открытие и прочие операции с объектами ядра станут для Вас, как разработчика Windows-приложений, повседневной рутиной. Система позволяет создавать и оперировать с несколькими типами таких объектов, в том числе, маркерами доступа (access token objects), файлами (file objects), проекциями файлов (file-mapping objects), портами завершения ввода-вывода (I/O completion port objects), заданиями (job objects), почтовыми ящиками (mailslot objects), мьютсксами (mutex objects), каналами (pipe objects), процессами (process objects), семафорами (semaphore objects), потоками (thread objects) и ожидаемыми таймерами (waitable timer objects). Эти объекты создаются Windows-функциями Например, CreateFtleMapping заставляет систему
сформировать объект "проекция файла". Каждый объект ядра — на самом деле просто блок памяти, выделенный ядром и доступный только ему. Этот блок представляет собой структуру данных, в элементах которой содержится информация об объекте. Некоторые элементы (дескриптор защиты, счетчик числа пользователей и др.) присутствуют во всех объектах, но большая их часть специфична для объектов конкретного типа. Например, у объекта "процесс" есть идентификатор, базовый приоритет и
код завершения, а у объекта "файл" — смещение в байтах, режим разделения и режим открытия
Поскольку структуры объектов ядра доступны только ядру, приложение не может самостоятельно найти эти структуры в памяти и напрямую модифицировать их содержимое Такое ограничение Microsoft ввела намеренно, чтобы ни одна программа не нарушила целостность структур объектов ядра. Это же ограничение позволяет Microsoft вводить, убирать или изменять элементы структур, нс нарушая работы каких-либо приложений.
Создание объекта ядра
Когда процесс инициализируется в первый paз, таблица описателей еще пуста. Но стоит одному из его потоков вызвать функцию, создающую объект ядра (например, CreateFtleMapping), как ядро выделяет для этого объекта блок памяти и инициализирует его, далее ядро просматривает таблицу описателей, принадлежащую данному процессу, и отыскивает свободную запись. Поскольку таблица еще пуста, ядро обнаруживает структуру с индексом 1 и инициализирует ее. Указатель устанавливается на внутренний адрес структуры данных объекта, маска доступа — на доступ без ограничений и, наконец, определяется последний компонент — флаги (О флагах мы поговорим позжс, в разделе о наследовании )
Вот некоторые функции, создающие объекты ядра (список ни в коей мере на полноту не претендует)
HANDLE CreateThread(
PSECURITY_ATTRIBUTES psa,
DWORD dwStackSize,
PTHREAD_START_ROUTINE pfnStartAddr,
PVOID pvParam,
DWORD dwCreationFlags,
PDWORD pdwfhreadId);
HANDEE CreateFile(
PCTSTR pszFileName,
DWORD dwDesiredAccebS,
DWORD dwShareMode,
PSECURITY_ATTRIBUTES psa,
DWORD dwCreationDistribution,
DWORD dwFlagsAndAttnbutes,
HANDEE hTemplateFile);
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD flProtect,
DWORD dwMdximumSizcHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);
HANDLE CreateSemaphore(
PSECURITY_ATTRIBUTES psa,
LONG lInitialCount,
LONG lMaximumCount,
PCTSTR pszName);
Все функции, создающие объекты ядра, возвращают описатели, которые привязаны к конкретному процессу и могут быть использованы в любом потоке данного процесса Значение описателя представляет собой индекс в таблице описателей, принадлежащей процессу, и таким образом идентифицирует место, где хранится информация, связанная с объектом ядра. Вот поэтому при отладке своего приложения и просмотре фактического значения описателя объекта ядра Вы и видите такие малые величины: 1, 2 и т. д. Но помните, что физическое содержимое описателей не задокументировано и может быть изменено. Кстати, в Windows 2000 это значение определяет, по сути, не индекс, а скорее байтовое смещение нужной записи от начала таблицы описателей.
Всякий раз, когда Вы вызываете функцию, принимающую описатель объекта ядра как аргумент, Вы передаете ей значение, возвращенное одной из Create-функций. При этом функция смотрит в таблицу описателей, принадлежащую Вашему процессу, и считывает адрес нужного объекта ядра.
Если Вы передаете неверный индекс (описатель), функция завершается с ошибкой и GetLastError возвращает 6 (ERROR_INVALID_HANDLE). Это связано с тем, что на самом деле описатели представляют собой индексы в таблице, их значения привязаны к конкретному процессу и недейовительны в других процессах.
Если вызов функции, создающей объект ядра, оказывается неудачен, то обычно возвращается 0 (NULL). Такая ситуация возможна только при острой нехватке памяти или при наличии проблем с защитой. К сожалению, отдельные функции возвращают в таких случаях пе 0, а -1 (INVALID_HANDLE_VALUE) Например, если CreateFile не сможет открыть указанный файл, она вернет именно INVALID_HANDLE_VALUE. Будьте очень осторожны при проверке значения, возвращаемого функцией, которая создает объект ядра. Так, для CreateMutex проверка на INVALID_HANDlE_VALUE бессмысленна:
HANDLE hMutex = CreateMutex(...);
if (hMutex == lNVALID_HANDLE_VALUE) {
// этот код никогда не будет выполнен, так как
// при ошибке CreateMutex возвращает NLlLL
}
Точно так же бессмыслен и следующий код:
HANDIE hFile = CreateFile(.. );
if (hFile — NULL} {
// и этот код никогда не будет выполнен, так как
// при ошибке CreateFile возвращает lNVALID_HANDLE_VALUE (-1)
}
Закрытие объекта ядра
Независимо от того, как именно Вы создали объект ядра, по окончании работы с ним его нужно закрьпь вызовом CloseHandle
BOOL CloseHandle(HANDLE hobj);
Эта функция сначала проверяет таблицу описателей, принадлежащую вызывающему процессу, чтобы убедиться, идентифицирует ли переданный ей индекс (описа
тель) объект, к которому этот процесс действительно имеет доступ. Если переданный индекс правилен, система получает адрес структуры данных объекта и уменьшает в этой структуре счетчик числа пользователей; как только счетчик обнулится, ядро удалит объект из памяти.
Если же описатель невереи, происходит одно из двух. В нормальном режиме выполнения процесса CloseHandle возвращает FALSE, a GetLastError — код ERROR_INVALID_HANDLE. Но при выполнении процесса в режиме отладки система просто уведомляет отладчик об ошибке.
Перед самым возвратом управления CloseHandle удаляет соответствующую запись из таблицы описателей: данный описатель тспсрь недействителен в Вашем процессе и использовать его нельзя. При этом запись удаляется независимо от того, разрушен объект ядра или нет! После вызова CloseHandle Вы больше не получите доступ к это-
му объекту ядра; но, если его счетчик не обнулен, объект остается в памяти Тут все нормально, это означает лишь то, что объект используется другим процессом (или процессами). Когда и остальные процессы завершат свою работу с этим объектом (тоже вызвав CloseHandle), он будет разрушен.
А вдруг Вы забыли вызвать CloseHandle — будет ли утечка памяти? И да, и нет. Утечка ресурсов (тех же объектов ядра) вполне вероятна, пока процесс еще исполняется. Однако по завершении процесса операционная система гарантированно освобождает все ресурсы, принадлежавшие этому процессу, и в случае объектов ядра действует так: в момент завершения процесса просматривает его таблицу описателей и закрывает любые открытые описатели.
3. Включение процесса в задание