출처: http://www.scs.stanford.edu/09wi-cs140/pintos/specs/kbd/scancodes-7.html


Next Previous Contents


7. Japanese keyboards

7.1 Japanese 86/106 keyboards

(Information from Barry Yip <g609296@cc.win.or.jp>, Norman Diamond, NIIBE Yutaka and H. Peter Anvin, who contributed the photographs of his JP106 keyboard above and of his Japanese laptop.)

Common Japanese keyboards have five additional keys (106-key, or 86-key for a notebook; these days there may also be 3 extra Windows keys). These keys have scancodes 70 (hiragana/katakana), 73 (backslash/underscore), 79 (henkan/zenkouho), 7b (muhenkan), 7d (yen/vertical bar).

Different keycaps:

USB Scancode Japanese US USB Scancode Japanese US
53 29 (hankaku/zenkaku) (` / ~) 47 1a (@ / `) ([ / {)
31 03 (2 / ") (2 / @) 48 1b ([ / {) (] / })
35 07 (6 / &) (6 / ^) 51 27 (; / +) (; / :)
36 08 (7 / ') (7 / &) 52 28 (: / *) (' / ")
37 09 (8 / () (8 / *) 29 2b (] / }) (backslash / |)
38 0a (9 / )) (9 / () 135 73 (backslash / _)
39 0b (0 / ~) (0 / )) 139 7b (muhenkan)
45 0c (- / =) (- / _) 138 79 (henkan/zenkouho)
46 0d (^ / overbar) (= / +) 136 70 (hiragana/katakana)
137 7d (\ / |)

ASCII and JIS-Roman differ in two or three points: the code positions where ASCII has backslash, tilde, broken bar, JIS-Roman uses yen, overbar and vertical bar, respectively.

Some keyboards have the tilde printed on the keycap for the 0 key, some don't. Similarly, some keyboards have the backslash printed on the keycap for the _ key and some don't, but in all cases you need Shift to get _.

7.2 Description of the all-Japanese keys

Norman Diamond adds to the previous section:

To the left of the spacebar, (Shift-JIS) 無変換 (muhenkan) means no conversion from kana to kanji. To the right of the spacebar, 変換 (henkan) means conversion from kana to kanji. In Microsoft systems it converts the most recently input sequence of kana to the system's first guess at a string of kanji/kana/etc. with the correct pronunciation and a guess at the meaning. Repeated keypresses change it to other possible guesses which are either less common or less recently used, depending on the situation. The shifted version of this key is 前侯補 (zenkouho) which means "previous candidate" -- "zen" means "previous", while "kouho" means "candidate" (explanation courtesy of NIIBE Yutaka) -- it rotates back to earlier guesses for kanji conversion. The alt version of this key is 全侯補 also pronounced (zenkouho), which means "all candidates" -- here, "zen" means "all" -- it displays a menu of all known guesses. I never use the latter two functions of the key, because after pushing the henkan key about three times and not getting the desired guess, it displays a menu of all known guesses anyway.

Next on the right, ひらがな (hiragana) means that phonetic input uses one conventional Japanese phonetic alphabet, which of course can be converted to kanji by pressing the henkan key later. The shifted version is カタカナ (katakana) which means the other Japanese phonetic alphabet, and the alt version is ローマ字 (ro-maji) which means the Roman alphabet.

Near the upper left, 半/全 (han/zen) means switch between hankaku (half-size, the same size as an ASCII character) and zenkaku (full-size, since the amount of space occupied by a kanji is approximately a square, twice as fat as an ASCII character). It only affects katakana and a few other characters (for example there's a full-width copy of each ASCII character in addition to the single-byte half-width encodings). The alt version of this is 漢字 (kanji) which actually causes typed Roman phonetic keys to be displayed as Japanese phonetic kana (either hiragana or katakana depending on one of the other keys described above) and doesn't cause conversion to kanji.

7.3 A Japanese keyboard that imitates a US one

John Bradford reports that he has a Japanese keyboard (an IBM 5576 KEYBOARD-2, part number 94X1110) that by default simulates US key layout. Thus, pressing the @ key yields scancodes 2a 03 (fake shift followed by digit 2), pressing Shift - yields scancodes b6 0d (fake shift down, =) with release 8d 36, etc.

Thus, the (translated Set 2) scancodes can be read off the table with differences between the Japanese and the US layout.

In this state the non-ASCII keys (Yen and overline) yield an error (ff). The Japanese keys hankaku, kanji/katakana, muhenkan, zenkoho/henkan, hiragana, zenmen ki, yield the codes expected from keys in that position on a US keyboard: 29 (`/~), 38 (LAlt), 39 (space), 39 (space), 39 (space), e0 38 (RAlt), respectively.

Switching the keyboard to Set 3 enables the Japanese keys. In untranslated Set 3 these give codes: hankaku 0e, Yen 13, overline (shift ^), kanji/katakana 19, muhenkan 85, zenkoho/henkan 86, hiragana 87, zenmen ki 39. (Also: backslash/underscore 5c, bracketright/braceright 53.)

This is the only keyboard I know that gives more information in Set 3 than in Set 2. It reports keyboard ID ab 90.


Next Previous Contents

AND

출처: http://kin.naver.com/detail/detail.php?d1id=1&dir_id=10501&eid=DrJYJ1zmOJ5rRRdXHEsbYkQSezr4xMeL&qb=yK7A5SC+xr26xbAgxNq15SDA1LfCIGFsdA==&pid=fRhhGwoi5UsssZ54ZHosss--522617&sid=SUX8B6fyRUkAAH-zpnk

처음엔 1963년 미국표준협회에서 알파벳 뿐만 아니라 0,1~9 까지 숫자와 ! @ # $ 등의 특수문자들을 7비트의 기계어로 표현하는 표준안을 제안한 코드이름이 아스키코드 American Standard Code for information interchange)를 사용하였는데,

 

 

이 아스키 코드는 7비트로서 컴퓨터나 인터넷상에서 텍스트 파일을 위한 가장 일반적인 형식입니다.

ASCII 파일에는 각각의 알파벳이나 숫자 그리고 특수 문자들이 7비트 (0000000~1111111)이며 그외에 주소가 넘어가면 윈도우NT 계열의 기본값인 유니코드를 쓰게됩니다.

 

이것은 아스키코드의 코드표입니다.

10진수

ASCII

10진수

ASCII

10진수

ASCII

10진수

ASCII

0

NULL

32

SP

64

@

96

.

1

SOH

33

!

65

A

97

a

2

STX

34

"

66

B

98

b

3

ETX

35

#

67

C

99

c

4

EOT

36

$

68

D

100

d

5

ENQ

37

%

69

E

101

e

6

ACK

38

&

70

F

102

f

7

BEL

39

`

71

G

103

g

8

BS

40

(

72

H

104

h

9

HT

41

)

73

I

105

i

10

LF

42

*

74

J

106

j

11

VT

43

+

75

K

107

k

12

FF

44

`

76

L

108

l

13

CR

45

-

77

M

109

m

14

SO

46

.

78

N

110

n

15

SI

47

/

79

O

111

o

16

DLE

48

0

80

P

112

p

17

DC1

49

1

81

Q

113

q

18

SC2

50

2

82

R

114

r

19

SC3

51

3

83

S

115

s

20

SC4

52

4

84

T

116

t

21

NAK

53

5

85

U

117

u

22

SYN

54

6

86

V

118

v

23

ETB

55

7

87

W

119

w

24

CAN

56

8

88

X

120

x

25

EM

57

9

89

Y

121

y

26

SUB

58

:

90

Z

122

z

27

ESC

59

;

91

[

123

{

28

FS

60

<

92

\

124

|

29

GS

61

=

93

]

125

}

30

RS

62

>

94

^

126

~

31

US

63

?

95

_

127

DEL

출처 아스키코드표|작성자 곰탱이

"예를 들어 ALT키를 누르시고 64라고 써보시면 "@"가 써질것입니다."

 

이런식으로 처음엔 아스키코드가 일반화 되었지만, 컴퓨터도 글로벌 화가 되어 다른 언어도 수용할 공간이 없어 마이크로 소프트에선 유니코드라는 코드를 만들어 (정확히는 아스키 코드를 확장) 세계 모든 언어를 수용하게끔 되었습니다.

기본 아스키코드의 메모리 범위인 7비트(0000000~1111111) 에서 유니코드는 16비트 (0000000000000000~1111111111111111) 의 주소로 늘려 다른 언어도 수용할 만한 공간을 마련하였습니다.

하지만 기본 베이스는 아스키코드를 바탕으로 하기 때문에, 영어 부분에선 아스키 코드로 하여도 별 지장이 없습니다.

 

(하지만 중국 같이 많은 문자가 있는 나라는 유니코드 메모리 구간을 더 늘려달라고 하는 뒷담이..)

====================================================================================

Unicode / 유니코드

Unicode는 세계 각국의 언어를 통일된 방법으로 표현할 수 있게 제안된 국제적인 코드 규약의 이름이다.

8비트 문자코드인 아스키(ASCII) 코드를 16비트로 확장하여 전 세계의 모든 문자를 표현하는 표준코드이다.

8비트로 표현할 수 있는 256자는 영어나 라틴권 등에서는 문제가 없으나, 한국, 일본, 중국, 아랍 등의 다양한 문자들을 표현하는 데는 한계가 있다. 또한 각 나라마다 같은 코드 값에 다른 글자를 쓰는 방식으로는 국제간의 원활한 자료 교환이 불가능하기 때문에 코드를 16비트 체제로 확장해서 65,536자의 영역 안에 전 세계의 모든 글자를 표시하는 표준안이다.

영어를 사용하는 국가에서는 아스키 코드보다 두 배의 공간이 필요하기 때문에 일반적인 통신 등에서는 그만큼의 낭비가 되지만 유니코드를 이용하면 프로그램을 하나만 만들면 모든 나라들의 글자를 처리할 수 있기 때문에 그만큼 큰 이점도 되는 것이다.

11,172자의 한글을 연속된 공간에 가나다라 순서로 '가'에서 'ㅎ'까지를 코드화하는 방식이 유니코드 기술 위원회(UTC)에서 채택한 유니코드 2.0 규격이다.

 

Source : http://kmh.ync.ac.kr/encycl/terms/termsU/unicode.htm

 

이 부분은 제 언어 실력이 없기에 간단하게 정리된 글을 퍼옵니다.

=======================================================================================

윗부분은 아스키 코드에 불과하니, 그 외의 주소 부분인 유니코드는

 

시작-실행 에서 "CHARMAP"를 쳐보셔서 메모리 값을 찾아보시기 바랍니다.

 

 

여기서 밑 부분 "U+0021 (0x21): Exclamation Mark

가 보이시나요?

 

이부분에 U+뒤에 붙은 숫자가 바로 유니코드 주소값입니다. 저기 보이다 시피 "!"가 선택되어있지요?

밑에 보시면 메모리값이 0x21 입니다.

 

근데 여기서 잠깐! 햇갈리실 부분이 있는데 여기서 ALT키를 누르시고 바로 21 누르신다고 되는것이 아닙니다.

 

유니코드의 주소값은 16진수라는 것으로 되어있기 때문에, 2x16=32+ 1x1 = 십진수로는 33이 됩니다.

 

그럼 alt키를 누르시고 33 이라고 입력하시면

 

!

 

보시다시피 !표가 입력됩니다.

 

궁금증이 해결되셨나요??

 

자세한 유니코드목록을 알고 싶으시면 검색하실때

 

유니코드표 라고 검색해보시면 됩니다.

 

 

여기서 중요한걸 알려드리지 않았군요.;

운영체제의 언어 버전마다 설치되어있는 언어파일이 각각 다르기 때문에, 특정 언어같은 경우 같은 값을 입력하더라도 다른 값을 출력할 수 있습니다. 대표적으로 " ? " 가 있습니다. 이는 운영체제에서 값을 제대로 읽지 못하여 ?로 표시되어나오는것입니다. 이것들을 제대로 표시하기 위해선 언어파일을 반드시 설치해주셔야 하며,

사용하는 OS의 문자열 값이 사용하시려는 메모리값의 언어와 같은 언어로 기본값으로 되어있어야 합니다.

 

특히 중국어 처럼 언어가 많은 나라의 경우 메모리를 대부분 풀로 씁니다.

 

 


AND

출처: http://kin.naver.com/detail/detail.php?d1id=1&dir_id=10104&eid=Lr8YeOa58cYoO4p5+YpULl1xLs6/yhl5&qb=x9Gx2yC+xr26xbDE2rXlx6UgyK7A5Q==&pid=fRU8twoi5UKssc/zViwsss--333712&sid=SUX8B6fyRUkAAH-zpnk


우리가 일반적으로 쓰고 있는 지금의 IBM 호환용 컴퓨터는 미국에서 발명되었기 때문에 영문자만을 표현하도록 고안되었다. 숫자 0~9까지 10개, 영문 대소문자를 합쳐서 32개, 그 외의 문장기호와 특수문자들을 모두 합치면 128개면 충분했고, 이러한 문자들을 터미널(Terminal:단말기)을 통해서 호스트컴퓨터로 입력시키기에 충분히 가능했다. 128이라는 10진수를 0부터 시작하는 16진수로 표현하면 7F이고, 전기적인 수치인 2진수로는 0111 1111이 128번째의 값(10진수로는 128)에 해당한다. 129번째 값부터 256번째 값(1111 1111)까지는 확장코드(Extended Code)를 가짐으로써 특별히 Box나 Line, 특수문자 코드를 가진다. 이렇게 8개의 자릿 수 1바이트(Byte)로 영어권에서 충분히 사용가능하며, 이것을 American Standard Code for Information Interchange(ASCII:아스키)라고 한다. 그러나 비영어권에서는 1byte로 모든 해당 국가의 문자를 사용할 수 없다. 특히 동양권 문자들일 경우는 더욱 그렇다. 그러나 개인용 컴퓨터는 결국 16비트로 진화되었고 8비트와는 달리 정보를 처리할 수 있는 영역이 있어 상당한 발전을 이루었고 8비트의 제한된 범위를 뛰어넘게 되어 지금은 각 국가별 코드페이지를 가지게 되었다. 이러한 코드 페이지를 연결하기 위해서는 기본 8비트 단위의 아스키를 기반으로 해서 상위 아스키(80~FF)와 하위 아스키(00~7F)를 구분하고 상위 아스키에 코드페이지를 연결 방식으로 해당 문자 코드를 연결해서 확장해서 사용하게 된다.


 0000 0000부터 0111 1111까지의 128개의 코드는 그대로 사용하지만 129번째 1000 0000부터는 확장코드로써 코드 페이지를 갖게 하는 것이다. 따라서 8비트 중에 최상위 Bit값 1을 기준으로 구분하게 된다. 1이면 국가별 코드페이지가 사용되는 경우에 해당해서 1바이트 코드가 아닌 2Byte(16bit 코드체계)로 동작하게 된다.


[DBCS (Double Byte Character Set) Codepages]

코드 페이지 참조: Windows Codepage 949(Korean)

http://www.microsoft.com/globaldev/reference/dbcs/949.mspx


 위에 페이지를 보고 2바이트 체계 한글의 경우를 생각해 보자, 16진수로 8141이라는 코드가 있을 때 영어문자로 받아들여 1byte처리가 된다면 ü,A가 될 것이다. 이때는 0081, 0041로 코드를 받아서 처리가 되기 때문이다. 2byte로 묶어 처리되면 8141코드는 갂(HANGUL SYLLABLE KIYEOK A SSANGKIYEOK)을 나타내게 된다.


 흔히 한글이 응용어플리케이션에서 한글을 처리하지 못하는 경우는 이처럼 코드페이지 적용이 되지 않는 경우에 해당하며, 외국 웹 솔루션의 경우도 인코딩문제로 인해 겪을 수 있는 문제이다.


참고적으로 영문일 경우 확장코드(Extended Code)


그림출처: http://www.eclipse.net/~snowdog/ascii.html


한글 완성형의 경우는 모두 코드페이지 494에 속하며, 커맨드 셸 상에서는 chcp 437 명령에 의해서 영문전용으로 변경이 가능하다

[출처] 상위 아스키코드와 하위 아스키코드|작성자 에디터


AND

출처: http://harogipro.tistory.com/181

아스키 코드표

10진수

16진수

8진수

2진수

ASCII

10진수

16진수

8진수

2진수

ASCII

0

0×00

000

0000000

NULL

64

0×40

100

1000000

@

1

0×01

001

0000001

SOH

65

0×41

101

1000001

A

2

0×02

002

0000010

STX

66

0×42

102

1000010

B

3

0×03

003

0000011

ETX

67

0×43

103

1000011

C

4

0×04

004

0000100

EOT

68

0×44

104

1000100

D

5

0×05

005

0000101

ENQ

69

0×45

105

1000101

E

6

0×06

006

0000110

ACK

70

0×46

106

1000110

F

7

0×07

007

0000111

BEL

71

0×47

107

1000111

G

8

0×08

010

0001000

BS

72

0×48

110

1001000

H

9

0×09

011

0001001

HT

73

0×49

111

1001001

I

10

0×0A

012

0001010

LF

74

0×4A

112

1001010

J

11

0×0B

013

0001011

VT

75

0×4B

113

1001011

K

12

0×0C

014

0001100

FF

76

0×4C

114

1001100

L

13

0×0D

015

0001101

CR

77

0×4D

115

1001101

M

14

0×0E

016

0001110

SO

78

0×4E

116

1001110

N

15

0×0F

017

0001111

SI

79

0×4F

117

1001111

O

16

0×10

020

0010000

DLE

80

0×50

120

1010000

P

17

0×11

021

0010001

DC1

81

0×51

121

1010001

Q

18

0×12

022

0010010

SC2

82

0×52

122

1010010

R

19

0×13

023

0010011

SC3

83

0×53

123

1010011

S

20

0×14

024

0010100

SC4

84

0×54

124

1010100

T

21

0×15

025

0010101

NAK

85

0×55

125

1010101

U

22

0×16

026

0010110

SYN

86

0×56

126

1010110

V

23

0×17

027

0010111

ETB

87

0×57

127

1010111

W

24

0×18

030

0011000

CAN

88

0×58

130

1011000

X

25

0×19

031

0011001

EM

89

0×59

131

1011001

Y

26

0×1A

032

0011010

SUB

90

0×5A

132

1011010

Z

27

0×1B

033

0011011

ESC

91

0×5B

133

1011011

[

28

0×1C

034

0011100

FS

92

0×5C

134

1011100

29

0×1D

035

0011101

GS

93

0×5D

135

1011101

]

30

0×1E

036

0011110

RS

94

0×5E

136

1011110

^

31

0×1F

037

0011111

US

95

0×5F

137

1011111

_

32

0×20

040

0100000

SP

96

0×60

140

1100000

.

33

0×21

041

0100001

!

97

0×61

141

1100001

a

34

0×22

042

0100010

"

98

0×62

142

1100010

b

35

0×23

043

0100011

#

99

0×63

143

1100011

c

36

0×24

044

0100100

$

100

0×64

144

1100100

d

37

0×25

045

0100101

%

101

0×65

145

1100101

e

38

0×26

046

0100110

&

102

0×66

146

1100110

f

39

0×27

047

0100111

'

103

0×67

147

1100111

g

40

0×28

050

0101000

(

104

0×68

150

1101000

h

41

0×29

051

0101001

)

105

0×69

151

1101001

i

42

0×2A

052

0101010

*

106

0×6A

152

1101010

j

43

0×2B

053

0101011

+

107

0×6B

153

1101011

k

44

0×2C

054

0101100

'

108

0×6C

154

1101100

l

45

0×2D

055

0101101

-

109

0×6D

155

1101101

m

46

0×2E

056

0101110

.

110

0×6E

156

1101110

n

47

0×2F

057

0101111

/

111

0×6F

157

1101111

o

48

0×30

060

0110000

0

112

0×70

160

1110000

p

49

0×31

061

0110001

1

113

0×71

161

1110001

q

50

0×32

062

0110010

2

114

0×72

162

1110010

r

51

0×33

063

0110011

3

115

0×73

163

1110011

s

52

0×34

064

0110100

4

116

0×74

164

1110100

t

53

0×35

065

0110101

5

117

0×75

165

1110101

u

54

0×36

066

0110110

6

118

0×76

166

1110110

v

55

0×37

067

0110111

7

119

0×77

167

1110111

w

56

0×38

070

0111000

8

120

0×78

170

1111000

x

57

0×39

071

0111001

9

121

0×79

171

1111001

y

58

0×3A

072

0111010

:

122

0×7A

172

1111010

z

59

0×3B

073

0111011

;

123

0×7B

173

1111011

{

60

0×3C

074

0111100

<

124

0×7C

174

1111100

|

61

0×3D

075

0111101

=

125

0×7D

175

1111101

}

62

0×3E

076

0111110

>

126

0×7E

176

1111110

~

63

0×3F

077

0111111

?

127

0×7F

177

1111111

DEL

출처:아스키 코드표




AND

출처: http://moogi.new21.org/zb41/view.php?id=Freeboard&page=10&sn1=&divpage=1&sn=off&ss=on&sc=on&select_arrange=name&desc=desc&no=1552


IME API는 윈도우 API 중에서 공부하기 힘든 부분 중 하나입니다. 가장 큰 이유가, 영어 문서를 보면 거기 나오는 각 개념들이 한국어 IME와 어떤 관련이 있는지 도통 감잡을 수가 없는 경우가 많기 때문이지요. :-(

윈도우 IME API의 기능들 중엔 중국, 일본어 IME에나 필요하고 한국어 IME에는 필요없는 기능도 많이 있습니다. 반면 한국어 IME에 필요한 구조나 기능이 거기에는 불충분하게 구현된 것도 많고요.

1. 이미 입력된 한글에 대해 한자 후보 윈도우를 열기

ImmSetCompositionString()으로 임의의 글자를 조합하는 상태를 강제로 만든 후, ImmNotifyIME() 함수로 후보 윈도우를 열면 됩니다.

HIMC hImc=::ImmGetContext(m_hWnd);
::ImmSetCompositionString(hImc, SCS_SETSTR, "오", 2, NULL, 0);
::ImmNotifyIME(hImc, NI_OPENCANDIDATE, 0, 0);
::ImmReleaseContext(m_hWnd, hImc);

이렇게 하면 이 윈도우에 대해 '오'를 조합하는 상태가 됨과 동시에 한자 창이 뜹니다. 단, 그전에 먼저 한영 키를 눌러 '한글' 상태를 만드시고요.

한자 키가 눌러지면, 커서가 가리키는 한글을 블록으로 잡은 뒤 이 코드를 수행합니다. 그래서 사용자가 한자 번호를 누르면, 블록(한글)이 지워지고 그게 한자로 대체되는 효과가 납니다.

2. 한자를 한글로 바꾸기

한자를 한글로 바꾸는 기능은 한국어 IME가 지원하지 않습니다. <날개셋>은 한자로 바꾸는 기능과 마찬가지로 이것을 한자 독음 테이블로 독자적으로 구현했습니다.

3. 후보 윈도우 자체를 바꾸기

ImmAssociateContext 함수로 윈도우 IME를 완전히 끈 뒤, VK_HANGUL이나 VK_HANJA 키가 들어왔을 때에 대한 처리를 OnKeyDown에서 직접 하면 됩니다.

hPrevImc=::ImmAssociateContext(m_hWnd, NULL);

그러면 이 윈도우에서는 IME 창 자체가(한영, 한자 변환 버튼이 있는 도구상자) 뜨지 않고, IME 관련 글쇠가 눌러져도 아무 반응이 생기지 않습니다. 필요할 때는 NULL 대신 hPrevImc를 다시 넘겨주면 이 윈도우는 원래대로 윈도우 IME의 지시를 받게 됩니다.

> 안녕하세요..
> IME관련 문서들을 찾아보다가 이 홈페이지를 들러보게 되었습니다. 홈페이지를 보다보니 카이스트 후배시더군요.. 저는 99학번 전산과 박준하라고 합니다.
> 홈페이지 내용이 알차고, 후배께서는 뭔가에 충분히 매진하고 있는 것 같아 정말 보기 좋고, 한편으론 부럽기도 합니다.
>
> 제가 여기를 찾아오게 된 건 IME관련 문서들을 찾기 위해서 였는데, 인터넷에서는 생각보다 많은 자료들이 없더라구요. 현재 mfc에서 CWnd를 계승받아 CEdit와 같은 기능을 구현하려고 하고 있습니다. 하다보니 정말 복잡한게 많더군요..
>
> 지금 제가 여쭤볼 것은 한자 입력에 관한 것입니다. 한글을 입력하는 도중에 한자키를 누르면, 그 처리는 IME자체에서 하게 되는지 한자 입력이 잘 됩니다. 문제는 한글을 미리 쭉 입력한 후, 그 한글을 한자로 바꾸기 위해서, 한자로 바꿀 한글 앞에 커서를 두고서, 또는 바꿀 한글을 선택한 상태에서 한자키를 누르면 도무지 작동하지를 않습니다. 그럴 경우도 한글 입력 중에 한자키를 누른 것과 같이 후보창(candidate window)가 떠서 한자를 선택할 수 있게 했으면 좋겠는데 말입니다.
>
> msdn에서도 candidate window 등에 관련된 함수를 몇몇 보았으나, 어떻게 사용하는 지 잘 모르겠고, 또한 이 부분에 관련해서는 예제 또한 찾기가 힘들더군요.
>
> 개발하신 날개셋편집기를 보니, 그 기능 역시 완전히 구현되어 있었습니다. 한자입력 창이 기본 ime의 창과 틀린 것으로 보아 직접 구현하신 거라 생각됩니다만, 혹시 도움을 주실 수 있다면, 제가 하고 싶은 부분에 도움이 될만한 조언이나 참고자료를 주시면 정말 감사하겠습니다.
>
> 그리고, 날개셋편집기에서와 마찬가지로 후보창(candidate window) 자체를 어떻게 바꿀 수 있는 지도 알고 싶습니다.
>
> 그럼 좋은 하루 되시길~
>


AND

출처: http://blog.naver.com/kbs3033?Redirect=Log&logNo=70003251127

윈도우에서 사용하는 문자 코드에 대한 이해

 


컴퓨터의 기본 용도가 문자열 처리를 필두로한 데이터 처리라는 점에는 이견이 없을 것이다. 그럼에도 불구하고 개발자들이 문자열 처리에 대한 문제를 간과하기 쉬운 것은 이와 관련된 내용을 다루는 책이 별로 없기 때문이다. 따라서 이번호의 주제를 '기본기를 다지자' 정했다. 글에서는 컴퓨터 내부에서 다루는 문자열 처리를 비롯해 이와 관련된 함수에 대해 살펴보겠다.




지난 3월호에 첫 글이 나간 후 많은 독자들이 메일을 보내왔는데, 이번 글을 쓸 때 그 의견들을 많이 참고했다. 메일의 대부분은 한글과 영문 문자열의 처리 문제와 유니코드 처리, 다국어 문자의 표현에 관련된 것이었는데 한결같이 제대로 정리된 자료가 없다는 내용이었다. 물론 MSDN에 자료가 있긴 하지만 사람들이 주로 관심을 갖는 내용이 아니고, 또 체계적으로 정리가 돼 있거나 번역된 것이 전무하다는 것도 문제점으로 지적할 수 있다.

요즘 나오는 프로그래밍 관련 책들은 모두 사용자 인터페이스에 집중된 얘기일 뿐 가장 기본이 되는 문자열 처리에 대해서는 거의 다루고 있지 않아 프로그램을 시작하는 사람들이 두려움을 갖기 쉽다. 필자가 알고 지내는 어떤 사람이 프로그래머 채용 시험에 기본적인 텍스트 파일 처리 - 라인 수를 세거나 단어를 세는 등 - 에 대한 문제를 출제한 적이 있는데, 시험에 응시한 대부분의 사람이 거의 풀지 못하는 것을 보고 한탄했다는 얘기를 들은 적이 있다. 컴퓨터의 기본 용도가 데이터 처리, 특히 문자열 처리라는 것을 감안할 때, 얼마나 많은 프로그래머 입문자들이 기본은 무시한 채 눈에 보이는 겉멋 부리기에만 열중하고 있는지 짐작할 수 있다. '기본'을 다루는 책은 팔리지 않기 때문에 서점에 안나오지는 모르겠지만, 최소한 이 글에서 만이라도 기본기를 다뤄 보도록 하자. 이번호에는 컴퓨터 내부에서 다루는 문자열 처리에 대해 살펴보고, 이와 관련된 함수들을 정리해 보겠다.

 

한 바이트 문자 집합

컴퓨터를 처음 배울 때는 영문자를 표현하는 아스키(ASCII) 코드라는 것을 배운다(지난호에서 이를 ISO/IEC 646이라고 하고, 이것의 8비트 확장인 ISO/IEC 2022도 사용되고 있다고 했다). 그러면 아스키가 컴퓨터에서 표현된 맨 처음 문자 코드인가하면 그렇지는 않다. IBM 대형 컴퓨터에서 사용했던 EBCDIC 코드도 지금까지 사용되고 있고, 7비트인 아스키 코드를 기반으로 8비트 확장을 통한 그래픽 문자를 정의해 사용한 적도 있다. 그리고 이렇게 만들어진 문자에 번호를 붙인 것을 문자 집합(character set)이라고 한다. 7비트였던 아스키는 0x20부터 0x7E까지 정의돼 있으며, 8비트인 한 바이트 내에서 다른 영역(0x80 이후의 값)은 컴퓨터 제조회사마다 달라졌다. 이를 OEM 문자 집합(Original equipment manufacturer character set)이라고 하며, 흔히 MS 도스에서 사용하는 문자 집합을 말한다.

하지만 윈도우에서 문자를 출력할 때 사용하는 문자 집합은 OEM 문자 집합이 아닌, ANSI 문자 집합이다. 따라서 아스키 128 이후의 값은 OEM 문자 집합과는 다를 수밖에 없다. OEM 문자 집합은 각 국가마다 자신들의 언어 특성을 반영해 변형시켰는데, 이렇게 변형된 문자 집합을 일컬어 코드 페이지라고 한다. 미국에서 일반적으로 사용되는 OEM 코드 페이지 값은 437이며, 우리나라는 949이다. 도스 창에서 hcode /e chcp 437이란 명령을 주면 영문 코드 페이지를 사용하겠다는 것이고, hcode /k chcp 949로 주면 한글 코드 페이지를 사용하겠다는 의미이다. <화면 1>은 보조 프로그램 중 글꼴이 표현할 수 있는 글자들을 보여주는 '문자표'라는 프로그램으로, Times New Roman 글꼴을 보여주고 있다.

코드페이지 437 ANSI 문자 집합에서 한글 글꼴을 선택하면 128번부터 255사이의 글자들이 나타나지 않는다. 이는 이 사이의 글자들이 DBCS(Double-byte Character Set)에서 사용되기 때문인데, 영문 프로그램 중에서 나 과 같은 128번 이후의 글자를 이용해서 문서를 만든 경우 화면에 이들 글자 대신 '', ''과 같은 이상한 글자들이 나타나는 것이다. 이것은 코드 페이지를 혼용해 사용하는 것을 윈도우의 출력 시스템이 영문만으로 이뤄진 문자열임을 파악하지 못하고 한글을 처리하듯 했기 때문인데, 이 부분은 쉽게 고쳐지지 않을 것으로 보인다.

다시 OEM ANSI 문자 집합 얘기로 돌아가면 코드 윈도우의 파일 시스템인 FAT16은 파일 이름을 OEM 문자 집합을 사용하며, 윈도우는 ANSI 문자 집합을 사용하기 때문에 윈도우 프로그램을 만드는 경우 문자 변환을 위해 윈도우 API에서는 두 개 코드 간의 변환을 위한 함수를 마련해 놓고 있다. 이 함수들이 Win32 API로 넘어오면서 다른 이름으로 바뀌고 예전의 이름들은 단지 호환을 위해서 제공되고 있을 뿐이다. < 1>을 보면 Ansi라는 접두어가 Char로 바뀐 것을 볼 수 있는데, 이는 Char로 시작하는 함수가 Ansi 문자 집합만을 의미하는 것이 아니라 컴파일 옵션으로 _UNICODE를 정의하면 유니코드 문자 집합을 의미하기 때문에 Ansi라는 접두어를 표기한 것이다.

<표 1> OEM과 ANSI 코드 간의 변환을 위한 함수
Win16 Win32
AnsiLower CharLower
AnsiLowerBuff CharLowerBuff
AnsiNext CharNext, CharNextExA
AnsiPrev CharPrev, CharPrevExA
AnsiToOem CharToOem
AnsiToOemBuff CharToOemBuff
AnsiUpper CharUpper
AnsiUpperBuff CharUpperBuff
OemToAnsi OemToChar
OemToAnsiBuff OemToCharBuff

 

< 1>의 함수 중 CharNext CharPrev와 같은 함수들은 문자열 안에서 포인터가 가리키는 문자로부터 한 글자 앞이나 뒤로 이동하는 경우에 사용되는데, 현재 윈도우의 코드 페이지가 DBCS 코드를 다룰 수 있다면 단순히 한 바이트 앞이나 뒤로 이동하지 않고 글자의 코드를 판단해 두 바이트 앞이나 뒤로 이동한다. 명확하게 코드 페이지를 지정하고자 한다면 CharNextExA CharPrevExA를 사용할 수 있을 것이다.

현재 시스템이 사용하고 있는 코드 페이지를 알아내는 함수는 GetACP이고, 현재 시스템의 OEM 코드 페이지를 알아내는 함수는 GetOEMCP이다. 함수에서 돌아오는 값에 대한 정보는 도움말을 찾아보면 알 수 있고, 현재 사용하고 있는 코드의 최대 바이트 수를 알려면 GetCPInfo 함수를 사용해야 한다. 유니코드를 사용하거나 DBCS 코드 시스템이라면 한 글자가 나타낼 수 있는 최대 바이트 수는 2를 가리키며, 그렇지 않다면 1을 가리킬 것이다.

<예제 1>은 간단하게 이들 함수의 사용 예를 보인 것이다. 컴파일하는 방법은 도스 창을 열고 이 소스가 있는 곳에서 다음과 같이 하면 getcp.exe 실행 파일이 만들어진다.

cl getcp.c

물론 도스 창에서 컴파일할 수 있게 컴파일러와 환경 변수가 설정돼 있어야 할 것이다.

<예제 1> getcp.c

#include <windows.h>

 

int main(void)

{

   CPINFO cpInfo;

   int i;

   BYTE* q;

 

   printf("Current Ansi Code Page is %d\n", GetACP());

   printf("Current OEM Code Page is %d\n", GetOEMCP());

 

   if (GetCPInfo(CP_ACP, &cpInfo)) {

      printf("MaxCharSize is %d\n", cpInfo.MaxCharSize);

      printf("The size of DefaultChar is %d,

         Char is ", MAX_DEFAULTCHAR);

      for (i = 0; i < MAX_DEFAULTCHAR; i++)

         printf("%c", cpInfo.DefaultChar[i]);

      printf("\n");

      printf("The size of LeadByte is %d:\n",

         MAX_LEADBYTES);

      printf("Byte : ");

      q = cpInfo.LeadByte;

      for (i = 0; i < MAX_LEADBYTES; i++)

         printf("%0x ", *q++);

      printf("\n");

   }

   return 0;

}

 

[실행 결과]

 

c:\docs\techcolumn>getcp

Current Ansi Code Page is 949

Current OEM Code Page is 949

MaxCharSize is 2

The size of DefaultChar is 2, Char is ?

The size of LeadByte is 12:

Byte : 81 fe 0 0 0 0 0 0 0 0 0 0

두 바이트 문자 집합

DBCS는 소위 확장 8비트 문자 집합이라고 한다. 문자를 나타내는 단위가 한 바이트 혹은 두 바이트이기 때문에 일종의 ANSI 문자 집합으로 간주된다. 한자(漢字)를 사용하는 국가들은 한 바이트에서는 표현이 불가능하기 때문에 DBCS 코드 시스템을 사용하고, 한글처럼 넓은 코드 영역을 차지하는 경우에도 DBCS를 사용한다. 대표적인 국가들이 중국, 일본, 한국(줄여서 CJK라고 한다)이다. 중국이나 일본 문화에서는 한자가 아주 큰 비중을 차지하는 반면, 우리나라의 경우는 한자를 사용하고 있긴 하지만 예의 두 나라에 비해서는 사용 빈도가 떨어진다고 볼 수 있다. 이들 국가가 사용하는 한자(ideography)들은 유사성도 많지만 각기 다른 한자를 사용하고, 사용 빈도도 다르다(중국은 본토와 대만이 서로 다른 코드 시스템을 사용하고 있다). 하지만 유사한 한자들을 통합하는 것이 중요한 문제로 대두돼 그것이 유니코드에서 결실을 보았다.

그러나 유니코드는 각 국가 코드 간의 교환을 목적으로 하기 때문에 쉽게 받아들여지지 않아 국가마다 기존의 코드 체계를 계속 사용하고 있다. 이런 문제들로 인해 개발자들은 DBCS 코드 시스템을 사용하기 위한 많은 테크닉들을 익혀야 했다. 얼마 전까지만 해도 대부분의 문서들은 조합형과 완성형의 두 가지 형태가 비슷한 비율로 만들어졌지만, 요즘은 인터넷 사용자의 증가로 인해 완성형이 주로 사용되고 있다. 여기에는 윈도우 시스템에서 제공하는 DBCS가 완성형을 바탕으로 구성돼 있는 것도 큰 영향을 미쳤다(이 글에서는 네트웍을 통한 한글 문자의 교환에 대해서는 다루지 않고, 한글 코드 인코딩 방식이 다양하며 통합에 어려움을 겪고 있다는 정도만 언급하겠다).

현재 한글 윈도우에는 통합형(확장 완성형, 또는 통합 완성형이라고도 한다) DBCS가 사용되고 있다. 한글 윈도우 95에서 코드 시스템은 기존 데이터의 이용과 유니코드와의 코드 변환을 쉽게 하기 위해 기존의 완성형 2,350자 외에 11,172자에 속한 한글을 코드 영역 여기저기에 분산, 배치했다. 물론 기존의 DBCS와 빠른 변환을 위한 것이었겠지만 다른 방법도 있지 않았나 하는 아쉬움이 남는다(조합형에서 침범한다던 코드 영역을 확장 완성형은 그대로 사용하고 있다). 덕분에 코드의 배열이 사전 순서와는 완전히 별개로 이뤄져 통합형 코드를 이용한 정렬은 strcmp와 같은 함수로는 정확한 결과를 얻을 수 없기 때문에 이런 함수를 이용한 프로그램은 정상적인 동작을 기대할 수 없다. 더구나 2,350자 이외의 한글은 순서를 어떻게 잡아야할지 대책도 없는 것 같다. CompareString과 같은 함수나 _mbscoll과 같은 DBCS용 런타임 라이브러리 함수로 처리하면 된다고 말할 수도 있겠지만, 기존 프로그램을 고려했다기보다는 개발하는 측의 편의만을 앞세웠다는 것에는 변명할 여지가 없을 것이다.

이런 모든 문제는 기존의 코드가 유니코드로 변환되면 해결된다지만 유니코드로 표현하지 못하는 한글도 있다는 게 또 다른 문제로 등장한다. 혹자는 11,172자가 한글이 표현할 수 있는 글자의 전부가 아니냐고 반문할 지도 모르겠다. 물론 초성 19, 중성 21, 종성 27개의 자소로 표현할 수 있는 한글의 가지수가 총 11,172개이지만(종성이 없는 경우도 고려했다), 여기에는 초성 없이 중성과 종성으로 이뤄진 567개의 글자가 빠져 있다. 사전에서는 '- 다', '- 다'와 같이 초성이 없는 글자를 표현하는 경우가 있다. 즉 유니코드만으로는 초등학교 국어 교과서는 만들 수 없는 것이다.

여기까지는 분명 DBCS 코드와는 좀 거리가 있는 내용이다. 하지만 한글 조합에 대한 명확한 이해 없이 완성형 코드만을 고집했기에 드러나는 문제점을 그냥 접어 둘 수만은 없어 이렇게 얘기를 꺼내게 된 것이다. 혀재 한글 윈도우 95에는 Iso10646.exe라는 프로그램이 제공되는데, 이것은 이전에 MS가 통합형이라는 코드를 들고 나오면서 국내 표준의 혼란을 우려한 반응 때문에 완성형만 입력 가능하게 했지만, 이 프로그램을 사용하면 통합형 11,172자가 모두 입력 가능해진다. 결국 코드를 사용하면 기존의 완성형만 고려한 프로그램들은 정상적인 동작을 못하거나 잘못된 결과를 나타낼 것이다. 통합형에 대한 여러 가지 대처 방법이 있겠지만 현재로서는 사용자가 완성형만을 입력하기를 바랄 수 밖에 없을 것이다(<화면 2>). 통합형은 유니코드와 완성형의 교량 역할을 하는 코드로서는 분명 적절한 선택이지만 그 파급 효과에 대해서는 무책임했다고 할 것이다.

다시 본론으로 돌아가자면 DBCS 코드는 첫번째 바이트의 영역에 따라 문자가 한 바이트 코드인지 두 바이트 코드인지를 결정한다. 보통은 0x80보다 크면 두 바이트로 문자를 처리한다. 따라서 문자열을 다룰 때 현재 위치의 바이트만을 보고는 이 글자가 두 바이트로 표현된 문자의 첫째 바이트인지, 둘째 바이트인지 알 수 없다. 윈도우에서는 이것을 검사하는 함수를 제공하는데, 현재 바이트가 첫째 바이트인지를 알아내는 함수가 IsDBCSLeadByte. 하지만 이 방법은 현재 코드 페이지에서 두 바이트를 갖는 문자의 첫째 바이트의 범위(완성형의 경우 0x81부터 0xFE까지)를 검사해 알아내는 것이고, 문자열 중간에서 검사하는 방법은 런타임 라이브러리 안에 있는 함수를 사용해야 한다. 문자열 안에서 첫번째 바이트(Lead Byte)를 검사하는 방법은 문자열의 처음부터 검사해서 현재 가리키고 있는 문자가 DBCS 코드의 첫째 바이트(lead)인지 둘째 바이트(trail)인지를 판별하는 방법이다. 비주얼 C++에서 제공하는 런타임 라이브러리 중에 _ismbslead, _ismbstrail이라는 함수가 있는데, 이들은 문자열의 시작 포인터도 같이 받는다. 즉 문자열 중간에서 단순히 문자만을 비교하면 정확한 값을 얻을 수 없는 것이다.

<예제 2> ismbslead ismbstrail 함수 사용 예

#include <windows.h>

 

int main(void)

{

   char str[]= "abc 1.대한민국 2.완성형 3.조합형 4.통합형 efg";

   char* p = str;

 

   printf("str : %s\n", str);

   printf("byte: ");

   while (*p) {

      if (_ismbblead(*p))

         printf("^");

      else if (_ismbbtrail(*p))

         printf(".");

      else

         printf(" ");

      p++;

   }

   printf("\nstr : ");

   p = str;

   while (*p) {

      if (_ismbslead(str, p))

         printf("|");

      else if (_ismbstrail(str, p))

         printf(".");

      else

         printf(" ");

      p++;

   }

   printf("\n");

   return 0;

}

 

[실행 결과]

c:\docs\techcolumn>islead

str : abc 1.대한민국 2.완성형 3.조합형 4.통합형 efg

byte: ...^^   ^^^^^^^^   ^^^^^^   ^^^^^^   ^^^^^^ ...

str :    |.   |.|.|.|.   |.|.|.   |.|.|.   |.|.|.

결과는 ismbslead이나 ismbstrail를 쓰지 않으면 문자열에서 첫째 바이트인지 둘째 바이트인지 정확하게 판단할 수 없다는 것을 보여준다. ismbblead IsDBCSLeadByte는 동일한 결과를 보인다. 문자열 안에서 정확하지 않지만 이 함수들은 각기 다른 상황에서 유용하게 쓰인다.

그밖에 DBCS 코드를 위한 런타임 함수들이 있다. 이 함수들을 사용하려면 windows.h include하기 전에 #define _MBCS라고 하든지 컴파일 옵션으로 /D _MBCS라고 준다. locale.h include하고 main이 시작한 후 관련 함수들을 사용하기 전에 다음과 같이 지정해 주어야 한다.

setlocale(LC_ALL, "kor");

뒤의 문자열은 각 국가별로 다르므로 유의하기 바란다. 이 명령이 없으면 디폴트로 영문자를 처리하기 때문에 정상적인 동작을 하지 않는다.

 

<표 2> DBCS 문자열에 유용한 함수들
_mbslen DBCS 문자열의 글자 수를 세어 준다. 바이트 수가 아님에 주의!
_mbstrlen DBCS 문자열의 글자 수를 세어 준다. 각 문자가 범위 안에 있는지 검사한다.
_mbsinc DBCS 문자열에서 다음 문자로 이동한다. DBCS 문자를 처리해 준다.
_mbsdec DBCS 문자열에서 이전 문자로 이동한다. DBCS 문자를 처리해 준다.
_mbscoll DBCS 문자열끼리 사전 순서로 비교한다.
mbstowcs DBCS 문자열을 유니코드로 변환한다(MultiByteToWideChar 참조).
wcstombs 유니코드 문자열을 DBCS 문자열로 변환한다(WideCharToMultiByte 참조).

 

DBCS 코드를 다룰 때 주의할 점은 0x80이 넘어가는 문자를 담고 있는 변수가 비교나 다른 연산을 하는 경우 0보다 작은 값으로 취급돼 에러가 발생할 수 있다는 것이다. 이 때는 변수를 unsigned char로 선언해 쓰든가 연산할 때 캐스팅하는 방법을 쓰면 안전하다. 좀더 강력한 방법으로 컴파일 옵션에서 /J 옵션을 주면, 디폴트로 컴파일할 때 char unsigned char로 간주된다.

그 밖의 주의사항으로 문자열에서 DBCS 문자가 취급되는 바이트의 배열 순서는 빅 엔디안(Big endian) 방식으로, 두 바이트로 코드를 표시할 때 상위 바이트가 문자열에서 먼저 나타나기 때문에 다음과 같은 코드는 상위 바이트와 하위 바이트가 반대로 돼서 들어간다. 실수하기 쉬운 것이므로 문자열 이외의 곳에서는 바이트 순서를 뒤바꿔 관리하는 경우가 없도록 주의하는 것이 에러를 막는 방법이다.

char* p;

unsigned short code;

....

if (IsDBCSLeadByte(*p))

   code = *(unsigned short*)p;

OEM 코드 페이지 중 한글 완성형은 949이고, 한글 조합형은 1361이다. 완성형을 조합형으로 변환하거나 조합형을 완성형으로 변환하려면 유니코드로 일단 변환시킨 후에 다시 다른 코드로 변환해야 한다. 이때 사용하는 함수가 MultiByteToWideChar WideCharToMultiByte이다. 이 함수의 시작 파라미터로 코드 페이지를 줄 때 1361이나 949를 주면 해당 DBCS를 조합형이나 완성형으로 인식하고 변환한다.

 

유니코드

윈도우 프로그램 작성 시에 유니코드를 사용할 것을 권장하는 가장 큰 목적은 소프트웨어의 각 나라별 지역화(localization)가 쉽기 때문이다. 또 여러 말로 쓰인 텍스트를 처리하기 쉽기 때문인데, 이는 영어권 국가의 프로그래머들이 일본과 같이 DBCS 코드 시스템을 사용하는 프로그램에 따로 노력을 들이지 않고 프로그램을 만들 수 있도록 하는데 실제 목적이 있다고도 하겠다. 이것은 국내 프로그래머들에게는 상대적으로 좋은 점이 없다고도 할 수 있겠지만, 일본이나 중국과 같은 곳에 프로그램을 수출할 때는 많은 도움이 될 것이다. 하지만 현실은 국내의 한글 처리조차 유니코드로 처리되지 않고 있다. 이는 대부분의 문서가 완성형으로 만들어져 관리되고 있고, 또 대량의 문서를 만들어 내는 프로그램들이 유니코드로 데이터를 처리하고 있지 않기 때문이다.

지난 번에도 언급한 적이 있지만 한글 윈도우 95는 유니코드를 지원하는 함수가 기본적인 것밖에 없기 때문에 유니코드를 처리하려면 프로그램 내에서 윈도우 API를 사용하지 않고 wcs*로 시작하는 런타임 라이브러리를 사용하는 것이 좋다. NT 95에서 동시에 동작하는 바이너리 파일을 만든다면 프로그램 안에서 문자열을 다루는 API 함수를 부르는 경우에는 반드시 ANSI API를 부르도록 해야 한다. 이렇게 하면 NT에서는 속도가 느려지겠지만, 95에서 안정적인 동작을 보장하기 위해서는 어쩔 수가 없다. 따라서 MFC를 사용해 프로그래밍하는 경우에도 NT에서만 동작하려고 하는 것이 아니라면 컴파일 옵션으로 _UNICODE를 주어서는 안된다. MFC도 이 옵션에 따라서 호출되는 함수가 유니코드용과 ANSI용으로 바뀐다.

한 소스로 유니코드용과 ANSI용으로 컴파일하려 한다면 TCHAR 사용을 권장하지만, 유니코드용과 ANSI용 함수를 섞어 부른다면 TCHAR와 혼동되지 않게 주의해야 한다. 유니코드 문자열을 다루는 것은 2바이트 정수 배열을 다루는 것과 동일하므로 포인터 연산을 하는 경우 바이트 문자열을 다루는 것에 주의해야 한다. 다음은 간단한 코딩 예이다.

LPTSTR psEnd, psStart, pText;

....

nCount = psEnd - psStart;          // 두 포인터 사이의

      // 문자 개수

nLen = (psEnd - psStart) * sizeof(TCHAR); // 두 포인터 사이의

      // 바이트 수

....

chCur = *pText;   // 한 글자를 chCur

    // 대입하고 다음 글자로 포인터 이동

pText = CharNext(pText);

 

앞의 코드에서 CharNext 대신 pText++와 같은 명령을 사용할 수 있겠지만, DBCS의 경우에는 쓸 수 없으므로 CharNext _tcsinc 함수를 쓰면 _wcsinc _mbsinc, _strinc로 맵핑돼 쓸 수 있다.

단순히 이런 문자열 처리 이외에도 유니코드를 사용하기 전에 주의해야 할 것이 있다. MFC를 사용하면 실수가 적겠지만 API를 직접 호출하는 경우라면 문자열을 넘기는 부분이나 메시지에서 받는 문자열들은 유니코드 옵션을 주고 컴파일했는지 아닌지에 따라서 유니코드일 수도 있고 ANSI 코드일 수도 있다. 참고로 해당 윈도우가 유니코드 문자열로 메시지를 처리하는지를 판단하는 함수는 IsWindowUnicode이다. 윈도우 프로시저를 서브 클래싱하는 경우에 있어서 유니코드를 처리하는 프로그램이 ANSI로 처리하는 윈도우의 프로시저를 서브 클래싱하거나 그 반대의 경우에도 자동으로 메시지 변환이 이뤄지므로 서브 클래싱할 때 어떤 방식으로 했는지만 주의한다면 문제가 없을 것이다. SetWindowLongW로 서브 클래싱을 하면 유니코드로 처리하겠다는 것이고, SetWindowLongA로 서브 클래싱을 하면 ANSI로 프로시저를 처리하겠다는 것을 말한다.

이밖에도 유니코드 관련 함수들로는 유럽의 발음 기호들을 유니코드 내의 코드로 변환시켜 주는 FoldString이나 일어에서 히라가나나 가타카나 간의 변환을 해주는 LCMapString과 같은 복잡한 기능을 하는 변환 함수도 있다. 좀더 자세한 것을 알고 싶으면 NLSAPI(National Language Support API) 스펙 4.5 MSDN 안에 있으므로 잘 읽어보기 바란다. MS는 자사 제품이 세계적으로 널리 사용되기를 원하기 때문에 각 국가 언어에 대한 지원을 무척 잘하고 있다. 체계적인 정리와 충실한 구현까지 웬만한 함수들은 런타임 라이브러리 안에 모두 들어있다. 대부분은 일본어에 대한 지원이라 아쉽지만 그 덕택에 한국어와 중국어가 쉽게 지원되는 것은 다행이라 인정해야 할 것이다.

유니코드가 분명 현재 전세계적으로 사용하는 문자의 대부분을 표현할 수 있는 것은 사실이지만, 각 국가의 모든 요구 조건을 충족시키지는 못한다. 여전히 각 국가별로 사용되는 문자 집합들은 유니코드와 병행해 사용되는 것이 있으리라 생각한다. 또한 옛 한글이나 옛 한자와 같이 현재 사용하지는 않지만 문서 처리상 필요하다면 유니코드를 바탕으로 한 변형 유니코드 등을 생각할 수 있을 것이다. 하지만 여전히 유니코드와의 호환 문제는 남아있고, 한 글자의 크기가 16비트밖에 안된다는 것이 제한점으로 남아 있다. 이런 경우는 결국 한 문자의 크기를 32비트로 표현할 수밖에 없는데 이것에 대한 얘기는 다음으로 미루기로 하겠다.

 

IME

앞서 얘기한 것은 한 나라에서 사용하는 글자를 컴퓨터의 메모리에서 어떻게 표현하고 다루는가에 대한 것이었다. 지금부터 얘기할 것은 글자를 입력하는 방법과 화면이나 종이에 출력하는 방법이다. 여기서는 글자를 입력하는 방법에 대해서 간단히 살펴보려고 한다.

MS는 응용 프로그램의 변경을 최소화하면서 세계 각국의 나라에서 자신들의 글자를 쉽게 입력할 수 있는 방법을 제공하려고 했다. 영어권에서 사용하는 글자들은 단순히 키보드에 있는 글자와 일대일 대응을 하기에 하나의 키를 누를 때마다 해당 키에 대응되는 문자가 눌린 것으로 판단해 간단하게 프로그램에서 처리할 수 있다. 하지만 발음 기호와 영문자가 조합되는 유럽권 글자나 자소가 결합하는 한글, 이루 셀 수 없이 다양한 입력 방식을 가진 한자 등은 키보드와 일대일로 대응할 수 없다. 한 개의 글자가 이뤄지기 위해서는 몇 번의 키를 입력해야 하고 입력된 키를 해석해서 문자를 만들어 내야 하는 프로그램이 있어야 하기 때문이다. 이러한 프로그램들은 글자를 입력받는 응용 프로그램의 일부분으로 제공됐는데, 그 대표적인 프로그램으로 도스용 아래아 한글을 들 수 있다. 윈도우로 넘어오면서 MS는 이러한 입력 부분에서의 문제를 운영체제 차원에서 기본 기능으로 제공하면 응용 프로그램을 만드는 측에서 수월해질 것이라고 판단했고, 그 결과 IME(input method editor)라는 기능을 제공함으로써 윈도우에서의 입력 문제를 해결하려고 했다.

극동 지역(far east, 즉 한국, 중국, 대만, 일본) IME는 일본어 지원 IME를 바탕으로 해서 만들어졌다고 볼 수 있다. 일본어의 입력 방법은 다양하게 있지만 윈도우에서 제공하는 것은 발음을 영어로 입력하면 일본어 사전을 뒤져서 그 발음과 유사하거나 발음으로 시작하는 단어들을 제시(candidate)하고 선택하게 한다. 마치 한글 발음을 치고 한자 키를 눌러 한자를 선택하는 방법과 유사하지만 일본어 입력은 한 글자만 선택하는 것이 아니라 여러 단어를 입력할 수도 있다는 점에서 차이가 있다. 그렇기 때문에 일본어 IME는 응용 프로그램과 매우 밀접하게 메시지를 주고받으면서 동작한다. 또 중국어는 매우 다양한 입력 방식이 있지만 단어를 입력하기보다는 한자 한 개를 입력하는 방법이 주로 사용되며, 한글도 유사한 방식으로 입력이 가능하다. IME의 복잡도로 본다면 일본어, 중국어, 한국어의 순서가 될 것이다.

극동 지역 IME의 개발을 위해서는 일본어를 입력할 수 있는 것만 만들면 다른 것은 간단히 해결된다. 예를 들어 극동 지역 윈도우에 포함된 워드패드는 리치 에디트 컨트롤을 사용해 만든 프로그램인데, 리치 에디트 컨트롤은 극동 지역 윈도우 간의 바이너리가 동일하다. 즉 같은 프로그램으로 각국 윈도우에서 IME를 이용해 동작한다는 것이다. 예전에 베타 버전인 아웃룩 익스프레스(Outlook Express) 프로그램을 사용할 때 조합중인 글자에 점선으로 밑줄이 그어져 있고 색이 다르게 처리되는 것을 본 적이 있다. 이것은 일본어 IME에서 글자를 입력하는 방식으로, 최종 마무리가 각국별로 되기 전이었기 때문인데 이를 통해 일단 일본어 버전부터 만든다는 것을 짐작할 수 있었다. 좀 불안하기는 하지만 일본어 윈도우에 한글 IME 파일을 복사해 쓸 수도 있는 것을 보면 그 구조의 유사성이 얼마나 높은지를 알 수 있을 것이다.

일단 IME의 구조를 살펴보면 좀 답답하게 설계했다는 느낌이 든다(너무 많은 자원을 소모하는 것을 볼 수 있다). 극동 지역의 윈도우는 일단 응용 프로그램이 시작하면 IME를 사용하기 위해서 다음과 같은 5개의 팝업 윈도우를 생성한다.

① IME : 시스템에서 제공하는 표준 IME 윈도우, 다섯 개중 가장 최상 위에 있다.
② MSIME95 :
한글 입력을 위해 제공된 IME, 클래스 이름은 바뀔 수 있다
.
③ IMESTATE : IME
의 상태를 표시하는 윈도우, /, 전각/반각, 한자 상태를 표시한다
.
④ IMECOMP :
조합 중인 글자를 표시하는 윈도우, 프로그램에서 위치를 지정한다
.
⑤ IMECAND :
선택할 수 있는 한자 리스트를 표시한다.

MSIME95 IME 클래스로 만들어진 윈도우를 오너(Owner) 윈도우로 삼고, IMESTATE, IMECOMP, IMECAND 클래스로 만들어진 윈도우는 MSIME95를 오너 윈도우로 갖고 있다. IME 윈도우의 오너 윈도우는 해당 응용 프로그램에서 만든 팝업 윈도우로 지정돼 있다. 오너 윈도우에 대해 생소한 사람들을 위해 간단히 설명하면, 부모/자식 윈도우와 비슷한 관계로 팝업 윈도우를 만들 때 오너 윈도우를 지정함으로써 윈도우들의 종속 관계를 지정하는 방법이다.

앞에서 언급한 클래스로 만들어진 5개의 윈도우의 관계를 보면 제일 먼저 IME 클래스로 윈도우를 만들고(디폴트 IME라는 타이틀을 갖지만, 화면에 보이지는 않는다), 시스템에서 제공하는 클래스인 MSIME95 클래스를 이용해 윈도우를 만든다. 이 윈도우 생성 시에 IMESTATE, IMECOMP, IMECAND의 순서로 윈도우를 만들게 된다. 보통 화면에 IME로 보이는 것은 IMESTATE 클래스에서 만들어진 윈도우이다. 이것은 거의 사용하지도 않는 전각/반각 상태를 - 일본 IME 영향이라 생각한다 - 표시하고 있으며, 한글 윈도우 3.1에서는 수시로 화면에 나타나기 때문에 화면에 표시하지 않으려고 했던 프로그래머(필자라고 눈치챘겠지만)에게는 무척이나 고생스러운 윈도우였다.

응용 프로그램에서 IME를 사용하는 방법은 세 가지로 분류된다. 첫째는 IME를 고려하지 않고 만들어진 프로그램으로 대부분의 영문 프로그램들이 여기에 속한다. 이들 프로그램은 디폴트 IME 클래스가 키보드 메시지를 처리해서 영문의 경우에는 WM_CHAR 한 개를, 한글이나 한자의 경우에는 WM_CHAR 두 개를 연속으로 발생시켜 프로그램이 WM_CHAR를 처리할 때 입력되도록 한다. 경우에 따라서는 화면에 이상한 글자가 찍히면서 화면을 업데이트해야 글자가 정상으로 보인다. 외국의 유명 에디터가 이런 경우에 해당하는데 MS 제품들은 거의 이와는 다른 방법을 사용한다.

둘째는 IME를 전부 사용하기에는 너무 복잡해서 최소한의 노력만을 기울여 프로그램을 만든 경우로 IME의 기능을 일부만 지원하는(half aware) 프로그램들이다. IMECOMP 윈도우를 적당한 위치로 옮겨 놓고 WM_CHAR 대신 WM_IME_COMPOSITION이나 WM_IME_CHAR과 같은 메시지를 이용해 완성된 글자를 처리하는데 보기에는 깔끔하지 않아도 글자를 처리하는데 있어서는 문제가 발생하지 않는다.

셋째는 IME의 기능을 API를 이용해서 직접 사용하는 프로그램들(full aware) IMECOMP 윈도우 외에도 UI 윈도우를 컨트롤하며 제공되는 메시지를 제대로 처리하는 프로그램들이다. 주로 MS 제품들이나 워드프로세서들이 여기에 속한다. 해야 하는 일이 많아서 제대로 하려면 작업이 상당하다.

대부분의 프로그램들이 입력을 에디트 컨트롤에서 받아 처리하기 때문에 IME를 직접 다루지 않지만, 프로그램에서 에디트 컨트롤이 해주는 수준으로 프로그램을 만드는 것은 쉬운 일만은 아니다. 또한 예전부터 다양한 글자 입력을 위해서 디폴트 IME 이외에 다른 IME를 쉽게 만들 수 있도록 관련 자료의 공개를 요청했지만, 일본과는 달리 우리나라에서는 새로운 IME를 만들기 위한 샘플 소스 조차 공개하지 않고 있는 실정이다. 일본에서는 상품으로 팔리는 IME가 있을 정도로 다양한 입력 방식이 있다. 국내에서도 속기용 자판들이 개발돼 있지만 IME 방식으로 여러 응용 프로그램에서 공통으로 사용될 수 있다는 얘기는 들은 적이 없다. MS에서 방침이 바뀌었는지 모르겠지만 쉬운 방법은 없는 것 같다. 다음 호에서 IME를 컨트롤하는 방법에 대해 좀더 살펴보고 IME를 커스터마이즈하는 법을 찾아보도록 하겠다.

IME를 설계한 사람들을 보면 일본 사람들이 두 명, 한국 사람 한 명, 중국 사람 한 명이다. 윈도우 3.1에서 시스템에 글로벌하게 존재하던 IME가 윈도우 95로 넘어오면서 각각의 쓰레드마다 만들어지고, 매 응용 프로그램마다 5개의 윈도우를 만들면서 독립성은 유지되지만 자원의 낭비는 심해졌다. 윈도우에는 눈에 보이지 않는 프로그램들이 몇 개씩 동작하는데 이들 모두에게 5개의 윈도우가 생성돼 있다고 생각해 보기 바란다. 가뜩이나 메모리 소요량이 많은 윈도우에서 엄청난 낭비를 주도하는 주범이라고 할 만하다.

이보다 더 큰 문제는 활성 쓰레드가 변할 때, 즉 응용 프로그램이 전환될 때 IME는 자신의 상태를 새로 설정한다. 얼핏 생각하면 독립적이어서 좋다고 할지 모르겠지만, IME는 키보드와 연관돼 있으며 마우스처럼 많은 메시지를 생성시키지도 않는다. 즉 키보드는 한 개밖에 없으며 현재 프로그램을 사용하는 사람이 현재 어느 언어를 사용하는지도 확실히 알고 있다. 쓰레드 별로 처리하더라도 어차피 현재 조합중인 상태를 벗어나 강제로 완성시키기 때문에 쓰레드 별로 분리해 처리하는 것이 별 의미가 없다. 예전에 워드프로세서를 만들 때 윈도우 별로 현재 사용하는 언어를 지정할 수 있게 했는데 사용자에게 혼동만을 가져와서 곤란한 적이 있었다. 지금 새로 윈도우를 만든다면 IME 부분은 하나의 입력 디바이스에 붙어 있는 필터로서 동작하게 했으면 한다. 기존의 프로그램과의 호환성이 발목을 붙잡겠지만 IME를 하나의 독립된 프로그램으로 동작하게 하고 메시지로 통신하면 자원 공유도 높아지고 잦은 응용 프로그램 전환시 나타나는 IME의 오동작도 자연스럽게 사라질 것이라 생각한다.

 

, 포기할 수 없는 자유여!

더 이상 한글 처리가 외국 프로그램이 국내에 팔리는데 장벽이 되지 못한다는 말을 들은지 6년이 지났다. 우습게도 한글 처리는 운영체제를 개발하는 외국회사에서 주도권을 잡고 있으며, 대부분의 프로그램들은 에디트 컨트롤만 쓰면 되는 상황이 돼 버렸다. 시스템에서 제공하는 문자 코드가 아닌 다른 코드를 사용하려면 최소한 에디트 컨트롤을 만들어야 한다. 그러나 한 번이라도 에디트 컨트롤을 만들어 본 사람들은 알겠지만, 에디트 컨트롤 하나가 웬만한 에디터의 수준이기 때문에 배보다 배꼽이 더 커지는 상황이 돼 버린다. 결국 현재 시스템이 제공하는 코드나 IME를 사용해 획일화된 환경에서 사용해야 한다. 한글 윈도우에서는 기본 영문 키보드 드라이버가 아닌 다른 국가 키보드 드라이버를 사용하면 IME가 정상적으로 동작하지 않는다. 필자는 영문의 경우 쿼티(Qwerty) 자판이 아닌 드보락(Dvorak) 자판을, 한글은 3벌식을 사용한다. 수많은 키보드 레이아웃을 제공하며 다양한 선택을 가능하게 하지만 그것은 영어 문화권에서의 사치일 뿐이고, 우리에게는 선택권이 없다. 무엇이 우리에게서 자유를 빼앗아갔는지는 독자들이 스스로 생각해 볼 일이며, 마지막으로 능력있는 프로그래머들의 우리 문화 사랑을 기대해 본다.


필자 연락처 : freefish@netsgo.com, freefish@chollian.net, foolfish@hitel.net
정리 : 정주향(jhjung@infoage.co.kr)




 

AND

출처: http://blog.naver.com/sginnaw?Redirect=Log&logNo=140008490332

1 Character Sets

1.1 Single Byte & Double Byte Character Sets

보통 프로그래머들은 텍스트 문자열을 single byte character(SBCS)로 다루는데 익숙해져 있다. 예를 들어 strlen을 호출하면, 0으로 끝나기 까지 single byte character의 갯수를 반환하며 우리들은 그것을 당연하게 사용하고 있다. 이것은 문자 셋이 255자를 넘지는 않는 alphabet에는 적절할지 모르지만, 일본어나 한문, 우리나라 글자처럼 symbol이 255자를 훨씬 넘는 언어권에서는 single byte 체계로는 문자 셋을 제대로 표현할 수 없다. 이런 언어권에서는 문자를 표현하기 위해 2바이트, 즉 double byte character set(DBCS)으로 구성하게 된다. 이런 언어의 문자는 때론 1바이트, 때론 2바이트로도 나타낼 수 있기 때문에 프로그래머의 입장에서는 이런 문자를 구분하는데 상당한 애로점을 가지게 된다. 예를 들어 strlen을 호출해도 문자열 안에 얼마나 많은 문자열이 있는지 알 수 없으며 단지 문자열을 구성하는 바이트 수만 알 수 있게 된다. ANSI C run-time library는 DBCS을 다루지 않기 때문이다. 그나마 MS의 Visual C++ 런타임 라이브러리에는 _mbslen과 같이 Multy-byte(SBCS과 DBCS 둘다 혼용하는 바이트 체계) 문자열을 다루는 함수들이 포함되어 있다.

1.2 Wide Byte Character Sets (Unicode)

유니코드에서 모든 문자는 2바이트(16비트) 값을 가진다. 즉, 최대 65,000개 이상의 문자들을 표현할 수 있다는 이야기다.
현재 유니코드에는 ascii에서부터 라틴어, 아라비아어, 중국어 , 한글등이 언어부터 수학 기호, 특수 기호, 발음 기호까지 문자 셋에 포함되어 있다. 대략 65,000개의 총 코드 포인트중 절반 가량인 35,000개의 코드 셋이 할당되어 있고 나머지는 확장을 위해 남겨두었다고 한다.
65,536개의 문자들은 지역적으로 그룹을 형성해서 나뉘어져 있다. 다음의 테이블은 몇 개의 region과 문자 셋을 예로 든 것이다.

16 Bit Code Character 16 Bit Code Character
0000 - 007F ASCII table 0300 - 036F 일반적인 구분 마크들Generic diacritical marks
0080 - 00FF 라틴1문자 Latin1 characters 0400 - 04FF 키릴어Cyrillic
0100 - 017F 유럽형 라틴어European Latin 0530 - 058F 영어Armenian
0180 - 01FF 확장형 라틴어Extended Latin 0590 - 05FF 헤브류어Hebrew
0250 - 02AF 표준 음성기호Standard phonetic 0600 - 06FF 아라비아어Arabic
02B0 - 02FF 수정된 문자들Modified letters 0900 - 097F 데이버나거리Devanagari
see also [WWW]What is Unicode?

2 윈도우 운영 체제와 유니코드

* 윈도우2000 : 유니코드와 ANSI 모두 지원
* 윈도우98 : ANSI만 지원
* 윈도우CE : 유니코드만 지원

3 유니코드를 지원하는 소스 코드 만들기

유니코드를 고려해야하는 상황은 주로 문자character와 관련이 있는 코드에서다. 문자와 관련이 있는 부분은 크게 변수, 정적변수에 할당되는 문자열, 함수로 나누어질 수 있다.

3.1 변수Variables

문자열과 관련이 있는 대표적인 변수형은 char type이다. 표준 C헤더 파일인 string.h에는 유니코드 문자 변수를 위해 wchar_t를 정의하고 있다. 아래와 같이 2바이트로 정의되어 있다.
typedef unsigned short wchar_t;    // 유니코드용 문자형 

예를 들어, 문자열 버퍼를 다음과 같이 정의할 경우

wchar_t szBuffer[100]; 
이 버퍼는 총 200바이트가 할당되는 것이다. 그러나 ANSI만을 지원하는 플랫폼과의 compatibility를 위해서 wchat_t를 직접 쓰지 않고 TCHAR type을 정의하고 있다. _UNICODE를 정의해서 코드를 유니코드 지원용으로 컴파일 하고자 한다면 TCHAR는 다음과 같이 선언 된다.
typedef wchat_t TCHAR;   // 유니코드 모드로 컴파일할 경우 
그러나 ANSI만을 지원하도록 컴파일하고자 한다면 TCHAR는 다음과 같이 선언된다.
typedef char TCHAR;     // ANSI 모드로 컴파일할 경우 

즉, 유저는 문자열 버퍼를 정의하고자 한다면 다음과 같이 쓰면 되는 것이다.

TCHAR szBuffer[100]; 

문자형 변수뿐 아니라 상수형 문자열 포인터 변수에서도 유니코드를 고려해야 한다. 만약 코드내에서 LPCSTR(const char*)나 LPSTR(char*)와 같은 문자열 포인터 변수를 쓴다면 이 코드는 오직 ANSI 체계만 지원할 수 있게 된다. 이때는 LPCSTR, LPSTR 대신에 각각 LPCTSTR, LPTSTR 형으로 쓰는 것이 유니코드를 대비한 코딩이 될 수 있다.

결론은, 문자형 및 문자열 포인터는 다음의 형으로 쓰도록 한다.

type ANSI UNICODE Generic
character char wchar_t TCHAR
character pointer PSTR, 0LPSTR PWSTR, LPWSTR PTSTR, LPTSTR
constant character pointer PCSTR, LPCSTR PCWSTR, LPCWSTR PCTSTR, LPCTSTR

3.2 문자열text strings

이것은 정적영역의 문자열을 포인팅할 때도 마찬가지이다.
TCHAR *szError = "Error"; 
이와같이 썼을 때, MS C++ 컴파일러는 모든 static 문자열은 유니코드가 아닌 ANSI로 인식하기 때문에 문제가 발생한다. 즉, 문자열 "Error"는 무조건 5바이트로 인식하게 되는 것이다. 이때 static 문자열도 유니코드로 컴파일하고자 한다면 다음과 같이 선언해야 한다.
TCHAR *szError = L"Error"; 
문자열 앞의 L은 문자열을 유니코드로 컴파일하라는 것을 지시한다. 즉, L 지시자 다음의 "Error" 문자열은 10바이트로 컴파일 되는 것이다. 그러나 만약 코드를 ANSI로 컴파일한다면 위의 코드는 문제를 발생시키게 된다. TCHAR처럼, static 문자열도 유니코드/ANSI 모두 자유롭게 컴파일이 가능하도록 하려면 static 문자열 앞에 L 대신에 _TEXT 매크로를 써야한다.
TCHAR *szError = _TEXT("Error"); 
_TEXT는 다음과 같이 literal charater에서도 쓸 수 있다.
if (szError[0] == _TEXT('j')) { 
        .... 
} 

참고로 _TEXT 매크로는 UNICODE로 컴파일 할 때는 다음과 같이 정의 된다.

#define _TEXT(x) L##x 
만약 ANSI로 컴파일 될 때는,
#define _TEXT(x) x 

_TEXT 매크로는 win32 API 환경에서 쓰는 매크로이며 만약 MFC 환경에서 컴파일 한다면 _TEXT 매크로와 더블어 _T 매크로도 쓸 수 있다.

TCHAR *szError = _T("Error");   // MFC 환경이라면... 

문자열string

Win32 API MFC
_TEXT("abc") _TEXT("abc"), _T("abc")

3.3 함수functiions

3.3.1 문자열 함수string functions

위에서 설명하였듯이 strcpy, strchr, strcat와 같은 표준 C런타임 문자열 함수는 오직 ANSI만을 지원한다. 그래서 ANSI C에서는 유니코드 문자열을 다루기 위해 유니코드 문자열 함수를 정의하였다. 다음의 예가 ANSI C 스트링 함수와 그에 대응하는 유니코드 스트링 함수를 나타내고 있다.
char *strcat(char*, const char*); 
wchar_t *wcscat(wchar_t*, const wchar_t*); 
 
char* strchar(const char*, int); 
wchar_t* wcschar(const wchar_t*, wchar_t); 
 
char* strcpy(char*, const char*); 
wchar_t wcscpy(wchar_t*, const wchar_t*); 
 
size_t strlen(const char*); 
size_t wcslen(const wchar_t*); 
보다시피 유니코드 스트링 함수는 ANSI C 스트링 함수에 앞에 str을 빼고 대신 wcs라는 접두어를 붙인 것이다. wcs는 wide character string의 약자이다.
그러나 위의 함수들을 쓰려면 같은 동작을 수행하는 모듈에서 ANSI용과 유니코드용 코드를 따로 작성해야 할 것이다. 변수와 문자열과 마찬가지로 ANSI와 유니코드 모두를 한 코드에서 컴파일이 가능하도록 하려면 str이나 wcs가 들어가는 자리에 _tcs를 붙이면 된다.
TCHAR* _tcscat(TCHAR*, const TCHAR*); 
TCHAR* _tcschar(const TCHAR*, TCHAR); 
TCHAR* _tcscpy(TCHAR*, const TCHAR*); 
size_t _tcslen(const TCHAR*); 
     .......... 

3.3.2 윈도우 함수windows API

MS의 윈도우 API는 유니코드용으로 만들어져 있기 때문에 유저는 윈도우 API를 이용할 때 특별히 유니코드를 염두에 둘 필요는 없다.
참고로 윈도우 API에서 끝에 대문자 W가 붙는 것은 Wide를 나타내며 유니코드 스트링을 위한 버전이고 A가 붙는 것은 ANSI 스트링을 받는 함수이다. 윈도우 라이브러리 내부에는 다음과 같이 코딩이 되어 있다.
#ifdef UNICODE 
#define CreateWindowEx CreateWindowExW 
#else 
#define CreateWindowEx CreateWindowExA 
유저는 단지 CreateWindowEx 만 호출해주면 유니코드용은 CreateWindowExW로, ANSI용은 CreateWindowExA로 실행이 된다. (좀더 정학히 말하면, 윈도우2000에서는 CreateWindowEx를 호출하면 CreateWindowExA가 실행이 되고 그 안에서 CreateWindowExW가 호출이 되는 구조를 가지고 있다. CreateWindowExA는 일종의 레이어 함수로 스트링 변환을 해서 유니코드 함수를 호출하고 리턴 될 때 ANSI 스트링에 맞게 메모리를 해제해서 리턴된다. 자세한 설명은 참고도서를 참조하도록 한다.)

4 UNICODE 를 고려해서 코딩하는 법

* ANSI와 유니코드 모두 컴파일 될 수 있는 generic data type을 사용한다. (예: TCHAR, PTSTR, LPCTSTR, LPTSTR)
* 바이트, 바이트 포인터, 데이타 버퍼를 이용할 땐 BYTE, PBYTE와 같은 explicit data type을 사용한다.
* literal 문자와 스트링에 _TEXT, _T 와 같은 매크로를 사용한다.
* 문자를 바이트로 인식하는 오류를 범하지 않는다. 예를 들어 문자열 버퍼에 있는 문자수를 계산할 때 sizeof(szBuf)와 같이 쓰면 안되고, 'sizeof(szBuf)/sizeof(TCHAR)와 같이 써야 한다. 그리고 메모리를 문자 수대로 할당하고자 할 때 malloc(nCharacter)'가 아니고 malloc(nCharacter*sizeof(TCHAR))로 호출해야 한다.

5 유니코드 스트링 조작 함수

윈도우에서는 다음과 같은 유니코드 문자열을 다루는 함수를 제공하고 있다.

함수명 기능
lstrcat 스트링을 다른 스트링의 끝에 연결
lstrcmp 대소문자를 구별해서 두개의 스트링을 비교
lstrcmpi 대소문자를 구별하지 않고 스트링을 비교
lstrcpy 두 개의 스트링을 카피
lstrlen 스트링의 길이를 리턴
위 함수들은 UNICODE 매크로가 정의되어 있다면 유니코드 버전으로, 그렇지 않다면 ANSI 버전으로 실행된다. 예를 들면 lstrcat는 유니코드로 컴파일 된다면 lstrcatW를 호출하고 ANIS 모드로 컴파일 된다면 lstrcatA를 호출하게 되는 것이다.
참고로 언어별 스트링을 비교하는 함수로 다음과 같은 API가 제공되고 있다.
int CompareString(LCID lcid, DWORD fdwStyle, PCWSTR pString1, int cch1, PCTSTR pString2, int cch2); 
이때 lcid는 지역ID를 나타낸다. 나머지는 MSDN을 참고하면 된다.

마지막으로 printf 함수는 소스를 _UNICODE 매크로를 정의하여 컴파일하면 printf에 전달되는 모든 문자와 스트링은 유니코드로 표현되고 _UNICODE 매크로를 정의하지 않고 컴파일하면 파라미터의 스트링은 ANSI로 인식하게 된다.
그러나 formatted data를 다루는 sprintf계열의 함수는 유니코드와 ANSI용이 분리되어 있다.

ANSI UNICODE Generic Window API
sprintf swprintf _stprintf wsprintf
역시 ANSI, 유니코드 모두 generic하게 사용하기 위해선 _stprintf 함수를 쓰면 되고 윈도우API인 wsprintf는 유저가 유니코드나 ANSI를 고려할 필요가 없다.
각각의 함수가 쓰이는 예제는 다음과 같다.
char szA[100];    // ANSI 스트링 버퍼 
char szW[100];   // 유니코드 스트링 버퍼 
 
sprintf(szA, "%s", "ANSI string");    // 일반적인 ANSI 스트링을 표현할 때 
 
sprintf(szA, "%s", L"Unicode string");   // 유니코드 문자열을 ANSI로 변환 
 
swprintf(szW, L"%s", L"Unicode string");  // 유니코드 스트링을 표현할 때 
 
swprintf(szW, L"%s", "ANSI string");   // ANIS 스트링을 유니코드 스트링으로 변환 

6 유니코드와 ANSI의 구별

스트링 버퍼에 있는 문자들이 유니코드인지 구별하기 위해서 다음의 함수가 제공된다.
DWORD IsTextUnicode(CONST PVOID pvBuffer, int cb, PINT pResult); 
주위할 점은 위 함수는 버퍼의 내용을 통계적으로 구분할 뿐이라는 것이다. 따라서 그 결과가 정확하지 않을 수 있다. 자세한 설명은 MSDN을 참고한다. 

[출처] UNICODE|작성자 별명엄따


AND

IME 개요

키보드 궁금하니? 2008. 12. 4. 19:52
출처: http://www.redwiki.net/wiki/wiki.php/IME%20%B0%B3%BF%E4



IME 개요

  • MSDN에서 퍼왔습니다.

Contents

[-]
1 IME에 대하여
1.1 상태(Status), 조합(Composition), 그리고 후보글자(Candidate) 윈도우
1.2 IME Window Class
1.3 IME Messages

1 IME에 대하여 #

The input method editor relieves users of the need to remember all possible character values. Instead, the IME monitors the user's keystrokes, anticipates the characters the user may want, and presents a list of candidate characters from which to choose.

By default, the IME provides an IME window through which users enter keystrokes and view and select candidates. Applications can use the input method manager (IMM) functions and messages to create and manage their own IME windows, providing a custom interface while using the conversion capabilities of the IME.

IMM은 단지 아시아권(중국,일본,한국)에 로컬라이징된 윈도우즈버전에서만 사용가능합니다. 이들 시스템에서는 IMM이 사용가능한지 여부를 GetSystemMetrics 메소드에 SM_DBCSENABLED 옵션을 걸어 호출하여 확인해볼 수 있습니다. 윈도우즈 2000에는 모든 기능을 갖춘 IME 기능지원을 모든 지역 언어 윈도우버전에 지원하지만, IMM은 아시아 언어팩을 설치할경우에만 사용가능하다는 점에 주의하십시요. IME사용가능한 어플리케이션인지 여부는 GetSystemMetrics 메소드에 SM_IMMENABLED 옵션을 걸어 호출하여 확인해볼 수 있습니다.

1.1 상태(Status), 조합(Composition), 그리고 후보글자(Candidate) 윈도우 #

IME을 위한 사용자 인터페이스는 status, composition, candidate 윈도우로 구성되어있습니다. The status window indicates that the IME is open and provides the user the means to set the conversion modes. The composition window appears when the user enters text and, depending on the conversion mode, either displays the text as entered or displays converted text. The candidates window appears in conjunction with the composition window. It contains a list of "candidates" (alternative characters) for the selected character or characters in the composition window. The user can scroll through the candidates list and select the desired characters, then return to the composition window. The user can compose the desired text in this way until the composition string is finalized and the window is closed. The IME sends the composed characters to the application in the form of WM_IME_CHAR or WM_IME_COMPOSITION/GCS_RESULT messages. If the application does not process these messages, the DefWindowProc function translates them into one or more WM_CHAR messages.

By default, the system automatically creates and manages status, composition, and candidates windows for all windows that require text input. For many applications, this default processing is sufficient. These applications rely entirely on the system for IME support and are said to be IME-unaware because they are unaware of the many tasks the system carries out to manage the IME windows.

An IME-aware application, on the other hand, participates in the creation and management of IME windows. Such applications control the operation, position, and appearance of the default windows by sending messages to and by intercepting and processing messages intended for these windows. In some cases, applications create their own IME windows and provide complete processing for their custom status, composition and candidates windows.

1.2 IME Window Class #

The "IME" window class is a predefined system global class that defines the appearance and behavior of the standard IME windows. The class is similar to common control classes in that you create a window of this class by using the CreateWindowEx function. Like static controls, an IME window does not respond to user input by itself. Instead, it notifies the IME of user input actions and processes control messages sent to it by the IME or applications to carry out a response to the user action.

IME-aware applications sometimes create their own IME windows using the IME class. This allows the application to take advantage of the default processing of the IME window while having control of the positioning of the window.

1.3 IME Messages #

The system sends IME window messages to the window procedure of the application when certain events occur that affect the IME windows. For example, the system sends the WM_IME_SETCONTEXT message to the application when a window is activated. IME-unaware application pass these messages to the DefWindowProc function which sends them to the corresponding default IME window. IME-aware applications either process these messages or forward them to their own IME windows.

You can direct an IME window to carry out a command, such as change the position of composition window, by using the WM_IME_CONTROL message. The IME notifies the application about changes to the composition string by using the WM_IME_COMPOSITION message and about general changes to the status of the IME windows by sending the WM_IME_NOTIFY message.


AND

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

출처: http://blog.naver.com/indra82y.do?Redirect=Log&logNo=140006589082


베트남어 폰트 다운로드 및 사용방법
베트남어 폰트

2004/10/11 22:24

복사 http://blog.naver.com/indra82y/140006589082

첨부파일 (2)

<<new>>
첨부파일 첫번째와 두번째는 동일한 내용이므로 하나만 다운받아서 설치하시면 됩니다되도록이면 첫번째 파일을 받으시길 권장합니다. 첫번째 파일을 다운 받아서 압축을 푸신 다음 확장자가 exe인 파일을 더블 클릭하시면 자동으로 설치가 완료 됩니다. 그 이후는 아래의 사항과 동일합니다. 사족으로 말씀드리자면... 이번에 노트북을 포맷했는데 제 컴에서 안깨지고 잘 돌아가는 녀석(?)들을 압축해서 백업시켜뒀거든요. 그게 첫번째 파일입니다. 다른 컴퓨터에서 재시험해보니 역시 폰트가 안깨지네요. 참고로, 압축을 풀고 실행한 폴더를 다른 곳으로 옮겨도 베트남어가 깨어져 나오는 기이한 현상이 종종 일어나니... 폰트가 있는 폴더를 옮기지 않는 것이 좋을 듯 싶습니다.  

 

 

 

<<주의사항 >>

 

최근 제가 다른 컴에서 다운을 받아보니 폰트가 깨어져서 나오거나, 기본 설정이 다른 경우가 있더군요. <다운받아서 설정했는데도 베트남어가 안써지는 경우>: 아래에 첨부해 둔 그림과 동일하게 설정하시면 됩니다. 제가 표시해 놓은 빨간색 화살표만 보지 마시고, 기타 체크박스에도 그림과 똑같이 체크해주세요! (특히, 폰트가 깨어져서 나올 때!) 이 때, 설정해야 할 탭은 동일합니다.  

 

 

  ★              ★                   ★       ★                                      ★     

 

 

1. 우측 상단에 첨부된 파일을 다운 받습니다.

(vietkey가 회원제로 바뀌었더군요. 그래서 인스톨 파일을 첨부시켰습니다.)

 

2. vknt.exe를 설치합니다.

 

3. 바탕화면의 Vietkey2000 바로가기 아이콘을 실행시킵니다.

 

4. 상단의 Input Methods 탭에 있는 체크 박스에서 VNI를 선택합니다.

(그림 참고)

 

 

                            

 

(주의사항) 제가 포샵으로 그려놓은 빨간색 화살표에만 체크하시면, 간혹 베트남어가 안써지는 경우가 생깁니다. 그림상에 표시가 된 모든 체크박스에 체크를 해주세요. <그림과 똑같이!!!>

  

5. Input Methods탭 옆에 있는 Char sets탭을 클릭합니다. 위에 있는 8-bit character sets는 그대로 두고 그 아래에 있는  Unicode 16-bit에서 맨위에 있는 Unicode Precompound, TCVN 6909-2001 체크 박스에 체크합니다.

(그림 참고)

 

 

                          

 

6. 하단에 있는 Task Bar를 클릭하면 끝!!! 그러면 바탕화면 하단에(시간 나오는 부분 있죠?) 파란색 글씨로 E라고 쓰여진 아이콘이 보일겁니다. 그걸 한번 더 클릭하면 빨간색 글씨로 된 V로 바뀝니다. 그리고 나서 베트남어로 타자를 치면 됩니다. *^-^* 

 

7. 사용방법

 

<Cách gõ chữ Việt>

 

1. A + 1: á

2. A + 2: à

3. A + 3: ả

4. A + 4: ã

5. A + 5: ạ

6. A + 6: â

7. U + 7: ư

8. A + 8: ă

9. D + 9: đ

 

 

  ★              ★                   ★       ★                                      ★     

 

* 만약... 실행되지 않거나 잘 이해가 되지 않으신 분은 쪽지 주세요.

AND