c# 进程间通信
一、进程间通讯的方式
进程间通讯的方式有很多,常用的有共享内存(内存映射文件、共享内存DLL、剪切板等)、命名管道和匿名管道、发送消息等几种方法来直接完成,另外还可以通过socket口、配置文件和注册表等来间接实现进程间数据通讯任务。以上这几种方法各有优缺点,具体到在进程间进行大数据量数据的快速交换问题上,则可以排除使用配置文件和注册表的方法;另外,由于管道和socket套接字的使用需要有网卡的支持,因此也可以不予考虑。这样,可供选择的通讯方式只剩下共享内存和发送消息两种。
二、发送消息实现进程间通讯
在后台代码文件中添加using System.Runtime.InteropServices;命名空间,System.Runtime.InteropServices提供了相应的类或者方法来支持托管/非托管模块间的
互相调用。其中几个比较重要的类:
DllImportAttribute : 该类提供对非托管动态链接库进行引用的方法,并告诉我们的编译器该程序的静态入口点是非托管的动态连接库,它的静态属性提供了对非托管动态链接库进行调用所必需的信息,作为最基本的要求,该类应该定义提供调用的非托管动态链接库的名称。
StructLayoutAttribute: 该类使得用户可以控制类或结构的数据字段的物理布局。
MarshalAsAttribute : 指示如何在托管代码和非托管代码之间封送数据。
发送消息时需要使用windows api 32函数
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr wnd,int msg,IntPtr wP,IntPtr lP); 此方法各个参数表示的意义
wnd:接收消息的窗口的句柄。如果此参数为HWND_BROADCAST,则消息将被发送到系统中所有顶层窗口,包括无效或不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口,但消息不被发送到子窗口。
msg:指定被发送的消息类型。例如:窗口消息WM_SYSCOMMAND:0x0112;键盘事件WM_KEYDOWN:0x0100; 拷贝数据WM_COPYDATA:0x004A;
wP:消息内容。例如:WM_SYSCOMMAND 中的参数:窗口最大化SC_MAXIMIZE:0xF030
lP:指定附加的消息指定信息。例如自定义结构体。
示例1:SendMessage(wnd, WM_SYSCOMMAND, (IntPtr)0xF030, (IntPtr)0);
向指定窗口wnd,发送窗口消息WM_SYSCOMMAND,消息内容是窗口最大化0xF030,附加消息为空。
示例2:SendMessage(wnd, WM_KEYDOWN, (IntPtr)msg, (IntPtr)0);向指定窗口wnd,发送窗口消息WM_KEYDOWN,消息内容是msg,附加消息为空。接收端添加键盘事件处理
public Form1()
{
InitializeComponent();
this.KeyDown += new KeyEventHandler(Form1_KeyDown);
}
//处理 WM_KEYDOWN事件
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
label1.Text = Convert.ToString(e.KeyData);
}
示例3:发送端:
public struct COPYDATASTRUCT
{
//用户自定义数据
public IntPtr dwData;
//消息长度
public int cbData;
//消息内容
/
/MarshalAs属性指示如何在托管代码和非托管代码之间封送数据
//UnmanagedType枚举值LPStr:单字节、空终止的 ANSI 字符串
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
IntPtr wnd = item.MainWindowHandle;
byte[] sarr = Encoding.Default. Box1.Text);进程间通信 共享内存
int len = sarr.Length;
COPYDATASTRUCT cds;
cds.dwData = (IntPtr)100;
cds.lpData = Box1.Text;
cds.cbData = len + 1;
SendMessage(wnd, WM_COPYDATA, (IntPtr)0, ref cds) ;
接收端:
const int WM_COPYDATA = 0x004A;
//Winform的消息处理,多数时候是通过事件处理程序进行的,但当没有对应的事件时通常的做法是声明DefWndProc或者WndProc或者IMessageFilter
protected override void DefWndProc(ref Message m)
{
switch (m.Msg)
{
case WM_COPYDATA:
COPYDATASTRUCT mystr = new COPYDATASTRUCT();
Type mytype = mystr.GetType();
mystr = (COPYDATASTRUCT)m.GetLParam(mytype);
label1.Text = "收到消息:
" + mystr.lpData + System.Environment.NewLine + "消息长度:
" + mystr.cbData + "Byte" + System.Environment.NewLine + "HWnd:" + m. HWnd + System.Environment.NewLine + "Msg:" + m.Msg + System.Environme nt.NewLine + "WParam:" + m.WParam + System.Environment.NewLine + "LPa ram:" + m.LParam + System.Environment.NewLine + "Result:" + m.Result;
break;
default:
base.DefWndProc(ref m);
break;
}
}
SendMessage与PostMessage之间的区别:SendMessage和PostMessage,这两个函数虽然功能非常相似,都是负责向指定的窗口发送消息,但是SendMessage() 函数发出消息后一直等到接收方的消息响应函数处理完之后才能返回,并能够得到返回值,在此期间发送方程序将被阻塞,SendMessage() 后面的语句不能被继续执行,即是说此方法是同步的。而PostMessage() 函数在发出消息后马上返回,其后语句能够被立即执行,但是无法获取接收方的消息处理返回值,即是说此方法是异步的。
三、共享内存实现进程间通讯
内存映射文件允许你保留一块地址空间,然后将该物理存储映射到这块内存空间中进行操作。物理存储是文件管理,而内存映射文件是操作系统级内存管理。
优势:
1.访问磁盘文件上的数据不需执行I/O操作和缓存操作(当访问文件数据时,作用尤
其显著);
2.让运行在同一台机器上的多个进程共享数据(单机多进程间数据通信效率最高);
利用文件与内存空间之间的映射,应用程序(包括多个进程)可以通过直接在内存中进行读写来修改文件。.NET Framework 4 用托管代码按照本机Windows函数访问内存映射文件的方式来访问内存映射文件,管理 Win32 中的内存映射文件。
有两种类型的内存映射文件:
1.持久内存映射文件
持久文件是与磁盘上的源文件关联的内存映射文件。在最后一个进程使用完此文件后,数据将保存到磁盘上的源文件中。这些内存映射文件适合用来处理非常大的源文件。
2.非持久内存映射文件
非持久文件是未与磁盘上的源文件关联的内存映射文件。当最后一个进程使用完此文件后,数据将丢失,并且垃圾回收功能将回收此文件。这些文件适用于为进程间通信 (IPC) 创建共享内存。
1)在多个进程之间进行共享(进程可通过使用由创建同一内存映射文件的进程所指派的公用名来映射到此文件)。
2)若要使用一个内存映射文件,则必须创建该内存映射文件的完整视图或部分视图。还可以创建内存映射文件的同一部分的多个视图,进而创建并发内存。为了使两个视图能够并发,必须基于同一内存映射文件创建这两个视图。
3)如果文件大于应用程序用于内存映射的逻辑内存空间(在 32 位计算机上为2GB),则还需要使用多个视图。
有两种类型的视图:流访问视图和随机访问视图。使用流访问视图可对文件进行顺序访问;在使用持久文件时,随机访问视图是首选方法。
.Net 共享内存内存映射文件原理:通过操作系统的内存管理器访问的,因此会自动将此文件分隔为多个页,并根据需要对其进行访问。您不需要自行处理内存管理。
C# .Net 共享内存演示代码如下:
//持久内存映射文件:基于现有文件创建一个具有指定公用名的内存映射文件
if (!File.Exists(@"c:\MemoryMappedFile.data"))
{
FileStream temp = File.Create(@"c:\MemoryMappedFile.d ata");
temp.Close();
}
if (mmf == null)
{
mmf = MemoryMappedFile.CreateFromFile(@"c:\MemoryMapp edFile.data", FileMode.Open, "MappedFile", length);
}
//通过指定的偏移量和大小创建内存映射文件视图服务器
using (var accessor = mmf.CreateViewAccessor(0, length)) //偏移量,可以控制数据存储的内存位置;大小,用来控制存储所占用的空间
{
//Marshal提供了一个方法集,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法。
byte[] temp = Encoding.UTF8.GetBytes(t.Text);
//修改内存映射文件视图
for (long i = 0; i < length; )
{
//char c = accessor.ReadChar(temp[i]);
if (i >= temp.Length)
{
break;
}
accessor.Write(i, temp[i]);
//i += Marshal.SizeOf(c[i]);
i++;
}
}
//另一个进程或线程可以,在系统内存中打开一个具有指定名称的现有内存映射文件
using (var mmf = MemoryMappedFile.OpenExisting("MappedFile"))
{
long length = 1024 * 125;
using (var accessor = mmf.CreateViewAccessor(0, lengt h))
{
byte[] tempbyte=new byte[length];
for (long i = 0; i < length;)
{
byte c = accessor.ReadByte(i);
tempbyte[i] = c;
i++;
}
textBox1.Text = Encoding.UTF8.GetString(tempbyte) ;
}
}
//非持久内存映射文件:未映射到磁盘上的现有文件的内存映射文件
tt = MemoryMappedFile.CreateOrOpen(guidtemp.ToString(), 1024 *10 24);
using (MemoryMappedViewStream stream = tt.CreateViewStrea m()) //创建文件内存视图流
{
var writer = new BinaryWriter(stream);
writer.Write(t.Text);
}