정리

[Win32] IPC - #2 Message(메시지)

저장소/VC++
[출처] 윈도우즈 API 정복, 김상형 지음

메시지는 정보를 교환할 수 있는 가장 간단한 방법이며 또한 가장 빠른 방법이기도 하다.
두 프로세스가 서로 윈도우 핸들만 알고 있다면 약속된 메시지의 wParam, lParam을 통해 정보를 교환할 수 있다.
메시지는 메모리를 거치지 않고 운영체제에 의해 직접 전달되므로 주소 공간이 격리되어 있더라도 잘 전달된다.

[ICPMessage1]
#define WM_IPC WM_USER + 1
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
   HDC hdc;
   PAINTSTRUCT ps;
   TCHAR* pszMsg = _T("마우스 왼쪽 버튼을 누르면 메시지 전송됨.");
   HWND hWnd2;

   switch(iMessage)
   {
   case WM_LBUTTONDOWN:
      hWnd2 = ::FindWindow(NULL, _T("IPCMessage2"));
      if(NULL != hWnd2)
         ::SendMessage(hWnd2, WM_IPC, (WPARAM)1234, (LPARAM)0);
      return 0;
   case WM_PAINT:
      hdc = ::BeginPaint(hWnd, &ps);
      ::TextOut(hdc, 10, 50, pszMsg, _tcslen(pszMsg));
      ::EndPaint(hWnd, &ps);
      return 0;
   case WM_DESTORY:
      ::PostQuitMessage(0);
      return 0;
   }

   return (DefWindowProc(hWnd, iMessage, wParam, lParam));
}

[ICPMessage2]
#define WM_IPC WM_USER + 1
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
   HDC hdc;
   PAINTSTRUCT ps;
   TCHAR* pszMsg = _T("메시지를 전달받는 녀석.");
   TCHAR szStr[128] = {0,};

   switch(iMessage)
   {
   case WM_IPC:
      hdc = ::GetDC(hWnd);
      _stprintf(szStr, _T("%d를 받았습니다."), wParam);
      ::TextOut(hdc, 10, 100, szStr, _tcslen(szStr));
      ::ReleaseDC(hWnd, hdc);
      return 0;
   case WM_PAINT:
      hdc = ::BeginPaint(hWnd, &ps);
      ::TextOut(hdc, 10, 50, pszMsg, _tcslen(pszMsg));
      ::EndPaint(hWnd, &ps);
      return 0;
   case WM_DESTORY:
      ::PostQuitMessage(0);
      return 0;
   }

   return (DefWindowProc(hWnd, iMessage, wParam, lParam));
}

비교적 크기가 작은 정보를 전달할 때만 사용할 수 있다.
정보라기보다는 단순한 어떤 사실을 통보하기 위한 목적으로도 사용할 수 있다.
그러나 문자열이나 구조체같은 큰 데이터는 이 메시지로 전달할 수 없다. 메시지와 함께 전달되는 wParam, lParam은 둘 다 더해봐야 불과 8바이트에 불과하기 때문이다.


Win32 환경은 프로세스간 메모리 공간이 격리됨으로 해서 훨씬 더 안정적이기는 하지만 간단한 문자열을 교환하는데도 아주 복잡한 방법이 동원되어야 하는 불편함이 있지만 이러한 불편함을 해소하기 위해 IPC를 위한 전용 메시지가 추가되었고, 이 메시지가 바로 WM_COPYDATA 메시지이다.
wParam에 정보를 보내는 윈도우의 핸들을 넣고 lParam에는 다음 구조체의 포인터를 넣는다.

typedef struct tagCOPYDATASTRUCT{   // cds
DWORD dwData;
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT;

dwData는 교환하고자 하는 정수값이며 lpData는 교환하고자 하는 문자열(또는 이진 데이터)이며 cbData는 lpData의 크기이다. 정수 하나와 포인터 하나를 보낼 수 있다. 이 구조체에 값을 채운 후 WM_COPYDATA 메시지를 보내면 운영체제가 구조체에 보관된 값을 해당 윈도우로 전달한다.

[CopyData1]
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
   HDC hdc;
   PAINTSTRUCT ps;
   COPYDATASTRUCT cds;
   TCHAR* pszStr = _T("WM_COPYDATA 메시지를 위한 Tesst String");
   TCHAR* pszMsg = _T("마우스 왼쪽 버튼을 누르면 메시지 전송됨.");
   HWND hWnd2;

   switch(iMessage)
   {
   case WM_LBUTTONDOWN:
      cds.dwData = 0;
      cds.cbData = _tcslen(pszStr);
      cds.lpData = pszStr;
      hWnd2 = ::FindWindow(NULL, _T("CopyData2"));
      if(NULL != hWnd2)
         ::SendMessage(hWnd2, WM_COPYDATA, (WPARAM)hWnd, (LPARAM)&cds);
      return 0;
   case WM_PAINT:
      hdc = ::BeginPaint(hWnd, &ps);
      ::TextOut(hdc, 10, 50, pszMsg, _tcslen(pszMsg));
      ::EndPaint(hWnd, &ps);
      return 0;
   case WM_DESTORY:
      ::PostQuitMessage(0);
      return 0;
   }

   return (DefWindowProc(hWnd, iMessage, wParam, lParam));
}

[CopyData2]
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
   HDC hdc;
   PAINTSTRUCT ps;
   COPYDATASTRUCT* pcds;
   TCHAR* pszMsg = _T("CopyData1 프로그램으로부터 메시지를 전달받는다.");

   switch(iMessage)
   {
   case WM_COPYDATA:
      pcds = (PCOPYDATASTRUCT)lParam;
      hdc = ::GetDC(hWnd);
      ::TextOut(hdc, 10, 100, (LPCTSTR)pcds->lpData, pcds->cbData);
      ::ReleaseDC(hWnd, hdc);
      return 0;
   case WM_PAINT:
      hdc = ::BeginPaint(hWnd, &ps);
      ::TextOut(hdc, 10, 50, pszMsg, _tcslen(pszMsg));
      ::EndPaint(hWnd, &ps);
      return 0;
   case WM_DESTROY:
      ::PostQuitMessage(0);
      return 0;
   }
}

COPYDATASTRUCT 구조체의lpData벰버는 포인터이므로 구조체나 배열도 보낼 수 있다. 이 포인턴를 전달받는 프로세스가 참조할 수 있게 변환하는 것은 운영체제가 대신 하는 데 내부적으로 복잡한 처리를 하고 있음은 쉽게 상상이 갈 것이다. 전다랗ㄹ 데이터의 크기만큼 파일 맵핑을 생성하고 통신 대상 프로세스들의 주소 공간에 동시에 맵해 놓고 번지를 lParam으로 전달한다. 이 메시지는 무척 편맇게 사용할 수 있는 반면 그다지 효율이 좋지는 못하다.

WM_COPYDATA 메시지를 사용할 때는 다음 사항에 주의해야 한다.
1. lpData에는 받는 쪽에서 읽을 수 있는 값만 전달해야 한다. 단순한 문자열이라면 간단하겠지만 복잡한 이진 포맷의 데이터라면 통신하는 양쪽이 포맷을 알고 있어야만 한다.
2. 받은 쪽에서 데이터를 다 사용하기 전에 전달된 값이 바뀌지 않도록 해야 한다. SendMessage는 메시지가 완전히 처리되기 전에 리턴하지 않으므로 자신이 직접 변경할 수는 없지만 멀티 스레드를 쓸 경우 다른 스레드에서 값을 변경할 여지를 차단해야 한다.
3. 받는 데이터는 일기 전용이므로 변경해서는 안 된다. 만약 꼭 변경하고자 한다면 로컬 메모리에 복사한 후 사본을 변경해야 한다.
4. WM_COPYDATA 메시지는 SendMessage로 보내야 하며 PostMessage로 부쳐서는 안 된다. 임시적인 처리를 통해 데이터를 전달하는 것이므로 메시지를 큐에 붙이는 것은 의미가 없다. (SendMessage로만 보낼 수 있다. PostMessage로 큐에 붙이면 이 메시지는 무시된다.)

'저장소 > VC++' 카테고리의 다른 글

[Win32] Service - #4 서비스 제어  (0) 2009.05.13
[Win32] Service - #3 서비스 설치  (0) 2009.05.13
[Win32] Service - #2 서비스 생성  (0) 2009.05.13
[Win32] Service - #1 정의  (0) 2009.05.13
[Win32] IPC - #1 정의  (0) 2009.05.12