windows内存映射学习及帮助类实现

本文通过创建文件内存映射类,学习windows内存映射相关知识;创建内存映射文件后,可以按照内存操作方式操作文件;支持32位程序处理超过4G大小的文件。
感谢http://blog.csdn.net/csafu/article/details/8602142, 
引用部分内容"文件映射问题
内存映射文件并不是简单的文件I/O操作,实际用到了Windows的核心编程技术--内存管理。
所以,如果想对内存映射文件有更深刻的认识,必须对Windows操作系统的内存管理机制有清楚的认识,
内存管理的相关知识非常复杂,超出了本文的讨论范畴,在此就不再赘述,感兴趣的读者可以参阅其他相关书籍。
下面给出使用内存映射文件的一般方法:
 
首先要通过CreateFile()函数来创建或打开一个文件内核对象,这个对象标识了磁盘上将要用作内存映射文件的文件。
在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后,只指定了映像文件的路径,映像的长度还没有指定。
为了指定文件映射对象需要多大的物理存储空间还需要通过CreateFileMapping()函数来创建一个文件映射内核对象以告诉
系统文件的尺寸以及访问文件的方式。在创建了文件映射对象后,还必须为文件数据保留一个地址空间区域,并把文件数据
作为映射到该区域的物理存储器进行提交。由MapViewOfFile()函数负责通过系统的管理而将文件映射对象的全部或部分
映射到进程地址空间。此时,对内存映射文件的使用和处理同通常加载到内存中的文件数据的处理方式基本一样,
在完成了对内存映射文件的使用时,还要通过一系列的操作完成对其的清除和使用过资源的释放。
这部分相对比较简单,可以通过UnmapViewOfFile()完成从进程的地址空间撤消文件数据的映像、
通过CloseHandle()关闭前面创建的文件映射对象和文件对象。"
具体实现:
#ifndef MEMFILEMAPHELPER_H
#define MEMFILEMAPHELPER_H
//文件内存映射类,创建内存映射文件;创建后,可以按照内存操作方式操作文件
/*
文件映射问题 
内存映射文件并不是简单的文件I/O操作,实际用到了Windows的核心编程技术--内存管理。
所以,如果想对内存映射文件有更深刻的认识,必须对Windows操作系统的内存管理机制有清楚的认识,
内存管理的相关知识非常复杂,超出了本文的讨论范畴,在此就不再赘述,感兴趣的读者可以参阅其他相关书籍。
下面给出使用内存映射文件的一般方法: 

首先要通过CreateFile()函数来创建或打开一个文件内核对象,这个对象标识了磁盘上将要用作内存映射文件的文件。
在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后,只指定了映像文件的路径,映像的长度还没有指定。
为了指定文件映射对象需要多大的物理存储空间还需要通过CreateFileMapping()函数来创建一个文件映射内核对象以告诉
系统文件的尺寸以及访问文件的方式。在创建了文件映射对象后,还必须为文件数据保留一个地址空间区域,并把文件数据
作为映射到该区域的物理存储器进行提交。由MapViewOfFile()函数负责通过系统的管理而将文件映射对象的全部或部分
映射到进程地址空间。此时,对内存映射文件的使用和处理同通常加载到内存中的文件数据的处理方式基本一样,
在完成了对内存映射文件的使用时,还要通过一系列的操作完成对其的清除和使用过资源的释放。
这部分相对比较简单,可以通过UnmapViewOfFile()完成从进程的地址空间撤消文件数据的映像、
通过CloseHandle()关闭前面创建的文件映射对象和文件对象。
*/ #include <Windows.h> #include <WinBase.h> #include <string> #include <iostream> using namespace std; //typedef unsigned char byte; //typedef unsigned long DWORD; //typedef void* HANDLE; class CMemFileMapHelper{ public: enum MemFileType{SEQ_READ=0,SEQ_WRITE,RANDOM_READ,RANDOM_WRITE,SEQ_READ_WRITE,RANDOM_READ_WRITE}; protected: HANDLE m_FileHandler;//原始文件句柄 HANDLE m_FileMemMapHandler;//内存映射文件句柄 unsigned __int64 m_FileSize; unsigned __int64 m_CurOffset; size_t m_FileChunkSize;//文件分割块大小,当文件太大时,将大文件分割成多个文件块 size_t m_CurChunkSize;//当前文件块大小 DWORD m_MapChunkSize;//当前操作系统的分配粒度,内存映射大小 byte* m_BaseAddr;//内存映射文件首地址,m_BaseAddr+n =>访问当前文件块第n字节处的位置 bool m_FileMapped;//是否映射 DWORD m_ViewAccess; public: CMemFileMapHelper(){ m_FileMapped = false; m_FileHandler = NULL; m_FileMemMapHandler = NULL; m_BaseAddr = NULL; m_FileSize = 0; m_FileChunkSize = 0; m_CurChunkSize = 0; m_MapChunkSize = 0; m_CurOffset = 0; m_ViewAccess=0; } ~CMemFileMapHelper(){ if(m_FileMapped) ReleaseFileMapping(); } void ShowError(char* errmsg){ cout<<errmsg<<endl; } //将文件加载到内存映射 bool BuildFileMapping(const char* fileName,MemFileType type = SEQ_READ, unsigned __int64 view_size=0){ DWORD err_code; char err_msg[100]; string shared_name = GetLastFileName(fileName); //存取模式//GENERIC_READ | GENERIC_WRITE DWORD access_mode; //共享模式// FILE_SHARE_READ | FILE_SHARE_WRITE DWORD share_mode; /*文件属性: FILE_FLAG_WRITE_THROUGH 操作系统不得推迟对文件的写操作 FILE_FLAG_OVERLAPPED 允许对文件进行重叠操作 FILE_FLAG_NO_BUFFERING 禁止对文件进行缓冲处理。文件只能写入磁盘卷的扇区块 FILE_FLAG_RANDOM_ACCESS 针对随机访问对文件缓冲进行优化 FILE_FLAG_SEQUENTIAL_SCAN 针对连续访问对文件缓冲进行优化 FILE_FLAG_DELETE_ON_CLOSE 关闭了上一次打开的句柄后,将文件删除。特别适合临时文件 */ DWORD mmf_flag; /*打开文件方式: CREATE_NEW 创建文件;如文件存在则会出错 CREATE_ALWAYS 创建文件,会改写前一个文件 OPEN_EXISTING 文件必须已经存在。由设备提出要求 OPEN_ALWAYS 如文件不存在则创建它 TRUNCATE_EXISTING 讲现有文件缩短为零长度*/ DWORD file_create_mode; /*页面内存访问方式: PAGE_EXECUTE 可执行 PAGE_EXECUTE_READ 可读,可执行 PAGE_EXECUTE_READWRITE 可读,可写,可执行 PAGE_EXECUTE_WRITECOPY 可读,可写,可执行,以Read-on-write和copy-on-write方式共享 PAGE_NOACCESS 不可访问 PAGE_READONLY 只读 并且hFile对应的文件必须以GENERIC_READ形式打开。 PAGE_READWRITE 可读,可写 并且hFile对应的文件必须以GENERIC_READ 和 GENERIC_WRITE形式打开。 PAGE_WRITECOPY copy-on-write保护机制 并且hFile对应的文件必须以GENERIC_READ 和 GENERIC_WRITE形式打开。 PAGE_GUARD 保护,如果访问则异常(不能单独使用) PAGE_NOCACHE 不进行CPU缓存(不能单独使用) PAGE_WRITECOMBINE write-combined优化(不能单独使用) */ DWORD page_access_mode; /*虚拟页面视图访问方式 FILE_MAP_WRITE:一个可读写属性的文件视图被创建,保护模式为PAGE_READWRITE FILE_MAP_READ :一个只读属性的文件视图被创建,保护模式为PAGE_READWRITE 或 PAGE_READONLY FILE_MAP_ALL_ACCESS:与FILE_MAP_WRITE模式相同 FILE_MAP_COPY:保护模式为PAGE_WRITECOPY时,得到一个视图文件,当你对视图文件写操作时,页面自动交换,并且你所做的修改不会损坏原始数据资料。 */ //文件映射为一个映像,映像的大小=> size_t view_size switch(type){ case SEQ_READ: { access_mode = GENERIC_READ; share_mode = FILE_SHARE_READ; mmf_flag = FILE_FLAG_SEQUENTIAL_SCAN; file_create_mode = OPEN_EXISTING; page_access_mode = PAGE_READONLY; m_ViewAccess = FILE_MAP_READ; view_size = 0;//将整个文件映射为一个映像 } break; case RANDOM_READ: { access_mode = GENERIC_READ; share_mode = FILE_SHARE_READ; mmf_flag = FILE_FLAG_RANDOM_ACCESS; file_create_mode = OPEN_EXISTING; page_access_mode = PAGE_READONLY; m_ViewAccess = FILE_MAP_READ; view_size = 0; } break; case SEQ_WRITE: { access_mode = GENERIC_READ | GENERIC_WRITE; share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE; mmf_flag = FILE_FLAG_WRITE_THROUGH;//FILE_FLAG_SEQUENTIAL_SCAN file_create_mode = CREATE_NEW; page_access_mode = PAGE_READWRITE; m_ViewAccess = FILE_MAP_WRITE; } break; case RANDOM_WRITE: { access_mode = GENERIC_READ | GENERIC_WRITE; share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE; mmf_flag = FILE_FLAG_RANDOM_ACCESS; file_create_mode = CREATE_NEW; page_access_mode = PAGE_READWRITE; m_ViewAccess = FILE_MAP_WRITE; } break; case SEQ_READ_WRITE: { access_mode = GENERIC_READ | GENERIC_WRITE; share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE; mmf_flag = FILE_FLAG_SEQUENTIAL_SCAN; file_create_mode = OPEN_ALWAYS; page_access_mode = PAGE_READWRITE; m_ViewAccess = FILE_MAP_READ|FILE_MAP_WRITE;//FILE_MAP_ALL_ACCESS } break; case RANDOM_READ_WRITE: { access_mode = GENERIC_READ | GENERIC_WRITE; share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE; mmf_flag = FILE_FLAG_RANDOM_ACCESS; file_create_mode = OPEN_ALWAYS; page_access_mode = PAGE_READWRITE; m_ViewAccess = FILE_MAP_READ|FILE_MAP_WRITE;//FILE_MAP_ALL_ACCESS } break; default: return false; } //1.创建文件 /* HANDLE CreateFile(   LPCTSTR lpFileName, //指向文件名的指针  DWORD dwDesiredAccess, //访问模式(写/读) DWORD dwShareMode, //共享模式 LPSECURITY_ATTRIBUTES lpSecurityAttributes, //指向安全属性的指针 DWORD dwCreationDisposition, //如何创建 DWORD dwFlagsAndAttributes, //文件属性 HANDLE hTemplateFile //用于复制文件句柄 ); 返回值   如执行成功,则返回文件句柄。 INVALID_HANDLE_VALUE表示出错,会设置GetLastError。 即使函数成功,但若文件存在,且指定了CREATE_ALWAYS 或 OPEN_ALWAYS,GetLastError也会设为ERROR_ALREADY_EXISTS */ m_FileHandler = CreateFile(fileName,access_mode,share_mode,NULL,file_create_mode,mmf_flag,NULL); err_code = GetLastError(); switch(err_code){ case INVALID_HANDLE_VALUE: sprintf(err_msg,"文件打开失败"); ShowError(err_msg); return false; break; case ERROR_ALREADY_EXISTS: if(m_FileHandler == NULL &&(type == SEQ_WRITE || type == RANDOM_WRITE) ){ sprintf(err_msg,"文件已存在"); ShowError(err_msg); return false; } break; } //2.创建文件映射 /* HANDLE CreateFileMapping( HANDLE hFile, //物理文件句柄 LPSECURITY_ATTRIBUTES lpAttributes, //安全设置, 一般NULL DWORD flProtect, //保护设置 DWORD dwMaximumSizeHigh, //高位文件大小 DWORD dwMaximumSizeLow, //低位文件大小 LPCTSTR lpName //共享内存名称 ); 调用CreateFileMapping的时候GetLastError的对应错误 ERROR_FILE_INVALID 如果企图创建一个零长度的文件映射, 应有此报 ERROR_INVALID_HANDLE 如果发现你的命名内存空间和现有的内存映射, 互斥量, 信号量, 临界区同名就麻烦了 ERROR_ALREADY_EXISTS 表示内存空间命名已经存在 */ //2.1获取文件大小 DWORD fileSizeLow = 0,fileSizeHigh = 0; if(type == SEQ_READ || type == RANDOM_READ || type == SEQ_READ_WRITE || type == RANDOM_READ_WRITE){ fileSizeLow = GetFileSize(m_FileHandler,&fileSizeHigh); //文件长度 m_FileSize = ((unsigned __int64)fileSizeHigh << 32) + (unsigned __int64)fileSizeLow; } else { m_FileSize = view_size;//待创建的文件的大小 fileSizeHigh = view_size >> 32; fileSizeLow = view_size & 0xFFFFFFFF; } //2.2创建映射文件 m_FileMemMapHandler = CreateFileMapping(m_FileHandler,NULL,page_access_mode,fileSizeHigh,fileSizeLow,shared_name.c_str()); err_code = GetLastError();//错误类型定义在WinError.h if(m_FileMemMapHandler == NULL){ sprintf(err_msg,"创建映射文件错误"); CloseHandle(m_FileHandler); ShowError(err_msg); return false; } switch(err_code){ case ERROR_FILE_INVALID: { sprintf(err_msg,"企图创建一个零长度的文件映射错误"); CloseHandle(m_FileHandler); ShowError(err_msg); return false; } break; case ERROR_INVALID_HANDLE: { sprintf(err_msg,"你的命名内存空间和现有的内存映射, 互斥量, 信号量, 临界区同名"); CloseHandle(m_FileHandler); ShowError(err_msg); return false; } break; case ERROR_ALREADY_EXISTS: { sprintf(err_msg,"内存空间命名已经存在"); CloseHandle(m_FileHandler); ShowError(err_msg); return false; } break; } //3.加载映射文件 /* LPVOID MapViewOfFile( HANDLE hFileMappingObject, //物理文件句柄 DWORD dwDesiredAccess, //对文件数据的访问方式 DWORD dwFileOffsetHigh, //文件的偏移地址高位 DWORD dwFileOffsetLow, //文件的偏移地址低位 DWORD dwNumberOfBytesToMap); 文件的偏移地址由DWORD型的参数dwFileOffsetHigh和dwFileOffsetLow组成的64位值来指定, 而且必须是操作系统的分配粒度的整数倍,对于Windows操作系统,分配粒度固定为64KB dwNumberOfBytesToMap:映射文件部分的大小,如果为0,则映射整个文件。 返回值: 如果成功返回返回映射视图的起始地址,如果失败返回NULL。 在完成对映射到进程地址空间区域的文件处理后,需要通过函数UnmapViewOfFile()完成对文件数据映像的释放,该函数原型声明如下: BOOL UnmapViewOfFile(LPCVOID lpBaseAddress); */ //3.1动态获取当前操作系统的分配粒度: SYSTEM_INFO sinf; GetSystemInfo(&sinf); m_MapChunkSize = sinf.dwAllocationGranularity; m_FileChunkSize = 1000*m_MapChunkSize; //3.2把文件数据映射到进程的地址空间 /* 而在某些特殊行业,经常要面对十几GB乃至几十GB容量的巨型文件,而一个32位进程所拥有的虚拟地址空间只有232 = 4GB,显然不能一次将文件映像全部映射进来。对于这种情况只能依次将大文件的各个部分映射到进程中的一个较小的地址空间。这需要对上面的一般流程进行适当的更改: 1)映射文件开头的映像。 2)对该映像进行访问。 3)取消此映像 4)映射一个从文件中的一个更深的位移开始的新映像。 5)重复步骤2,直到访问完全部的文件数据。 */ m_CurOffset = 0; if(m_FileSize > m_FileChunkSize) m_CurChunkSize = m_FileChunkSize; else m_CurChunkSize = m_FileSize; m_BaseAddr = (byte*)MapViewOfFile(m_FileMemMapHandler,m_ViewAccess,0,0,m_CurChunkSize); if(m_BaseAddr != NULL){ m_FileMapped = true; return true; } else{ err_code = GetLastError();//错误类型定义在WinError.h switch(err_code) { case ERROR_ACCESS_DENIED: sprintf(err_msg,"文件数据映射到进程的地址空间错误,无权限!"); break; } CloseHandle(m_FileMemMapHandler); CloseHandle(m_FileHandler); ShowError(err_msg); return false; } } bool ReleaseFileMapping(){ /* 在完成对映射到进程地址空间区域的文件处理后,需要通过函数UnmapViewOfFile()完成对文件数据映像的释放,该函数原型声明如下: BOOL UnmapViewOfFile(LPCVOID lpBaseAddress); // lpBaseAddress 映射视图起始地址 */ if(!m_FileMapped || m_BaseAddr == NULL) return false; //1.释放文件数据映像 if(m_BaseAddr!=NULL) UnmapViewOfFile(m_BaseAddr); //2.关闭内存映射句柄 CloseHandle(m_FileMemMapHandler); //3.关闭进行内存映射的文件 CloseHandle(m_FileHandler); //重置状态 m_FileMapped = false; m_FileHandler = NULL; m_FileMemMapHandler = NULL; m_BaseAddr = NULL; m_FileSize = 0; m_FileChunkSize = 0; m_CurChunkSize = 0; m_MapChunkSize = 0; m_CurOffset = 0; m_ViewAccess=0; return true; } string GetLastFileName(const char* pathName){ char spliter = '\\'; int pos = strlen(pathName); for(;pos>=0 &&(*(pathName+pos)) != spliter; pos--); const char* fname = pathName + (pos + 1); string fileName(fname); return fileName; } bool IsFileMapped(){ return m_FileMapped; } unsigned __int64 GetCurFileSize(){ if(m_FileMapped) return m_FileSize; else return -1; } bool MapFileChunk(unsigned __int64 offset, size_t len)//前提len <= m_FileChunkSize { if(offset+len > m_FileSize) return false; if(len > m_CurChunkSize) return false; bool needNewChunk = false; if((offset < m_CurOffset)||//请求的地址在上一个chunk中 ((offset + len) > (m_CurOffset + m_CurChunkSize)))//请求的地址在下一个chunk中 { m_CurOffset = offset; DWORD offsetmod = m_CurOffset % m_MapChunkSize; m_CurOffset -= offsetmod;//文件的偏移地址必须是操作系统的分配粒度的整数倍 if(m_CurOffset + m_FileChunkSize > m_FileSize) m_CurChunkSize = m_FileSize - m_CurOffset; else m_CurChunkSize = m_FileChunkSize; needNewChunk = true; } else needNewChunk = false; if(needNewChunk) { //3)取消此映像 //4)映射一个从文件中的一个更深的位移开始的新映像。 UnmapViewOfFile(m_BaseAddr); m_BaseAddr = (byte*)MapViewOfFile(m_FileMemMapHandler,m_ViewAccess,(m_CurOffset >> 32),(m_CurOffset & 0xFFFFFFFF),m_CurChunkSize); if(m_BaseAddr == NULL){ char err_msg[100]; DWORD err_code = GetLastError();//错误类型定义在WinError.h switch(err_code) { case ERROR_ACCESS_DENIED: sprintf(err_msg,"文件数据映射到进程的地址空间错误,无权限!"); break; } ShowError(err_msg); return false; } } return true; } //从相对m_BaseAddr首地址offset位置拷贝len长度的数据到dst;确保dst有足够的内存空间 bool GetMemory(void* dst,unsigned __int64 offset, size_t len){ if(offset < 0 || offset + len > m_FileSize) return false; unsigned __int64 curoffset=offset; size_t curlen = 0; while(len>0) { if(len > m_FileChunkSize) curlen = m_FileChunkSize; else curlen = len; if(!MapFileChunk(curoffset,curlen)) return false; memcpy((char*)dst+(curoffset - offset),m_BaseAddr+(curoffset - m_CurOffset),curlen); curoffset += curlen; len -= curlen; } return true; } //向相对m_BaseAddr首地址offset位置写入len长度的src数据;确保src有足够的内存空间 bool WriteMemory(void* src,unsigned __int64 offset, size_t len){ /*在使用内存映射文件时,为了提高速度,系统将文件的数据页面进行高速缓存, 而且在处理文件映射视图时不立即更新文件的磁盘映像。 为解决这个问题可以考虑使用FlushViewOfFile()函数, 该函数强制系统将修改过的数据部分或全部重新写入磁盘映像, 从而可以确保所有的数据更新能及时保存到磁盘。 将内存复制到所映射的物理文件上面 FlushMapViewOfFile函数可以将内存里面的内容DUMP到物理磁盘上面 FlushViewOfFile 把文件映射视图中的修改的内容或全部写回到磁盘文件中 BOOL FlushViewOfFile( LPCVOID lpBaseAddress, // 修改内容的起始地址 DWORD dwNumberOfBytesToFlush // 修改的字节数目 ); 函数执行成功返回非零。 */ if(offset < 0 || offset + len > m_FileSize) return false; unsigned __int64 curoffset=offset; size_t curlen = 0; while(len>0) { if(len > m_FileChunkSize) curlen = m_FileChunkSize; else curlen = len; if(!MapFileChunk(curoffset,curlen)) return false; memcpy(m_BaseAddr+(curoffset - m_CurOffset),(char*)src+(curoffset - offset),curlen); FlushViewOfFile(m_BaseAddr+(curoffset - m_CurOffset),curlen);//把文件映射视图中的修改的内容或全部写回到磁盘文件中 curoffset += curlen; len -= curlen; } return true; } }; #endif

 

 

测试:

 1 // Demo.cpp : 定义控制台应用程序的入口点。
 2 //
 3 
 4 #include "stdafx.h"
 5 #include <iostream>
 6 #include <fstream>
 7 #include <string>
 8 #include "MemFileMapHelper.h"
 9 using namespace std;
10 
11 
12 typedef struct{
13     double X;
14     double Y;
15     double Z;
16 }stru_pos;
17 
18 int main(int argc, char* argv[])
19 {
20     bool flag;
21     int nSize = 10;
22     char* fileName = "F:\\Code\\cpp\\Demo\\Demo\\test.txt";
23 
24     stru_pos *posArr = new stru_pos[nSize];
25     for (int i=0;i<nSize;i++)
26     {
27         posArr[i].X = i+1;
28         posArr[i].Y = i+2;
29         posArr[i].Z = i+3;
30     }
31 
32     CMemFileMapHelper fh;
33     //seq write
34     flag = fh.BuildFileMapping(fileName,CMemFileMapHelper::SEQ_WRITE,nSize * sizeof(stru_pos));
35     if(flag){
36         fh.WriteMemory(posArr,0,nSize*sizeof(stru_pos));
37         fh.ReleaseFileMapping();
38     }
39     if(!flag) return -1;
40 
41     ////radom write
42     //flag = fh.BuildFileMapping(fileName,CMemFileMapHelper::RANDOM_WRITE,nSize * sizeof(stru_pos));
43     //if(flag){
44     //    for (int i=nSize-1;i>=0 && flag;i--)
45     //    {
46     //        flag = fh.WriteMemory(&posArr[i],i*sizeof(stru_pos),sizeof(stru_pos));
47     //        cout<<posArr[i].X <<" "<<posArr[i].Y <<" "<<posArr[i].Z<<endl;
48     //    }
49     //    fh.ReleaseFileMapping();
50     //}
51     //if(!flag) return -1;
52 
53     //seq read
54     flag = fh.BuildFileMapping(fileName,CMemFileMapHelper::SEQ_READ);
55     for (int i=0;i<nSize && flag;i++)
56     {
57         stru_pos pos;
58         flag = fh.GetMemory(&pos,i*sizeof(stru_pos),sizeof(stru_pos));
59         cout<<pos.X <<" "<<pos.Y <<" "<<pos.Z<<endl;
60     }
61     fh.ReleaseFileMapping();
62 
63     ////random read
64     //flag = fh.BuildFileMapping(fileName,CMemFileMapHelper::RANDOM_READ);
65     //for (int i=nSize - 1;i>=0 && flag;i--)
66     //{
67     //    stru_pos pos;
68     //    flag = fh.GetMemory(&pos,i*sizeof(stru_pos),sizeof(stru_pos));
69     //    cout<<pos.X <<" "<<pos.Y <<" "<<pos.Z<<endl;
70     //}
71     //fh.ReleaseFileMapping();
72 
73     ////random read write
74     //flag = fh.BuildFileMapping(fileName,CMemFileMapHelper::SEQ_READ_WRITE);
75     //stru_pos pos;
76     //flag = fh.GetMemory(&pos,5*sizeof(stru_pos),sizeof(stru_pos));
77     //cout<<pos.X <<" "<<pos.Y <<" "<<pos.Z<<endl;
78     //pos.X = pos.Y = pos.Z = 13;
79     //flag = fh.WriteMemory(&pos,5*sizeof(stru_pos),sizeof(stru_pos));
80     //cout<<pos.X <<" "<<pos.Y <<" "<<pos.Z<<endl;
81     //fh.ReleaseFileMapping();
82 
83     delete[] posArr;
84     cin>>flag;
85     return 0;
86 }

32位程序处理4G以上文件,参考网络内容: “

而在某些特殊行业,经常要面对十几GB乃至几十GB容量的巨型文件,而一个32位进程所拥有的虚拟地址空间只有232 = 4GB,显然不能一次将文件映像全部映射进来。对于这种情况只能依次将大文件的各个部分映射到进程中的一个较小的地址空间。这需要对上面的一般流程进行适当的更改:

  1)映射文件开头的映像。

  2)对该映像进行访问。

  3)取消此映像

  4)映射一个从文件中的一个更深的位移开始的新映像。

  5)重复步骤2,直到访问完全部的文件数据。

  下面给出一段根据此描述而写出的对大于4GB的文件的处理代码:
// 选择文件
CFileDialog fileDlg(TRUE, "*.txt", "*.txt", NULL, "文本文件 (*.txt)|*.txt||", this);
fileDlg.m_ofn.Flags |= OFN_FILEMUSTEXIST;
fileDlg.m_ofn.lpstrTitle = "通过内存映射文件读取数据";
if (fileDlg.DoModal() == IDOK)
{
 // 创建文件对象
 HANDLE hFile = CreateFile(fileDlg.GetPathName(), GENERIC_READ | GENERIC_WRITE,
   0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 if (hFile == INVALID_HANDLE_VALUE)
 {
  TRACE("创建文件对象失败,错误代码:%d\r\n", GetLastError());
  return;
 }
 // 创建文件映射对象
 HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
 if (hFileMap == NULL)
 {
  TRACE("创建文件映射对象失败,错误代码:%d\r\n", GetLastError());
  return;
 }
 // 得到系统分配粒度
 SYSTEM_INFO SysInfo;
 GetSystemInfo(&SysInfo);
 DWORD dwGran = SysInfo.dwAllocationGranularity;
 // 得到文件尺寸
 DWORD dwFileSizeHigh;
 __int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);
 qwFileSize |= (((__int64)dwFileSizeHigh) << 32);
 // 关闭文件对象
 CloseHandle(hFile);
 // 偏移地址
 __int64 qwFileOffset = 0;
 // 块大小
 DWORD dwBlockBytes = 1000 * dwGran;
 if (qwFileSize < 1000 * dwGran)
  dwBlockBytes = (DWORD)qwFileSize;
  while (qwFileSize > 0)
  {
   // 映射视图
   LPBYTE lpbMapAddress = (LPBYTE)MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS,
      (DWORD)(qwFileOffset >> 32), (DWORD)(qwFileOffset & 0xFFFFFFFF),
      dwBlockBytes);
   if (lpbMapAddress == NULL)
   {
    TRACE("映射文件映射失败,错误代码:%d\r\n", GetLastError());
    return;
   }
   // 对映射的视图进行访问
   for(DWORD i = 0; i < dwBlockBytes; i++)
    BYTE temp = *(lpbMapAddress + i);
   // 撤消文件映像
   UnmapViewOfFile(lpbMapAddress);
   // 修正参数
   qwFileOffset += dwBlockBytes;
   qwFileSize -= dwBlockBytes;
  }
  // 关闭文件映射对象句柄
  CloseHandle(hFileMap);
  AfxMessageBox("成功完成对文件的访问");
}

  在本例中,首先通过GetFileSize()得到被处理文件长度(64位)的高32位和低32位值。然后在映射过程中设定每次映射的块大小为1000倍的分配粒度,如果文件长度小于1000倍的分配粒度时则将块大小设置为文件的实际长度。在处理过程中由映射、访问、撤消映射构成了一个循环处理。其中,每处理完一个文件块后都通过关闭文件映射对象来对每个文件块进行整理。CreateFileMapping()、MapViewOfFile()等函数是专门用来进行内存文件映射处理用的。

posted @ 2016-10-30 14:48  小小鸟的大梦想  阅读(2369)  评论(0编辑  收藏  举报