3. Некоторые классы MFC

В этом разделе мы опишем несколько классов, которые вы будете использовать при создании собственных приложений. XE "классы MFC"

В первую очередь, мы рассмотрим класс CObject XE "CObject", который является базовым для большей части классов библиотеки MFC. Затем мы изучим классы, предназначенные для хранения информации, классы, связанные с файловой системой компьютера, процедурой сохранения и восстановления объектов в файле на диске, классы реализующие механизм исключений.

Класс CObject - основной класс MFC

Подавляющее большинство классов из библиотеки MFC наследуются от основного класса CObject XE "CObject". Практически все классы, которые используются в ваших приложениях, например CView или CWinApp, унаследованы от класса CObject.

Класс CObject обеспечивает наиболее общие свойства всех других классов. Так, класс CObject позволяет сохранять и восстанавливать состояние объектов, порожденных из данного класса. Например, текущее состояние объекта можно записать в файл на диске, а затем восстановить объект из этого файла. Этот процесс иногда называют преобразованием в последовательную форму (serialization).

Для объектов, порожденных от CObject, можно получить информацию во время работы программы. Такая возможность используется при динамическом создании объектов. MFC версии 4.0 позволяет также определить имя типа объекта во время работы программы. Эта особенность называется Run-Time Type Information XE "Run-Time Type Information" (RTTI XE "RTTI" ). Кроме того возможности класса CObject используются для вывода диагностических сообщений объектами, порожденными от этого класса.

Класс CObject не позволяет осуществлять множественное наследование. Ваш класс должен иметь только один базовый класс CObject.

Чтобы приложение смогло воспользоваться всеми возможностями класса CObject, необходимо включить в описание и определение класса специальные макрокоманды.

Например, чтобы получить возможность во время выполнения приложения определять название класса и его базовый класс, следует включить в объявление и определение вашего класса макрокоманды DECLARE_DYNAMIC и IMPLEMENT_DYNAMIC. А если вам надо, чтобы состояние объектов класса можно было сохранять и восстанавливать из архивного класса, следует включить в объявление и определение класса макрокоманды DECLARE_SERIAL и IMPLEMENT_SERIAL.

Конструкторы класса

Для класса CObject определены два конструктора. Первый конструктор используется по умолчанию и не имеет параметров. Именно этот конструктор вызывается конструкторами классов, наследованных от CObject:


CObject();

Второй конструктор класса CObject называется конструктором копирования, потому что в качестве параметра objectSrc он принимает ссылку на другой объект этого же класса. Конструктор копирования создает новый объект и выполняет копирование всех элементов объекта, указанного в качестве параметра.

Для класса CObject конструктор копирования описан как private и не имеет определения. Если вы попытаетесь передать по значению объект класса, наследованного от CObject, и не определите конструктор копирования в своем классе, вы получите ошибку на этапе компиляции.

В случае, если бы конструктор копирования класса CObject не был описан, компилятор создал бы его автоматически. Таким образом описание конструктора копирования как private, принуждает вас явно определить конструктор копирования для порожденных классов (конечно если конструктор копирования необходим):


private:
	CObject(const CObject& objectSrc);

Для динамического создания и удаления объектов класса CObject вы можете воспользоваться операторами new и delete. Эти операторы переопределены в классе CObject, однако назначение и основные принципы их работы остались прежними.

Оператор присваивания

Для класса CObject описан оператор присваивания. Он описан с ключевым словом private и не имеет реализации:


private:
    void operator =( const CObject& src );
	

Таким образом для классов, наследованных от CObject запрещается выполнение операции копирования по умолчанию. Если такая операция необходима, вы должны явно определить ее в своем классе. Если вы не определите в своем классе оператор присваивания, но попытаетесь им воспользоваться, компилятор выдаст сообщение об ошибке.

Диагностика

Класс CObject содержит методы AssertValid и Dump, которые могут помочь на этапе отладки приложения. Оба эти методы определены как виртуальные. Вы можете переопределить их в своем классе.

Проверка целостности объектов класса

Метод AssertValid XE "CObject:AssertValid" выполняет проверку целостности объекта класса. Он проверяет состояние элементов данных класса на недопустимые значения. Если вы работаете с отладочной версией приложения и метод AssertValid обнаружит ошибку во внутреннем представлении объекта класса, выполнение приложения прерывается с выдачей соответствующего сообщения.

virtual void AssertValid() const;

Если вы наследуете класс от базового класса CObject и желаете использовать возможности метода AssertValid, вы должны переопределить его. Переопределенный метод AssertValid должен вызывать метод AssertValid базового класса, чтобы проверить целостность соответствующей части объекта. Затем необходимо выполнить проверку элементов порожденного класса. Для этого используйте макрокоманду ASSERT:

ASSERT(booleanExpression)

Макрокоманда ASSERT проверяет свой параметр booleanExpression. Если параметр макрокоманды имеет нулевое значение (FALSE), она отображает диагностическое сообщение и прерывает работу приложения. Если параметр booleanExpression не равен нулю (TRUE) работа приложения продолжается и макрокоманда не выполняет никаких действий.

Макрокоманда ASSERT выполняет проверку только в отладочной версии приложения. В окончательной версии приложения, построенной без отладочной информации, макрокоманда ASSERT не работает.

Если проверку параметра макрокоманды необходимо выполнять и в окончательной версии приложения, вы можете использовать вместо макрокоманды ASSERT макрокоманду VERIFY. Но при обнаружении ошибки работа приложения не будет прервана.

Вот пример переопределения метода AssertValid для класса CFigure, наследованного от базового класса CObject:


// Класс CFigure наследуется от базового класса CObject
class	CFigure : public CObject
{
	// Переопределяем виртуальный метод базового класса
	int	m_area = 0;

	// Остальные элементы класса...
}

// Переопределяем виртуальный метод AssertValid класса CObject 
void CFigure::AssertValid() const
{
	// Сначала проверяем целостность элементов базового класса
	CObject::AssertValid();

	// Проверяем элемент m_area. 
	// Он должен быть больше или равен нулю
	ASSERT(m_area >= 0); 
}

Получение дампа объекта класса

Виртуальный метод Dump позволяет получить дамп объекта данного класса:

virtual void Dump(CDumpContext& dc) const;

Метод Dump XE "CObject:Dump" имеет единственный параметр dc, определяющий контекст отображения для вывода дампа объекта. Часто в качестве параметра dc используется предопределенный объект afxDump. Он позволяет передавать информацию в окно отладчика Visual C++. Объект afxDump определен только для отладочной версии приложения.

Вы можете переопределить метод Dump для своего класса. Переопределенный метод должен сначала вызывать метод Dump базового класса, а затем выводить значения элементов самого класса. Для вывода значений элементов объекта класса в контекст dc можно использовать операторы <<, переопределенные для класса CDumpContext.

Если класс определен с макрокомандами IMPLEMENT_DYNAMIC XE "IMPLEMENT_DYNAMIC" или IMPLEMENT_SERIAL XE "IMPLEMENT_SERIAL", то метод Dump класса CObject будет отображать также имя самого класса.

Для класса CFigure, описанного выше, метод Dump можно определить следующим образом:


void CFigure::Dump(CDumpContext &dc) const
{
	// Вызываем метод Dump базового класса
	CObject::Dump(dc);

	// Выводим в контекст dc значение элемента m_area 
	// класса CFigure
	dc << "Площадь = " << m_area;
}

Сохранение и восстановление состояния объекта

В классе CObject определены метод IsSerializable XE "CObject:IsSerializable" и виртуальный метод Serialize, которые используются для сохранения и восстановления состояния объектов в файлах на диске. Чтобы объекты класса можно было сохранять в файлах на диске с возможностью их последующего восстановления, объявление класса объекта должно содержать макрокоманду DECLARE_SERIAL XE "DECLARE_SERIAL", а реализация класса макрокоманду IMPLEMENT_SERIAL XE "IMPLEMENT_SERIAL". Более подробно об сохранении и восстановлении объектов можно прочитать в разделе “Запись и восстановление объектов”.

Метод IsSerializable

Метод IsSerializable позволяет определить, можно ли записать состояние объекта в файле, а потом восстановить его. Если это возможно, метод IsSerializable возвращает ненулевое значение.

BOOL IsSerializable() const;

Виртуальный метод Serialize

Виртуальный метод Serialize XE "CObject:Serialize" вызывается, когда надо сохранить или восстановить объект класса из файла на диске. Вы должны переопределить этот метод в своем классе, чтобы сохранить или восстановить его элементы. Переопределенный метод Serialize должен вызывать метод Serialize базового класса:


virtual void Serialize(CArchive& ar);
	throw(CMemoryException);
	throw(CArchiveException);
	throw(CFileException);

Ниже прототипа метода Serialize мы указали исключения, которые могут быть им вызваны. Более подробно об исключениях вы можете прочитать в разделе “Исключения - класс CException” данной главы.

Информация о классе

Класс CObject содержит два метода: GetRuntimeClass и IsKindOf, позволяющих получить информацию о классе объекта.

Виртуальный метод GetRuntimeClass

Виртуальный метод GetRuntimeClass XE "CObject:GetRuntimeClass" возвращает указатель на структуру CRuntimeClass, описывающую класс объекта, для которого метод был вызван:

virtual CRuntimeClass* GetRuntimeClass() const;

Для каждого класса, наследованного от CObject поддерживается своя структура CRuntimeClass. Если вы желаете использовать метод GetRuntimeClass в своем классе, наследованном от CObject, вы должны поместить в реализации класса макрокоманду IMPLEMENT_DYNAMIC XE "IMPLEMENT_DYNAMIC" или IMPLEMENT_SERIAL XE "IMPLEMENT_SERIAL".

Структура CRuntimeClass XE "CRuntimeClass" содержит различную информацию о классе. Ниже перечислены несколько основные полей этой структуры.

Поле структуры CRuntimeClass Описание
const char* m_pszClassName Указатель на строку, закрытую двоичным нулем, в которой расположено имя класса
int m_nObjectSize Размер объектов класса
WORD m_wSchema Номер схемы (schema number) класса. Используется при автоматическом сохранении и восстановлении объектов класса в файле. Если объекты класса не могут быть сохранены и восстановлены (в объявлении класса отсутствует макрокоманда IMPLEMENT_SERIAL), m_wSchema содержит значение -1
void (*m_pfnConstruct) (void* p) Указатель на конструктор класса, используемый по умолчанию. Этот конструктор не имеет параметров
CRuntimeClass* m_pBaseClass Указатель на структуру CRuntimeClass, содержащую аналогичную информацию о базовом классе

Кроме перечисленных элементов структуры, она содержит метод CreateObject. Этот метод позволяет динамически создать объект соответствующего класса уже во время работы приложения. Если объект класса не создан, метод возвращает значение NULL.

CObject* CreateObject();

Метод IsKindOf

Метод IsKindOf XE "CObject:IsKindOf" определяет отношение объекта и класса, представленного указателем pClass на структуру CRuntimeClass. Метод правильно работает только для классов, в объявлении которых указаны макрокоманды DECLARE_DYNAMIC XE "DECLARE_DYNAMIC" или DECLARE_SERIAL XE "DECLARE_SERIAL".

BOOL IsKindOf(const CRuntimeClass* pClass) const;

Метод возвращает ненулевое значение, если объект, для которого он вызван, принадлежит классу заданному параметром pClass или классу наследованному от него. В противном случае метод возвращает нулевое значение.

Класс CPoint - точка на плоскости

В предыдущих томах серии “Библиотека системного программиста” мы рассматривали структуру POINT XE "POINT", используемую средствами разработки приложений Windows. Структура POINT позволяет сохранить координаты точки в двумерном пространстве.

Библиотека классов MFC включает в себя класс CPoint XE "CPoint", который можно использовать вместо структуры POINT. Класс CPoint имеет несколько конструкторов, которые вы можете использовать.

Первый конструктор класса не имеет параметров:

CPoint();

Вы можете создать объект класса CPoint и сразу присвоить ему значения. Если известны отдельные координаты точки, воспользуйтесь конструктором с двумя параметрами:

CPoint(int initX, int initY);

Первый параметр initX определяет х-координату точки, а параметр initY - y-координату точки. Если надо создать объект CPoint и записать в него координаты из переменной типа POINT или другого объекта класса CPoint, используйте другой конструктор:

CPoint(POINT initPt);

Можно создать объект CPoint и записать в него данные из объекта класса CSize или структуры SIZE:

CPoint(SIZE initSize);

Если у вас есть переменная типа DWORD, в младшем слове которой записана x-координата точки, а в старшем слове y-координата, то можно создать объект класса CPoint и записать в него данные из этой переменной:

CPoint(DWORD dwPoint);

Объекты класса CPoint можно сравнивать друг с другом, пользуясь обычными операторами равенства == (равно) и != (не равно). Результатом действия этих операторов является значение типа BOOL. Если условие определенное операторами равенства выполняется, возвращается ненулевое значение. В противном случае результат равен нулю.

Класс CSize - относительные координаты

Класс CSize создан на основе структуры SIZE, предназначенной для определения относительных координат точек в двумерном пространстве. Так как CSize наследуется от структуры SIZE, он включает все ее поля. Вы можете непосредственно обращаться к элементам SIZE. Элементы данных cx и cy объявлены как доступные. Объекты класса CSize можно использовать вместо переменных типа SIZE - их можно указывать в качестве параметров функций. Вы можете передавать объекты CSize функциям, которые принимают параметры типа SIZE. Объявление класса CSize расположено во включаемом файле #include <afxwin.h>.

Класс CString - текстовые строки

Практически ни одно приложение не обходится без использования текстовых строк. Язык Си и Си++ не имеет специального строкового типа. Обычно для хранения строк используют массивы символов. MFC предоставляет программисту более удобное средство для организации и работы со строками. Для этого предназначен специальный класс CString.

Строки CString XE "CString" состоят из символов, типа TCHAR XE "TCHAR". Тип TCHAR определяется по разному в зависимости от того, определен или нет символ _UNICODE XE "_UNICODE". Если символ _UNICODE не определен, TCHAR соответствует обычному типу char. В противном случае он представляет символы, определяемые двумя байтами.

При использовании класса CString отпадает необходимость постоянно следить за тем, чтобы длина строки не превысила объем выделенной для нее памяти. Строки CString XE "CString" автоматически расширяются и сокращаются в зависимости от фактической длины строки.

В класс CString входят методы, выполняющие все основные операции над строками - копирование, сравнение, присваивание и т. д. Над объектами класса CString определены такие операции как +, -, облегчающие работу со строками. Строки, представленные объектами CString, можно сравнивать, используя обычные операторы сравнения <, >, <=, >=, ==. Объекты класса CString можно записывать на диск - для них работает механизм сохранения и восстановления объектов класса. Фактически вы можете работать со стоками как с другими простыми типами данных.

Строки CString можно использовать совместно с строками языка Си (массивами символов, заканчивающихся знаком двоичного нуля). Вы можете обращаться к объектам класса CString как к обычным массивам символов, то есть использовать их как строки символов языка Си. Вы можете указывать строки в качестве параметра функций и методов, которые принимают параметры типа const char* или LPCTSTR.

Класс CString содержит большое количество различных методов, предназначенных для работы со строками. Среди них есть методы для доступа к отдельным элементам строки, сравнения строк между собой, поиска подстрок, сохранения строк в файле и их последующего восстановления. Мы оставляем класс CString для вашего самостоятельного изучения и ограничимся описанием только самых основным методов.

Конструктор класса

Класс CString имеет несколько различных конструкторов, позволяющих создавать строки на основе различных данных.

Конструктор класса CString, используемый по умолчанию, не имеет параметров. Он создает пустую строку. В последствии вы можете записать в нее любой текст.

CString();

Для класса CString определен конструктор копирования. Он позволяет создать строку на основе другой, ранее определенной строки. Этот и все остальные конструкторы могут вызвать исключение CMemoryException XE "CMemoryException", если для создания строки недостаточно оперативной памяти.


CString(const CString& stringSrc);
	throw(CMemoryException);

Конструктор, представленный ниже, позволяет создать строку из nRepeat символов ch. Параметр nRepeat можно не указывать. В этом случае строка будет содержать единственный символ ch.


CString(TCHAR ch, int nRepeat = 1);
	throw(CMemoryException);

Вы можете создать новую строку и скопировать в нее nLength символов из массива lpch. Строка, расположенная в массиве lpch, может не иметь завершающего двоичного нуля. Для этого используйте следующий указатель:


CString(LPCTSTR lpch, int nLength);
	throw(CMemoryException);

Новую строку можно создать и скопировать в нее данные из массива символов, закрытого двоичным нулем. В этом случае количество символов, которые будут записаны в новую строку не указывается. Копируются все символы до двоичного нуля.

Для создания строки вы можете воспользоваться одним из трех конструкторов, представленных ниже. Как видите, конструкторы отличаются только типом параметра:


CString(const unsigned char* psz);
	throw(CMemoryException);

CString(LPCWSTR lpsz);
	throw(CMemoryException);

CString(LPCSTR lpsz);
	throw(CMemoryException);

Коллекции

Перед программистом часто возникает задача упорядоченного хранения объектов и переменных. Для этого он вынужден самостоятельно реализовывать такие структуры как списки, сложные массивы. Библиотека классов MFC позволяет значительно облегчить решение этих задач.

В состав MFC входят специальные классы для хранение и обработки групп переменных или объектов. Они позволяют быстро создавать массивы и списки объектов. Облегчается создание небольших словарей, позволяющих по ключевому полю получить ранее записанное в словарь значение. Класс словарей реализует эффективный механизм хеширования. С использованием таких классов отпадает необходимость в самостоятельной разработке и длительной отладке. Программист сможет уделить больше времени прикладной части своего приложения.

Массивы - шаблон CArray

Библиотека MFC включает классы для организации массивов байт, слов, двойных слов, указателей, объектов класса CString и указателей на объекты класса CObject. В MFC версии 4.0 добавлен шаблон класса массива, позволяющий создавать на его основе собственные массивы переменных любых типов и массивы объектов любых классов. Для доступа к элементам массива можно использовать оператор [].

Ниже представлен прототип шаблона CArray XE "CArray".


template <class TYPE, class ARG_TYPE> 
	class CArray : public CObject

Параметр шаблона XE "шаблона" XE "шаблоны" TYPE определяет тип элементов массива. В качестве TYPE можно указывать не только простые типы, например int, char, но также типы, являющиеся классами. Второй параметр шаблона ARG_TYPE определяет тип параметра массива, который используется для доступа к его элементам.

Мы будем использовать шаблон CArray для организации массива в приложении Single (см. раздел “Простейший графический редактор” главы “Однооконный интерфейс”).

Списки - шаблон CList

В состав MFC входят классы, предназначенные для организации двунаправленных списков указателей, строк, состоящих из объектов CString, указателей на объекты класса CObject. В MFC версии 4.0 добавлен шаблон класса списка CList XE "CList". С помощью этого шаблона можно создавать списки, состоящие из любых объектов.

Списковый класс имеет методы для вставки и удаления элементов в начало, конец и середину списка. Вы можете получить предыдущий и следующий элемент из списка.

Ниже представлен прототип шаблона CList.


template <class TYPE, class ARG_TYPE> 
	class CList : public CObject

Словари - шаблон CMap

Словарь, это таблица переменной длины, состоящая из двух колонок. Первая колонка содержит ключевые поля, а вторая - соответствующие им значения. Пользуясь объектами этого класса, вы можете по ключевому полю получить связанное с ним значение. Для поиска по ключу используется алгоритм хеширования, реализация которого вручную заняла бы слишком много времени.

В MFC включены классы для создания словарей отображающих значения слов, указателей, указателей на объекты типа CObject и объектов CString. Библиотека MFC версии 4.0 содержит шаблоны для организации словарей. Шаблоны позволяют создать класс словарей, основанных на объектах любых типов.

Ниже представлен прототип шаблона CMap XE "CMap".


template <class KEY, class ARG_KEY, class VALUE, 
			 class ARG_VALUE> class CMap : public CObject

Параметр шаблона KEY определяет тип ключевого поля словаря. Параметр ARG_KEY является типом данных, используемых в качестве аргумента KEY. Параметр VALUE соответствует типу элементов, хранящихся в словаре, а параметр ARG_VALUE используется в качестве аргумента VALUE.

Класс CTime - дата и время

Для работы с календарными датами и временем в состав библиотеки классов MFC включен класс CTime XE "CTime". Класс основан на элементе типа time_t, в котором будет храниться дата и время. Элемент типа time_t объявлен как private, поэтому вы не можете обращаться непосредственно к этому элементу. Для этого в состав класса CTime входит набор методов.

Класс CTime включает несколько конструкторов, которые можно использовать для создания и инициализации объектов данного класса. Выберите тот конструктор, который наиболее удобен в каждом конкретном случае.

Если вам требуется создать массив, состоящий из объектов класса CTime, используйте конструктор без указания параметров:

CTime();

Объекты, созданные этим конструктором не инициализированы. Перед тем как ими пользоваться, запишите в них дату и время:

CTime(const CTime& timeSrc); 

Параметр timeSrc определяет уже существующий объект класса CTime. Новый объект класса инициализируется значением даты и времени, взятым из существующего объекта.

Если в программе уже существует переменная типа time_t, в которой хранится значение, то можно создать новый объект класса CTime, основываясь на этой переменной:

CTime(time_t time); 

Параметр time определяет переменную типа time_t, значение которой будет записано в создаваемый объект.

Если существует несколько переменных, в которых отдельно хранятся год, месяц, день, часы, минуты и секунды, то можно создать объект класса CTime, сразу же записав в него значения из этих переменных:


CTime(int nYear, int nMonth, int nDay, 
		int nHour, int nMin, int nSec, int nDST = -1); 

Первые 6 параметров метода определяют время и дату. Параметр nYear должен содержать год, nMonth - месяц, nDay - день, nHour - час, nMin - минуты, nSec - секунды. NYear может принимать значения от 1900 до 2038, nMonth - от 1 до 12 и nDay от 1 до 31.

Параметр nDST управляет режимом перехода на летнее время. Если nDST содержит нуль, то время представляется в стандартном виде, режим перехода на летнее время не используется. Чтобы включить этот режим, параметр nDST должен содержать значение, больше нуля. Параметр nDST является необязательным. Если его не указать, считается, что он равен нулю. В этом случае конструктор самостоятельно определяет режим перехода на летнее время.

Следующий конструктор позволяет создать объект класса CTime и записать в него дату и время, определенные в формате, принятом в операционной системе MS-DOS:

CTime(WORD wDosDate, WORD wDosTime, int nDST = -1); 

Параметры wDosDate и wDosTime должны содержать, соответственно, дату и время в формате MS-DOS. Параметр nDST управляет режимом перехода на летнее время. Мы уже рассматривали его выше.

Кроме класса CTime, существует, как минимум, еще две структуры, в которых может храниться значения даты и времени. Первая такая структура называется SYSTEMTIME. В ней хранятся текущие значения даты и времени. Структура SYSTEMTIME определена следующим образом:


typedef struct _SYSTEMTIME {
	WORD wYear;		// год
	WORD wMonth; 		// месяц
	WORD wDayOfWeek; 		// день недели
	WORD wDay; 		// календарная дата
	WORD wHour; 		// часы
	WORD wMinute; 		// минуты
	WORD wSecond; 		// секунды
	WORD wMilliseconds; 	// миллисекунды
} SYSTEMTIME;

Если необходимо создать объект класса CTime, сразу записав в него текущие значения времени и даты, используйте конструктор, представленный ниже:

CTime(const SYSTEMTIME& sysTime, int nDST = -1);

Параметр sysTime является указателем на структуру типа SYSTEMTIME. Необязательный параметр nDST управляет режимом отсчета даты и описан нами выше.

Вторая структура, в которой хранятся значения даты и времени, называется FILETIME. Она служит для хранения 64-битового числа, представляющего дату и время как количество 100 наносекундных интервалов времени, прошедших с первого января 1601 года.


typedef struct _FILETIME {
	DWORD dwLowDateTime;   	// младшие 32 бита
	DWORD dwHighDateTime;  	// старшие 32 бита
} FILETIME, *PFILETIME, *LPFILETIME; 

Конструктор имеет следующий прототип:


CTime(const FILETIME& fileTime, int nDST = -1);

Файловая система - класс CFile

Библиотека MFC включает класс CFile XE "CFile", предназначенный для обеспечения работы с файлами. Он позволяет упростить использование файлов, представляя файл как объект, который можно создать, читать, записывать и т. д. Класс CFile наследуется непосредственно от класса CObject:

CFile <- CObject 

Чтобы получить доступ к файлу, сначала надо создать объект класса CFile. Конструктор класса CFile позволяет сразу после создания такого объекта открыть файл. Но мы воспользуется более общим способом и откроем файл позже, вызвав метод Open.

Открытие и создание файлов

После создания объекта класса CFile можно открыть файл, вызвав метод Open. Методу Open надо указать путь к открываемому файлу и режим его использования. Прототип метода Open имеет следующий вид:


virtual BOOL 
Open(LPCTSTR lpszFileName, UINT nOpenFlags, 
		CFileException* pError = NULL);

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

Второй параметр nOpenFlags определяет действие, выполняемое методом Open с файлом, а также атрибуты файла. Ниже представлен список возможных значений параметра nOpenFlags:

Возможные значения nOpenFlags Описание
CFile::modeCreate Создается новый файл. Если указанный файл существует, то его содержимое стирается и длина устанавливается равной нулю
CFile::modeNoTruncate Этот флаг предназначен для использования совместно с флагом CFile::modeCreate. Если создается уже существующий файл, то его содержимое не будет удалено
CFile::modeRead Файл открывается только для чтения
CFile::modeReadWrite Файл открывается для чтения и записи
CFile::modeWrite Файл открывается только для записи
CFile::modeNoInherit Указывает, что файл не должен наследоваться порожденным процессом
CFile::shareCompat Открывает файл в режиме совместимости. Любой другой процесс может открыть этот файл несколько раз. Операция вызывает ошибку, если файл уже открыт другим процессом в любом другом режиме кроме режима совместимости
CFile::shareDenyNone Не запрещается доступ к открываемому файлу ни на чтение, ни на запись. Вызывает ошибку, если файл уже открыт в режиме совместимости любым другим процессом
CFile::shareDenyRead После того как файл открыт, другим процессам запрещается его чтение. Вызывает ошибку, если уже открыт в режиме совместимости или для чтения другим процессом
CFile::shareDenyWrite После того как файл открыт, другим процессам запрещается запись в него. Вызывает ошибку, если уже открыт в режиме совместимости или для записи другим процессом
CFile::shareExclusive После того как файл открыт, другим процессам запрещается запись и чтение из этого файла. Вызывает ошибку, если файл уже открыт для чтения или для записи любым процессом
CFile::typeText Используется классами, порожденными от класса CFile, например CStdioFile, для работы с файлами в текстовом режиме. Текстовый режим обеспечивает преобразование комбинации символа возврата каретки и символа перевода строки.
CFile::typeBinary Используется классами, порожденными от класса CFile, например CStdioFile, для работы с файлами в двоичном режиме

Необязательный параметр pError, который является указателем на объект класса CFileException, используется только в том случае, если выполнение операции с файлом вызывает ошибку. Если вы указали параметр pError и случилась ошибка, то в объект будет записана дополнительная информация.

Метод Open возвращает ненулевое значение, если файл открыт и нуль в случае ошибки. Ошибка при открытии файла может случится, например, если методу Open указан несуществующий файл.

Идентификатор открытого файла

В состав класса CFile входит элемент данных m_hFile типа UINT. В нем хранится идентификатор открытого файла. Если вы создали объект класса CFile, но еще не открыли никакого файла, то в m_hFile записана константа hFileNull.

Обычно нет необходимости непосредственно использовать идентификатор открытого файла. Методы класса CFile позволяют выполнять практически любые операции с файлами и не требуют указывать идентификатора файла. Так как m_hFile является элементом класса, то реализация его методов всегда имеет свободный доступ к нему.

Закрытие файлов

После того, как вы поработали с файлом, его надо закрыть. Класс CFile имеет для этого специальный метод Close:

 
virtual void Close();
	throw(CFileException);

Метод закрывает файл. Если вы создали объект класса CFile и открыли файл, а затем объект удаляется, то связанный с ним файл закрывается автоматически с помощью деструктора.

Чтение и запись файлов

Для доступа к файлам предназначены пять различных методов класса CFile: Read, ReadHuge, Write, WriteHuge, Flush. Методы Read и ReadHuge предназначены для чтения данных из предварительно открытого файла. В 16-разрядной операционной системе Windows функция Read может считать не больше, чем 65535 байт. На метод ReadHuge такие ограничения не накладываются. В 32-разрядных операционных системах оба метода могут одновременно считать из файла больше, чем 65535 байт.


virtual UINT Read(void* lpBuf, UINT nCount);
	throw(CFileException);

Данные, прочитанные из файла, записываются в буфер lpBuf. Параметр nCount определяет количество байт, которое надо считать из файла. Фактически из файла может быть считано меньше байт, чем запрошено параметром nCount. Это происходит, если во время чтения достигнут конец файла. Метод Read возвращает количество байт, прочитанных из файла:


DWORD ReadHuge(void* lpBuffer, DWORD dwCount);
	throw(CFileException);

Назначение параметров метода ReadHuge аналогично назначению параметров метода Read. Спецификация ReadHuge считается устаревшей и оставлена только для обратной совместимости с 16-разряднымми операционными системами.

Для записи в файл предназначены методы Write и WriteHuge. Метод WriteHuge не накладывает ограничения на количество одновременно записываемых байт. В 16-разрядных версиях операционной системы Windows метод Write позволяет записать не больше 65535 байт. Это ограничение снято в операционных системах Windows NT и Windows 95.


virtual void Write(const void* lpBuf, UINT nCount);
	throw(CFileException);

Метод Write записывает в открытый файл nCount байт из буфера lpBuf. В случае возникновения ошибки записи, например, переполнения диска, метод Write вызывает исключение.

Не смотря на то, что спецификация WriteHuge считается устаревшей, вы можете использовать ее для создания 16-разрядных приложений в среде Visual C++ версии 1.5х.

Метод Flush

Когда вы используете метод Write или WriteHuge для записи данных на диск, они некоторое время могут находится во временном буфере. Чтобы удостоверится, что необходимые изменения внесены в файл на диске, используйте метод Flush:


virtual void Flush();
	throw(CFileException);

Операции с файлами

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

Операционная система MS-DOS содержит команду REN, позволяющую переименовывать файлы. Класс CFile включает статический метод Rename, выполняющий функции этой команды:


static void PASCAL 
Rename(LPCTSTR lpszOldName, LPCTSTR lpszNewName);
	throw(CFileException);

Метод Rename изменяет имя файла, определенного параметром lpszOldName на lpszNewName. Метод нельзя использовать для переименования каталогов. В случае возникновения ошибки метод вызывает исключение.

Для удаления файлов предназначена команда DEL операционной системы MS-DOS. Класс CFile включает статический метод Remove, позволяющий удалить указанный файл:


static void PASCAL Remove(LPCTSTR lpszFileName);
	throw(CFileException);

Параметр lpszFileName должен содержать путь удаляемого файла. Метод Remove не позволяет удалять каталоги. Если удалить файл невозможно, например, из-за неправильно указанного имени файла, то метод вызывает исключение.

Чтобы определить дату и время создания файла, его длину и атрибуты, предназначен статический метод GetStatus. Существует две разновидности метода - первый определен как виртуальный, а второй - как статический методы.

Виртуальная версия метода GetStatus определяет состояние открытого файла, связанного с данным объектом класса CFile. Вызывайте этот метод только тогда, когда объект класса CFile создан и файл открыт:

BOOL GetStatus(CFileStatus& rStatus) const;

Статическая версия метода GetStatus позволяет определить характеристики файла, не связанного с объектом класса CFile. Чтобы воспользоваться этим методом, не обязательно предварительно открывать файл.


static BOOL PASCAL 
GetStatus(LPCTSTR lpszFileName, CFileStatus& rStatus);

Параметр lpszFileName должен содержать путь к файлу. Путь может быть полным или не полным - относительно текущего каталога.

Параметр rStatus должен содержать указатель на структуру типа CFileStatus, в которую заносится информация о файле.

Структура типа CFileStatus имеет элементы, описанные в следующей таблице:

Поле структуры CFileStatus Описание
CTime m_ctime Дата и время создания файла. Описание класса CTime представлено нами в главе “Дата и время”
CTime m_mtime Дата и время последней модификации файла
CTime m_atime Дата и время, когда последний раз выполнялось чтение из файла
LONG m_size Размер файла в байтах
BYTE m_attribute Атрибуты файла
char m_szFullName[_MAX_PATH] Полное имя файла в стандарте операционной системы Windows. Виртуальная версия метода не заполняет это поле

Атрибуты файла, указанные в поле m_attribute структуры CFileStatus, определяются как переменная перечислимого типа Attribute. Этот тип определен в классе CFile следующим образом:


enum Attribute {
	normal =    0x00,
	readOnly =  0x01,
	hidden =    0x02,
	system =    0x04,
	volume =    0x08,
	directory = 0x10,
	archive =   0x20
};
Атрибут Описание
normal Нормальный файл
readOnly Файл, который можно открыть только для чтения
hidden Скрытый файл
system Системный файл
volume Метка тома
directory Каталог
archive Архивный

Метод GetStatus возвращает ненулевое значение при нормальном завершении и нуль в случае ошибки. Ошибка обычно возникает, если вы указываете несуществующий файл.

Блокировка

В состав класса CFile включены методы LockRange и UnlockRange, позволяющие заблокировать один или несколько фрагментов данных файла для доступа других процессов. Если приложение пытается повторно блокировать данные, уже заблокированные раньше этим или другим приложением, вызывается исключение. Блокировка представляет собой один из механизмов, позволяющих нескольким приложениям или процессам одновременно работать с одним файлом, не мешая друг другу.

Установить блокировку можно с помощью метода LockRange:


virtual void LockRange(DWORD dwPos, DWORD dwCount);
	throw(CFileException);

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

Чтобы снять установленные блокировки, надо воспользоваться методом UnlockRange. Если в одном файле установлено несколько блокировок, то каждая из них должна сниматься отдельным вызовом метода UnlockRange:


virtual void UnlockRange(DWORD dwPos, DWORD dwCount);
	throw(CFileException);

Как и для метода LockRange, параметры dwPos и dwCount должны указывать начало фрагмента и его размер. Размер фрагмента должен соответствовать размеру фрагмента, указанному при вызове метода LockRange.

Позиционирование

Чтобы переместить указатель текущей позиции файла в новое положение, можно воспользоваться одним из следующих методов класса CFile - Seek, SeekToBegin, SeekToEnd. В состав класса CFile также входят методы, позволяющие установить и изменить длину файла - GetLength, SetLength.

Когда вы открываете файл, указатель текущей позиции файла находится в самом начале файла. Когда вы читаете данные из файла, этот указатель перемещается в конец файла и указывает на данные, которые будут получены очередной операцией чтения из файла.

Чтобы переместить указатель текущей позиции файла в любое место, можно воспользоваться универсальным методом Seek. Он позволяет переместить указатель на определенное число байт относительно начала, конца файла или текущей позиции указателя:


virtual LONG Seek(LONG lOff, UINT nFrom);
	throw(CFileException);

Параметр lOff определяет число байт, на которое надо переместить указатель текущей позиции файла. Параметр lOff может быть положительной или отрицательной величиной. Если lOff больше нуля, то указатель смещается в сторону конца файла, если lOff меньше нуля, то указатель смещается в начало файла.

Параметр nFrom определяет, относительно чего задано это смещение. В качестве nFrom можно указать три различные константы, также определенные в классе CFile:

Константа Параметр lOff задает смещение относительно
CFile::begin Начала файла
CFile::current Текущей позиции файла
CFile::end Конца файла

В случае успешного перемещения указателя файла в новую позицию метод Seek возвращает новое смещение указателя относительно начала файла. Если новая позиция указателя задана неправильно, например, вы пытаетесь переместить указатель в позицию перед началом файла, вызывается исключение.

Чтобы переместить указателя файла в начало или конец файла, наиболее удобно использовать специальные методы. Метод SeekToBegin перемещает указатель в начало файла, а метод SeekToEnd - в его конец. Приведем прототип метода SeekToBegin:


void SeekToBegin();
	throw(CFileException);

Фактически вызов метода SeekToBegin эквивалентен вызову метода Seek с параметром lOff, содержащим нуль и параметром nFrom, содержащим константу CFile::begin.

Метод SeekToEnd имеет почти такой же прототип как метод SeekToBegin, но перемещает указатель в конец файла:


DWORD SeekToEnd();
	throw(CFileException);

Метод SeekToEnd возвращает длину файла в байтах. Если вам надо определить длину открытого файла, совсем не обязательно перемещать его указатель. Можно воспользоваться методом GetLength. Этот метод также возвращает длину открытого файла в байтах:


virtual DWORD GetLength() const;
	throw(CFileException);

Метод SetLength позволяет изменить длину открытого файла:


virtual void SetLength(DWORD dwNewLen);
	throw(CFileException);

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

Вы можете определить текущую позицию указателя файла с помощью метода GetPosition. Возвращаемое методом GetPosition 32-разрядное значение определяет смещение указателя от начала файла:


virtual DWORD GetPosition() const;
	throw(CFileException);

Характеристики открытого файла

Чтобы определить расположение открытого файла на диске, надо вызвать метод GetFilePath. Этот метод возвращает объект класса CString, в котором содержится полный путь файла, включая имя диска, каталоги, имя диска и его расширение:

virtual CString GetFilePath() const;

Если требуется определить только имя и расширение открытого файла, можно воспользоваться методом GetFileName. Он возвращает объект класса CString, в котором находится имя файла:

virtual CString GetFileName() const;

В случае, когда надо узнать только имя открытого файла без расширения, воспользуйтесь методом GetFileTitle:

virtual CString GetFileTitle() const;

Последний метод класса CFile позволяет установить путь файла. Этот метод не создает, не копирует и не изменяет имени файла, он только заполняет соответствующий элемент данных в объекте класса CFile:

virtual void SetFilePath(LPCTSTR lpszNewName);

Единственный параметр метода lpszNewName должен содержать указатель на строку символов, содержащую путь файла. Эта строка должна оканчиваться двоичным нулем.

Файловая система - классы CMemFile и CStdioFile

В библиотеку MFC входит класс CMemFile XE "CMemFile", наследуемый от базового класса CFile. Класс CMemFile представляет файл, размещенный в оперативной памяти. Вы можете работать с объектами класса CMemFile также, как с объектами класса CFile. Отличие заключается в том, что файл, связанный с объектом CMemFile, на самом деле расположен не на магнитном диске, а в оперативной памяти компьютера. За счет этого операции с таким файлом происходят значительно быстрее, чем с обычными файлами.

CMemFile <- CFile <- CObject

Работая с объектами класса CMemFile, можно использовать все методы класса CFile, которые мы уже описали в предыдущей главе. Вы можете записывать данные в такой файл и считывать их. К сожалению, для класса CMemFile не реализованы методы блокировки LockRange и UnlockRange и метод для копирования Duplicate. Кроме этих методов, в состав класса CMemFile включены дополнительные методы.

Для создания объектов класса CMemFile предназначены два различных конструктора. Первый конструктор CMemFile имеет всего один необязательный параметр nGrowBytes:

CMemFile(UINT nGrowBytes = 1024);

Этот конструктор создает в оперативной памяти пустой файл. После создания файл автоматически открывается. Вы не должны специально вызывать метод Open.

Когда вы начинаете запись в такой файл, автоматически выделяется блок памяти. Для получения памяти методы класса CMemFile вызывают стандартные функции malloc, realloc и free. Если выделенного блока памяти недостаточно, его размер увеличивается. Увеличение блока памяти файла происходит частями по nGrowBytes байт. После удаления объекта класса CMemFile используемая им память автоматически возвращается системе.

Второй конструктор класса CMemFile имеет более сложный прототип. Этот конструктор используется в тех случаях, когда вы сами выделяете память для файла:


CMemFile(BYTE* lpBuffer, UINT nBufferSize, 
	UINT nGrowBytes = 0);

Параметр lpBuffer указывает на буфер, который будет использоваться для файла. Размер буфера определяется параметром nBufferSize.

Необязательный параметр nGrowBytes используется более комплексно, чем в первом конструкторе класса. Если nGrowBytes содержит нуль, то созданный файл будет содержать данные из буфера lpBuffer. Длина такого файла будет равна nBufferSize.

Класс CMemFile позволяет получить указатель на область памяти, используемую файлом. Через этот указатель можно непосредственно работать с содержимым файла, не ограничивая себя методами класса CFile.

Для получения указателя на буфер файла вы можете воспользоваться методом Detach:

BYTE * Detach();

Перед эти полезно определить длину файла и соответственно, размер буфера памяти, вызвав метод GetLength.

Метод Detach закрывает данный файл и возвращает указатель на используемый им блок памяти. Если вам требуется опять открыть файл и связать с ним блок оперативной памяти, вызовите метод Attach:


void 
Attach(BYTE* lpBuffer, UINT nBufferSize, UINT nGrowBytes = 0);

Параметры метода Attach соответствуют параметрам второго конструктора класса CMemFile, рассмотренному выше. Параметр lpBuffer указывает на буфер размера nBufferSize, который будет связан с файлом.

Если необязательный параметр nGrowBytes равен нулю, то созданный файл будет содержать данные из буфера lpBuffer. Если nGrowBytes больше нуля, то содержимое буфера lpBuffer игнорируется. Если вы запишете в такой файл больше данных, чем помещается в отведенном вами буфере, его размер автоматически увеличивается на nGrowBytes байт. Для управления буфером файла класс CMemFile вызывает стандартные функции malloc, calloc и free. Поэтому, чтобы не нарушать механизм управления памяти, буфер lpBuffer должен быть создан функциями malloc или calloc.

Модификация класса CMemFile

Вы можете наследовать от класса CMemFile XE "CMemFile" собственные классы. При этом вы можете реализовать свой механизм выделения памяти для файла, чтения и записи данных. Для этого в состав CMemFile входят виртуальные методы Alloc, Free, Realloc, Memcpy и GrowFile.

Методы Alloc, Realloc и Free вызываются другими методами класса CMemFile чтобы выделить блок оперативной памяти для файла, изменить его размер и вернуть после использования операционной системе. Если вы решили сами управлять распределением памяти для файла, вы должны переназначить все эти методы.

Метод Alloc вызывается другими методами класса, когда необходимо получить блок оперативной памяти размера nBytes. Метод возвращает указатель на этот блок:

BYTE * Alloc(DWORD nBytes);

Когда размер файла изменяется, может возникнуть необходимость изменения размера блока памяти, используемого файлом. Для этого методы класса CMemFile могут вызывать метод Realloc:

BYTE * Realloc(BYTE* lpMem, DWORD nBytes);

В качестве параметра методу Realloc передается указатель lpMem на блок памяти и число nBytes, определяющее новый размер блока памяти файла. Метод Realloc возвращает указатель на новый блок памяти. Его адрес может измениться. Если операционная система не может изменить размер блока памяти, метод Realloc возвращает значение NULL.

После использования блока памяти, его необходимо освободить и вернуть операционной системе. Для этого предназначен метод Free:

void Free(BYTE * lpMem);

В качестве параметра lpMem задается адрес блока памяти файла, который надо освободить.

Виртуальные методы класса CFile Read и Write, переназначенные в классе CMemFile, вызывают метод Memcpy. Метод Memcpy предназначен для обмена данными. Вы можете переопределить этот метод в своем классе:


BYTE * 
Memcpy(BYTE* lpMemTarget, BYTE* lpMemSource, UINT nBytes); 

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

Если происходит изменение длины файла, вызывается метод GrowFile. В качестве параметра dwNewLen указывается новый размер файла. Вы можете переназначить этот метод в своем классе:

void GrowFile(DWORD dwNewLen); 

Файловая система - класс CStdioFile

Если вы привыкли пользоваться функциями потокового ввода/вывода из стандартной библиотеки трансляторов Си и Си++, обратите внимание на класс CStdioFile XE "CStdioFile", наследованный от базового класса CFile. Этот класс позволяет выполнять буферизованный ввод/вывод в текстовом и двоичном режиме.

CStdioFile <- CFile <- CObject

В текстовом режиме выполняется специальная обработка символов возврата каретки и перевода строки. Когда в файл, открытый в текстовом режиме, записывается символ перевода строки \n (код 0x0A), он преобразуется в два символа - символ перевода строки (код 0x0A) и символ возврата каретки (код 0x0D). И наоборот, когда из файла считывается пара символов перевода строки и возврата каретки, они преобразуются в один символ перевода строки.

Для объектов класса CStdioFile можно вызывать все методы его базового класса CFile, кроме методов Duplicate, LockRange и UnlockRange. Напомним, что класс CMemFile, также наследованный от базового класса CFile, тоже работает с этими методами.

В класс CStdioFile входит элемент данных m_pStream, который содержит указатель на открытый файл. Если объект CStdioFile создан, но файл либо еще не открыт, либо закрыт, тогда m_pStream содержит константу NULL.

Класс CStdioFile имеет три различных конструктора. Первый конструктор класса CStdioFile не имеет параметров:

CStdioFile();

Этот конструктор только создает объект класса, но не открывает никаких файлов. Чтобы открыть файл, надо вызвать метод Open базового класса CFile.

Второй конструктор класса CStdioFile можно вызывать, если файл уже открыт и вам надо создать новый объект класса и связать с ним открытый файл:

CStdioFile(FILE* pOpenStream);

Этот конструктор можно использовать, если файл был открыт стандартной функцией fopen.

Параметр pOpenStream должен содержать указатель на файл, полученный вызовом стандартной функции fopen.

Третий, последний конструктор можно использовать, если надо создать объект класса CStdioFile, открыть новый файл и связать его с только что созданным объектом:


CStdioFile(LPCTSTR lpszFileName, UINT nOpenFlags);
	throw(CFileException);

Если указанный файл не может быть открыт, вызывается исключение CFileException.

Параметр lpszFileName должен содержать указатель на строку с именем файла. Можно указать полный путь файла, а не только его имя. Параметр nOpenFlags определяет режим, в котором будет открыт файл. Возможные значения этого параметра были описаны ранее (см. Метод Open класса CFile).

Для чтения и записи в текстовый файл класс CStdioFile включает два новых метода ReadString и WriteString. Метод ReadString позволяет прочитать из файла строку символов, а метод WriteString - записать.

Метод ReadString имеет две формы. Первая используется для чтения строк из файла в буфер памяти, а вторая для чтения строк и записи их в объект класса CString.

Вот описание первой формы метода ReadString:


virtual LPTSTR ReadString(LPTSTR lpsz, UINT nMax);
	throw(CFileException);

Из открытого файла считывается текстовая строка и записывается в буфер lpsz. Считается, что строка файла оканчивается символами перевода строки и возврата каретки. В конец строки, записанной в буфер lpsz, заносится символ двоичного нуля (\0).

Максимальное количество считываемых символов определяется параметром nMax. Если в строке файла больше, чем nMax - 1 байт, то остальные символы не будут прочитаны. Метод ReadString возвращает указатель на прочитанную строку или NULL, если достигнут конец файла.

Вторая форма метода ReadString не намного отличается от первой. Вместо двух параметров lpsz и nMax указывается один параметр rString, указывающий на объект класса CString, в который будет записана строка, прочитанная из файла:


BOOL ReadString(CString& rString);
	throw(CFileException);

Еще одно отличие второй формы метода ReadString заключается в типе возвращаемого им значения. Если достигнут конец файла, то вторая форма метода ReadString возвращает константу FALSE.

Для записи в файл текстовой строки предназначен метод WriteString:


virtual void WriteString(LPCTSTR lpsz);
	throw(CFileException);

В качестве параметра lpsz этого метода надо указать адрес буфера с текстовой строкой, закрытой символом \0. Символ \0 не записывается в файл. Если в текстовой строке lpsz есть символы перевода строки, они записываются как пара символов возврата каретки и перевода строки.

Метод WriteString может вызвать исключение, если во время записи в файл произойдет ошибка, например переполнение диска.

Приложение TestFile

Теперь мы представим вам приложение, которое использует класс CStdioFile для записи в файл информации о файловой системе компьютера. Для определения характеристик файловой системы мы воспользовались функцией GetVolumeInformation из программного интерфейса Win32.

Эта функция позволяет определить различные характеристики файловой системы, установленной на данном томе. Ниже представлен прототип функции GetVolumeInformation:


BOOL GetVolumeInformation(
	LPCTSTR	lpRootPathName, 
	LPTSTR	lpVolumeNameBuffer, 
	DWORD  	nVolumeNameSize, 
	LPDWORD	lpVolumeSerialNumber, 
	LPDWORD	lpMaximumComponentLength,
	LPDWORD	lpFileSystemFlags, 
	LPTSTR	lpFileSystemNameBuffer, 
	DWORD 	nFileSystemNameSize 
);	

Первый параметр lpRootPathName перед вызовом функции должен указывать на строку, содержащую путь корневого каталога тома, информацию о котором надо получить. Если вы присвоите lpRootPathName значение NULL, по умолчанию будет выбран корневой каталог текущего тома.

Параметр lpVolumeNameBuffer должен содержать указатель на буфер, в который будет записано имя тома. Длина буфера определяется параметром nVolumeNameSize. Если вас не интересует имя тома, вы можете указать для параметра lpVolumeNameBuffer значение NULL. В этом случае значение nVolumeNameSize не используется.

Параметр lpVolumeSerialNumber должен содержать адрес переменной типа DWORD, в которую будет записан серийный номер тома. Если вас не интересует серийный номер, вы можете указать для параметра значение NULL.

Параметр lpMaximumComponentLength должен содержать адрес переменной, в которую будет записана максимальная длина файла, для данной файловой системы. Для операционной системы Windows 95 эта переменная будет содержать значение 255, несмотря на то, что тип файловой системы остался FAT.

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

Константа Описание
FS_CASE_IS_PRESERVED При записи файлов на диск сохраняется написание (заглавные и прописные символы) имени файла
FS_CASE_SENSITIVE Файловая система различает заглавные и прописные символы в именах файлов
FS_UNICODE_STORED_ON_DISK Файловая система обеспечивает кодировку имен файлов Unicode
FS_FILE_COMPRESSION Обеспечивается возможность сжатия отдельных файлов, расположенных на томе. Эта константа не может быть использована вместе с другими
FS_VOL_IS_COMPRESSED Данный том является компрессованным. Такой том может быть создан системами типа DoubleSpace. Эта константа не может быть использована вместе с другими

Параметр lpFileSystemNameBuffer содержит указатель на буфер, в который будет записан тип файловой системы. В зависимости от типа файловой системы, установленной на данном томе, это может быть FAT, NTFS или HPFS XE "HPFS".

Размер буфера определяется параметром nFileSystemNameSize. Если вас не интересует тип файловой системы, запишите в параметр lpFileSystemNameBuffer значение NULL.

Если функция GetVolumeInformation завершилась без ошибок, она возвращает значение TRUE. В противном случае возвращается значение FALSE, а для получения дополнительной информации вы можете вызвать функцию GetLastError.

В листинге 3.1 представлен исходный текст приложения TestFile. В этом приложении используются всего несколько классов библиотеки классов MFC. Для организации самого приложения мы наследовали от базового класса CWinApp XE "CWinApp" главный класс приложения CMFStartApp. Класс CWinApp был описан нами ранее в разделе “Приложение MFStart”.

В классе CMFStartApp определены два метода: InitInstance и FileSystemInfo. Метод InitInstance является переопределением виртуального метода InitInstance базового класса CWinApp. Этот метод вызывается каждый раз при запуске приложения. Мы используем метод InitInstance для того, чтобы вызвать метод FileSystemInfo и сразу завершить работу приложения.

Метод FileSystemInfo получает данные о файловой системе текущего тома и записывает их в файл fsystem.dat в текущем каталоге. Для получения информации о файловой системе используется функция GetVolumeInformation, описанная выше.

Данные, полученные функцией FileSystemInfo, временно заносятся в строку strTmpOut класса CString. При этом используется метод Format, чтобы получить форматированную запись, содержащую дополнительное текстовое описание.

Для записи в файл мы создали объект класса CStdioFile. В конструкторе мы сразу указали имя файла, который надо создать и открыть. Все операции с файлом, включая его создание, выполняются в блоке try. В случае возникновении ошибки во время создания или других операций с файлом вызывается исключение, которое будет обработано блоком catch.

Мы специально создаем файл и открываем его, используя конструктор класса CStdioFile, а не метод Open. Метод Open в случае возникновения ошибки возвращает соответствующее значение, но не вызывает исключение. Поэтому надо было бы специально проверять значение возвращаемое методом Open.

Для записи в файл мы вызываем метод WriteString, передавая ему в качестве параметра строку strTmpOut, которая содержит данные подготовленные для записи. Каждый вызов метода WriteString записывает в файл одну строку, так как в конце strTmpOut мы всегда записываем символ перевода строки.

Чтобы закрыть файл, мы используем метод Close базового класса CFile. В случае успешного завершения на экране отображается сообщение с именем файла fsystem.dat и управление возвращается методу InitInstance.

Если при работе с файлом возникла ошибка, управление передается блоку catch. В нем отображается сообщение об ошибке, а затем управление сразу возвращается методу InitInstance. Невыполненная часть блока try игнорируется.

Листинг 3.1. Файл TestFile.cpp


// Включаемый файл для MFC
#include <afxwin.h>

//===================================================== 
// Главный класс приложения CMFStartApp
//===================================================== 
class CMFStartApp : public CWinApp
{
public:
	virtual BOOL	InitInstance();
	void			FileSystemInfo();
};
 
// Создаем объект приложение класса CMFStartApp
CMFStartApp MFStartApp;
 
//===================================================== 
// Переопределяем виртуальный метод InitInstance
// класса CWinApp
//===================================================== 
BOOL CMFStartApp::InitInstance()
{
	// Определяем характеристики файловой системы
	FileSystemInfo();	

	// Завершаем приложение
	return FALSE;
}

//===================================================== 
// Метод FileSystemInfo главного класса приложения
// Определяет характеристики текущего тома и записывает 
// их в файл 
//===================================================== 
void CMFStartApp::FileSystemInfo() 
{

	// Метка тома
	CString	VolumeNameBuffer;

	// Максимальная длина метки тома
	DWORD	nVolumeNameSize = 100;	

	// Серийный номер тома
	DWORD	VolumeSerialNumber;

	// Максимальная длина имени файла
	DWORD	MaximumComponentLength;

	// Характеристики файловой системы
	DWORD	FileSystemFlags;

	// Тип файловой системы
	CString	FileSystemNameBuffer;

	// Максимальная длина строки типа файловой системы
	DWORD	nFileSystemNameSize = 100;

	// Получаем данные о файловой системе и текущем устройстве
	GetVolumeInformation(
		NULL, 
		VolumeNameBuffer.GetBuffer(nVolumeNameSize), 
		nVolumeNameSize, 
		&VolumeSerialNumber,
		&MaximumComponentLength, &FileSystemFlags,
		FileSystemNameBuffer.GetBuffer(nFileSystemNameSize),
		nFileSystemNameSize );

	// Снимаем блокировку буферов 
	VolumeNameBuffer.ReleaseBuffer();
	FileSystemNameBuffer.ReleaseBuffer();

	// Обрабатываем ошибочные ситуации, которые могут 
	// возникнуть при работе с файлами
	try
	{
		// Создаем файл fsystem.dat и открываем его для записи
		CStdioFile file("fsystem.dat", 
			CFile::modeCreate | 
			CFile::modeWrite | 
			CFile::typeText);

		// Временная строка, используемая для записи в файл
		CString	strTmpOut;

		// Увеличиваем размер буфера до 512 байт
		strTmpOut.GetBuffer(512);

		// Записываем в файл метку тома
		strTmpOut.Format("Метка тома: %s \n", VolumeNameBuffer);
		file.WriteString(strTmpOut);

		// Записываем в файл серийный номер
		strTmpOut.Format("Серийный номер: %X \n", 
			VolumeSerialNumber);

		file.WriteString(strTmpOut);

		// Записываем в файл тип файловой системы
		strTmpOut.Format("Тип файловой системы: %s \n", 
			FileSystemNameBuffer);

		file.WriteString(strTmpOut);
	
		// Записываем в файл максимальную длину имени файла
		strTmpOut.Format("Максимальная длина имени файла: %d \n", 
			MaximumComponentLength);

		file.WriteString(strTmpOut);
		
		// Записываем в файл свойства файловой системы
		strTmpOut = "Свойства файловой системы \n";

		if(FileSystemFlags &amp; FS_CASE_IS_PRESERVED)
			strTmpOut += "   FS_CASE_IS_PRESERVED\n";

		if(FileSystemFlags &amp; FS_CASE_SENSITIVE)
			strTmpOut += "   FS_CASE_SENSITIVE\n";
	
		if(FileSystemFlags &amp; FS_UNICODE_STORED_ON_DISK)
			strTmpOut += "   FS_UNICODE_STORED_ON_DISK\n";
	
		if(FileSystemFlags &amp; FS_PERSISTENT_ACLS)
			strTmpOut += "   FS_PERSISTENT_ACLS\n";
	
		if(FileSystemFlags &amp; FS_FILE_COMPRESSION)
			strTmpOut += "   FS_FILE_COMPRESSION\n";

		if(FileSystemFlags &amp; FS_VOL_IS_COMPRESSED)
			strTmpOut += "   FS_VOL_IS_COMPRESSED\n";

		file.WriteString(strTmpOut);	
	
		// Закрываем файл
		file.Close();
		// Отображаем сообщение об успешном завершении приложения
		MessageBox(NULL, "File fsystem.dat", "Message", MB_OK);
	}

	// Обработчик исключения. Вызывается при ошибках 
	// работы с файлами
	catch(...)
	{
		// Отображаем сообщение о возникшей ошибке 
		MessageBox(NULL, "File I/O Error", "Error", MB_OK);
	}

	return;
}

Файл fsystem.dat, созданный приложением, можно просмотреть в любом текстовом редакторе, например Notepad или WordPad. В листинге 3.2 приведен пример файла, полученного при помощи приложения TestFile на нашем компьютере, на котором установлена операционная система Windows 95.

Листинг 3.2. Файл fsystem.dat


Метка тома:  LIBRARY
Серийный номер: 1D794E8D 
Тип файловой системы: FAT 
Максимальная длина имени файла: 255 
Свойства файловой системы 
   FS_CASE_IS_PRESERVED
   FS_UNICODE_STORED_ON_DISK

Исключения - класс CException

Как мы рассказывали в главе “Обработка исключительных ситуаций”, язык Си++ позволяет вызывать и обрабатывать исключения любого типа. Однако эта возможность практически не используются классами, определенными в библиотеке MFC.

Для обработки исключительных ситуаций, возникающих в MFC, определен специальный класс. Сам класс CException XE "CException" является абстрактным классом. Объекты такого класса создавать нельзя. Для обработки исключительных ситуаций, возникающих в MFC, используется классы наследованные от класса CException:


CMemoryException        <- |  <-  CException
CFileException          <- |
CArchiveException       <- |
CNotSupportedException  <- |
CResourceException      <- |
CDaoException           <- |
CDBException            <- |
COleException           <- |
COleDispatchException   <- |
CUserException          <- |

Как правило, эти исключения вызываются методами классов MFC, когда возникают какие-либо ошибочные ситуации. Так, например, если вы попытаетесь открыть несуществующий файл, воспользовавшись для этого методом Open из класса CFile, то будет вызвано исключение CFileException.

Если вы желаете обрабатывать исключения, которые вызываются методами классов MFC, вы должны определить обработчики для этих исключений. Каждый такой обработчик должен представлять собой блок catch, в качестве аргумента которого надо использовать указатель на объект класса CException или указатель на объект класса, наследованного от класса CException:


try
{
	// Здесь может находится код, который вызывает исключение
}

// Обработчик для исключения типа CMemoryException
catch(CMemoryException* ptrException)
{
	// Обработка исключения...

	// В конце удаляем объект исключения
	ptrException -> Delete;
}

Еще раз подчеркнем, что обработчик исключений MFC должен принимать указатель на объект класса CException (или класса, наследованного от CException). Этот объект создается при возникновении исключительных ситуаций внутри методов MFC. После того как этот объект окажется не нужен, ваш обработчик должен его удалить. Для этого предназначен метод Delete, определенный в классе CException. Не используйте для удаления объектов класса CException и объектов классов, наследованных от него, обыкновенный оператор delete.

Обработчик исключения может выполнять различные действия в зависимости от того какое исключение и в каком контексте было вызвано. Для этого вы можете использовать методы и данные из объекта, переданного в обработчик исключения.

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

Класс Исключение вызывается
CMemoryException XE "CMemoryException" При распределении оперативной памяти
CFileException XE "CFileException" При работе с файлами
CArchiveException XE "CArchiveException" Во время записи или восстановления объектов (Archive/Serialization)
CNotSupportedException XE "CNotSupportedException" При обращении к неизвестный метод, который не поддерживается данным классом
CResourceException XE "CResourceException" Ошибка при работе с ресурсами Windows
CDaoException XE "CDaoException" Ошибка при работе с базами данных, через средства DAO
CDBException XE "CDBException" Ошибка при работе с базами данных, через средства ODBC
COleException XE "COleException" Ошибка при работе OLE
COleDispatchException XE "COleDispatchException" Ошибка при работе OLE
CUserException XE "CUserException" При обработке этого исключения на экране отображается сообщение, а затем вызывается исключение CException

Сейчас мы не будем рассматривать исключения, связанные с технологией OLE и базами данных.

Класс CException

Класс CException XE "CException" включает два виртуальных метода GetErrorMessage и ReportError. Эти методы позволяют получить словесное описание причины, которая привела к вызову исключения. Заметим, что методы GetErrorMessage и ReportError чисто виртуальные, поэтому они должны быть переопределены в наследуемом классе:


virtual BOOL 
GetErrorMessage(LPTSTR lpszError, UINT nMaxError, 
	PUINT pnHelpContext = NULL);

Когда вы вызываете в обработчике исключения метод GetErrorMessage, он записывает в буфер lpszError сообщение об ошибке, вызвовшей исключение. Размер буфера надо указать в параметре nMaxError. В конце сообщения всегда записывается символ двоичного нуля. Если сообщение не помещается в буфер lpszError (оно больше чем nMaxError - 1 байт), тогда в буфер записываются только nMaxError - 1 символов сообщения. В последний байт записывается двоичный нуль.

Необязательный параметр pnHelpContext может содержать указатель на переменную типа UINT, в которую будет записан идентификатор контекстной подсказки (help context ID).

Метод GetErrorMessage возвращает ненулевое значение, если сообщение об ошибке доступно, и нуль в противном случае.

Вы можете вызывать метод ReportError из обработчика исключений:


virtual int 
ReportError(UINT nType = MB_OK, UINT nMessageID = 0);

Метод ReportError отображает в диалоговой панели на экране сообщение об ошибке, вызвавшей данное исключение. Параметр nType определяет внешний вид диалоговой панели сообщения. В качестве параметра nType можно указать любую комбинацию стилей панелей сообщения, таких как MB_OK, MB_OKCANCEL, MB_RETRYCANCEL, MB_ICONEXCLAMATION, MB_ICONINFORMATION, MB_ICONQUESTION, MB_ICONSTOP. Если вы не укажите этот параметр, тогда подразумевается стиль MB_OK, то есть панель сообщения с одной кнопкой OK.

Иногда исключение может не иметь текстового описания. Вы можете указать методу ReportError, чтобы он отображал в этом случае определенное сообщение. Текст этого сообщения надо сохранить в строковом ресурсе, а соответствующий идентификатор передать методу ReportError в качестве второго параметра nMessageID. Если вы не укажите этот параметр, тогда отображается сообщение “No error message is available”.

Метод ReportError возвращает значение типа AfxMessageBox. Оно определяет, какая кнопка была нажата в диалоговой панели с сообщением.

Методы GetErrorMessage и ReportError используют данные из ресурсов, созданных AppWizard. Поэтому они могут работать неправильно, если приложение создано без использования AppWizard.

Класс CMemoryException

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

Когда приложение пытается создать новую переменную или объект, вызывая оператор new, то в том случае, если память под него не может быть выделена, создается объект класса CMemoryException XE "CMemoryException" и вызывается соответствующее исключение.

Функции malloc не вызывают исключение CMemoryException, но вы можете проверять значение, возвращаемое функцией malloc, и если оно равно нулю, вызывать его сами.

Чтобы самому вызвать исключение, воспользуйтесь функцией AfxThrowMemoryException XE "AfxThrowMemoryException" :

void AfxThrowMemoryException();

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

Приведем небольшой пример использования этой функции. Допустим, вы пытаетесь получить область оперативной памяти для хранения данных с помощью функции GlobalAlloc. Если операционная система не может выделить область памяти такого размера, она возвращает NULL и вы можете вызвать функцию AfxThrowMemoryException:


if(GlobalAlloc(GMEM_FIXED, 1000000) == NULL) 
	AfxThrowMemoryException();

Класс CFileException

Класс CFileException XE "CFileException" предназначен для обработки исключительных ситуаций, возникающих во время создания или вызова методов класса CFile и порожденных от него классов. Этот класс описан нами в разделе “Класс CFile” и предназначается для работы с файловой системой. Естественно, при работе с файловой системой могут возникнуть самые разнообразные ошибки (исключительные ситуации): попытка открыть несуществующий файл, переполнение диска во время операции записи, ошибка чтения с диска и т. д.

Наибольший интерес для нас представляет элемент данных m_cause из класса CFileException. В него заносится код, по которому можно определить причину исключения.

Константа Причина ошибки
CFileException:: none Без ошибки
CFileException:: generic Неопределенная ошибка
CFileException:: fileNotFound Файл не найден
CFileException:: badPath Задан несуществующий путь
CFileException:: tooManyOpenFiles Открыто слишком много файлов
CFileException:: accessDenied Доступ к файлу закрыт
CFileException:: invalidFile Использование неправильного идентификатора (дескриптора) файла
CFileException:: removeCurrentDir Попытка удалить текущий каталог
CFileException:: directoryFull Переполнение структуры каталогов. Невозможно создать новый каталог
CFileException:: badSeek Ошибка во время перемещения указателя файлов
CFileException:: hardIO Ошибка аппаратного обеспечения компьютера
CFileException:: sharingViolation Программа SHARE.EXE не загружена или общая область заблокирована (locked)
CFileException:: lockViolation Попытка заблокировать область файла, которая уже была заблокирована ранее
CFileException:: diskFull Нет свободного пространства на диске
CFileException:: endOfFile Достигнут конец файла

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

Приложение Except

Приложение Except, исходный текст которого представлен в листинге 3.3, показывает как можно выполнить обработку исключительных ситуаций. Оно содержит блок try и несколько обработчиков исключений для объектов типа CMemoryException, CFileException, CException, а также универсальный обработчик. Если в блоке try вызывается исключение, связанное с ошибкой в файловой системе или системе распределения памяти, оно обрабатывается соответствующими блоками catch. Если исключение вызвано с объектом другого типа, но наследованным от класса CException, например CArchiveException, CNotSupportedException или CResourceException, тогда оно обрабатывается блоком catch для объектов CException. И наконец, если объект исключения не имеет базовым классом CException, оно обрабатывается в последнем блоке catch.

Листинг 3.3. Файл Except.cpp


#include "stdafx.h"

int WINAPI WinMain(
					HINSTANCE	hInstance,
					HINSTANCE	hPrevInstance,
					LPSTR   	lpCmdLine,
					int     	nShowCmd
	) 
{
	try
	{
		CFile file("This file is absent", CFile::modeRead);
		// Здесь могут быть операторы, вызывающие другие 
		// исключения
	}

	// Обработчик для исключения типа CMemoryException
	catch(CMemoryException* ptrException)
	{
		MessageBox(NULL,"Memory Exception", "Exception", 
			MB_OK | MB_ICONSTOP);

		ptrException -> Delete();
	}

	// Обработчик для исключения типа CFileException
	catch(CFileException* ptrException)
	{
		if(ptrException -> m_cause == 
                    CFileException::fileNotFound)
			MessageBox(NULL,"File Not Found", "Exception", 
				MB_OK | MB_ICONSTOP);

		else if(ptrException -> m_cause == 
					CFileException::diskFull)
			MessageBox(NULL,"The disk is full", "Exception", 
				MB_OK | MB_ICONSTOP);

		else MessageBox(NULL,"File Exception", "Exception", 
					MB_OK | MB_ICONSTOP);

		ptrException -> Delete();
	}

	// Обработчик для исключений класса CException и 
	// классов наследованных от него
	catch(CException* ptrException)
	{
		MessageBox(NULL,"Exception", "Exception", 
			MB_OK | MB_ICONSTOP);
		ptrException -> Delete();
	}
	
	// Все остальные исключения обрабатываются здесь
	catch(...)
	{
		MessageBox(NULL,"Another Exception", "Exception", 
			MB_OK | MB_ICONSTOP);
	}

	return 0;
}

В блоке try мы пытаемся открыть для чтения файл с именем This file is absent. Длинные имена файлов, содержащие символы пробелов, разрешены в операционных системах Windows 95 и Windows NT. Если файла This file is absent нет на диске, тогда создается объект класса CFileException и вызывается исключение.

Обработчик исключений, связанных с ошибками при работе с файловой системой, проверяет, вызвано ли оно тем, что приложение пытается открыть несуществующий файл. Если это так, на экране отображается сообщение File Not Found.

После обработки исключения, управление передается первому оператору за последним блоком catch. В нашем примере это оператор return. Он завершает работу приложения.

Вы можете сами создать объект класса CFileException и вызвать исключение. Для этого рекомендуется использовать функцию AfxThrowFileException XE "AfxThrowFileException" :

void AfxThrowFileException(int cause, LONG lOsError = –1); 

Параметр cause должен определять причину исключения. В качестве этого параметра можно задавать возможные значения для элемента данных m_cause из класса CFileException (см. таблицу выше). Необязательный параметр lOsError может содержать код ошибки, определенной операционной системой.

Класс CArchiveException

Исключительные ситуации, возникающие во время записи и восстановления объектов из файла, вызывают исключение CArchiveException XE "CArchiveException".

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

Константа Причина ошибки
CArchiveException:: none Без ошибки
CArchiveException:: generic Неопределенная ошибка
CArchiveException:: readOnly Попытка записи в архивный объект, открытый для чтения
CArchiveException:: endOfFile Обнаружен конец файла при чтении объекта
CArchiveException:: writeOnly Попытка читать из архивного объекта, открытого для записи
CArchiveException:: badIndex Неправильный формат файла
CArchiveException:: badClass Попытка прочитать объект в объект неправильного типа
CArchiveException:: badSchema Попытка чтения объекта с несоответствующей версией класса

Чтобы создать объект CArchiveException и вызвать исключение воспользуйтесь функцией AfxThrowArchiveException XE "AfxThrowArchiveException" :

void AfxThrowArchiveException(int cause); 

Параметр cause должен определять причину вызова исключения. Возможный список значений этого параметра представлен в таблице выше (см. элемент данных m_cause класса CArchiveException).

Класс CNotSupportedException

Если приложение пытается вызвать несуществующий метод класса, то вызывается исключение CNotSupportedException XE "CNotSupportedException". Конструктор класса CNotSupportedException имеет следующий вид:

CNotSupportedException();

Однако если вы сами желаете вызвать из своего кода исключение этого типа, то вместо того, чтобы создавать объект класса CNotSupportedException вручную и передавать его оператору throw, воспользуйтесь функцией AfxThrowNotSupportedException XE "AfxThrowNotSupportedException" :

void AfxThrowNotSupportedException();

Класс CResourceException

Если в процессе работы возникают проблемы с ресурсами, например приложение пытается загрузить несуществующий ресурс, тогда вызывается исключение CResourceException XE "CResourceException". Вы можете вызвать это исключение сами. Для этого воспользуйтесь функцией AfxThrowResourceException XE "AfxThrowResourceException" :

void AfxThrowResourceException();

Класс CUserException

Если какая-либо операция при работе приложения закончилась с ошибкой, оно может вызвать функцию AfxMessageBox, чтобы сообщить об этом пользователю, а затем вызвать исключение с объектом класса CUserException XE "CUserException". Чтобы создать объект класса CUserException и вызвать исключение, воспользуйтесь функцией AfxThrowUserException XE "AfxThrowUserException" :

void AfxThrowUserException();

Запись и восстановление объектов

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

Чтобы облегчить программисту решение этой задачи, библиотека классов MFC определяет механизм записи и восстановления объектов (serialization). Поддержка механизма записи и восстановления объектов осуществляется средствами класса CObject.

Классы, наследованные от CObject также могут обеспечивать работу механизма записи и восстановления объектов. Для этого при объявлении класса надо указать макрокоманду DECLARE_SERIAL XE "DECLARE_SERIAL", а при определении - макрокоманду IMPLEMENT_SERIAL XE "IMPLEMENT_SERIAL".

Макрокоманду DECLARE_SERIAL надо поместить перед описанием вашего класса во включаемом файле. Непосредственно после имени макрокоманды надо указать имя класса - class_name:

DECLARE_SERIAL(class_name)

Макрокоманду IMPLEMENT_SERIAL надо указать перед определением класса в файле исходного текста приложения, имеющего расширение CPP. Прототип макрокоманды IMPLEMENT_SERIAL представлен ниже:

IMPLEMENT_SERIAL(class_name, base_class_name, wSchema)

Параметр class_name определяет имя вашего класса, base_class_name - имя базового класса из которого непосредственно наследуется ваш класс. Последний параметр wSchema - это число типа UINT, определяющее версию программы. Если вы разработаете новую версию своего приложения и измените набор данных, которые необходимо записать в файл, измените параметр wSchema.

В классе должны быть определены специальные методы для записи и восстановления состояния объектов этого класса. Обычно эти методы сохраняют и восстанавливают элементы данных из класса. Таким образом, объекты класса сами отвечают за то, как они сохраняют и восстанавливают свое состояние.

Методы, сохраняющие и восстанавливающие состояние класса, взаимодействуют с объектом класса CArchive, который осуществляет непосредственную запись и чтение информации из файла на диске.

Класс CObject содержит виртуальный метод Serialize XE "CObject:Serialize", отвечающий за запись и чтение объектов классов, наследованных от класса CObject:


virtual void Serialize(CArchive& ar);
	throw(CMemoryException);
	throw(CArchiveException);
	throw(CFileException);

В качестве параметра ar, методу передается указатель на объект класса CArchive XE "CArchive", используемый для записи и восстановления его состояния из файла. Чтобы узнать, какую операцию должен выполнить метод Serialize, воспользуйтесь методами CArchive::IsLoading или CArchive::IsStoring.

Новая реализация метода Serialize должна первым делом вызвать метод Serialize базового класса. Это гарантирует, что при сохранении и восстановлении объекта будут обработаны все элементы всех базовых классов.

Метод Serialize вызывается объектами класса CArchive когда приложение читает или записывает этот объект, вызывая методы CArchive::ReadObject XE "CArchive:ReadObject" или CArchive::WriteObject XE "CArchive:WriteObject". Сразу отметим, что с методами CArchive::ReadObject XE "CArchive:ReadObject" и CArchive::WriteObject непосредственно связаны операторы записи << и чтения >>.

Перед тем как создать объект класса CArchive, необходимо создать объект класса CFile. Связывая с объектом CFile XE "CFile" файл на диске, имейте в виду, что если вы желаете записать объект файл, то файл надо открыть на запись, а если надо считать файл с диска и загрузить из него данные в объект, открыть файл надо для чтения.

Конструктор класса CArchive имеет следующий вид:


CArchive(CFile* pFile, UINT nMode, int nBufSize = 512, 
	void* lpBuf = NULL);
	throw(CMemoryException, CArchiveException, CFileException);

Параметр pFile должен содержать указатель на объект класса CFile, из которого будут считываются или записываться данные. Перед вызовом конструктора файл, связанные с объектом pFile должен быть уже открыт.

Параметр nMode определяет, будут данные записываться в файл или считываться из него. Параметр nMode может принимать одно из трех значений CArchive::load, CArchive::store или CArchive::bNoFlushOnDelete, описанных в следующей таблице.

Константа Описание
CArchive::load Данные считываются из файла на диске. Впоследствии они будут записаны в восстанавливаемый объект. В этом случае файл, определенный параметром pFile, должен быть открыт для чтения.
CArchive::store Данные будут записываются в файл на диске. Файл, определенный параметром pFile должен быть открыт для записи.
CArchive:: bNoFlushOnDelete Когда вызывается деструктор класса CArchive, он автоматически вызывает метод Flush для файла pFile. Если вы укажите этот флаг, то метод Flush вызван не будет. Чтобы предотвратить потерю данных, вы должны будете перед вызовом деструктора закрыть данный файл.

Для операций чтения и записи в файл класс CArchive выполняет буферизацию. Размер этого буфера определяется необязательным параметром nBuf Size в байтах. Если вы не укажите размер буфера, используется буфер размером 512 байт.

Конструктор класса CArchive сам получает у операционной системы блок оперативной памяти для буфера. Однако с помощью необязательного параметра lpBuf вы можете предоставить собственный буфер. В этом случае параметр nBufSize должен указывать размер этого буфера.

Созданный вами объект класса CArchive можно будет использовать только для записи или только для чтения. Если вам надо сначала считать данные, а потом записать, следует создать для этого два отдельных объекта класса CArchive.

Не используйте методы класса CFile для доступа к файлу во время его использования совместно с объектами класса CArchive. Запись, чтение или перемещение указателя файла может вызвать нарушения во внутренней структуре файла архива.

Когда созданный объект класса CArchive передается функции Serialize, вы можете определить, предназначен он для записи или для чтения, вызвав метод CArchive::IsLoading XE "CArchive:IsLoading" или CArchive::IsStoring XE "CArchive:IsStoring".

Метод IsStoring возвращает ненулевое значение, если данный объект предназначен для записи в файл и нуль в противном случае:

BOOL IsStoring() const;

Метод IsLoading является полной противоположностью метода IsStoring. Он возвращает ненулевое значение, если данный объект предназначен для чтения из файла и нуль в противном случае. Вы можете использовать любой метод IsLoading или IsStoring.

Основное предназначение объекта класса CArchive заключается в том, что объекты вашего класса могут посредством него записать свое состояние в файл, а затем при необходимости восстановить его. Для этого в классе CArchive определены операторы записи в файл << и чтения из файла >>. Вы также можете использовать методы WriteString, Write, ReadString и Read. Опишем эти операторы и методы более подробно.

Запись в архивный файл

Когда приложение желает сохранить состояние объекта данного класса в файле, оно вызывает для него метод Serialize. В качестве параметра этому методу передается указатель на объект класса CArchive, связанные с файлом, открытым на запись.

В этом случае реализация метода Serialize должна сохранить в файле все элементы данных, которые потом потребуется восстановить. Для этого необходимо воспользоваться оператором << или методами WriteString и Write, определенными в классе CArchive.

Оператор << можно использовать для записи в архивный файл переменных простых типов, например long, int, char и объектов других классов, которые наследованы от класса CObject.

В одной строке программы можно использовать оператор << несколько раз. Такая запись сокращает исходный код программы и делает его более легким для понимания, так как все операторы << будут сгруппированы вместе.

Для записи в архивный файл массивов удобнее использовать метод Write класса CArchive XE "CArchive" :


void Write(const void* lpBuf, UINT nMax);
	throw(CFileException);

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

Параметр lpBuf является указателем на сохраняемую область данных, а параметр nMax определяет количество байт из этой области, которое надо записать в файл.

Если требуется сохранить строку символов, закрытую нулем, то гораздо удобнее вместо метода Write использовать метод WriteString. Метод WriteString записывает в архивный файл строку lpsz:


void WriteString(LPCTSTR lpsz);
	throw(CFileException);

Чтение из архивного файла

В предыдущем разделе мы рассказали как сохранить объект вашего класса в архивном файле. Теперь опишем, как восстановить записанное ранее состояние объекта класса из архивного файла. Когда приложение желает восстановить состояние объекта данного класса, оно вызывает для него метод Serialize. В качестве параметра этому методу передается указатель на объект класса CArchive, связанного с файлом, открытым для чтения.

Реализация метода Serialize должна восстановить из файла все элементы данных, которые были в него записаны. Для этого можно воспользоваться оператором >> или методами ReadString и Read, определенными в классе CArchive.

Вы должны считывать данные из архивного файла именно в том порядке, в котором они были в него записаны.

Оператор >> можно использовать для чтения из архивного файла переменных простых типов, например long, int, char и объектов других классов, которые наследованы от класса CObject. В одной строке программы можно использовать оператор >> несколько раз.

Для чтения из архивного файла массивов, записанных в него методом Write, надо использовать метод Read класса CArchive. Он позволяет прочитать из файла определенное количество байт и записать его в указанный буфер:


UINT Read(void* lpBuf, UINT nMax);
	throw(CFileException);

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

Если требуется прочитать из архивного файла строку, записанную в него методом WriteString, воспользуйтесь методом ReadString. В состав класса CArchive входят два метода ReadString, которые предназначены для записи прочитанной из файла строки в объект класса CString или в строку Си.

Первый метод имеет более простой прототип. Единственный параметр rString определяет объект класса CString, в который будет записана полученная информация. Если метод ReadString успешно завершит чтение, он возвращает значение TRUE, в противном случае - значение FALSE:

BOOL ReadString(CString& rString);

Если вам надо записать прочитанную из архивного файла строку в массив символов, воспользуйтесь другим прототипом метода ReadString:


LPTSTR ReadString(LPTSTR lpsz, UINT nMax);
	throw(CArchiveException);

Параметр lpsz должен указывать на буфер памяти, в который будет записана информация. Параметр nMax определяет максимальное количество символов, которое метод может прочитать из файла. Размер буфера lpsz должен быть как минимум на единицу больше, чем значение nMax (в конец строки lpsz будет записан символ \0).

Когда метод Serialize завершит работу по восстановлению или записи объекта из архивного файла, вы должны закрыть используемый для этого объект класса CArchive. Для этого необходимо вызвать метод Close:


void Close();
	throw(CArchiveException, CFileException);

После вызова этого метода закройте файл, связанный с объектом CArchive, вызвав метод CFile::Close, и удалите сам объект класса CFile.

Многозадачные приложения

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

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

Библиотека классов MFC позволяет создавать многозадачные приложения XE "многозадачные приложения".

Приложения Windows 95 и Windows NT могут быть многозадачными. Такие приложения состоят из нескольких независимых задач. Дополнительные задачи могут использоваться, чтобы выполнять фоновую работу. Класс CWinApp XE "CWinApp" представляет основную задачу приложения. Если вам надо создать дополнительные задачи, вы должны воспользоваться классом CWinThread XE "CWinThread".


Приложение Pview 95 позволяет просмотреть список процессов, загруженных в памяти и остановить любой процесс