7.5不使用串行化的文档视结构程序
在MFC例子中有一个DIBLOOK(见SAMPLES\MFC\GENERAL\DIBLOOK目录),它是一个位图显示程序,演示了在不使用串行化的情况下实现文档的输入输出功能。有关位图、调色板的使用在第十一章有详细介绍,这里只讨论与文档视结构相关的内容。我们先看DIBLOOK的文档声明和定义。
清单7-16 CDibDoc的类声明文件
// dibdoc.h : interface of the CDibDoc class
#include "dibapi.h"
class CDibDoc : public CDocument
{
protected: // create from serialization only
CDibDoc();
DECLARE_DYNCREATE(CDibDoc)
// Attributes
public:
HDIB GetHDIB() const
{ return m_hDIB; }
CPalette* GetDocPalette() const
{ return m_palDIB; }
CSize GetDocSize() const
{ return m_sizeDoc; }
// Operations
public:
void ReplaceHDIB(HDIB hDIB);
void InitDIBData();
// Implementation
protected:
virtual ~CDibDoc();
virtual BOOL OnSaveDocument(LPCTSTR lpszPathName);
virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
protected:
HDIB m_hDIB;
CPalette* m_palDIB;
CSize m_sizeDoc;
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
virtual BOOL OnNewDocument();
// Generated message map functions
protected:
//{{AFX_MSG(CDibDoc)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
/////////////////////////////////////////////////////////////////////////////
清单7-17 CDibDoc类的实现文件
// dibdoc.cpp : implementation of the CDibDoc class
#include "stdafx.h"
#include "diblook.h"
#include <limits.h>
#include "dibdoc.h"
#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CDibDoc
IMPLEMENT_DYNCREATE(CDibDoc, CDocument)
BEGIN_MESSAGE_MAP(CDibDoc, CDocument)
//{{AFX_MSG_MAP(CDibDoc)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CDibDoc construction/destruction
CDibDoc::CDibDoc()
{
//初始化文档的DIB句柄和调色板
m_hDIB = NULL;
m_palDIB = NULL;
m_sizeDoc = CSize(1,1); // dummy value to make CScrollView happy
}
CDibDoc::~CDibDoc()
{
if (m_hDIB != NULL)
{
::GlobalFree((HGLOBAL) m_hDIB);
}
if (m_palDIB != NULL)
{
delete m_palDIB;
}
}
BOOL CDibDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
return TRUE;
}
void CDibDoc::InitDIBData()
{
if (m_palDIB != NULL)
{
delete m_palDIB;
m_palDIB = NULL;
}
if (m_hDIB == NULL)
{
return;
}
// Set up document size
LPSTR lpDIB = (LPSTR) ::GlobalLock((HGLOBAL) m_hDIB);
if (::DIBWidth(lpDIB) > INT_MAX ||::DIBHeight(lpDIB) > INT_MAX)
{
::GlobalUnlock((HGLOBAL) m_hDIB);
::GlobalFree((HGLOBAL) m_hDIB);
m_hDIB = NULL;
CString strMsg;
strMsg.LoadString(IDS_DIB_TOO_BIG);
MessageBox(NULL, strMsg, NULL, MB_ICONINFORMATION | MB_OK);
return;
}
m_sizeDoc = CSize((int) ::DIBWidth(lpDIB), (int) ::DIBHeight(lpDIB));
::GlobalUnlock((HGLOBAL) m_hDIB);
// Create copy of palette
m_palDIB = new CPalette;
if (m_palDIB == NULL)
{
// we must be really low on memory
::GlobalFree((HGLOBAL) m_hDIB);
m_hDIB = NULL;
return;
}
if (::CreateDIBPalette(m_hDIB, m_palDIB) == NULL)
{
// DIB may not have a palette
delete m_palDIB;
m_palDIB = NULL;
return;
}
}
BOOL CDibDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
CFile file;
CFileException fe;
if (!file.Open(lpszPathName, CFile::modeRead | CFile::shareDenyWrite, &fe))
{
ReportSaveLoadException(lpszPathName, &fe,
FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
return FALSE;
}
DeleteContents();
BeginWaitCursor();
// replace calls to Serialize with ReadDIBFile function
TRY
{
m_hDIB = ::ReadDIBFile(file);
}
CATCH (CFileException, eLoad)
{
file.Abort(); // will not throw an exception
EndWaitCursor();
ReportSaveLoadException(lpszPathName, eLoad,
FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
m_hDIB = NULL;
return FALSE;
}
END_CATCH
InitDIBData();
EndWaitCursor();
if (m_hDIB == NULL)
{
// may not be DIB format
CString strMsg;
strMsg.LoadString(IDS_CANNOT_LOAD_DIB);
MessageBox(NULL, strMsg, NULL, MB_ICONINFORMATION | MB_OK);
return FALSE;
}
SetPathName(lpszPathName);
SetModifiedFlag(FALSE); // start off with unmodified
return TRUE;
}
BOOL CDibDoc::OnSaveDocument(LPCTSTR lpszPathName)
{
CFile file;
CFileException fe;
if (!file.Open(lpszPathName, CFile::modeCreate |
CFile::modeReadWrite | CFile::shareExclusive, &fe))
{
ReportSaveLoadException(lpszPathName, &fe,
TRUE, AFX_IDP_INVALID_FILENAME);
return FALSE;
}
// replace calls to Serialize with SaveDIB function
BOOL bSuccess = FALSE;
TRY
{
BeginWaitCursor();
bSuccess = ::SaveDIB(m_hDIB, file);
file.Close();
}
CATCH (CException, eSave)
{
file.Abort(); // will not throw an exception
EndWaitCursor();
ReportSaveLoadException(lpszPathName, eSave,
TRUE, AFX_IDP_FAILED_TO_SAVE_DOC);
return FALSE;
}
END_CATCH
EndWaitCursor();
SetModifiedFlag(FALSE); // back to unmodified
if (!bSuccess)
{
// may be other-style DIB (load supported but not save)
// or other problem in SaveDIB
CString strMsg;
strMsg.LoadString(IDS_CANNOT_SAVE_DIB);
MessageBox(NULL, strMsg, NULL, MB_ICONINFORMATION | MB_OK);
}
return bSuccess;
}
void CDibDoc::ReplaceHDIB(HDIB hDIB)
{
if (m_hDIB != NULL)
{
::GlobalFree((HGLOBAL) m_hDIB);
}
m_hDIB = hDIB;
}
/////////////////////////////////////////////////////////////////////////////
// CDibDoc diagnostics
#ifdef _DEBUG
void CDibDoc::AssertValid() const
{
CDocument::AssertValid();
}
void CDibDoc::Dump(CDumpContext& dc) const
{
CDocument::Dump(dc);
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////////
// CDibDoc commands
DIBLOOK读入和保存标准的Windows设备无关位图。在内存中,位图以一个HDIB句柄表示。DIBLOOK没有重 载CDocument::Serialize()函数,而是重载了CDocument::OnOpenDocument和CDocument::OnSaveDocument函数。这两个函数使用框架传过来得文件路径名pszPathName,打开一个文件对象,读入或保存DIB数据。这就是说,DIBLOOK把本来在Serialize()中完成的对象保存和载入两个任务分别交与OnSaveDocument()函数和OnOpenDocument()函数去完成。如果读者希望绕过文档的串行化提供文档数据的保存和载入,也只需要重载这两个成员函数:OnOpenDocument()和OnSaveDocument(),通过文件路径参数打开文件,从中读取应用程序数据或向文件里写入应用程序数据。
在OnOpenDocument()中,还必需自己调用DeleteContents()清除原来文档的数据,并调用SetModifiedFlag(FALSE)。在OnSaveDocument()中也要调用SetModifiedFlag(FALSE)将文档修改标志改为FALSE。
在OnOpenDocument()函数开始处(见清单7.18),有一些地方需要解释一下。
清单7.18 OnOpenDocument()函数
BOOL CDibDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
CFile file;
CFileException fe;
if (!file.Open(lpszPathName, CFile::modeRead | CFile::shareDenyWrite, &fe))
{
ReportSaveLoadException(lpszPathName, &fe,
FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
return FALSE;
}
......
}
7.5.1 文件操作
文件读写
OnOpenDocument首先声明一个CFile类的对象。CFile是MFC提供的一个类,它提供了访问二进制文件的接口。可以使用带参数的CFile构造函数创建对象,在构造函数中指定了文件名和打开文件的模式,这样在对象创建的同时也就打开了这个文件;也可以象本例那样使用不带参数的CFile构造函数构造一个CFile对象,然后调用CFile::Open()打开一个文件。
BOOL CFile::Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL );
CFile::Open成员函数带三个参数,第一个参数指定了要打开的文件的完整路径名,如“c:\hello\hello.cpp”;第二个参数指定打开文件的模式。
常见的文件打开模式有以下几种:
CFile::modeCreate:创建一个新文件,如果该文件已经存在,则把该文件长度置为零
CFile::modeNoTruncate:与modeCreate一起使用。告诉CFile,如果要创建的文件已经存在,则不再将文件长度设置为零。这对于系统设置文件、日志文件等特别有用,因为第一次启动系统时,这些文件通常不存在,需要创建,而下次则只需要修改文件。
CFile::modeRead:打开文件用于读
CFile::modeWrite:打开文件用于写
CFile::modeReadWrite:打开文件且对文件可读可写
可以使用比特位或“|”对上述操作进行组合。比如,要打开文件写,可以用以下方式打开:
CFile file;
file.Open(“c:\\readme.txt”,CFile::modeCreate|CFile::modeWrite);
读文件
既然已经打开了文件,就可以对文件进行读写操作了。要读取文件内容到内存,可以调用CFile::Read()。CFile::Read()函数原型如下:
UINT Read( void* lpBuf, UINT nCount );
Read函数包含两个参数,第一个参数是一个缓冲区指针,该缓冲区用于存放从文件读进来的内容。第二个参数是要读取的字节数。Read函数返回实际读入的字节数。例如:
CFile file;
char buf[100];
int nBytesRead;
nBytesRead=file.Read(buf,100);
写文件
写文件与读文件操作方式类似,通过调用CFile::Write函数来完成。
void Write( const void* lpBuf, UINT nCount );
Write函数第一个参数是指向要写入到文件中的缓冲区的指针,第二个参数是要写入到文件中的字节数。例如:
CFile file;
CString str(“This is a string.”);
file.Write(str,str.GetLength());
关闭文件
在完成文件读写操作后,要调用CFile::Close成员函数及时将文件关闭。
CFile file;
//一些读写操作.....
file.Close();
7.5.2异常处理
在打开和保存文件时,我们并未作传统的错误检查,而是采用一种异常机制来处理错误。
异常处理为异常事件提供了结构化、规范化的服务。它一般是指处理错误状态。
我们先回顾一下传统的错误处理方式。传统的错误处理方式通常有两种:
1.返回错误值
2.使用goto,setjmp/longjmp协助报告错误
对于第一种技术,要求程序员记住各种错误代码,并且加入大量的检查情况。由于大多数错误是很少会发生,这样处理的结果是代码冗余性很大,效率不高。
第二种技术不但使程序可读性降低,更严重的是,使得函数里的对象不能释放、删除。比如:
void SomeOperation()
{
CMyClass obj1;
if(error)goto errHandler;
...
}
...
errHandler:
//handler error
在上面的程序片断中,由于goto跳转,无法调用obj的析构函数在退出SomeOperation()函数时释放其所占的内存,造成内存泄漏。
而且,以上两种错误处理方法都无法考虑到不可预见的错误。C++引入异常处理这一重要概念很好的解决了上述问题。异常处理在处理异常事件时会自动调用已经超出范围的局部对象的析构函数,这样就可以防止内存泄漏。
下面是OnSaveDocument()函数中的异常处理代码:
CFile file;
CFileException fe;
if (!file.Open(lpszPathName, CFile::modeCreate |
CFile::modeReadWrite | CFile::shareExclusive, &fe))
{
ReportSaveLoadException(lpszPathName, &fe,
TRUE, AFX_IDP_INVALID_FILENAME);
return FALSE;
}
// replace calls to Serialize with SaveDIB function
BOOL bSuccess = FALSE;
TRY
{
BeginWaitCursor();
bSuccess = ::SaveDIB(m_hDIB, file);
file.Close();
}
CATCH (CException, eSave)
{
file.Abort(); // will not throw an exception
EndWaitCursor();
ReportSaveLoadException(lpszPathName, eSave,
TRUE, AFX_IDP_FAILED_TO_SAVE_DOC);
return FALSE;
}
END_CATCH
异常处理由一个TRY-CATCH-END_CATCH结构组成。TRY{ }语句块中包含可能发生错误的代码,可以理解为“试运行”这一语句块。CATCH{} END_CATCH子块包含了错误处理代码。如果发生错误,就转入CATCH{} END_CATCH子块执行。该子块可以根据CATCH中的参数分析产生错误的原因,报告错误或做出相应处理。
CATCH()包含两个参数,第一个参数是异常类。MFC的异常有下列几种:
MFC异常类
处理的异常
CMemoryException 内存异常 CNotSupportedException 设备不支持 CArchiveException 档案(archive)异常 CFileException 文件异常 OsErrorException 把DOS错误转换为异常 ErrnoToException 把错误号转换为异常 CResourceException 资源异常 COleException OLE异常
用户还可以从CException类派生出自己的异常类,用以处理特定类型的错误。
CATCH的第二个参数是产生的异常的名字。
引起异常的原因存放在异常的数据成员m_cause中。OnSaveDocument()只是简单的处理文件保存错误,并没有指出引起错误的原因。我们可以对它进行一些修改,使它能够报告引起错误的原因。
...
TRY
{
...
}
CATCH(CFileException,e)
{
switch(e->m_cause)
{
case CFileException::accessDenied:
AfxMessageBox(“Access denied!”);
break;
case CFileException::badPath:
AfxMessageBox(“Invalid path name”);
break;
case CFileException::diskFull:
AfxMessageBox(“Disk is full”);
break;
case CFileException::hardIO:
AfxMessageBox(“Hardware error”);
break;
}
}
END_CATCH
...
}
用户也可以不必直接处理异常,而通过调用THROW_LAST(),把异常交给上一级TRY-CATCH结构来处理。其实,在DIBLOOK中,就是这么做的,请看OnSaveDocument()函数调用的SaveDIB函数的片段:
BOOL WINAPI SaveDIB(HDIB hDib, CFile& file)
{
//...
TRY
{
//...
}
CATCH (CFileException, e)
{
//...
::GlobalUnlock((HGLOBAL) hDib);
THROW_LAST();
}
END_CATCH
//...
}
在SaveDIB中,并没有直接处理异常,而是通过调用THROW_LAST(),把异常交由调用它的上一级函数OnSaveDocument()去处理。
异常并不仅仅用于错误处理。比如,在文本编辑器的CEditorDoc::Serialize()成员函数中,我们就利用读取文件引起的异常判断是否已经到了文件尾部。读者请回顾一下该函数。
异常处理给程序的错误处理带来许多便利。但是,必需意识到异常处理并不是万能的。在加入异常处理后,程序员仍然有许多工作要做。更不可以滥用异常,因为异常会带来一些开销。应用程序应当尽可能排除可能出现的错误。