Джонсон Харт - Системное программирование в среде Windows
Даже если дескриптор файла является синхронным (то есть созданным без флага FILE_FLAG_OVERLAPPED), структура OVERLAPPED может послужить в качестве альтернативы функции SetFilePointer для указания позиции в файле. В этом случае возврат после вызова функции ReadFile или иного вызова не происходит до тех пор, операция ввода/вывода пока не завершится. Этой возможностью мы уже воспользовались в главе 3. Также обратите внимание на то, что незавершенные операции ввода/вывода однозначно идентифицируются комбинацией дескриптора файла и соответствующей структуры OVERLAPPED.
Ниже перечислены некоторые предостережения, которые следует принимать во внимание.
• Не допускайте повторного использования структуры OVERLAPPED в то время, когда связанная с ней операция ввода/вывода, если таковая имеется, еще не успела завершиться.
• Аналогичным образом, избегайте повторного использования события, указанного в структуре OVERLAPPED.
• Если существует несколько незакрытых запросов, относящихся к одному и тому же перекрывающемуся дескриптору, используйте для синхронизации не дескрипторы файлов, а дескрипторы событий.
• Если структура OVERLAPPED или событие выступают в качестве автоматических переменных внутри блока, обеспечьте невозможность выхода из блока до синхронизации с операцией ввода/вывода. Кроме того, во избежание утечки ресурсов следует позаботиться о закрытии дескриптора до выхода из блока.
Состояния перекрывающегося ввода/вывода
Возврат из функций ReadFile и WriteFile, а также двух указанных выше функций, относящихся к именованным каналам, в случаях, когда они используются для выполнения перекрывающихся операций ввода вывода, осуществляется немедленно. В большинстве случаев операция ввода/вывода к этому моменту завершена не будет, и возвращаемым значением при чтении и записи будет FALSE. Функция GetLastError возвратит в этой ситуации значение ERROR_IO_PENDING.
По окончании ожидания перехода объекта синхронизации (события или, возможно, дескриптора файла) в сигнальное состояние, свидетельствующее о завершении операции, вы должны выяснить, сколько байтов было передано. В этом и состоит основное назначение функции GetOverlappedResult.
BOOL GetOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped, LPWORD lpcbTransfer, BOOL bWait)
Указание конкретной операции ввода/вывода обеспечивается сочетанием дескриптора и структуры OVERLAPPED. Значение TRUE параметра bWait указывает на то, что до завершения операции функция GetOverlappedResult должна находиться в состоянии ожидания; в противном случае возврат из функции должен быть немедленным. В любом случае эта функция будет возвращать значение TRUE только после успешного завершения операции. Если возвращаемым значением функции GetOverlappedResult является FALSE, то функция GetLastError возвратит значение ERROR_IO_INCOMPLETE, что позволяет вызывать эту функцию для опроса завершения ввода/вывода.
Количество переданных байтов хранится в переменной *lpcbTransfer. Всегда убеждайтесь в том, что с момента ее использования в операции перекрывающегося ввода/вывода структура OVERLAPPED остается неизменной.
Отмена выполнения операций перекрывающегося ввода/вывода
Булевская функция CancelIO позволяет отменить выполнение незавершенных операций перекрывающегося ввода/вывода, связанных с указанным дескриптором (у этой функции имеется всего лишь один параметр). Отменяется выполнение всех инициированных вызывающим потоком операций, использующих данный дескриптор. На операции, инициированные другими потоками, вызов этой функции никакого влияния не оказывает. Отмененные операции завершаются С ошибкой ERROR OPERATION ABORTED.
Пример: использование дескриптора файла в качестве объекта синхронизации
Перекрывающийся ввод/вывод очень удобно и просто реализуется в тех случаях, когда может существовать только одна незавершенная операция. Тогда для целей синхронизации программа может использовать не событие, а дескриптор файла.
Приведенный ниже фрагмент кода показывает, каким образом программа может инициировать операцию чтения для считывания части файла, продолжить свое выполнение для осуществления других видов обработки, а затем перейти в состояние ожидания перехода дескриптора файла в сигнальное состояние.
OVERLAPPED ov = { 0, 0, 0, 0, NULL /* События не используются. */ };
HANDLE hF;
DWORD nRead;
BYTE Buffer[BUF_SIZE];
…
hF = CreateFile( …, FILE_FLAG_OVERLAPPED, … );
ReadFile(hF, Buffer, sizeof(Buffer), &nRead, &ov);
/* Выполнение других видов обработки. nRead не обязательно достоверно.*/
/* Ожидать завершения операции чтения. */
WaitForSingleObject(hF, INFINITE);
GetOverlappedResult(hF, &ov, &nRead, FALSE);
Пример: преобразование файлов с использованием перекрывающегося ввода/вывода и множественной буферизации
Программа 2.4 (atou) осуществляла преобразование ASCII-файла к кодировке UNICODE путем последовательной обработки файла, а в главе 5 было показано, как выполнить такую же последовательную обработку с помощью отображения файлов. В программе 14.1 (atouOV) та же самая задача решается с использованием перекрывающегося ввода/вывода и множественных буферов, в которых хранятся записи фиксированного размера.
Рисунок 14.1 иллюстрирует организацию программы с четырьмя буферами фиксированного размера. Программа реализована таким образом, чтобы количество буферов можно было определять при помощи символической константы препроцессора, но в нижеследующем обсуждении мы будем предполагать, что существуют четыре буфера.
Сначала в программе выполняется инициализация всех элементов структур OVERLAPPED, определяющих события и позиции в файлах. Для каждого входного и выходного буферов предусмотрена отдельная структура OVERLAPPED. После этого для каждого из входных буферов инициируется операция перекрывающегося чтения. Далее с помощью функции WaitForMultipleObjects в программе организуется ожидание одиночного события, указывающего на завершение чтения или записи. При завершении операции чтения входной буфер копируется и преобразуется в соответствующий выходной буфер, после чего инициируется операция записи. При завершении записи инициируется следующая операция чтения. Заметьте, что события, связанные с входными и выходными буферами размещаются в единственном массиве, который используется в качестве аргумента при вызове функции WaitForMultipleObjects.
Рис. 14.1. Модель асинхронного обновления файла
Программа 14.1. atouOV: преобразование файла с использованием перекрывающегося ввода/вывода/* Глава 14. atouOV
Преобразование файла из кодировки ASCII в кодировку Unicode с использованием перекрывающегося ввода/вывода. Программа работает только в Windows NT. */
#include "EvryThng.h"
#define MAX_OVRLP 4 /* Количество перекрывающихся операций ввода/вывода.*/
#define REC_SIZE 0x8000 /* 32 Кбайт: Минимальный размер записи, обеспечивающий приемлемую производительность. */
#define UREC_SIZE 2 * REC_SIZE
int _tmain(int argc, LPTSTR argv[]) {
HANDLE hInputFile, hOutputFile;
/* Каждый из элементов определенных ниже массивов переменных */
/* и структур соответствует отдельной незавершенной операции */
/* перекрывающегося ввода/вывода. */
DWORD nin[MAX_OVRLP], nout[MAX_OVRLP], ic, i;
OVERLAPPED OverLapIn[MAX_OVRLP], OverLapOut[MAX_OVRLP];
/* Необходимость использования сплошного, двумерного массива */
/* диктуется Функцией WaitForMultipleObjects. */
/* Значение 0 первого индекса соответствует чтению, значение 1 – записи.*/
HANDLE hEvents[2][MAX_OVRLP];
/* В каждом из определенных ниже двух буферных массивов первый индекс */
/* нумерует операции ввода/вывода. */
CHAR AsRec[MAX_OVRLP][REC_SIZE];
WCHAR UnRec[MAX_OVRLP][REC_SIZE];
LARGE_INTEGER CurPosIn, CurPosOut, FileSize;
LONGLONG nRecord, iWaits;
hInputFile = CreateFile(argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
hOutputFile = CreateFile(argv[2], GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, NULL);
/* Общее количество записей, подлежащих обработке, вычисляемое */
/* на основе размера входного файла. Запись, находящаяся в конце, */
/* может быть неполной. */
FileSize.LowPart = GetFileSize(hInputFile, &FileSize.HighPart);
nRecord = FileSize.QuadPart / REC_SIZE;
if ((FileSize.QuadPart % REC_SIZE) != 0) nRecord++;
CurPosIn.QuadPart = 0;
for (ic = 0; ic < MAX_OVRLP; ic++) {
/* Создать события чтения и записи для каждой структуры OVERLAPPED.*/
hEvents[0][ic] = OverLapIn[ic].hEvent /* Событие чтения.*/
= CreateEvent(NULL, TRUE, FALSE, NULL);
hEvents[1][ic] = OverLapOut[ic].hEvent /* Событие записи. */
= CreateEvent(NULL, TRUE, FALSE, NULL);
/* Начальные позиции в файле для каждой структуры OVERLAPPED. */