実際の開発では、アプリケーションは複数のアプリケーションで構成されている可能性があり、アプリケーションの各コンポーネント間のデータ相互作用が鍵となり、どのように迅速かつ効率的にデータ相互作用を行うことができますか?サーバ間のプロセスの相互作用であれば、Remoting、WCF、GRPCなどのリモートプロシージャコール技術(RPC)を採用することができ、この方法はネットワークカードを介してネットワーク転送され、一定のデータ変換やネットワーク転送などのパフォーマンスコストが存在する。同じサーバ上のプロセス間データの相互作用である場合、このリモートプロシージャコール技術も使用され、最適なソリューションではありません。ネットワークをバイパスしてクロスプロセスデータをやりとりするには?答えは“共有メモリ”であり、今日は簡単な小さな例を取り、共有メモリを介してプロセス間のデータ相互作用のアプリケーションを簡単に説明し、使用を学ぶためにのみ、欠点がある場合は、指摘してください。

共有メモリーとは?
オペレーティングシステムでは、システムは各プロセスに独立したメモリを割り当て、プロセスがプログラムを実行し、データを格納し、各プロセス間のメモリは互いに独立して干渉しないため、プログラムの安定した秩序ある実行を確保します。このプロセスのメモリ保護メカニズムは、データセキュリティとプログラムの安定性を大きく保証しますが、相互作用を必要とするプロセス間には克服できない障壁があります。ありがたいことに、オペレーティングシステムもこれを考慮しています。共有メモリです。共有メモリShared Memoryは、複数のプロセスが同じ物理メモリを共有できるようにする ** プロセス間通信IPC ** メカニズムです。これにより、データ交換の効率が向上します。他のIPC 方式(パイプライン、メッセージキューなど)と比較して、共有メモリは、カーネルを介してデータをコピーするのではなく、データが直接メモリに格納されるため、最も高速で低オーバーヘッドの利点があります。
NETでの共有メモリ
NETプラットフォームでは、共有メモリはMemoryMappedFileを介して実装されます。メモリマップファイルでは、アドレス空間の一部を保持し、その物理ストレージをこのメモリ空間にマップして操作できます。物理ストレージはファイル管理であり、メモリマップファイルは ** オペレーティングシステムレベルのメモリ管理です。**メモリマップファイルに关わる主な点は以下の通りである。
- 共有メモリの作成:メモリマップファイルの作成には2つの方法があります。1つはMemoryMappedFile.CreateNewメソッドを使用して直接作成する方法です。もう1つは、MemoryMappedFile.CreateFromFileを呼び出して既存のファイルから作成する方法です。
- 共有メモリアクセサ:. NETでは、共有メモリはMemoryMappedViewAccessorを介してアクセスされます。MemoryMappedFileのインスタンスオブジェクトのCreateViewAccessorで作成されます。
- 読み取りと書き込み:共有メモリにはByte配列としてデータが格納され、バイト配列はアクセサのReadArrayとWriteArrayを介して読み書きできます。
メモリ·マップ·ファイル作成{{めもりまっぷふぁいるさくせい}}
メモリ·マップ·ファイルは、次の2つの形式で作成されます。
1つ目は直接作成で、MemoryMappedFileが提供する静的メソッドCreateNewを使って作成されます。

2つ目はMemoryMappedFileが提供する静的メソッドCreateFromFileを使用して作成され、既存のファイルまたはファイルストリームを使用して作成されます。

MemoryMappedFileが提供する静的メソッドOpenExistを使用して、既存のメモリマップファイルを開きます。

MemoryMappedFileが提供する静的メソッドCreateOrOpenを使用して、次のようにメモリマップファイルを作成または開きます。

メモリマップファイルアクセサ
主に共有メモリを操作するために使用されるMemory Mapファイルアクセサは、MemoryMappedFileインスタンスオブジェクトのCreateViewAccessorによって実装されます。

メモリーマップファイルリソース解放
MemoryMappedFileはIDisposableインターフェイスを実装し、Disposeメソッドを直接呼び出すことができる。
共有メモリ手顺
この例では、主に2つのWin Form実行可能プログラムがあり、それぞれ読み取り共有メモリ/書き込み共有メモリを持ち、実行時には2つのプログラムが同時に実行されます。

主に固定形式構造体データ読み書き共有メモリと可変長データ読み書き共有メモリについて紹介します。
メモリ·マップ·ファイルオブジェクト作成{{めもりまっぷふぁいるおぶじぇくとさくせい}
この例では、次のように共有メモリーの書き込み時にメモリー·マップ·ファイルを作成し、共有メモリーの読み取り時にメモリー·マップ·ファイルを開きます。
/// <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();
}
}
}
共有メモリのバイト読み取りと書き込み
次のように、不定長共有メモリのバイトの読み取り/書き込みを行います。
/// <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);
}
ここで、capはメモリーマップファイルのデフォルト容量で、サイズは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;
}
}
次に、“Push”ボタンをクリックして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プロジェクトページで、ユーザーは“Push”ボタンをクリックして共有メモリを読み取り、内部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;
}
これにより、あるプログラムのユーザが選択した画像を共有メモリを介して別のアプリケーションに転送することができます。神は驚くべきことではない。
固定長コンテンツの読み取りと書き込み
実際のアプリケーションでは、共有メモリは値型の構造体、参照型のバイト配列のデータベッドをサポートしており、構造体内の属性順序はメモリ内の順序である。
ソリッド·モデルの定義
構造体をポインタ間で変換できるようにするには、以下のように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のFree HGlobalを使用してリクエストのポインタを解放します。
/// <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のFree HGlobalを使用してリクエストポインタを解放します。
/// <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プロジェクトページで、ユーザーは構造体ボタンをクリックし、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プロジェクトページで、ユーザーは構造体ボタンをクリックして共有メモリから構造体を取得し、以下のようにポップアップ表示します。
private void btnReadStruct_Click(object sender, EventArgs e)
{
//定义共享内存帮助对象并打开共享内存
ShareMemoryHelper helper = new ShareMemoryHelper();
helper.OpenShareMemory();
//读取结构体
TestData testData = helper.ReadMemoryWithStruct();
MessageBox.Show(testData.ToString());
}
例の紹介。
まず、以下のようにプロセス間でデータを交換する不定長の画像です。

固定長の構造体タイプは、次のようにプロセス間でデータを交換します。

以上が“プロセス間の高速データ交換ソリューションの推奨”の内容であり、レンガを投げ、一緒に学び、一緒に進歩することを目的としています。
