操作系统很早就开始使用内存映射文件(Memory Mapped File)来作为进程间的共享存储区,这是一种非常高效的进程通讯手段。Win32 API中也包含有创建内存映射文件的函数,然而,这些函数都运行于非托管环境下,在.NET中只能通过平台调用机制来使用它们,用起来很不方便。幸运的是,.NET 4.0新增加了一个System.IO. MemoryMappedFiles命名空间,其中添加了几个类和相应的枚举类型,从而使我们可以很方便地创建内存映射文件。

1 内存映射文件原理

所谓内存映射文件,其实就是在内存中开辟出一块存放数据的专用区域,这区域往往与硬盘上特定的文件相对应。进程将这块内存区域映射到自己的地址空间中,访问它就象是访问普通的内存一样。

图 1 内存映射文件原理图

在.NET中,使用MemoryMappedFile对象表示一个内存映射文件,通过它的CreateFromFile()方法根据磁盘现有文件创建内存映射文件,调用这一方法需要提供一个与磁盘现有文件相对应的FileStream对象。

以下示例代码动态创建一个MyFile.dat文件,然后将其映射到系统内存中,设定容量为1M:

FileStream fs = new FileStream("MyFile.dat", FileMode.Create,

FileAccess.ReadWrite);

MemoryMappedFile memoryFile = MemoryMappedFile.CreateFromFile(fs, "MyFile", 1024*1024);

注意用于创建内存映射文件的文件流必须是可读写的

扩充阅读:

关于内存映射文件的容量

默认情况下,在调用MemoryMappedFile.CreateFromFile()方法时如果不指定文件容量,那么,创建的内存映射文件的容量等同于文件的大小。

在上面的示例代码中,由于磁盘文件是临时生成的,其长度为0,所以,必须在创建内存映射文件时同时指定其容量。

在设定内存映射文件的容量时,其值不能小于磁盘文件的现有长度,但可以比它大。但要注意这将导致一个戏剧化的结果:磁盘文件自动增长到声明的容量大小!

可以多次调用MemoryMappedFile.CreateFromFile(),每次传给它一个更大的容量数值以不断扩充磁盘文件的大小。

当不再使用一个MemoryMappedFile对象时,注意应该及时地调用其Dispose()方法释放它所占有的系统资源。因为MemoryMappedFile实际上对应着运行操作系统核心的核心对象,如果不及时关闭,会造成操作系统核心资源(比如句柄)的浪费,要等到MemoryMappedFile对象被CLR垃圾回收,或者整个进程中止时,这些资源才会被操作系统回收再利用。

另外,内存映射文件的容量其实是指最大允许分配给内存映射文件的内存存储区字节数,并不意味着系统会马上分配指定容量的内存。进程中访问这块映射到磁盘文件中的存储区时,操作系统如果发现其内容还未装入内存,就会从磁盘文件中装入相应内容到内存中。因此,不用担心声明一个大的内存映射文件容量会导致内存的浪费。

当MemoryMappedFile对象创建之后,我们并不能直接对其进行读写,必须通过一个MemoryMappedViewAccessor对象来访问这个内存映射文件。

MemoryMappedFile. CreateViewAccessor()方法可以创建MemoryMappedViewAccessor对象,而此对象提供了一系列读写的方法,用于向内存映射文件中读取和写入数据。

以下示例代码创建了一个内存映射文件访问对象并使用它写入数据:

FileStream fs =…;  //创建FileStream对象

MemoryMappedFile memoryFile=…;  //创建内存映射文件

//创建内存映射文件访问对象

MemoryMappedViewAccessor accessor=

memoryFile.CreateViewAccessor(0, 1024);

for (int i = 0; i < 1024; i+=2)

accessor.Write(i, ‘c’);

上述代码中要注意,在创建内存映射文件访问对象需要指定它所能访问的内存映射文件的内容范围,这个“范围”称为“内存映射视图(Memory Mapped View)”。可以将它与“放大镜”类比,当使用一个放大镜阅读书籍时,一次只能放大指定部分的文字。类似地,我们只能在内存映射视图所规定的范围内存取内存映射文件。

在上述代码中,我们看到内存映射视图对象accessor只提取了内存映射文件开头1024个字节的内容,然后,向其中写入了512个“c”字符。

当调用内存映射视图对象的Write()方法时,需要指明从哪个位置(即方法的第一个参数)开始写入数据,并且需要计算清楚要写入的数据占几个字节,这样,当写入下一个数据时,就知道应该从哪个位置开始。

注意,Write()方法中的位置是相对视图对象而非内存映射文件本身,因此,此位置数值再加上视图距内存映射文件开头的位置数据才是写入的数据在文件中的真实位置。

Write()方法有多个重载形式,可以向内存映射文件中写入多种类型的数据,但要注意计算清楚其写入的位置,避免造成数据覆盖问题。

类似地,内存映射视图对象提供了多个重载的Read()方法,可以从内存映射文件中读取数据。

比较有趣的是,在同一个进程中可以针对同一个内存映射文件创建多个视图对象,从而允许我们同时修改同一个文件的不同部分,在关闭视图对象时由操作系统保证将所有修改都写回到原始文件中。

下面我们来看一个示例。

2 在同一进程内同时读写同一内存映射文件

示例项目UseMMFInProcess运行时会在程序的当前目录下创建一个“MyFile.dat”文件,然后,创建了两个内存映射视图对象,分别向文件的前半部分和后半部分写入不同的数据,然后再从中读出来(图 2)。

图 2 示例项目UseMMFInProcess

这个示例展示的技术很基础,请读者自行查看源码。

3 使用内存映射文件在进程间传送值类型数据

在前面的例子中,内存映射文件直接与某个特定的磁盘文件相对应,事实上,我们也可以不用创建磁盘文件而直接使用Windows的分页文件。这种方式是实现进程间互传数据的典型方式。

调用MemoryMappedFile.CreateNew()或MemoryMappedFile.CreateOrOpen()方法可以在系统内存(System Memory)中直接创建一个内存映射文件,这个内存映射文件所对应的“物理文件”是Windows的系统分页文件。两个方法都需要给映射文件指定一个唯一的名称。不同之处在于CreateOrOpen ()方法在指定名称的映射文件存在时就直接将其返回给进程,而CreateNew()方法始终是新创建一个内存映射文件。

扩充阅读:

Windows的系统分页文件和休眠文件

默认情况下,在安装Windows的分区根目录下,会找到两个具有“隐藏”属性的pagefile.sys和hiberfil.sys文件,前者(pagefile.sys)就是Windows的分页文件,用于保存从物理内存中换出的内存页,我们可以用它的一部分来创建内存映射文件。后者(hiberfil.sys)则是“系统休眠”文件,当Windows启用了休眠功能时,就会在硬盘上找到这个文件,它的内容是系统休眠时物理内存中的数据,当计算机从休眠中“醒”过来时,通过从此文件中加载信息以恢复上次工作的状态。

内存映射文件创建好以后,可以如同前面介绍的方法一样创建视图对象,然后使用Read和Write系列方法存取。

只要指定同一个名字,那么,多个进程就可以使用同一个内存映射文件交换数据。示例UseMMFBetweenProcess展示了在两个进程间相互交换一个结构体变量的情况:

图 3 示例项目UseMMFBetweenProcess

两个进程要交换的数据格式如下:

public struct MyStructure

{

public int IntValue

{  get;  set;   }

public float FloatValue

{  get;  set;   }

}

启动UseMMFBetweenProcess程序的两个实例,在其中一个窗体上输入两个数字之后,点击“保存”按钮,然后在另一个进程的窗体上点击“提取”,可以看到另一个进程写入的信息出现在本进程的文本框中。

示例程序采用MemoryMappedFile.CreateOrOpen()方法创建或打开一个内存映射文件,然后调用MemoryMappedViewAccessor类的泛型方法Write<T>()和Read<T>()向内存映射文件中写入和读取数据。

注意,泛型方法Write<T>()和Read<T>()中的泛型参数T必须是值类型(比如整型int和结构体struct),特别地,对于用户自定义的结构体,要求其成员也必须是值类型。

例如,以下结构体将无法写入到内存映射文件中,因为其成员Info是string类型的,这是一个引用类型。

public struct ErrorStruct

{

public string Info;

}

之所以要求泛型参数不能是引用类型,其道理非常简单,如果结构体中的某个成员是引用类型,那么在程序运行时,计算机无法知道应该向内存映射文件中写入多少个字节,因为引用类型的变量所引用的对象位于托管堆中,其占用存储空间的大小不经过计算是难以确定的,而完成这个计算工作将花费不少的系统资源(想想一个对象可能又会引用到另一个对象就明白了),这会严重影响内存映射文件读写操作效率。

两个进程不能交换引用类型的数据,这个限制似乎还不小,但事实上,我们完成可以通过对象序列化技术来突破这个限制,在两个进程间交换任意大小的对象(只要内存映射文件有足够的容量)。请看下一小节的示例UseMMFBetweenProcess2。

4 利用序列化技术通过内存映射文件实现进程通讯

图4  示例:UseMMFBetweenProcess2

如图 4所示,运行示例程序的多个实例,加载图片并输入图片说明,点击相应按钮后,可以在多个进程间直接交换以下格式的信息:

   [Serializable]

class MyPic

{

public Image pic;       //图片

public string picInfo;  //图片信息说明

}

请注意这是一个引用类型的数据对象,并且它附加了可序列化“[Serializable]”的代码属性。

如果要向内存映射文件中序列化对象,必须将内存映射文件转换为可顺序读取的流。幸运的是,MemoryMappedFile类的CreateViewStream()方法可以创建一个MemoryMappedViewStream对象,通过它即可序列化对象,其代码框架如下:

//创建或打开内存映射文件

MemoryMappedFile memoryFile = MemoryMappedFile.CreateOrOpen(...);

//创建内存映射流

MemoryMappedViewStream stream = memoryFile.CreateViewStream();

//创建要在进程间交换的信息对象

MyPic obj =...;

//向内存映射流中序列化对象

IFormatter formatter = new BinaryFormatter();

stream.Seek(0, SeekOrigin.Begin);

formatter.Serialize(stream, obj);

请读者自行阅读源码了解更多技术细节。

=================================================

下载本文示例源码及PDF文档

原文地址:http://blog.csdn.net/bitfan/article/details/4438458

.NET 4.0中使用内存映射文件实现进程通讯的更多相关文章

  1. C++中使用内存映射文件处理大文件

    引言 文件操作是应用程序最为基本的功能之一,Win32 API和MFC均提供有支持文件处理的函数和类,常用的有Win32 API的CreateFile().WriteFile().ReadFile() ...

  2. C#内存映射文件学习[转]

    原文链接 内存映射文件是由一个文件到进程地址空间的映射. C#提供了允许应用程序把文件映射到一个进程的函(MemoryMappedFile.CreateOrOpen).内存映射文件与虚拟内存有些类似, ...

  3. Windows进程间通讯(IPC)----内存映射文件

    内存映射文件原理 内存映射文件是通过在虚拟地址空间中预留一块区域,然后通过从磁盘中已存在的文件为其调度物理存储器,访问此虚拟内存空间就相当于访问此磁盘文件了. 内存映射文件实现过程 HANDLE hF ...

  4. C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped 转 VC中进程与进程之间共享内存 .net环境下跨进程、高频率读写数据 使用C#开发Android应用之WebApp 分布式事务之消息补偿解决方案

    C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped 转 节点通信存在两种模型:共享内存(Shared memory)和消息传递(Messages passing). ...

  5. 内存映射文件详解-----C++实现

    先不说内存映射文件是什么.贴个代码先,. #include <iostream> #include <fcntl.h> #include <io.h> #inclu ...

  6. MemoryMappedFile 内存映射文件 msdn

    http://msdn.microsoft.com/zh-cn/library/dd997372%28v=vs.110%29.aspx 内存映射文件 .NET Framework 4.5 其他版本 1 ...

  7. C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped 转

    原文:C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped 转 节点通信存在两种模型:共享内存(Shared memory)和消息传递(Messages passing ...

  8. C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped

    节点通信存在两种模型:共享内存(Shared memory)和消息传递(Messages passing). 内存映射文件对于托管世界的开发人员来说似乎很陌生,但它确实已经是很远古的技术了,而且在操作 ...

  9. 内存映射文件(Memory-Mapped File)

    Java Memory-Mapped File所使用的内存分配在物理内存而不是JVM堆内存,且分配在OS内核. 1: 内存映射文件及其应用 - 实现一个简单的消息队列 / 计算机程序的思维逻辑 在一般 ...

随机推荐

  1. 通俗易懂之Tensorflow summary类 & 初识tensorboard

    前面学习的cifar10项目虽小,但却五脏俱全.全面理解该项目非常有利于进一步的学习和提高,也是走向更大型项目的必由之路.因此,summary依然要从cifar10项目说起,通俗易懂的理解并运用sum ...

  2. 云计算--MPI

    [root@localhost mpi]# mpicc -c base.c[root@localhost mpi]# mpicc -o base base.o[root@localhost mpi]# ...

  3. TAU调研咨询

    厦门宇能科技有限公司 GPRS-RTU/DTU.3/4G路由器.无线远程抄表.管网监控 咨询电话:0592-5710250 2017-07-04 9:36:16 您好,欢迎光临.请问有什么可以帮到您? ...

  4. Nginx安装方式探究

    Ubuntu 16.04(阿里云ECS),Nginx 1.10.3 (Ubuntu) 本文探究两种安装方式: 1.源码安装(手动) 2.APT安装(自动) 源码安装(手动) 步骤简介: 下载.解压.. ...

  5. Matplotlib安装感想

    刚刚安装完numpy,看完书又涉及到matplotlib,哎,安装它浪费了我很多时间,但收获很多呀 下面介绍一下具体的安装过程: (1)http://matplotlib.org/downloads. ...

  6. 前端JavaScript高级面试笔记

    一.ES6 1.模块化 ES6通过export和import实现模块化 ES6的模块化的基本规则或特点, 欢迎补充: 1:每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件 ...

  7. Ibatis.Net 表连接查询学习(五)

    IBatis.Net之多表查询 一.定制实际对应类的方式 首先配置多表的测试数据库,在之前Person表中增加一列"CountryId",新建一张Country表,两张表关系如下: ...

  8. mysql连接池模块

    如果不想程序在查询数据时卡死或等待过长时间,一般不推荐在node中开启一个连接后全部查询都用这个链接并且不关闭.因为node里面的mysql不像php里的那样会在完成查询后断开,只要不主动断开,连接一 ...

  9. tenaorflow函数(1)

    TensorFlow 将图形定义转换成分布式执行的操作, 以充分利用可用的计算资源(如 CPU 或 GPU.一般你不需要显式指定使用 CPU 还是 GPU, TensorFlow 能自动检测.如果检测 ...

  10. Oracle学习笔记:with as子查询用法

    With as短语,也叫做子查询部分(subquery factoring),可以定义一个SQL片断,该SQL片断会被整个SQL语句用到.该语句会在真正的查询之前预先构造一个临时表,之后可以多次使用做 ...