Описание функций приложения
Приведем описание функций, определенных в нашем приложении. Для экономии места мы не будем подробно описывать фрагменты кода, связанные с созданием дочерних MDI-окон. При необходимости обращайтесь к 17 тому “Библиотеки системного программиста”, в котором приведена подробная информация о создании MDI-приложений.
Описание исходных текстов приложения
Если вы читали нашу книгу “Операционная система Microsoft Windows 95 для программиста” (22 том “Библиотеки системного программиста”), то вы уже сталкивались с 32-разрядными приложениями, работающими в сплошной модели памяти. При создании приложения VIRTUAL мы использовали те же инструментальные средства, что и в упомянутой книге, а именно Microsoft Visual C++ версии 4.0.
Определение каталога Microsoft Windows NT
Функция GetWindowsDirectory позволяет узнать путь к каталогу, в который установлена операционная система Windows (например, C:\WINNT):
UINT GetWindowsDirectory(
LPTSTR lpBuffer, // адрес буфера
UINT uSize); // размер буфера в байтах
Определение количества дисковых устройств в системе
Для того чтобы определить список установленных в системе логических дисковых устройств, вы можете вызвать функцию GetLogicalDrives:
DWORD GetLogicalDrives(VOID);
Эта функция не имеет параметров и возвращает 32-разрядное значение, каждый бит которого отвечает за свое логическое устройство. Самый младший, нулевой бит соответствует устройству с идентификатором A:, бит с номером 1 - устройству с идентификатором A:, и так далее. Если бит установлен, устройство присутствует в системе, если нет - отсутствует.
Более развернутую информацию о составе логических дисковых устройств в системе можно получить при помощи функции GetLogicalDriveStrings:
DWORD GetLogicalDriveStrings(
DWORD nBufferLength, // размер буфера
LPTSTR lpBuffer); // адрес буфера для записи
// сведений об устойствах
Если вызвать эту функцию с параметрами nBufferLength и lpBuffer равными, соответственно, 0 и NULL, она вернет размер буфера, необходимый для записи информации о всех логических дисковых устройствах, присутствующих в системе. После этого вы можете вызвать функцию GetLogicalDriveStrings еще раз, заказав предварительно буфер нужного размера и указав функции правильный размер буфера.
Функция GetLogicalDriveStrings заполнит буфер текстовыми строками вида:
a:\
b:\
c:\
Каждая такая строка закрыта двоичным нулем. Последняя строка будет закрыта двумя двоичными нулями.
Определение параметров логического устройства
Одним из наиболее интересных параметров логического устройства является размер свободного пространства на нем. Этот параметр вместе с некоторыми другими вы можете определить при помощи функции GetDiskFreeSpace:
BOOL GetDiskFreeSpace(
LPCTSTR lpRootPathName, // адрес пути к корневому каталогу
LPDWORD lpSectorsPerCluster,// количество секторов в кластере
LPDWORD lpBytesPerSector, // количество байт в секторе
LPDWORD lpNumberOfFreeClusters, // количество свободных
// кластеров
LPDWORD lpTotalNumberOfClusters); // общее количество
// кластеров
Перед вызовом этой функции вы должны подготовить несколько переменных типа DWORD и передать функции их адреса. Функция GetDiskFreeSpace запишет в эти переменные параметры логического диска, перечисленные в комментариях к прототипу функции.
Для того чтобы определить размер свободного пространства на диске в байтах, вы должны умножить значение количества свободных кластеров (записанное по адресу lpNumberOfFreeClusters) на количество секторов в кластере (записанное по адресу lpSectorsPerCluster) и на количество байт в одном секторе (которое будет записано по адресу lpBytesPerSector). Более подробно о делении диска на кластеры и секторы вы можете узнать из 19 тома “Библиотеки системного программиста”.
В программном интерфейсе Microsoft Windows NT есть еще одна функция, с помощью которой вы можете определить параметры дискового устройства. Это функция GetVolumeInformation:
BOOL GetVolumeInformation(
LPCTSTR lpRootPathName, // адрес пути к корневому каталогу
LPTSTR lpVolumeNameBuffer, // буфер для имени тома
DWORD nVolumeNameSize, // размер буфера lpVolumeNameBuffer
LPDWORD lpVolumeSerialNumber, // буфер для серийного номера
// тома
LPDWORD lpMaximumComponentLength, // буфер для максимальной
// длины имени файла, допустимой для данного тома
LPDWORD lpFileSystemFlags, // буфер для системных флагов
LPTSTR lpFileSystemNameBuffer, // буфер для имени
// файловой системы
DWORD nFileSystemNameSize); // размер буфера
// lpFileSystemNameBuffer
Перед использованием этой функции вы должны подготовить несколько буферов и передать функции их адреса. Функция заполнит буферы параметрами устройства, корневой каталог которого задан параметром lpRootPathName.
В буфере системных флагов, адрес которого передается функции через параметр lpFileSystemFlags, могут быть установлены следующие флаги:
Флаг |
Описание |
FS_CASE_IS_PRESERVED |
Система делает различия между заглавными и прописными буквами в именах файлов при записи этих имен на диск |
FS_CASE_SENSITIVE |
Система делает различия между заглавными и прописными буквами |
FS_UNICODE_STORED_ON_DISK |
Система может работать с кодировкой Unicode в именах файлов |
FS_PERSISTENT_ACLS |
Система способна работать со списком контроля доступа к файлам ACL (access-control list). Такая возможность есть в файловой системе NTFS, но отсутствует в файловых системах HPFS и FAT |
FS_FILE_COMPRESSION |
Файловая система способна сжимать (компрессовать) отдельные файлы |
FS_VOL_IS_COMPRESSED |
Для тома используется автоматическая компрессия данных |
Определение приоритета задачи
Зная идентификатор задачи, нетрудно определить ее относительный приоритет. Для этого следует воспользоваться функцией GetThreadPriority:
int GetThreadPriority(HANDLE hThread);
Эта функция возвращает одно из значений, перечисленных выше в разделе “Изменение приоритета задачи” или значение THREAD_PRIORITY_ERROR_RETURN при возникновении ошибки.
Определение размера блока памяти
Зная адрес блока памяти, полученного из пула, вы можете определить его размер при помощи функции HeapSize:
DWORD HeapSize(
HANDLE hHeap, // идентификатор пула
DWORD dwFlags, // управляющие флаги
LPCVOID lpMem); // адрес проверяемого блока памяти
В случае ошибки эта функция возвращает значение 0xFFFFFFFF.
Если блоком памяти пользуется только одна задача процесса, вы можете передать через параметр dwFlags значение HEAP_NO_SERIALIZE.
Определение системного каталога
При необходимости с помощью функции GetSystemDirectory вы можете определить путь к системному каталогу Microsoft Windows NT (например, C:\WINNT\SYSTEM32, если Microsoft Windows NT установлена на диск C:):
UINT GetSystemDirectory(
LPTSTR lpBuffer, // адрес буфера
UINT uSize); // размер буфера в байтах
Определение текущего каталога
С помощью функции GetCurrentDirectory приложение может определить текущий каталог:
DWORD GetCurrentDirectory(
DWORD nBufferLength, // размер буфера
LPTSTR lpBuffer); // адрес буфера
Перед вызовом этой функции вы должны подготовить буфер. Через параметр lpBuffer функции GetCurrentDirectory следует передать адрес буфера, а через параметр nBufferLength - размер буфера в байтах.
В случае успеха функция возвращает размер заполенной части буфера, при ошибке - нулевое значение.
Определение текущего значения счетчика семафора
Единаственная возможность определения текущего значения счетчика семафора заключается в увеличении этого значения функцией ReleaseSemaphore. Значение счетчика, которое было до увеличения, будет записано в переменную, адрес которой передается функции ReleaseSemaphore через параметр lplPreviousCount.
Заметим, что в операционной системе Microsoft Windows NT не предусмотрено средств, с помощью которых можно было бы определить текущее значение семафора, не изменяя его. В частности, вы не можете задать функции ReleaseSemaphore нулевое значение инкремента, так как в этом случае указанная функция просто вернет соответствующий код ошибки.
Определение типа устройства
С помощью функции GetDriveType вы можете определить тип дискового устройства:
UINT GetDriveType(LPCTSTR lpRootPathName);
В качестве параметра функции GetDriveType нужно передать текстовую строку имени устройства, например, полученную при помощи функции GetLogicalDriveStrings.
В зависимости от типа указанного устройства функция GetDriveType может вернуть одно из следующих значений:
Значение | Описание | ||
0 | Тип устройства не удалось определить | ||
1 | Указанный корневой каталог не существует | ||
DRIVE_REMOVABLE | Устройство со сменным носителем данных | ||
DRIVE_FIXED | Устройство с несменным носителем данных | ||
DRIVE_REMOTE | Удаленное (сетевое) устройство | ||
DRIVE_CDROM | Устройство чтения CD-ROM | ||
DRIVE_RAMDISK | Электронный диск (RAM-диск) |
Определения и глобальные переменные
Константа MEMBLOCK_SIZE определяет размер блока памяти в байтах, над которым выполняются операции. Мы работаем с блоком памяти размером 4096 байт, что соответствует одной странице, однако вы можете попробовать и другие значения. Например, вы можете попытаться задать очень большие значения и убедиться в том, что при этом хотя память и будет предоставлена в ваше распоряжение, вы не сможете выполнить фиксирование соответствующих страниц.
В глобальных переменных hInst, szAppName и szAppTitle хранится, соответственно, идентификатор приложения, полученный при запуске через параметр hInstance функции WinMain, имя и заголовок главного окна приложения.
Глобальная переменная lpMemoryBuffer содержит указатель на блок виртуальной памяти размером MEMBLOCK_SIZE байт. Это именно тот блок памяти, с которым в нашем приложении выполняются все операции.
И, наконец, в глобальной переменной hSetMenu хранится идентификатор меню Set protection, который необходим для выполнения операции отметки строк этого меню.
Помимо обычных include-файлов, характерных для наших приложений, в приложении MultiSDI используется файл process.h. Этот файл содержит прототип функции _beginthread, с помощью которой наше приложение создает задачи.
Для синхронизации задач, выполняющих рисование в окне приложения, мы определили глобальную структуру csWindowPaint типа CRITICAL_SECTION, которая будет использоваться в качестве критической секции:
CRITICAL_SECTION csWindowPaint;
В глобальной переменной fTerminate хранится флаг, установка которого в состояние TRUE вызывает завершение всех трех задач рисования.
И, наконец, в массиве hThreads хранятся идентификаторы запущенных задач. Этот массив будет использован, в частности, для того, чтобы перед удалением критической секции убедиться, что все задачи рисования завершили свою работу.
Так как для запуска задач в приложении MultiMDI мы пользуемся функцией CreateThread, нам не нужно включать файл process.h.
В глобальных переменных szFrameClassName и szChildClassName хранятся указатели, соответственно, на имена классов для окна Frame Window и окна Child Window.
Адрес заголовка приложения хранится в глобальной переменной szWindowTitle.
Переменная hInst используется для хранения идентификатора приложения.
Переменные hwndFrame, hwndClient и hwndChild используются, соответственно, для хранения идентификаторов окон Frame Window, Client Window и дочернего окна (в момент его создания).
Для каждого дочернего MDI-окна мы создаем структуру типа CHILD_WINDOW_TAG, в которой сохраняем такую информацию, как признак активности задачи, запущенной для этого окна, критическую секцию для рисования в окне, а также идентификатор задачи, запущенной для окна:
typedef struct _CHILD_WINDOW_TAG
{
BOOL fActive;
CRITICAL_SECTION csChildWindowPaint;
HANDLE hThread;
} CHILD_WINDOW_TAG;
Кроме того, мы определили указатель на эту структуру:
typedef CHILD_WINDOW_TAG *LPCHILD_WINDOW_TAG;
Для работы со стандартной диалоговой панелью, с помощью которой выбирается запускаемый программный файл, в исходный текст приложения включается файл commctrl.h.
В области глобальных переменных нашего приложения определены переменные dwCreationFlags и fWaitTermination. Первая из них содержит флаги создания процессов, которые настраиваются при помощи диалоговой панели Start Options и используются при запуске процессов функцией CreateProcess. Во второй переменной хранится признак необходимости ожидания завершения процессов. Содержимое этой переменной изменяется также при помощи диалоговой панели Start Options.
Приложение SEMMDI сделано на базе описанного ранее приложения MultiMDI, поэтому здесь мы приведем сокращенное описание определений, глобальных переменных и функций.
Для каждого дочернего MDI-окна в приложении MultiMDI мы создавали структуру типа CHILD_WINDOW_TAG, в которой хранилась такая информация, как признак активности задачи, запущенной для этого окна, критическая секция для рисования в окне, а также идентификатор задачи, запущенной для окна. Создавая приложение SEMMDI, мы добавили в эту структуру поле fWaiting:
typedef struct _CHILD_WINDOW_TAG
{
BOOL fActive;
BOOL fWaiting;
CRITICAL_SECTION csChildWindowPaint;
HANDLE hThread;
} CHILD_WINDOW_TAG;
typedef CHILD_WINDOW_TAG *LPCHILD_WINDOW_TAG;
В поле fWaiting записывается значение TRUE, когда соответствующая задача находится в состоянии ожидания семафора. Проверяя содержимое этого поля, мы запрещаем пользователю удалять окна, если соответствующие им задачи находятся в состоянии ожидания семафора.
Основные характеристики файловой системы NTFS
Теперь, после такого краткого обзора наиболее популярных файловых систем, мы может сказать, что файловая система NTFS вобрала в себя самое лучшее из всего, что было создано к настоящему моменту в этой области. Обладая практически такой же высокой производительностью, как файловая система FAT, файловая система NTFS допускает использование длинных имен, имеет намного более высокую надежность и средства разграничения доступа, мощные средства поиска файлов в каталогах, основанные на использовании B-деревьев, и не требует выполнения периодической дефрагментации диска.
Что касается имен файлов, то в файловой системе NTFS допускается указывать имена размером до 255 символов в кодировке UNICODE, когда каждый символ представляется двумя байтами. Кодировку UNICODE мы рассмотрим подробнее в одном из следующих томов “Библиотеки системного программиста”, посвященного операционной системе Microsoft Windows NT. Сейчас мы только скажем, что она позволяет сохранить имена файлов при их копировании в системы, использующие другие национальные языки.
В именах файлов и каталогов можно использовать строчные либо прописные буквы, при этом файловая система не делает между ними различий. В итоге имена MyFile, MYFILE и myfile означают одно и то же. В режиме совместимости со стандартом POSIX, однако, операционная система Microsoft Windows NT будет считать все перечисленные выше имена разными. Рассмотрение стандарта POSIX, предназначенного для совместимости с UNIX-программами (точнее говоря, приложения UNIX, отвечающие стандарту POSIX, будет легче переносить на платформу Microsoft Windows NT), выходит за рамки нашей книги.
Использование B-деревьев при поиске имен в каталогах вместо обычного последовательного пребора значительно сокращает время открывания файлов, особенно если каталог содержит очень много файлов (последнее не редкость, так как современные приложения состоят из десятков, если не сотен файлов).
Стоит специально отметить, что в отличие от HPFS, файловая система NTFS содержит специальные средства, предназначенные для повышения устойчивости к различным аварийным ситуациям, таким, например, как внезапное отключение электропитания или аварийный останов операционной системы. Эти срества используют логику обработки транзакций, в результате чего операции, которые в результате аварии не успели завершиться, будут отменены с восстановлением файловой системы в исходное состояние.
Файловая система NTFS практически не имеет ограничений на макисмальный размер файла. Так, если файловые системы FAT и HPFS позовляет создавать файлы размером не более 232 байта, файловая система NTFS может работать с файлами размером до 264 байта. Максимальный размер пути, который в FAT составлял 64 байта, в NTFS не ограничен.
Для совместимости с программами MS-DOS, запущенными под управлением операционной системы Microsoft Windows NT, файловая система NTFS для всех файлов и каталогов создает короткие альтернативные имена, аналогично тому, как это делает операционная система Microsoft Windows 95. Если операционная система Microsoft Windows NT Server используется в качестве файл-сервера сети, то альтернативные имена позволяют получить доступ ко всем файлам и каталогам сервера из рабочих станций MS-DOS, неспособных работать с длинными именами напрямую.
Ко всему прочему добавим, что файловая система NTFS в операционной системе Microsoft Windows NT версии 3.51 позволяет выполнять динамическую компрессию файлов, аналогично тому как это делает драйвер DriveSpace или Stacker в операционной системе MS-DOS.
Освобождение идентификатора объекта Mutex
Если объект Mutex больше не нужен, вы должны освободить его идентификатор при помощи универсальной функции CloseHandle. Заметим, тем не менее, что при завершении процесса освобождаются идентификаторы всех объектов Mutex, созданных для него.
Освобождение идентификатора задачи
После завершения процесса идентификаторы всех созданных им задач освобождаются. Однако лучше, если приложение будет самостоятельно освобождать ненужные ей идентификаторы задач, так как они являются ограниченным системным ресурсом.
Для освобождения идентификатора задачи вы должны передать его функции CloseHandle, имеющей единственный параметр.
Еще одно замечание относительно освобождения идентификаторов задач и процессов приведено в следующей главе, посвященной процессам.
Освобождение объекта Mutex
Для отказа от владения объектом Mutex (то есть для его освобождения) вы должны использовать функцию ReleaseMutex:
BOOL ReleaseMutex(HANDLE hMutex);
Через единственный параметр этой функции необходимо передать идентификатор объекта Mutex. Функция возвращает значение TRUE при успешном завершении и FALSE при ошибке.
Проводя аналогию с критическими секциями, заметим, что освобождение объекта Mutex соответствует выходу из критической секции.
Освобождение памяти
Память, выделенную с помощью функции HeapAlloc, следует освободить, как только в ней отпадет надобность. Это нужно сделать при помощи функции HeapFree:
BOOL HeapFree(
HANDLE hHeap, // идентификатор пула
DWORD dwFlags, // флаги освобождения памяти
LPVOID lpMem); // адрес освобождаемого блока памяти
Если блоком памяти пользуется только одна задача процесса, вы можете передать через параметр dwFlags значение HEAP_NO_SERIALIZE.
Если размер блока памяти, выделенного функцией HeapAlloc, был изменен функцией HeapReAlloc, для освобождения такого блока памяти вы все равно должны использовать функцию HeapFree.
Освобождение виртуальной памяти
После использования вы должны освободить полученную ранее виртуальную память, вызвав функцию VirtualFree:
BOOL VirtualFree(
LPVOID lpvAddress, // адрес области
DWORD cbSize, // размер области
DWORD fdwFreeType); // выполняемая операция
Через параметры lpvAddress и cbSize передаются, соответственно, адрес и размер освобождаемой области.
Если вы зарезервировали область виртуальной памяти функцией VirtualAlloc с параметром MEM_RESERVE для последующего получения страниц в пользование и затем вызвали эту функцию еще раз с параметром MEM_COMMIT, вы можете либо совсем освободить область памяти, обозначив соответствующие страницы как свободные, либо оставить их зарезервированными, но не используемыми.
В первом случае вы должны вызвать функцию VirtualFree с параметром fdwFreeType, равным MEM_RELEASE, во втором - с параметром MEM_DECOMMIT.
Открытие объекта Mutex
Зная имя объекта Mutex, задача может его открыть с помощью функции OpenMutex, прототип которой приведен ниже:
HANDLE OpenMutex(
DWORD fdwAccess, // требуемый доступ
BOOL fInherit, // флаг наследования
LPCTSTR lpszMutexName ); // адрес имени объекта Mutex
Флаги доступа, передаваемые через параметр fdwAccess, определяют требуемый уровень доступа к объекту Mutex. Этот параметр может быть комбинацией следующих значений:
Значение | Описание | ||
EVENT_ALL_ACCESS | Указаны все возможные флаги доступа | ||
SYNCHRONIZE | Полученный идентификатор можно будет использовать в любых функциях ожидания события |
Параметр fInherit определяет возможность наследования полученного идентфикатора. Если этот параметр равен TRUE, идентфикатор может наследоваться дочерними процессами. Если же он равен FALSE, наследование не допускается.
Через параметр lpszEventName вы должны передать функции адрес символьной строки, содержащей имя объекта Mutex.
С помощью функции OpenMutex несколько задач могут открыть один и тот же объект Mutex и затем выполнять одновременное ожидание для этого объекта.
Открытие события
Если событие используется задачами, созданными только в рамках одного процесса, его не нужно открывать. В качестве параметра функциям, изменяющим состояние объекта-события, вы можете передавать идентификатор события, полученный при его создании от функции CreateEvent.
Если же событие используется для синхронизации задач, принадлежащих разным процессам, вы должны при создании события задать его имя. Задача, изменяющая состояние события и принадлежащая другому процессу, должна открыть объект-событие с помощью функции OpenEvent, передав ей имя этого объекта.
Прототип функции OpenEvent представлен ниже:
HANDLE OpenEvent(
DWORD fdwAccess, // флаги доступа
BOOL fInherit, // флаг наследования
LPCTSTR lpszEventName); // адрес имени объекта-события
Флаги доступа, передаваемые через параметр fdwAccess, определяют требуемый уровень доступа к объекту-событию. Этот параметр может быть комбинацией следующих значений:
Значение | Описание | ||
EVENT_ALL_ACCESS | Указаны все возможные флаги доступа | ||
EVENT_MODIFY_STATE | Полученный идентификатор можно будет использовать для функций SetEvent и ResetEvent | ||
SYNCHRONIZE | Полученный идентификатор можно будет использовать в любых функциях ожидания события |
Параметр fInherit определяет возможность наследования полученного идентфикатора. Если этот параметр равен TRUE, идентфикатор может наследоваться дочерними процессами. Если же он равен FALSE, наследование не допускается.
И, наконец, через параметр lpszEventName вы должны передать функции адрес символьной строки, содержащей имя объекта-события.
Заметим, что с помощью функции OpenEvent несколько задач могут открыть один и тот же объект-событие и затем выполнять одновременное ожидание для этого объекта.
Открывание семафора
Если семафор используется только для синхронизации задач, созданных в рамках одного приложения, вы можете создать безымянный семафор, указав в качестве параметра lpName функции CreateSemaphore значение NULL. В том случае, когда необходимо синхронизовать задачи разных процессов, следует определить имя семафора. При этом один процесс создает семафор с помощью функции CreateSemaphore, а второй открывает его, получая идентификатор для уже существующего семафора.
Существующий семафор можно открыть функцией OpenSemaphore, прототип которой приведен ниже:
HANDLE OpenSemaphore(
DWORD fdwAccess, // требуемый доступ
BOOL fInherit, // флаг наследования
LPCTSTR lpszSemaphoreName ); // адрес имени семафора
Флаги доступа, передаваемые через параметр fdwAccess, определяют требуемый уровень доступа к семафору. Этот параметр может быть комбинацией следующих значений:
Значение | Описание | ||
SEMAPHORE_ALL_ACCESS | Указаны все возможные флаги доступа | ||
SEMAPHORE_MODIFY_STATE | Возможно изменение значение счетчика семафора функцией ReleaseSemaphore | ||
SYNCHRONIZE | Полученный идентификатор можно будет использовать в любых функциях ожидания события |
Параметр fInherit определяет возможность наследования полученного идентфикатора. Если этот параметр равен TRUE, идентфикатор может наследоваться дочерними процессами. Если же он равен FALSE, наследование не допускается.
Через параметр lpszSemaphoreName вы должны передать функции адрес символьной строки, содержащей имя семафора.
Если семафор открыт успешно, функция OpenSemaphore возвращает его идентификатор. При ошибке возвращается значение NULL. Код ошибки вы можете определить при помощи фукнции GetLastError.
Отметки времени для файла
Операционная система Microsoft Windows NT хранит для каждого файла отдельно дату и время его создания, дату и время момента последнего доступа к файлу, а также дату и время момента, когда последний раз выполнялась запись данных в файл.
Всю эту информацию вы можете получить для открытого файла при помощи функции GetFileTime, прототип которой приведен ниже:
BOOL GetFileTime(
HANDLE hFile, // идентификатор файла
LPFILETIME lpCreationTime, // время создания
LPFILETIME lpLastAccessTime, // время доступа
LPFILETIME lpLastWriteTime); // время записи
Перед вызовом этой функции вы должны подготовить три структуры типа FILETIME и передать их адреса через параметры lpCreationTime, lpLastAccessTime и lpLastWriteTime. В эти структуры будет записана, соответственно, дата и время создания файла hFile, дата и время момента последнего доступа к файлу, а также дата и время момента, когда последний раз выполнялась запись данных в этот файл.
Структура FILETIME определена следующим образом:
typedef struct _FILETIME
{
DWORD dwLowDateTime; // младшее слово
DWORD dwHighDateTime; // старшее слово
} FILETIME;
Согласно документации, в структуре FILETIME хранится 64-разрядное значение даты и времени в виде количества интервалов размером 100 наносекунд от 1 января 1601 года.
Что делать с таким представлением даты и времени?
В программном интерфейсе Microsoft Windows NT предусмотрен набор функций, предназначенных для преобразования этого формата времени в более привычные нам форматы и обратно, а также для сравнения значений времени в формате структуры FILETIME.
С помощью функции FileTimeToSystemTime вы можете преобразовать дату и время из формата структуры FILETIME в более удобный для использования формат, определяемый структурой SYSTEMTIME:
BOOL FileTimeToSystemTime(
CONST FILETIME *lpFileTime, // указатель на структуру
// FILETIME
LPSYSTEMTIME lpSystemTime); // указатель на структуру
// SYSTEMTIME
Структура SYSTEMTIME определена так:
typedef struct _SYSTEMTIME
{
WORD wYear; // год
WORD wMonth; // месяц (1 - январь, 2 - февраль, и т. д.)
WORD wDayOfWeek; // день недели
// (0 - воскресение, 1 - понедельник, и т. д.)
WORD wDay; // день месяца
WORD wHour; // часы
WORD wMinute; // минуты
WORD wSecond; // секунды
WORD wMilliseconds; // миллисекунды
} SYSTEMTIME;
Обратное преобразование формата времени из формата структуры SYSTEMTIME в формат структуры FILETIME можно сделать при помощи функции SystemTimeToFileTime:
BOOL SystemTimeToFileTime(
CONST SYSTEMTIME *lpSystemTime, // указатель на структуру
// SYSTEMTIME
LPFILETIME lpFileTime); // указатель на структуру FILETIME
Для установки новых отметок времени файла необходимо воспользоваться функцией SetFileTime:
BOOL SetFileTime(
HANDLE hFile, // идентификатор файла
LPFILETIME lpCreationTime, // время создания
LPFILETIME lpLastAccessTime, // время доступа
LPFILETIME lpLastWriteTime); // время записи
Если вам нужно сравнить два значения времени в формате FILETIME, то проще всего это сделать при помощи функции CompareFileTime:
LONG CompareFileTime(
CONST FILETIME *lpTime1, // адрес первой структуры FILETIME
CONST FILETIME *lpTime2); // адрес второй структуры FILETIME
Если времена и даты, записанные в обеих структурах, равны, функция CompareFileTime возвращает нулевое значение. Если первая дата и вермя больше второго, возвращается значение 1, если же меньше - возвращается отрицательное значение -1.
Для вас могут также представлять интерес еще две функции, выполняющие преобразование формата времени. Это функции FileTimeToDosDateTime и DosDateTimeToFileTime, выполняющие, соответственно, преобразование даты и времени из формата структуры FILETIME в формат, принятый в операционной системы MS-DOS, и обратно. Описание этих функций при необходимости вы найдете в SDK.
Относительный приоритет задач
Как мы уже говорили, в рамках одного процесса может быть запущено несколько задач. Точно также как невозможно задать явным образом уровень приоритета процессов (лежащий в диапазоне значений от 1 до 31), невозможно задать и уровень приоритета задач, запущенных процессом. Вместо этого процесс при необходимости устанавливает функцией SetThreadPriority относительный приоритет задач, который может быть несколько ниже или выше приоритета процесса.
Указанной выше функции можно передать одно из следующих значений, определяющих новый приоритет задачи относительно приоритета процесса:
Значение | Относительное изменение уровня приоритета | ||
THREAD_PRIORITY_TIME_CRITICAL | Устанавливается абсолютный уровень приоритета 15 или 31 | ||
THREAD_PRIORITY_HIGHEST | +2 | ||
THREAD_PRIORITY_ABOVE_NORMAL | +1 | ||
THREAD_PRIORITY_NORMAL | 0 | ||
THREAD_PRIORITY_BELOW_NORMAL | -1 | ||
THREAD_PRIORITY_LOWEST | -2 | ||
THREAD_PRIORITY_IDLE | Устанавливается абсолютный уровень приоритета 1 или 16 |
Если процесс имеет класс приоритета, равный значению REALTIME_PRIORITY_CLASS, использование относительного приоритета THREAD_PRIORITY_TIME_CRITICAL приведет к тому, что уровень приоритета задачи будет равен 31. Если же это значение относительного приоритета укажет процесс более низкого класса приоритета, уровень приоритета задачи установится равным 15.
Для процесса с классом приоритета REALTIME_PRIORITY_CLASS использование относительного приоритета THREAD_PRIORITY_IDLE приведет к тому, что будет установлен уровень приоритета задачи, равный 16. Если же значение THREAD_PRIORITY_IDLE будет использовано менее приоритетным процессом, уровень приоритета задачи будет равен 1.
Операционная система может автоматически изменять приоритет задач, повышая его, когда задача начинает взаимодействовать с пользователем, а затем постепенно уменьшая. Приоритет задач, находящихся в состоянии ожидания, также уменьшается.
Процесс может запустить задачу, а потом увеличить ее приоритет. В этом случае главная задача процесса, запустившая более приоритетную задачу, будет временно приостановлена. Если же процесс запустит задачу и уменьшит ее приоритет таким образом, что он станет меньше приоритета главной задачи процесса, будет приостановлена запущенная задача.
В составе Resource Kit for Windows NT и в составе SDK поставляется приложение Process Viewer (рис. 2.2), пользуясь которым можно просмотреть и в некоторых случаях изменить приоритеты процессов и задач, а также получить другую информацию о запущенных задачах (использованное процессорное время с момента запуска, процент работы системного и пользовательского кода, использование виртуальной памяти и так далее).
Рис. 2.2. Приложение Process Viewer
С помощью этого приложения вы также можете завершить работу процесса, если возникнет такая необходимость.
Ожидание завершения нескольких задач или процессов
Часто одна задача должна дожидаться завершения сразу нескольких задач или процессов, либо одной из нескольких задач или процессов. Такое ожидание нетрудно выполнить с помощью функции WaitForMultipleObjects, прототип которой приведен ниже:
DWORD WaitForMultipleObjects(
DWORD cObjects, // количество идентификаторов в массиве
CONST HANDLE *lphObjects, // адрес массива идентификаторов
BOOL fWaitAll, // тип ожидания
DWORD dwTimeout); // время ожидания в миллисекундах
Через параметр lphObjects функции WaitForMultipleObjects нужно передать адрес массива идентификаторов. Размер этого массива передается через параметр cObjects.
Если содержимое параметра fWaitAll равно TRUE, задача переводится в состояние ожидания до тех пор, пока все задачи или процессы, идентификаторы которых хранятся в массиве lphObjects, не завершат свою работу. В том случае, когда значение параметра fWaitAll равно FALSE, ожидание прекращается, когда одна из указанных задач или процессов завершит свою работу. Для выполнения бесконечного ожидания, как и в случае функции WaitForSingleObject, через параметр dwTimeout следует передать значение INFINITE.
Как пользоваться этой функцией?
Пример вы можете найти в исходных текстах приложеения MultiSDI, описанного ранее.
Прежде всего вам необходимо подготовить массив для хранения идентификаторов задач или процессов, завершения которых нужно дождаться:
HANDLE hThreads[3];
После этого в массив следует записать идентификаторы запущенных задач или процессов:
hThreads[0] = (HANDLE)_beginthread(PaintEllipse, 0,
(void*)hWnd);
hThreads[1] = (HANDLE)_beginthread(PaintRect, 0,
(void*)hWnd);
hThreads[2] = (HANDLE)_beginthread(PaintText, 0,
(void*)hWnd);
Для выполнения ожидания следует вызвать функцию WaitForMultipleObjects, передав ей количество элементов в массиве идентификаторов, адрес этого массива, тип и время ожидания:
WaitForMultipleObjects(3, hThreads, TRUE, INFINITE);
В данном случае задача, вызвавшая функцию WaitForMultipleObjects, перейдет в состояние ожидания до тех пор, пока все три задачи не завершат свою работу.
Функция WaitForMultipleObjects может вернуть одно из следующих значений:
WAIT_FAILED (при ошибке);
WAIT_TIMEOUT (если время ожидания истекло);
значение в диапазоне от WAIT_OBJECT_0 до (WAIT_OBJECT_0 + cObjects - 1), которое, в зависимости от содержимого параметра fWaitAll, либо означает что все ожидаемые идентификаторы перешли в отмеченное состояние (если fWaitAll был равен TRUE), либо это значение, если из него вычесть константу WAIT_OBJECT_0, равно индексу идентификатора отмеченного объекта в массиве идентфикаторов lphObjects.
значение в диапазоне от WAIT_ABANDONED_0 до (WAIT_ABANDONED_0 + cObjects - 1), если ожидание для объектов Mutex было отменено. Индекс соответствующего объекта в массиве lphObjects можно определить, если вычесть из кода возврата значение WAIT_ABANDONED_0.
Ожидание завершения задачи или процесса
Если нужно дождаться завершения одной задачи или одного процесса, лучше всего воспользоваться для этого функцией WaitForSingleObject, с которой вы уже знакомы из предыдущих приложений.
Прототип функции WaitForSingleObject представлен ниже:
DWORD WaitForSingleObject(
HANDLE hObject, // идентификатор объекта
DWORD dwTimeout); // время ожидания в миллисекундах
В качестве параметра hObject этой функции нужно передать идентификатор объекта, для которого выполняется ожидание, а в качестве параметра dwTimeout - время ожидания в миллисекундах (ожидание может быть и бесконечным, если для времени указать значение INFINITE).
Многие объекты операционной системы Microsoft Windows NT, такие, например, как идентификаторы задач, процессов, файлов, могут находиться в двух состояниях - отмеченном (signaled) и неотмеченном (nonsignaled). В частности, если задача или процесс находятся в состоянии выполнения (то есть работают), соответствующие идентификаторы находятся в неотмеченном состоянии. Когда же задача или процесс завершают свою работу, их идентификаторы отмечаются (то есть переходят в отмеченное состояние).
Если задача создает другую задачу или процесс, и затем вызывает функцию WaitForSingleObject, указав ей в качестве первого параметра идентификатор созданной задачи, а в качестве второго - значение INFINITE, родительская задача переходит в состояние ожидания. Она будет находиться в состоянии ожидания до тех пор, пока дочерняя задача или процесс не завершит свою работу.
Заметим, что функция WaitForSingleObject не проверяет состояние идентификатора дочерней задачи или процесса в цикле, дожидаясь ее (или его) завершения. Такое действие привело бы к тому, что родительской задаче выделялись бы кванты процессорного времени, а это как раз то, чего мы хотели бы избежать. Вместо этого функция WaitForSingleObject сообщает планировщику задач, что выполнение родительской задачи, вызвавшей эту функцию, необходимо приостановить до тех пор, пока дочерняя задача или процесс не завершит свою работу.
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
Запущенный таким образом процесс называется отсоединенным (detached). Он будет жить своей жизнью независимо от состояния запустившего его процесса.
Теперь поговорим о коде завершения функции WaitForSingleObject.
В случае ошибки функция возвращает значение WAIT_FAILED. При этом код ошибки можно получить при помощи функции GetLastError.
Если же функция завершилась успешно, она может вернуть одно из следующих трех значений: WAIT_OBJECT_0, WAIT_TIMEOUT или WAIT_ABANDONED.
Если состояние идентификатора объекта, для которого выполнялось ожидание, стало отмеченным, функция фозвращает значение WAIT_OBJECT_0. Таким образом, когда мы ожидаем завершение задачи и задача завершилась “естественным образом”, функция WaitForSingleObject вернет именно это значение.
Если время ожидания, заданное во втором параметре функции WaitForSingleObject истекло, но объект так и не перешел в отмеченное состояние, возвращается значение WAIT_TIMEOUT. Очевидно, при бесконечном ожидании вы никогда не получите этот код завершения.
Код завершения WAIT_ABANDONED возвращается для объекта синхронизации типа Mutex (его мы рассмотрим позже), который не был освобожден задачей, завершившей свою работу. Таким образом, в этом случае ожидание было отменено и соответствующий объект (Mutex) не перешел в отмеченное состояние.
Параметры функции CreateProcess
Функция CreateProcess имеет много параметров, однако ей не так сложно пользоваться, как это может показаться на первый взгляд:
BOOL CreateProcess(
LPCTSTR lpApplicationName, // указатель на имя исполняемого
// модуля
LPTSTR lpCommandLine, // указатель на командную строку
LPSECURITY_ATTRIBUTES lpProcessAttributes, // указатель на
// атрибуты защиты процесса
LPSECURITY_ATTRIBUTES lpThreadAttributes, // указатель на
// атрибуты защиты задачи
BOOL bInheritHandles, // флаг наследования идентификатора
DWORD dwCreationFlags,// флаги создания процесса
LPVOID lpEnvironment, // указатель на блок среды выполнения
LPCTSTR lpCurrentDirectory, // указатель на имя текущего
// каталога
LPSTARTUPINFO lpStartupInfo, // указатель на структуру
// STARTUPINFO
LPPROCESS_INFORMATION lpProcessInformation); // указатель на
// структуру PROCESS_INFORMATION
Если функция CreateProcess завершается успешно, она возвращает значение TRUE. В противном случае возвращается значение FALSE. Код ошибки вы можете получить, вызвав функцию GetLastError.
Передача данных между процессами и задачами
Так как процессы выполняются в изолированных адресных пространствах, возникают некоторые трудности при необходимости организовать обмен данными между процессами. Вы не можете просто предать из одного процесса другому указатели на глобальные области памяти или идентфикаторы каких-либо ресурсов (например, идентификатор кисти, растрового изображения и так далее), так как в контексте другого процесса эти указатели и идентификаторы не имеют смысла.
Что же делать?
Для передачи данных между процессами вы должны использовать такие средства, как файлы, отображаемые в память, средства динамической передачи данных DDE, трубы и другие специальные средства, которые мы рассмотрим позже. При этом дополнительно необходимо использовать средства синхронизации процессов.
Что же касается задач, запущенных в рамках одного процесса, то здесь ситуация несколько легче. Так как все задачи работают в едином адресном пространстве, то они могут обмениваться данными, например, через глобальные переменные. Разумеется, и в этом случае без средств синхронизации не обойтись.
Перемещение файла
С помощью функции MoveFile вы можете выполнить перемещение файла:
BOOL MoveFile(
LPCTSTR lpExistingFileName, // адрес пути
// существующего файла
LPCTSTR lpNewFileName); // адрес пути копии файла
Параметры lpExistingFileName и lpNewFileName, определяющие, соответственно, пути к старому и новому месторасположению файла, могут указывать на разные дисковые устройства.
Немного большими возможностями обладает другая функция, предназначенная для перемещения файлов, - функция MoveFileEx:
BOOL MoveFileEx(
LPCTSTR lpExistingFileName, // адрес пути
// существующего файла
LPCTSTR lpNewFileName, // адрес пути копии файла
DWORD dwFlags); // режим копирования
Дополнительный параметр dwFlags, определяющий один из режимов копирования, может принимать логическую комбинацию следующих значений:
Значение | Описание | ||
MOVEFILE_REPLACE_EXISTING | Перемещение с замещением существующего файла | ||
MOVEFILE_COPY_ALLOWED | Если файл перемещается на другое устройство, для перемещения используются функции CopyFile и DeleteFile (удаление файла). Это значение не совместимо со значением MOVEFILE_DELAY_UNTIL_REBOOT | ||
MOVEFILE_DELAY_UNTIL_REBOOT | Файл будет перемещен только после перезапуска операционной системы Microsoft Windows NT |
Режим MOVEFILE_DELAY_UNTIL_REBOOT удобен для создания программ автоматической установки приложений (инсталляторов).
Получение блока памяти из пула
Для получения памяти из стандартного или динамического пула приложение должно воспользоваться функцией HeapAlloc, прототип которой мы привели ниже:
LPVOID HeapAlloc(
HANDLE hHeap, // идентификатор пула
DWORD dwFlags, // управляющие флаги
DWORD dwBytes); // объем получаемой памяти в байтах
Что касается параметра hHeap, то для него вы можете использовать либо идентификатор страндартного пула памяти, полученного от функции GetProcessHeap, либо идентификатор динамического пула, созданного приложением при помощи функции HeapCreate.
Параметр dwBytes определяет нужный приложению объем памяти в байтах.
Параметр dwFlags может быть комбинацией следующих значений:
Значение | Описание | ||
HEAP_GENERATE_EXCEPTIONS | Если при выполнении функции произойдет ошибка, возникнет исключение | ||
HEAP_NO_SERIALIZE | Если указан этот флаг, не выполняется блокировака одновременного обращения к блоку памяти нескольких задач одного процесса | ||
HEAP_ZERO_MEMORY | Выделенная память заполняется нулями |
Получение идентификатора стандартного пула
Идентификатор стандартного пула получить очень просто. Этот идентификатор возвращает функция GetProcessHeap, не имеющая параметров:
HANDLE GetProcessHeap(VOID);
Получение информации о файле по его идентификатору
С помощью функции GetFileInformationByHandle вы сможете получить разнообразную информацию об открытом файле по его идентификатору:
BOOL GetFileInformationByHandle(
HANDLE hFile, // идентификатор файла
LPBY_HANDLE_FILE_INFORMATION lpFileInformation ); // адрес
// структуры, в которую будет записана информация о файле
Функция GetFileInformationByHandle записывает информацию о файле в структуру типа BY_HANDLE_FILE_INFORMATION, определенную следующим образом:
typedef struct _BY_HANDLE_FILE_INFORMATION
{
DWORD dwFileAttributes; // атрибуты файла
FILETIME ftCreationTime; // время создания файла
FILETIME ftLastAccessTime; // время доступа к файлу
FILETIME ftLastWriteTime; // время записи в файл
DWORD dwVolumeSerialNumber; // серийный номер тома
DWORD nFileSizeHigh; // размер файла (старшее слово)
DWORD nFileSizeLow; // размер файла (младшее слово)
DWORD nNumberOfLinks; // количество связей файла
DWORD nFileIndexHigh; // системный номер файла
// (старшее слово)
DWORD nFileIndexLow; // системный номер файла
// (младшее слово)
} BY_HANDLE_FILE_INFORMATION;
Поле nNumberOfLinks используется приложениями в стандарте POSIX.
Что же касается системного номера файла, то он отличается от идентификатора файла, полученного при помощи функции CreateFile по смыслу и значению. Системные номера файлов являются глобальными и различаются для всех файлов, открытых в системе.
Получение информации об использовании виртуальной памяти
В программном интерфейсе Microsoft Windows NT есть средства для получения справочной информации об использовании процессами виртуальной памяти. Это функции VirtualQuery и VirtualQueryEx. С помощью них процесс может исследовать, соответственно, собственное адресное пространство и адресное пространство других процессов, определяя такие характеристики областей виртуальной памяти, как размеры, тип доступа, состояние и так далее. Из-за ограниченного объема книги мы не будем рассматривать эти функции. При необходимости вы сможете найти их подробное описание в справочной системе, поставляющейся вместе с SDK или системой разработки Microsoft Visual C++.
С помощью приложения Process Walker, которое поставляется в составе SDK, вы сможете визуально исследовать распределение виртуальной памяти для процессов. На рис. 1.12 показан фрагмент такого распределения для приложения CALC.EXE. Приложение вызывает функции VirtualQuery и VirtualQueryEx (исходные тексты приложения поставляется в составе SDK; вы найдете их в каталоге win32sdk\mstools\samples\sdktools\winnt\pviewer).
Рис. 1.12. Исследование распределения виртуальной памяти при помощи приложения Process Walker
Для загрузки и исследования процесса вы должны выбрать из меню Process строку Load Process. Затем при помощи диалоговой панели Open executable image следует выбрать загрузочный файл нужного вам приложения. В окне появится распределение памяти в виде таблицы.
В столбце Address отобажается логический адрес областей памяти. Обратите внимание, что в модели памяти FLAT он состоит только из смещения. Базовый адрес отображается в столбце BaseAddr и имеет смысл только для зарезервированных (Reserve) или готовых к использованию (Commit) страниц. Для свободных страниц (Free) он равен нулю.
В столбце Prot в виде двухбуквенного сокращения имен сответствующих констант отображается тип доступа, разрешенный для страниц. Например, страницы, доступные только на чтение, отмечены в этом столбце как RO (PAGE_READONLY).
В линейном адресном пространстве процесса находятся не только код и данные самого процесса. В него также отображаются страницы системных библиотек динамической загрузки DLL (как это видно из рис. 1.12). При этом в столбце Object может отображаться тип объекта (библиотека DLL или исполняемый EXE-модуль), а в столбцах Section и Name, соответственно, имя секции и имя библиотеки DLL.
Если сделать двойной щелчок левой клавишей мыши по строке, соответствующей страницам памяти, отмеченным как Commit, на экране появится окно с дампом содержимого этих страниц (рис. 1.13).
Рис. 1.13. Просмотр содержимого страниц, готовых для использования
Вы можете использовать приложение Process Walker для отладки создаваемых вами приложений, например, контролируя использование ими виртуальной памяти.
Получение виртуальной памяти
У приложения есть две возможности заказать для себя страницы виртуальной памяти. Первая возможность заключается в резервировании заданного диапазона адресов в адресном пространстве приложения, вторая - в фактическом получении в пользование страниц виртуальной памяти, к которым можно выполнять обращение.
Процесс резервирования не отнимает много времени и не приводит к изменениям в файлах страниц. При резервировании приложение только отмечает область памяти, лежащую в заданном диапазоне адресов как зарезервированную.
Для чего может потребоваться резервирование диапазона адресов?
Например, при отображении файла, имеющего большие размеры, в память, приложение может зарезервировать область виртуальной памяти, имеющую размер, равный размеру файла (даже если файл занимает несколько сотен Мбайт). При этом гарантируется, что для адресации этой области памяти можно будет использовать сплошной диапазон адресов. Это удобно, если вы собираетесь работать с файлом, как с массивом, расположенным в оперативной памяти.
Если же приложение не резервирует, а получает страницы памяти для непосредственного использования, эти страницы физически создаются в виртуальной памяти и заполняются нулями. При этом может происходить запись в файлы страниц. Разумеется, такой процесс отнимает гораздо больше времени, чем резервирование.
Для того чтобы зарезервировать или получить в свое распоряжение некоторое количество страниц виртуальной памяти, приложение должно воспользоваться функцией VirtualAlloc, прототип которой представлен ниже:
LPVOID VirtualAlloc(
LPVOID lpvAddress, // адрес области
DWORD cbSize, // размер области
DWORD fdwAllocationType, // способ получения памяти
DWORD fdwProtect); // тип доступа
Параметры lpvAddress и cbSize задают, соответственно, начальный адрес и размер резервируемой либо получаемой в пользование области памяти. При резервировании адрес округляется до ближайшей границы блока размером 64 Кбайт. В остальных случаях адрес округляется до границы ближайшей страницы памяти.
Заметим, что параметр lpvAddress можно указать как NULL. При этом операционная система выберет начальный адрес самостоятельно.
Что же касается параметра cbSize, то он округляется до целого числа страниц. Поэтому если вы пытаетесь с помощью функции VirtualAlloc получить область памяти размером в один байт, вам будет выделена страница размером 4096 байт. Аналогично, при попытке получить блок памяти размером 4097 байт вы получите две страницы памяти общим размером 8192 байта. Как мы уже говорили, программный интерфейс системы управления виртуальной памятью не предназначен для работы с областями малого размера.
Для параметра fdwAllocationType вы можете использовать одно из следующих значений:
Значение |
Описание |
MEM_RESERVE |
Функция VirtualAlloc выполняет резервирование диапазона адресов в адресном пространстве приложения |
MEM_COMMIT |
Выполняется выделение страниц памяти для непосредственной работы с ними. Выделенные страницы заполняются нулями |
MEM_TOP_DOWN |
Память выделяется в области верхних адресов адресного пространства приложения |
Значение |
Разрешенный доступ |
PAGE_READWRITE |
Чтение и запись |
PAGE_READONLY |
Только чтение |
PAGE_EXECUTE |
Только исполнение программного кода |
PAGE_EXECUTE_READ |
Исполнение и чтение |
PAGE_EXECUTE_READWRITE |
Исполнение, чтение и запись |
PAGE_NOACCESS |
Запрещен любой вид доступа |
PAGE_GUARD |
Сигнализация доступа к старнице. Это значение можно использовать вместе с любыми другими, кроме PAGE_NOACCESS |
PAGE_NOCACHE |
Отмена кэширования для страницы памяти. Используется драйверами устройств. Это значение можно использовать вместе с любыми другими, кроме PAGE_NOACCESS |
С другой стороны, у вас есть возможность получения страниц, предназначенных только для хранения исполнимого кода. Если такие страницы отмечены как PAGE_EXECUTE, для них не разрешаются операции чтения и записи.
При необходимости зафиксировать обращение к той или иной странице приложение может отметить ее как PAGE_GUARD. Если произойдет попытка обращения к такой странице, возникнет исключение с кодом STATUS_GUARD_PAGE, после чего признак PAGE_GUARD будет автоматически сброшен.
В случае успешного завершения функция VirtualAlloc возвратит адрес зарезервированной или полученной области страниц. При ошибке будет возвращено значение NULL.
Приложение может вначале зарезервировать страницы, вызвав функцию VirtualAlloc с параметром MEM_RESERVE, а затем получить их в пользование, вызвав эту же функцию еще раз для полученной области памяти, но уже с параметром MEM_COMMIT.
Последовательный доступ к ресурсам
При создании мультизадачных приложений часто возникает необходимость обеспечить последовательный доступ параллельно работающих задач к какому-либо ресурсу. Типичным примером может послужить приложение MULTISDI, в котором несколько задач одновременно рисуют в одном окне приложения, пользуясь контекстом отображения и графическими функциями. Другой пример - данные, которые изменяются и читаются несколькими задачами сразу. Процедуры изменения и чтения в данном случае необходимо выполнять строго последовательно, иначе данные могут быть искажены.
Приведем еще один типичный пример, когда необходимо обеспечить последовательный доступ к ресурсу.
Пусть на счету в банке у фирмы лежит 2 млн. долларов. Два торговых агента фирмы одновременно пытаются сделать покупки, причем стоимость товара в обоих случаях равна 1,5 млн. долларов. При этом процесс покупки заключается в том, что вначале программа получает запись базы данных, в которой хранится общая сумма, затем она вычитает из нее стоимость товара и сохраняет новое значение общей суммы.
Допустим, что для выполнения покупки программа создает по одной задаче на каждого торгового агента, и эти задачи будут работать одновременно. Если не предусмотреть средства синхронизации доступа к записи базы данных, возможно возникновение такой ситуации, когда процессы покупки (получение общей суммы - вычитание стоимости товара - сохранение нового значение общей суммы), выполняемые этими двумя задачами, перекроются во времени.
К чему это может привести?
Если перекрытия во времени не произойдет, то вначале задача, запущенная для первого торгового агента, прочитает общую сумму, которая будет равна 2 млн. долларов. Затем она вычтет из нее стоимость покупки и запишет результат (500 тыс. долларов) обратно. Второй торговый агент уже не сможет сделать покупку, так как он обнаружит, что на счету осталось слишком мало денег.
В том случае, если покупки делаются одновременно, события могут развиваться в следующей последовательности:
первый агент получает значение общей суммы (2 млн. долларов);
второй агент получает значение общей суммы (2 млн. долларов);
первый агент вычитает из этой суммы стоимость товара (1,5 млн. долларов) и записывает результат (500 тыс. долларов) в базу данных;
второй агент вычитает из общей суммы стоимость товара и записывает результат в базу данных
Кто пострадает в данной ситуации?
Вероятно, банк, так как два торговых агента сделают покупки на сумму, превышающую ту, что лежит на счету у фирмы. Возможно также, что и программист, который не предусмотрел возможность возникновения таких накладок.
Ошибку можно было бы предотвратить, если во время выполнения любой задачей операции изменения текущего счета запретить доступ к этому счету других задач. Можно предложить следующий вариант сценария совершения покупок:
задача изменения счета проверяет, не заблокирован ли доступ к соответствующей записи базы данных. Если заблокирован, задача переводится в состояние ожидания до разблокировки. Если же доступ не заблокирован, задача выполняет блокировку такого доступа для всех других задач;
задача получает значения общей суммы;
задача выполняет вычитание стоимости купленного товара из общей суммы и записывает результат в базу данных;
задача разблокирует доступ к записи базы данных
Таким образом, при использовании приведенного выше сценария во время попытки одновременного изменения значения счета двумя торговыми агентами задача, запущенная чуть позже, перейдет в состояние ожидания. Когда же первый агент сделает покупку, задача второго агента получит правильное значение остатка (500 тыс. долларов), что не позволит ему приобрести товар на 1,5 млн. долларов.
В программном интерфейсе операционной системы Microsoft Windows NT имеются удобные средства организации последовательного доступа к ресурсам. Это критические секции, объекты взаимно исключающего доступа Mutex и блокирующие функции изменения содержимого переменных.
Предметный указатель
__except, 32
__try, 32
_beginthread, 45; 46; 47; 49; 51; 57; 58; 97; 112
_beginthreadex, 45; 47; 49
_hread, 141; 145
_hwrite, 141; 145
_lclose, 141
_lcreat, 141; 143
_llseek, 141
_lopen, 141
_lread, 141; 145; 150
_lwrite, 141; 145; 150
BIOS, 141; 157
BY_HANDLE_FILE_INFORMATION, 150
CDFS, 140
CheckDlgButton, 93
CLIENTCREATESTRUCT, 74
CloseHandle, 82; 87; 93; 96; 102; 103; 104; 109; 113; 121; 125; 137; 145; 146
CompareFileTime, 149
CopyFile, 152
CREATE_ALWAYS, 144
CREATE_DEFAULT_ERROR_MODE, 79
CREATE_NEW, 144
CREATE_NEW_CONSOLE, 79
CREATE_NEW_PROCESS_GROUP, 79
CREATE_SEPARATE_WOW_VDM, 79
CREATE_SUSPENDED, 46; 47; 79
CREATE_UNICODE_ENVIRONMENT, 79
CreateDirectory, 153
CreateEvent, 98; 99; 100; 101; 102; 108
CreateFile, 143; 144; 145; 146; 147; 150; 152; 158; 159
CreateMDIWindow, 75
CreateMutex, 108; 109; 111; 116
CreateProcess, 43; 78; 80; 82; 86; 87; 92; 93; 96
CreateSemaphore, 121; 124; 137
CreateThread, 45; 46; 47; 48; 58; 64; 74; 75; 127
CRITICAL_SECTION, 106
CW_USEDEFAULT, 22; 51; 62; 64; 75; 81; 84; 112; 124; 126
DDE, 35
DEBUG_ONLY_THIS_PROCESS, 79
DEBUG_PROCESS, 79
DefWindowProc, 58
DeleteCriticalSection, 52; 58; 71; 77; 106; 134
DeleteFile, 153
DETACHED_PROCESS, 79
DeviceIoControl, 158
DosDateTimeToFileTime, 150
DrawText, 52; 58; 59; 67; 76; 106; 107; 113; 130; 138
DRIVE_CDROM, 156
DRIVE_FIXED, 156
DRIVE_RAMDISK, 156
DRIVE_REMOTE, 156
DRIVE_REMOVABLE, 156
Ellipse, 59
EndDialog, 94
EnterCriticalSection, 52; 53; 54; 55; 59; 67; 68; 70; 76; 77; 106; 107; 108; 130; 131; 134
errno, 45
ERROR_ALREADY_EXISTS, 99; 101; 102; 108; 111; 116; 121; 145
ERROR_NO_MORE_FILES, 154
EVENT_ALL_ACCESS, 99; 109
EVENT_MODIFY_STATE, 99
ExitProcess, 82
ExitThread, 48; 49
FAT, 140
FILE_ATTRIBUTE_ARCHIVE, 144; 148
FILE_ATTRIBUTE_COMPRESSED, 144; 148
FILE_ATTRIBUTE_HIDDEN, 144; 148
FILE_ATTRIBUTE_NORMAL, 144; 148
FILE_ATTRIBUTE_READONLY, 144; 148
FILE_ATTRIBUTE_SYSTEM, 144; 148
FILE_BEGIN, 146
FILE_CURRENT, 147
FILE_END, 147
FILE_FLAG_BACKUP_SEMANTICS, 145
FILE_FLAG_DELETE_ON_CLOSE, 145
FILE_FLAG_NO_BUFFERING, 144
FILE_FLAG_OVERLAPPED, 144; 150; 152; 159
FILE_FLAG_POSIX_SEMANTICS, 145
FILE_FLAG_RANDOM_ACCESS, 144
FILE_FLAG_SEQUENTIAL_SCAN, 145
FILE_FLAG_WRITE_THROUGH, 144
FILE_NOTIFY_CHANGE_ATTRIBUTES, 155
FILE_NOTIFY_CHANGE_DIR_NAME, 155
FILE_NOTIFY_CHANGE_FILE_NAME, 155
FILE_NOTIFY_CHANGE_LAST_WRITE, 155
FILE_NOTIFY_CHANGE_SECURITY, 155
FILE_NOTIFY_CHANGE_SIZE, 155
FILE_SHARE_READ, 144; 147
FILE_SHARE_WRITE, 144; 147
FILETIME, 149
FileTimeToDosDateTime, 150
FileTimeToSystemTime, 149
FindClose, 154
FindCloseChangeNotification, 155
FindFirstChangeNotification, 155
FindFirstFile, 154
FindNextChangeNotification, 155
FindNextFile, 154
FLAT, 14; 19
FlushFileBuffers, 146
free, 8; 17; 35; 36; 38; 40; 71; 77; 134
FS_CASE_IS_PRESERVED, 157
FS_CASE_SENSITIVE, 157
FS_FILE_COMPRESSION, 157
FS_PERSISTENT_ACLS, 157
FS_UNICODE_STORED_ON_DISK, 157
FS_VOL_IS_COMPRESSED, 157
FSCTL_DISMOUNT_VOLUME, 158
FSCTL_GET_COMPRESSION, 158
FSCTL_LOCK_VOLUME, 158
FSCTL_SET_COMPRESSION, 158
FSCTL_UNLOCK_VOLUME, 158
GDT, 11
GDT, 10
GENERIC IOCTL, 158
GENERIC_READ, 143
GENERIC_WRITE, 144
GetCurrentDirectory, 153
GetDiskFreeSpace, 156
GetDriveType, 156
GetExceptionCode, 26; 32; 37; 38; 39
GetExitCodeThread, 46; 49
GetFileAttributes, 148
GetFileInformationByHandle, 150
GetFileSize, 148
GetFileTime, 149
GetLastError, 36; 38; 39; 78; 87; 93; 97; 99; 100; 101; 102; 104; 108; 111; 116; 121; 122; 145; 146; 147; 148; 154
GetLogicalDrives, 156
GetLogicalDriveStrings, 156
GetOverlappedResult, 151
GetProcessHeap, 33; 34; 38; 39; 40
GetSystemDirectory, 153
GetThreadPriority, 48; 69; 77; 132
GetVolumeInformation, 157
GetWindowLong, 76; 77
GetWindowsDirectory, 153
Global Descriptor Table, 10
Global Heap, 12
GlobalAlloc, 13; 15; 35; 36
GlobalDiscard, 35
GlobalFlags, 35
GlobalFree, 35
GlobalHandle, 35
GlobalLock, 13; 35
GlobalReAlloc, 35
GlobalSize, 35
GlobalUnlock, 35
GMEM_DDESHARE, 35
HANDLE_MSG, 30
HEAP_GENERATE_EXCEPTIONS, 33; 34; 37; 39
HEAP_NO_SERIALIZE, 33; 34; 35; 38; 40
HEAP_REALLOC_IN_PLACE_ONLY, 34; 37; 39
HEAP_ZERO_MEMORY, 34; 38; 39
HeapAlloc, 34; 35; 37; 38; 39
HeapCreate, 33; 34; 36; 39
HeapDestroy, 33; 34; 38
HeapFree, 34; 35; 38; 39; 40
HeapReAlloc, 34; 35; 37; 38; 39
HeapSize, 34
High Memory Area, 9
High Performance File System, 142
HIGH_PRIORITY_CLASS, 43; 66; 80; 88; 128
HPFS, 140; 142
IBM Lan Server, 142
IDLE_PRIORITY_CLASS, 43; 66; 80; 81; 88; 129
INFINITE, 48; 96
InitializeCriticalSection, 51; 58; 64; 75; 106; 107; 127
INT21h, 9
InterlockedDecrement, 110
InterlockedExchange, 110
InterlockedIncrement, 110
INVALID_HANDLE_VALUE, 145; 154; 155
IOCTL_DISK_CHECK_VERIFY, 158
IOCTL_DISK_EJECT_MEDIA, 158
IOCTL_DISK_FORMAT_TRACKS, 158
IOCTL_DISK_GET_DRIVE_GEOMETRY, 158
IOCTL_DISK_GET_DRIVE_LAYOUT, 158
IOCTL_DISK_GET_MEDIA_TYPES, 158
IOCTL_DISK_GET_PARTITION_INFO, 158
IOCTL_DISK_LOAD_MEDIA, 158
IOCTL_DISK_MEDIA_REMOVAL, 158
IOCTL_DISK_PERFORMANCE, 158
IOCTL_DISK_REASSIGN_BLOCKS, 158
IOCTL_DISK_SET_DRIVE_LAYOUT, 158
IOCTL_DISK_SET_PARTITION_INFO, 158
IOCTL_DISK_VERIFY, 158
IOCTL_SERIAL_LSRMST_INSERT, 158
IsDlgButtonChecked, 94
LDT, 11
LDT, 10
LeaveCriticalSection, 52; 54; 55; 59; 67; 68; 71; 76; 77; 106; 107; 108; 130; 131; 134
LoadImage, 30
LoadModule, 78
Local Descroptor Table, 10
Local Heap, 13
LocalAlloc, 13; 35; 36
LocalDiscard, 35
LocalFlags, 35
LocalFree, 35
LocalHandle, 35
LocalLock, 13; 35
LocalReAlloc, 35
LocalSize, 35
LockFile, 147; 148
MAKEINTRESOURCE, 92
malloc, 8; 35; 36; 38; 40; 64; 75; 77; 127
MAX_PATH, 99; 108; 154
MDICREATESTRUCT, 75
MDI-приложение, 59
MEM_COMMIT, 16; 17; 23; 31
MEM_DECOMMIT, 17
MEM_RELEASE, 17; 23; 24; 31
MEM_RESERVE, 16; 17; 23; 30; 31
MEM_TOP_DOWN, 16
Microsoft Defrag, 141
MoveFile, 152; 154
MOVEFILE_COPY_ALLOWED, 153
MOVEFILE_DELAY_UNTIL_REBOOT, 153
MOVEFILE_REPLACE_EXISTING, 153
MoveFileEx, 152; 154
Mutex, 108
NORMAL_PRIORITY_CLASS, 43; 66; 80; 81; 88; 128
Norton Speedisk, 141
NTFS, 140
OPEN_ALWAYS, 144
OPEN_EXISTING, 144
OpenEvent, 98; 99; 100; 102; 103; 104
OpenFile, 86; 92; 141
OpenMutex, 109
OpenSemaphore, 121; 122
OVERLAPPED, 150; 159
PAGE_EXECUTE, 16
PAGE_EXECUTE_READ, 16
PAGE_EXECUTE_READWRITE, 16
PAGE_GUARD, 17; 19; 20; 21; 25; 28
PAGE_NOACCESS, 17; 18; 23; 24; 28; 31; 32
PAGE_NOCACHE, 17
PAGE_READONLY, 16; 17; 18; 19; 20; 24; 28
PAGE_READWRITE, 16; 18; 20; 25; 28
pipe, 143
POSIX, 143
PostQuitMessage, 58
process, 42
process.h, 47
PROCESS_INFORMATION, 82
PROCESS_VM_OPERATION, 19
Prototype Page Table Entry, 15
PulseEvent, 98; 100
ReadFile, 145; 146; 150; 151; 152
ReadFileEx, 151
REALTIME_PRIORITY_CLASS, 43; 66; 76; 80; 88; 128
Rectangle, 59
RegisterClass, 30
RegisterClassEx, 30
ReleaseMutex, 109; 113; 114; 115; 116; 117
ReleaseSemaphore, 122; 127; 134; 138
RemoveDirectory, 154
Requested Privilege Level, 11
ResetEvent, 98; 99; 100
Resource Kit for Windows NT, 44
ResumeThread, 46; 48; 68; 77; 79; 131
RTL_CRITICAL_SECTION, 106
SCSI, 141; 158
SECURITY_ATTRIBUTES, 45; 78; 99; 108; 121; 143; 153
SEMAPHORE_ALL_ACCESS, 122
SEMAPHORE_MODIFY_STATE, 122
SendMessage, 77
SetCurrentDtirecory, 153
SetEndOfFile, 147; 148
SetEvent, 98; 99; 100; 104; 105
SetFileAttributes, 148
SetFilePointer, 146; 147; 148
SetFileTime, 149
SetLastError, 39
SetPriorityClass, 48; 66; 76; 128; 129
SetProcessWorkingSetSize, 18
SetThreadPriority, 43; 47; 68; 69; 77; 131
SetVolumeLabel, 157
SetWindowLong, 75
Sleep, 48; 54; 55; 59; 71; 115; 116; 117; 134
STARTF_USECOUNTCHARS, 81
STARTF_USEFILLATTRIBUTE, 81
STARTF_USEPOSITION, 81
STARTF_USESHOWWINDOW, 80
STARTF_USESIZE, 81
STARTF_USESTDHANDLES, 81
STARTUPINFO, 81; 92
STATUS_GUARD_PAGE, 17
STILL_ACTIVE, 49
SuspendThread, 48; 68; 77; 131
SW_HIDE, 82
SW_MAXIMIZE, 82
SW_MINIMIZE, 81
SW_RESTORE, 82
SW_SHOW, 82
SW_SHOWDEFAULT, 81; 82
SW_SHOWMAXIMIZED, 82
SW_SHOWMINIMIZED, 82
SW_SHOWMINNOACTIVE, 82
SW_SHOWNA, 82
SW_SHOWNOACTIVATE, 82
SW_SHOWNORMAL, 82
SYNCHRONIZE, 99; 109; 122
SYSTEMTIME, 149
SystemTimeToFileTime, 149
Table Indicator, 11
TerminateProcess, 82
TerminateThread, 48; 70; 77; 132
thread, 42
THREAD_PRIORITY_ABOVE_NORMAL, 43; 48; 69; 132
THREAD_PRIORITY_BELOW_NORMAL, 43; 48; 69; 132
THREAD_PRIORITY_ERROR_RETURN, 48
THREAD_PRIORITY_HIGHEST, 43; 48; 69; 131; 132
THREAD_PRIORITY_IDLE, 43; 48
THREAD_PRIORITY_LOWEST, 43; 48; 68; 69; 77; 131; 132
THREAD_PRIORITY_NORMAL, 43; 48; 69; 131; 132
THREAD_PRIORITY_TIME_CRITICAL, 43; 48
TINY, 14
TRUNCATE_EXISTING, 144
UNICODE, 142
UNIX-программы, 143
UnlockFile, 147; 148
VFAT, 141
VirtualAlloc, 16; 17; 18; 23; 30; 31
VirtualFree, 17; 23; 24; 31
VirtualLock, 18; 26; 32
VirtualProtectEx, 19
VirtualQuery, 19
VirtualQueryEx, 19
WAIT_ABANDONED, 97
WAIT_ABANDONED_0, 97
WAIT_FAILED, 93; 97
WAIT_OBJECT_0, 97
WAIT_TIMEOUT, 97
WaitForMultipleObjects, 52; 58; 97; 98; 109; 113; 155
WaitForSingleObject, 87; 93; 95; 96; 97; 98; 101; 102; 103; 108; 109; 113; 114; 115; 116; 117; 120; 122; 133; 139; 151; 155
WIN32_FIND_DATA, 154
WinExec, 78
WM_CLOSE, 67; 76; 77; 130; 138
WM_COMMAND, 23; 27; 30; 31; 51; 53; 55; 59; 64; 66; 67; 68; 75; 76; 77; 85; 86; 87; 92; 93; 112; 114; 126; 129; 131; 138; 139
WM_COMMNAD, 58
WM_CREATE, 23; 30; 31; 51; 55; 58; 63; 74; 112; 126
WM_DESTROY, 23; 30; 31; 51; 55; 58; 67; 112; 129
WM_INITDIALOG, 93
WM_MDICREATE, 75
WM_MDIDESTROY, 77
WM_PAINT, 49; 51; 55; 58; 67; 76; 112; 116; 130; 138
WM_RBUTTONDOWN, 76
WNDCLASSEX, 30
WriteFile, 145; 146; 150; 151; 152
WriteFileEx, 151
асинхронные операции с файлами, 150
атрибуты файла, 148
базовый адрес, 11
виртуальная память, 12
вытесняющая мультизадачность, 41
глобальный пул, 12
дескриптор прототипа PTE, 15
дескриптор страницы памяти, 15
задача, 42
каталог таблиц страниц, 12
каталог таблиц страниц, 12
классы приоритета процессов, 43
критическая секция, 106
линейный адрес, 10
логический адрес, 9
локальный пул, 13
механизм динамической передачи сообщений DDE, 35
невытесняющая мультизадачность, 41
область старшей памяти, 9
объект Mutex, 108
объект-событие, 98
объекты синхронизации, 44
объекты-семафоры, 119
относительный приоритет задач, 43
передача данных между процессами, 45
переключательная мультизадачность, 41
поток, 42
приложение Process Viewer, 44
приложение Process Walker, 19
процесс, 42; 78
реальный режим работы процессора, 8
сегмент, 8
селектор, 10
смещение, 8; 10
состояние страниц памяти, 15
страницы памяти, 12
таблица страниц, 12
трубы, 143
уровень приоритета задач, 42
устройство чтения CD-ROM, 156
физический адрес, 8
формат селектора, 11
электронный диск, 156
Преимущества файловой системы NTFS
Прежде чем мы перечислим достоинства файловой системы NTFS, сделаем краткий обзор недостатков других файловых систем, созданных для различных операционныъх систем персональных компьютеров.
Приложение HEAPMEM
На примере приложения HEAPMEM мы покажем вам, как можно использовать функции, предназначенные для работы с пулами памяти.
В отличие от предыдущего приложения, приложение HEAPMEM работает в так называемом консольном (или текстовом) режиме. Такое приложение может пользоваться стандартными функциями консольного ввода/вывода из библиотеки C++. Для него система создает отдельное окно (рис. 1.19).
Рис. 1.19. Окно консольного приложения HEAPMEM
Что делает наше приложение?
Приложение HEAPMEM тремя различными способами решает одну и ту же задачу: получение небольшого блока памяти, запись в нее текстовой строки и отображение этой строки в консольном окне, показанном на рис. 1.19.
Первый способ предполагает использование динамического пула памяти и обработку исключений. В приложении намеренно создаются две ситуации, в которых происходят исключения с кодами C0000017 и C0000005. Во второй раз приложение работает со стандартным пулом памяти и не обрабатыает исключения, проверяя код завершения функций. И, наконец, третий способ связан с использованием функций malloc и free.
Приложение MultiMDI
В предыдущем разделе мы привели исходные тексты однооконного мультизадачного приложения. Больший интерес, на наш взгляд, имеет создание многооконного MDI-приложения, в котором для каждого окна создается своя задача. В качестве шаблона для создания такого приложения вы можете взять исходные тексты приложения MultiMDI, которые мы приведем в этом разделе.
В главном окне приложения MultiMDI вы можете создать дочерние MDI-окна, в которых выполняется циклическое отображение эллипсов случайной формы и цвета (рис. 2.4).
Рис. 2.4. Мультизадачное MDI-приложение MultiMDI
Кроме того что вы можете создавать дочерние окна и с помощью стандартного для MDI-приложений меню Window изменять расположение дочерних окон и представляющих их в минимизированном виде пиктограмм, у вас есть возможность управлять классом приоритета процесса, в рамках которого выполняется приложение, а также устанавливать относительный приоритет отдельных задач, приостанавливать их, возобновлять выполнение приостановленных задач, удалять задачи и закрывать дочерние окна.
С помощью меню Priotitry class, показанном на рис. 2.5, вы можете устанавливать один из четырех классов приоритета текущего процесса.
Рис. 2.5. Меню Priotitry class для установки приоритета процесса
Если сделать щелчок правой клавишей мыши во внутренней области дочернего MDI-окна, на экране появится плавающее меню, с помощью которого можно управлять задачей, запущенной для данного окна. Это меню показано на рис. 2.6.
Рис. 2.6. Плавающее меню, предназначенное для управления задачей, запущенной для MDI-окна
С помощью строки Suspend можно приостановить работу задачи, а с помощью строки Resume - восстановить. Так как для каждой задачи система создает счетчик приостановок, то если вы два раза подряд приостановили задачу, выбрав строку Suspend, то для возобновления ее работы вам придется два раза выбрать строку Resume.
Изменяя относительный приоритет отдельных окон, вы можете заметить, что скорость перерисовки эллипсов также изменяется. Если вы работаете на быстром компьютере, эффект будет заметнее, если создать не менее 20 - 30 дочерних MDI-окон.
Выбрав строку Get Priority, вы можете узнать текущий относительный приоритет для любого дочернего MDI-окна. Значение относительного приоритета будет показано в отдельной диалоговой панели (рис. 2.7).
Рис. 2.7. Просмотр относительного приоритета задачи, запущенной для MDI-окна
Выбрав строку Kill Thread, вы принудительно завершите работу соответствующей задачи, после чего она не будет отзываться на любые команды.
С помощью строки Close Window вы можете закрыть любой дочернее MDI-окно, в том числе то, для которого было выполнено принудительное завершение работы задачи. Дочернее окно можно также закрыть, сделав двойной щелчок левой клавишей мыши по системному меню дочернего окна. В меню Window есть строка Close all, позволяющая закрыть сразу все дочерние MDI-окна.
Приложение MultiSDI
Наше первое мультизадачное приложение называется MultiSDI. В его главном окне (рис. 2.3) рисуют четыре задачи, одна из которых является главной и соответствует функции WinMain.
Рис. 2.3. Главное окно приложения MultiSDI
Немного позже мы приведем исходные тексты MDI-приложения MultiMDI, в котором для каждого дочернего MDI-окна создается отдельная задача.
Главная задача приложения MultiSDI рисует в главном окне приложения во время обработки сообщения WM_PAINT. Кроме того, она создает еще три задачи, которые рисуют в окне приложения, соответственно, эллипсы, прямоугольники и текстовую строку TEXT. Цвет и размеры фигур, а также цвет текстовой строки и цвет фона, на котором отображается эта строка, выбираются случайным образом, поэтому содержимое главного окна изменяется хаотически. Это выглядит достоточно забавно.
Так как все задачи выполняют рисование в одном окне, нам пришлось использовать простейшее средство синхронизации задач - критическую секцию. Проблемы синхронизации задач, работающих параллельно, мы рассмотрим в отдельной главе нашей книги.
Приложение MutexSDI
Для демонстрации способор работы с объектами Mutex мы переделали исходные тексты приложения MultiSDI, в котором синхронизация задач выполнялась при помощи критических секций. В новом приложении MutexSDI вместо критической секции мы использовали объект Mutex. Кроме того, в приложении MutexSDI демонстрируется способ обнаружения работающей копии приложения, основанный на том, что имена объектов Mutex являются глобальными (так же, как и имена объектов-событий, рассмотренных нами ранее).
Исходный текст приложения приведен в листинге 4.3. Так как он сделан из исходного текста приложения MultiSDI, мы опишем только основные отличия.
Листинг 4.3. Файл mutexsdi/mutexsdi.c
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <process.h>
#include <stdio.h>
#include "resource.h"
#include "afxres.h"
#include "mutexsdi.h"
HINSTANCE hInst;
char szAppName[] = "MutexMultiSDI";
char szAppTitle[] = "Multithread SDI Application with Mutex";
// Имя объекта Mutex
char szMutexName[] = "$MyMutex$MutexMultiSDI$";
// Идентификатор объекта Mutex
HANDLE hMutex;
// Признак завершения всех задач
BOOL fTerminate = FALSE;
// Массив идентификаторов запущенных задач
HANDLE hThreads[3];
// -----------------------------------------------------
// Функция WinMain
// -----------------------------------------------------
int APIENTRY
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hWnd;
MSG msg;
// Сохраняем идентификатор приложения
hInst = hInstance;
// Создаем объект Mutex
hMutex = CreateMutex(NULL, FALSE, szMutexName);
if(hMutex == NULL)
{
MessageBox(NULL, "CreateMutex Error",
szAppTitle, MB_OK | MB_ICONEXCLAMATION);
return 0l;
}
// Преверяем, не было ли это приложение запущено ранее
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
MessageBox(NULL, "MutexSDI already started",
szAppTitle, MB_OK | MB_ICONEXCLAMATION);
return 0l;
}
// Регистрируем класс окна
memset(&wc, 0, sizeof(wc));
wc.cbSize = sizeof(WNDCLASSEX);
wc.hIconSm = LoadImage(hInst,
MAKEINTRESOURCE(IDI_APPICONSM),
IMAGE_ICON, 16, 16, 0);
wc.style = 0;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
wc.hIcon = LoadImage(hInst,
MAKEINTRESOURCE(IDI_APPICON),
IMAGE_ICON, 32, 32, 0);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU);
wc.lpszClassName = szAppName;
if(!RegisterClassEx(&wc))
if(!RegisterClass((LPWNDCLASS)&wc.style))
return FALSE;
// Создаем главное окно приложения
hWnd = CreateWindow(szAppName, szAppTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
NULL, NULL, hInst, NULL);
if(!hWnd) return(FALSE);
// Отображаем окно и запускаем цикл
// обработки сообщений
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
// -----------------------------------------------------
// Функция WndProc
// -----------------------------------------------------
LRESULT WINAPI
WndProc( HWND hWnd, UINT msg, WPARAM wParam,
LPARAM lParam)
{
switch(msg)
{
HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate);
HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy);
HANDLE_MSG(hWnd, WM_PAINT, WndProc_OnPaint);
HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand);
default:
return(DefWindowProc(hWnd, msg, wParam, lParam));
}
}
// -----------------------------------------------------
// Функция WndProc_OnCreate
// -----------------------------------------------------
BOOL WndProc_OnCreate(HWND hWnd,
LPCREATESTRUCT lpCreateStruct)
{
// Сбрасываем флаг завершения задач
fTerminate = FALSE;
// Запускаем три задачи, сохраняя их идентификаторы
// в массиве
hThreads[0] = (HANDLE)_beginthread(PaintEllipse,
0, (void*)hWnd);
hThreads[1] = (HANDLE)_beginthread(PaintRect,
0, (void*)hWnd);
hThreads[2] = (HANDLE)_beginthread(PaintText,
0, (void*)hWnd);
return TRUE;
}
// -----------------------------------------------------
// Функция WndProc_OnDestroy
// -----------------------------------------------------
#pragma warning(disable: 4098)
void WndProc_OnDestroy(HWND hWnd)
{
// Устанавливаем флаг завершения задач
fTerminate = TRUE;
// Дожидаемся завершения всех трех задач
WaitForMultipleObjects(3, hThreads, TRUE, INFINITE);
// Перед завершением освобождаем идентификатор
// объекта Mutex
CloseHandle(hMutex);
// Останавливаем цикл обработки сообщений, расположенный
// в главной задаче
PostQuitMessage(0);
return 0L;
}
// -----------------------------------------------------
// Функция WndProc_OnPaint
// -----------------------------------------------------
#pragma warning(disable: 4098)
void WndProc_OnPaint(HWND hWnd)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rc;
DWORD dwRetCode;
// Ожидаем, пока объект Mutex не перейдет в
// отмеченное состояние
dwRetCode = WaitForSingleObject(hMutex, INFINITE);
// Если не было ошибок, выполняем рисование
if(dwRetCode == WAIT_OBJECT_0)
{
// Перерисовываем внутреннюю область окна
hdc = BeginPaint(hWnd, &ps);
GetClientRect(hWnd, &rc);
DrawText(hdc, "SDI Window", -1, &rc,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hWnd, &ps);
// Переводим объект Mutex в неотмеченное состояние
ReleaseMutex(hMutex);
}
return 0;
}
// -----------------------------------------------------
// Функция WndProc_OnCommand
// -----------------------------------------------------
#pragma warning(disable: 4098)
void WndProc_OnCommand(HWND hWnd, int id,
HWND hwndCtl, UINT codeNotify)
{
switch (id)
{
case ID_FILE_EXIT:
{
// Завершаем работу приложения
PostQuitMessage(0);
return 0L;
break;
}
case ID_HELP_ABOUT:
{
MessageBox(hWnd,
"Multithread SDI Application with Mutex\n"
"(C) Alexandr Frolov, 1996\n"
"Email: frolov@glas.apc.org",
szAppTitle, MB_OK | MB_ICONINFORMATION);
return 0L;
break;
}
default:
break;
}
return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify,
DefWindowProc);
}
// -----------------------------------------------------
// Функция задачи PaintEllipse
// -----------------------------------------------------
void PaintEllipse(void *hwnd)
{
HDC hDC;
RECT rect;
LONG xLeft, xRight, yTop, yBottom;
short nRed, nGreen, nBlue;
HBRUSH hBrush, hOldBrush;
DWORD dwRetCode;
srand((unsigned int)hwnd);
while(!fTerminate)
{
// Ожидаем, пока объект Mutex не перейдет в
// отмеченное состояние
dwRetCode = WaitForSingleObject(hMutex, INFINITE);
// Если не было ошибок, выполняем рисование
if(dwRetCode == WAIT_OBJECT_0)
{
hDC = GetDC(hwnd);
nRed = rand() % 255;
nGreen = rand() % 255;
nBlue = rand() % 255;
GetWindowRect(hwnd, &rect);
xLeft = rand() % (rect.left + 1);
xRight = rand() % (rect.right + 1);
yTop = rand() % (rect.top + 1);
yBottom = rand() % (rect.bottom + 1);
hBrush = CreateSolidBrush(RGB(nRed, nGreen, nBlue));
hOldBrush = SelectObject(hDC, hBrush);
Ellipse(hDC, min(xLeft, xRight), min(yTop, yBottom),
max(xLeft, xRight), max(yTop, yBottom));
SelectObject(hDC, hOldBrush);
DeleteObject(hBrush);
ReleaseDC(hwnd, hDC);
ReleaseMutex(hMutex);
}
// Если ожидание было отменено или произошла ошибка,
// прерываем цикл
else
{
break;
}
Sleep(100);
}
}
// -----------------------------------------------------
// Функция задачи PaintRect
// -----------------------------------------------------
void PaintRect(void *hwnd)
{
HDC hDC;
RECT rect;
LONG xLeft, xRight, yTop, yBottom;
short nRed, nGreen, nBlue;
HBRUSH hBrush, hOldBrush;
DWORD dwRetCode;
srand((unsigned int)hwnd + 1);
while(!fTerminate)
{
dwRetCode = WaitForSingleObject(hMutex, INFINITE);
if(dwRetCode == WAIT_OBJECT_0)
{
hDC = GetDC(hwnd);
nRed = rand() % 255;
nGreen = rand() % 255;
nBlue = rand() % 255;
GetWindowRect(hwnd, &rect);
xLeft = rand() % (rect.left + 1);
xRight = rand() % (rect.right + 1);
yTop = rand() % (rect.top + 1);
yBottom = rand() % (rect.bottom + 1);
hBrush = CreateSolidBrush(RGB(nRed, nGreen, nBlue));
hOldBrush = SelectObject(hDC, hBrush);
Rectangle(hDC, min(xLeft, xRight), min(yTop, yBottom),
max(xLeft, xRight), max(yTop, yBottom));
SelectObject(hDC, hOldBrush);
DeleteObject(hBrush);
ReleaseDC(hwnd, hDC);
ReleaseMutex(hMutex);
}
else
{
break;
}
Sleep(100);
}
}
// -----------------------------------------------------
// Функция задачи PaintText
// -----------------------------------------------------
void PaintText(void *hwnd)
{
HDC hDC;
RECT rect;
LONG xLeft, xRight, yTop, yBottom;
short nRed, nGreen, nBlue;
DWORD dwRetCode;
srand((unsigned int)hwnd + 2);
while(!fTerminate)
{
dwRetCode = WaitForSingleObject(hMutex, INFINITE);
if(dwRetCode == WAIT_OBJECT_0)
{
hDC = GetDC(hwnd);
GetWindowRect(hwnd, &rect);
xLeft = rand() % (rect.left + 1);
xRight = rand() % (rect.right + 1);
yTop = rand() % (rect.top + 1);
yBottom = rand() % (rect.bottom + 1);
nRed = rand() % 255;
nGreen = rand() % 255;
nBlue = rand() % 255;
SetTextColor(hDC, RGB(nRed, nGreen, nBlue));
nRed = rand() % 255;
nGreen = rand() % 255;
nBlue = rand() % 255;
SetBkColor(hDC, RGB(nRed, nGreen, nBlue));
TextOut(hDC, xRight - xLeft,
yBottom - yTop,"TEXT", 4);
ReleaseDC(hwnd, hDC);
ReleaseMutex(hMutex);
}
else
{
break;
}
Sleep(100);
}
}
В области глобальных переменных определен массив szMutexName, в котором хранится имя объекта Mutex, используемого нашим приложением. Идентификатор этого объекта после его создания будет записан в глобальную переменную hMutex.
Функция WinMain создает объект Mutex, вызывая для этого функцию CreateMutex, как это показано ниже:
hMutex = CreateMutex(NULL, FALSE, szMutexName);
if(hMutex == NULL)
{
. . .
return 0l;
}
В качестве атрибутов защиты передается значение NULL. Так как второй параметр функции CreateMutex равен FALSE, объект Mutex после создания будет находиться в отмеченном состоянии.
Если при создании объекта функция CreateMutex не вернула признак ошибки (значение NULL), приложение проверяет, действительно ли был создан новый объект Mutex или же функция вернула идентификатор для уже существующего в системе объекта с таким же именем. Проверка выполняется с помощью функции GetLastError:
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
MessageBox(NULL, "MutexSDI already started",
szAppTitle, MB_OK | MB_ICONEXCLAMATION);
return 0l;
}
Если эта функция вернула значение ERROR_ALREADY_EXISTS, значит была запущена копия этого приложения, которая и создала объект Mutex с именем szMutexName. В этом случае приложение выводит сообщение и завершает свою работу.
В нашем приложении четыре задачи могут рисовать в главном окне. Это главная задача процесса и три задачи, созданные главной задачей.
Главная задача выполняет рисование при обработке сообщения WM_PAINT. Обработкой этого сообщения занимается функция WndProc_OnPaint.
Перед тем как получить контекст отображения и приступить к рисованию, эта функция вызывает функцию WaitForSingleObject, пытаясь стать владельцем объекта Mutex:
dwRetCode = WaitForSingleObject(hMutex, INFINITE);
Если никакая другая задача в данный момент не претендует на этот объект и, следовательно, не собирается рисовать в окне приложения, функция WaitForSingleObject немедленно возвращает управление с кодом возврата, равным WAIT_OBJECT_0. После этого функция WndProc_OnPaint получает контекст отображения, выполняет рисование, освободает контекст отображения и затем переводит объект Mutex в неотмеченное состояние, вызывая функцию ReleaseMutex:
ReleaseMutex(hMutex);
После этого другие задачи, выполняющие ожидание для объекта Mutex, могут продолжить свою работу.
В приложении определены три функции задач, выполняющих рисование в окне приложения. Синхронизация всех этих функций между собой и с главной задачей процесса выполняется одинаковым способом. Все эти задачи выполняют в цикле ожидание события для объекта Mutex с идентификатором hMutex:
while(!fTerminate)
{
dwRetCode = WaitForSingleObject(hMutex, INFINITE);
if(dwRetCode == WAIT_OBJECT_0)
{
hDC = GetDC(hwnd);
. . .
Ellipse(hDC, min(xLeft, xRight), min(yTop, yBottom),
max(xLeft, xRight), max(yTop, yBottom));
. . .
ReleaseDC(hwnd, hDC);
ReleaseMutex(hMutex);
}
else
break;
Sleep(100);
}
Если ни одна задача не владеет объектом Mutex, функция WaitForSingleObject возвращает значение WAIT_OBJECT_0. При этом задача выполняет рисование в окне приложения. В том случае когда какая-либо другая задача владеет объектом Mutex, данная задача перейдет в состояние ожидания. Если при ожидании произошла ошибка, цикл завершает свою работу.
После рисования задача переводит объект Mutex в неотмеченное состояние с помощью функции ReleaseMutex.
Кратко перечислим другие файлы, имеющие отношение к приложению MutexSDI.
В файле mutexsdi.h (листинг 4.4) находятся прототипы функций, определенных в приложении.
Листинг 4.4. Файл mutexsdi/mutexsdi.h
LRESULT WINAPI
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL WndProc_OnCreate(HWND hWnd,
LPCREATESTRUCT lpCreateStruct);
void WndProc_OnDestroy(HWND hWnd);
void WndProc_OnPaint(HWND hWnd);
void WndProc_OnCommand(HWND hWnd, int id,
HWND hwndCtl, UINT codeNotify);
void PaintEllipse(void *hwnd);
void PaintRect(void *hwnd);
void PaintText(void *hwnd);
Файл resource.h (листинг 4.5) создается автоматически и содержит определения констант для файла ресурсов приложения.
Листинг 4.5. Файл mutexsdi/resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by MutexSDI.RC
//
#define IDR_APPMENU 102
#define IDI_APPICON 103
#define IDI_APPICONSM 104
#define ID_FILE_EXIT 40001
#define ID_HELP_ABOUT 40003
#define ID_FORMAT_BOLD 40010
#define ID_FORMAT_ITALIC 40011
#define ID_FORMAT_UNDERLINE 40012
#define ID_FORMAT_PARAGRAPH_LEFT 40014
#define ID_FORMAT_PARAGRAPH_RIGHT 40015
#define ID_FORMAT_PARAGRAPH_CENTER 40016
#define ID_EDIT_DELETE 40021
#define ID_FILE_SAVEAS 40024
#define ID_EDIT_SELECTALL 40028
#define ID_SETPROTECTION_PAGENOACCESS 40035
#define ID_SETPROTECTION_PAGEREADONLY 40036
#define ID_SETPROTECTION_PAGEREADWRITE 40037
#define ID_SETPROTECTION_PAGEGUARD 40038
#define ID_MEMORY_READ 40039
#define ID_MEMORY_WRITE 40040
#define ID_MEMORY_LOCK 40041
#define ID_MEMORY_UNLOCK 40042
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 121
#define _APS_NEXT_COMMAND_VALUE 40043
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
И, наконец, файл ресурсов приложения mutexsdi.rc приведен в листинге 4.6.
Листинг 4.6. Файл mutexsdi/mutexsdi.rc
//Microsoft Developer Studio generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
//////////////////////////////////////////////////////////////
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"
//////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
//////////////////////////////////////////////////////////////
// English (U.S.) resources
#if !defined(AFX_RESOURCE_DLL) defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
//////////////////////////////////////////////////////////////
// Menu
//
IDR_APPMENU MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "E&xit", ID_FILE_EXIT
END
POPUP "&Help"
BEGIN
MENUITEM "&About...", ID_HELP_ABOUT
END
END
#ifdef APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
// TEXTINCLUDE
//
1 TEXTINCLUDE DISCARDABLE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE DISCARDABLE
BEGIN
"#include ""afxres.h""\r\n"
"\0"
END
3 TEXTINCLUDE DISCARDABLE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
// Icon
//
// Icon with lowest ID value placed first to ensure
// application icon
// remains consistent on all systems.
IDI_APPICON ICON DISCARDABLE "mutexsdi.ico"
IDI_APPICONSM ICON DISCARDABLE "mutexssm.ico"
//////////////////////////////////////////////////////////////
// String Table
//
STRINGTABLE DISCARDABLE
BEGIN
ID_FILE_EXIT "Quits the application"
END
#endif // English (U.S.) resources
//////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
// Generated from the TEXTINCLUDE 3 resource.
//
//////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
Приложение PSTART
С помощью приложения PSTART, исходные тексты которого мы привели в этом разделе, вы сможете запускать процессы, определяя для них класс приоритета.
Главное окно приложения PSTART показано на рис. 3.1.
Рис. 3.1. Главное окно приложения PSTART
Выбрав из меню File строку Start process, вы можете выбрать в диалоговой панели Start Process программный файл запускаемого приложения (рис. 3.2).
Рис. 3.2. Диалоговая панель Start Process
Для выбора параметров запуска воспользуйтесь диалоговой панелью Start Options (рис. 3.3), которая появится на экране при выборе из меню File строки Options.
Рис. 3.3. Диалоговая панель Start Options
В этой диалоговой панели вы можете выбрать класс приоритета запускаемого процесса, а также, включив переключатель Wait process termination, использовать режим запуска, при котором запускающий процесс дожидается завершения выполнения дочернего процесса.
Приложение SEMMDI
Приложение SEMMDI создано на базе приложения MultiMDI и демонстрирует использование семфафоров для ограничения количества работающих задач, запущенных для MDI-окон.
На рис. 4.6 показано главное окно приложения SEMMDI. Мы запустили это приложение в среде операционной системы Microsoft Windows 95, так как она также является мультизадачной операционной системой и может работать с семафорами и другими объектами синхронизации. Разумеется, что в среде Microsoft Windows NT приложение SEMMDI также будет работать.
Рис. 4.6. Главное окно приложения SEMMDI, запущенного в среде операционной системы Microsoft Windows 95
Выбирая из меню File строку New, вы можете создавать новые MDI-окна. В первых двух созданных вами окнах начнется процесс рисования эллипсов случайных размеров и цвета. В остальных окнах отображается только строка Waiting.
Если теперь закрыть одно из двух работающих окон (в которых идет рисование эллипсов), одно из ожидающих окон “просыпается” и в нем начинается процесс рисования. Таким образом, сколько бы вы ни создали MDI-окон, рисование будет выполняться только в двух из них.
Выбирая из меню Semaphore строку Increment, вы можете увеличивать значение семафора на единицу. При этом максимальное количество окон, в которых идент процесс рисования, также будет увеличиваться.
Заметим, что ожидающие окна нельзя удалить. Все остальные возможности приложения MultiMDI сохранены. В частности, если сделать щелчок правой клавишей мыши в MDI-окне, на экране появится плавающее меню, с помощью которого можно выполнять над этим окном различные операции.
Приложение VIRTUAL
Для демонстрации описанных выше функций, работающих со страницами виртуальной памяти, мы подготовили исходные тексты приложения VIRTUAL. Это приложение получает из адресного пространства приложения одну страницу виртуальной памяти и позволяет вам вручную устанавливать тип доступа для нее, проверяя результат на операциях чтения, записи и фиксирования страницы.
При помощи меню Set protection (рис. 1.14) вы можете установить для полученной страницы памяти тип доступа PAGE_NOACCES, PAGE_READONLY, PAGE_READWRITE, а также комбинацию типов доступа PAGE_READWRITE и PAGE_GUARD.
Рис. 1.14. Меню Set protection, предназначенное для установки типа доступа
При помощи строк меню Memory (рис. 1.15) вы можете выполнять над полученной страницей памяти операции чтения, записи, фиксирования и расфиксирования (соответственно, строки Read, Write, Lock и Unlock).
Рис. 1.15. Меню Memory, предназначенное для выполнения различных операций над страницей памяти
Первоначально для страницы устанавливается код доступа PAGE_NOACCES, запрещающий любой доступ, поэтому при выборе из меню Memory любой строки вы получите сообщение об ошибке.
Если установить код доступа PAGE_READONLY, то, как и следовало ожидать, вы сможете выполнить только операции чтения, фиксирования и расфиксирования страницы. Тип доступа PAGE_READWRITE дополнительно разешает выполнение операции записи.
В том случае, когда для страницы устанволена комбинация типов доступа PAGE_READWRITE и PAGE_GUARD, при первой попытке выполнения над этой страницей любой операции появляется сообщение об ошибке, так как возникает исключение. Во второй раз эта же операция выполнится без ошибки, так как после возникновения исключения тип доступа PAGE_GUARD сбрасывается автоматически.
Когда любое исключение возникает в 16-разрядном приложении Microsoft Windows версии 3.1, оно завершает свою работу с сообщением о фатальной ошибке. С этим не может ничего сделать ни пользователь, ни программист, создающий такие приложения. Что же касается операционной системы Microsoft Windows NT, то приложение может самостоятельно выполнять обработку исключений.
На рис. 1. 16 показано сообщение, которое возникло бы на экране при попытке приложения, не обрабатывающего исключения, обратиться к странице с типом доступа PAGE_NOACCES для чтения. Прочитав его, пользователь может нажимать кнопку OK, что приведет к аварийному завершению работы приложения.
Рис. 1.16. Сообщение, возникающее на экране при попытке обращения к странице с типом доступа PAGE_NOACCES
Информация, представленная в этом сообщении, ничего не дает пользователю. Не сможет он воспользоваться и предложением запустить отладчик, нажав кнопку Cancel, так как едва ли у него есть исходные тексты приложения и, что самое главное, желание разбираться с ними.
Аналогично, при попытке приложения обратиться к странице с типом доступа PAGE_GUARD, на экране появится сообщение, показанное на рис.1.17.
Рис. 1.17. Сообщение, возникающее при попытке обращения к странице с типом доступа PAGE_GUARD
Наше приложение VIRTUAL способно обрабатывать исключения, поэтому даже при попытках выполнения неразрешенного вида доступа все ограничивается выводом соответствующего сообщения, после чего работа приложения может быть продолжена. На рис. 1.18 показано такое сообщение, возникающее при обращении к памяти с типом доступа PAGE_GUARD.
Рис. 1.18. Сообщение об исключении, отображаемое приложением VIRTUAL
Обработку исключений мы рассмотрим в отдельной главе одной из следующих наших книг, посвященных операционной системе Microsoft Windows NT, а сейчас только скажем, что при ее использовании вы можете значительно повысить устойчивость работы приложения, встроив в него мощную систему проверки (и, в некоторых случаях, даже исправления) различных ошибок.
Приложения EVENT и EVENTGEN
В консольных приложениях EVENT и EVENTGEN, исходные тексты которых приведены в этом разделе, мы демонстрируем использование объектов-событий для синхронизации задач, принадлежащих различным процессам.
Первым необходимо запускать приложение EVENT. Его задача заключается в том, чтобы следить за работой второго приложения EVENTGEN. Приложение EVENTGEN позволяет пользователю вводить с помощью клавиатуры и отображать в своем окне произвольные символы. Каждый раз когда пользователь вводит в окне приложения EVENTGEN какой-либо символ, в окне контролирующего приложения EVENT отображается символ ‘*’ (рис. 4.3).
Рис. 4.3. Окна приложений EVENT и EVENTGEN
Так как пока мы не научились еще передавать данные из одного процесса в другой, мы не можем отобаржать в окне приложения EVENT символы, которые пользователь вводит в окне приложения EVENTGEN. Однако пока нам этого и не надо, так как мы здесь демонстрируем лишь способ синхронизации задач.
Приостановка и возобновление выполнения задачи
В некоторых случаях имеет смысл приостановить выполнение задачи. Например, если пользователь работает с многооконным приложением, и для каждого окна запускается отдельная задача, для повышения производительности можно приостановить выполнение задач в неактивных окнах.
Приостановка выполнения задачи выполняется с помощью функции SuspendThread:
DWORD SuspendThread(HANDLE hThread);
Через единственный параметр этой функции нужно передать идентификатор приостанавливаемой задачи.
Для каждой задачи операционная система хранит счетчик приостановок, который увеличивается при каждом вызове фукнции SuspendThread. Если значение этого счетчика больше нуля, задача приостанавливается.
Для уменьшения значения счетчика приостановок и, соответственно, для возобновления выполнения задачи вы должны использовать фукнцию ResumeThread:
DWORD ResumeThread(HANDLE hThread);
В случае ошибки функции SuspendThread и ResumeThread возвращают значение 0xFFFFFFFF.
Прямое управление дисковым устройством
Несмотря на то что набор функций, предназначенных для работы с дисками и файлами в среде Microsoft Windows NT, достаточно мощный и удобный, иногда его возможностей может оказаться недостаточно. Например, если вы разрабатываете систему защиты от несанкционированного копирования дискет, вам могут потребоваться средства нестандартного форматирования дискет. Вам также может потребоваться выполнять из приложения извлечение сменного носителя данных или блокировку последнего в накопителе, проверку замены сменного носителя данных или другие “необычные” операции.
Создавая аналогичную програму MS-DOS, вы можете выполнить нестандартное форматиование, например, с помощью функций BIOS или обращаясь непосредственно к портам дискового контроллера. В среде Microsoft Windows NT оба эти способа непригодны, так как функции BIOS и порты контроллера диска недоступны для обычных приложений, работающих в защищенном режиме.
Выход, однако, есть.
Он заключается в прямом обращении к драйверу дискового устройства с помощью функции управления вводом/выводом, которая называется DeviceIoControl. По своим возможностям он напоминает способ, описанный нами в 19 томе “Библиотеки системного программиста” и связанный с использованием функций GENERIC IOCTL.
Прототип функции DeviceIoControl мы привели ниже:
BOOL DeviceIoControl(
HANDLE hDevice, // идентификатор устройства
DWORD dwIoControlCode, // код выполняемой операции
LPVOID lpInBuffer, // буфер для входных данных
DWORD nInBufferSize, // размер буфера lpInBuffer
LPVOID lpOutBuffer, // буфер для выходных данных
DWORD nOutBufferSize, // размер буфера lpOutBuffer
LPDWORD lpBytesReturned, // указатель на счетчик
// выведенных байт
LPOVERLAPPED lpOverlapped); // указатель на
// структуру OVERLAPPED
Через параметр hDevice вы должны передать идентификатор устройства, полученный от функции CreateFile. Для того чтобы воспользоваться этой функцией для открывания устройства, вы должны указать имя устройства следующим образом (пример приведен для диска C:):
hDevice = CreateFile(“\\\\.\\C:”, 0, FILE_SHARE_READ, NULL,
OPEN_EXISTING, 0, NULL);
С помощью параметра dwIoControlCode можно задать один из следующих кодов операции:
Код операции |
Описание |
FSCTL_DISMOUNT_VOLUME |
Размонтирование тома |
FSCTL_GET_COMPRESSION |
Определение состояния компрессии для каталога или файла |
FSCTL_LOCK_VOLUME |
Блокирование тома |
FSCTL_SET_COMPRESSION |
Установка состояния компрессии для каталога или файла |
FSCTL_UNLOCK_VOLUME |
Разблокирование тома |
IOCTL_DISK_CHECK_VERIFY |
Проверка замены носителя данных для устройства со сменным носителем |
IOCTL_DISK_EJECT_MEDIA |
Извлечение носителя данных из устройства с интерфейсом SCSI |
IOCTL_DISK_FORMAT_TRACKS |
Форматирование нескольких дорожек диска |
IOCTL_DISK_GET_DRIVE_GEOMETRY |
Получение информации о физической геометрии диска |
IOCTL_DISK_GET_DRIVE_LAYOUT |
Получение информации о всех разделах диска |
IOCTL_DISK_GET_MEDIA_TYPES |
Получение информации о среде, которую можно использовать для хранения данных в устройстве |
IOCTL_DISK_GET_PARTITION_INFO |
Полечение информации о разделе диска |
IOCTL_DISK_LOAD_MEDIA |
Загрузка носителя данных в устройство |
IOCTL_DISK_MEDIA_REMOVAL |
Включение или отключение механизма извлечения носителя данных |
IOCTL_DISK_PERFORMANCE |
Получение информации о производительности устройства |
IOCTL_DISK_REASSIGN_BLOCKS |
Перевод блоков диска в область резервных блоков |
IOCTL_DISK_SET_DRIVE_LAYOUT |
Создание разделов на диске |
IOCTL_DISK_SET_PARTITION_INFO |
Установка типа разделов диска |
IOCTL_DISK_VERIFY |
Выполнение логического форматирования |
IOCTL_SERIAL_LSRMST_INSERT |
Разрешение или запрещение добавления информации о состоянии линии и модема в поток передаваемых данных |
В буфер, адрес которого передается через параметр lpOutBuffer, будет записан результат выполнения операции. Формат этого буфера также зависит от кода операции.
При необходимости с помощью функции DeviceIoControl вы можете выполнять асинхронные операции, подготовив структуру типа OVERLAPPED и передав ее адрес через параметр lpOverlapped. Не забудьте также при открывании устройства указать функции CreateFile флаг FILE_FLAG_OVERLAPPED.
Проблемы синхронизации задач и процессов
Создавая мультизадачное приложение, необходимо тщательно планировать взаимодействие задач, избегая конфликтных ситуаций, когда различные задачи пытаются одновременно использовать один и тот же ресурс. Например, одна задача может выполнять запись в ячейку памяти нового значения, а вторая - чтение из нее. Результат чтения при этом будет зависеть от момента, когда произойдет чтение - до записи нового значения или после. Возможно возникновение взаимных блокировок задач, когда задачи будут бесконечно ждать друг друга.
Операционная система организует для процессов и задач последовательный доступ к таким ресурсам, как принтер или последовательный асинхронный порт, однако если несколько задач вашего приложения рисуют что-либо в одном окне, вы должны сами позаботиться об организации последовательного доступа к таким ресурсам, как контекст отображения, кисти, шрифты и так далее.
Специально для организации взаимодействия задач в операционной системе Microsoft Windows NT предусмотрены так называемые объекты синхронизации. Это средства организации последовательного использования ресуросов (mutex), семафоры (semaphore) и события (event). Мы рассмотрим перечисленные средства позже в этой главе.
Поиск ошибок, возникающих в результате неправильно организованной синхронизации задач, может отнять много времени, поэтому прежде чем принять решение об использовании мультизадачности в вашем приложении, следует хорошо подумать, нужно ли это и оценить полученные в результате преимущества (если они вообще будут).
Процессы и задачи в Microsoft Windows NT
В операционной системе Microsoft Windows NT существуют два понятия, имеющие отношение к мультизадачности. Это процессы и задачи.
Процесс (process) создается, когда программа загружается в память для выполнения. Вы создаете процессы запуская, например, консольные прогрммы или графические приложения при помощи Program Manager. Как мы уже говорили, процессу выделяется в монопольное владение 2 Гбайта изолированного адресного пространства, в которое другие процессы не имеют никакого доступа.
Сразу после запуска процесса создается одна задача (thread), или, как ее еще называют в отечественной литературе, поток. Задача - это просто фрагмент кода приложения, который может выполняться автономно и независимо от других задач в рамках одного процесса. Например, функция WinMain в приведенных нами ранее примерах приложений может рассматриваться как задача, которая запускается сразу после запуска процесса. При необходимости эта задача может запускать другие задачи, реализуя таким образом мультизадачность в рамках процесса. Все задачи имеют доступ к памяти, выделенной запустившему их процессу.
Из сказанного выше следует, что с одной стороны, в операционной системе Microsoft Windows NT могут работать одновременно несколько процессов, с другой - в рамках каждого процесса могут параллельно работать несколько задач. Пользователь может запустить процесс, загрузив ту или иную программу в память для выполнения, но он не может запустить задачу, так как эта операция выполняется только процессами.