| 
				 一、概述 
内存映射文件是将磁盘文件部分或全部映射到物理内存的一块地址空间,通过此地址空间,实现应用程序像访问内存一样便捷地访问磁盘文件。通常应用程序要访问磁盘文件首先是打开文件,读文件,最后再关闭文件,这是一个非常烦琐的操作过程,尤其是在频繁访问大文件时,应用程序的复杂性和运行效率是无法容忍的,通过使用内存映射文件可以很好的解决这一问题,如图1所示。 
 
  
 
图1  内存映射文件 
二、过程介绍 
要正确使用内存映射文件必需执行六个过程:一是文件打开或创建;二是创建文件映射;三是将文件数据映射到址址空间;四是解除文件数据映射;五是关闭映射文件;六是关闭文件。上述过程主要用到四个API函数,下面对上述过程进行详细介绍: 
文件打开或创建使用CreateFile函数。调用成功返回一个文件句柄,这个句柄在后面创建映射文件时要用到。CreatFile函数原型如下: 
HANDLE CreateFile( 
    LPCTSTR lpFileName,         // 文件名  
    DWORD dwDesiredAccess,      // 访问模式  
    DWORD dwShareMode,          // 共享模式  
    LPSECURITY_ATTRIBUTES lpSecurityAttributes,        // 安全属性  
    DWORD dwCreationDistribution,                      // 创建方式 
    DWORD dwFlagsAndAttributes,                        // 文件属性  
    HANDLE hTemplateFile                                  
   );    
参数lpFileName为要打开的文件名,dwDesiredAccess参数描述打开文件访问方式,此参数直接影响内存映射文件的访问方式。 
创建文件映射对象CreatFileMapping函数,是为打开的文件指定一块磁盘空间,此空间大小要大于或等于已打开文件大小,否则不能够完整访问文件,即能够将整个文件完整的装入该地址空间内。CreatFileMapping函数原型如下: 
HANDLE CreateFileMapping( 
    HANDLE hFile,               // 文件句柄  
    LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // 安全属性  
    DWORD flProtect,            // 保护属性  
    DWORD dwMaximumSizeHigh,    // 映射对象大小高32位  
    DWORD dwMaximumSizeLow,     // 映射对象大小低32位 
    LPCTSTR lpName             // 文件映射对象名字  
   ); 
参数hFile为已打开或创建的文件句柄;flProtect 参数类似CreateFile函数中dwDesiredAccess参数,用于指定保护属性,但是在这里指定的保护属性要与CreateFile函数中相对应,如在CreateFile中指定GENERIC_READ,在CreateFileMapping中只能指定PAGE_READONLY;dwMaximumSizeHigh和 dwMaximumSizeLow分别是要创建内存映射文件大小的高、低32位值,即内存映射文件大小最大可达180亿GB,当然如要创建成功,必须要有180亿GB物理磁盘空间,实际上这种情况几乎用不到。 
将文件数据映射到进程地址空间MapViewOfFile函数,是从已打开的文件映射对象中指定起始位置,并映射指定大小数据到进程空间,更通俗地讲就是将磁盘文件的某一部分读入内存。MapViewOfFile函数原型如下: 
LPVOID MapViewOfFile( 
    HANDLE hFileMappingObject,  // 文件映射对象   
    DWORD dwDesiredAccess,      // 访问模式 
    DWORD dwFileOffsetHigh,     // 文件位置偏移高32位  
    DWORD dwFileOffsetLow,      // 文件位置偏移低32位  
    DWORD dwNumberOfBytesToMap // 字节数 
   ); 
参数hFileMappingObject是CreateFileMapping函数返回的文件映射句柄;dwDesiredAccess用于指定访问方式;dwFileOffsetHigh和dwFileOffsetLow用于指定映射文件偏移位置高、低32位地址,此值小于等于文件大小,如此值为0,则系统试图将从偏移地址到文件末尾全部映射,另外特别需要注意的是此偏移值的大小要是64KB的整数倍,系统默认64K;dwNumberOfBytesToMap参数指定映射数据字节数。函数调用成功返回进程地址空间映射数据首地址,用户程序根据此值进行数据访问。 
在执行完上述三步后就可以在指定的地址空间范围内对文件进行数据操作,当操作完成后再调用UnMapViewOfFile关闭映射,通过调用UnMapViewOfFile系统将内存中的数据回写到磁盘,调用CloseHandle函数关闭映射文件和文件句柄。 
三、文件操作类示例 
在应用程序中频繁地调用上述API函数会使程序冗长且不易理解,如将其封装为文件操作类可以使用程序更清晰,简洁。下面通过一个文件操作类示例详细介绍内存映射文件的使用过程。类定义如下: 
const FILE_CACHE_SIZE = 200*64*1024;  
// = 6.4MB  表示可CACHE文件大小,即文件大小小于此值文件可以打开 
      FILE_MAPVIEW_SIZE = 64*1024;  
// = 64KB  表示将文件映射到地址区域大小,此值为64K的整数倍,且应小于等于  FILE_CACHE_SIZE 
      FILE_READONLY = 0; 
      FILE_WRITE = 1; 
Type  
TFileCache = class(TObject) 
  private 
    mMappingViewSize     : INT64; 
    mWriteMappingOffset  : INT64; 
    mWriteBufferOffset   : INT64; 
    mReadMappingOffset   : INT64; 
    FfileHandle          : integer; 
    FmappingHandle       : integer; 
    mWriteBuffer         : pointer; 
    mReadBuffer          : pointer; 
    dwWriteFizeSize      :Dword; 
    dwReadFileSize       :Dword; 
    dwShareMode          :Dword; 
    CacheActive          :Boolean; 
  public 
    { Public declarations } 
    constructor Create; 
    destructor  Destroy; override; 
    function OpenFileCache(const AFileName:String;ShareMode:Dword;WriteFileSize:INT64): boolean; 
    function CloseFileCache(): boolean; 
    function WriteData(WriteAddressOffset:INT64;pData:Dword; pDataLength: integer): boolean; 
    function ReadData(pAddressOffset: INT64; var pData; pDataLength: integer): boolean; 
    property ReadFileSize : Dword read dwReadFileSize; 
    property WriteFileSize : Dword read dwWriteFizeSize Write dwWriteFizeSize; 
    property Active : boolean read CacheActive ; 
  end; 
TFileCache类中主定义了打开文件缓冲OpenFileCache、关闭文件缓冲CloseFileCache、写数据WriteData、读数据ReadData四个函数。通过OpenFileCache函数可以完成打开或创建文件并打开或创建映射文件,参数AfileName用于指定文件名;ShareMode用于指定访问方式,0代表读,大于等于1为写;WriteFileSize用于指定创建文件大小,只有在写方式下起作用。返回TRUE则打开成功,否则返回FALSE。实现的核心代码如下: 
   function TFileCache.OpenFileCache(const AFileName: String;ShareMode:Dword;WriteFileSize:INT64): boolean; 
begin 
  if (WriteFileSize >FILE_CACHE_SIZE) and (ShareMode=1) 
    then 
    begin 
      raise Exception.Create('WriteFileSize Too much to Cache'); 
      result:=false; 
      exit; 
    end 
    else 
      dwWriteFizeSize:= WriteFileSize; 
  
  //打开或创建文件 
  case ShareMode  of 
    0:  begin 
          FFileHandle := CreateFile(PChar(AFileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); 
          dwShareMode:=0; 
        end; 
    1:  begin 
          FFileHandle := CreateFile(PChar(AFileName), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ, nil, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); 
          dwShareMode:=1; 
        end;   
    else 
      FFileHandle := CreateFile(PChar(AFileName), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ, nil, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); 
      raise Exception.Create('FileCache ShareMode=0 is Read else is write'); 
      result:=false; 
      exit; 
  end; {end 打开或创建文件} 
  if FFileHandle = INVALID_HANDLE_VALUE then 
  begin 
    raise Exception.Create('Error when open file'); 
    result:=false; 
    exit; 
  end 
  else 
  begin 
    dwReadFileSize:=GetFileSize(FFileHandle,nil); 
    if dwReadFileSize > FILE_CACHE_SIZE 
    then 
    begin 
      raise Exception.Create('FileSize Too much to Cache'); 
      result:=false; 
      exit; 
    end; 
    case ShareMode  of 
      0:  FMappingHandle := CreateFileMapping(FFileHandle, nil, PAGE_READONLY, 0, dwReadFileSize, nil); //DWORD(FILE_CACHE_SIZE shr 32) 
      1:  FMappingHandle := CreateFileMapping(FFileHandle, nil, PAGE_READWRITE, 0, DWORD(FILE_CACHE_SIZE and $FFFFFFFF), nil); 
      else 
        FMappingHandle := CreateFileMapping(FFileHandle, nil, PAGE_READWRITE, 0, DWORD(FILE_CACHE_SIZE and $FFFFFFFF), nil); 
        raise Exception.Create('FileCache ShareMode=0 is Read else is write'); 
        result:=false; 
        exit; 
    end;{end case} 
  end; {end 创建文件映射} 
  if FMappingHandle=0 then 
  begin 
    raise Exception.Create('Error when mapping file'); 
    result:=false; 
    exit; 
  end; 
  mWriteMappingOffset := 0; 
  mWriteBuffer := nil; 
  mWriteBufferOffset := 0; 
  mReadMappingOffset := 0; 
  mReadBuffer := nil; 
  CacheActive:=true; 
  Result := true; 
end; 
CloseFileCache用于关闭文件映射文件以及文件句柄,并恢复类属性值,代码如下: 
   function TFileCache.CloseFileCache(): boolean; 
begin 
    if (mWriteBuffer <> nil) 
    then 
      UnmapViewOfFile(mWriteBuffer); 
    if (mReadBuffer <> nil) 
    then 
      UnmapViewOfFile(mReadBuffer); 
    if (FMappingHandle <> 0) 
    then 
      CloseHandle(FMappingHandle); 
    if (FFileHandle <> INVALID_HANDLE_VALUE) then 
    begin 
      if dwShareMode=0 
      then 
        SetFilePointer(FFileHandle,dwReadFileSize,nil,FILE_BEGIN) 
      else 
        SetFilePointer(FFileHandle,dwWriteFizeSize,nil,FILE_BEGIN); 
      SetEndofFile(FFileHandle); 
      CloseHandle(FFileHandle); 
    end; 
    mMappingViewSize := FILE_MAPVIEW_SIZE; 
    FFileHandle := INVALID_HANDLE_VALUE; 
    FMappingHandle := 0; 
    mWriteMappingOffset := 0; 
    mWriteBuffer := nil; 
    mWriteBufferOffset := 0; 
    mReadMappingOffset := 0; 
    mReadBuffer := nil; 
  
    dwReadFileSize:=0; 
    dwWriteFizeSize:=0; 
    dwShareMode:=0; 
    CacheActive:=false; 
end;  
WriteData函数实现在文件指定位置写入指定大小数据。参数WriteAddressOffset指定文件偏移置;pData要写入数据的地址指针;pDataLength写入数据长度,其代码如下: 
function TFileCache.WriteData(WriteAddressOffset:INT64;pData:Dword; pDataLength: integer): boolean; 
var 
    datawrote: integer; 
    datacanwrite: integer; 
    datatowrite: integer; 
    actualdatatowrite: integer; 
    MapViewOffset,WriteMapBufferOffset:int64; 
begin 
    datawrote := 0; 
    MapViewOffset:= trunc(WriteAddressOffset/mMappingViewSize) * mMappingViewSize; 
    WriteMapBufferOffset:=WriteAddressOffset mod mMappingViewSize; 
    while (datawrote < pDataLength) do 
    begin 
        datacanwrite := mMappingViewSize - WriteMapBufferOffset; 
        if (mWriteBuffer<>nil) and (datacanwrite <= 0) then 
        begin 
            UnmapViewOfFile(mWriteBuffer); 
            mWriteBuffer := nil; 
            MapViewOffset := MapViewOffset + mMappingViewSize; 
            WriteMapBufferOffset:=0; 
        end; 
        if (mWriteBuffer = nil) then 
        begin 
            mWriteBuffer := MapViewOfFile(FMappingHandle, FILE_MAP_WRITE, DWORD(mWriteMappingOffset shr 32), DWORD(MapViewOffset and $FFFFFFFF), mMappingViewSize); 
            datacanwrite := mMappingViewSize - WriteMapBufferOffset; 
            if (mWriteBuffer = nil) then 
            begin 
              raise Exception.Create('Error when map view of file.'); 
              result:=false; 
              Exit; 
            end; 
        end; 
        datatowrite := pDataLength - datawrote; 
        if (datacanwrite >= datatowrite) 
        then 
            actualdatatowrite := datatowrite 
        else 
            actualdatatowrite := datacanwrite; 
        CopyMemory(Pointer(Longint(mWriteBuffer) + WriteMapBufferOffset), Pointer(Longint(Pointer(pData)) + datawrote), actualdatatowrite); 
         WriteMapBufferOffset := WriteMapBufferOffset + actualdatatowrite; 
        datawrote := datawrote + actualdatatowrite; 
    end; {while end} 
    if mWriteBuffer<>nil 
    then 
    begin 
      UnmapViewOfFile(mWriteBuffer); 
      mWriteBuffer := nil; 
    end; 
    Result := true; 
end; 
ReadData函数实现从文件指定位置读取指定大小数据。参数pAddressOffset指定文件偏移置;pData为存放读取数据的地址指针;pDataLength读取数据长度,实现的核心代码如下: 
   function TFileCache.ReadData(pAddressOffset: INT64; var pData; pDataLength: integer): boolean; 
var 
    datareaded: integer;          //已读数据大小 
    datacanread: integer;         //能读数据大小 
    datatoread: integer;          //读数据大小 
    actualdatatoread: integer;    //事实读数据大小 
    high:Dword; 
    LastMapViewSize:Dword;        //最后一个文件块大小,此块值当<=文件映像大小mMappingViewSize 
    actualMapViewSize:Dword; 
begin 
    datareaded := 0; 
    while (datareaded < pDataLength) do 
    begin 
        datacanread := mReadMappingOffset + mMappingViewSize - pAddressOffset - datareaded; 
        if (mReadBuffer<>nil) and ((datacanread <= 0) or (datacanread > mMappingViewSize)) then 
        begin 
            UnmapViewOfFile(mReadBuffer); 
            mReadBuffer := nil; 
        end; 
        if (mReadBuffer = nil) then 
        begin 
            mReadMappingOffset := (pAddressOffset + datareaded) div mMappingViewSize * mMappingViewSize; 
            LastMapViewSize:=dwReadFileSize-mReadMappingOffset; //求所剩文件大小 
            if LastMapViewSize<= mMappingViewSize 
            then 
              actualMapViewSize:=LastMapViewSize 
            else 
              actualMapViewSize:=mMappingViewSize; 
            high:=DWORD(mReadMappingOffset shr 32); 
            mReadBuffer := MapViewOfFile(FMappingHandle, FILE_MAP_READ, high, DWORD(mReadMappingOffset and $FFFFFFFF), actualMapViewSize); 
            datacanread := mReadMappingOffset + mMappingViewSize - pAddressOffset - datareaded; 
            if (mReadBuffer = nil) then 
            begin 
                raise Exception.Create('Error when map view of file.'); 
                Result := false; 
            end; 
        end; 
        datatoread := pDataLength - datareaded; 
        if (datacanread >= datatoread) then 
            actualdatatoread := datatoread 
        else 
            actualdatatoread := datacanread; 
        CopyMemory(Pointer(Longint(@pData) + datareaded), Pointer(LongInt(mReadBuffer) + pAddressOffset - mReadMappingOffset + datareaded), actualdatatoread); 
        datareaded := datareaded + actualdatatoread; 
    end; {while end} 
    if mReadBuffer<>nil 
    then 
    begin 
      UnmapViewOfFile(mReadBuffer); 
      mReadBuffer := nil; 
    end; 
    Result := true; 
end; 
WriteData函数与ReadWData函数在流程上基本相同,如图2所示,如要从一文件偏移置为50K的位置读取40K数据时要首先将文件从起始到64K-1位置的数据映射到进程地址空间,然后从50K的位置读取14K数据后(图中数据框虚线所示),再关闭映射,并从文件64K位置映射下一个64K数据到进程地址空间,然后再读取余下的26K数据。文件操作类调用示例程序界面如图3所示,读文件关键代码如下: 
  
图2 文件读写过 
 
 
  
 
 
  
图3 示例程序界面 
procedure TForm1.Button2Click(Sender: TObject); 
var 
   s: string; 
   fileoffset,charsize:Dword; 
begin 
  
    if FR.Active 
    then 
    begin 
      fileoffset:=strtoint(trim(edit1.Text)); 
      charsize:=strtoint(trim(edit2.Text)); 
      if FR.ReadFileSize >= fileoffset 
      then 
      begin 
        if FR.ReadFileSize>=(fileoffset+charsize) 
        then 
        begin 
        SetString(S, nil, charsize); 
        FR.ReadData(fileoffset, Pointer(S)^, charsize); 
        Memo1.Lines.Add(S); 
        end 
        else 
          showmessage('字节数超出文件大小'); 
      end 
      else 
        showmessage('读位置超出文件大小'); 
    end 
    else 
      showmessage('文件未打开'); 
end; 
写文件的关键代码如下: 
procedure TForm1.Button6Click(Sender: TObject); 
var 
  fileoffset,charsize:Dword; 
begin 
    if FW.Active 
    then 
    begin 
      fileoffset:=strtoint(trim(edit3.Text)); 
      charsize:=strtoint(trim(edit4.Text)); 
      if fileoffset <= FILE_CACHE_SIZE 
      then 
      begin 
        if fileoffset+charsize <=FILE_CACHE_SIZE 
        then 
        begin 
          FW.WriteData(fileoffset,Dword(memo1.Text),charsize); 
        end 
        else 
          showmessage('写入字节数大于映射文件大小'); 
  
      end 
      else 
        showmessage('写入位置超出映射文件大小'); 
    end 
    else 
      showmessage('文件未打开'); 
end; 
四、结语 
基于内存映射文件编写的文件操作类可以广泛用于文件读写操作,其具有占用系统资源少,运行效率高,结构清晰等特点,特别适合用于大文件操作,数据采集等系统应用中。读者可以根据应用需要在此文件操作类基础上不断丰富类函数,构建自己的应用系统。 			
				 |