[C#] MemoryMappedFile 클래스

YongHo·2024년 3월 27일

지난 날에 Python으로 공유 메모리에 대해서 작성했었다. 'SharedMemory' 클래스를 사용했었는데 이번에는 C# Process와 Python Process가 연동이 되어야 한다는 것이다! C#을 안 쓴지도 오래됐고 다시 처음부터 보고 있었기 때문에 기간 내에 할 수 있을까 라는 약간의 걱정이 앞섰지만 역시 구글링의 힘은 대단한 것 같다🥰

0. MemoryMappedFile 클래스

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' 메서드일 것 같다.

0-1. 메모리 매핑 파일 생성하기

0-1-1. MemoryMappedFile.CreateNew()

        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));
            {
            }
	}
  • String? mapname : 메모리 매핑 파일에 할당할 이름으로, 다른 프로세스에서 접속하기 위해 필요하다.
  • long capacity : 메모리 매핑 파일에 할당할 최대 크기이다.

[추가 옵션 - 생략 가능]

  • MemoryMappedFileAccess : 메모리 매핑 파일에 허용되는 액세스 유형을 지정하는 열거 값 중 하나, 기본값은 ReadWrite이며, 'Read', 'Write'도 있다.

  • MemoryMappedFileOptions : 메모리 매핑 파일을 생성할 때의 옵션
    - None : 특별한 옵션을 적용하지 않음
    - DelayAllocatePages : 파일에 대해 물리적 메모리를 지연하여 할당, 파일에 접근할 때 까지 실제 메모리가 접근되지 않음.

  • HandleInheritability : 메모리 매핑 파일 핸들의 상속 여부를 지정한다.
    - None : 핸들이 상속되지 않음
    - Inheritable : 핸들이 자식 프로세스에 상속됨, 이 옵션을 사용하면 생성된 메모리 매핑 파일에 접근하는 또 다른 프로세스가 해당 핸들을 상속하여 사용 가능

0-1-2. MemoryMappedFile.CreateOrOpen()

        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'이 무조건 좋은게 아닌가 싶긴 했다.

0-2. 메모리 매핑 파일 접근하기

0-2-1. MemoryMappedFile.CreateViewAccessor()

        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())
		{
		}
            }
	}
  • offset : 메모리 매핑 파일에서 접근하려는 데이터의 시작 위치를 지정함 (byte 단위)
  • size : 접근하려는 데이터 크기 지정 (byte 단위)
  • access : 메모리 매핑 파일에 대한 액세스 권한 지정

이러한 매개변수들이 있지만 매개변수를 넣지 않아도 사용 가능하다! 이제 접근을 했으며 읽고 써야지 않겠는가! 읽고 쓰는건 정말 간단하다.

0-3. 메모리 매핑 파일 읽고 쓰기

0-3-1. MemoryMappedViewAccessor.Read

	// 데이터를 쓰기
	byte[] data = Encoding.UTF8.GetBytes("Hello, Memory Mapped Files!");
	accessor.WriteArray(0, data, 0, data.Length);

데이터를 쓰기 위해서는 메모리 공간에 올린 데이터를 Byte배열로 바꿔서 Array만큼 작성해서 써주면 된다.

0-3-2. MemoryMappedViewAccessor.Write

	// 데이터를 읽기
	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

1. MemoryMappedFile 클래스 사용 예제

아무튼 이렇게 읽고/쓰기 예제를 간단하게 마쳐보았다. 그리고 내가 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;
            }
        }
    }
}

아직까지는 잘 구현되었는데 문제가 있거나 잘못 코딩한 부분이 있으면 알려주시면 감사하겠습니다🥰

2. 마주친 에러??

사실 이걸 작성할까 고민을 하긴 했는데 처음 C#으로 공유 메모리 생성 예제를 작성하고 Python과 연동을 했었는데 'Python쪽에서 접근을 할 수 없다고 뜨더라..' 무슨 문제일까 찾던 도중 다시 작성했던 예제로 테스트를 해봤는데 이번에는 잘 돌아가더라.. 그래서 일단 그냥 진행 했는데 왜 접근이 막혔는지 시간이 난다면 찾아보고 싶다!!

3. 회고

아직 갈 길이 멀고 내가 부족한게 맞지만 난 너무 시작 전부터 겁부터 먹고 보는거 같다.. 앞으로는 좀 더 자신감을 가져도 되지 않을까..?

0개의 댓글