你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 计算机安全与维护
6.11 内存映射文件的操作
 

一、概述

内存映射文件是将磁盘文件部分或全部映射到物理内存的一块地址空间,通过此地址空间,实现应用程序像访问内存一样便捷地访问磁盘文件。通常应用程序要访问磁盘文件首先是打开文件,读文件,最后再关闭文件,这是一个非常烦琐的操作过程,尤其是在频繁访问大文件时,应用程序的复杂性和运行效率是无法容忍的,通过使用内存映射文件可以很好的解决这一问题,如图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_READONLYdwMaximumSizeHigh dwMaximumSizeLow分别是要创建内存映射文件大小的高、低32位值,即内存映射文件大小最大可达180亿GB,当然如要创建成功,必须要有180亿GB物理磁盘空间,实际上这种情况几乎用不到。

将文件数据映射到进程地址空间MapViewOfFile函数,是从已打开的文件映射对象中指定起始位置,并映射指定大小数据到进程空间,更通俗地讲就是将磁盘文件的某一部分读入内存。MapViewOfFile函数原型如下:

LPVOID MapViewOfFile(

    HANDLE hFileMappingObject,  // 文件映射对象 

    DWORD dwDesiredAccess,      // 访问模式

    DWORD dwFileOffsetHigh,     // 文件位置偏移高32

    DWORD dwFileOffsetLow,      // 文件位置偏移低32

    DWORD dwNumberOfBytesToMap // 字节数

   );

参数hFileMappingObjectCreateFileMapping函数返回的文件映射句柄;dwDesiredAccess用于指定访问方式;dwFileOffsetHighdwFileOffsetLow用于指定映射文件偏移位置高、低32位地址,此值小于等于文件大小,如此值为0,则系统试图将从偏移地址到文件末尾全部映射,另外特别需要注意的是此偏移值的大小要是64KB的整数倍,系统默认64KdwNumberOfBytesToMap参数指定映射数据字节数。函数调用成功返回进程地址空间映射数据首地址,用户程序根据此值进行数据访问。

在执行完上述三步后就可以在指定的地址空间范围内对文件进行数据操作,当操作完成后再调用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;

四、结语

基于内存映射文件编写的文件操作类可以广泛用于文件读写操作,其具有占用系统资源少,运行效率高,结构清晰等特点,特别适合用于大文件操作,数据采集等系统应用中。读者可以根据应用需要在此文件操作类基础上不断丰富类函数,构建自己的应用系统。

  推荐精品文章

·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录
·2023年10月目录
·2023年9月目录 
·2023年8月目录 

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089