반응형

http://blog.naver.com/ships95/120102532486




아무리 훌륭한 디버거가 있더라도 모든 경우에 디버거를 사용할 수 있는 것은 아닙니다. 특히 윈도우 서비스를 만들 때에는 디버거를 사용하기 꽤 까다롭죠? 한 가지 방법이 있다면 서비스 프로세스가 생성된 다음에 디버거에 연결해서 디버깅할 수 있는데 만일 프로세스 생성과정에서 일어나는 것에 대한 디버깅은 역시 어렵게 됩니다. 이럴 때 보통 이벤트 뷰어를 사용하게 되는데 개발 과정에서 이벤트를 뷰어를 사용하는 것도 여간 번거로운 일이 아닙니다. 보통 이런 상황에서는 원시적이긴 하지만 로그 파일을 사용하는 것이 일반적이 방법입니다.
최근 다국어 환경을 염두해 두고 개발을 하고 있는데 로그 파일을 시스템에 독립적인 유니코드를 사용하면 다국화 환경을 지원하는 데 좀 더 편리하겠다는 생각에 프로그램 내부 문자열을 모두 wchar_t를 사용하여 유니코드로 변경하고 STL의 wfstream(또는 wofstream)을 사용하여 로그 파일을 출력하려고 했습니다. 하지만 STL의 wfstream이 기본적으로 wchar_t형의 문자열을 유니코드로 출력을 하지 않는다는 것이 이제야 알게 되었습니다. 표준화는 접어 두고라도 출력 연산자(<<)의 유혹에 wfstream으로 유니코드 출력하는 방법을 알아 보게 되었습니다.

STL의 입출력 스트림(Input/Output Stream)을 통한 입출력 방식은 시스템에 설정된 언어 문화적 환경에 의해 방식이 바뀌게 되어 있습니다. 예를 들어 한국어를 사용하는 환경에서 날짜는 주로 “2009년 12월 12일 토요일”로 표기하지만, 영어를 사용하는 환경에서는 주로 “Tuesday, December 15, 2009″라고 표기합니다. 이런 언어 문화적 환경을 결정하는 것을 로켈(locale)이라고 합니다.
이러한 로켈은 숫자(Numeric), 시간/날짜(Time/Date), 화폐(Monetary), 문자(Character Type), 문자열 비교(Collation), 메시지(Message) 등 여섯 개 범주로 언어 환경을 지정하게 됩니다. 이 여섯 개의 범주는 다시 패싯(Facet)이라는 것을 통해 실제 구현됩니다. 이제 프로그래머는 응용 프로그램에서 사용할 여섯 개의 범주의 패싯을 로켈에 설정하고 이 로켈을 입출력 스트림에 적용하면 프로그램 내부와 외부 사이의 입출력을 제어할 수 있게 되는 것입니다. 입출력 스트림, 로켈, 패싯의 관계를 그림으로 보면 다음과 같습니다.

패싯 작성, 로케일의 생성 및 입출력 스트림에 적용하는 방법은 어렵지 않으니 STL 문서를 참고하시기 바랍니다. (그렇다고 그다지 쉬운 것은 아닙니다. ^^)
제가 관심있는 부분은 유니코드를 출력하는 부분인데 이 부분은 문자 범주에 속합니다. 문자 관련 언어 환경이란 대소문자, 숫자, 부호 등을 구분하는 방식과 문자의 이진수 표현 방식 즉 문자 부호화 또는 인코딩(Character Encoding)을 지정하는 것입니다. 유니코드라는 것이 문자열을 이진수로 표현하는 인코딩 방법이기 때문에 문자 인코딩 관련 패싯을 잘 만들어서 로켈에 적용하고 이를 입출력 스트림에 적용하면 되는 것입니다.
다행인 것은 현재 개발 중에 있는 대부분(사실 모든) 프로젝트는 프로그램 내부에서는 유니코드를 사용하고 있기 때문에 내부 인코딩 상태를 그대로 내보내는 패싯을 만들기만 하면 된다는 것입니다. 즉 아무런 변환 없이 그대로 내보내면 된다는 것입니다. 앞서 이야기한 대로 STL에 기본 구현된 codecvt 패싯은 wchar_t를 다중 바이트 문자열로 변환을 하도록 되어 있어 유니코드 문자열을 그대로 출력할 수 없는 것입니다.
인코딩 패싯을 처음부터 만들려면 인코딩 구현뿐만 아니라 패싯이 가져야 할 다양한 기능을 구현해야 하는데 쉬운 일이 아닙니다. 따라서, STL이 기본적으로 구현해 놓은 codecvt를 적절히 확장해서 쉽게 구현할 수 있습니다.
아래 예는 비주얼 C++과 함께 제공되는 STL codecvt를 사용하였지만 다른 STL 구현도 크게 차이가 없을 것으로 보입니다.

 
1class Unicodecvt : public std::codecvt<wchar_tcharmbstate_t>
2{
3protected:
4    virtual bool do_always_noconv() const
5    {
6        return true;
7    }
8};
 

위의 Unicodecvt는 사실은 유니코드로 변환하는 패싯이 아니라 wchar_t를 아무 변환없이 출력하는 패싯이기 때문에 진정한 의미의 유니코드 패싯이라고 할 수 없지만 앞서 언급한 것처럼 내부 코드를 유니코드를 사용하고 있다면 가장 간단히 구현할 수 있는 패싯이라고 생각됩니다. 이 패싯을 이용해서 유니코드로 출력하는 예는 아래와 같습니다.

 
01#include <fstream>
02void main(void)
03{
04    std::wofstream log(L"test.log", std::ios_base::binary);
05    log.imbue(std::locale(std::locale(""), new Unicodecvt));
06    wchar_t* msg = L"오류가 발생했습니다.";
07    int error_no = 1;
08    log << wchar_t(0xFEFF);
09    log << msg << L" 오류번호: " << error_no << std::endl;
10}
 

위의 예에서 4번째 줄에 std::ios_base::binary를 사용한 이유는 앞에서 정의한 Unicodecvt를 사용하게 되면 fstream의 << 연산자가wchar_t형의 문자를 출력할 때 내부적으로 fputwc라는 C 표준 함수를 사용하는데 이 함수가 파일을 일반 모드로 파일을 열게 되면 오류를 발생시키기 때문입니다. 다른 STL 구현에서는 어떻게 되는지 아직 확인하지 못했습니다. 또한 8번째 줄에 0xFEFF를 파일 처음에 출력(파일에는FFFE 순으로 저장됨)하는데 이것을 바이트 순서 표시(Byte Order Mark, BOM)라고 합니다. 이것은 파일이 16바이트 유니코드 인코딩을 사용해 만들어졌다는 것을 의미하는 것으로 메모장과 같은 대부분의 텍스트 파일 편집기가 이것을 해석하여 파일의 내용을 옳바르게 보여주게 됩니다.

비주얼 C++의 STL을 사용해서 유니코드 텍스트 파일 출력에 대해서 간단히 알아봤는데 C++의 표준 입출력 스트림의 국제화에 대한 좀 더 자세한 내용은 니콜라이 조슈티스(Nicolai M. Josuttis)의 The C++ Standard Library의 14장 국제화(Chapter 14 Internationalization)을 참고하시기 바랍니다.

반응형

+ Recent posts