当文件过大时,无法一次性载入内存时,就需要分次,分段的载入文件
主要是用了以下的WinAPI
LPVOID MapViewOfFile(HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
DWORD dwNumberOfBytesToMap);
MapViewOfFile() 函数负责把文件数据映射到进程的地址空间,参数hFileMappingObject 为 CreateFileMapping()返回的文件映像对象句柄。参数dwDesiredAccess则再次指定了对文件数据的访问方式,而且同样要与 CreateFileMapping()函数所设置的保护属性相匹配。虽然这里一再对保护属性进行重复设置看似多余,但却可以使应用程序能更多的对数据的保护属性实行有效控制。MapViewOfFile()函数允许全部或部分映射文件,在映射时,需要指定数据文件的偏移地址以及待映射的长度。其中,文件的偏移地址由DWORD型的参数dwFileOffsetHigh和dwFileOffsetLow组成的64位值来指定,而且必须是操作系统的分配粒度的整数倍,对于Windows操作系统,分配粒度固定为64KB。当然,也可以通过如下代码来动态获取当前操作系统的分配粒度:
SYSTEM_INFO sinf;
GetSystemInfo(&sinf);
DWORD dwAllocationGranularity = sinf.dwAllocationGranularity;
参数dwNumberOfBytesToMap指定了数据文件的映射长度,这里需要特别指出的是,对于Windows 9x操作系统,如果MapViewOfFile()无法找到足够大的区域来存放整个文件映射对象,将返回空值(NULL);但是在Windows 2000下,MapViewOfFile()只需要为必要的视图找到足够大的一个区域即可,而无须考虑整个文件映射对象的大小。
由此看出,分页映射文件时,每页的起始位置startpos,必须为64K的整数倍。
以下贴出源代码,防止忘记了
using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; namespace BlueVision.SaYuan.FileMapping { public class ShareMemory { [DllImport( "user32.dll", CharSet = CharSet.Auto )] public static extern IntPtr SendMessage( IntPtr hWnd, int Msg, int wParam, IntPtr lParam ); [DllImport( "Kernel32.dll", CharSet = CharSet.Auto )] public static extern IntPtr CreateFileMapping( IntPtr hFile, IntPtr lpAttributes, uint flProtect, uint dwMaxSizeHi, uint dwMaxSizeLow, string lpName ); [DllImport( "Kernel32.dll", CharSet = CharSet.Auto )] public static extern IntPtr OpenFileMapping( int dwDesiredAccess, [MarshalAs( UnmanagedType.Bool )] bool bInheritHandle, string lpName ); [DllImport( "Kernel32.dll", CharSet = CharSet.Auto )] public static extern IntPtr MapViewOfFile( IntPtr hFileMapping, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, uint dwNumberOfBytesToMap ); [DllImport( "Kernel32.dll", CharSet = CharSet.Auto )] public static extern bool UnmapViewOfFile( IntPtr pvBaseAddress ); [DllImport( "Kernel32.dll", CharSet = CharSet.Auto )] public static extern bool CloseHandle( IntPtr handle ); [DllImport( "kernel32", EntryPoint = "GetLastError" )] public static extern int GetLastError(); [DllImport( "kernel32.dll" )] static extern void GetSystemInfo( out SYSTEM_INFO lpSystemInfo ); [StructLayout( LayoutKind.Sequential )] public struct SYSTEM_INFO { public ushort processorArchitecture; ushort reserved; public uint pageSize; public IntPtr minimumApplicationAddress; public IntPtr maximumApplicationAddress; public IntPtr activeProcessorMask; public uint numberOfProcessors; public uint processorType; public uint allocationGranularity; public ushort processorLevel; public ushort processorRevision; } /// <summary> /// 获取系统的分配粒度 /// </summary> /// <returns></returns> public static uint GetPartitionsize() { SYSTEM_INFO sysInfo; GetSystemInfo( out sysInfo ); return sysInfo.allocationGranularity; } const int ERROR_ALREADY_EXISTS = 183; const int FILE_MAP_COPY = 0x0001; const int FILE_MAP_WRITE = 0x0002; const int FILE_MAP_READ = 0x0004; const int FILE_MAP_ALL_ACCESS = 0x0002 | 0x0004; const int PAGE_READONLY = 0x02; const int PAGE_READWRITE = 0x04; const int PAGE_WRITECOPY = 0x08; const int PAGE_EXECUTE = 0x10; const int PAGE_EXECUTE_READ = 0x20; const int PAGE_EXECUTE_READWRITE = 0x40; const int SEC_COMMIT = 0x8000000; const int SEC_IMAGE = 0x1000000; const int SEC_NOCACHE = 0x10000000; const int SEC_RESERVE = 0x4000000; IntPtr m_fHandle; IntPtr m_hSharedMemoryFile = IntPtr.Zero; IntPtr m_pwData = IntPtr.Zero; bool m_bAlreadyExist = false; bool m_bInit = false; uint m_MemSize = 0x1400000;//20M long m_offsetBegin = 0; long m_FileSize = 0; FileReader File = new FileReader(); /// <summary> /// 初始化文件 /// </summary> /// <param name="MemSize">缓冲大小</param> public ShareMemory( string filename, uint memSize ) { // 分页映射文件时,每页的起始位置startpos,必须为64K的整数倍。 // memSize即缓存区的大小必须是系统分配粒度的整倍说,window系统的分配粒度是64KB this.m_MemSize = memSize; Init( filename ); } /// <summary> /// 默认映射20M缓冲 /// </summary> /// <param name="filename"></param> public ShareMemory( string filename ) { this.m_MemSize = 0x1400000; Init( filename ); } ~ShareMemory() { Close(); } /// <summary> /// 初始化共享内存 /// /// 共享内存名称 /// 共享内存大小 /// </summary> /// <param name="strName"></param> protected void Init( string strName ) { //if (lngSize <= 0 || lngSize > 0x00800000) lngSize = 0x00800000; if ( !System.IO.File.Exists( strName ) ) throw new Exception( "未找到文件" ); System.IO.FileInfo f = new System.IO.FileInfo( strName ); m_FileSize = f.Length; m_fHandle = File.Open( strName ); if ( strName.Length > 0 ) { //创建文件映射 m_hSharedMemoryFile = CreateFileMapping( m_fHandle, IntPtr.Zero, ( uint )PAGE_READONLY, 0, ( uint )m_FileSize, "mdata" ); if ( m_hSharedMemoryFile == IntPtr.Zero ) { m_bAlreadyExist = false; m_bInit = false; throw new Exception( "CreateFileMapping失败LastError=" + GetLastError().ToString() ); } else m_bInit = true; ////映射第一块文件 //m_pwData = MapViewOfFile(m_hSharedMemoryFile, FILE_MAP_READ, 0, 0, (uint)m_MemSize); //if (m_pwData == IntPtr.Zero) //{ // m_bInit = false; // throw new Exception("m_hSharedMemoryFile失败LastError=" + GetLastError().ToString()); //} } } /// <summary> /// 获取高32位 /// </summary> /// <param name="intValue"></param> /// <returns></returns> private static uint GetHighWord( UInt64 intValue ) { return Convert.ToUInt32( intValue >> 32 ); } /// <summary> /// 获取低32位 /// </summary> /// <param name="intValue"></param> /// <returns></returns> private static uint GetLowWord( UInt64 intValue ) { return Convert.ToUInt32( intValue & 0x00000000FFFFFFFF ); } /// <summary> /// 获取下一个文件块 块大小为20M /// </summary> /// <returns>false 表示已经是最后一块文件</returns> public uint GetNextblock() { if ( !this.m_bInit ) throw new Exception( "文件未初始化。" ); //if ( m_offsetBegin + m_MemSize >= m_FileSize ) return false; uint m_Size = GetMemberSize(); if ( m_Size == 0 ) return m_Size; // 更改缓冲区大小 m_MemSize = m_Size; //卸载前一个文件 //bool l_result = UnmapViewOfFile( m_pwData ); //m_pwData = IntPtr.Zero; m_pwData = MapViewOfFile( m_hSharedMemoryFile, FILE_MAP_READ, GetHighWord( ( UInt64 )m_offsetBegin ), GetLowWord( ( UInt64 )m_offsetBegin ), m_Size ); if ( m_pwData == IntPtr.Zero ) { m_bInit = false; throw new Exception( "映射文件块失败" + GetLastError().ToString() ); } m_offsetBegin = m_offsetBegin + m_Size; return m_Size; //创建成功 } /// <summary> /// 返回映射区大小 /// </summary> /// <returns></returns> private uint GetMemberSize() { if ( m_offsetBegin >= m_FileSize ) { return 0; } else if ( m_offsetBegin + m_MemSize >= m_FileSize ) { long temp = m_FileSize - m_offsetBegin; return ( uint )temp; } else return m_MemSize; } /// <summary> /// 关闭内存映射 /// </summary> public void Close() { if ( m_bInit ) { UnmapViewOfFile( m_pwData ); CloseHandle( m_hSharedMemoryFile ); File.Close(); } } /// <summary> /// 从当前块中获取数据 /// </summary> /// <param name="bytData">数据</param> /// <param name="lngAddr">起始数据</param> /// <param name="lngSize">数据长度,最大值=缓冲长度</param> /// <param name="Unmap">读取完成是否卸载缓冲区</param> /// <returns></returns> public void Read( ref byte[] bytData, int lngAddr, int lngSize, bool Unmap ) { if ( lngAddr + lngSize > m_MemSize ) throw new Exception( "Read操作超出数据区" ); if ( m_bInit ) { // string bb = Marshal.PtrToStringAuto(m_pwData);// Marshal.Copy( m_pwData, bytData, lngAddr, lngSize ); } else { throw new Exception( "文件未初始化" ); } if ( Unmap ) { bool l_result = UnmapViewOfFile( m_pwData ); if ( l_result ) m_pwData = IntPtr.Zero; } } /// <summary> /// 从当前块中获取数据 /// </summary> /// <param name="bytData">数据</param> /// <param name="lngAddr">起始数据</param> /// <param name="lngSize">数据长度,最大值=缓冲长度</param> /// <exception cref="Exception: Read操作超出数据区"></exception> /// <exception cref="Exception: 文件未初始化"></exception> /// <returns></returns> public void Read( ref byte[] bytData, int lngAddr, int lngSize ) { if ( lngAddr + lngSize > m_MemSize ) throw new Exception( "Read操作超出数据区" ); if ( m_bInit ) { Marshal.Copy( m_pwData, bytData, lngAddr, lngSize ); } else { throw new Exception( "文件未初始化" ); } } /// <summary> /// 从当前块中获取数据 /// </summary> /// <param name="lngAddr">缓存区偏移量</param> /// <param name="byteData">数据数组</param> /// <param name="StartIndex">数据数组开始复制的下标</param> /// <param name="lngSize">数据长度,最大值=缓冲长度</param> /// <exception cref="Exception: 起始数据超过缓冲区长度"></exception> /// <exception cref="Exception: 文件未初始化"></exception> /// <returns>返回实际读取值</returns> public uint ReadBytes( int lngAddr, ref byte[] byteData, int StartIndex, uint intSize ) { if ( lngAddr >= m_MemSize ) throw new Exception( "起始数据超过缓冲区长度" ); if ( lngAddr + intSize > m_MemSize ) intSize = m_MemSize - ( uint )lngAddr; if ( m_bInit ) { IntPtr s = new IntPtr( ( long )m_pwData + lngAddr ); // 地址偏移 Marshal.Copy( s, byteData, StartIndex, ( int )intSize ); } else { throw new Exception( "文件未初始化" ); } return intSize; } /// <summary> /// 写数据 /// </summary> /// <param name="bytData">数据</param> /// <param name="lngAddr">起始地址</param> /// <param name="lngSize">个数</param> /// <returns></returns> private int Write( byte[] bytData, int lngAddr, int lngSize ) { if ( lngAddr + lngSize > m_MemSize ) return 2; //超出数据区 if ( m_bInit ) { Marshal.Copy( bytData, lngAddr, m_pwData, lngSize ); } else { return 1; //共享内存未初始化 } return 0; //写成功 } } internal class FileReader { const uint GENERIC_READ = 0x80000000; const uint OPEN_EXISTING = 3; System.IntPtr handle; [DllImport( "kernel32", SetLastError = true )] public static extern System.IntPtr CreateFile( string FileName, // file name uint DesiredAccess, // access mode uint ShareMode, // share mode uint SecurityAttributes, // Security Attributes uint CreationDisposition, // how to create uint FlagsAndAttributes, // file attributes int hTemplateFile // handle to template file ); [System.Runtime.InteropServices.DllImport( "kernel32", SetLastError = true )] static extern bool CloseHandle ( System.IntPtr hObject // handle to object ); public IntPtr Open( string FileName ) { // open the existing file for reading handle = CreateFile ( FileName, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0 ); if ( handle != System.IntPtr.Zero ) { return handle; } else { throw new Exception( "打开文件失败" ); } } public bool Close() { return CloseHandle( handle ); } } }