进程通信有多种方式,比如socket、管道、共享内存。c#直接提供了共享内存的相关库,但直接使用起来还是不太方便,需要使用Marshal处理内存对齐以及托管非托管转换的问题,本文提供一种,将上述操作包装,借助反射的手段,实现通过类和属性的方式使用共享内存。
一、.net共享内存对象
MemoryMappedFile是.net的共享内存对象,一般通过MemoryMappedFile.CreateNew的方式创建共享内存,最简单的创建方式是,传入名称及大小。
//申请共享内存
MemoryMappedFile mmf = MemoryMappedFile.CreateNew(name, size);
写共享内存,在指定位置,通过数组写入数据。
using (var accessor = mmf.CreateViewAccessor(0, Size))
{
accessor.WriteArray(field.Position, array, 0, array.Length);
}
读共享内存,在指定位置读取数据到数组。
using (var accessor = mmf.CreateViewAccessor(0, Size))
{
accessor.ReadArray(field.Position, buffer, 0, buffer.Length);
}
二、定义上层共享内存对象
1、共享内存对象
class SharedMemory : IDisposable
{
/// <summary>
/// 共享内存名称(唯一标识)
/// </summary>
public string Name { private set; get; }
/// <summary>
/// 大小
/// </summary>
public long Size { private set; get; }
/// <summary>
/// 创建共享内存
/// </summary>
/// <param name="name">共享内存名称(唯一标识)</param>
/// <param name="sharedMemoryFieldsMapping">映射实体对象</param>
/// <returns>共享内存对象</returns>
public static SharedMemory Create(string name, ISharedMemoryPropertiesMapping sharedMemoryFieldsMapping);
/// <summary>
/// 关闭共享内存
/// </summary>
public void Close();
/// <summary>
/// 关闭共享内存
/// </summary>
public void Dispose();
}
2、映射实体接口
继承此接口的类即可以作为共享内存的映射数据。
//共享内存映射接口,实现此接口的对象其属性可以直接与共享内存映射。
//规则是,属性类型只支持结构体以及byte[]数组
//属性排列顺序即内存排列顺序
interface ISharedMemoryPropertiesMapping
{
event Action<object, SharedMemoryPropertyChangedEventArgs> PropertyChanged;
event Func<string, object> GetPropertyValue;
}
public class SharedMemoryPropertyChangedEventArgs
{
public object Value { set; get; }
public string FieldName { set; get; }
}
//内存映射接口简化实现
abstract class SharedMemoryFieldsMapping : ISharedMemoryPropertiesMapping
{
protected void NotifyFieldChanged(object value, [System.Runtime.CompilerServices.CallerMemberName] string filedName = "");
protected object NotifyGetFieldValue(object defalut, [System.Runtime.CompilerServices.CallerMemberName]string filedName = "");
public event Action<object, SharedMemoryPropertyChangedEventArgs> PropertyChanged;
public event Func<string, object> GetPropertyValue;
}
三、使用方法
通过上述方式实现的上层共享内存对象,使用共享内存将变得非常简单。只需如下步骤:
1、定义共享内存实体
继承SharedMemoryFieldsMapping,定义需要的属性,set调用NotifyFieldChanged传入value,get调用NotifyGetFieldValue传入属性类型的缺省值 ,如下所示:
class SharedData : SharedMemoryFieldsMapping
{
public double CurentTime{set{ NotifyFieldChanged(value);}get{return (double)NotifyGetFieldValue(double.NaN);} }
public double Duration { set { NotifyFieldChanged(value); } get { return (double)NotifyGetFieldValue(double.NaN); } }
public int IsPause { set { NotifyFieldChanged(value); } get { return (int)NotifyGetFieldValue(1); } }
public int IsMute { set { NotifyFieldChanged(value); } get { return (int)NotifyGetFieldValue(1); } }
public int Heartbeat { set { NotifyFieldChanged(value); } get { return (int)NotifyGetFieldValue(0); } }
public int Flag { set { NotifyFieldChanged(value); } get { return (int)NotifyGetFieldValue(0); } }
public int Length { set { NotifyFieldChanged(value); } get { return (int)NotifyGetFieldValue(0); } }
[SharedMemeory(Size = 1024)]
public byte[] Input { set { NotifyFieldChanged(value); } get { return (byte[])NotifyGetFieldValue(null); } }
[SharedMemeory(Size = 1024)]
public byte[] Output { set { NotifyFieldChanged(value); } get { return (byte[])NotifyGetFieldValue(null); } }
}
注:(1)、属性的类型必须是值类型即元类型或结构体,引用类型只支持数组且必须设置数长度。
(2)、属性排列顺序即内存排列顺序
(3)、当前版本内存对齐为1,在c++中的内存对齐需要设置为#pragma pack(1)
上述对象在c++程序中可以直接使用如下数据结构对应:
typedef struct {
double CurentTime;//当前时间
double Duration;//总时长
int IsPause;
int IsMute;
int Heartbeat;//心跳
int Flag;
int Length;
char Input[1024];
char Output[1024];
}SharedData;
2、创建共享内存
初始化一个共享内存实体对象,然后调用SharedMemory.Create,传入共享内存的名称以及共享内存实体对象即创建完成,此后直接读写实体的属性即是在读写共享内存。
SharedData dataShared = new SharedData();
var sharedMemory = SharedMemory.Create(sharedName, dataShared);
3、销毁共享内存
使用完成后需要销毁共享内存,否则可能会一直存在操作系统中,就算进程退出,共享内存可能依然存在。
sharedMemory.Dispose();
四、使用实例
这里的实例只用了互斥锁来控制资源有序访问,真实使用场景一般是需要用条件变量或者信号量来做访问控制的。下面是具体代码:
1、父进程
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace testshared
{
class Program
{
//内存映射的实体 属性排列顺序与内存排列直接相关
class SharedData : SharedMemoryFieldsMapping
{
public double CurentTime { set { NotifyFieldChanged(value); } get { return (double)NotifyGetFieldValue(double.NaN); } }
public double Duration { set { NotifyFieldChanged(value); } get { return (double)NotifyGetFieldValue(double.NaN); } }
public int IsPause { set { NotifyFieldChanged(value); } get { return (int)NotifyGetFieldValue(1); } }
public int IsMute { set { NotifyFieldChanged(value); } get { return (int)NotifyGetFieldValue(1); } }
public int Heartbeat { set { NotifyFieldChanged(value); } get { return (int)NotifyGetFieldValue(0); } }
public int Flag { set { NotifyFieldChanged(value); } get { return (int)NotifyGetFieldValue(0); } }
public int Length { set { NotifyFieldChanged(value); } get { return (int)NotifyGetFieldValue(0); } }
[SharedMemeory(Size = 1024)]
public byte[] Info { set { NotifyFieldChanged(value); } get { return (byte[])NotifyGetFieldValue(null); } }
}
static void Main(string[] args)
{
Console.WriteLine("父进程启动");
//创建共享内存
SharedData data = new SharedData();
var sharedMemory = SharedMemory.Create("testshared", data);
//创建互斥锁
Mutex mutex = new Mutex(false,"testsharedMutex");
Console.WriteLine("父进程发送:hello word!");
//加锁写入消息
mutex.WaitOne();
data.Info= Encoding.ASCII.GetBytes("hello word!");
mutex.ReleaseMutex();
//启动子进程并将互斥锁名称传入
var process = new Process();
process.StartInfo.FileName = "ctestshared.exe";
process.StartInfo.Arguments = "testsharedMutex";
process.Start();
Thread.Sleep(2000);
Console.WriteLine("父进程发送:exit");
//加锁写入消息
mutex.WaitOne();
data.Info = Encoding.ASCII.GetBytes("exit\0");
mutex.ReleaseMutex();
//等待子进行退出
process.WaitForExit();
//销毁共享内存及互斥锁
sharedMemory.Dispose();
mutex.Dispose();
Console.WriteLine("父进程退出");
}
}
}
2、子进程
#include <iostream>
#include<Windows.h>
typedef struct _Shared {
double CurentTime;//当前时间
double Duration;//总时长
int IsPause;
int IsMute;
int Heartbeat;//心跳
int Flag;
int Length;
char Info[1024];
}Shared;
int main(int argc, char** argv)
{
if (argc < 2)
return -1;
printf("子进程启动\n");
//创建父进程的互斥锁
auto mtx = CreateMutexA(NULL, FALSE, argv[1]);
//打开共享内存
HANDLE hfile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, "testshared");
if (NULL == hfile)
{
MessageBoxA(0, "不能打开共享内存", "提示", MB_OK);
return -1;
}
//映射地址
Shared* shared;
shared = (Shared*)MapViewOfFile(hfile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(Shared));
//进入互斥锁并读取数据
WaitForSingleObject(mtx, INFINITE);
printf("子进程接收:%s\n", shared->Info);
ReleaseMutex(mtx);
while (1)
{
Sleep(30);
//进入互斥锁并读取数据
WaitForSingleObject(mtx, INFINITE);
if (strcmp(shared->Info, "exit")==0)
{
printf("子进程接收:%s\n", shared->Info);
ReleaseMutex(mtx);
break;
}
ReleaseMutex(mtx);
}
//销毁资源
CloseHandle(hfile);
CloseHandle(mtx);
printf("子进程退出\n");
return 0;
}