IT_Programming/MFC · API

[펌] 유니코드를 위한 Win API | MFC 프로그래밍

JJun ™ 2009. 7. 31. 01:55

UNICODE
1988년 애플Apple과 제록스Xerox에 의해 표준화된 문자 셋character set


목차

Character Sets
Single Byte & Double Byte Character Sets
Wide Byte Character Sets (Unicode)
윈도우 운영 체제와 유니코드
유니코드를 지원하는 소스 코드 만들기
변수Variables
문자열text strings
함수functiions
문자열 함수string functions
윈도우 함수windows API
UNICODE 를 고려해서 코딩하는 법
유니코드 스트링 조작 함수
유니코드와 ANSI의 구별
Reference

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

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 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을 참고한다.

 

Written by 이동우