MS 에서는 윈도우용 어플리케이션을 만들때 유니코드로 만들기를 권장하고 있다. 개인적으로도 일본어와 한국어를 병행해서 사용해야 하는 일이 많은 관계로, 윈도우에서의 비 유니코드 프로그램을 실행하는것은 상당히 귀찮기 때문에, 그 필요성을 절실히 느껴왔다. 특히나 파일이름에 있어서 여러 언어의 혼용과 병용은 파일을 읽지 못하거나, 아예 프로그램이 동작하지 않는 사태에 까지 이르게 하기 때문이다.
윈도우에서 프로그램을 만들며 생기는 한가지 문제는 UTF-8 과 UTF-16 으로 씌여진 소스파일에 대한 처리이다. 현재 GCC MingW 3.4.5 에서는 BOM이 존재하지 않는 UTF-8 형식의 소스파일을 잘 인식한다. 반면 Visual C++ 에서는 BOM이 포함되어야 한다. 또다른 점으로서 C++ 에서는 int 와 마찬가지로 wchar_t 라는 사이즈 미정의 정체 불명의 자료형이 사용되어진다. 주로 unsigned 16 비트 정수형으로 정의되어지는듯 하기도 하지만, 일부에서는 32 비트 정수형으로 정의되어 지기도 한다. 따라서 배열로 선언된 wchar_t 의 크기는 실제로 코드가 UTF-16 으로 되어있다 하더라도, 0x0000 과 같은 2 바이트가 중간에 포함될 가능성도 배제할 수 없다.
많은 문제속에서, 개인적인 프로젝트인 Islbazi 와 GLAN 에서는 개별적이고 일관적인 형태로 처리하기로 했다. 하나의 프로그램속에서는 외부의 API 가 어떤 형태를 필요로 하던간에, UTF-16 으로 처리를 하고, 상수문자열의 경우 UTF-8 로부터 UTF-16 으로 변환하여 처리를 하는것이다. 프로그램 소스 파일이 UTF-8 인 관계로 프로그램에서도 내부적으로 UTF-8 을 사용하면 되기도 하지만, 한글처리에 있어서 1 글자가 3 바이트라는 애매한 간격으로 구분이 되기때문에, UTF-16 을 사용함으로서 한글 1 글자를 2 바이트로 처리하는것이 개별적인 글자에 대한 처리가 훨씬 간편했기 때문이다. 어차피 API 와의 호환성을 고려하면, UTF 간의 변환은 반드시 필요하다고 여겨지는것이기 때문에, 실행시 UTF 간의 변환을 위한 처리가 필요하다는 단점을 제외하면 크게 문제될것은 없었다. 이 단점 때문에 컴파일 시간에 상수문자열의 UTF 변환을 시도해보기도 하였으나, 좋은 성과를 거두지는 못하였다. 상수시간에는 문자열 변환을 실패하였지만, 실행시간의 변환을 자료형에 의존하여 편하게 하기 위한 도구 - [유니코드의 표현형식에 대한 변환]을 직접 구현해 볼까 한다. UTF 자체에 대한 자세한 정보는 앞의 링크를 참조하기 바란다. 실제 변환에 대해서는 이곳을 참조하면 도움이 될것이다. 한편, boost 는 사용하고 있지 않으므로, boost 에서 현재 관련 기능을 제공하고 있는지의 여부는 알지 못한다.
C++ 의 표준 템플릿 라이브러리에는 std::codecvt [주1] 라는 코드 변환을 위한 템플릿 객체가 존재한다. 각종 문자열 엔코딩 형식 변환을 위해 사용하기에 적합한 객체이다. 따라서, 구현할 객체의 인터페이스는 이 std::codecvt 를 모방하기로 한다. 그렇게 만들면, std::codecvt 의 인터페이스를 갖는 객체를 템플릿 인자로 받는 코드들에 대해서, 쉽게 전환 가능한 이점이 있기 때문이다. 실제 구현에서는 표준 라이브러리에서는 codecvt 라는 이름에 전문화를 통해 이용될 가능성이 높지만, 사실 이는 비포인터 문자열에 대한 처리가 곤란한 문제가 있다. 모두 같은 구현자가 라이브러리를 같은 형태로 만들면 상관없지만, 앞으로 구현할 maid::codecvt 의 경우에는 외부에서 덧붙이는 형태로 구현되기 때문에, std::string 같은 객체에서 얻어온 포인터를 직접 탐색하는 일은 껄그럽다. 특히 대입에 있어서 언제나 char* 같은 포인터형을 이용해야만 한다면 더욱 그러하다. std::string 으로 대입을 시키기 위해서는, 우선 임시로 메모리공간을 할당하고, 그 공간에 변환된 문자열을 일단 저장한뒤에, 그 문자열을 다시 std::string 에 복사해 넣는 과정이 필요로 해지기 때문이다. 그렇기 때문에, 템플릿 인자로서 char 와 같은 문자 자료형을 직접 받는것이 아니고, 문자열 그 자체의 자료형을 받음으로서 std::string 이든, 사용자 정의 문자열 자료형이든 관계없이, 같은 인터페이스라면, 일관적으로 처리 하도록 만드는것이 더 낫다고 판단된다. 따라서, 실제로 구현된 maid::codecvt 는 다음과 같은 템플릿 인자를 받는다.
template <class internAT_, class externAT_, class stateT_>
class codecvt;
[주2] 현재 유니코드 버전 5 가 나와있지만, maid::codecvt 가 만들어질 당시의 버전은 4 였다. 버전 5 에서 추가된 글자들이 있지만, 그역시 32 비트의 범위내에서 표현 가능할것이라 생각된다. 만일 표현번위가 더 커진다면, 더 큰 크기를 갖는 자료형으로 교체함으로서 문제는 없을것이다.
또한 임의의 유니코드 엔코딩 형식간의 변환에 있어서 일단 1글자를 읽은뒤, 1글자를 쓰는 형태를 취하기로 한다. 특히 임의의 엔코딩 형식에 대해서, UTF-32 로 표현된 1글자를 읽고 쓰는것만 구현하면, 모든 변환에 대해서 중간에 UTF-32 로 변환하는 과정을 통해 임의의 엔코딩 형식에서 다른 엔코딩 형식으로 변환하는것이 가능해진다. 즉 UTF-8 에서 UTF-16 으로 직접 변환하는것이 아니고, UTF-8 ↔ UTF-32 ↔ UTF-16 의 과정을 거쳐 변환하는것이다. 혹시 UTF-7 을 구현했다고 하면, UTF-7 ↔ UTF-8, UTF-7 ↔ UTF-16 을 직접 구현하는것이 아니고, UTF-7 ↔ UTF-32 만을 구현함으로서, UTF-7 ↔ UTF-32 ↔ UTF-8, UTF-16 의 과정을 통해 기존에 정의된 형식과의 변환이 가능해진다.
앞으로 여유가 되는대로 UTF 의 변환과 그 구현의 템플릿 라이브러리화를 구체적으로 다루어 가도록 하겠다. □
윈도우에서 프로그램을 만들며 생기는 한가지 문제는 UTF-8 과 UTF-16 으로 씌여진 소스파일에 대한 처리이다. 현재 GCC MingW 3.4.5 에서는 BOM이 존재하지 않는 UTF-8 형식의 소스파일을 잘 인식한다. 반면 Visual C++ 에서는 BOM이 포함되어야 한다. 또다른 점으로서 C++ 에서는 int 와 마찬가지로 wchar_t 라는 사이즈 미정의 정체 불명의 자료형이 사용되어진다. 주로 unsigned 16 비트 정수형으로 정의되어지는듯 하기도 하지만, 일부에서는 32 비트 정수형으로 정의되어 지기도 한다. 따라서 배열로 선언된 wchar_t 의 크기는 실제로 코드가 UTF-16 으로 되어있다 하더라도, 0x0000 과 같은 2 바이트가 중간에 포함될 가능성도 배제할 수 없다.
많은 문제속에서, 개인적인 프로젝트인 Islbazi 와 GLAN 에서는 개별적이고 일관적인 형태로 처리하기로 했다. 하나의 프로그램속에서는 외부의 API 가 어떤 형태를 필요로 하던간에, UTF-16 으로 처리를 하고, 상수문자열의 경우 UTF-8 로부터 UTF-16 으로 변환하여 처리를 하는것이다. 프로그램 소스 파일이 UTF-8 인 관계로 프로그램에서도 내부적으로 UTF-8 을 사용하면 되기도 하지만, 한글처리에 있어서 1 글자가 3 바이트라는 애매한 간격으로 구분이 되기때문에, UTF-16 을 사용함으로서 한글 1 글자를 2 바이트로 처리하는것이 개별적인 글자에 대한 처리가 훨씬 간편했기 때문이다. 어차피 API 와의 호환성을 고려하면, UTF 간의 변환은 반드시 필요하다고 여겨지는것이기 때문에, 실행시 UTF 간의 변환을 위한 처리가 필요하다는 단점을 제외하면 크게 문제될것은 없었다. 이 단점 때문에 컴파일 시간에 상수문자열의 UTF 변환을 시도해보기도 하였으나, 좋은 성과를 거두지는 못하였다. 상수시간에는 문자열 변환을 실패하였지만, 실행시간의 변환을 자료형에 의존하여 편하게 하기 위한 도구 - [유니코드의 표현형식에 대한 변환]을 직접 구현해 볼까 한다. UTF 자체에 대한 자세한 정보는 앞의 링크를 참조하기 바란다. 실제 변환에 대해서는 이곳을 참조하면 도움이 될것이다. 한편, boost 는 사용하고 있지 않으므로, boost 에서 현재 관련 기능을 제공하고 있는지의 여부는 알지 못한다.
C++ 의 표준 템플릿 라이브러리에는 std::codecvt [주1] 라는 코드 변환을 위한 템플릿 객체가 존재한다. 각종 문자열 엔코딩 형식 변환을 위해 사용하기에 적합한 객체이다. 따라서, 구현할 객체의 인터페이스는 이 std::codecvt 를 모방하기로 한다. 그렇게 만들면, std::codecvt 의 인터페이스를 갖는 객체를 템플릿 인자로 받는 코드들에 대해서, 쉽게 전환 가능한 이점이 있기 때문이다. 실제 구현에서는 표준 라이브러리에서는 codecvt 라는 이름에 전문화를 통해 이용될 가능성이 높지만, 사실 이는 비포인터 문자열에 대한 처리가 곤란한 문제가 있다. 모두 같은 구현자가 라이브러리를 같은 형태로 만들면 상관없지만, 앞으로 구현할 maid::codecvt 의 경우에는 외부에서 덧붙이는 형태로 구현되기 때문에, std::string 같은 객체에서 얻어온 포인터를 직접 탐색하는 일은 껄그럽다. 특히 대입에 있어서 언제나 char* 같은 포인터형을 이용해야만 한다면 더욱 그러하다. std::string 으로 대입을 시키기 위해서는, 우선 임시로 메모리공간을 할당하고, 그 공간에 변환된 문자열을 일단 저장한뒤에, 그 문자열을 다시 std::string 에 복사해 넣는 과정이 필요로 해지기 때문이다. 그렇기 때문에, 템플릿 인자로서 char 와 같은 문자 자료형을 직접 받는것이 아니고, 문자열 그 자체의 자료형을 받음으로서 std::string 이든, 사용자 정의 문자열 자료형이든 관계없이, 같은 인터페이스라면, 일관적으로 처리 하도록 만드는것이 더 낫다고 판단된다. 따라서, 실제로 구현된 maid::codecvt 는 다음과 같은 템플릿 인자를 받는다.
template <class internAT_, class externAT_, class stateT_>
class codecvt;
- internAT_ : Internal array type, 즉 내부 표현을 위한 문자열 자료형. std::codecvt 의 첫번째 템플릿 인자가 내부에서 사용되는 문자 자료형이라는 점에 주의하자.
- externAT_ : External array type, 외부 표현을 위한 문자열 자료형. 이것도 std::codecvt 의 두번째 템플릿 인자와 대응된다.
- stateT_ : State type, 변환과정에서 상태를 저장하기 위한 자료형. 유니코드 버전 4의 경우[주2] unsigned 32 비트 자료형의 범위내에서 표현 할 수 있다. 따라서, 임의의 1글자를 저장하기 위한 자료형으로서 uint32 정도면 충분할것이다. 물론, 나중에 필요에 의해 더 큰 자료형으로 교체함으로서 확장이 가능하다.
[주2] 현재 유니코드 버전 5 가 나와있지만, maid::codecvt 가 만들어질 당시의 버전은 4 였다. 버전 5 에서 추가된 글자들이 있지만, 그역시 32 비트의 범위내에서 표현 가능할것이라 생각된다. 만일 표현번위가 더 커진다면, 더 큰 크기를 갖는 자료형으로 교체함으로서 문제는 없을것이다.
또한 임의의 유니코드 엔코딩 형식간의 변환에 있어서 일단 1글자를 읽은뒤, 1글자를 쓰는 형태를 취하기로 한다. 특히 임의의 엔코딩 형식에 대해서, UTF-32 로 표현된 1글자를 읽고 쓰는것만 구현하면, 모든 변환에 대해서 중간에 UTF-32 로 변환하는 과정을 통해 임의의 엔코딩 형식에서 다른 엔코딩 형식으로 변환하는것이 가능해진다. 즉 UTF-8 에서 UTF-16 으로 직접 변환하는것이 아니고, UTF-8 ↔ UTF-32 ↔ UTF-16 의 과정을 거쳐 변환하는것이다. 혹시 UTF-7 을 구현했다고 하면, UTF-7 ↔ UTF-8, UTF-7 ↔ UTF-16 을 직접 구현하는것이 아니고, UTF-7 ↔ UTF-32 만을 구현함으로서, UTF-7 ↔ UTF-32 ↔ UTF-8, UTF-16 의 과정을 통해 기존에 정의된 형식과의 변환이 가능해진다.
앞으로 여유가 되는대로 UTF 의 변환과 그 구현의 템플릿 라이브러리화를 구체적으로 다루어 가도록 하겠다. □