

Программирование на языке 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);
}
читать отзывы (0)
Читатель, взгляните, пожалуйста, на исходный код метода 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
}
Естественно, что сериализация неразрывно связана с файлами. Но для осуществления сериализации объекты класса 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, не поддерживаются. Обращение к ним немедленно приводит к выработке исключения.
Методу 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() размер буфера динамически увеличивается. В противном случае длина файла делается равной значению, переданному в качестве аргумента. Обратите внимание, уважаемый читатель, что никакого перераспределения памяти при этом не происходит. Указатель файла в том случае, если его значение оказывается превышающим новый размер файла, устанавливается на конец файла новой длины. Все легко и просто!
При этом указатель, переданный в качестве аргумента, может не совпадать с возвращаемым указателем, так как в том случае, если просто увеличить длину блока не удается, метод просто выделяет новый буфер в другом месте, копирует туда содержимое исходного буфера, после чего исходный буфер освобождается.
Вернемся к методу 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 ()) ;
}
Аргументами при вызове метода служит указатель на буфер, содержимое которого должно быть записано в файл, и число записываемых в файл байтов. Как и в методе 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(), которая "сбрасывает" буфер файла на устройство хранения файла.
Разница между методами 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);
}
Аргументами при вызове файла являются указатель на буфер, в который будут считаны данные, а также количество байтов, которые предполагается считать. Так как тип второго аргумента – 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); }
