Программирование на языке MFC

Мой второй блог в серии программирования

Архив раздела «Возможности MFC»

Как легко заметить, конструктору CCtrlView::CCtrlView() в каче­стве параметра передается имя класса, которое тут же записыва­ется в поле m_strClass. Отметим этот факт. Затем в методе CFrameWnd::CreateView() вызывается метод Create() нашего объ­екта представления:

BOOL CWnd::Create(LPCTSTR IpszClassName, LPCTSTR IpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext)

{

// can’t use for desktop or pop-up windows // (use CreateEx instead) ASSERT(pParentWnd != NULL); ASSERT((dwStyle & WS_POPUP) == 0); return CreateEx (0,

IpszClassName,

IpszWindowName,

dwStyle I WS_CHILD,

rect.left, rect.top,

rect.right – rect.left,

rect.bottom – rect.top,

pParentWnd->GetSafeHwnd(),

(HMENU)nID,

(LPVOID)pContext);

}



Читатель, взгляните, пожалуйста, на исходный код метода CMDIChildWnd::LoadFrame(). В нем вы увидите следующий опе­ратор:

if (!Create(GetlconWndClass(dwDefaultStyle, nIDResource), strTitle, dwDefaultStyle, rectDefault,

(CMDIFrameWnd*) pParentWnd, pContext)) …

BOOL CMDIChildWnd::Create(LPCTSTR IpszClassName,

LPCTSTR IpszWindowName, DWORD dwStyle, const RECT& rect, CMDIFrameWnd* pParentWnd, CCreateContext* pContext)

{

if (pParentWnd == NULL) {

CWnd* pMainWnd = AfxGetThread()->m_pMainWnd; ASSERT(pMainWnd != NULL); ASSERT_KINDOF(CMDIFrameWnd, pMainWnd); pParentWnd = (CMDIFrameWnd*)pMainWnd;

ASSERT(::IsWindow(pParentWnd->m_hWndMDIClient));

// insure correct window positioning pParentWnd->RecalcLayout();

// first copy into a CREATESTRUCT for PreCreate CREATESTRUCT cs; cs.dwExStyle = OL; cs.IpszClass = IpszClassName; cs.lpszName = IpszWindowName; cs.style = dwStyle; cs.x = rect.left; cs.y = rect.top; cs.cx = rect.right – rect.left; cs.cy = rect.bottom – rect.top; cs.hwndParent = pParentWnd->m_hWnd; cs.hMenu = NULL;

cs.hlnstance = AfxGetlnstanceHandle(); cs.lpCreateParams = (LPVOID)pContext;

if (!PreCreateWindow(cs)) {

PostNcDestroy(); return FALSE;

}

// extended style must be zero for MDI Children // (except under Win4)

ASSERT(afxData.bWin4 || cs.dwExStyle == 0);

ASSERT(cs.hwndParent == pParentWnd->m_hWnd); // must not change

// now copy into a MDICREATESTRUCT for real create MDICREATESTRUCT mcs; mcs.szClass = cs :*lpszClass; mcs.szTitle = cs.lpszName; mcs.hOwner = cs.hlnstance;

mcs.x = cs.x; mcs.у = cs.y; mcs.cx = cs.cx; mcs.cy = cs.cy;

mcs.style = cs.style & -(WS_MAXIMIZE | WS_VISIBLE); mcs.lParam = (LONG)cs.lpCreateParams;

// create the window through the MDICLIENT window AfxHookWindowCreate(this); HWND hWnd =

(HWND)::SendMessage(pParentWnd->m_hWndMDIClient, WM_MDICREATE, 0,

(LPARAM)&mcs); if (!AfxUnhookWindowCreate()) PostNcDestroy(); // cleanup if MDICREATE fails too soon

if (hWnd ~ NULL) return FALSE;

// special handling of visibility (always created invisible) if (cs.style & WS_VISIBLE) {

// place the window on top in z-order before showing it ::BringWindowToTop(hWnd);

// show it as specified

if (cs.style & WS_MINIMIZE)

ShowWindow(SW_SHOWMINIMIZED); else if (cs.style & WS_MAXIMIZE)

ShowWindow(SW_SHOWMAXIMIZED) ; else

ShowWindow(SW_SHOWNORMAL);

// make sure it is active (visibility == activation) pParentWnd->MDIActivate(this);

// refresh MDI Window menu

::SendMessage(pParentWnd->m_hWndMDIClient, WM_MDIREFRESHMENU, 0, 0) ; }

ASSERT(hWnd == m_hWnd); return TRUE;

}

fCMDIChJldWnd::OnCreate)



Итак, что же происходит при работе конструктора? Естествен­но, инициализируются все поля объекта. Мы можем заметить, что поле m_nMode хранит режим работы архива, в поле m_pFile со­храняется указатель на объект класса CFile, на основе которого создан архив. Назначение остальных полей, надеюсь, мы поймем чуть позже. Обратите внимание, читатель, что среди инициализи­руемых полей есть поле m_bUserBuf, которому присваивается зна­чение TRUE.

В том случае, если длина буфера, переданного конструктору, окажется менее 128 байтов, конструктор не будет использовать переданный ему буфер и сделает указатель на буфер равным NULL, а длину буфера сделает равной 128 байтам. Длина бу­фера сохраняется в поле m_nBufSize. После этого, в том случае, если указатель на буфер оказался равным NULL (например, при вызове конструктора использовался указатель по умолчанию или указанный при вызове конструктора размер буфера был меньше 128 байтов), то конструктор выделяет новый буфер тре­буемого размера, записывает указатель на буфер в поле mJpBufferStart и… Нам становится понятно назначение еще одного поля! Поле m_bUserBuf является флагом. Значение TRUE этого поля определяет, что объект будет использовать ранее подготовленный буфер, значение FALSE говорит о том, что бу­фер, используемый объектом, был создан во время работы кон­структора. Естественно, в том случае, когда конструктор са­мостоятельно выделяет буфер для работы, значение этого поля делается равным FALSE.

После того как все указатели проинициализированы, определяет­ся указатель на конец буфера. Этот указатель хранится в поле mJpBufMax. Но возникает вопрос – а зачем нам необходимо хра­нить две величины, фактически определяющие размер буфера – поле mjiBufSize и поле mJpBufSize? Вопрос закономерен, то отвечу я на него немного позже, ОК?

А затем происходит одна интересная вещь. Указатель на те­кущую позицию буфера (поле mJpBufCur) в том случае, если происходит чтение из архива, устанавливается на конец буфе­ра. В случае, если происходит запись в архив, указатель уста­навливается на начало буфера. Это, разумеется, не означает, что чтение или запись будут производиться в обратном поряд­ке. Нет, конечно, все гораздо проще. Если при операции чтения из архива в буфер указатель на текущую позицию буфера дос­тигает конца буфера, это означает, что буфер заполнен, читать больше некуда, и необходимо произвести некоторые действия для того, чтобы буфер можно было бы использовать повторно. Аналогично, если при записи из буфера в архив указатель на начало файла и на текущее позицию в буфере совпадают, то это означает, что все данные из буфера уже «сброшены» в ар­хив и что опять-таки необходимо произвести некоторые дейст­вия для того, чтобы заполнить буфер. Другими словами, поло­жение указателя является своеобразным флагом, вынуждающим метод произвести подготовку буфера к считыванию данных из архива или записи данных в архив.

Запись в архив и чтение из архива больших объемов информа­ции производится при помощи перегруженных операторов ««» и «»». В классе CArchive мы можем найти перегруженные опера­ции для чтения из архива и записи в архив переменных типа WORD, DWORD, float, double. Помимо этого предусмотрены операции за­писи в архив и чтения из архива строк. Для того, чтобы понять, как происходит чтение из архива и запись в архив примитивов, рас­смотрим, каким образом осуществляется запись в архив и чтение из архива переменной типа WORD.



Первым аргументом конструктора является указатель на объ­ект класса CFile, на основе которого будет создан объект класса CArchive. Второй аргумент – это режим работы архива. Если вы заглянете в описание класса CArchive, то увидите в нем перечис­ление Mode, в котором как раз и приводятся значения, которые может принимать второй аргумент конструктора. В 14 приве­дено назначение каждого из этих режимов:

Режим работы архива, кстати, может быть получен при помощи методов lsl_oading()

_AFX_INLINE BOOL CArchive::IsLoading() const { return (m_nMode & CArchive::load) != 0; }

и lsStoring()

_AFX_INLINE BOOL CArchive::IsStoring() const { return (m_nMode & CArchive::load) == 0; }

Третий аргумент конструктора – это размер временного буфера, который будет использоваться для промежуточного хранения данных. По умолчанию размер буфера равен 4К (4096 байтов). Четвертым аргументом, как можно уже догадаться, является указатель на вре­менный буфер. По умолчанию указатель на буфер равен NULL, т. е. буфер по умолчанию создается самим конструктором.



Исходный текст конструктора клас-саа CArchive находится в файле агссоге.срр:

Пример

CArchive::CArchive(CFile* pFile, UINT nMode, int nBufSize, void* lpBuf) :

m_strFileName(pFile->GetFilePath() )

{

ASSERT_VALID(pFile);

// initialize members not dependent on allocated buffer

m_nMode = nMode; m_pFile = pFile; m_pSchemaMap = NULL; m_pLoadArray = NULL; m_pDocument = NULL; m_bForceFlat = TRUE;

m_nObjectSchema = (UINT)-l; // start with invalid schema

if (IsStoringO )

m_nGrowSize = nBlockSize; else

m_nGrowSize = nGrowSize; m_nHashSize = nHashSize;

// initialize the buffer. minimum size is 128 m_lpBufStart = (BYTE*)lpBuf; m_bUserBuf = TRUE; m_bDirectBuffer = FALSE;

if (nBufSize < nBufSizeMin) {

// force use of private buffer of minimum size m_nBufSize = nBufSizeMin; m_lpBufStart = NULL;

}

else

m_nBufSize = nBufSize;

nBufSize = m_nBufSize; if (m_lpBufStart == NULL) {

// check for CFile providing buffering support m_bDirectBuffer =

m_pPile->GetBufferPtr(CFile::bufferCheck); if (!m_bDirectBuffer) {

// no support for direct buffering, // allocate new buffer m_lpBufStart = new BYTE[m_nBufSize]; m_bUserBuf = FALSE;

}

else {

I/ CFile* supports direct buffering! nBufSize = 0; // will trigger initial FillBuffer

}

}

if (!m_bDirectBuffer) {

ASSERT(m_lpBufStart !=NULL);

ASSERT(AfxIsValidAddress(m_lpBufStart,

nBufSize, IsStoring()));

}

m_lpBufMax = m_lpBufStart + nBufSize;

m_lpBufCur = (IsLoading()) ? m_lpBufMax : m_lpBufStart;
ASSERT (m_pStoreMap == NULL);
// same as m_pLoadArray

}



03.02.2010

Естественно, что сериализация неразрывно связана с файла­ми. Но для осуществления сериализации объекты класса CFile слишком громоздки. Поэтому для сериализации используются так называемые архивы. Архив создается на базе файла и обеспечи­вает более удобные средства для осуществления записи объектов в файл. Класс CArchive в файле afx.h описан следующим образом:

class CArchive {

public:

// Flag values

enum Mode { store = 0, load = 1Г

bNoFlushOnDelete = 2, bNoByteSwap = 4 };

CArchive(CFile* pFile,

UINT nMode,

int nBufSize = 4096,

void* lpBuf = NULL); -CArchive();

// Attributes

BOOL IsLoadingO const; BOOL IsStoringO const; BOOL IsByteSwapping() const; BOOL IsBufferEmpty() const; CFile* GetFileO const;

UINT GetObjectSchema(); // only valid when reading

// a CObject* void SetObjectSchema(UINT nSchema);

// pointer to document being serialized — must set // to serialize COleClientltems in a document! CDocument* m_pDocument;

// Operations

UINT Read(void* lpBuf, UINT nMax);

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

void Flush(); void Close();

void Abort(); // close and shutdown without exceptions

// reading and writing strings

void WriteString(LPCTSTR lpsz);

LPTSTR ReadString(LPTSTR lpsz, UINT nMax);

BOOL ReadString(CStringS rString);

public:

// Object I/O is pointer based to avoid added // construction overhead.

// Use the Serialize member function directly for // embedded objects.

friend CArchive& AFXAPI operator«(CArchive& ar,

const CObject* pOb);

friend CArchiveS AFXAPI operator»(CArchive& ar,

CObject*& pOb); friend CArchive& AFXAPI operator>>(CArchive& ar,

const CObject*& pOb);

// insertion operations CArchive& operator« (BYTE by) ; CArchiveS operator« (WORD w) ; CArchiveS operator« (LONG 1) ; CArchive& operator« (DWORD dw) ; CArchiveS operator« (float f) ; CArchive& operator« (double d) ; CArchive& operator<< (int i); CArchive& operator<<(short w); CArchive& operator<< (char ch); CArchive& operator<<(unsigned u);

// extraction operations CArchiveS operator» (BYTE& by) ; CArchive& operator>>(W0RD& w); CArchive& operator» (DWORDS dw) ; CArchive& operator» (L0NG& 1) ; CArchive& operator» (floats f) ; CArchive& operator>>(doubles d);

CArchive& operator>>(int& i);

CArchive& operator>>(short& w); CArchive& operator>>(char& ch); CArchive& operator>>(unsigned& u);

// object read/write

CObject* ReadObject(const CRuntimeClass* pClass); void WriteObject(const CObject* pOb);

// advanced object mapping (used for forced references) void MapObject(const CObject* pOb);

// advanced versioning support

void WriteClass(const CRuntimeClass* pClassRef);

CRuntimeClass* ReadClass(const CRuntimeClass*

pClassRefRequested = NULL, UINT* pSchema = NULL, DWORD* pObTag = NULL);

void SerializeClass(const CRuntimeClass* pClassRef);

// advanced operations (used when storing/loading // many objects)

void SetStoreParams(UINT nHashSize = 2053,

UINT nBlockSize = 128); void SetLoadParams(UINT nGrowBy = 1024);

// Implementation public:

BOOL m_bForceFlat; // for COleClientltem implementation

// (default TRUE) BOOL m_bDirectBuffer; // TRUE if m_pFile supports

// direct buffering void FillBuffer(UINT nBytesNeeded);

void CheckCount(); // throw exception if m_nMapCount

// is too large

// special functions for reading and writing

// (16-bit compatible) counts

DWORD ReadCount();

void WriteCount(DWORD dwCount);

// public for advanced use UINT m_nObjectSchema; CString m_strFileName;

protected:

// archive objects cannot be copied or assigned

CArchive(const CArchive& arSrc);

void operator= (const CArchive& arSrc);

BOOL m_nMode; BOOL m_bUserBuf; int m_nBufSize;

CFile* m_pFile; «’

BYTE* m_lpBufCur; BYTE* m_lpBufMax; BYTE* m_lpBufStart;

// array/map for CObject* and CRuntimeClass* load/store

UINT m_nMapCount;

union

{

CPtrArray* m_pLoadArray; CMapPtrToPtr* m_pStoreMap;

};

// map to keep track of mismatched schemas CMapPtrToPtr* mjpSchemaMap;

// advanced parameters (controls performance with // large archives) UINT m_nGrowSize; UINT m_nHashSize;

};

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



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

Для того чтобы реализовать сериализацию, можно пойти при­вычным путем – в программе создать файл, затем при помощи стан­дартных операций записи в файл «сбросить» в него данные, потом при помощи таких же стандартных операций считать данные. Но де­ло в том, что программе придется работать не с объектами, а с не­которыми блоками памяти или, на худой конец, со строками. Все будет работать нормально, но каждый раз придется считать, какую переменную куда записать, помнить о формате записанной инфор­мации… Ужас!

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

Но я не зря выше употребил слово «стандартные». Дело в том, что MFC позволяет программистам использовать процедуры се-риализации, которые являются стандартными для MFC. Конечно, процедуру сериализации для всех возможных объектов и органи­зованных в памяти структур данных написать невозможно, но впол­не возможно определить порядок применения процедур сериали-зации, описать интерфейсы и т.д.



Обратите внимание на один интересный факт. В том случае, если в сумме указатель файла и число записываемых байтов пре­вышают размер файла, вызывается метод GrowFile(), т. е. размер файла динамически увеличивается. Затем метод вызывает Метсру(), который непосредственно осуществляет копирование из буфера в файл в памяти. Никакого значения метод WriteQ не возвращает.

Метод работает подобно методу Read(), отличие состоит только в направлении перемещения информации. Кроме этого, метод Write() не возвращает никакого значения.

Перед тем как завершить рассмотрение класса CMemFile, не­обходимо заметить, что методы LockRange(), UnlockRange() и Duplicate(), унаследованные от класса CFile, не поддерживаются. Обращение к ним немедленно приводит к выработке исключения.



03.02.2010

Методу Write(), который используется для записи данных из бу­фера памяти в файл, в качестве аргументов также передаются указатель на буфер в памяти, данные из которого должны быть занесены в файл, и количество записываемых байтов. Исходный код метода находится в файле filemem.cpp:

void CMemFile::Write(const void* lpBuf, UINT nCount) {

ASSERT_VALID(this);

if (nCount == 0) return;

ASSERT(lpBuf != NULL);

ASSERT(AfxIsValidAddress(lpBuf, nCount, FALSE));

if (m_nPosition + nCount > m_nBufferSize) GrowFile(m_nPosition + nCount);

ASSERT(m_nPosition + nCount <= m_nBufferSize);

Memcpy((BYTE*)m_lpBuffer + m_nPosition, (BYTE*)lpBuf, nCount);

m_nPosition += nCount;

if (m_nPosition > m_nFileSize) m_nFileSize = m_nPosition;

ASSERT_VALID(this);

}



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



Как мы и ожидали, этому методу передаются указатель на бу­фер-приемник данных, указатель на буфер-источник данных и ко­личество байтов, которые должны быть скопированы. При этом копирование происходит при помощи функции memcpy().

Как мне представляется, этот метод является вспомогательным и предназначен прежде всего для обеспечения нормальной рабо­ты методов Read() и WriteQ.

Исходный код метода Read(), предназначенного для чтения ин­формации из файла в памяти, находится в файле filemem.cpp:

UINT CMemFile::Read(void* lpBuf, UINT nCount) {

ASSERT_VALID(this);

if (nCount == 0) return 0;

ASSERT(lpBuf != NULL);

ASSERT(AfxIsValidAddress(lpBuf, nCount));

if (m_nPosition > m_nFileSize) return 0;

UINT nRead;

if (m_nPosition + nCount > m_nFileSize)

nRead = (UINT)(m_nFileSize – m_nPosition);

else

nRead = nCount;

Memcpy((BYTE*)lpBuf,

(BYTE*)m_lpBuffer + m_nPosition,

nRead); m_nPosition += nRead;

ASSERT_VALID(this); return nRead;

}



Еще один вопрос, который может возникнуть у читателя -а как же осуществляется считывание данных из файла в памя­ти в буфер? Наверняка, используется что-нибудь подобное функ­ции memcpyO? Да, уважаемый читатель! Оказывается, у объек­та класса CMemFile есть специальный метод, который называ­ется Метсру()! Его исходный текст можно найти все в том же файле filemem.cpp:

#pragma intrinsic(memcpy)

BYTE* CMemFile::Memcpy(BYTE* lpMemTarget,

const BYTE* lpMemSource, UINT nBytes)

{

ASSERT(lpMemTarget != NULL); ASSERT(lpMemSource != NULL);

ASSERT(AfxIsValidAddress(lpMemTarget, nBytes)); ASSERT(AfxIsValidAddress(lpMemSource,

nBytes,

FALSE));

return (BYTE*)memcpy(lpMemTarget,

lpMemSource, nBytes);

}

#pragma function(memcpy)



Еще одним методом, влияющим на длины буфера и зоны, явля­ется метод Setl_ength(). Его исходный код находится в файле filemem.cpp:

void CMemFile::SetLength(DWORD dwNewLen) {

ASSERT_VALID(this); if (dwNewLen > m_nBufferSize) GrowFile(dwNewLen) ;

if (dwNewLen < m_nPosition)

m_nPosition = dwNewLen; m_nFileSize = dwNewLen; ASSERT_VALID(this);

}

Методу в качестве аргумента передается новая длина файла. В случае, если длина файла превышает размер буфера, то тогда при помощи метода GrowFile() размер буфера динамически увели­чивается. В противном случае длина файла делается равной зна­чению, переданному в качестве аргумента. Обратите внимание, ува­жаемый читатель, что никакого перераспределения памяти при этом не происходит. Указатель файла в том случае, если его значение оказывается превышающим новый размер файла, устанавливает­ся на конец файла новой длины. Все легко и просто!



03.02.2010

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

Вернемся к методу GrowFile(). Исходный код этого метода нахо­дится в файле filemem.cpp:

void CMemFile::GrowFile(DWORD dwNewLen) {

ASSERT_VALID(this);

if (dwNewLen > m_nBufferSize) {

// grow the buffer

DWORD dwNewBufferSize = (DWORD)m_nBufferSize;

// watch out for buffers which cannot be grown! ASSERT(m_nGrowBytes != 0) ; if (m_nGrowBytes == 0)

AfxThrowMemoryException ();

// determine new buffer size while (dwNewBufferSize < dwNewLen) dwNewBufferSize += m_nGrowBytes;

// allocate new buffer

BYTE* lpNew;

if (m_lpBuffer == NULL)

lpNew = Alloc(dwNewBufferSize); else

lpNew = Realloc(m_lpBuffer, dwNewBufferSize);

if (lpNew == ftULL)

AfxThrowMemoryException (); m_lpBuffer = lpNew; m_nBufferSize = dwNewBufferSize;

}

ASSERT_VALID(this);

}

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

Но допустим, все прошло нормально. В таком случае метод оп­ределяет размер требуемого буфера. Как только размер требуе­мого буфера определен, метод пытается выделить требуемый блок памяти. При этом если у объекта еще не было ассоциированного с ним буфера, то метод пытается выделить буфер при помощи ме­тода А11ос(). Если же у объекта был ассоциированный буфер, то метод использует для выделения памяти метод Realloc(). Если па­мять по каким-то причинам выделена быть не может, то вырабаты­вается исключение. Если же все прошло нормально, то тогда ме­тод корректирует указатель на буфер и значение длины буфера. Если оставить все детали, то можно сказать, что метод GrowFileQ динамически увеличивает размер буфера, ассоциированного с объектом, после чего корректирует указатель на буфер и зна­чение длины буфера.



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

Исходный код метода Realloc() находится в файле filetxt.cpp:

BYTE* CMemFile::Realloc(BYTE* lpMem, DWORD nBytes) {

return (BYTE*)realloc(lpMem, (UINT)nBytes) ;

}

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



Думаю, читатель вправе задать мне сейчас вопрос – а каким образом осуществляется увеличение размера файла? Какова, так сказать, физика этого процесса? Уважаемый читатель, на этот вопрос мы смо­жем ответить после того, как рассмотрим метод GrowFile(), который и решает эту задачу. Но перед этим я хотел бы остановиться еще на двух методах, которые обеспечивают управление памятью. Исходный код метода А11ос() находится в файле filetxt.cpp:

BYTE* CMemFile::Alloc(DWORD nBytes) {

return (BYTE*)malloc((UINT)nBytes);

}



Но, думаю, у читателя уже возник вопрос – а как же быть в тех случаях, когда мы для создания файла в памяти используем кон­структор по умолчанию? Для того чтобы можно было ассоцииро­вать объект класса CMemFile с уже созданным буфером, можно воспользоваться методом Attach(). Его исходный код находится в файле filemem.cpp:

void CMemFile::Attach(BYTE* lpBuffer,

UINT nBufferSize, UINT nGrowBytes)

{

ASSERT(m_lpBuffer == NULL);

m_nGrowBytes = nGrowBytes;

m_nPosition = 0;

m_nBufferSize = nBufferSize;

m_nFileSize = nGrowBytes == 0 ? nBufferSize : 0; m_lpBuffer = lpBuffer; m_bAutoDelete = FALSE;

}

При вызове этого метода в качестве аргументов используются указатель на буфер в памяти, размер этого буфера и размер при­ращения, который по умолчанию равен нулю. Код метода полностью совпадает с кодом второго конструктора. Разница между ними в том, что при вызове конструктора создается объект класса, к ко­торому принадлежу вызываемый конструктор. При вызове мето­да (не конструктора) объект не создается. Следовательно, в отличие от вызова конструктора, при вызове метода AttachQ ранее созданный буфер в памяти ассоциируется с объектом класса CMemFile.

Для того чтобы «отсоединить» буфер от объекта, используется метод Detach():

BYTE* CMemFile::Detach() {

BYTE* lpBuffer = m_lpBuffer; m_lpBuffer = NULL; m_nFileSize = 0; m_nBufferSize = 0; m_nPosition = 0; return lpBuffer;

}

Фактически метод обнуляет поля объекта, после чего возвра­щает указатель на «отсоединенный» от объекта буфер. Этот указатель в дальнейшем может быть использован другими фраг­ментами программы.



Класс CStdioFile унаследован от класса CFile. Как следует из его названия, класс расширяет и реализует возможности потока вво­да-вывода, как они объявлены в файле stduio.h. Описание класса можно найти в файле afx.h:

class CStdioFile : public CFile {

DECLARE_DYNAMIС(CStdioFile) public:

// Constructors CStdioFile() ;

CStdioFile(FILE* pOpenStream) ;

CStdioFile(LPCTS^R IpszFileName, UINT nOpenFlags);

// Attributes

FILE* m_pStream; // stdio FILE

// m_hFile from base class is // _fileno(m_pStream)

// Operations

// reading and writing strings

virtual void WriteString(LPCTSTR lpsz);

virtual LPTSTR ReadString(LPTSTR lpsz, UINT nMax);

virtual BOOL ReadString(CString& rString);

// Implementation public:

virtual -CStdioFile() ; #ifdef _DEBUG

void Dump(CDumpContext& dc) const; #endif

virtual DWORD GetPosition() const; virtual BOOL Open(LPCTSTR IpszFileName,

UINT nOpenFlags,

CFileException* pError = NULL); virtual UINT Read(void* lpBuf, UINT nCount); virtual void Write(const void* lpBuf, UINT nCount); virtual LONG Seek(LONG lOff, UINT nFrom); virtual void Abort(); virtual void Flush(); virtual void Close();

// Unsupported APIs

virtual CFile* Duplicate() const;

virtual void LockRange(DWORD dwPos, DWORD dwCount); virtual void UnlockRange(DWORD dwPos, DWORD dwCount);

};



Исходный код деструктора объекта CFile() находится в файле filecore.cpp:

CFile::-CFile() {

if (m_hFile != (UINT)hFileNull && m_bCloseOnDelete) Close ();

}

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



Аргументами при вызове метода являются смещение начала раз­блокируемой области файла от начала файла и число разблоки­руемых байтов. Метод разрешает доступ другим процессам к ра­нее заблокированной части файла. Если при разблокировке про­исходит какая-то ошибка, то метод формирует исключение. Когла я проверял работу этого метода, то я, естественно, проверил, что произойдет, если я постараюсь разблокировать не всю заблокиро­ванную ранее область файла, а только ее часть. Так вот – если хотя бы один аргумент при вызове метода UnlockRange() не совпа­дает с с соответствующим аргументом вызванного ранее метода LockRange(), то вырабатывается исключение. Отсюда делаем вы­вод – разблокировать возможно только ранее заблокированную область файла, при этом разблокировать часть ранее заблокиро­ванной области невозможно.



Аргументами при вызове метода являются смещение начала блокируемой области от начала файла и число блокируемых бай­тов. Метод запрещает другим процессам доступ к определен­ной аргументами части файла посредством вызова глобаль­ной функции LockFileQ. При возникновении ошибки формирует­ся исключение.

Снять блокировку можно при помощи обращения к методу Un-lockRange():

void CFile::UnlockRange(DWORD dwPos, DWORD dwCount) {

ASSERT_VALID(this);

ASSERT(m_hFile != (UINT)hFileNull);

if (!::UnlockFile((HANDLE)m_hFile, dwPos, 0,

dwCount, 0))

CFileException::ThrowOsError ((LONG) ::GetLastError ()) ;

}



При необходимости программист может запретить другим процес­сам доступ к отдельным частям файла. Для этой цели использует­ся метод LockRange(), исходный код которого можно найти в фай­ле filecore.cpp:

void CFile::LockRange(DWORD dwPos, DWORD dwCount)

{

ASSERT_VALID(this);

ASSERT(m_hFile != (UINT)hFileNull);

if (!::LockFile((HANDLE)m_hFile, dwPos, 0, dwCount, 0)) CFileException: :ThrowOsError ((LONG) ::GetLastError ()) ;

}



03.02.2010

Аргументами при вызове метода служит указатель на буфер, содержимое которого должно быть записано в файл, и число запи­сываемых в файл байтов. Как и в методе ReadQ, если число запи­сываемых байтов равно НУЛЮ, то запись в файл не производится. После этого метод вызывает глобальную функцию WriteFileQ, при помощи которой записывает в файл указанной количество бай­тов. Если при записи в файл происходит ошибка, то формируется исключение.

На метод Write() накладываются те же ограничения, что и на метод Read(). Сцществует также метод WriteHuge(), исходный код которого находится в файле afx.ini:

_AFX_INLINE void CFile::WriteHuge(const void* lpBuffer,

DWORD dwCount) { Write(lpBuffer, (UINT)dwCount); }

И в этом случае разница между методами Write() и WriteHuge() будет заметна только на системах, на которых размер UlNT’a не равен размеру DWORD’a.

Еще один метод, осуществляющий запись в файл, – это метод Flush(). Его исходный код находится в файле filecore.cpp:

void CFile::Flush() {

ASSERT_VALID(this); if (m_hFile == (UINT)hFileNull) return;

if (!::FlushFileBuffers((HANDLE)m_hFile))

CFileException::ThrowOsError((LONG)::GetLastError ());

}

Здесь, очевидно, нет ничего сложного. Если у объекта нет ассо­циированного с ним файла, то метод, не производя никаких дейст­вий, просто возвращает управление. В том случае, если у объекта есть ассоциированный с ним файл, то метод вызывает глобаль­ную функцию FlushFileBuffers(), которая "сбрасывает" буфер фай­ла на устройство хранения файла.



03.02.2010

Разница между методами Read() и ReadHuge() будет заметна только на тех системах, на которых максимальное значение UlNT’a не равно максимальному значению, которое может быть записано при помощи DWORD’a.

Записывать данные в файл можно при помощи метода Write(), исходный код которого находится в файле fiulecore.cpp:

void CFile::Write(const void* lpBuf, UINT nCount) {

ASSERT_VALID(this);

ASSERT(m_hFile != (UINT)hFileNull);

if (nCount == 0)

return; // avoid Win32 "null-write" option

ASSERT(lpBuf != NULL);

ASSERT(AfxIsValidAddress(lpBuf, nCount, FALSE)); DWORD nWritten;

if (!::WriteFile((HANDLE)m_hFile, lpBuf, nCount, SnWritten, NULL))

CFileException::ThrowOsError((LONG) ::GetLastError() ,

m_strFileName);

// Win32s will not return an error // all the time (usually DISK_FULL) if (nWritten != nCount)

AfxThrowFileException (CFileException:idiskFull,

-1, m_strFileName);

}



03.02.2010

Аргументами при вызове файла являются указатель на буфер, в который будут считаны данные, а также количество байтов, кото­рые предполагается считать. Так как тип второго аргумента – UINT, то максимальное число байтов, которые мы можем считать, равно максимальному числу, которое можно разместить в переменной этого типа. Естественно, это зависит от системы, на которой запу­щена программа.

слово возвращаемого функцией ReadByte() значения равен NULL. Так как мы заведомо не можем указать, что считываем более байтов, чем можно указать при помощи UlNT’a, то коли­чество считанных байтов уложится в 32 разряда, и указатель на двойное слово, содержащее старшие 32 разряда числа считан­ных байтов, нам просто-напросто не нужен. Как и обычно, в слу­чае ошибки формируется исключение.

Для считывания информации можно воспользоваться также и ме­тодом ReadHuge(). Его исходный код находится в файле afx.ini:

_AFX_INLINE DWORD CFile::ReadHuge(void* lpBuffer,

DWORD dwCount) { return (DWORD)Read(lpBuffer, (UINT)dwCount); }