Программирование для Windows NT

         

Описание функций приложения


Приведем описание функций, определенных в нашем приложении. Для экономии места мы не будем подробно описывать фрагменты кода, связанные с созданием дочерних 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

Память выделяется в области верхних адресов адресного пространства приложения

С помощью параметра fdwProtect приложение может установить желаемй тип доступа для заказанных страниц. Можно использвать одно из следующих значений:

Значение

Разрешенный доступ

PAGE_READWRITE

Чтение и запись

PAGE_READONLY

Только чтение

PAGE_EXECUTE

Только исполнение программного кода

PAGE_EXECUTE_READ

Исполнение и чтение

PAGE_EXECUTE_READWRITE

Исполнение, чтение и запись

PAGE_NOACCESS

Запрещен любой вид доступа

PAGE_GUARD

Сигнализация доступа к старнице. Это значение можно использовать вместе с любыми другими, кроме PAGE_NOACCESS

PAGE_NOCACHE

Отмена кэширования для страницы памяти. Используется драйверами устройств. Это значение можно использовать вместе с любыми другими, кроме PAGE_NOACCESS

Если страница отмечена как PAGE_READONLY, при попытке записи в нее возникает аппаратное прерывание защиты доступа (access violation). Эта страница также не может содержать исполнимый код. Попытка выполнения такого кода приведет к возникновению прерывания.

С другой стороны, у вас есть возможность получения страниц, предназначенных только для хранения исполнимого кода. Если такие страницы отмечены как 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

Разрешение или запрещение добавления информации о состоянии линии и модема в поток передаваемых данных

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

В буфер, адрес которого передается через параметр 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 могут работать одновременно несколько процессов, с другой - в рамках каждого процесса могут параллельно работать несколько задач. Пользователь может запустить процесс, загрузив ту или иную программу в память для выполнения, но он не может запустить задачу, так как эта операция выполняется только процессами.