Windows环境下读写文件一般有下列有下面几种方式:C语言的文件操作函数,如fopen函数等,C++的I/O流库,Win32 API的文件操作函数,如CreateFile()、WriteFile()、ReadFile(),MFC的文件操作类,如CFile和CStdioFile等等。但是在大型的数据文件,上面的文件处理方法是不太适合的。对于大文件的操作一般是以内存映射文件来加以处理的。为此本人以读取著名的遥感图像文件格式pix文件来说明如何应用内存文件映射来设计一个通用的文件操作类。
一.内存文件映射的基本原理
首先要通过CreateFile()函数来创建或打开一个文件内核对象,这个对象标识了磁盘上将要用作内存映射文件的文件。在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后,只指定了映像文件的路径,映像的长度还没有指定。为了指定文件映射对象需要多大的物理存储空间还需要通过CreateFileMapping()函数来创建一个文件映射内核对象以告诉系统文件的尺寸以及访问文件的方式。在创建了文件映射对象后,还必须为文件数据保留一个地址空间区域,并把文件数据作为映射到该区域的物理存储器进行提交。由MapViewOfFile()函数负责通过系统的管理而将文件映射对象的全部或部分映射到进程地址空间。此时,对内存映射文件的使用和处理同通常加载到内存中的文件数据的处理方式基本一样,在完成了对内存映射文件的使用时,还要通过一系列的操作完成对其的清除和使用过资源的释放。这部分相对比较简单,可以通过UnmapViewOfFile()完成从进程的地址空间撤消文件数据的映像、通过CloseHandle()关闭前面创建的文件映射对象和文件对象。
二.应用内存文件映射的基本思路
由上可知,内存文件映射实际上开辟一段内存空间和文件磁盘空间进行映射,因此操作文件的基本思路就是首先获取这段内存的基地址,然后获取信息结构的偏移量,比如你要获取文件头的信息,实际上就是先取得基地址,然后那个偏移量就是文件头的大小,这个和移动文件指针和文件指针的偏移量原理上是差不多的。
下面我以操作pix文件为例,具体代码如下:
/**//*
* 文件名: Pix.h
* 作者 : 朱金灿
* 日期 : 08.05.15
* 文件描述:pix文件操作类声明
*/
class __declspec(dllexport) CPix : public CObject
...{
public:
CPix(void);
~CPix(void);
// operation 打开操作
public:
// 打开pix文件,获取文件头信息
BOOL OpenForFileHead(char* pszFileName);
// 打开pix文件,获取通道信息
BOOL OpenForImgHeader(char* pszFileName);
public:
// 释放内存
BOOL Close();
private:
// 获取所映射的那段内存的起始地址
BOOL GetMapViewAddress(char* pszFileName);
private:
// pix文件头,限于版权原因,此结构体不公开
FileHeader_t m_FileHeader;
// 通道信息头指针,限于版权原因,此结构体不公开
ImageHeader_t *m_pImageHeader;
// 内存映射文件的起始位置
LPBYTE m_pbFile;
HANDLE m_hFileHandle;
HANDLE m_hFileMapping;
};
/**//*
* 文件名: Pix.cpp
* 作者 : 朱金灿
* 日期: 08.05.15
* 文件描述:pix文件操作类实现
*/
CPix::CPix(void)
...{
m_pImageHeader = NULL;
m_hFileHandle = NULL;
m_hFileMapping = NULL;
m_pbFile = NULL;
m_pImageHeader = NULL;
}
CPix::~CPix(void)
...{
Close();
}
/**//*================================================================
*
* 函数名:GetMapViewAddress
*
* 参 数:
*
* [IN] char* pszFileName ---- 输入文件名
*
* 功能描述:
*
* 进行内存文件映射,获取所映射内存基地址
*
* 返回值:成功TRUE,失败FALSE
*
* 抛出异常:
*
* 作 者:朱金灿 08.05.15.
*
================================================================*/
BOOL CPix::GetMapViewAddress(char* pszFileName)
...{
m_hFileHandle = ::CreateFile(pszFileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if( INVALID_HANDLE_VALUE == m_hFileHandle )
...{
LPVOID lpMsgBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
AfxMessageBox((LPCTSTR)lpMsgBuf);
LocalFree(lpMsgBuf);
return FALSE;
}
DWORD dwError = 0;
LARGE_INTEGER li;
li.LowPart = GetFileSize( m_hFileHandle, (unsigned long*)(&li.HighPart));
if (li.LowPart == INVALID_FILE_SIZE && (dwError = GetLastError()) != NO_ERROR )
...{
LPVOID lpMsgBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
AfxMessageBox((LPCTSTR)lpMsgBuf);
LocalFree(lpMsgBuf);
return FALSE;
}
m_hFileMapping = CreateFileMapping( m_hFileHandle,
NULL,
PAGE_READWRITE,li.HighPart, li.LowPart,NULL);
if( INVALID_HANDLE_VALUE == m_hFileMapping )
...{
CloseHandle( m_hFileHandle );
LPVOID lpMsgBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
AfxMessageBox((LPCTSTR)lpMsgBuf);
LocalFree(lpMsgBuf);
return FALSE;
}
__int64 qwFileOffset = 0;
m_pbFile = static_cast<LPBYTE>(MapViewOfFile(m_hFileMapping,
FILE_MAP_ALL_ACCESS,(DWORD)(qwFileOffset>>32),
(DWORD)(qwFileOffset&0xFFFFFFFF),0));
if (NULL==m_pbFile)
...{
LPVOID lpMsgBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
AfxMessageBox((LPCTSTR)lpMsgBuf);
LocalFree(lpMsgBuf);
return FALSE;
}
return TRUE;
}
/**//*================================================================
*
* 函数名:OpenForFileHead
*
* 参 数:
*
* [IN] char* pszFileName ---- 输入文件名
*
* 功能描述:
*
* 打开文件,获取文件头信息
*
* 返回值:成功TRUE,失败FALSE
*
* 抛出异常:
*
* 作 者:朱金灿 08.05.15.
*
================================================================*/
BOOL CPix::OpenForFileHead(char* pszFileName)
...{
if (!GetMapViewAddress(pszFileName))
...{
return FALSE;
}
memcpy(&m_FileHeader,m_pbFile,sizeof(FileHeader_t));
return TRUE;
}
/**//*================================================================
*
* 函数名:OpenForImgHeader
*
* 参 数:
*
* [IN] char* pszFileName ---- 输入文件名
*
* 功能描述:
*
* 打开文件,获取各个通道信息
*
* 返回值:成功TRUE,失败FALSE
*
* 抛出异常:
*
* 作 者:朱金灿 08.05.15.
*
================================================================*/
BOOL CPix::OpenForImgHeader(char* pszFileName)
...{
if(!OpenForFileHead(pszFileName))
return FALSE;
int nBandNum=atoi(m_FileHeader.Channels);
m_pImageHeader = new ImageHeader_t[nBandNum];
if (NULL==m_pImageHeader)
...{
AfxMessageBox("开辟内存失败");
return FALSE;
}
for (int i =0;i<nBandNum;i++)
...{
memcpy(&m_pImageHeader[i],m_pbFile+sizeof(FileHeader_t)+i*sizeof(ImageHeader_t),sizeof(ImageHeader_t));
}
return TRUE;
}
/**//*================================================================
*
* 函数名:Close
*
* 参 数:
*
*
*
* 功能描述:
*
* 撤销文件数据映像,释放相关内存
*
* 返回值:成功TRUE,失败FALSE
*
* 抛出异常:
*
* 作 者:朱金灿 08.05.15.
*
================================================================*/
BOOL CPix::Close()
...{
delete m_pImageHeader;
UnmapViewOfFile(m_pbFile);
if (NULL!=m_hFileHandle)
...{
if(!CloseHandle(m_hFileHandle))
return FALSE;
}
if (NULL!=m_hFileMapping)
...{
if(CloseHandle(m_hFileHandle))
return FALSE;
}
return TRUE;
}
总结:
1. 假如要读取其它文件格式,首先肯定是要了解该文件格式,定义相关结构体,然后进行内存文件映射,获取基地址。要读取哪部分信息,实际上就是计算其离基地址的偏移量,然后使用memecpy函数。
2. 因为要读取文件不同部分的信息,所以最好设计为多个open函数以便获取文件不同部分的信息。
3. 必须有close函数来撤销文件数据映像,释放相关内存。
参考文献:
《VC++中使用内存映射文件处理大文件》作者:中国电波传播研究所 郎锐