. 메시지 살펴보기

키 하나를 누를 때 여러 개의 IME 메시지가 발생하며 또 한 메시지가 다른 메시지를 추가로 발생시키는 경우도 많기 때문에 IME 메시지가 언제 어떤 순서로 발생하는지를 정확하게 파악하는 것은 다소 어렵다. 시점과 순서를 정확하게 파악하지 못하면 IME 메시지를 처리하기가 어려워지는데 그래서 IME 메시지의 발생 순서와 메시지 내용을 살펴 보기 위한 간단한 테스트 프로그램을 작성해보았다.

메시지를 살펴보려면 Spy라는 툴을 사용해도 되지만 이 툴은 워낙 일반적인 툴이기 때문에 IME 메시지만 살펴보기에 부적당하고 또 플래그들의 의미도 정확하게 해석해 주지 않기 때문에 학습용으로는 불편한 것 같아 오로지 IME 관련 메시지만 살펴 볼 수 있는 예제를 따로 만들어 보았다. 소스 자체는 별로 분석의 가치가 없으므로 구경만 하도록 하고 IME 메시지를 테스트하는 유틸리티로 활용하도록 하자. 전체 소스는 다음과 같다.

 

#include <windows.h>

 

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

HINSTANCE g_hInst;

HWND hWndMain;

LPCTSTR lpszClass=TEXT("ImeMsg");

 

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance

       ,LPSTR lpszCmdParam,int nCmdShow)

{

     HWND hWnd;

     MSG Message;

     WNDCLASS WndClass;

     g_hInst=hInstance;

    

     WndClass.cbClsExtra=0;

     WndClass.cbWndExtra=0;

     WndClass.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);

     WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);

     WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);

     WndClass.hInstance=hInstance;

     WndClass.lpfnWndProc=(WNDPROC)WndProc;

     WndClass.lpszClassName=lpszClass;

     WndClass.lpszMenuName=NULL;

     WndClass.style=CS_HREDRAW | CS_VREDRAW;

     RegisterClass(&WndClass);

 

     hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,

          CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,

          NULL,(HMENU)NULL,hInstance,NULL);

     ShowWindow(hWnd,nCmdShow);

     hWndMain=hWnd;

    

     while(GetMessage(&Message,0,0,0)) {

          TranslateMessage(&Message);

          DispatchMessage(&Message);

     }

     return (int)Message.wParam;

}

 

HWND hList;

void AddString(char *str)

{

     int count;

 

     SendMessage(hList,LB_ADDSTRING,0,(LPARAM)str);

     count=(int)SendMessage(hList,LB_GETCOUNT,0,0);

     SendMessage(hList,LB_SETCURSEL,(WPARAM)count-1,0);

}

 

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

     HDC hdc;

     PAINTSTRUCT ps;

     char str[256];

     char szChar[128];

     char szTmp[256];

     char Mes[]="입력하기 전에 왼쪽 버튼을 클릭하여 포커스를 주고 목록을 지울 때는 오른쪽 버튼 클릭";

 

     switch(iMessage) {

     case WM_CREATE:

          hList=CreateWindow("listbox",NULL,WS_CHILD | WS_VISIBLE | WS_BORDER |

              WS_VSCROLL | WS_HSCROLL | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT,

              0,0,600,600,hWnd,(HMENU)0,g_hInst,NULL);

          return 0;

     case WM_SIZE:

          MoveWindow(hList,0,30,LOWORD(lParam),HIWORD(lParam)-30,TRUE);

          return 0;

     case WM_PAINT:

          hdc=BeginPaint(hWnd, &ps);

          TextOut(hdc,5,5,Mes,lstrlen(Mes));

          EndPaint(hWnd, &ps);

          return 0;

     case WM_LBUTTONDOWN:

          SetFocus(hWnd);

          return 0;

     case WM_RBUTTONDOWN:

          SendMessage(hList,LB_RESETCONTENT,0,0);

          return 0;

     case WM_DESTROY:

          PostQuitMessage(0);

          return 0;

     case WM_IME_SETCONTEXT:

          if (wParam == TRUE) {

              lstrcpy(szTmp, "활성");

          } else {

              lstrcpy(szTmp, "비활성");

          }

          wsprintf(str, "WM_IME_SETCONTEXT %s lParam=%x",szTmp,lParam);

          AddString(str);

          break;

     case WM_IME_NOTIFY:

          lstrcpy(szTmp,"");

          if (wParam == IMN_CHANGECANDIDATE) lstrcpy(szTmp,"IMN_CHANGECANDIDATE");

          if (wParam == IMN_CLOSECANDIDATE) lstrcpy(szTmp,"IMN_CLOSECANDIDATE");

          if (wParam == IMN_GUIDELINE) lstrcpy(szTmp,"IMN_GUIDELINE");

          if (wParam == IMN_OPENCANDIDATE) lstrcpy(szTmp,"IMN_OPENCANDIDATE");

          if (wParam == IMN_OPENSTATUSWINDOW) lstrcpy(szTmp,"IMN_OPENSTATUSWINDOW");

          if (wParam == IMN_SETCANDIDATEPOS) lstrcpy(szTmp,"IMN_SETCANDIDATEPOS");

          if (wParam == IMN_SETCOMPOSITIONFONT) lstrcpy(szTmp,"IMN_SETCOMPOSITIONFONT");

          if (wParam == IMN_SETCOMPOSITIONWINDOW) lstrcpy(szTmp,"IMN_SETCOMPOSITIONWINDOW");

          if (wParam == IMN_SETCONVERSIONMODE) lstrcpy(szTmp,"IMN_SETCONVERSIONMODE");

          if (wParam == IMN_SETOPENSTATUS) lstrcpy(szTmp,"IMN_SETOPENSTATUS");

          if (wParam == IMN_SETSENTENCEMODE) lstrcpy(szTmp,"IMN_SETSENTENCEMODE");

          if (wParam == IMN_SETSTATUSWINDOWPOS) lstrcpy(szTmp,"IMN_SETSTATUSWINDOWPOS");

 

          wsprintf(str, "WM_IME_NOTIFY 명령=%s, lParam=%x",szTmp,lParam);

          AddString(str);

          break;

     case WM_IME_STARTCOMPOSITION:

          AddString("WM_IME_STARTCOMPOSITION");

          break;

     case WM_IME_ENDCOMPOSITION:

          AddString("WM_IME_ENDCOMPOSITION");

          break;

     case WM_IME_COMPOSITION:

          szChar[0]=HIBYTE(LOWORD(wParam));

          szChar[1]=LOBYTE(LOWORD(wParam));

          szChar[2]=0;

          if (lParam | GCS_COMPATTR) {

              lstrcpy(szTmp,"GCS_COMPATTR | ");

          } else {

              lstrcpy(szTmp,"");

          }

          if (lParam & GCS_COMPCLAUSE) lstrcat(szTmp,"GCS_COMPCLAUSE | ");

          if (lParam & GCS_COMPREADSTR) lstrcat(szTmp,"GCS_COMPREADSTR | ");

          if (lParam & GCS_COMPREADATTR) lstrcat(szTmp,"GCS_COMPREADATTR | ");

          if (lParam & GCS_COMPREADCLAUSE) lstrcat(szTmp,"GCS_COMPREADCLAUSE | ");

          if (lParam & GCS_COMPSTR) lstrcat(szTmp,"GCS_COMPSTR | ");

          if (lParam & GCS_CURSORPOS) lstrcat(szTmp,"GCS_CURSORPOS | ");

          if (lParam & GCS_DELTASTART) lstrcat(szTmp,"GCS_DELTASTART | ");

          if (lParam & GCS_RESULTCLAUSE) lstrcat(szTmp,"GCS_RESULTCLAUSE | ");

          if (lParam & GCS_RESULTREADCLAUSE) lstrcat(szTmp,"GCS_RESULTREADCLAUSE | ");

          if (lParam & GCS_RESULTREADSTR) lstrcat(szTmp,"GCS_RESULTREADSTR | ");

          if (lParam & GCS_RESULTSTR) lstrcat(szTmp,"GCS_RESULTSTR | ");

          if (lParam & CS_INSERTCHAR) lstrcat(szTmp,"CS_INSERTCHAR | ");

          if (lParam & CS_NOMOVECARET) lstrcat(szTmp,"CS_NOMOVECARET | ");

 

          if (lstrlen(szTmp) != 0) szTmp[lstrlen(szTmp)-2]=0;

          wsprintf(str, "WM_IME_COMPOSITION 문자=%s,lParam=%s",szChar,szTmp);

          AddString(str);

          break;

          //return 0;

     case WM_IME_CHAR:

          if (IsDBCSLeadByte((BYTE)(wParam >> 8))) {

              szChar[0]=HIBYTE(LOWORD(wParam));

              szChar[1]=LOBYTE(LOWORD(wParam));

              szChar[2]=0;

          } else {

              szChar[0]=(BYTE)wParam;

              szChar[1]=0;

          }

          wsprintf(str, "WM_IME_CHAR 문자=%s,lParam=%x",szChar,lParam);

          AddString(str);

          break;

          //return 0;

     case WM_IME_COMPOSITIONFULL:

          AddString("WM_IME_COMPOSITIONFULL");

          break;

     case WM_IME_CONTROL:

          AddString("WM_IME_CONTROL");

          break;

     case WM_IME_KEYDOWN:

          AddString("WM_IME_KEYDOWN");

          break;

     case WM_IME_KEYUP:

          AddString("WM_IME_KEYUP");

          break;

     case WM_IME_SELECT:

          AddString("WM_IME_SELECT");

          break;

     case WM_CHAR:

          wsprintf(str, "WM_CHAR 문자=%c,lParam=%x",wParam,lParam);

          AddString(str);

          return 0;

     }

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

}

 

리스트박스를 하나 만들고 전달된 메시지의 내용을 문자열로 바꾼 후 리스트박스에 순서대로 보여주는 간단한 구조를 가지고 있다. IME 메시지는 모두 문자열로 바뀌어 리스트박스에 출력되며 DefWindowProc으로 전달된다. 키보드로 문자열을 입력하면서 어떤 메시지가 전달되는지 살펴 보도록 하자. 먼저 영문으로 korea를 입력해보았다.

실행되자 마자 포커스를 받았으므로 WM_IME_SETCONTEXT 메시지가 전달되며 상태 윈도우를 열었다는 통지 메시지도 전달되었다. IMN_OPENSTATUSWINDOW 통지 메시지는 시스템설정에서 IME 상태표시 옵션이 선택되어 있지 않으면 전달되지 않는다. 각각의 영문문자에 대해 WM_CHAR 메시지가 전달되는데 보다시피 영문을 입력할 때는 WM_CHAR 메시지면 충분하다. 이번에는 한글을 입력할 때 어떤 메시지가 전달되는지 보기 위해 한글 모드로 바꾼 후 사람을 입력해보자. ㅅ ㅏ ㄹ ㅏ ㅁ 그리고 스페이스까지 순서대로 입력하였다.

역시 영문보다는 훨씬 더 복잡하다는 것을 알 수 있는데 한 음소가 입력될 때마다 여러 개의 메시지가 전달된다. ㅅ ㅏ ㄹ까지 입력했을 때의 조립 윈도우에는 이라고 나오며 각 음소마다 WM_IME_COMPOSITION/GCS_COMPSTR이 전달되어 아직 조립중이라는 것을 알려준다. 이 상황에서 ㅏ를 입력하면 한 음절이 완성되면서 흥미진진한 일대의 사건이 벌어지는데 순서대로 메시지를 분석해보자.

 

메시지

의미

WM_IME_COMPOSITION/

GCS_RESULTSTR

자가 완전히 조립되었다. ‘다음에 입력된 문자가 모음이므로 받침이 제거되고 자가 확정된다.

WM_IME_CHAR

완성된 문자를 별도의 메시지로 다시 보내준다.

WM_IME_COMPOSITION

/GCS_COMPSTR

자는 아직 조립중이다. 다음 입력되는 문자에 따라 수도 있고 수도 있으므로 완성되지 않았다.

WM_CHAR

완성된 자의 상하위 바이트에 대해 메시지를 보내준다.

 

문자에서 ㅏ가 입력됨으로써 가 완성되고 는 아직 조립중인 상태인데 하나의 문자가 완성될 때 여러 개의 메시지가 동시에 전달된다.

한 사건에 대해 왜 이렇게 여러 개의 메시지가 전달되는가 하면 IME를 처리하는 프로그램이든 무시하는 프로그램이든 모두 이 문자를 전달받아야 하기 때문이다. 만약 IME 메시지를 직접 처리하면서 더 이상의 다른 메시지를 받을 필요가 없다면 해당 메시지를 DefWindowProc으로 보내지 않으면 된다. WM_IME_CHAR의 끝에 있는 break; 문을 return 0;으로 변경해보자. 그리고 다시 같은 문자를 입력하면 완성된 문자에 대해 WM_CHAR 메시지는 오지 않는다.

이 결과로부터 한글 완성시에 보내지는 WM_CHAR메시지는 WM_IME_CHAR 메시지에 의해 추가적으로 발생하는 메시지임을 알 수 있다. WM_IME_CHAR 메시지에서 return 0;하는 것은 내가 완성된 문자를 직접 다루겠다는 확실한 의사 표현이 된다. WM_CHAR에서는 영문문자만 입력받고 한글 입력은 WM_IME_CHAR에서 처리하겠다면 이 방식을 사용하면 된다.

또한 마찬가지로 WM_IME_COMPOSITION의 끝에 있는 break; 문도 return 0;으로 변경하면 완성된 문자에 대해 WM_IME_CHAR 메시지가 발생하지 않는다. WM_IME_CHAR 메시지도 추가로 발생하는 메시지이다.

이렇게 되면 완성된 문자든 조립중인 문자든 모두 WM_IME_COMPOSITION에서 처리하겠다는 의사 표시를 분명히 하는 셈이 된다. 이 예제를 통해 IME 메시지가 어떤 순서대로 발생하는지를 잘 정리해놓고 다음으로 넘어가도록 하자. 생각보다 훨씬 복잡하므로 그 순서를 잘 기억해 둘 필요가 있다. 여러 가지 문자들을 입력해보면서 메시지가 어떻게 발생하는지 잘 연구해보도록 하자.