进程通信有多种方式,比如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;
}