IMM과 IME

키보드 궁금하니? 2008. 12. 4. 19:49
출처: http://blog.naver.com/blueriderx?Redirect=Log&logNo=90018308451

IMM과 IME

2007/06/04 13:31

복사 http://blog.naver.com/blueriderx/90018308451

이 글을 써야 하는 이유 
한글 윈도우에서 영문 프로그램을 사용해도 한글을 쓰는 데는 큰 문제가 없다. 하지만 뭔가 어색함을 느끼게 된다. 예를 들면 한글을 입력하다가 지우거나 하면, 절반씩 쪼개지거나 줄의 끝에서 절반으로 쪼개지곤 한다. 또 어떤 경우에는 한글 윈도우의 한글/한자 변환 기능을 제대로 사용할 수 없는 때도 있다. 한편 영문 윈도우즈용으로 만들어졌지만 한글을 자체 내장하고 있는 프로그램을 한글 윈도우에서 실행시키면, 한글 윈도우의 한/영 전환키와 충돌하는 등 뭔가 개운치 않다. 반대로 한글 윈도우 용으로 만들어진 프로그램을 영문 윈도우즈의 한메한글 상태에서 작동시켜도 어설프긴 매 한 가지다.
 심지어 어떤 프로그램은 뜨지도 않고 죽어버리며, 작동한다해도 엉뚱하게 동작하거나 죽어버리는 경우도 있다(마소CD의 Issue 1이 좋은 예이다. 한글 윈도우에서는 잘 작동하지만, 한메한글에서는 갑자기 죽는다. 다음 버전에서는 수정할 계획이라 함).  이런 문제는 한글 윈도우가 영문 윈도우즈와 정확히 어떤 점에서 어떻게 다른지 이해한다면, 확실히 해결할 수 있다. 또 한글 윈도우용의 한글 SDK 매뉴얼만 정독해도 많은 지식을 습득할 수 있을 것이다.
하지만 한글 SDK의 중요 부분은 모두 영문으로 되어 있기 때문에 쉽게 이해하기가 어렵고, 그래서 필자 같은 개발자들은 그동안 누군가 설명해줄 것을 은근히 기다려왔다. 그러나 안타깝게도 윈도우즈 자체에 대한 기사나 발표는 여럿 봐왔지만, 한글 윈도우에 관한 것은 거의 찾아 볼 수가 없었다.
        특히 최근 들어서는 윈도우즈용 SDK가 비주얼 C++나 볼랜드 C++ 등의 컴파일러에 포함되는 형태로 발표되고 있어, 윈도우즈용 프로그램을 작성하는 데 많이 사용되고 있다. 하지만 한글 버전의 컴파일러가 아직 발표되지 않은 상황에서 한글 SDK에 포함되어 있는 내용을 한글 SDK없이 사용할 수 있는 솔루션이 반드시 필요하다.
가까운 예로 일본 버전의 비주얼 C++에는 일본어 윈도우즈용의 헤더와 라이브러리 파일이 포함되어 있는 등 윈도우즈 시장 자체의 로컬라이즈를 위해 많이 노력하고 있다.
하지만 한국마이크로소프트의 경우에는 한글 윈도우와 오피스 등 몇 가지 '돈이 될 만한 제품(Money-Making Products)'을 로컬라이즈하여 파는 데 치중할 뿐, 시장이 작거나 돈이 안되는 품목은 미국 것을 그대로 들여다 파는 수입상 역할밖에 못하고 있다.
결국 이런 상황 속에서 프로그래머용 도구인 컴파일러 등은 미국 것을 그대로 쓰면서 타겟은 한국에서 사용하는 한글 윈도우로 해야 하는 우수꽝스런 상황이 벌어지고 있는 것이다.
        어쨌든 필자가 이 글을 쓰는 이유는 이런 답답한 상황을 조금씩이나마 해결해 가자는 목적에서 출발한 것이다. 따라서 윈도우즈 프로그래밍을 이해하고 한글 윈도우나 한메한글, 자체 한글 등을 사용한 프로그래밍을 하고 싶거나 이미 하고 있는 개발자가 주 대상이다.

기본 개념
한글 윈도우는 어떤 것이냐
우선 한글 윈도우 3.1은 마이크로소프트가 한국을 특별히 배려해서
한글화작업을 한 버전이 아니라는 사실을 알아야 한다. 윈도우즈 3.1은
세계적으로 크게 4가지 버전이 있는데, 그중의 한 버전이다.

        첫째는 마이크로소프트와 미국인들이 쓰기 위한 원래 미국
버전의 윈도우즈로, 표준 윈도우즈이며 미국 이외 지역에서도 상당히
많이 사용되고 있다.

 두번째로 동일한 1바이트 코드체계이면서 각 나라별로 상위 바이트의
배열과 화폐 단위, 시간/날짜 순서 등을 변경한 버전인 유럽 버전의
윈도우즈가 있다. 세번째로는 역시 1바이트 체계지만 오른쪽에서
왼쪽으로 쓰는 근동 지역의 아랍어권의 윈도우즈가 있다.

그리고 네번째로는 문자 수가 많아서 1바이트 표현이 도저히 불가능한
1바이트 코드와 2바이트 코드를 혼용해 쓰는 극동 지역을 위한
DBCS(Double Byte Code System) 윈도우즈가 있다.

다시 말해 한글 윈도우는 한국마이크로소프트가 단독 설계한 것이
아니라, FECAPI(Far East Common Application Programming Interface,
극동지역 공통 응용 프로그램 인터페이스)에 따라 중국어 버전(대만과
본토 버전), 일본어 버전, 한글 버전 등과 함께 동시에 설계된 DBCS
버전의 윈도우즈중 하나인 것이다.

        한글 윈도우에서 특이할 만한 것은 이것의 입력기가 일본어
윈도우나 중국어 윈도우보다 더 복잡하다는 점이다. 왜냐 하면
일본어나 중국어는 한글과 같이 미완성/완성 코드 개념이 없기
때문이다.

하지만 한글 윈도우의 입력기는 독립적으로 추상화되어 있어 일단 한글
윈도우의 IMM, IME를 이용한 프로그램에 익숙해지면, 중국과 일본
시장용 버전도 손쉽게 작업할 수 있는 이점이 있다. 이런 이유로
마이크로소프트가 한글 제품(한글 워드, 한글 엑셀 등이 대표적이다)을
만들 때는 대부분 일본이나 대만 출신 개발자와 한 팀이 되어 작업을
많이 한다. 참고로 볼랜드 제품의 한글 버전은 싱가포르 등에서 작업을
해서 들여오는 경우가 많다.


입력기란 무엇이고, 왜 필요한가
입력기라고 하면 보통 키보드 등을 통해 사용자가 컴퓨터에 어떤
내용을 입력하도록 도와주는 프로그램을 말한다.

특히 한글을 사용하는 환경에서 입력기가 중요한 것은 영문 같은
1바이트 글자들은 한 글자를 치면 글자가 입력되는 단순한 과정이지만,
한글이나 한자는 글자 하나를 입력하기 위해 여러 번 글자를 쳐야 하는
등 복잡한 과정을 거쳐야 하기 때문이다.

또 한 가지 이유는 한글 입력 방법도 사용자에 따라 여러 가지로 나뉠
수 있다는 데 있다.

        우리나라는 일본이나 중국에 비하면 입력기가 훨씬 발전한
상태이다. 입력 방법도 크게 2벌식/3벌식의 2가지로 통일되어 있고,
그다지 혼란스럽지 않다.

하지만 일본의 경우에는 입력 방식이 매우 다양하고, 중국은 매년 수십
개의 새로운 입력 방식이 발표될 정도로 혼란스럽기 짝이 없다.

일본이나 중국의 입력기를 보고 있노라면 한글은 항상 초성 → 중성 →
종성의 순서에 따라 자소가 조합되어 글자가 만들어지도록 한 우리
조상의 슬기에 감사해야 한다는 생각이 든다.

 비록 2벌식이니 3벌식이니 끊임없이 싸우고들 있지만, 자소를
조합해서 글자를 만들어 내는 과정과 이 과정이 완료되어 다음 글자로
넘어가기 전에는 현재 글자가 과연 어떤 글자가 될지 아무도 알 수
없다는 점에서 프로그래머의 관점에선 2벌식과 3벌식이 거의 동일하다.

        윈도우즈 이전의 도스 프로그램들은 한글을 사용하기 위해
별도의 램상주 프로그램을 이용하거나, 자체적으로 한글을 내장하는
방법을 썼다. 한 때는 그 편리성으로 인해 한글을 자체 내장한
프로그램이 더 선호되었고, 경쟁적으로 한글을 내장한 프로그램이 많이
나왔다.

그러나 한글을 내장하는 과정에서 가장 어려웠던 것은 한글을 출력하는
부분보다는 한글을 입력받는 쪽이었다.

윈도우즈에서는 한글 출력 부분을 영문 출력 부분과 동일하게 다루도록
함으로써 해결했지만, 입력기 쪽은 영문 윈도우즈와는 다른 해결책을
이용해 해결했다. 바로 IMM(Input Method Manager, 입력기 매니저)과
IME(입력 편집기)라는 개념을 도입한 것이다.
0
        DBCS 버전의 윈도우즈에서 입력을 처리하는 부분은 크게 IMM과
IME으 두 부분으로 나뉜다. 이중 IMM은 입력을 총괄하는 공통
부분이고, IME는 키보드나 입력 방식의 차이에 따라 사용자가 끼워
넣어(Plug-In) 사용하는 모듈이다.

예를 들면 현재 한글 윈도우 3.1에는 4개의 IME가 번들되어 있다.

① 2벌식 입력기 - 문자 단위(문자 단위로 지워진다)
② 2벌식 입력기 - 자소 단위(자소 단위로 지워진다)
③ 3벌식 입력기 - IBM390 자판 기준
④ 4벌식 입력기 - 공병우식 자판 기준

        다른 나라에서는 이러한 입력 부분이 써드파티 ISV에 의해
개발되어 제어판을 통해 추가로 설치해서 사용할 수 있도록 되어 있다.
하지만 국내에서는 한국마이크로소프트를 통한 IME의 업그레이드(한글
워0드, 한글 엑셀 등 응용 프로그램 발매 시마다 IME의 신버전이
포함되어 있다) 외에는 별다른 써드파티에서의 IME 개발이 이루어지지
못하고 있다.

이것은 한국마이크로소프트의 폐쇄적인 정책 탓이라고도 할 수 있는데,
일례로 우리와 비슷한 상황의 일본어 윈도우즈를 보자. 일본어
윈도우즈에는 흔글과 유사한 워드프로세서인 이찌다로를 만드는
회사에서 나오는 윈도우즈용 IME가 들어 있는데,

이것은 일본마이크로소프트가 개발한 IME보다 더 편리하고 널리 쓰이고
있다. 하지만 우리의 상황은 어떤가. 흔글을 만드는 한글과컴퓨터가
한글 윈도우를 지원하여 더 쓰기 편하게 수정하기 보다는 독자 노선을
걷는 양상을 보이고 있어, 당분간 혁신적으로 편리한 IME를 기대하는
것은 어려울 것 같다.

 그러나 한편으로 다르게 생각한다면 그만큼 우리의 입력기 문화가
안정화되어 있어서 일본이나 중국처럼 계속 바뀌야 하는 상황이
아니라는 점에서는 무척 다행스런 일이라 할 수 있다.
   마이크로소프트웨어 구입문의 : 588-7682


입력기 매니저(IMM)
한글 윈도우에 여러 개의 IME를 설치할 수 있는 관계로 일부
사용자들은 IME가 입력기의 전부라고 생각할 수도 있을 것이다. 하지만
프로그래머라면 IME를 제어하는 부분, 즉 IMM에 대해 알아 둬야 할
필요가 있다.

        IMM 자체는 DLL로 구성되어 있으며, 독자적인 윈도우즈
프로그램인 IME를 호출하는 역할과 키보드 입력을 훅(Hook)을 통해
가로챈 다음 IME의 함수를 불러 현재 상황에 맞게 재조합해서 키보드
입력 메시지가 보내져야 할 응용 프로그램으로 보내는 기능도 한다.

 즉 IMM은 IME의 상사(Manager)로써 그보다 상급자인 사장(응용
프로그램)에게 지시받아 다시 IME에게 업무 지시를 하고, 그 결과를
사장(응용 프로그램)에 다시 보고하는 등의 중간 관리자 역할을 한다.

그러나 프로그래머가 IMM을 직접 제어할 때라고는 IME 자체를 껐다
켜는 정도 외에는 없으며, 나머지는 IMM의 존재를 거의  느끼지
않으면서 프로그램을 짤 수 있다.

 따라서 자체 한글을 내장한 프로그램의 경우에 포커스를 갖게 되면,
IME를 잠시 껐다가 포커스를 2잃을 때 IME를 원래대로 켜주는 정도로
사용하는 외에는 IMM의 API를 직접 이용하는 경우는 거의 없을 것이다.

입력 편집기(IME)
IME는 키보드 입력을 통해 한글 자소를 조합할 수 있도록 도와주는
프로그램이다.

입력 편집기 또는 입력기를 제공할 수 있는 회사는 한글 SDK 상에
씌어진 대로라면, 마이크로소프트와 OEM 업체, 또는 입력기 전문
제공업체로 제한된다.

그러나 현재까지는 마이크로소프트에서 제공한 솔루션만이 있을
뿐이다. IMM이 DLL로 작동하는 것과 달리 IME는 독자적인 윈도우즈
프로그램이며, 글자 조합을 위해 자신의 조합 모듈을 사용한다. 그래서
2벌식 IME과 3벌식 IME는 자체 내에 각각 다른 조합 모듈을 갖고 있다.

        IME는 3가지 종류의 윈도우를 갖고 있다. 첫번째는 현재
상태(영/한, 전각/반각, 한자 버튼)를 나타내는 모드 윈도우, 두번째는
글자가 조합중일 때 그 모습을 보여주는 인터림(interim) 윈도우(한글
윈도우용 응용 프로그램에서는 나타나지 않지만, 영문 윈도우즈용 응용
프로그램을 한글 윈도우에서 실행시키면 나타난다),

세번째는 한자를 고를 수 있게 하는 한자 리스트 윈도우이다. 이 세
윈도우는 IME API라는 DBCS 버전의 윈도우즈에 추가된 API를 통해
제어할 수 있다.


        IME는 여러 응용 프로그램과의 호환성을 위해 다음과 같은
4개의 입력 수준을 갖고 있다.

1 수준
IME가 기정된 위치에 조합중인 한글과 한자 리스트가 나타나며, 응용
프로그램은 조합중인 글자에 대해서는 전혀 메시지를 받지 않는다.

단지 글자가 완성되었을 때만 코드값을 전달받게 되며, 완성된 글자는
WM_CHAR 메시지를 통해 전달된다. 

        이 수준은 완성된 글자만을 통과시키므로 복잡한 한글 입력을
고려하지 않고 설계된 프로그램(대부분의 영문 윈도우즈용 응용
프로그램들) 등에 적합하다. 프로그램 수행 시 IME 레벨을 세팅하지
않고 그냥 실행시키면 디폴트로 이 수준이 적용된다.

        하지만 입력중인 글자가 엉뚱한 위치에 보이다가 완성되었을
때 툭 튀어 나오고, 조합된 글자는 반쪽씩 지워지는 등의 문제가 있어
별로 권장하고 싶지 않다.

2 수준
응용 프로그램에 완성된 글자만을 전달한다는 점에 있어서는 1 수준과
동일한 방법이다. 다른 점이 있다면 응용 프로그램의 현재 캐럿
위치(caret position)에 인터림 윈도우가 표시되어 조합중인 글자가
응용 프로그램의 케럿 위치에 나타나므로 복잡한 처리가 없는 간단한
에디터나 통신 에뮬레이터 등에 적합한 방법이다.

        그러나 시스템 폰트가 아닌 다양한 폰트를 사용해야 하는
워드프로세서나 스프레드시트 등 대부분의 응용 프로그램에서는 입력
도중 갑자기 12포인트짜리 미완성 글자가 튀어나오면 신경이 쓰여
작업하는 데 더 불편할 것이다.

3 수준
IME는 현재까지 입력된 키보드에 따른 조합중인 한글 글자를
WM_INTERIM 메시지를 통해, 완성된 한글 글자는 WM_CHAR를 통해 응용
프로그램에 전달한다. 이 수준을 통해 조합중인 글자에 대해서도
적당한 폰트와 색상 등을 지정해 마치 완성된 글자처럼 다룰 수 있다.
따라서 거의 모든 응용 프로그램에서 이용할 수 있다. 

        그러나 이 부분을 처리하려면 그만큼 응용 프로그램에 부담이
많이 간다. 특히 2바이트 코드가 한 번에 오지 않고 두 번에 걸쳐
1바이트씩 전달되는 등 불합리한 요소가 많지만, 현재까지는 어쩔 수
없는 차선책이라고 생각된다. 시중에 나와 있는 거의 모든 한글
윈도우용 프로그램이 이 수준을 사용하고 있다고 생각하면 틀림없다.

4 수준
키보드에서 입력되는 모든 글자들이 VK(Virtual Key, 가상키)
코드값으로 변환없이 응용 프로그램에 전달된다. 그렇다면 IME를 쓰지
않는 것과 다른 점이 없는데 왜 IME API를 쓰느냐고 반문하는 독자가
있을지도 모르겠다.

  이것은 이 코드값을 응용 프로그램에서 다시 재처리한 후 IME에 보내
조합하거나 한자 변환을 거치는 등의 작업을 하기 위한 것이다. 예를
들어 요즘의 윈도우즈용 워드프로세서에서 특수 키보드를 부르게 하는
루틴 등은 현재의 키입력 모드에 따라 VK 코드값을 변환시킨 다음
IME에 전달하거나, 아니면 직접 출력하는 등의 방법을 이용한다. 이
방법은 키보드 매크로 등을 구현할 때도 이용할 수 있다. 

        그러나 이 수준을 적용하기 위해서는 응용 프로그램의 입력
부분이 상당히 복잡해지며, 대부분의 응용 프로그램이 3 수준만으로 별
무리없이 처리된다.
   마이크로소프트웨어 구입문의 : 588-7682


IME를 활용하기 전에
문자열 처리 함수
IME를 활용한 예제 프로그램을 보이기 전에 예제에서 활용하고 있는
문자열 처리 함수에 대해 알아야 겠다. 이 함수들을 윈도우즈에서
표준으로 제공하는 API중 하나지만, 한글 윈도우와 영문 윈도우즈에서
서로 다르게 구현되어 있다.

한글 윈도우에서 동작하지 않는 영문 프로그램이 있는데, 바로  이런
이유에서이다.

        또한 이 함수에 의존적인 프로그램을 짜면 한메한글
환경에서는 정확히 동작하지 않는 수도 있다. 따라서 필자는
프로그래밍을 할 때 다음 기능을 하는 함수들을 별도로 재작성해 놓고
사용함으로써 한글 윈도우와 한메한글 환경에서의 차이를 극복하고
있다. 문제가 되는 함수들은 다음 3가지이다.

BOOL IsDbcsLeadByte(BYTE bChar)
바이트인 bChar값이 KSC5601에 지정된 범위 내의 2바이트 코드에
속하는지의 여부를 돌려준다. 다시 말해 0xA1 <= bChar <= 0xFE까지일
때 TRUE를 리턴하며, 이외의 경우에는 FALSE값을 돌려준다. 

        한 가지 주의할 점은 char형을 놓고 (0xa1 <= bChar && bChar
<= 0xfe)와 같은 식으로 연산해서는 안된다는 것이다. 왜냐 하면
bChar형은 기본적으로 Signed된 값이고, 0xa1,0xfe 등은 int형이므로
전혀 원하지 않는 결과값이 나오게 되기 때문이다.

이보다는 차라리 (bChar & 0x80)과 같은 식으로 연산하는 것이 더
바람직하다(정밀도는 떨어지지만, 조합형 코드에 대해서도 적용할 수
있다).

AnsiNext
문자열 내에서 다음 글자로 이동한 포인터값을 돌려준다. 지정된
글자가 DBCS의 앞쪽 바이트에 해당되는 값을 가진다면, 이 함수를
이용해 2바이트 전진한 코드값을 얻을 수 있다.

그러나 한메한글에서는 어떤 상황에서도 항상 1바이트 전진한 코드값만
나오게 되므로 이 함수에 의존적인 프로그램을 짜면 문제가 발생할 수
있다. 1바이트 영문 프로그램에서 ptr++ 등으로 쓰던 문자열 처리
부분을 이 함수나 대응하는 다른 함수로 바꿔 사용하도록 수정해야
한다.

AnsiPrev
문자열 내에서 이전 글자로 이동한 포인터값을 돌려준다. 지정된
글자가 DBCS의 앞쪽 바이트라면 2바이트만큼을 뺀 포인터값이 돌아
오겠지만, DBCS의 뒷쪽 바이트라면 3바이트만큼을 뺀 포인터값이
돌아오는 동작을 한다.

        주의할 것은 AnsiPrev는 항상 문자열의 제일 앞부터 검색해
와야 하기 때문에 이 함수를 이용하면 프로그램의 성능에 영향을 받을
수 있다는 점이다. 1바이트 영문 프로그램에서 ptr-- 등으로 쓰이던
문자열 검색 부분을 대치하려는 용도로 만들어졌다.
   마이크로소프트웨어 구입문의 : 588-7682


DBCS 글자 처리 변환
DBCS 환경에 맞춰 제대로 동작하는 프로그램을 만들려면 어떻게 해야
할까?  한글 SDK 매뉴얼을 보면 다음 상황을 고려해야 한다고 설명하고
있는데, 읽어보면 너무 당연한 내용 같지만 사실은 모든 윈도우즈용
한글 응용 프로그램을 작성할 때의 기본 수칙이다.

▣ DBCS 코드값을 입력으로 받아들인다
심지어는 IME를 거치지 않고, <Alt>키 조합을 이용한 한글 코드값
입력도 자연스럽게 받아 들여야 한다.

▣ DBCS 코드의 첫 바이트와 두 바이트 사이에 다른 내용을 다시
추가해서는 안되며, DBCS 코드의 두 값은 항상 연속적으로 처리되어야
한다

▣ 글자를 지울 때
DBCS 코드 글자는 두 바이트가 동시에 지워져야 하며, 반쪽씩 지워지는
동작을 해서는 안된다.

▣ 글자를 선택할 때
DBCS 코드 글자들을 절반씩 선택하도록 허용해서는 안된다.

▣ 마우스를 더블 클릭하여 단어를 선택할 수 있도록 허용하는
프로그램
DBCS 글자들을 포함하는 단어도 선택할 수 있도록 해야 한다. 예를
들어 영문 프로그램을 그대로 사용할 경우 'Anti라는'이라는 단어에서
더블 클릭을 하면 'Anti라는'처럼 Anti만 선택되고 말지만, 한글화
작업이 끝나면 'Anti라는' 자체가 하나의 단어로 취급되어야 한다.

▣ 마우스를 이용해 입력하는 위치를 지정할 때
커서를 DBCS 글자 중간에 위치시키지 않고, 항상 왼쪽이나 오른쪽에
위치하도록 동작해야 한다.

① 클릭한 위치가 글자의 좌측 3/4 위치 이내라면 글자의 좌측에
커서를 위치시킨다(DBCS 코드의 두번째 바이트의 절반 위치보다 앞쪽을
찍었다는 말도 된다).

② 클릭한 위치가 글자의 우측 1/4 지점 이내라면 글자의 우측에
커서를 위치시킨다(DBCS 코드의 두번째 바이트의 절반보다 더 우측을
찍었다는 말도 된다).

▣ 키보드를 이용해서 입력 위치를 지정할 때
커서가 DBCS 코드의 중간에 위치하지 않도록 해야 한다.

▣ 멀티라인 윈도우에서 오른쪽에 DBCS 코드를 표시하는 데 충분한
공간이 없을 경우
글자가 반쪽씩 표시되면 안되며, 그 글자는 다음 줄의 첫번째로 옮겨져
표시되어야 한다. 


IMM API
IMM에는 총 9개의 API가 있지만, 이중 응용 프로그램이 사용할 수 있는
일반적인 API로는 단 2개만 알면 충분하다. 

BOOL WINAPI WINNLSEnableIME(HWND hWnd, BOOL bEnable)
IME을 잠시 활성화/비활성화하면서 동시에 IME에서 사용되는
윈도우들을 표시하거나 감춘다. 이 함수를 이용하는 한글을 자체
내장한 프로그램을 동작시킬 때 IME를 잠시 비활성화시키면, 한/영
전환키가 충돌하는 등의 문제를 막을 수 있을 것이다.

        그러나 윈도우즈 3.1은 다른 프로그램과 IME를 공유해야
하므로 포커스를 잃었을 때는 자신이 IME를 비활성화하기 이전 상태로
IME를 원상복귀시켜 놓아야 한다. 윈도우즈 3.1은 프로그램끼리 사이
좋게 지내야 하는 더부살이임을 명심하도록.

 hWnd는 항상 NULL이어야 하며, bEnable이 TRUE이면 IME를 활성화,
bEnable이 FALSE이면 비활성화이다. 그리고 리턴값은 상태를 바꾸기
이전의 상태값이다.

BOOL WINAPI WINNLSGetEnableStatus(HWND hWnd)
바로 직전의 WINNLSEnableIME 함수로 인해 세팅된 현재의 IME 상태를
알아낸다. hWnd에는 항상 NULL을 넣어야 하고, 리턴값이 TRUE이면 현재
활성화된 상태, FALSE이면 현재 비활성화된 상태이다.

        이외에도 전각/반각/한자 등 버튼의 눌린 상태를 제어할 수
있는 함수가 있지만, 자주 사용되지 않으므로 따로 설명하지 않겠다.

IME API
IME에는 SendIMEMessage와 SendIMEMessageEx의 두 개 API밖에 존재하지
않는다. 그렇다면 어떻게 그 많은 메시지를 주고 받는 걸까? 바로
윈도우즈의 MainWndProc에 넘기는 wParam과 lParam처럼 정해진 형식의
메시지 파라미터용 구조체를 정해 놓고 메시지를 주고 받는 방법을
사용한다. 따라서 IME를 사용하려면 우선 여기에서 사용하는 구조체의
내용을 알아야 한다.

데이터 구조
typedef struct tagIMESTRUCT {
        UINT    fnc;            // 서브 펑션 지정
        WPARAM  wParam;         // 이하 서브 펑션에 따라 의미 변경
        UINT    wCount;
        UINT    dchSource;
        UINT    dchDest;
        LPARAM  lParam1;
        LPARAM  lParam2;
        LPARAM  lParam3;
        } IMESTRUCT;
typedef IMESTRUCT far *LPIMESTRUCT;

WORD WINAPI SendIMEMessage(hWndApp, lParam)
IMESTRUCT 구조체에 지정된 IME의 서브 펑션을 부르거나 해당 기능을
수행하는 데 사용된다. 서브 펑션은 현재 활성화된 IME로 보내진다.
hWndApp는 IME를 사용하려는 프로그램의 윈도우 핸들, lParm은
IMESTRUCT 구조체를 담고 있는 글로벌 메모리의 핸들을 하위 워드에
담는다.

이때 메모리는 GMEM_MOVEABLE과 GMEM_SHARE 플래그를 갖고,
GloballAlloc 함수를 이용해 할당받은 것이어야 한다. 상위 워드는
사용되지 않는다.

리턴값으로는 TRUE와 FALSE가 돌아오는데, TRUE일 때는 성공적으로
수행되었을 경우이고, FALSE는 당연히 뭔가가 잘못되었을 경우이다.
FALSE일 경우에는 IMESTRUCT의 wParam에 에러 코드가 담기는데, 에러의
종류와 의미는 한글 SDK를 참조하기 바란다.

LRESULT WINAPI SendIMEMessageEx(hWndApp, lParam)
위의 경우와 동일하며, 단지 결과값이 실패일 경우 구조체에 담겨
돌아오는 wParam의 의미와 종류가 SendIMEMessage API보다 다양하여
보다 정밀한 프로그래밍에 유효하다. 

        하지만 이 API는 WM_CONVERTREQUESTEX 메시지를 처리하는
IME에 대해서만 유효하다는 점에 주의해야 한다.
   마이크로소프트웨어 구입문의 : 588-7682


서브 펑션의 종류
IME에서 지원하는 서브 펑션들은 종류가 상당히 많다(<표 1> 참조).

<표 1> IME 지원 서브 펑션과 기능
------------------------------------------------------------------
------------------------
서브 펑션                       기능
------------------------------------------------------------------
------------------------
IME_AUTOMATA                    한글 오토마타를 제공한다.
IME_CODECONVERT                 전자/반자 및 완성형/조합형 변환을
제공한다.
IME_CONVERTLIST                 한자 리스트를 얻는다.
IME_DESTROYIME*                 IME를 종료한다.
IME_GETIMECAPS(IME_QUERY)       현재 IME에서 해당 서브 펑션이
지원되는지 체크한다.
IME_GETLEVEL                    입력 모드의 수준을 얻어낸다.
IME_GETMNTABLE                  니모닉 테이블을 얻는다.
IME_GET_MODE                    IME 모드를 얻는다.
IME_GETOPEN                     IME의 상태를 얻는다.
IME_GETVERSION                  IME의 버전을 얻는다.
IME_HANJAMODE                   IME의 한자 모드를 세팅한다.
IME_MOVEIMEWINDOW               IME의 위치를 지정한다.
IME_SELECT                      IME를 선택하거나 선택하지 않는다.
IME_SETLEVEL                    입력 모드의 수준을 지정한다.
IME_SET_MODE                    IME의 모드를 지정한다.

IME_SETOPEN                     IME의 상태를 지정한다.
IME_WINDOWUPDATE*               IME에 관련된 윈도우 표시를 켜거나
끈다(모드
                          윈도우,인터림 윈도우, 한자 리스트
윈도우)
------------------------------------------------------------------
-----
'*'표가 붙은 API들은 IME 내부적으로만 이용되고, 일반적인 응용
 프로그램에서는 사용되지 않는다.
------------------------------------------------------------------
-----

        <표 1>의 서브 펑션을 사용할 때 IMESTRUCT 구조체 내에
담기는 위치와 의미는 서브 펑션별로 다른데, 자세한 내용을 모두
설명하자면 SDK 매뉴얼을 번역하는 셈이 되므로 이 글에서는 피하기로
하자.

다만 '이달의 디스켓'으로 제공되는 한글 입력 예제 프로그램에서
사용하는 IME API에 대해서만 설명하겠다.

        이 예제에서는 간단하면서도 꼭 필수적인 서브 펑션만을
사용했는데, IME_GETLEVEL, IME_SETLEVEL, IME_GET_MODE,
IME_SET_MODE와 IME_HAJAMODE 등 5가지이다. 이중 IME_GETLEVEL과
IME_GET_MODE는 프로그램이 포커스를 갖기 이전에 IME 상태를 알아내어
보관하는 데 사용하고, IME_SETLEVEL과 IME_SET_MODE는 프로그램이
포커스를 갖고 있는 동안 IME를 자신에게 알맞게 세팅해 쓰기
위함이다.

 그리고 IME_HANJAMODE는 한글이 조합중이거나 아닐 때라도 아무
글자에 대고 한자키 혹은 모드 윈도우에서 漢자 버튼을 누르면, 한자
리스트 윈도우가 펼쳐지도록 하는 목적으로 사용되었다.


IME_GETLEVEL
기능 : 현재의 IME가 갖고 있는 한글/한자 입력 수준을 얻어낸다. 호출
시에는 어떤 인수도 필요하지 않다.

리턴값 : wParam에 현재 한글/한자 입력 수준이 담겨 돌아온다.

IME_GET_MODE
기능 : 현재의 IME 모드 윈도우(3개의 단추가 있어 23, 즉 8가지
경우를 조합할 수 있다)의 모드를 알아내는 목적으로 쓰인다. 
리턴값 : wParam에 3개 모드값의 비트 OR 연산한 결과값이 담겨
돌아온다. 각 모드 비트들과 내용은 다음과 같다.

비트 필드명                     내용
IME_MODE_APHANUMERIC            0이면 한글, 1이면 영문
IME_MODE_SBCSCHAR               0이면 전자, 1이면 반자
IME_MODE_HANJACONVERT           0이면 무변환, 1이면 한자 변환 상태
IME_HANJAMODE

기능 :  한자 변환 모드로 IME를 세팅하려는 목적으로 사용된다.
사용자가 굳이 지정하지 않아도 IME는 입력 도중에 한자 변환을
자동으로 해 주지만, 이미 입력된 글자를 넘겨주고 입력 도중일 때처럼
한자 리스트 윈도우가 나오게 하려면 이들 함수를 사용해야 한다.
인수를 두 개 넘겨 주어야 하는데, 각 인수와 그 타입, 내용은 다음과
같다.

인수            타입            내용
wParam          WPARAM          IME_REQUEST_CONVERT
dchSource       UINT            한글 문자열의 오프셋(IME 구조체의
                                시작 위치부터의 오프셋)

리턴값 : 정상적으로 수행되었으면 TRUE, 아니면 FALSE가 돌아온다.

IME_SETLEVEL
기능 : wParam에 선택하고 싶은 레벨을 넣어서 호출한다.
리턴값 : wParam=1, 2, 3, 4일 때는 리턴값이 없다. wParam=0일 때는
이전의 IME 활성화 상태를 돌려주는데, 0일 때는 활성화되어 있는
것을, 1일 때는 비활성 상태인 것을 가리킨다.

IME_SET_MODE
기능 : IME 모드를 세팅한다. wParam에 들어갈 인수는
IME_GET_MODE에서 설명한 것처럼 3가지 비트 필드의 OR 연산한
결과값이다. 
리턴값 : 성공적으로 수행되면 이전의 IME 모드를 돌려준다. 아니면
FALSE이다.


        예제 프로그램 LEV3.EXE을 실행시켜 보면, 태스크 스위치를
통해 다른 프로그램과 왔다갔다 하더라도 프로그램의 한글 입력 모드
창은 모드를 그대로 유지하고 있음을 알 수 있다.

그러나 좀 더 완벽하게 구현하려면 이것만으로는 부족하다. 여기에는
포함되지 않았지만, IME_MOVEIMEWINDOW 등과 같은 서브 펑션을
사용하면 IME 윈도우즈의 여러 개 창 위치를 각 프로그램에 고유하게
보존시키는 것도 생각할 수 있다.

 가령 예제 프로그램을 실행시켜 입력 창을 항상 타이틀바 왼쪽 구석에
놓고 싶다면, 이것은 전적으로 프로그래머의 취향에 달린 것이다.

그러나 <Alt-Tab>키를 눌러 다른 프로그램으로 태스크 스위칭하여
쓰려고 보니까, 입력 창의 위치가 LEV3.EXE에서 쓰던 곳에 그대로 남아
있어서 불편하다면 그것은 프로그래머의 잘못이다.

현재 프로그램이 포커스를 잃을 때에는 당연히 현재 프로그램이
포커스를 갖기 이전 상태로 IME를 돌려주기 위해 노력해야 한다.
아니면 현재 프로그램에서 아예 그 세팅을 바꾸지 말든지.

        이 서브 펑션들을 이용하면 재미있는 기능을 구현할 수 있다.
최근에 나온 마소CD를 들쳐 보면 클리퍼에서 한/영키를 전환하는 등의
내용이 나오는데, 바로 이런 식의 기능을 IME_SET_MODE 함수로
해치울(?) 수 있다.

  또 다른 예로 흔글의 대화상자는 한글 입력 상태이다가도 영문만
입력할 수 있는 대화상자를 열면, 자동으로 영문 모드가 되었다가
끝내고 돌아오면 이전 상태로 되어 있곤 한다.

한글을 입력해야 할 대화상자 등에서는 자동으로 한글로 입력 모드를
바꿔주는 등 대부분의 경우에 번거로운 한/영 전환키를 누르지 않고
편안히 쓸 수 있다.

하지만 안타깝게도 요즘 나오는 윈도우즈용 프로그램들은 그러한
세심한 부분까지 신경을 쓰지 않은 경우가 너무 많다. 어떤 윈도우즈용
워드프로세서는 태스크 스위칭을 해도 원래 상태로 세팅을 보존하지
않아 다른 프로그램으로 잠깐 가서 한/영 모드를 변환해 놓고, 한 줄
입력한 다음 다시 이전에 입력하던 원고를 치려면 한참을 영문 상태로
잘못 입력하는 경우도 있다.

특히 한글화가 안된 영문 프로그램을 한글 윈도우에서 사용하면 모두
이런 식으로 동작하는 예가 많다.

        이것은 원래 윈도우즈 API에 포함되지 않은 기능을 IME라는
하나의 서브 루틴이 아닌 별개의 윈도우즈 프로그램을 모든 윈도우즈
응용 프로그램이 공유함으로써 빚어지는 문제이다.

만약 IME 관련 기능이 각 프로그램 인스턴스에서 불러 쓸 수 있는 API
수준에서 제공된다면, 이런 문제는 말끔히 해결될 것이다. 다행히도
최근 국마이크로소프트에서 주최한 윈도우즈 95 발표회에서 윈도우즈
95에서는 IME가 더 이상 독자적인 프로그램이 아닌 API의 일부로서
작동할 것이라고 밝혔다.

이로써 윈도우즈에 한글이 세들어 사는 형태에서 당당한 일부분으로
작동하는 형태로 바뀔 것을 기대해 본다.  기회가 닿는다면 한글
윈도우즈 95의 입력기 쪽도 한 번 다뤄보고 싶다.


못다한 몇 마디
이외에도 입력 시스템의 구조도나 한글 입력의 흐름도, IME를 윈도우에
설치/제거하거나 IME에 관련된 정보를 액세스할 수 있는 IMP 등을
설명하고 싶지만,  한글 윈도우 3.1 SDK를 참조할 것을 권하는 게
도리일 것이다(자세히 설명되어 있으니까).

        한메한글에 대해 언급하자면 대부분의 IME 인터페이스를 한글
윈도우 3.1과 동일하게 구현했지만, IME_HANJAMODE 서브 펑션이
구현되지 않았다는 점이 아쉽다.

그리고 이보다 우수한 한자 사전 기능이 별도로 구현되었으나, 이
API를 프로그래머가 구현할 수 있도록 공개하지 않은 점 또한 아쉽기
짝이 없다. 초기에 한메한글이 발표되었을 때는 관련 API들을 사용할
수 있도록 SDK도 발표한다고 떠들썩했던 기억이 아직도 머리 속에 남아
있는데, 지금까지 감감무소식이다.

이런 작지 않은 사실이 모여 한메한글이 한글 윈도우과의 시장
쟁탈전에서 밀리게 된 것인지도 모른다.
        
예제 프로그램
예제 프로그램은 단순히 입력 연습을 할 수 있는 정도의 의미밖에
없지만, 여기에 줄 단위 혹은 문단 단위의 자료 구조를 갖추고 파일
입출력만 구현하면 금방 에디터를 만들 수 있다.

이달의 디스켓(#    )

파일 이름               내용
LEV3.C                  예제의 소스
LEV3.DEF                정의 파일
LEV3.EXE                실행 파일 
LEV3.H                  헤더 파일
LEV3.MAP                맵핑 파일
LEV3.DBJ                오브젝트 파일
LEV3.RC                 리소스 파일
LEV3.RES                레지스터 파일

 

<리스트 1> LEV3.C
001:/*************************************************************
***   
002:  lev3.c
003:  한글 IME 3수준 예제
004:
005:WinMain() - calls initialization function, processes message
loop
006:InitApplication() - initializes window data and registers
window
007:InitInstance() - Creates main window and set input level as 3
008:MainWndProc() - processes messages
009:About() - processes messages for "About" dialog box
010:
011:**************************************************************
**/
012:#include <windows.h>
013:#include <stdlib.h>
014:#include <ime.h>
015:#include "lev3.h"
016:
017://
018:// Double-byte windows patch currently handles AnsiNext &
019:// Prev incorrectly...
020:// (Requested compatiblity per Hanme Hangeul patch --
021://
022:#define IsDBCSLeadByte(c)     ((c) & 0x80)
023:#define AnsiNext(p)     ( IsDBCSLeadByte(*p) ? ((p)+2):
((p)+1))
024:#define AnsiPrev(p,s)   (MyAnsiPrev((p),(s)))
025://
026:// should we undefine the switch here and
027:// just leave the macro in effect ?
028://
029:// to emulate AnsiPrev, AnsiNext Function
030:// provided in DBCS windows
031:// First done in June 1, 1994 by espark
032:
033:// MyAnsiPrev()
034://
035:// You Have to be very cautious to use this function
036:// Cause there's no error checking in this function
037://
038:LPSTR FAR PASCAL MyAnsiPrev(LPCSTR lpchStart,
039:      LPCSTR lpchCurrentChar)
040:{
041:char far *lpPrev;
042:char far *lpStr;
043:
044:lpPrev = lpStr = lpchStart;
045:
046:while (lpStr < lpchCurrentChar) {
047:   lpPrev = lpStr;
048:   lpStr = AnsiNext(lpStr);
049:   }
050:
051:return ((LPSTR) lpPrev);
052:}
053:
054:// 함수명 : InitApplication()
055:// 목적 : 윈도우를 초기화하고 등록한다.
056:BOOL InitApplication(hInstance)
057:HANDLE hInstance;
058:{
059:    WNDCLASS  wc;
060:
061:    wc.style  = CS_HREDRAW | CS_VREDRAW;
062:    wc.lpfnWndProc = MainWndProc;
063:    wc.cbClsExtra = 0;
064:    wc.cbWndExtra = 0;
065:    wc.hInstance = hInstance;
066:    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
067:    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
068:    wc.hbrBackground = (
069:       HBRUSH)GetStockObject(WHITE_BRUSH);
070:    wc.lpszMenuName = "Lev3Menu";
071:    wc.lpszClassName = szAppName;
072:
073:    return (RegisterClass(&wc));
074:}
075:
076:// 함수명 :  InitInstance(HANDLE, int)
077:// 목적: 메인 윈도우를 생성하고 3수준으로 입력 레벨을 맞춘다.
078:
079:BOOL InitInstance(hInstance, nCmdShow)
080:HANDLE          hInstance;
081:int             nCmdShow;
082:{
083:HWND    hWnd;
084:HANDLE  hKs;
085:LPIMESTRUCT  lpKs;
086:
087:    hInst = hInstance;
088:
089:    hWnd= CreateWindow(szAppName,
090:    "IME Lev 3 Sample",
091:     WS_OVERLAPPEDWINDOW,
092:     CW_USEDEFAULT,
093:     CW_USEDEFAULT,
094:     CW_USEDEFAULT,
095:     CW_USEDEFAULT,
096:     NULL, NULL,
097:     hInstance, NULL);
098:
099:    if (!hWnd)
100:    return (FALSE);
101:
102:    ShowWindow(hWnd, nCmdShow);
103:    UpdateWindow(hWnd);
104:
105:    // 3 수준으로 IME 수준을 세팅한다.
106:    hKs = GlobalAlloc (
107:        GMEM_MOVEABLE|GMEM_DDESHARE,
108:     (LONG)sizeof(IMESTRUCT));
109:    lpKs = (LPIMESTRUCT)GlobalLock(hKs);
110:    lpKs->fnc = IME_SETLEVEL;
111:    lpKs->wParam = 3;
112:    GlobalUnlock(hKs);
113:    SendIMEMessage (hWnd, MAKELONG(hKs,0));
114:    GlobalFree(hKs);
115:
116:    return (TRUE);
117:
118:}
119:
120:// TypeRightDBCS(PSTR,PSTR)
121:
122:// 현재 위치가 DBCS 영역 내인지의 여부를 체크한다.
123:
124:BOOL TypeRightDBCS( pBase, pStr )
125:PSTR pBase, pStr;
126:{
127:    PSTR pCurrent = pStr;
128:
129:    if (pBase == pCurrent)
130:     return TRUE;
131:
132:    do {
133:     pCurrent--;
134:     if (!IsDBCSLeadByte(*pCurrent)) {
135:         pCurrent++;
136:         break;
137:     }
138:    } while(pCurrent != pBase);
139:
140:    // If we proceeded by odd number, then we are
141:    // in middle of DBCS.
142:    return( ((pStr - pCurrent) & 1) ? FALSE : TRUE );
143:}
144:
145:// 함수명 : WinMain(HANDLE, HANDLE, LPSTR, int)
146:// 목적 : 초기화 함수들을 부르고, 메시지 루프를 처리한다.
147:
148:int PASCAL WinMain (HANDLE hInstance,
149:    HANDLE hPrevInstance, LPSTR lpsxCmdLine,
150:    int nCmdShow)
151:{
152:MSG     msg;
153:
154:    if (!hPrevInstance)
155:    if (!InitApplication(hInstance))
156:        return (FALSE);
157:
158:    if (!InitInstance(hInstance, nCmdShow))
159:    return (FALSE);
160:
161:    while( GetMessage(&msg, NULL, 0, 0) ) {
162:       TranslateMessage(&msg);
163:       DispatchMessage(&msg);
164:    }
165:
166:    return msg.wParam;
167:}
168:
169:// 함수명: MainWndProc(HWND, unsigned, WORD, LONG)
170:
171:// 목적: 메시지를 처리한다.   
172:
173://처리할 메시지:
174://   WM_COMMAND - 프로그램 메뉴(About 대화상자)
175://   WM_CREATE  - 윈도우 생상
176://   WM_KEYDOWN - 키보드 입력
177://   WM_CHAR    - DBCS를 포함한 ASCII 코드 입력
178://   WM_INTERIM - 입력 도중 글자
179://   WM_PAINT   - 윈도우 갱신
180://   WM_DESTROY - 윈도우 종료
181:
182:// 이 부분에서 특히 WM_INTERIM을 포함한 다양한 키보드 입력
183:// 메시지를 처리하는 것을 보인다. 화살표키와 <Del>키를
184:// 사용할 때 DBCS 문자 처리에 대해 주의해 볼 것
185:
186:long FAR PASCAL MainWndProc (HWND hWnd, WORD message,
187:     WORD wParam, LONG lParam)
188:{
189:static  char *pBuffer=NULL;
190:static  int cxChar, cyChar, cxClient, cyClient,
191:        cxBuffer, cyBuffer,
192:        xCaret, yCaret;
193:HDC     hdc;
194:int     x,y,i;
195:PAINTSTRUCT     ps;
196:TEXTMETRIC      tm;
197:FARPROC lpProcAbout;
198:
199:    switch(message) {
200:    case WM_COMMAND:
201:        if (wParam == IDM_ABOUT) {
202:
203:            // ABOUT 대화상자를 부른다.
204:            lpProcAbout = MakeProcInstance(
205:                              About, hInst);
206:
207:            DialogBox(hInst,
208:                "AboutBox",
209:                hWnd,
210:                lpProcAbout);
211:
212:            FreeProcInstance(lpProcAbout);
213:            return 0;
214:        }
215:        break;
216:
217:    case WM_CREATE:
218:        hdc = GetDC(hWnd);
219:
220:        SelectObject(hdc, GetStockObject(
221:                     SYSTEM_FIXED_FONT));
222:        GetTextMetrics(hdc, &tm);
223:        cxChar = tm.tmAveCharWidth;
224:        cyChar=tm.tmHeight;
225:        ReleaseDC(hWnd, hdc);
226:        return 0;
227:
228:        // When changing size of Window,
229:        // reallocate Edit Buffer
230:    case WM_SIZE:
231:        cxClient = LOWORD(lParam);
232:        cyClient = HIWORD(lParam);
233:
234:            // x size of Buffer
235:        cxBuffer = max(1, cxClient / cxChar);     
236:            // y size of Buffer
237:        cyBuffer = max(1, cyClient / cyChar);      
238:
239:        if (pBuffer != NULL)
240:            free(pBuffer);
241:
242:        // Can not allocate local memory above 64 Kb
243:        if((LONG)(cxBuffer * cyBuffer) > 65535L ||
244:           (pBuffer = malloc(cxBuffer *
245:           cyBuffer)) == NULL)
246:             MessageBox(hWnd,
247:                       "Window too large. cannot
248:                       allocate enough memory.",
249:                       "type",
250:                      MB_ICONEXCLAMATION | MB_OK);
251:        else
252:            for (y = 0; y < cyBuffer; y++)
253:                for (x = 0; x < cxBuffer; x++)
254:                       // Initialize Buffer
255:                    BUFFER(x, y) = ' ';  
256:
257:        xCaret = 0;
258:        yCaret = 0;
259:
260:        if (hWnd == GetFocus())
261:             SetCaretPos(xCaret * cxChar,
262:                         yCaret * cyChar);
263:
264:        return 0;
265:
266:    case WM_SETFOCUS:
267:        CreateCaret (hWnd, NULL, 2, cyChar);
268:        SetCaretPos(xCaret * cxChar,
269:                    yCaret * cyChar);
270:        ShowCaret(hWnd);
271:        return 0;
272:
273:    case WM_KILLFOCUS:
274:        HideCaret(hWnd);
275:            // Caret is system resource.
276:        DestroyCaret();
277:        return 0;
278:
279:    case WM_KEYDOWN:
280:        switch(wParam) {
281:            case VK_HOME:
282:                xCaret = 0;
283:                break;
284:            case VK_END:
285:                xCaret = cxBuffer - 1;
286:                break;
287:            case VK_PRIOR:
288:                yCaret = 0;
289:                break;
290:            case VK_NEXT:
291:                 yCaret = 0;
292:                 break;
293:            case VK_LEFT:
294:                if (IsDBCSLeadByte(BUFFER(
295:                    xCaret-1, yCaret)))
296:                    xCaret = max(xCaret - 2, 0);
297:                else
298:                    xCaret = max(xCaret - 1, 0);
299:                break;
300:            case VK_RIGHT:
301:                if (IsDBCSLeadByte(BUFFER(
302:                    xCaret, yCaret)))
303:                   xCaret = min(xCaret + 2,
304:                        cxBuffer - 1);
305:                else
306:                   xCaret = min(xCaret + 1,
307:                              cxBuffer - 1);
308:                break;
309:            case VK_UP:
310:                yCaret = max(yCaret - 1, 0);
311:                if(!TypeRightDBCS(pBuffer,
312:                   &BUFFER(xCaret, yCaret) ) )
313:                   xCaret -= 1;
314:                break;
315:            case VK_DOWN:
316:                yCaret = min(yCaret + 1,
317:                             cyBuffer - 1);
318:                if(!TypeRightDBCS( pBuffer,
319:                    &BUFFER(xCaret, yCaret) ) )
320:                       xCaret -= 1;
321:                break;
322:
323:            // Chance to convert Hangeul to Hanja
324:            // with pre-edited character
325:            case VK_HANJA:
326:            // If we have Interim currently,
327:            // it will be done by System
328:                if( IsInterim )
329:                    break;
330:
331:                {
332:                HANDLE  hKs;
333:                LPIMESTRUCT  lpKs;
334:                LPSTR lp;
335:
336:        hKs = GlobalAlloc(
337:                     GMEM_MOVEABLE|GMEM_DDESHARE,
338:                     (LONG)sizeof(IMESTRUCT));
339:                lpKs = (LPIMESTRUCT)GlobalLock(hKs);
340:                lpKs->fnc = IME_HANJAMODE;
341:                lpKs->wParam = IME_REQUEST_CONVERT;
342:                lpKs->dchSource = (WORD)( &(
343:                                lpKs->lParam1) );
344:                lp = lpSource( lpKs );
345:                *lp++ = BUFFER(xCaret, yCaret);
346:                *lp++ = BUFFER(xCaret+1, yCaret);
347:                *lp++ = '\0';
348:                GlobalUnlock(hKs);
349:                // If you need to know
350:                // whether System enter Hanja convert
351:                // mode after this call,
352:                // please refer the Return value.
353:                if( SendIMEMessage( hWnd,
354:                        MAKELONG(hKs,0) ) )
355:                    ;
356:
357:                GlobalFree(hKs);
358:                }
359:                break;
360:
361:            case VK_DELETE:
362:                if( IsDBCSLeadByte(BUFFER(
363:                        xCaret, yCaret)) )
364:                      i = 2;
365:                else
366:                      i = 1;
367:                for (x = xCaret; x <
368:                       cxBuffer - i; x++)
369:                        BUFFER(x, yCaret) =
370:                           BUFFER(x+i, yCaret);
371:                for( ; i == 0; i-- )
372:                    BUFFER (cxBuffer - i,
373:                           yCaret) = ' ';
374:
375:                HideCaret(hWnd);
376:                hdc = GetDC(hWnd);
377:
378:                SelectObject(hdc,
379:                        GetStockObject(
380:                        SYSTEM_FIXED_FONT));
381:
382:                TextOut (hdc, xCaret * cxChar,
383:                         yCaret * cyChar,
384:                         &BUFFER(xCaret, yCaret),
385:                         cxBuffer - xCaret);
386:                ShowCaret(hWnd);
387:                ReleaseDC(hWnd, hdc);
388:                break;
389:        }   // end of switch(wParam)
390:
391:        SetCaretPos (xCaret * cxChar,
392:                     yCaret * cyChar);
393:        return 0;
394:
395:    case WM_INTERIM:
396:    case WM_CHAR:
397:        for ( i=0; i < LOWORD(lParam); i++){
398:            switch (wParam) {
399:                case '\b':   // backspace
400:                    if (xCaret > 0){
401:                        if (IsDBCSLeadByte(
402:                                BUFFER(xCaret-1,
403:                                yCaret))) {
404:                            xCaret -= 2;
405:                            SendMessage(hWnd,
406:                                WM_KEYDOWN,
407:                                VK_DELETE, 2L);
408:                        } else {
409:                            xCaret -= 1;
410:                            SendMessage(hWnd,
411:                                WM_KEYDOWN,
412:                                VK_DELETE, 1L);
413:                        }
414:                    }
415:                    break;
416:                case '\t':      // tab
417:                    do {
418:                        SendMessage(hWnd,
419:                                WM_CHAR, ' ', 1L);
420:                    } while (xCaret % 8 != 0);
421:                    break;
422:                case '\n':      // linefeed
423:                    if (++yCaret == cyBuffer)
424:                        yCaret = 0;
425:                    break;
426:                case '\r': // carriage return
427:                    xCaret = 0;
428:
429:                    if (++yCaret == cyBuffer)
430:                        yCaret = 0;
431:                    break;
432:          // escape -> Clear all window content
433:                case '\x1B' :
434:                    for (y=0; y<cyBuffer; y++)
435:                        for (x=0; x<cxBuffer; x++)
436:                            BUFFER(x, y) = ' ';
437:                    xCaret = 0;
438:                    yCaret = 0;
439:                    InvalidateRect(hWnd,
440:                             NULL, FALSE);
441:                    break;
442:
443:                default: // character codes
444:                         // If it breaks DBCS,
445:                         // delete second byte also.
446:                    if(IsDBCSLeadByte(
447:                        BUFFER(xCaret, yCaret) ) )
448:                        BUFFER(xCaret+1,
449:                        yCaret) = ' ';
450:                    BUFFER(xCaret, yCaret) =
451:                           LOBYTE(wParam);
452:                    HideCaret(hWnd);
453:                    hdc = GetDC(hWnd);
454:                    SelectObject (hdc,
455:                       GetStockObject(
456:                           SYSTEM_FIXED_FONT));
457:
458:                    if( message == WM_INTERIM )
459:                        IsInterim ++;
460:                    else
461:                        IsInterim = 0;
462:                    if (IsDBCSLeadByte(
463:                            LOBYTE(wParam))) {
464:                        // We will wait
465:                        // until receive DBCS second byte.
466:                        while (1) {
467:                           GetMessage( (LPMSG)&
468:                             vmsgLast, NULL, 0, 0 );
469:                           if ((vmsgLast.message ==
470:                              WM_CHAR) ||
471:                              (vmsgLast.message ==
472:                              (WM_INTERIM)) {
473:                               if ((vmsgLast.wParam >=
474:                                   0xA1) ||
475:                                   (vmsgLast.wParam <=
476:                                    0xFE) )
477:                                      break;
478:                           } else
479:                              TranslateMessage(
480:                                      &vmsgLast );
481:                        }
482:                        BUFFER(xCaret+1, yCaret) =
483:                              LOBYTE(vmsgLast.wParam);
484:                        TextOut (hdc,
485:                           xCaret * cxChar,
486:                           yCaret * cyChar,&
487:                           BUFFER(xCaret, yCaret), 2);
488:                        // If current is Interim,
489:                        // don't proceed Caret.
490:                        if( !IsInterim )
491:                            xCaret += 2;
492:                    } else {
493:                        TextOut (hdc,
494:                           xCaret * cxChar,
495:                           yCaret * cyChar,
496:                           &BUFFER(xCaret,
497:                           yCaret), 2);
498:                        xCaret++;
499:                    }
500:                    ShowCaret(hWnd);
501:                    ReleaseDC(hWnd, hdc);
502:
503:                    if (xCaret == cxBuffer) {
504:                        xCaret = 0;
505:                        if (!IsInterim)
506:                                yCaret++;
507:                        if (yCaret == cyBuffer)
508:                                yCaret = 0;
509:                    }
510:
511:            // If previous was Interim and
512:            // next key will be following,
513:            // we will process following key
514:            // input at current position.
515:            // Be carefull ! this is
516:            // happen when we replace
517:            // old WM_INTERIM with DBCS WM_CHAR.
518:            if( WasInterim ){
519:                        MSG msg;
520:                        int wp;
521:
522:                        if (PeekMessage ((LPMSG)&msg,
523:                            hWnd, WM_KEYDOWN,
524:                            WM_KEYUP, PM_NOYIELD |
525:                            PM_NOREMOVE) ) {
526:                            if( msg.message==
527:                              WM_KEYDOWN &&
528:                              ( (wp=msg.wParam)==
529:                              VK_LEFT ||
530:                              wp==VK_UP ||
531:                              wp==VK_RIGHT ||
532:                              wp==VK_DOWN ||
533:                              wp==VK_DELETE) )
534:                                xCaret -= 2;
535:                        }
536:                    }
537:                        // Save old status
538:                    WasInterim = IsInterim; 
539:                    break;
540:            }       // end of switch (wParam)
541:        }   // end of for ( i=0; i <
542:                          LOWORD(lParam); i++){
543:        SetCaretPos (xCaret * cxChar,
544:                     yCaret * cyChar);
545:        return 0;
546:
547:    case WM_PAINT:
548:        hdc = BeginPaint(hWnd, &ps);
549:        SelectObject(hdc,
550:                GetStockObject(SYSTEM_FIXED_FONT));
551:
552:        for (y=0; y<cyBuffer; y++)
553:            TextOut(hdc, 0, y*cyChar,
554:                   (LPSTR)&BUFFER(0,y),
555:                   cxBuffer);
556:        EndPaint(hWnd, &ps);
557:        return 0;
558:
559:    case WM_DESTROY:
560:        PostQuitMessage(0);
561:        return 0;
562:
563:    }   // End of switch(message)
564:
565:    return DefWindowProc (hWnd, message,
566:                          wParam, lParam);
567:}
568:
569:BOOL FAR PASCAL About(hDlg, message, wParam, lParam)
570:HWND hDlg;
571:unsigned message;
572:WORD wParam;
573:LONG lParam;
574:{
575:    switch (message) {
576:    case WM_INITDIALOG:
577:        return (TRUE);
578:
579:    case WM_COMMAND:
580:        if (wParam == IDOK) {
581:            EndDialog(hDlg, TRUE);
582:            return (TRUE);
583:        }
584:        break;
585:    }
586:    return (FALSE);
587:}

 

   출처 :마이크로소프트웨어 

[출처] IMM과 IME|작성자 블르



AND