본문 바로가기

Programming/Win32&MFC&COM

Memory Mapped File(MMF)를 이용한 프로세스간 메모리 공유

반응형

Win32 이상의 환경에서는 프로세스의 주소 공간이 독립적으로 관리됩니다.

프로세스의 주소 공간이 독립적이기 때문에 다른 프로세스의 주소 공간을 공유할 수 없습니다.

A라는 프로세스의 0x1234라는 주소값은 B라는 프로세스의 0x1234와는 전혀 다른 공간입니다.

이런 메모리 관리 구조는 OS를 좀 더 안정적이고 견고하게 만들었습니다.

그렇지만 각각의 프로세스 사이에서 정보를 주고 받는 것이 어렵게 되었습니다.

프로세스 사이에 정보를 공유하기 위해서 Memory Mapped File(MMF)이라는 방식을 사용합니다.

이름 그대로 파일을 메모리에 맵핑하기 위해서 사용하는 기법입니다.

간단하게 설명하자면, 파일을 열어서 해당 파일을 메모리에 맵핑시켜서 사용하는 것입니다.

MMF를 이용해서 하드디스크의 파일을 프로세스의 주소 공간에 연결해서 사용이 가능합니다.

여기서는 파일을 여는 것이 아닌 프로세스가 서로 접근할 수 있는 메모리의 공유 영역을 생성합니다.

File Mapping object는 Named kernel object로 생성되면 모든 프로세스에 영향을 줍니다.

쓰레드 동기화에 사용되는 뮤텍스와 유사합니다.

뮤텍스가 생성되면 모든 프로세스에서 동기화 처리가 가능합니다.

MMF의 File Mapping Object 또한 모든 프로세스의 사용이 가능합니다.

커널 영역은 일반적으로 접근이 불가능하지만 Win32 API를 통해서 허용된 부분에 대해 접근이 가능합니다.

예제를 먼저 확인해 보도록 하겠습니다.

먼저 A 프로세스의 코드입니다.

Win32 프로젝트를 생성하고 다른 설정은 수정하지 않고 프로젝트를 생성합니다.

프로젝트 이름은 MMFWriter라고 작성하면 됩니다.

다음 코드를 Global Variables가 있는 곳에 작성합니다.

#define UM_REQUEST_READMEMORY WM_USER + 1
#define IDC_SENDBUTTON 1000
#define SIZE_OF_MEMORYMAP 1024

HANDLE g_hMapFile = nullptr;
TCHAR* g_ptMapFile = nullptr;
HWND g_SendBtn = nullptr;

먼저 필요한 변수들을 선언합니다.

그리고 WndProc 함수를 다음과 같이 수정합니다.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;
	HWND hDest;

	switch (message)
	{
	case WM_COMMAND:
		wmId    = LOWORD(wParam);
		wmEvent = HIWORD(wParam);
		// Parse the menu selections:
		switch (wmId)
		{
		case IDM_ABOUT:
			DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
			break;
		case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		case IDC_SENDBUTTON:
			_tcscpy_s(g_ptMapFile, SIZE_OF_MEMORYMAP / sizeof(TCHAR), _T("Message from writer"));
			hDest = FindWindow(NULL, _T("MMFReader"));
			SendMessage(hDest, UM_REQUEST_READMEMORY, 0, 0);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		// TODO: Add any drawing code here...
		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY:
		UnmapViewOfFile(g_ptMapFile);
		CloseHandle(g_hMapFile);
		PostQuitMessage(0);
		break;
	case WM_CREATE:
		g_hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, SIZE_OF_MEMORYMAP, _T("Example Map"));
		g_ptMapFile = (TCHAR*)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, SIZE_OF_MEMORYMAP);
		g_SendBtn = CreateWindow(_T("button"), _T("Send Data"), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, 10, 10, 100, 100, hWnd, (HMENU)IDC_SENDBUTTON, hInst, nullptr);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

WM_CREATE와 WM_DESTORY, IDC_SENDBUTTON을 처리하는 부분이 추가되었습니다.

MMFWriter의 WM_CREATE에서 메모리 영역을 맵핑시킵니다.

CreateFileMapping 함수를 호출해서 생성할 수 있습니다.

여기서 중요한 점은 첫 번째에 INVALID_HANDLE_VALUE를 전달한다는 점입니다.

파일을 연결하기 위해서는 파일의 핸들을 전달하지만 여기서는 파일을 사용하지 않기 때문에 저렇게 합니다.

그리고 MapViewOfFile 함수를 이용해서 파일(여기서는 메모리)의 뷰를 주소 공간에 맵핑시킵니다.

이제 메모리 영역에 작성하면 됩니다.

IDC_SENDBUTTON을 처리하는 부분에 메모리의 포인터에 그대로 복사를 하는 것을 확인할 수 있습니다.

그리고 MMFReader를 찾아서 사용자 정의 메시지를 전달합니다.

종료할 때는 UnMapViewOfFileCloseHandle을 호출하면 됩니다.

그리고 MMFReader라는 Win32 프로젝트를 생성합니다.

MMFWriter와 동일하게 기본 설정으로 생성하면 됩니다.

동일하게 Global Variables에 다음 선언을 추가합니다.

/* code for SHARED MEMORY begin */
#define UM_REQUEST_READMEMORY WM_USER + 1
#define SIZE_OF_MEMORYMAP 1024

HANDLE g_hMapFile = nullptr;
TCHAR* g_ptMapFile = nullptr;
/* code for SHARED MEMORY end */

WndProc의 내용은 다음과 같이 수정하면 됩니다.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
	case WM_COMMAND:
		wmId    = LOWORD(wParam);
		wmEvent = HIWORD(wParam);
		// Parse the menu selections:
		switch (wmId)
		{
		case IDM_ABOUT:
			DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
			break;
		case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		// TODO: Add any drawing code here...
		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY:
		UnmapViewOfFile(g_ptMapFile);
		CloseHandle(g_hMapFile);
		PostQuitMessage(0);
		break;
	case WM_CREATE:
		g_hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, SIZE_OF_MEMORYMAP, _T("Example Map"));
		g_ptMapFile = (TCHAR*)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, SIZE_OF_MEMORYMAP);
		break;
	case UM_REQUEST_READMEMORY:
		MessageBox(hWnd, g_ptMapFile, nullptr, MB_OK);
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

WM_CREATE의 내용은 버튼을 생성하는 부분이 제외된 것만 다르고 동일합니다.

그리고 MMFWriter에서 UM_REQUEST_READMEMORY 메시지가 오기 때문에 핸들러에 추가합니다.

이번에는 포인터에서 단순하게 읽어서 메시지박스로 출력합니다.

WM_DESTROY는 동일합니다.

이제 MMFWriter와 MMFReader를 동시에 실행한 상태에서 MMFWriter의 버튼을 누릅니다.

그러면 MMFReader에서 메시지박스를 띄우는 것을 확인할 수 있습니다.

일반적으로 다른 프로세스의 메모리를 참조할 수 없지만 커널 오브젝트를 통해서 상호 참조가 가능하게 됐습니다.

커널 오브젝트는 일반적으로 Create 함수를 여러 번 호출해도 동일한 이름이면 추가로 생성하지 않습니다.

대신에 기존에 있던 커널 오브젝트의 핸들을 리턴합니다.

그래서 양쪽에서 동시에 CreateFileMapping으로 동일한 이름의 파일 맵핑 오브젝트를 사용할 수 있는 것입니다.

MMF는 프로세스간 통신(IPC)에서 유용하게 사용할 수 있습니다.

반응형