출처 : 「윈도우즈 MFC 프로그래밍 - 원리...」본문 中 ### - 71페이지



매로크의 # ##

#define macro_define(str)   (#str)

가령 매크로를 위와 같이 정의하고, char *p = macro_define(string); 이라고 코드를 구현한다면 p는
"string"이라는 문자열을가리키게 된다, 즉 매크로정의의 인자를 문자열화 한다.

다음의 매크로 정의에 사용되는 ##은 매크로 인자를 연결하여 새로운 토큰을 구성한다.

#define macro_define(t1,t2)   (t1##t2)

위와 같이 매크로를 정의하고 int macro_define(na,na2); 라고 코드를 기술하면 int na1na2;로 구현한 것과 같다.
즉, ## 는 전처리 단계에서 두 개의 매크로 인자를 연결하여 새로운 토큰을 생성한다. 이렇게 매크로 정의에서
사용되는 두개의 #과 ##은 MFC가 자신이 사용하는 여러 매크로( 메세지 매크로, CRuntimeClass 매크로)를
구현하기 위해 내부적으로 자주 사용 한다.

AND

출처 :http://bwangel.egloos.com/923770

Batch 파일

SET MSDEVOPENFILE=%1
call C:\Rnd\Ms\PlatSDK_2K3SP1\setenv /X64 /RETAIL
call "%MSDevDir%\Bin\msdev" %MSDEVOPENFILE% /useenv
VC++ 6.0 환경
01. x64 Configuration 환경을 추가한다.
02. Project - Sttings - ProjectSettings 창을 띄운다.
03. General Tab - Output directories - Intermediate files, Output files 를 수정한다.
04. Link Tab - General - Output file name 을 수정한다.
05. Link Tab - General - Object/library modules 에 BufferOverflowU.lib 추가한다.
06. Link Tab - Debug - Debug Info 에 Separate types 항목의 체크를 없애준다.
07. Link Tab - Project Options 에서 /machine:I386 을 삭제한다.
08. Link Tab - Project Options 에서 /machine:AMD64 로 추가한다.
09. Link Tab - Output에서 Entry-Point Symbol 에 wWinMainCRTStartup를 추가한다.

10. C/C++ Tab - General - Preprocessor definitions 에 _MBCS 삭제한다.
11. C/C++ Tab - General - Preprocessor definitions 에    
      WIN32,_AMD64_=1,WIN64,_WIN64,_UNICODE,UNICODE 를 추가한다.
12. C/C++ Tab - General - Debug info 를 Program Database 로 변경한다.
13. C/C++ Tab - C++ Language - /GX(Enable exception handling) 체크를 삭제한다.
14. C/C++ Tab - C++ Language - Project Options 에서 /GZ(Calling Convention)
     삭제한다.
15. C/C++ Tab - C++ Language - Project Options 에서 /EHsc 추가한다.
16. C/C++ Tab - C++ Language - Project Options 에서 /Wp64 추가한다.
AND

출처: http://blog.naver.com/hypalgesia3?Redirect=Log&logNo=70074511013

CoInitialize : http://msdn.microsoft.com/en-us/library/ms678543(VS.85).aspx

CoInitializeEx :  http://msdn.microsoft.com/en-us/library/ms695279(VS.85).aspx

COINIT Enumeration : http://msdn.microsoft.com/en-us/library/ms678505(VS.85).aspx


Peek Message : http://msdn.microsoft.com/en-us/library/ms644943(VS.85).aspx 

Send Message : http://msdn.microsoft.com/en-us/library/ms644950(VS.85).aspx


STA : single threaded apartment

MTA : multi  threaded apartment


STA의 경우 :

thread A가 objects들을 생성한다.

다른 thread들이 이 object들의 method를 호출하면, 이 모든 것들은 thread A 안에서, serialization이 되어서 동작을 한다.

즉, 외부의 모든 call들은 message queue로 들어오고, 따라서 자연스럽게 serialization이 된다.

따라서 object 코드를 작성할 때, 단일 쓰레드에서 동작한다고 가정하고 코드를 작성하면 된다.


MTA의 경우 :

CoInitializeEx에 COINIT_MULTITHREADED값을 주어서, COM library를 initialization을 하면 된다.

다른 thread들이 이 object들의 method들을 호출하면, 이 method들은 다른 thread들 내부에서 실행이 되는것 같다.

특정 method, 또는 특정 object가 동시에 여러 thread들로 부터 호출이 될 수 있으므로,

object의 code 내에서 synchronization (critical section, semaphore, mutex)을 구현해야 한다.

특정 thread data를 object 내부에 저장을 할 수 없다.(왜냐하면, object는 자신을 호출한 thread의 수명을 제어하지 않기 때문이다.)

(특정 thread의 정보를 저장하고 싶다면, Thread Local Storage를 이용하라)


[출처] STA와 MTA의 개념|작성자 hypalgesia3

AND

출처: http://msdn.microsoft.com/en-us/goglobal/bb964664.aspx


see Locale ID and Language Collections for Windows XP and Windows Server 2003.

Language - Country/Region LCID Hex LCID Dec
Afrikaans - South Africa 0436 1078
Albanian - Albania 041c 1052
Amharic - Ethiopia 045e 1118
Arabic - Saudi Arabia 0401 1025
Arabic - Algeria 1401 5121
Arabic - Bahrain 3c01 15361
Arabic - Egypt 0c01 3073
Arabic - Iraq 0801 2049
Arabic - Jordan 2c01 11265
Arabic - Kuwait 3401 13313
Arabic - Lebanon 3001 12289
Arabic - Libya 1001 4097
Arabic - Morocco 1801 6145
Arabic - Oman 2001 8193
Arabic - Qatar 4001 16385
Arabic - Syria 2801 10241
Arabic - Tunisia 1c01 7169
Arabic - U.A.E. 3801 14337
Arabic - Yemen 2401 9217
Armenian - Armenia 042b 1067
Assamese 044d 1101
Azeri (Cyrillic) 082c 2092
Azeri (Latin) 042c 1068
Basque 042d 1069
Belarusian 0423 1059
Bengali (India) 0445 1093
Bengali (Bangladesh) 0845 2117
Bosnian (Bosnia/Herzegovina) 141A 5146
Bulgarian 0402 1026
Burmese 0455 1109
Catalan 0403 1027
Cherokee - United States 045c 1116
Chinese - People's Republic of China 0804 2052
Chinese - Singapore 1004 4100
Chinese - Taiwan 0404 1028
Chinese - Hong Kong SAR 0c04 3076
Chinese - Macao SAR 1404 5124
Croatian 041a 1050
Croatian (Bosnia/Herzegovina) 101a 4122
Czech 0405 1029
Danish 0406 1030
Divehi 0465 1125
Dutch - Netherlands 0413 1043
Dutch - Belgium 0813 2067
Edo 0466 1126
English - United States 0409 1033
English - United Kingdom 0809 2057
English - Australia 0c09 3081
English - Belize 2809 10249
English - Canada 1009 4105
English - Caribbean 2409 9225
English - Hong Kong SAR 3c09 15369
English - India 4009 16393
English - Indonesia 3809 14345
English - Ireland 1809 6153
English - Jamaica 2009 8201
English - Malaysia 4409 17417
English - New Zealand 1409 5129
English - Philippines 3409 13321
English - Singapore 4809 18441
English - South Africa 1c09 7177
English - Trinidad 2c09 11273
English - Zimbabwe 3009 12297
Estonian 0425 1061
Faroese 0438 1080
Farsi 0429 1065
Filipino 0464 1124
Finnish 040b 1035
French - France 040c 1036
French - Belgium 080c 2060
French - Cameroon 2c0c 11276
French - Canada 0c0c 3084
French - Democratic Rep. of Congo 240c 9228
French - Cote d'Ivoire 300c 12300
French - Haiti 3c0c 15372
French - Luxembourg 140c 5132
French - Mali 340c 13324
French - Monaco 180c 6156
French - Morocco 380c 14348
French - North Africa e40c 58380
French - Reunion 200c 8204
French - Senegal 280c 10252
French - Switzerland 100c 4108
French - West Indies 1c0c 7180
Frisian - Netherlands 0462 1122
Fulfulde - Nigeria 0467 1127
FYRO Macedonian 042f 1071
Gaelic (Ireland) 083c 2108
Gaelic (Scotland) 043c 1084
Galician 0456 1110
Georgian 0437 1079
German - Germany 0407 1031
German - Austria 0c07 3079
German - Liechtenstein 1407 5127
German - Luxembourg 1007 4103
German - Switzerland 0807 2055
Greek 0408 1032
Guarani - Paraguay 0474 1140
Gujarati 0447 1095
Hausa - Nigeria 0468 1128
Hawaiian - United States 0475 1141
Hebrew 040d 1037
Hindi 0439 1081
Hungarian 040e 1038
Ibibio - Nigeria 0469 1129
Icelandic 040f 1039
Igbo - Nigeria 0470 1136
Indonesian 0421 1057
Inuktitut 045d 1117
Italian - Italy 0410 1040
Italian - Switzerland 0810 2064
Japanese 0411 1041
Kannada 044b 1099
Kanuri - Nigeria 0471 1137
Kashmiri 0860 2144
Kashmiri (Arabic) 0460 1120
Kazakh 043f 1087
Khmer 0453 1107
Konkani 0457 1111
Korean 0412 1042
Kyrgyz (Cyrillic) 0440 1088
Lao 0454 1108
Latin 0476 1142
Latvian 0426 1062
Lithuanian 0427 1063
Malay - Malaysia 043e 1086
Malay - Brunei Darussalam 083e 2110
Malayalam 044c 1100
Maltese 043a 1082
Manipuri 0458 1112
Maori - New Zealand 0481 1153
Marathi 044e 1102
Mongolian (Cyrillic) 0450 1104
Mongolian (Mongolian) 0850 2128
Nepali 0461 1121
Nepali - India 0861 2145
Norwegian (Bokmål) 0414 1044
Norwegian (Nynorsk) 0814 2068
Oriya 0448 1096
Oromo 0472 1138
Papiamentu 0479 1145
Pashto 0463 1123
Polish 0415 1045
Portuguese - Brazil 0416 1046
Portuguese - Portugal 0816 2070
Punjabi 0446 1094
Punjabi (Pakistan) 0846 2118
Quecha - Bolivia 046B 1131
Quecha - Ecuador 086B 2155
Quecha - Peru 0C6B 3179
Rhaeto-Romanic 0417 1047
Romanian 0418 1048
Romanian - Moldava 0818 2072
Russian 0419 1049
Russian - Moldava 0819 2073
Sami (Lappish) 043b 1083
Sanskrit 044f 1103
Sepedi 046c 1132
Serbian (Cyrillic) 0c1a 3098
Serbian (Latin) 081a 2074
Sindhi - India 0459 1113
Sindhi - Pakistan 0859 2137
Sinhalese - Sri Lanka 045b 1115
Slovak 041b 1051
Slovenian 0424 1060
Somali 0477 1143
Sorbian 042e 1070
Spanish - Spain (Modern Sort) 0c0a 3082
Spanish - Spain (Traditional Sort) 040a 1034
Spanish - Argentina 2c0a 11274
Spanish - Bolivia 400a 16394
Spanish - Chile 340a 13322
Spanish - Colombia 240a 9226
Spanish - Costa Rica 140a 5130
Spanish - Dominican Republic 1c0a 7178
Spanish - Ecuador 300a 12298
Spanish - El Salvador 440a 17418
Spanish - Guatemala 100a 4106
Spanish - Honduras 480a 18442
Spanish - Latin America 580a 58378
Spanish - Mexico 080a 2058
Spanish - Nicaragua 4c0a 19466
Spanish - Panama 180a 6154
Spanish - Paraguay 3c0a 15370
Spanish - Peru 280a 10250
Spanish - Puerto Rico 500a 20490
Spanish - United States 540a 21514
Spanish - Uruguay 380a 14346
Spanish - Venezuela 200a 8202
Sutu 0430 1072
Swahili 0441 1089
Swedish 041d 1053
Swedish - Finland 081d 2077
Syriac 045a 1114
Tajik 0428 1064
Tamazight (Arabic) 045f 1119
Tamazight (Latin) 085f 2143
Tamil 0449 1097
Tatar 0444 1092
Telugu 044a 1098
Thai 041e 1054
Tibetan - Bhutan 0851 2129
Tibetan - People's Republic of China 0451 1105
Tigrigna - Eritrea 0873 2163
Tigrigna - Ethiopia 0473 1139
Tsonga 0431 1073
Tswana 0432 1074
Turkish 041f 1055
Turkmen 0442 1090
Uighur - China 0480 1152
Ukrainian 0422 1058
Urdu 0420 1056
Urdu - India 0820 2080
Uzbek (Cyrillic) 0843 2115
Uzbek (Latin) 0443 1091
Venda 0433 1075
Vietnamese 042a 1066
Welsh 0452 1106
Xhosa 0434 1076
Yi 0478 1144
Yiddish 043d 1085
Yoruba 046a 1130
Zulu 0435 1077
HID (Human Interface Device) 04ff 1279


AND

출처: http://blog.naver.com/blueriderx?Redirect=Log&logNo=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:}


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

AND

출처: http://blog.naver.com/wondo21c?Redirect=Log&logNo=30043174174

앞으로 문자열을 코딩할 때, char 대신에 TCHAR을 사용해야겠다.


확장성과 이식성을 위해서 앞으로 그렇게 습관을 들여야겠다.


Visual Studio 2005 프로젝트 속성 ->구성속성을 보면


문자 집합으로 2가지를 사용할 수 있다.

1. 멀티바이트 문자 집합

2. 유니코드 문자 집합




아스키코드는 모든 문자 하나가 1byte를 차지한다.


하지만, 아스키 문자 코드 만으로는 한글이나 일어 등의 다른 문자를 표시할 수 없다.


그래서 아스키 문자 코드에다가 다른 문자(2byte)들을 포함한 문자 집합이 멀티바이트 문자 집합이다.


정확히는 모르겠지만, 한 문자가 2byte를 넘는 문자도 존재할 것이다.


그래서 용어 자체가 멀티바이트 문자 집합이 아닐까 생각한다.


그런데 멀티바이트 문자 집합은 특정 문자 집합마다의 코드페이지가 존재한다.


예를 들어, 같은 코드 번호 일지라도 한글 코드 페이지로 해석하면 한글이 나오지만,


일어 코드 페이지로 해석하면 일어가 나온다.


그래서 이상하게 깨지는 문자 등을 우리는 목격할 수 있다.




이것의 방안으로 탄생한 것이 유니코드!


유니코드는 아스키 문자 코드 뿐만 아니라, 한글, 일어 등등 어떠한 문자들을 총 망라하여


각 한 문자에 2byte씩으로 할당하여 만든 문자 집합이다.


그리하여 각각의 특정 문자는 고유의 유니코드값을 가진다.




우리의 문제는 코딩 시에, 어떻게 해야 하는가!


너무나도 초보인 나는 아직 ANSI 표준 문자열을 쓰는데도 익숙치 않다.


하지만 메모리를 다루는 프로그래머로써 1bit의 메모리라두 잘못된 경우에는


프로그램 전체를 뻑(Crash)하는 우려를 범하곤 한다.


나같은 게임 프로그래머로서는 게임 실컷 만들어 놓고, 유저들에게 버그 많다고 욕들어 먹는다. ^^;;




게임뿐만 아니라 어떤 프로그램도 요새는 세계화가 아닌가!


그래서 한글과 영어만으로는 완벽하다고 안심해서는 안된다.


대만어, 프랑스어 등등 어떠한 문자 앞에서도 굴하지 않아야 된다.


그렇다고 우리가 각각의 나라별로 게임을 개별적으로 만들어 줄 수는 없지 않은가!


그래서 소스 상에서 유연성이 필요한 것 같다.




일단, 나는 자주 쓰는 몇 개의 습관부터 고치고자 노력해야 겠다.


 char  TCHAR
 strcat_s()  _tcscat_s()
 strcpy_s(), strncpy_s()  _tcscpy_s() , _tcsncpy_s()
 strlen()  _tcslen
 sprintf_s()  _stprintf_s


 


그리고 문자열을 바로 쓸 때,


"" 대신에 TEXT("")을 쓰자.


TEXT 매크로는 유니코드의 설정에 따라 상수의 타입을 달리 한다.


예를 들어 유니코드로 컴파일 할 경우, TEST("a")를 16bit(2byte) 문자로 인식하고,

아닐 경우, 8bit(1byte) ANSI 문자로 인식한다.






밑의 표는 인터넷에서 구한 것.



 



 
 
 
 

AND

출처: http://www.codeguru.com/cpp/misc/misc/multi-lingualsupport/article.php/c10451

The Basics of UTF-8
Rating:

Marius Bancila (view profile)
September 12, 2005

Short History

Probably the best-known character set is the 7-bit char set known as ASCII. It stands for American Standards Committee for Information Interchange and was designed for communication in US English. It contains 128 different characters, including lowercase and uppercase letters, digits 0-9, various punctuation marks, non-printing characters (new line, tab, and so forth), and control characters (null termination, backspace, bell, delete, and so on).


(continued)

But, because it was designed to handle English, there were problems with European languages that need diacritics (special marks added to a letter to indicate a special pronunciation). As a result, ASCII was extended and several char codes with 255 codes were created. One of them, often called IBM character set, uses the characters with numeric value between 128–255 (having the most significant bit set to 1) for graphics and line drawing and some special European characters. Another 8-bit character set is ISO 8859-1 Latin 1 (also called simply ISO Latin-1). Characters with numeric value between 128–255 are used to encode characters specific to languages that are written in some approximation of Latin alphabet, hence the name.

European languages are not the only ones spoken and written around the planet; African and Asian languages were not supported by 8-bit character sets. The Chinese alphabet alone has more than 80,000 different characters (or pictograms). However, combining similar characters from Chinese, Japanese, and Vietnamese, so that some chars represent different words in different languages, they, along with languages from Europe, Africa, Middle East, and other regions can be encoded in just 2 bytes. And so, UNICODE was created. It extends ISO Latin-1 by adding an extra high-order byte. When this byte is 0, the character in the low-order byte is an ISO Latin-1 character. UNICODE offers support for alphabets from Europe, Africa, Middle East, Asia (including the unified Han set of East Asian ideograms and the complete ideograms for Korean Hangul). On the other hand, UNICODE does not provide support for Braille, Cherokee, Ethiopic, Khmer, Mongolian, Hmong, Tai Lu, Tai Mau, and the like. (Mongolian is commonly written using the Cyrillic alphabet and Hmong can be written in ASCII). It also does not provide support for many of the archaic languages, such as Ahom, Akkadian, Aramaic, Babylonian Cuneiform, Balti, Brahmi, Etruscan, Hittite, Javanese, Numidian, Old Persian Cuneiform, Syrian, and many others.

It proves that many times using UNICODE texts that can be written in ASCII is inefficient, because the UNICODE text has a double size than the same text in ASCII, half of it being nothing but zeros. To handle this problem, several intermediate formats were created. They are called Universal Transformation Format, or simply UTF. There are currently several forms of UTF: UTF-7, UTF-7.5, UTF-8, UTF-16, and UTF-32. This article is focused on the basics of UTF-8.

UTF-8

UTF-8 is a variant-length character encoding for Unicode, created by Ken Thompson in 1992, in a New Jersey diner, where he designed it in the presence of Rob Pike on a placemat. It is currently standardized as RFC 3629. UTF-8 uses 1 to 6 bytes to encode one UNICODE character. (If the UNICODE char is represented on 2 bytes, there is a need for mostly 3 bytes; if the UNICODE char is represented as 4 bytes, 6 bytes may be needed.) 4 or 6 bytes to encode a single char may seem too much, but the UNICODE chars that need that are rarely used.

The transformation table for UTF-8 is presented below:

UNICODE UTF-8
00000000 - 0000007F 0xxxxxxx
00000080 - 000007FF 110xxxxx 10xxxxxx
00000800 - 0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
00010000 - 001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
00200000 - 03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
04000000 - 7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

The UNICODE characters that actually represent ASCII chars are encoded in a single byte, and the UTF-8 representation is actually the ASCII representation. All other UNICODE characters require at least 2 bytes. Each of these bytes starts with an escape sequence. The first byte has a unique sequence, composed on N bits on 1 followed by 1 bit of 0. The N number of bits of 1 indicates the number of bytes on which the character is encoded.

Examples

UNICODE \uCA (11001010) requires 2 bytes for UTF-8 encoding:

\uCA -> C3 8A

UNICODE \uF03F (11110000 0011111) requires 3 bytes for UTF-8 encoding:

\u F03F -> EF 80 BF

Advantages

Here are several advantages of UTF-8:

  • UTF-8 can be read and written quickly just with bit-mask and bit-shift operations.
  • Comparing two char strings in C/C++ with strcmp() gives the same result as wcscmp(), so that legicographic sorting and tree-search order are preserved.
  • Bytes FF and FE never appear in an UTF-8 output, so they can be used to indicate an UTF-16 or UTF-32 text (see BOM).
  • UTF-8 is byte order independent. The bytes order is the same on all systems, so that it doesn't actually require a BOM.

Disadvantages

UTF-8 has several disadvantages:

  • You cannot determine the number of bytes of the UTF-8 text from the number of UNICODE characters because UTF-8 uses a variable length encoding.
  • It needs 2 bytes for those non-Latin characters that are encoded in just 1 byte with extended ASCII char sets.
  • ISO Latin-1, a subset of UNICODE, is not a subset of UTF-8.
  • The 8-bit chars of UTF-8 are stripped by many mail gateways because Internet messages were originally designed as 7-bit ASCII. The problem led to the creation of UTF-7.
  • UTF-8 uses the values 100xxxxx in more than 50% of its representation, but existing implementation of ISO 2022, 4873, 6429, and 8859 systems mistake these as C1 control codes. The problem led to the creation of UTF-7,5.

Modified UTF-8

Java uses UTF-16 for the internal text representation and supports a non-standard modification of UTF-8 for string serialization. There are two differences between the standard and modified UTF-8:

  • In modified UTF-8, the null character (U+0000) is encoded with two bytes (11000000 10000000) instead of just one (00000000), which ensures that there are no embedded nulls in the encoded string (so that if the string is processed with a C-like language, the text is not truncated to the first null character).
  • In standard UTF-8, characters outside the BMP (Basic Multilingual Plain) are encoded using the 4-byte format, but in modified UTF-8 they are represented as surrogate pairs and then the surrogate pairs are encoded individually in sequence. As a result, characters that require 4 bytes in standard UTF-8 require 6 bytes in modified UTF-8.

Byte Order Mark

BOM is a character that indicates the endianness of a UNICODE text encoded in UTF-16, UTF-32 and in the same time a marker to indicate that text is encoded in UTF-8, UTF-16, UTF-32 (UTF-8 is byte-order independent).

Encoding Representation
UTF-8 EF BB BF
UTF-16 Big Endian FE FF
UTF-16 Little Endian FF FE
UTF-32 Big Endian 00 00 FE FF
UTF-32 Little Endian FF FE 00 00

UTF-8 C++ Encoding Sample

Here are four functions written in C++ that encode and decode 2 and 4 bytes UNICODE text in/from UTF-8.

#define         MASKBITS                0x3F
#define         MASKBYTE                0x80
#define         MASK2BYTES              0xC0
#define         MASK3BYTES              0xE0
#define         MASK4BYTES              0xF0
#define         MASK5BYTES              0xF8
#define         MASK6BYTES              0xFC

typedef unsigned short   Unicode2Bytes;
typedef unsigned int     Unicode4Bytes;

void UTF8Encode2BytesUnicode(std::vector< Unicode2Bytes > input,
                             std::vector< byte >& output)
{
   for(int i=0; i < input.size(); i++)
   {
      // 0xxxxxxx
      if(input[i] < 0x80)
      {
         output.push_back((byte)input[i]);
      }
      // 110xxxxx 10xxxxxx
      else if(input[i] < 0x800)
      {
         output.push_back((byte)(MASK2BYTES | input[i] >> 6));
         output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
      }
      // 1110xxxx 10xxxxxx 10xxxxxx
      else if(input[i] < 0x10000)
      {
         output.push_back((byte)(MASK3BYTES | input[i] >> 12));
         output.push_back((byte)(MASKBYTE | input[i] >> 6 & MASKBITS));
         output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
      }
   }
}

void UTF8Decode2BytesUnicode(std::vector< byte > input,
                             std::vector< Unicode2Bytes >& output)
{
   for(int i=0; i < input.size();)
   {
      Unicode2Bytes ch;

      // 1110xxxx 10xxxxxx 10xxxxxx
      if((input[i] & MASK3BYTES) == MASK3BYTES)
      {
         ch = ((input[i] & 0x0F) << 12) | (
               (input[i+1] & MASKBITS) << 6)
              | (input[i+2] & MASKBITS);
         i += 3;
      }
      // 110xxxxx 10xxxxxx
      else if((input[i] & MASK2BYTES) == MASK2BYTES)
      {
         ch = ((input[i] & 0x1F) << 6) | (input[i+1] & MASKBITS);
         i += 2;
      }
      // 0xxxxxxx
      else if(input[i] < MASKBYTE)
      {
         ch = input[i];
         i += 1;
      }

      output.push_back(ch);
   }
}

void UTF8Encode4BytesUnicode(std::vector< Unicode4Bytes > input,
                             std::vector< byte >& output)
{
   for(int i=0; i < input.size(); i++)
   {
      // 0xxxxxxx
      if(input[i] < 0x80)
      {
         output.push_back((byte)input[i]);
      }
      // 110xxxxx 10xxxxxx
      else if(input[i] < 0x800)
      {
         output.push_back((byte)(MASK2BYTES | input[i] > 6));
         output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
      }
      // 1110xxxx 10xxxxxx 10xxxxxx
      else if(input[i] < 0x10000)
      {
         output.push_back((byte)(MASK3BYTES | input[i] >> 12));
         output.push_back((byte)(MASKBYTE | input[i] >> 6 & MASKBITS));
         output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
      }
      // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
      else if(input[i] < 0x200000)
      {
         output.push_back((byte)(MASK4BYTES | input[i] >> 18));
         output.push_back((byte)(MASKBYTE | input[i] >> 12 & MASKBITS));
         output.push_back((byte)(MASKBYTE | input[i] >> 6 & MASKBITS));
         output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
      }
      // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
      else if(input[i] < 0x4000000)
      {
         output.push_back((byte)(MASK5BYTES | input[i] >> 24));
         output.push_back((byte)(MASKBYTE | input[i] >> 18 & MASKBITS));
         output.push_back((byte)(MASKBYTE | input[i] >> 12 & MASKBITS));
         output.push_back((byte)(MASKBYTE | input[i] >> 6 & MASKBITS));
         output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
      }
      // 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
      else if(input[i] < 0x8000000)
      {
         output.push_back((byte)(MASK6BYTES | input[i] >> 30));
         output.push_back((byte)(MASKBYTE | input[i] >> 18 & MASKBITS));
         output.push_back((byte)(MASKBYTE | input[i] >> 12 & MASKBITS));
         output.push_back((byte)(MASKBYTE | input[i] >> 6 & MASKBITS));
         output.push_back((byte)(MASKBYTE | input[i] & MASKBITS));
      }
   }
}

void UTF8Decode4BytesUnicode(std::vector< byte > input,
                             std::vector< Unicode4Bytes >& output)
{
   for(int i=0; i < input.size();)
   {
      Unicode4Bytes ch;

      // 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
      if((input[i] & MASK6BYTES) == MASK6BYTES)
      {
         ch = ((input[i] & 0x01) << 30) | ((input[i+1] & MASKBITS) << 24)
              | ((input[i+2] & MASKBITS) << 18) | ((input[i+3]
                        & MASKBITS) << 12)
              | ((input[i+4] & MASKBITS) << 6) | (input[i+5] & MASKBITS);
         i += 6;
      }
      // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
      else if((input[i] & MASK5BYTES) == MASK5BYTES)
      {
         ch = ((input[i] & 0x03) << 24) | ((input[i+1]
                & MASKBITS) << 18)
              | ((input[i+2] & MASKBITS) << 12) | ((input[i+3]
                  & MASKBITS) << 6)
              | (input[i+4] & MASKBITS);
         i += 5;
      }
      // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
      else if((input[i] & MASK4BYTES) == MASK4BYTES)
      {
         ch = ((input[i] & 0x07) << 18) | ((input[i+1]
                & MASKBITS) << 12)
              | ((input[i+2] & MASKBITS) << 6) | (input[i+3] & MASKBITS);
         i += 4;
      }
      // 1110xxxx 10xxxxxx 10xxxxxx
      else if((input[i] & MASK3BYTES) == MASK3BYTES)
      {
         ch = ((input[i] & 0x0F) << 12) | ((input[i+1] & MASKBITS) << 6)
              | (input[i+2] & MASKBITS);
         i += 3;
      }
      // 110xxxxx 10xxxxxx
      else if((input[i] & MASK2BYTES) == MASK2BYTES)
      {
         ch = ((input[i] & 0x1F) << 6) | (input[i+1] & MASKBITS);
         i += 2;
      }
      // 0xxxxxxx
      else if(input[i] < MASKBYTE)
      {
         ch = input[i];
         i += 1;
      }
      output.push_back(ch);
   }
}

References

About the Author
Marius Bancila is a Microsoft MVP for VC++. He works as a software developer for a Norwegian-based company. He is mainly focused on building desktop applications with MFC and VC#. He keeps a blog at www.mariusbancila.ro/blog, focused on Windows programming. In July 2007 together with two other Romanian MVPs he created codexpert.ro, a community for Romanian C++/VC++ programmers.

AND

출처 :http://vbdream.tistory.com/entry/Windows-TEBThread-Environment-Block-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0

 [Windows] PEB(Process Environment Block)/TEB(Thread Environment Block) 살펴보기

 윈도우에는 PEB와 TEB라 불리는 특수 구조체가 프로세스별로 존재합니다. PEB는 유저 모드 프로세스당 하나, TEB는 유저 모드 스레드당 하나씩 존재합니다.

 주의! 커널 모드(Kernel Mode)에서는 PEB/TEB가 존재하지 않습니다.


 PEB는 다음 논제로 남겨두기로 하고, TEB 부터 살펴보겠습니다.

 Windows XP SP3를 기준으로 TEB의 구조체는 아래와 같이 표현됩니다.

nt!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
   +0x034 LastErrorValue   : Uint4B
   +0x038 CountOfOwnedCriticalSections : Uint4B
   +0x03c CsrClientThread  : Ptr32 Void
   +0x040 Win32ThreadInfo  : Ptr32 Void
   +0x044 User32Reserved   : [26] Uint4B
   +0x0ac UserReserved     : [5] Uint4B
   +0x0c0 WOW32Reserved    : Ptr32 Void
   +0x0c4 CurrentLocale    : Uint4B
   +0x0c8 FpSoftwareStatusRegister : Uint4B
   +0x0cc SystemReserved1  : [54] Ptr32 Void
   +0x1a4 ExceptionCode    : Int4B
   +0x1a8 ActivationContextStack : _ACTIVATION_CONTEXT_STACK
   +0x1bc SpareBytes1      : [24] UChar
   +0x1d4 GdiTebBatch      : _GDI_TEB_BATCH
   +0x6b4 RealClientId     : _CLIENT_ID
   +0x6bc GdiCachedProcessHandle : Ptr32 Void
   +0x6c0 GdiClientPID     : Uint4B
   +0x6c4 GdiClientTID     : Uint4B
   +0x6c8 GdiThreadLocalInfo : Ptr32 Void
   +0x6cc Win32ClientInfo  : [62] Uint4B
   +0x7c4 glDispatchTable  : [233] Ptr32 Void
   +0xb68 glReserved1      : [29] Uint4B
   +0xbdc glReserved2      : Ptr32 Void
   +0xbe0 glSectionInfo    : Ptr32 Void
   +0xbe4 glSection        : Ptr32 Void
   +0xbe8 glTable          : Ptr32 Void
   +0xbec glCurrentRC      : Ptr32 Void
   +0xbf0 glContext        : Ptr32 Void
   +0xbf4 LastStatusValue  : Uint4B
   +0xbf8 StaticUnicodeString : _UNICODE_STRING
   +0xc00 StaticUnicodeBuffer : [261] Uint2B
   +0xe0c DeallocationStack : Ptr32 Void
   +0xe10 TlsSlots         : [64] Ptr32 Void
   +0xf10 TlsLinks         : _LIST_ENTRY
   +0xf18 Vdm              : Ptr32 Void
   +0xf1c ReservedForNtRpc : Ptr32 Void
   +0xf20 DbgSsReserved    : [2] Ptr32 Void
   +0xf28 HardErrorsAreDisabled : Uint4B
   +0xf2c Instrumentation  : [16] Ptr32 Void
   +0xf6c WinSockData      : Ptr32 Void
   +0xf70 GdiBatchCount    : Uint4B
   +0xf74 InDbgPrint       : UChar
   +0xf75 FreeStackOnTermination : UChar
   +0xf76 HasFiberData     : UChar
   +0xf77 IdealProcessor   : UChar
   +0xf78 Spare3           : Uint4B
   +0xf7c ReservedForPerf  : Ptr32 Void
   +0xf80 ReservedForOle   : Ptr32 Void
   +0xf84 WaitingOnLoaderLock : Uint4B
   +0xf88 Wx86Thread       : _Wx86ThreadState
   +0xf94 TlsExpansionSlots : Ptr32 Ptr32 Void
   +0xf98 ImpersonationLocale : Uint4B
   +0xf9c IsImpersonating  : Uint4B
   +0xfa0 NlsCache         : Ptr32 Void
   +0xfa4 pShimData        : Ptr32 Void
   +0xfa8 HeapVirtualAffinity : Uint4B
   +0xfac CurrentTransactionHandle : Ptr32 Void
   +0xfb0 ActiveFrame      : Ptr32 _TEB_ACTIVE_FRAME
   +0xfb4 SafeThunkCall    : UChar
   +0xfb5 BooleanSpare     : [3] UChar


TEB(Thread Environment Block)에서 몇가지 재미난 필드를 살펴봅시다.

+0x000 NtTib            : _NT_TIB
SEH(Structured Exception Handler) 체인 중 최상위 엔트리를 말합니다.

+0x01c EnvironmentPointer : Ptr32 Void
CreateProcess()의 Environment 필드에 의해 지시된 포인터 값입니다.

+0x020 ClientId         : _CLIENT_ID
프로세스 아이디와 스레드 아이디를 가지고 있는 필드(CLIENT_ID structure)입니다.

+0x02c ThreadLocalStoragePointer : Ptr32 Void
TlsAlloc/TlsGetValue/TlsSetValue 등의 Win32 API에 의해 참조되는 값으로, Thread Local Storage(TLS) pointer를 저장합니다.

+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
PEB(Process Environment Block) 포인터를 저장합니다.

+0x040 Win32ThreadInfo  : Ptr32 Void
스레드 정보를 담고 있는 커널 모드 포인터입니다.


그렇다면 Application에서 TEB를 어떻게 사용하고 참조하는 것일까요?

그 해답은 API를 디어셈블하면 얻을 수 있을겁니다. GetCurrentProcessId()를 Disassemble해보았습니다.

kernel32!GetCurrentProcessId:
7C8099B0  64 A1 18 00 00 00     MOV EAX,DWORD PTR FS:[18]
7C8099B6  8B 40 20              MOV EAX,DWORD PTR DS:[EAX+20]
7C8099B9  C3                    RETN

생각 외로 코드가 별로 길지 않죠? :p

ClientId: CLIENT_ID 필드 오프셋이 0x20 이였던가요? 왠지 'EAX+20' 이 눈에 띄는군요~

그러면 EAX에는 TEB pointer가 있다는 것인데...

그 전에는 MOV EAX, DWORD PTR FS:[18h] 로 FS segment block을 참조하고 있는것을 볼 수 있습니다.

저 코드를 임의로 실행시켜보니 EAX 값은 0x7FFDF000 이였습니다. (물론 Thread 마다 주소는 다를겁니다. :p)

Thread 마다 주소가 다를 것 같으니, FS segment에 대한 비밀을 파헤쳐 보아야 될 것 같은데요... 아쉽게도 유저 모드(Ring3)에서는 세그먼트 정보를 볼 수 없습니다. ( 유저 모드에서 얻을 수 있는 것은 단지 Segment Index 뿐이죠. )

Debugger에서 Segment Index를 확인해보니 'FS=003B' 였습니다. (이 값은 버전, 환경에 따라 다를 수 있습니다.)

003B 는 2진수로 '0011 1011' 이고, 하위 3비트는 RPL(Request Privilege Level), 그리고 그 다음 1비트는 TI(LDT 사용 여부)를 의미합니다. 실제 세그먼트 인덱스는 (하위 4비트를 버린 값)+8 의 값을 가집니다.

다음은 WinDbg에서 dg명령어를 이용하여 세그먼트 정보를 출력한 결과입니다.:

                                  P Si Gr Pr Lo
Sel    Base     Limit     Type    l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0000 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
0008 00000000 ffffffff Code RE Ac 0 Bg Pg P  Nl 00000c9b
0010 00000000 ffffffff Data RW Ac 0 Bg Pg P  Nl 00000c93
0018 00000000 ffffffff Code RE Ac 3 Bg Pg P  Nl 00000cfb
0020 00000000 ffffffff Data RW Ac 3 Bg Pg P  Nl 00000cf3
0028 bab50d70 000020ab TSS32 Busy 0 Nb By P  Nl 0000008b
0030 bab50000 00001fff Data RW Ac 0 Bg Pg P  Nl 00000c93
0038 7ffdd000 00000fff Data RW Ac 3 Bg By P  Nl 000004f3
0040 00000400 0000ffff Data RW    3 Nb By P  Nl 000000f2
0048 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000

7FFDD000 이군요. 아무래도 FS:[] 자체가 TEB인것 같습니다.

그렇다면 FS:[18h] 는 무엇이였던걸까요? 오프셋 0x18은 NT_TIB 안입니다. 그렇다면 NT_TIB 구조체를 조사해보면...

nt!_NT_TIB
   +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 StackBase        : Ptr32 Void
   +0x008 StackLimit       : Ptr32 Void
   +0x00c SubSystemTib     : Ptr32 Void
   +0x010 FiberData        : Ptr32 Void
   +0x010 Version          : Uint4B
   +0x014 ArbitraryUserPointer : Ptr32 Void
   +0x018 Self             : Ptr32 _NT_TIB

결국 FS:[0] 자기 자신을 가리키는 것이였습니다. Microsoft는 왜 Self 필드를 참조하는 것일까요? :p

필자 생각으로는, 아마 미래를 위해서 그리 구현해놓은 것 같습니다. TEB가 아닌 다른 구조체가 FS:[]로 바뀌어도, 0x18 오프셋만은 TEB의 포인터를 가리키면 후방 호환성이 유지되니까요.
(뭐, 그 덕분에 해커가 Self 필드를 수정하면 모든 Windows API에서 TEB에 접근할 때 다른 메모리를 참조할 수 있게 만들 수 있는 틈이 생긴 셈이지만요 :p)

결론적으로, GetCurrentProcessId() API는 CLIENT_ID를 참조하는 것을 볼 수 있습니다.

마지막으로, CLIENT_ID 구조체는 다음과 같습니다. 읽어주셔서 감사합니다.

typedef struct {
    LPVOID UniqueProcess; // Process ID (Offset: 0x0)
    LPVOID UniqueThread; // Thread ID (Offset: 0x4)
} CLIENT_ID, *PCLIENT_ID;


 

ps: Visual Basic이나 Delphi같이 Assembly Language에 접근하기 힘든 언어는, TEB의 주소를 구해야 할 필요가 있습니다.

Windows NT 이상 버전의 OS에서는 ntdll.dll에 NtCurrentTeb()을 export하고 있고, 그 함수는 인자가 없고, 반환값은 32비트 정수의 TEB 주소값입니다.

그 함수를 이용해서 TEB의 주소를 구해주고 메모리 API(RtlMoveMemory 등)를 이용해서 읽거나 써주면, FS:[]와 마찬가지의 효과로 TEB에 접근할 수 있습니다.
AND

출처: http://kslive.tistory.com/15

DllMain이 실행되는 시기는 언제인가?

------------------------------------------------------------------------------------

DLL_PROCESS_ATTACH  : 프로세스에 Dll이 최초 로드되는 경우에 호출된다.


 

DLL_PROCESS_DETACH  : Dll이 프로세스 주소공간에서 해제되는 경우에 호출된다.


 

DLL_THREAD_ATTACH    : 스레드가 생성이 되었을경우 현 프로세스 주소 공간에 존재하는 

                                                 모든 Dll들의 DllMain이 호출된다.

DLL_THREAD_DETACH    : 스레드가 종료되는 경우 현 프로세스 주소 공간에 존재하는

                                                 모든 Dll들의 DllMain이 호출된다.


 

------------------------------------------------------------------------------------

이 와 같은 경우에 DllMain이 호출되며 여기서 눈을 여겨 보아야할 부분은  DllMain 함수의

실행방법이다.

다음과 같은 경우가 있다.


 

switch(pArg)

{

case DLL_PROCESS_ATTACH:

           {

                         HANDLE aThread = CreateThread(. . . . . .);

                         WaitSignleObject(aThread, INFINITE);

                         CloseHandle(aThread);

           }

           break;
}


 

이 Dll은 프로세스에 최초 로드시 스레드를 하나 생성하며 이 스레드이 핸들이 시그널될때까지 대기 후 해당 스레드의 핸들을 닫는다.


 

겉으로 보기에는 아무 문제도 없는 코드다.

하지만 DllMain의 동작을 아는 사람이 보면 이 Dll은 교착상태를 100% 유발하는 코드라고 볼수있다. 어느 부분에서 교착상태(Dead-lock)이 발생할까?


 

1. Dll이 최초 로드가 되어 프로세스 주소 공간에 자리를 잡으며 DllMain을 호출한다.


 

2. DllMain에서 DLL_PROCESS_ATTACH: 이 부분이 실행이 되며 스레드를 생성한다.


 

3. 스레드를 생성하였으므로 DllMain을 다시 호출한다.(DLL_THREAD_ATTACH)


 

자 어느 부분일까? 문제는 2번과 3번의 사이에서 발생을 한다.


 

하나의 DllMain은 동시에 하나에 스레드에서 진입 및 실행이 가능하다. 이 말인즉 하나의 DllMain을 두 스레드 이상에서 호출하였을경우 하나의 스레드씩 순서대로 입장하여 해당 DllMain이 리턴을 할때 까지 다른 스레드들은 대기상태로 존재한다는 것이다.


 

그럼 위의 코드가 어디에서 교착상태가 발생하는지는 말하지않아도 알수있지 않을까?


 

그렇다 주스레드의 WaitForSingleObject(... , INFINITE) 와 생성된 스레드의 DllMain진입과정에서 일어난다.

주스레드는 생성된 스레드의 핸들이 시그널 될때까지 대기한다.(DllMain을 붙잡고 있는상태)

생성된 스레드는 DllMain을 실행하고 나서야 스레드가 시작되기때문에 DllMain에 진입할려는 상태로 존재하는것이다.


 

Dll을 제작시 이 부분을 유의하자. 나는 앞으로 DllMain에 스레드나 프로세스 생성같은 코드를 넣지 않겠다. 꽝꽝꽝!

AND

출처: http://www.jiniya.net/lecture/techbox/unicode1.html

MBCS는 한 문자를 표현하는데 여러 바이트를 사용하는 것을 말한다. 일반적인 영문자의 경우 모두 한 바이트로 표시되지만, 한글과 같은 경우에는 두 바이트를 사용한다. 또한 두 바이트를 사용하더라도 인코딩 규칙에 따라서 서로 다른 두 바이트가 생성된다. 따라서 인코딩 규칙이 맞지 않는 경우에는 제대로 된 문서를 읽을 수 없는 불편함이 있다. 이러한 불편함을 해결하기 위해서 나온 것이 유니코드다. 유니코드에 대해선 이미 많은 문서에서 소개가 되었기 때문에 별도로 설명하진 않겠다. 혹시 좀 더 자세한 내용이 궁금하다면 http://www.jinsuk.pe.kr/Unicode/Unicode_intro-kr.html를 방문해 보도록 하자.

Windows NT에 기반한 운영체제의 경우 커널 코드가 유니코드를 통해서 작성 되었다. 문자열을 입력 받는 API의 경우 유니코드 버전이 기본적으로 사용된다. 만약 MBCS 버전의 API를 호출한다면 내부적으로 MBCS문자열을 유니코드로 변경한 후에 다시 유니코드 버전의 API를 호출하게 된다. 따라서 MBCS 버전의 프로그램을 작성하는 것은 NT 기반의 운영체제에서는 비효율 적이다. 이러한 점 때문에 최근에는 대부분의 업체에서 NT 기반의 프로그램은 유니코드 버전을 출시하고 있다. 유니코드와 MBCS 빌드에 상관없이 유연하게 컴파일 되는 프로그램을 만들기 위해서 지켜야할 여섯 가지 기본 원칙에 대해서 알아보자.

첫째, 문자열 타입은 T형을 쓴다. T형의 경우 유니코드, MBCS 빌드에 상관없이 적절한 타입으로 변환되기 때문에 빌드가 변경된다고 해서 따로 타입을 수정해 주는 불편함이 없다. char를 사용할 장소에 TCHAR를, char *를 사용해야 한다면 LPTSTR을, const char *을 사용해야 한다면 LPCTSTR을 사용하도록 한다.

둘째, 문자열 리터럴은 항상 TEXT 매크로로 묶어 둔다. 문자열 리터럴은 프로그램 내에서 사용되는 문자열 상수를 의미한다. 그런데 이러한 문자열 상수의 경우 유니코드 빌드에선 앞에 L을 붙여서 wchar_t형이 되어야 한다. 즉, 보통의 경우엔 "%s\n"과 같이 사용하면 되지만, 유니코드 빌드에선 L"%s\n"로 변경해 주어야 한다. 이러한 작업을 일일이 빌드가 바뀔 때 마다 해주는 것은 매우 번거롭고 오류가 많이 나는 일이다. 이러한 작업을 도와주는 것이 TEXT 매크로다. 위에 언급된 문자열 상수의 경우에도 TEXT("%s\n")과 같이 사용하면 MBCS 빌드에선 "%s\n"이, 유니코드 빌드에선 L"%s\n"이 된다. 매크로가 알아서 처리해 준다. 따라서 문자열 리터럴은 항상 TEXT 매크로로 묶어주도록 하자.

셋째, CRT 문자열 함수는 T타입 함수를 사용한다. T형과 마찬가지로 이러한 함수들은 빌드에 따라 적절한 인자를 입력으로 받는다. 예를 들자면 문자열을 복사하는 strcpy에 대응하는 함수로는 _tcscpy가 있다. _tcscpy는 유니코드 빌드에선 인자를 wchar_t로 받고, MBCS빌드에선 인자를 char로 받는다. 따라서 _tcscpy를 사용하고 인자를 T형을 사용한다면 빌드가 변경된다고 해서 직접 수정해야 하는 부분이 없다. 아래 표에는 일반적으로 자주 사용하는 문자열 함수들과 거기에 대한 T타입 함수를 표시해 두었다. 표를 살펴보면 알겠지만 변환 규칙은 무척 간단하다. str로 시작하는 함수는 _tcs로 치환 하면 되고, 그렇지 않은 함수들은 앞에 _t를 붙이면 된다. 물론 그렇지 않은 함수들도 일부 있다. 그러한 함수들은 MSDN을 이용하면 손쉽게 T타입 함수명을 알 수 있다.

일반 함수명 T타입 함수명 기능
strcpy _tcscpy 문자열 복사
strncpy _tcsncpy 특정 길이 문자열 복사
strcat _tcscat 문자열 추가
strstr _tcsstr 문자열 검색
strchr _tcschr 문자 검색
strrchr _tcsrchr 역순 문자 검색
strcmp _tcscmp 문자열 비교
stricmp _tcsicmp 대소문자 구분 없이 문자열 비교
mkdir _tmkdir 디렉터리 생성
fopen _tfopen 파일 열기

넷째, 길이와 크기를 구분한다. 문자열에는 길이와 크기가 있다. MBCS 빌드에서 기본적인 문자 단위로 사용되는 char형의 경우 그 크기가 1바이트 이기 때문에 크기와 길이가 동등한 의미를 가진다. 이러한 이유로 기존의 MBCS 빌드에서 사용하던 많은 코드가 크기와 길이를 정확하게 구분하지 않고 혼용해서 사용하고 있다. 하지만 유니코드 환경으로 넘어오면 크기와 길이는 항상 달라진다. 따라서 함수의 인자가 길이와 크기 중 어떠한 것을 요구하는지 판단해서 정확하게 사용하도록 해야 한다.

다섯째, std::string을 사용하는 경우다. std::string의 경우 ANSI 문자열을 표현하도록 선언된 클래스다. 유니코드 문자열을 표현하는데 사용할 수 있는 타입으로 std::wstring이 있다. 이 둘을 빌드 타입이 변경될 때 마다 바꿔줘야 하는 것은 번거로운 일이다. 이쯤되면 std::tstring이 없는지 궁금해 질 것이다. 하지만 std::tstring은 없다. 없으면 우리가 만들어야 한다. std::string의 경우 std::basic_string<char>로 선언된 것이고, std::wstring의 경우 std::basic_string<wchar_t>로 선언된 것이다. 따라서 우리는 아래와 같이 std::tstring을 정의할 수 있다.

  1. namespace std { typedef basic_string<TCHAR> tstring; } 

마지막으로 일부 함수나 메시지 인자의 경우 반드시 char나, wchar_t로 취급해야 하는 경우가 있다. 이러한 경우 부득이하게 빌드에 따라서 문자열을 변환해서 사용해야 하며 빌드가 변경되면 반드시 코드를 수정해야 한다. 따라서 처음 작성시에 안전하게 두 가지 빌드로된 버전을 모두 작성해 놓거나 아니면 다른 빌드에서 컴파일 할 경우 에러가 나도록 처리해 두면 편리하다. 아래와 같은 전처리기를 사용하면 쉽게 작성할 수 있다. 유니코드 빌드에선 _UNICODE가 선언된다는 점을 기억해 두자.

  1. #ifdef _UNICODE  
  2. // 유니코드 빌드에서 수행할 내용  
  3. #else  
  4. // MBCS 빌드에서 수행할 내용  
  5. #endif 

만약 현재 프로그램이 MBCS 기반이고 유니코드 부분을 지금은 신경 쓰지 않아도 된다면 #error 전처리기를 사용하면 된다. 이렇게 하면 유니코드로 빌드 할 경우 컴파일 오류가 발생하기 때문에 어떤 부분이 수정되어야 하는지 바로 찾을 수 있기 때문에 편리하다.

  1. #ifdef _UNICODE  
  2. // 유니코드 부분은 작성 필요  
  3. #error 이 프로그램은 유니코드 빌드로 컴파일되지 않습니다.  
  4. #else  
  5. // MBCS 빌드에서 수행할 내용  
  6. #endif 
AND