3. 跨进程共享内存:内存映射文件
我们已经实现了跨线程和跨进程的共享资源访问同步。但是传递/接收消息还需要共享资源。对于线程来说,只需要声明一个类成员变量就可以了。但是对于跨进程来说,我们需要使用到 win32 API 提供的内存映射文件(Memory Mapped Files,简称MMF)。使用 MMF和使用 win32 信号量差不多。我们需要先调用 CreateFileMapping 方法来创建一个内存映射文件的句柄:
01 | [DllImport( "Kernel32.dll" ,EntryPoint= "CreateFileMapping" , |
02 | SetLastError= true ,CharSet=CharSet.Unicode)] |
03 | internal static extern IntPtr CreateFileMapping( uint hFile, |
04 | SecurityAttributes lpAttributes, uint flProtect, |
05 | uint dwMaximumSizeHigh, uint dwMaximumSizeLow, string lpName); |
07 | [DllImport( "Kernel32.dll" ,EntryPoint= "MapViewOfFile" , |
08 | SetLastError= true ,CharSet=CharSet.Unicode)] |
09 | internal static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, |
10 | uint dwDesiredAccess, uint dwFileOffsetHigh, |
11 | uint dwFileOffsetLow, uint dwNumberOfBytesToMap); |
13 | [DllImport( "Kernel32.dll" ,EntryPoint= "UnmapViewOfFile" , |
14 | SetLastError= true ,CharSet=CharSet.Unicode)] |
15 | [ return : MarshalAs( UnmanagedType.VariantBool )] |
16 | internal static extern bool UnmapViewOfFile(IntPtr lpBaseAddress); |
01 | public static MemoryMappedFile CreateFile( string name, |
02 | FileAccess access, int size) |
05 | throw new ArgumentException( "Size must not be negative" , "size" ); |
07 | IntPtr fileMapping = NTKernel.CreateFileMapping(0xFFFFFFFFu, null , |
08 | ( uint )access,0,( uint )size,name); |
09 | if (fileMapping == IntPtr.Zero) |
10 | throw new MemoryMappingFailedException(); |
12 | return new MemoryMappedFile(fileMapping,size,access); |
我们希望直接使用 pagefile 中的虚拟文件,所以我们用 -1(0xFFFFFFFF) 来作为文件句柄来创建我们的内存映射文件句柄。我们也指定了必填的文件大小,以及相应的名称。这样其他进程就可以通过这个名称来同时访问该映射文件。创建了内存映射文件后,我们就可以映射这个文件不同的部分(通过偏移量和字节大小来指定)到我们的进程地址空间。我们通过 MapViewOfFile 系统方法来指定:
01 | public MemoryMappedFileView CreateView( int offset, int size, |
02 | MemoryMappedFileView.ViewAccess access) |
04 | if ( this .access == FileAccess.ReadOnly && access == |
05 | MemoryMappedFileView.ViewAccess.ReadWrite) |
06 | throw new ArgumentException( |
07 | "Only read access to views allowed on files without write access" , |
10 | throw new ArgumentException( "Offset must not be negative" , "size" ); |
12 | throw new ArgumentException( "Size must not be negative" , "size" ); |
13 | IntPtr mappedView = NTKernel.MapViewOfFile(fileMapping, |
14 | ( uint )access,0,( uint )offset,( uint )size); |
15 | return new MemoryMappedFileView(mappedView,size,access); |
在不安全的代码中,我们可以将返回的指针强制转换成我们指定的类型。尽管如此,我们不希望有不安全的代码存在,所以我们使用 Marshal 类来从中读写我们的数据。偏移量参数是用来从哪里开始读写数据,相对于指定的映射视图的地址。
01 | public byte ReadByte( int offset) |
03 | return Marshal.ReadByte(mappedView,offset); |
05 | public void WriteByte( byte data, int offset) |
07 | Marshal.WriteByte(mappedView,offset,data); |
10 | public int ReadInt32( int offset) |
12 | return Marshal.ReadInt32(mappedView,offset); |
14 | public void WriteInt32( int data, int offset) |
16 | Marshal.WriteInt32(mappedView,offset,data); |
19 | public void ReadBytes( byte [] data, int offset) |
21 | for ( int i=0;i<data.Length;i++) |
22 | data[i] = Marshal.ReadByte(mappedView,offset+i); |
24 | public void WriteBytes( byte [] data, int offset) |
26 | for ( int i=0;i<data.Length;i++) |
27 | Marshal.WriteByte(mappedView,offset+i,data[i]); |
但是,我们希望读写整个对象树到文件中,所以我们需要支持自动进行序列化和反序列化的方法。
01 | public object ReadDeserialize( int offset, int length) |
03 | byte [] binaryData = new byte [length]; |
04 | ReadBytes(binaryData,offset); |
05 | System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter |
06 | = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); |
07 | System.IO.MemoryStream ms = new System.IO.MemoryStream( |
08 | binaryData,0,length, true , true ); |
09 | object data = formatter.Deserialize(ms); |
13 | public void WriteSerialize( object data, int offset, int length) |
15 | System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter |
16 | = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); |
17 | byte [] binaryData = new byte [length]; |
18 | System.IO.MemoryStream ms = new System.IO.MemoryStream( |
19 | binaryData,0,length, true , true ); |
20 | formatter.Serialize(ms,data); |
23 | WriteBytes(binaryData,offset); |
请注意:对象序列化之后的大小不应该超过映射视图的大小。序列化之后的大小总是比对象本身占用的内存要大的。我没有试过直接将对象内存流绑定到映射视图,那样做应该也可以,甚至可能带来少量的性能提升。
| |