在實際開發中,一款應用可能有多個應用程式組成,那這款應用各個組成部分之間的數據交互就成了關鍵,如何才能快速高效的進行數據交互呢?如果是跨伺服器的進程交互,可以採用remoting,wcf,grpc等遠程過程調用技術(rpc),這種方式會經過網卡進行網絡傳輸,存在一定的數據轉換及網絡傳輸等性能消耗。如果是同一台伺服器的進程間數據交互,也採用這種遠程過程調用技術,則不是最優方案。那如何才能繞過網絡來進行跨進程數據交互呢?答案就是“共享內存”,今天我們就以一個簡單的小例子,簡述進程間如何通過共享內存進行數據交互的應用,僅供學習分享使用,如有不足之處,還請指正。

什麼是共享內存?
在作業系統中,系統會為每一個進程分配一塊獨立的內存,以供進程運行程式和存儲數據,各個進程間的內存相互獨立,互不干擾,以保證程式的穩定有序的運行。雖然這種進程的內存保護機制在很大程度上保證了數據安全和程式穩定,但在需要進行交互的進程之間,也形成了難以逾越的壁壘。幸好作業系統也考慮到了這種情況,那就是共享內存。共享內存(shared memory)是一種 進程間通信(ipc) 機制,允許多個進程共享同一塊物理內存,從而提高數據交換效率。相比其他 ipc 方式(如管道、消息隊列等),共享內存具有 速度快、低開銷 的優勢,因為數據直接存儲在內存中,而無需通過內核進行數據拷貝。
net中的共享內存
在.net平台,共享內存通過memorymappedfile來實現,內存映射文件允許你保留一塊地址空間,然後將該物理存儲映射到這塊內存空間中進行操作。物理存儲是文件管理,而內存映射文件是**作業系統級內存管理。**內存映射文件技術主要涉及的知識點如下所示:
- 創建共享內存:內存映射文件的創建有兩種方式,一種是直接創建,通過memorymappedfile.createnew方法來實現;一種是通過已經存在的文件進行創建,它通過調用memorymappedfile.createfromfile來實現。
- 共享內存訪問器:在.net中,通過memorymappedviewaccessor來訪問共享內存,它通過memorymappedfile的實例對象的createviewaccessor來創建。
- 讀取寫入方式:共享內存中以byte數組的方式存儲數據,可以通過訪問器的readarray和writearray來讀取和寫入字節數組。
內存映射文件創建
內存映射文件有兩種創建形式:
第1種是直接創建,它通過memorymappedfile提供的靜態方法createnew來創建,如下所示:

第2種是通過memorymappedfile提供的靜態方法createfromfile來創建,它是利用已經存在的文件或文件流進行創建,如下所示:

打開已存在的內存映射文件,它通過memorymappedfile提供的靜態方法openexist來實現,如下所示:

創建或打開內存映射文件,它通過memorymappedfile提供的靜態方法createoropen來實現,如下所示:

內存映射文件訪問器
內存映射文件訪問器,主要用於操作共享內存,它通過memorymappedfile實例對象的createviewaccessor來實現,如下所示:

內存映射文件資源釋放
memorymappedfile實現了idisposable接口,直接調用它的dispose方法即可。
共享內存應用步驟
在本實例中,主要有兩個winform可執行程式組成,分別進行讀共享內存/寫共享內存,運行時兩個程式同時運行,如下所示:

主要居間固定格式的struct數據讀寫共享內存,和不定長的數據讀寫共享內存。
內存映射文件對象創建
本實例在寫共享內存時創建內存映射文件,讀共享內存時打開內存映射文件,如下所示:
/// <summary>
/// 创建或打开共享内存
/// </summary>
public void CreateOrOpenSharedMemory()
{
this.memoryMapped = MemoryMappedFile.CreateOrOpen(this.MapName, this.capacity, MemoryMappedFileAccess.ReadWriteExecute, MemoryMappedFileOptions.None, HandleInheritability.Inheritable);
this.memoryAccessor = this.memoryMapped.CreateViewAccessor();
}
/// <summary>
/// 从文件创建共享内存
/// </summary>
public void CreateFromFileShareMemory()
{
this.memoryMapped = MemoryMappedFile.CreateFromFile(new FileStream(@"", FileMode.Create), this.MapName, this.capacity, MemoryMappedFileAccess.ReadWriteExecute, HandleInheritability.Inheritable, true);
this.memoryAccessor = this.memoryMapped.CreateViewAccessor();
}
/// <summary>
/// 打开已存在的共享内存
/// </summary>
public void OpenShareMemory()
{
this.memoryMapped = MemoryMappedFile.OpenExisting(this.MapName);
this.memoryAccessor = this.memoryMapped.CreateViewAccessor();
}
不定長度字節讀寫
不定長度的字節數組方式讀取,以用戶打開的圖片為例,將圖片路徑和圖片內容以byte數組的形式通過共享內存進行數據交換。它們在共享內存中的存儲格式如下圖所示:

實體模型imagedata
首先創建實體模型imagedata,它的主要功能是將圖片對象bitmap和byte數組之間進行轉換,如下所示:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Okcoder.ShareMemory.Common
{
public class ImageData
{
public string ImageFullPath { get; set; }
public Bitmap ImageContent { get; set; }
/// <summary>
/// 从对象转换成数组
/// </summary>
/// <returns></returns>
public byte[] ImageToBytes()
{
var byteFullPath = Encoding.UTF8.GetBytes(this.ImageFullPath);
MemoryStream stream = new MemoryStream();
int lenFullPath = byteFullPath.Length;
byte[] byteFullPathLen = BitConverter.GetBytes(lenFullPath);
ImageContent.Save(stream, ImageContent.RawFormat);
var byteImageContent = stream.ToArray();
int lenImageContent = byteImageContent.Length;
byte[] byteImageContentLen = BitConverter.GetBytes(lenImageContent);
byte[] total = new byte[4 + lenFullPath + 4 + lenImageContent];
byteFullPathLen.CopyTo(total, 0);
byteFullPath.CopyTo(total, 4);
byteImageContentLen.CopyTo(total, 4 + lenFullPath);
byteImageContent.CopyTo(total, 4 + lenFullPath + 4);
stream.Close();
stream.Dispose();
return total;
}
/// <summary>
/// 从对象转换成对象
/// </summary>
/// <param name="bytes"></param>
public void BytesToImage(byte[] bytes)
{
int lenFullPathLen = BitConverter.ToInt32(bytes, 0);
var byteFullPath = new byte[lenFullPathLen];
bytes.Skip(4).Take(lenFullPathLen).ToArray().CopyTo(byteFullPath, 0);
this.ImageFullPath = Encoding.UTF8.GetString(byteFullPath);
int lenImageContent = BitConverter.ToInt32(bytes, 4 + lenFullPathLen);
var byteImageContent = new byte[lenImageContent];
bytes.Skip(4 +lenFullPathLen+4).Take(lenImageContent).ToArray().CopyTo(byteImageContent,0);
MemoryStream stream = new MemoryStream(byteImageContent);
this.ImageContent = (Bitmap)Image.FromStream(stream);
stream.Close();
stream.Dispose();
}
}
}
共享內存字節讀寫
不定長度的共享內存的byte讀/寫,如下所示:
/// <summary>
/// 读取字节
/// </summary>
/// <returns></returns>
public byte[] ReadMemoryWithBytes()
{
byte[] bytes = new byte[this.capacity];
this.memoryAccessor.ReadArray<byte>(0, bytes, 0, bytes.Length);
return bytes;
}
/// <summary>
/// 写入字节
/// </summary>
/// <param name="bytes"></param>
public void WriteMemoryWithBytes(byte[] bytes)
{
this.memoryAccessor.WriteArray<byte>(0, bytes, 0, bytes.Length);
}
其中capacity為內存映射文件的默認容量,大小為10m。
寫共享內存調用
首先在okcoder.sharememory.writer項目頁面,用戶選擇一張圖片,並顯示在ui頁面上,如下所示:
private void btnBrowser_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Title = "请选择图片";
openFileDialog.Filter = "PNG图片|*.png|JPG图片|*.jpg";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
string fileName = openFileDialog.FileName;
this.pictureBox1.Image = Bitmap.FromFile(fileName);
this.pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
this.txtImagePath.Text = fileName;
}
}
然後點擊“咻一下”按鈕封裝imagedata對象,並轉換成byte數組,然後寫入共享內存 ,如下所示:
private void btnWriteMemory_Click(object sender, EventArgs e)
{
//定义ImageData并转换成字节数组
ImageData imageData = new ImageData();
imageData.ImageFullPath = this.txtImagePath.Text;
imageData.ImageContent = (Bitmap)this.pictureBox1.Image;
byte[] bytes = imageData.ImageToBytes();
//定义共享内存帮助对象并打开共享内存
ShareMemoryHelper helper = new ShareMemoryHelper();
helper.CreateOrOpenSharedMemory();
//以字节方式写入共享内存
helper.WriteMemoryWithBytes(bytes);
}
讀共享內存調用
在okcoder.sharememory.reader項目頁面,用戶點擊“咻一下”按鈕,讀取共享內存,並轉換內imagedata,然後顯示在ui頁面上,如下所示:
private void btnRead_Click(object sender, EventArgs e)
{
//定义共享内存帮助对象并打开共享内存
ShareMemoryHelper helper = new ShareMemoryHelper();
helper.OpenShareMemory();
//以字节方式读取
byte[] bytes = helper.ReadMemoryWithBytes();
ImageData imageData = new ImageData();
//转换字节到ImageData对象
imageData.BytesToImage(bytes);
//页面赋值
this.txtImagePath.Text = imageData.ImageFullPath;
this.pictureBox1.Image = imageData.ImageContent;
this.pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
}
這樣通過咻咻兩下,就可以將一個程式用戶選擇的圖片,通過共享內存,傳遞到另一個應用程式。神不神奇,意不意外!
固定長度內容讀寫
在實際應用中,共享內存支持值類型的結構體,引用類型的byte數組的數據床底,結構體中的屬性排序,就是它在內存中的順序。
定義實體模型
首先定義結構體testdata,為了能夠讓結構體在指針之間進行轉換,必須通過structlayout特性將結構體聲明為可序列化,如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Okcoder.ShareMemory.Common
{
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct TestData
{
/// <summary>
/// Id,长度为4个字节
/// </summary>
public int Id;
/// <summary>
/// 年龄长度为4个字节
/// </summary>
public int Age;
/// <summary>
/// 分数 10个元素
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x0A)]
public int[] Scores;
public TestData()
{
this.Id = 0;
this.Age = 0;
this.Scores = new int[10];
}
public override string ToString()
{
return $"Id={this.Id},Age={this.Age},Scores={string.Join(",",this.Scores)}";
}
}
}
寫結構體類型
首先通過marshal申請內存空間,並通過marshal的structuretoptr方法將結構體存儲到申請的內存中,並指針指向開始位置,然後再通過marshal的copy方法將指針指向的內存複製到byte數組中,然後寫入到共享內存,最後通過marshal的freehglobal釋放申請的指針,如下所示:
/// <summary>
/// 写入结构体
/// </summary>
/// <param name="data"></param>
public void WriteMemoryWithStruct(TestData data)
{
//获取结构体的长度
int len = Marshal.SizeOf(typeof(TestData));
byte[] bytes = new byte[len];
//申请内存并获取指针
IntPtr p = Marshal.AllocHGlobal(len);
//结构体写入内存
Marshal.StructureToPtr(data, p, false);
//将内存复制到数组
Marshal.Copy(p, bytes, 0, len);
//写入数组共享到内存
this.memoryAccessor.WriteArray<byte>(0, bytes, 0, bytes.Length);
//释放内存
Marshal.FreeHGlobal(p);
//释放指针
p = IntPtr.Zero;
}
讀取結構體類型
首先通過marshal的allochglobal方法申請內存空間,並將共享內存的字節數組讀取出來並複製到申請的內存中,並指針指向開始位置,然後再通過marshal的指針轉結構體方法ptrtostructure,轉換成結構體對象,最後通過marshal的freehglobal釋放申請的指針,如下所示:
/// <summary>
/// 读取结构体
/// </summary>
/// <returns></returns>
public TestData ReadMemoryWithStruct()
{
//获取结构体类型的长度
int len = Marshal.SizeOf(typeof(TestData));
byte[] bytes = new byte[len];
//申请内存
IntPtr p = Marshal.AllocHGlobal(len);
//从共享内存读取数据
this.memoryAccessor.ReadArray<byte>(0, bytes, 0, bytes.Length);
//将字节数组复制到指针
Marshal.Copy(bytes, 0, p, len);
//指针转换到结构体
TestData data = (TestData)Marshal.PtrToStructure(p, typeof(TestData));
//释放内存
Marshal.FreeHGlobal(p);
//释放指针
p = IntPtr.Zero;
return data;
}
寫共享內存調用
在okcoder.sharememory.writer項目頁面,用戶點擊struct按鈕,然後封裝testdata實例對象,然後寫入共享內存,如下所示:
private void btnWriteStruct_Click(object sender, EventArgs e)
{
//定义TestData并赋值
TestData testData = new TestData();
testData.Id = 100;
testData.Age = 20;
for (int i = 0; i < 10; i++)
{
testData.Scores[i] = i + 60;
}
//定义共享内存帮助对象并打开共享内存
ShareMemoryHelper helper = new ShareMemoryHelper();
helper.CreateOrOpenSharedMemory();
//以结构体的形式写入共享内存
helper.WriteMemoryWithStruct(testData);
}
讀共享內存調用
在okcoder.sharememory.reader項目頁面,用戶點擊struct按鈕,從共享內存獲取結構體並彈框顯示,如下所示:
private void btnReadStruct_Click(object sender, EventArgs e)
{
//定义共享内存帮助对象并打开共享内存
ShareMemoryHelper helper = new ShareMemoryHelper();
helper.OpenShareMemory();
//读取结构体
TestData testData = helper.ReadMemoryWithStruct();
MessageBox.Show(testData.ToString());
}
實例演示
首先是不定長度的圖片在進程間交換數據,如下所示:

固定長度的結構體類型在進程間交換數據,如下所示:

以上就是《推薦一款進程間高速交換數據的解決方案》的全部內容,旨在拋磚引玉,一起學習,共同進步!
