출처 : http://devdev.tistory.com/74

 

 

문자세트 및 유니코드의 기본 그리고 윈도우32에서는 어떤 식으로 유니코드를 처리하고 있는지 살펴보도록 하자. 이 글을 통해 프로그램의 국제화에 대한 관심도를 높이고 국제화의 실현에 대한 전체적인 안목을 가질 수 있기를 바란다.

문자 세트

문자세트란 하나의 문자와 그 문자에 부여된 값의 관계를 나타내는 일종의 테이블과도 같은 것이다. 컴퓨터 분야에서사용되고있는 문자세트를 크게 본다면 SBCS(Single Byte Character Set), MBCS(Multi ByteCharacter Set), DBCS(double Byte Character Set), WBCS(Wide ByteCharacter Set 또는 Unicode) 등 네 가지로 분류할 수 있다.

SBCS는 하나의 문자세트에 부여된 글자 값이 Single Byte, 즉 0-255의 범위 내에 있음을 말한다. 우리가 잘알고있는 ASCII 또는 ISO-8859-1 문자 세트가 여기에 속한다. 주로 0x00부터 0x7F까지는 우리가 잘 알고있는ASCII를 나타내며 이 중에서도 0x20부터 0x7F까지는 화면 출력이 가능한 문자세트를 정의하고 있다. SBCS를 사용하는각 언어들은 0x80부터 0xFE까지의 문자세트를 제각기 따로 정의하고 있으며 우리는 이들 각 문자세트들을 각각 다른코드페이지(Codepage)로 정의하고 있다.

DBCS는 하나의 문자세트에 부여된 글자값이 두 바이트, 즉 0-65535의 범위 내에 있음을 말한다. 이는 원래의8비트에서 또 다른 8비트를 단순히 확장한 것이다. DBCS는 8비트로 처리할 수 없을 정도로 문자의 종류가 많은 중국, 한국,일본 그리고 대만 등에서 주로 사용하고 있다. DBCS는 SBCS의 한계를 넓히는 목적은 이루었다고 볼 수 있으나 프로그램상의많은 불편한 점을 함께 가져오므로 프로그래머에게는 일종의 골칫거리를 안고 온 셈이기도 하다(여기서 필자가 말하는 골칫거리란DBCS 자체를 가리키는것은 결코 아니며, DBCS를 적용하면서도 원래의 SBCS를 계속 지원해야만 하는 환경에서 오는프로그램상의 복잡성에서 오는 불편함을 의미한다).

MBCS란 SBCS와 DBCS를 한데 묶어놓은 문자세트를 의미하며, DBCS의 문자세트에는 거의 SBCS의 문자가포함되므로, 우리가 말하는 DBCS는 사실은 주로 MBCS를 의미한다. 한글의 KSC5601을 예를 들어보면, 문자세트의 처음부분인 0x00부터 0x7E 까지는 ASCII와 동일하게 되어있다. 이러한 배열은 중국, 일본, 대만에도 마찬가지며 나중에설명할 Unicode에서도 마찬가지다. DBCS에서 첫 번째 바이트를 리딩 바이트(Leading Byte)라 하고 두 번째바이트를 트래일링 바이트(Trailing Byte)라 하며, 첫 번째 128 문자는 ASCII로 되어있으며 나중 128 문자는각 언어마다 다르게 지정되어있다. 일본 코드페이지 932, 중국 936, 한국 949 그리고 대만 쪽에서 사용하는 코드페이지950 등이 DBCS의 대표적인 예라 할 수 있다. ASCII를 제외하고는 각 DBCS들이 모두 다르기 때문에 우리가 흔히프로그램에서 strlen()는 MBCS에서는 더 이상 직접 사용할 수가 없게된다. 즉, strlen()은 문자의 수를 반환하게되는데, SBCS에서는 문자의 수가 바이트 수와 일치하므로 strlen()의 반환값은 사실 해당 문자가 차지하는 바이트의 숫자일수도 있다. 하지만 MBCS에서는 문자의 수와 그 문자가 차지하는 바이트의 수는 그 값이 다르므로 strlen()으로 문자의수를 계산할 수 없게된다.

이와 같이 MBCS 사용에 있어서 가장 큰 문제점이라면 I/O에 연관된 문자의 처리에 해당되는 프로그램의 모든 로직을일일이 DBCS와 SBCS을 염두에 두고 개발해야 한다는 점이다. 하나의 프로그램에서 SBCS와 DBCS를 동시에 염두에 두고입출력을 개발해야 하는 것이 프로그래머에게 있어서는 귀찮은 일이며 자칫 실수하면 프로그램상의 BUG(버그 또는 오류)를 가져오는요인이 되기도 한다. SBCS로는 프로그램을 국제화(Internationalization 혹은 간단하게 i18n)할 수 없고MBCS를 사용하자니 프로그램상의 난이도가 늘어나게 된다. 또한 각 언어마다 제각기 다른 문자세트를 사용하게되니 프로그램국제화의 길은 점점 멀어지게되는 느낌이다. 여기서 개발된 것이 바로 유니코드이다.


 

유니코드

유니코드는 흔히 WBCS라 말하기도 한다. 왜냐하면 SBCS와 DBCS가 공존하는 MBCS와는 달리 모든 코드값이일괄적으로 16비트로 할당되기 때문이다. 하지만 유니코드의 구조를 잘 살펴보면 기존의 국제 표준화와의 호환성을 최대한 지키려애썼음을 한 눈에 알 수 있다. 유니코드도 DBCS에서처럼 문자값의 표현에 있어서 16비트를 사용하므로 0-65535 범위의값을 표현할 수 있다. 현재로서는 이 값으로 지구상의 모든 언어를 표현하기엔 충분한 셈이다.

그림 1에서 보는 바와 같이, 처음 127 문자인 0x0000부터 0x00 7F는 ASCII의 0x00부터 0x7F의문자와 동일하며, 0x0080부터 0x00FE는 국제표준화 ISO8859-1의 순서와 동일하게 배열하였다. 이로써 유니코드와SBCS의 국제 표준화 문자세트와의 관계를 쉽게 하였다. 유니코드는 또한 한글의 조합형과 코드 값만 다를 뿐 그 배열은동일하며, 따라서 Unicode와 조합형 코드와의 변환이 매우 용이하다. 유니코드에서 정의한 한글 조합형은 그림 1에서 보듯이AC00부터 D7A3, 즉 11172개이다. 그림 1에서 처음 256개의 유니코드 값은 ISO/ANSI 8859-1와 그 문자배열이 동일하며 (ISO/ANSI 8859-1의 처음 127개의 글자 값은 ASCII 7비트와 동일하다.), 중복되는 여러기호들 그리고 한국, 일본 및 중국, 대만에서 사용하는 한자 표기는 그 모양이 동일하면 모두 하나의 코드값으로 묶어놓았다.


 

·General Scripts : 주로 SBCS인 Latin, Cyrillic, Greek, Hebrew, Arabic, Devanagari, Thai 등의 문자세트들을 표현하고 있다.

·Symbols : 구두점, 학술기호, 그리고 각종 심벌들을 정의한다.

·CJK Misc.: CJK(Chinese, Japanese, Korean)에 사용되는 각종   음성기호

·CJK Ideographs : 한자 표기(20902)

·Hangul : 한글 표기(11172)

·Surrogate: Future Expansion(2048)

·Private Usage : 특정 벤더 또는 사용자 사용(6400)

·Compatibility : 유니코드와 다른 인코딩 시스템과의 있을 수 있는 호환성 문제에 대비하여 준비한 범위


 

한글 초성이 19개, 중성이 21개, 그리고 종성이 27개이다. 하지만 종성은 사용하지 않을 경우도 있으므로 사실상 종성은28개가 되는 셈이다. 따라서 조합형 한글이 지원하는 총 글자 수는 19×21×28 = 11,172로서 유니코드에서 지원하는글자 수와 일치한다. 유니코드에서는 완성된 한글 글자 외에도 한글 초성, 중성, 종성을 따로 정의하고 있으며 이들 자모값의체계적인 배치는 유니코드 내의 완성된 한글로의 변환을 용이하게 하고있다. 표 1의 자모판을 보면서 유니코드의 글자값을 함께계산해 보도록 하자.

유니코드에 정의된 자모값을 사용하여 완성된 유니코드의 글자값과 조합형 글자값을 계산할 수 있다. 첫 번째 번호는 각 자모에할당된 인덱스값이며 두 번째 번호는 해당 자모의 유니코드값이다. 조합형 문자세트로의 코드값 변환은 초성, 중성, 종성에 각각채움이 있으므로 위의 인덱스를 조금씩 변경하여야 한다.

자모값을 포함한 하나의 스트링을 받아 한글로 변환하는 방법을 유니코드 버전 2에서 제시하고있다. 초성, 중성, 종성의 각인덱스값을 각각 Cho_i, Jung_i, Jong_i로 정의한다면 유니코드 글자값(V)은 다음과 같이 구할 수 있다.


 

V = { [(Cho_i × 21) + Jung_i] × 28 } + Jong_i + 0xAC00


 

예를 들어, 유니코드 입력 스트링의 값이 0x1111(초성), 0x1171(중성), 0x11B6(종성)를 포함하고 있다면위의 자모값에 의하면 0x1111=ㅍ, 0x1171=ㅟ, 0x11B6=ㅀ이 되므로 완성된 글자는 ‘’ 이 되어야 할 것이다.그렇다면 위에 주어진 공식을 사용하여 확인해 보도록 하자.

0x1111은 초성으로 위 테이블 1에 의한 인덱스 값은 17 (0부터 계산함), 0x1171은 16, 그리고0x11B6은 14가 되므로 Cho_i=17, Jung_i=16, Jong_i=14가 된다. 따라서 이 값에 해당되는 유니코드의한글값 V = ( [ (17 × 21)+16 ] × 28 ) + (14 + 1) + 0xAC00 = D4DB ()이 된다.한글의 유니코드 배열이 0xAC00에서 시작되므로 한글의 첫 글자 ‘가’의 유니코드 값이 바로 0xAC 00이 된다. 역시 위의공식에 의하면 {((0 × 21)+0) × 28 + 0 + 0xAC00} = 0xAC00이 된다.

이 공식을 프로그램에 적용한다면 결국 다음과 비슷한 로직이 될 것이다.


 

void IndexToUnicode (unsigned short Cho_i, Jung_i, Jong_i)

{

   unicodeValue = ((Cho_i * 21 + Jung_i)) * 28;

   if(Jong_i)

   {

    unicodeValue += Jong_i + 1;

   }

   unicodeValue += 0xAC00;

}


 

한가지 유의할 점이 있다면 역시 종성의 인덱스 값(Jong_i) 계산인데, 종성이 사용되지 않으면 테이블 1에 표기된인덱스를 그대로 사용하며, 종성이 사용된 경우는 Jong_i + 1을 종성의 인덱스 값으로 계산해 주어야 한다는것이다(유니코드에 사용된 자모 인덱스를 사용하면 조립된 한글의 유니코드 값을 알 수 있다). 테이블 1의 인덱스를 사용하여다음과 같은 방법으로 조합형 문자 세트에서의 값을 찾아낼 수 있게 된다.


 

void toJohab(unsigned short Cho_i, Jung_i, Jong_i)

{

   Cho_i += 2;

   Jung_i += 3;

   if(Jung_i >= 25) Jung+=2;

     /* 중성 인덱스 24, 25는 유니코드에 정의되지 않았음 */

   if(Jung_i >= 18) Jung+=2;

     /* 중성 인덱스 16, 17은 유니코드에 정의되지 않았음 */

   if(Jung_i >= 9)  Jung+=2;

     /* 중성 인덱스 8, 9는 유니코드에 정의되지 않았음 */

   Jong_i += 2;

       

   johabValue = (1 << 15)|(Cho_i << 10)|(Jung_i << 5)|Jong_i;

}


 

앞서 유니코드의 예를 들면서 사용한 ‘’ 의 조합형 문자값은 다음과 같이 계산할 수 있다.


 

V = (1 << 15) | (19 << 10) | (23 << 5) | 16


 

따라서 ‘’ 에 할당된 조합형 문자값을 비트 형태로 표현하면 ‘1100 1110 1111 0000’이 되므로 0xCDF0이 된다.

지금까지 우리는 여러 나라의 문자세트들, 유니코드, 유니코드와 ASCII의 관계 그리고 유니코드와 한글 조합형 문자세트와의관계들을 알아보았다. 혹 어떤 독자들은 지금까지의 설명들이 아직 잘 이해가 가지 않을 수도 있으리라 생각한다. 하지만 그렇다고너무 걱정할 필요는 없을 것 같다. 유니코드의 이해가 아직은 윈도우 I/O 아키텍처에의 이해에 있어서 필수라고는 말할 수는 없기때문이다. 하지만 이미 많은 프로그래머가 유니코드의 필요를 실감하고 있으며 프로그램의 국제화의 측면에 있어서는 유니코드가 가까운미래에 반드시 필요하게 될 것이다. 특히 윈도우 NT를 사용하는 독자라면 유니코드의 이해는 거의 필수라 말하고 싶다.


 

Unicode와 윈도우

위에서 우리는 소프트웨어 국제화를 위해서는 유니코드가 왜 바람직한 해결책인가를 알아보았다. 유니코드 중심의 소프트웨어국제화를 염두에 두고 개발된 OS가 바로 윈도우 NT라 할 수 있다. 이는 윈도우 95 또는 윈도우 98과는 달리 원래부터유니코드 처리를 위해 최적화되어 개발되었으며 따라서 지역화를 더욱 단순화하고 다양한 문자들을 보다 손쉽게 처리할 수 있다. 이미많은 업체에서 소프트웨어의 국제화를 이루기 위한 수단으로 전통적인 문자세트와 유니코드를 함께 지원하고 있으며, 아직은 유니코드가완전한 뿌리를 내리지 못한 상황이므로 개발환경에서는 전통적인 문자세트와 유니코드를 동시에 지원하도록 하는 것이 현재로서는소프트웨어 국제화의 바람직한 접근방법이라 생각한다. 이렇게 두 문자체계를 동시에 지원하므로 프로그래머가 자유롭게 길을 선택할 수있다는 장점이 있으나, 내부적으로는 입출력되는 문자들을 변환하는 장치를 거쳐야 하므로 성능 면에서 다소 그 기능이 떨어지는단점이 있을 수도 있다. 앞으로 우리는 남은 지면을 통해 유니코드와 전통적인 문자세트를 소프트웨어의 국제화 측면에서 어떻게WIN32 프로그램에 적용해야 할 것인지를 알아보도록 하겠다.

많은 윈도우32 API가 이미 유니코드와 SBCS을 지원하도록 따로 개발되어있다. 물론 개발자는 같은 일을 하는 두 개의다른 코드를 만들어 관리하고 싶지는 않을 것이다. 따라서 기존 ANSI용 API를 사용하는 프로그램을 유니코드 API와 호환되게하는 일 또한 중요한 과제중의 하나가 될 것이다. 참고로 여기서 말하는 ANSI용 API란 우리가 흔히 사용하는 일반 API를의미하며, 이는 API 차원에서의 데이터 처리능력으로 보통 SBCS 또는 DBCS에서 주로 사용되어 왔다. 반면에 유니코드용API라는 개념은 API 차원에서 16비트를 입력받아 일을 처리하도록 개발된 API를 의미한다. 윈도우에서 쉽게 사용할 수 있는다이얼로그박스 MessageBox()를 예를 들어보자. 프로그램 상에서는 ‘MessageBox()’를 사용하지만 실제 컴파일러가사용하는 MessageBox() 버전은 MessageBoxA() 또는 MessageBoxW() 중의 하나이다. 어느 버전을사용하느냐는 프로그래머가 16비트 코드인 유니코드 실행파일을 만들 것인지 아니면 8비트 코드 ANSI 실행파일을 만들 것인지에따라 다르다.

프로그램 실행파일의 관점에서 볼때, 유니코드를 사용할 때와 사용하지 않을 때는 어떻게 다른가? 물론 지금까지의 설명을 종합해 볼 때 유니코드는 16비트를사용하므로 8비트 버전에 비해 두 배의 자원을 사용하게됨을 알고있다. 또한 OS의 문자처리 아키텍처가 실행되는 프로그램과 다를때는 문자 변환기를 사용하므로 성능 면에서 이론적으로 약간의 차이가 있음을 설명한 바 있다. 예를 들어 어떤 프로그램에서 다음과같은 두 개의 변수를 정의한다고 가정해 보자.


 

char charV;

char * pch;

윈도우32 아키텍처에서 유니코드를 사용하지 않을 때의 charV는 1 바이트 변수로서 그 값은 ‘\0’로 초기화된다.여기서 charV에 ‘A’를 할당하면 (charV=‘A’), charV는 ‘A’의 코드값인 0x41를 가지게 된다. 두 번째줄에서는 문자열을 가리키는 포인트를 pch로 정의하고 있으며, pch가 차지하는 메모리의 크기는 윈도우32 아키텍처 상4바이트(32비트)가 된다. pch=“ABCD”처럼 했을 때 ‘ABCD’는 5바이트 문자열(‘0x41’, ‘0x42’,‘0x43’, ‘0x44’, ‘0x00’)로 메모리에 저장된다. 한편, 유니코드를 사용할 때의 charV는 2 바이트 변수로변하며 따라서 그 값은 0x0041이 되며, 포인터로 정의된 pch는 그 자신의 크기는 변함없이 4바이트이나 ‘ABCD’의 값은‘0x0041’, ‘0x0042’, ‘0x0043’, ‘0x0044’, ‘0x0000’로 메모리에 저장되므로 12바이트 문자열이되는 셈이다. ANSI 문자와 UNICODE 처리에 있어서의 이러한 차이는 약간의 프로그램 성능의 변화를 초래할 수도 있다.유니코드를 입, 출력 소스로 예상하고 있는 윈도우 NT에서 ANSI 스트링을 사용하는 프로그램을 어떻게 처리하는지 그 순서를보면 대략 다음과 같다.


 

1. ANSI 윈도우 프로그램에서 ANSI 문자열을 윈도우 NT의 윈도우 시스템으로 보낸다

2. 윈도우 NT의 윈도우 시스템은 입력된 ANSI 문자열을 16비트 문자열 유니코드로 변환하여 윈도우 NT의 OS로 보낸다.

3. 윈도우 OS는 프로그램 실행 후 그 반환값을 윈도우 NT 윈도우 시스템으로 보낸다

4. 윈도우 NT 윈도우 시스템은 다시 이 값을 ANSI 윈도우 프로그램으로 보낸다. 이 때 ANSI 윈도우 프로그램이ANSI값을 예상하고 있다면 윈도우 NT 시스템은 윈도우 NT OS에서 반환받은 유니코드 값을 ANSI 형태로 변환하여 ANSI윈도우 프로그램으로 보낸다.


 

윈도우 NT는 native unicode, 즉 유니코드를 지원하도록 처음부터 디자인된 OS이다. 윈도우 NT는 문자열처리에 있어서 16비트를 사용하고 있으나 아직 대부분이 8비트를 사용하므로 NT는 위에서 나열한대로 변환기가 필요한 셈이다.

윈도우 9x는 유니코드를 거의 지원하지 않으므로 현 시점에서의 가장 바람직한 개발 환경은 역시 ANSI와 유니코드를 동시에지원하는 소스를 만드는 것이다. 이제 남은 지면을 통해 SBCS와 유니코드와의 관계를 API 차원에서 살펴보면서 유니코드,DBCS 그리고 SBCS과 관련된 API들을 잘 이해할 수 있기를 바란다.


 

Uncode 프로그램

일반적으로, 한 프로그램을 코딩한다고 말할 때 문자세트 사용의 관점에서 본다면 다음과 같은 세 가지 경우를 생각해 볼 수 있다.

·ANSI 버전 : ANSI 문자만 수용하는 프로그램

·UNICODE 버전 : Unicode 문자만 수용하는 버전

·통합버전 : 유니코드와 ANSI를 동시에 수용하는 프로그램    


 

어떤 버전을 사용하든지 프로그램의 로직은 말할 필요없이 동일하다. 다만 다른 점이라면 유니코드에서는 16비트로 문자를처리하며(ANSI 버전), 컴파일러가 유니코드 버전 API를 사용할 수 있도록 필요한 head 파일을 덧붙이며(UNICODE버전) 프로그램의 소스 파일에 _UNIC ODE를 정의해 주는 것(통합버전) 등이다. 여기서는 윈도우32 API를 사용하여하나의 소스프로그램이 어떻게 유니코드와 ANSI I/O를 동시에 인식할 수 있게 하는지를 주로 Run-Time에서 사용되는문자열의 처리와 관련된 API들을 알아보고 끝으로 i18n과 관련된 몇 가지 WIN32 API들을 알아보도록 하겠다.

우선 가장 먼저 우리가 이해해야 할 것이 있다면 바로 유니코드 또는 Wide Character(WC)를 정의하고 있는‘WCHAR.H’ 파일이다. C나 C++에서 정의하는 WC는 다른 어느 데이터 타입을 정의할 때와 마찬가지로 단순하다.


 

typedef unsigned short wchar_t;

wchar_t c = L‘A’에서, c의 값은 0x0041이 된다. CPU에서 0x0041이 저장될 때는 LSB(LeastSignifica nt Byte)을 먼저 나타내므로 0x0041은 메모리에서는 사실 ‘41 00’로 저장된다. 또한wchar_t * p = L“ABCD”;라고 정의했다면 CPU가 p의 값을 메모리에 저장할 때는 ‘4100 4200 43004400’처럼 된다. 여기서 유의해야 할 점은 하나의 문자열을 WC로 정의했다면 반드시 WC로 처리해야 한다는 점이다. 그렇지않으면 첫 번째 값 41을 읽은 후 00가 다음 문자로 처리되므로 문자열의 끝이 되어 버리는 셈이다. 너무 당연한 이치이지만자칫 잘못하면 오류가 발생할 수 있는 부분이기도 하다. 재미 삼아 다음 코딩을 실행했을 때의 결과값을 한번 생각해 보자.


 

wchar_t * p = L“ABCD”;

    printf(“%d”, strlen(p));

strlen() 런-타임 라이브러리는 WC를 처리하도록 고안되지 않았다. 따라서 위 코드를 실행했을 때의 출력값은 1이 될 것이다.


 

int strlen (const char * str)

       {

           int length = 0;

           while( *str++ )

                   ++length;

           return( length );

}

stelen()을 wcslen()으로 대치시키면 제 값을 출력할 수 있게된다. wcslen()은 다음과 같이 2 바이트를 처리하는 로직으로 되어있으므로 실제 입력되는 문자의 성질에 무관하게 2 바이트로 처리된다.  


 

size_t __cdecl wcslen(const wchar_t *wcs)

{

    const wchar_t * eos = wcs;

    while(*eos++);

    return ((size_t) (eos - wcs -1));

}


 

따라서 strlen()에서 발생하는 반대의 오류가 wcslen()에서 발생할 수 있다. 즉, strlen()를 정작사용해야 할 곳에 wcslen()을 사용하게 되면 그 값이 반으로 나타나게 될 것이다. 그렇다면 하나의 프로그램으로 상황에 따라strlen()이나 wcslen()을 선택적으로 사용하게 하고 싶다면 (WC와 SBCS의 두 가지 환경에서 하나의 소스코드로실행하고 싶다면), 다시 말해서 유니코드를 사용하여 프로그램의 국제화를 이루고 싶다면 어떻게 할 수 있겠는가?

이에 대한 답은 바로 통합형 버전의 API를 사용하는 것으로 헤드파일은 TCHAR.H이다. ‘#_UNICODE’와‘#define TCHAR.H’이 소스 프로그램에 정의되었을 때 소스 프로그램 위에 다음과 같은 형태로 수정해 주므로_UNICODE가 정의되었으면 _tstrlen()는 wcslen()로 실행하고, _UNCODE를 정의하지 않았다면_tstrlen()는 strlen()를 실행시키게 된다.


 

#define _UNICODE

    #include <TCHAR.H>


 

    wchar_t * p = L“ABCD”;

    printf(“%d”, _tstrlen(p));


 

문자열을 WC 형태로 저장하고 싶을 때 반드시 해당 문자열의 바로 앞에 L을 덧붙여 주어야 한다고 설명한 바 있다. 하지만반드시 WC만 사용하는 것이 아니므로 문자열 저장 방법 역시 위에서 설명한 Run-Time Library와 같은 형태의메커니즘이 필요한데, TEXT()가 바로 그것이다.


 

ifdef UNICODE #define __T(x) L##x

else #define __T(x) x

. . .

#define _T(x)  __T(x)

. . .

#define _TEXT(x) __T(x)

. . .

#define TEXT(x) _TEXT(x)

위의 로직은 단순하다. 예를 들어, UNCODE가 정의되었으면 __T(“ABCE”)를 L“ABCD”로 변환하고UNICODE가 정의되지 않았으면 __T(“ABCD”)는 기본형태를 사용한다는 지시이다. 여기서 기본형태란 ANSI 문자세트를의미하다. 그리고 __T는 _T 또는 _TEXT로 변환되므로 우리가 주로 사용하게 되는 매크로는 _TEXT()의 형태가 된다.맨 마지막 줄의 ‘define TEXT(x) _TEXT(x)’는 winnt.h에 정의되어 있는 것으로 알고있는데 간단하게‘windows.h’를 포함시키면 이런 메커니즘을 걱정할 필요없이 문자열을 항상 TEXT로 감싸주므로 유니코드를 사용할 수 있게되어있다. 따라서 위에서 사용된 로직을 염두에 두고 다시 수정해 보면 다음과 같다.


 

#define _UNICODE

#include <TCHAR.H>


 

TCHAR * p = TEXT(“ABCD”);


 

printf(“%d”, _tstrlen(p));


 

독자의 이해를 돕기 위해 다음과 같은 간단한 테스트 프로그램을 만들어 보았다.

본 소스코드를 여러 형태로 수정해 가면서 본 글에서 명시된 프로그램 국제화의 메커니즘들을 하나씩 실습해 보기바란다.  


 

#define _UNICODE


 

#include <TCHAR.H>


 

#include <windows.h>


 

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,

        LPSTR lpCmdLine, int nCmdShow)

{

    TCHAR *wc = TEXT(“ABCD”);

    TCHAR msg[80];


 

    wsprintf(msg,“_tcslen=%u, sizeof(msg)=%u”,

          _tcslen(wc),sizeof(msg));

    MessageBox(NULL, msg, “UNICODE”, MB_OK);


 

    return 0;

}


 

유니코드를 사용한 프로그램의 국제화에 사용되는 데이터 타입의 정의에 대한 지금까지의 설명을 정리해보면 다음과 같다.


 

/* Generic types */

#ifdef UNICODE

  typedef wchar_t TCHAR;

 #else

  typedef unsigned char TCHAR;

#endif

typedef TCHAR * LPTSTR, *LPTCH;


 

/* 8-bit character specific */

typedef unsigned char CHAR;

typedef CHAR *LPSTR, *LPCH;


 

/* Unicode specific (wide characters) */

typedef unsigned wchar_t WCHAR;

typedef WCHAR *LPWSTR, *LPWCH;

문자열을 처리하는 Win32 API들은 대개가 두개의 프로그램으로 이루어져 있는데 하나는 API 이름이 A로 끝나는것이고, 다른 하나는 그 이름이 W로 끝나는 것이다. 여기서 A는 ANSI 표준을 의미하고 W는 Wide 문자, 즉 유니코드를의미한다고 생각하면 된다. 어떤 프로그램이 위에서 설명한 것처럼 통합형 API를 사용할 수도 있고 xxxA() 또는xxxW()와 같이 특정 API를 구체적으로 부를 수도 있다. 필자는 다음과 같은 세 가지 이유 때문에 언제나 통합형 API를사용하도록 권하고 싶다.


 

·코드의 포팅이 쉽다 (WIN NT, Win9x)

·코딩이 용이하다

·프로그램을 국제화 할 수 있다


 

여기서 말하는 포팅이란 단순히 윈도우 NT와 윈도우 95/98 간의 포팅을 의미한다. 예를 들어 어떤 프로그램을 만들어컴파일 할 때, 윈도우 NT용이면 UNICODE를 정의해 주고, 95/8용이면 UINCODE를 정의하지 않는다. 이로인해 통합형API들을 사용할 경우 적절한 API를 컴파일러가 알아서 선택하여 사용하므로 일관성 있는 코딩이 가능하고 코딩 또한 용이하게되며, 유니코드를 사용하면 전 세계의 모든 인코딩 시스템을 지원할 수 있게되므로 프로그램의 국제화를 쉽게 실현할 수 있게된다.문자열의 처리와 관련된 API들의 매크로는 대략 lstrcatA(W), lstrc mp(wcscmp),lstrcmpi(wcscmpi), lstrcpy, lstrlen, lstrcmp and lstrcmpi 등이 있으며 이 중lstrcatA(W), lstrcmp(wcscmp), lstrcmpi(wcscm pi), lstrcpy, lstrlen은 같은일을 하는 두개의 API가 있으며, lstrcmp and lstrcmpi은 CompareString()를 불러 UNICODE와ANSI를 구분하게 되어있다.

위의 예제를 잘 활용하여 여기에 나열된 API 매크로들을 충분히 이해할 수 있기 바란다. 앞서 약속한 대로, 유니코드 및 ANSI와 관련된 WIN32 API들 중 우리가 이해하여야 할 몇 가지 API들을 나열해 보면 다음과 같다.


 

GetCPInfo

MultiByteToWideChar

WideCharToMultiByte

IsTextUnicode

IsDBCSLeadByte

IsWindowUnicode

CharNext,

CharPrev

GetInfo()는 CPINFO 스트럭처에 로케일에 관련된 정보를 채워주며 CPINFO에 포함되는 정보는 다음과 같다.


 

struct _cpinfo {  

    UINT MaxCharSize;

    BYTE DefaultChar[MAX_DEFAULTCHAR];

    BYTE LeadByte[MAX_LEADBYTES];

} CPINFO;


 

MaxCharSize는 프로그램이 사용중인 코드페이지 내에서의 문자의 크기를 바이트 수로 나타낸다. 즉, 코드페이지 949한글 완성형의 경우는 SBCS과 DBCS이 혼합되어 있으므로 MaxCharSize는 두 바이트, 즉 2가 될 것이다.DefaultChar란 다른 문자 세트가 현 시스템의 코드페이지로 변환될 때 사용할 문자를 의미하며 이것은 WideCharToMultiByte의 기본값이 되기도 한다. LeadByte란 사용중인 코드 페이지의 LeadByte 범위를 나타나며LeadByte가 없는 경우는 LeadByte[]는 NULL로 채워진다. MultiByteToWideChar와WideCharToMulti Byte는 SBCS과 DBCS에서 공통적으로 사용되는 문자들을 매핑하는 목적으로 사용되며,IsTextUnicode는 대상 문자열 중 UNICODE가 포함되었는지를 알아내려할 때 사용한다. WIN9x의 경우는UNICODE를 거의 지원하지 않으므로 본 API는 시스템 차원에서 무시된다. 그리고 IsDBC SLeadByte는 대상 문자가DBCS의 첫 번째 바이트인지를 알려고 할 때 사용할 수 있으며, IsWindow Unicode를 사용하여 윈도우에서 처리하는문자열의 타입이 UNICODE인지 아닌지를 판단한 수 있다.

끝으로 CharNext와 CharPrev를 사용하여 다음에 오는 문자를 살펴볼 수 있다. 이 두 API는 WIN16ANSI C를 사용하는 사람이라면 AnsiNext와 AnsiPrev를 연상하게 될 것이다. WIN32에서는 이전 코드와의호환성을 유지하기 위해 다음과 같은 매크로를 사용하고 있다.


 

#define AnsiNext CharNext

#define AnsiPrev CharPrev

안정적인 DNS서비스 DNSEver DNS server, DNS service
Posted by 키르히아이스

댓글을 달아 주세요