지난 날에 Python으로 공유 메모리에 대해서 작성했었다. 'SharedMemory' 클래스를 사용했었는데 이번에는 C# Process와 Python Process가 연동이 되어야 한다는 것이다! C#을 안 쓴지도 오래됐고 다시 처음부터 보고 있었기 때문에 기간 내에 할 수 있을까 라는 약간의 걱정이 앞섰지만 역시 구글링의 힘은 대단한 것 같다🥰
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
internal class Program
{
private const int FLAG_OFFSET = 0;
private const int EXIT_OFFSET = 1;
private const int DATA_OFFSET = 2;
private const int MEMORY_SIZE = 1024;
private static string sharedMemoryName = "EXAM_SharedMemory";
static void Main(string[] args)
{
using (var mmf = MemoryMappedFile.CreateOrOpen(sharedMemoryName, MEMORY_SIZE))
{
}
}
}
C#은 MemoryMappedFile 클래스로 메모리 매핑 파일에 대한 접근 기능을 제공한다. 이 클래스를 사용하면 다중 프로세스 간에 데이터를 공유 할 수도 있다고 한다. 각 프로세스는 메모리 매핑 파일에 액세스 하여 데이터를 읽거나 쓸 수 있으므로, 다른 프로세스가 수정한 데이터를 실시간으로 읽을 수 있다고 한다. MemoryMappedFile 클래스를 사용하기 위해서는 메모리 매핑된 파일을 만들어야 한다. 이 때 사용할 수 있는 메서드로는 여러가지가 있지만 내가 생각했을 때 대표적으로는 'MemoryMappedFile.CreateNew' 메서드와 'MemoryMappedFile.CreateOrOpen' 메서드일 것 같다.
static void Main(string[] args)
{
using (var mmf = MemoryMappedFile.CreateNew(String? mapname, long capacity, System.IO.MemoryMappedFiles.MemoryMappedFileAccess access, System.IO.MemoryMappedFiles.MemoryMappedFileOptions options, System.IO.HandleInheritability inheritabilit));
{
}
}
[추가 옵션 - 생략 가능]
MemoryMappedFileAccess : 메모리 매핑 파일에 허용되는 액세스 유형을 지정하는 열거 값 중 하나, 기본값은 ReadWrite이며, 'Read', 'Write'도 있다.
MemoryMappedFileOptions : 메모리 매핑 파일을 생성할 때의 옵션
- None : 특별한 옵션을 적용하지 않음
- DelayAllocatePages : 파일에 대해 물리적 메모리를 지연하여 할당, 파일에 접근할 때 까지 실제 메모리가 접근되지 않음.
HandleInheritability : 메모리 매핑 파일 핸들의 상속 여부를 지정한다.
- None : 핸들이 상속되지 않음
- Inheritable : 핸들이 자식 프로세스에 상속됨, 이 옵션을 사용하면 생성된 메모리 매핑 파일에 접근하는 또 다른 프로세스가 해당 핸들을 상속하여 사용 가능
static void Main(string[] args)
{
using (var mmf = MemoryMappedFile.CreateOrOpen(string mapName, long capacity, System.IO.MemoryMappedFiles.MemoryMappedFileAccess access, System.IO.MemoryMappedFiles.MemoryMappedFileOptions options, System.IO.HandleInheritability inheritability);
{
}
}
매개변수 옵션은 위 'CreateNew'와 동일해서 생략하도록 하겠다! 그렇다면 두 가지 메서드의 차이가 무엇이냐? 자세하진 않겠지만 'CreateNew'는 무조건 처음 만들어줄 때 사용 되는 것이고 이미 mapName의 메모리 매핑 파일이 존재하다면 에러를 반환한다고 한다. 그에 반해 'CreateOrOpen'은 mapName이 이미 존재하다면 그 파일에 접근하는 것이고 없다면 새로 만들어준다는 것이다. 분명 두 가지의 메서드가 존재하는 이유가 있겠지만 간단하게 봤을때는 'CreateOrOpen'이 무조건 좋은게 아닌가 싶긴 했다.
static void Main(string[] args)
{
using (var mmf = MemoryMappedFile.CreateNew(String? mapname, long capacity, System.IO.MemoryMappedFiles.MemoryMappedFileAccess access, System.IO.MemoryMappedFiles.MemoryMappedFileOptions options, System.IO.HandleInheritability inheritabilit));
{
using (MemoryMappedView accessor = MemoryMappedFile.CreateViewAccessor())
{
}
}
}
이러한 매개변수들이 있지만 매개변수를 넣지 않아도 사용 가능하다! 이제 접근을 했으며 읽고 써야지 않겠는가! 읽고 쓰는건 정말 간단하다.
// 데이터를 쓰기
byte[] data = Encoding.UTF8.GetBytes("Hello, Memory Mapped Files!");
accessor.WriteArray(0, data, 0, data.Length);
데이터를 쓰기 위해서는 메모리 공간에 올린 데이터를 Byte배열로 바꿔서 Array만큼 작성해서 써주면 된다.
// 데이터를 읽기
byte[] readData = new byte[data.Length];
accessor.ReadArray(0, readData, 0, readData.Length);
string text = Encoding.UTF8.GetString(readData);
Console.WriteLine(text);
읽기도 마찬가지로 Array만큼 읽어와서 다시 문자열로 변환해서 출력해주면 된다.
파이썬에서도 느꼈지만 메모리 접근하는 일이라 정말 어렵고 복잡할 줄 알았는데 진짜 간단하게 작성이 된다.. C#에서도 한 번 더 놀랐다.. 물론 내가 너무 간단한 예제를 작성해서 그런거겠지만 과연 크게 달라질까 싶기도 한다..🤔
이 외에 다른 메서드들도 정말 많지만 사용하지 않아서 설명을 못드리겠습니다..😂 아래 링크를 참조 부탁드립니다.
https://learn.microsoft.com/ko-kr/dotnet/api/system.io.memorymappedfiles.memorymappedviewaccessor?view=net-8.0
아무튼 이렇게 읽고/쓰기 예제를 간단하게 마쳐보았다. 그리고 내가 Python에서 작성한 공유 메모리 쓰기 코드를 C#으로 바꿔 보았는데 엄청 허접하겠지만 한 번 예제 올려보겠다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.MemoryMappedFiles;
using System.Runtime.CompilerServices;
namespace WriteSharedMemory
{
internal class Program
{
private const int FLAG_OFFSET = 0;
private const int EXIT_OFFSET = 1;
private const int DATA_OFFSET = 2;
private const int MEMORY_SIZE = 1024;
private static string sharedMemoryName = "EXAM_SharedMemory";
static void Main(string[] args)
{
Console.WriteLine("====== 테스트 상황입니다. ======\n" +
"상황 1. \n" +
"상황 2. \n" +
"상황 3. \n" +
"상황 4. \n" +
"상황 5. 시스템 종료)\n" +
"아래에 해당하는 숫자를 입력해주세요.\n" +
"===============================");
using (var mmf = MemoryMappedFile.CreateOrOpen(sharedMemoryName, MEMORY_SIZE))
{
bool bRunnung = true;
while (bRunnung)
{
Console.Write("입력: ");
int situation = Convert.ToInt32(Console.ReadLine());
SwitchSituation(mmf, situation, ref bRunnung);
}
ExitProgram(mmf);
}
}
private static void WriteToSharedMemory(MemoryMappedFile mmf, int situation)
{
using (var accessor = mmf.CreateViewAccessor())
{
byte flag = accessor.ReadByte(FLAG_OFFSET);
if (flag == 0)
{
string message = null;
switch (situation)
{
case 1:
message = "s1";
break;
case 2:
message = "s2";
break;
case 3:
message = "s3";
break;
case 4:
message = "s4";
break;
default:
break;
};
if (message != null)
{
byte[] buffer = Encoding.ASCII.GetBytes(message);
accessor.Write(FLAG_OFFSET, (byte)1);
accessor.WriteArray<byte>(DATA_OFFSET, buffer, 0, buffer.Length);
}
}
}
}
private static void ExitProgram(MemoryMappedFile mmf)
{
using (var accessor = mmf.CreateViewAccessor())
{
byte flag = accessor.ReadByte(FLAG_OFFSET);
if (flag == 0)
{
accessor.Write(EXIT_OFFSET, (byte)1);
}
}
}
private static void SwitchSituation(MemoryMappedFile mmf, int situation, ref bool run)
{
switch (situation)
{
case 1:
case 2:
case 3:
case 4:
WriteToSharedMemory(mmf, situation);
break;
case 5:
run = false;
break;
default:
Console.WriteLine("잘못 입력하셨습니다. 다시 시도 해주세요.");
break;
}
}
}
}
아직까지는 잘 구현되었는데 문제가 있거나 잘못 코딩한 부분이 있으면 알려주시면 감사하겠습니다🥰
사실 이걸 작성할까 고민을 하긴 했는데 처음 C#으로 공유 메모리 생성 예제를 작성하고 Python과 연동을 했었는데 'Python쪽에서 접근을 할 수 없다고 뜨더라..' 무슨 문제일까 찾던 도중 다시 작성했던 예제로 테스트를 해봤는데 이번에는 잘 돌아가더라.. 그래서 일단 그냥 진행 했는데 왜 접근이 막혔는지 시간이 난다면 찾아보고 싶다!!
아직 갈 길이 멀고 내가 부족한게 맞지만 난 너무 시작 전부터 겁부터 먹고 보는거 같다.. 앞으로는 좀 더 자신감을 가져도 되지 않을까..?