티스토리 뷰

흔석/프로그램 개발

IPC on Windows Platform

JOHNPARK82 2006. 5. 30. 01:25
IPC (Inter Process Communication) #1.
Feb 04 2003 14:11 Written by myku.hihome.com


IPC (Inter Process Communication)


사용자의 요구가 증대되면서 프로그램이 갖춰야 할 기능 또한 많아지고
당연히 프로그램 크기도 비대해졌다.
또한 하나의 프로그램만으로는 사용자의 욕구를 충족시키지 못해
여러개의 프로그램을 만들어야 하는 상황이 발생하고 있다.
여기서 서로 다른 프로그램 (엄밀히 얘기하면 프로세스)들간에
데이터를 교환하거나 특정 기능을 사용하려면 어떻게 해야 할까?
이런 문제를 해결하기 위한 것이 다음에 소개되는 IPC 기술(프로세스간 통신방법) 이다.


SendMessage() / PostMessage() : 사용자 정의 메시지, 시스템 등록 메시지
WM_COPYDATA, Atom 등을 이용하는 방법
DLL (Dynamic Link Library) 공유
동적 자료 교환 (Dynamic Data Exchange : DDE)
OLE 자동화 (Automation)
메모리 맵 파일 (Memory Map File)
NetBIOS
네임드 파이프 (Named Pipe)
메일 슬롯 (Mail Slot)


위에 나열된 IPC에는 동일 시스템(로컬 시스템)에서만 사용되는 것이 있고,
다른 시스템(리모트 시스템)에서도 작동되는 것이 있다.

다음의 예제는 wParam과 lParam 두 변수를 이용해서 데이터 전송이 가능하다.
이 두 변수의 크기는 다음과 같다.

WPARAM == unsigned int (UINT)

: 윈도우 3.0 / 3.1 같은 16비트 윈도우에서는 16비트(2바이트)이고,
Win32 (32비트 윈도우)에서는 32비트(4바이트)의 값을 가진다.

LPARAM == long

: 16비트나 32비트 모두 32비트(4바이트)의 값을 가진다.



만약 데이터가 4바이트가 넘을 경우, 같은 프로세스 내에서는 구조체에 정보를 담거나
메모리를 할당해서 정보를 저장한 후 이것의 포인터를 lParam을 통해 전달하면 된다.
하지만 다른 프로세스 간에는 이런식은 불가능하고 메모리 맵을 사용해야 한다.


1. SendMessage() / PostMessage() 사용

a) RegisterWindowMessage()를 사용할 경우

RegisterWindowMessage() 함수는 시스템에 유일한 새 윈도우 메시지를 정의하고 등록한다.
리턴되는 메시지 값은 SendMessage() 나 PostMessage() 같은 함수를 호출할 때 사용할 수 있다.

UINT RegisterWindowMessage(
LPCTSTR lpString // address of message string
);

이 함수의 원형은 위와 같다.
이 함수의 인자로는 메시지를 주고받을 프로그램끼리 미리 약속한 문자열을 넘겨준다.
같은 문자열을 이용하여 메시지를 등록한 프로그램끼리만
서로 메시지를 주고받을 수 있게 되는데,
이 함수의 반환값을 다른 프로그램에 전달할 메시지로 이용하면 된다.




/////////////////////////////////////////////////////////////////////
//
// 이제 첫 번째 예제를 만들어보자.
//
/////////////////////////////////////////////////////////////////////


Download here : Download Sample Project Here !!


1) 간단하게 테스트하기 위해 다이얼로그 베이스(Dialog based)로
프로젝트를 하나 만든다. 프로젝트명은 IPC1 이다.

2) 리턴되는 메시지 값을 저장하기 위한 변수(nRegMsg)를
IPC1Dlg.cpp 파일에 다음처럼 전역으로 지정한다.

UINT nRegMsg;

class CAboutDlg : public CDialog
{
public:
......................
......................
}

위의 예처럼 cpp파일의 class 정의부 위에 변수를 정의한다면 무난하겠다.

3) 초기화 함수인 OnInitDialog() 함수에서 메시지를 등록한다.
만약 SDI 형식이라면 OnInitialUpdate() 함수에서 이 작업을 하면 되겠다.

BOOL CIPC1Dlg::OnInitDialog()
{
...........................
...........................
// TODO: Add extra initialization here
nRegMsg = ::RegisterWindowMessage("This is IPC sample program1");

return TRUE; // return TRUE unless you set the focus to a control
}

4) 다음과 같이 다이얼로그 템플릿을 만든다.
"취소" 버튼은 지우고, "확인" 버튼은 캡션만 "종료" 라고 고친다.
여기서 왼쪽과 오른쪽의 에디트박스의 ID는 각각 IDC_EDIT_WPARAM, IDC_EDIT_LPARAM 이고
"전송" 이라 쓰인 버튼의 ID는 IDC_SEND 이다.





5) 클래스 위저드를 열어서 두 번째 탭 Member Variables를 선택하고,
두 개의 에디트 박스와 연결되는 변수를 다음과 같이 지정한다.

Control IDs:
IDC_EDIT_LPARAM
IDC_EDIT_WPARAM

Type:
long
UINT

Member:
m_lParam
m_wParam


6) 다음은 IDC_SEND 버튼에 대한 핸들러이다.
여기서 사용되는 SendMessage()의 첫 번째 인자는 반드시 HWND_BROADCST 이어야 한다.

SendMessage()의 첫번째 인자가 HWND_BROADCST인 경우 메시지는
시스템상의 모든 top-level 윈도우에 전달된다.
여기에는 disabled 또는 invisible 되어 있거나,
중첩되어 있거나(overlapped), pop-up 윈도우도 포함된다.
하지만 차일드 윈도우에는 메시지가 전달되지 않는다.

여기까지가 송신 프로그램에 대한 내용이다.

void CIPC1Dlg::OnSend()
{
// TODO: Add your control notification handler code here
UpdateData();
::SendMessage(HWND_BROADCAST, nRegMsg, (WPARAM)m_wParam, (LPARAM)m_lParam);
}



7) 여기서부터는 수신 프로그램에 관한 내용이다.

다음과 같이 IPC1Dlg.cpp 파일의 메시지맵 부분에 다음처럼
ON_REGISTERED_MESSAGE 매크로를 이용하여 등록한 메시지와 핸들러를 연결시킨다.

BEGIN_MESSAGE_MAP(CIPC1Dlg, CDialog)
//{{AFX_MSG_MAP(CIPC1Dlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_SEND, OnSend)
//}}AFX_MSG_MAP
ON_REGISTERED_MESSAGE(nRegMsg, OnRegMsg) // 추가
END_MESSAGE_MAP()



8) 다음은 메시지맵에 추가한 메시지에 연결되는 핸들러를 실제로 작성한다.

LRESULT CIPC1Dlg::OnRegMsg(WPARAM wParam, LPARAM lParam)
{
m_wParam = wParam;
m_lParam = lParam;
UpdateData(FALSE);

return 0L;
}

9) 이렇듯 하나의 프로그램에 송수신 부분을 다 넣을 수도 있지만,
두 개의 프로그램을 따로 만들 수도 있다.
따로 만든다면 송신 프로그램은 1~6번까지를 적용하면 되고,
수신 프로그램은 2, 3, 7, 8번을 적용하면 된다.


b) FindWindow()를 사용할 경우

FindWindow()의 함수 원형은 다음과 같다.

static CWnd* PASCAL FindWindow( LPCTSTR lpszClassName, LPCTSTR lpszWindowName );

FindWindow() 함수는 인자로 클래스 이름과 윈도우 캡션을 주고,
그에 해당하는 윈도우를 찾는다. 모든 top-level의 윈도우를 찾지만,
차일드 윈도우는 찾지 못한다. 해당하는 윈도우가 없으면 NULL값이 리턴된다.
만약 lpszClassName이 NULL이면 윈도우 캡션에 해당하는 윈도우만 찾고,
lpszWindowName이 NULL이면 주어진 클래스 이름에 해당하는 윈도우를 찾는다.





///////////////////////////////////////////////////////////////
//
// 여기서 두 번째 예제를 만들어보자.
//
///////////////////////////////////////////////////////////////

Download here : Download Send Program Here !!
Download here : Download Receive Program Here !!


1) 먼저 송신 프로그램을 만들어보자.
여기서는 송수신 프로그램을 별도로 만든다.
다이얼로그 베이스의 프로젝트를 하나 만든다.
프로젝트명은 IPC2이다.


2) 다음과 같이 다이얼로그 템플릿을 만든다.
"취소" 버튼은 지우고, "확인" 버튼은 캡션만 "종료" 라고 고친다.
여기서 왼쪽과 오른쪽의 에디트박스의 ID는 각각
IDC_EDIT_WPARAM, IDC_EDIT_LPARAM 이고 "전송" 이라 쓰인 버튼의 ID는 IDC_SEND 이다.

이것은 위 첫 번째 예제의 4번과 동일하다.
송신 프로그램에서 캡션은 상관없다.




3) 클래스 위저드를 열어서 두 번째 탭 Member Variables를 선택하고,
두 개의 에디트 박스와 연결되는 변수를 다음과 같이 지정한다.

이것 또한 위 첫 번째 예제의 5번과 동일하다.

Control IDs:
IDC_EDIT_LPARAM
IDC_EDIT_WPARAM

Type:
long
UINT

Member:
m_lParam
m_wParam


4) IPC2Dlg.cpp 파일의 상단에 다음과 같이 사용자 정의 메시지를 정의한다.

#define WM_MY_MESSAGE WM_USER+7


WM_USER 다음에 더하는 숫자는 송수신 프로그램에서 모두 같이 맞추어주기만 하면
지정된 범위내의 아무런 숫자라도 상관없다.



5) 다음으로 "전송" 버튼에 대한 핸들러를 작성한다.
여기까지가 송신 프로그램에 대한 설명이다.

void CIPC2Dlg::OnSend()
{
// TODO: Add your control notification handler code here
CWnd *pWnd = CWnd::FindWindow(NULL, "IPC_RECEIVE");
if(!pWnd) {
AfxMessageBox("Program is not found.");
return;
}

UpdateData();
pWnd->SendMessage(WM_MY_MESSAGE, m_wParam, m_lParam);
}



6) 이제 수신 프로그램을 만들어보자.

역시 간단히 구현하기 위해 프로젝트 명이 IPC3인 다이얼로그 베이스 프로젝트를 만든다.



7) IPC3Dlg.cpp 파일의 상단에 다음과 같이 사용자 정의 메시지를 정의한다.

#define WM_MY_MESSAGE WM_USER+7

송수신 프로그램에서 반드시 같은 메시지를 사용해야 한다.



8) 다이얼로그 템플릿을 다음과 같이 만든다.
에디트 박스의 ID는 왼쪽에서부터 IDC_EDIT_WPARAM, IDC_EDIT_LPARAM 로 한다.
그리고 반드시 아래 그림과 같이 캡션을 IPC_RECEIVE라고 고친다.



9) 클래스 위저드를 열어서 두 번째 탭 Member Variables를 선택하고,
두 개의 에디트 박스와 연결되는 변수를 다음과 같이 지정한다.

Control IDs:
IDC_EDIT_LPARAM
IDC_EDIT_WPARAM

Type:
long
UINT

Member:
m_lParam
m_wParam




10) IPC3Dlg.cpp 파일의 메시지맵 부분에 다음과 같이 한 줄 추가한다.

BEGIN_MESSAGE_MAP(CIPC3Dlg, CDialog)
//{{AFX_MSG_MAP(CIPC3Dlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_MY_MESSAGE, OnMyMessageProc) // 추가
END_MESSAGE_MAP()



11) 위의 메시지맵에 연결되는 핸들러를 작성한다.

LRESULT CIPC3Dlg::OnMyMessageProc(WPARAM wParam, LPARAM lParam)
{
m_wParam = wParam;
m_lParam = lParam;
UpdateData(FALSE);

return 0L;
}

12) 송수신 프로그램 두 개를 같이 실행한 다음 송신 프로그램에서
전송 버튼을 눌러 데이터를 보내면 된다.