라이프로그


msvc(윈도우)와 리눅스&유닉스 c++에서 wchar_t를 같이 사용하기 프로그래밍

개인적인 기록겸 오늘 실험해보면서 알게되고 고찰(?)한 것들을 적어본다.

상황
개발하려는 프로그램이 cp949나 euc-kr 또는 ksc5601 이라고 하는 것들로는 뭔가 부족하면 유니코드를 써야한다.
소스코드가 호환되어야하는 것은 필수.
종종 파일로 한글을 저장하고 다시 불러 쓰기도 한다.
이렇게 저장된 파일이 윈도우와 리눅스에서 같이 공유되면 좋지만....
(즉, 데이터 파일의 호환은 옵션)

이해를 돕기위한 설명.
wchar_t를 윈도우에서 파일로 출력하면 2바이트 단위다.
이 파일을 리눅스에서 읽으려면, 리눅스는 wchar_t가 4바이트여서 매우 힘들다.

방법 : ICU
가장 완벽한 해결법 같지만 여러가지 문제가 있다.
ICU를 사용하기에는 라이센스 문제는 영어가 모자라 조금 겁나서 못쓰겠다. 사용법도 쉽지 않다.
(일단 시간나면 변호사님에게 물어보고 라이센스 관련은 자문을 받아야...나중에 여기에 내용을 더 적겠슴)
(대강 보기에 무료로 상업적 이용이 가능하지만 반드시 오픈소스로 개발해야하는듯....)

사용만 가능하다면 여러 종류의 유닉스와 리눅스 윈도우를 모두 호환하고 있다.
그런데 L"한글" 이런식으로 적는 L 기호 문제를 ICU 특유의 방법으로 해결하는듯 한데 아마도 비효율적인거 같음.
UChar 라는 타입을 사용하는데 문자1개에 대한 L같은 포직스가 없는듯.
대신에 UnicodeString 이라는 타입은 간단한 매크로를 지원한다.
만약 앞글자의 종성이 ㅁ 인지 확인하기위해 ㅁ을 하드코딩하자고 하면
if( ??? == L'ㅁ' )
wchar_t를 그대로 사용하자면 위와같이 L을 포직스(?)로 적고 해결이 가능하다.
그런데 ICU는 ㅁ을 저장하는 UChar를 만들어야 하는데 핵사코드( 0xAB12 같은 형태)를 준비하던가..
( 핵사코드 직접쓰면 가독성이나 개발 속도, 유지보수 등등에서 정말 안좋음. )

wchar_t나 char를 UChar로 변환하는 함수들이 있는데 가장 유용해 보이는 것은 wchar_t를 호환해주는
u_strFromWCS()

가장 권장하는 것으로 보이는(내맘대로 추측) 방법은
UnicodeString ustr = UNICODE_STRING_SIMPLE("ㅁ");
ustr[0] 이렇게 사용하는 .....
단, 이 경우에는 euc-kr에서 표현할 수 없는 문자를 하드코딩하기 힘들어 보인다. 확인안해봤음.

도저히 UChar 하나만 쉽!게! 만드는 방법을 찾을 수가 없었다. orz
(아마 u_strFromWCS 소스코드를 직접 열어보면 어찌 편하게 될 지도....)

그리하여 가장 마음에 안드는 것은 메뉴얼이 제대로 없다는 것. 있긴한데 엉망.

최신판은 내부적으로 UCS2LE를 주로 사용하는듯. 즉, 자바나 C#과 비슷함.
일단 구현만하면 저장된 한글파일이 모두 UCS2LE니까 여러 플랫폼에서 호환됨. 
심지어 빅엔디안 cpu의 운영체제라도 호환가능.
즉, 소스와 데이터 모두 호환됨.

정리하면 라이센스와 메뉴얼이 문제라는 것.


방법 : wchar_t를 그대로 사용
wchar_t를 그냥 쓰고, 데이터파일의 호환은 힘들다면 포기한다.
또는 윈도우를 기준으로 데이터파일은 UCS2LE를 사용하고,
인텔CPU(리틀엔디안, LE)의 리눅스에선 UCS2LE를 UTF32LE(아마도)로 실시간 변환하여 처리하도록 한다.
이 외에 빅엔디안 유닉스도 알아서 UCS2LE의 파일을 UTF32BE로 알아서 변환하여 사용.

그럴려면 UCS2LE의 파일을 리눅스가 wchar_t로 읽어들여야 하는데
이건 간단하게 uint16_t를 중간자로 이용하면 가능하다.
간단히 예를 들자면
uint16_t a;
...fread를 이용해 a를 채움...
wchar_t b = (wchar_t)a;
이런식이다.


또는 데이터파일을 환경별로 만들어 사용한다!!!!

데이터파일을 생성하는 프로그램 a가 존재한다고 가정하자.
인텔CPU는 흔하니까 대충 리눅스 설치하고 a를 리눅스에서 컴파일한다면 바로 데이터를 리눅스용으로 만들 수 있다.

빅엔디안CPU는 제법 귀한편인데 아마 일반 PC로는 VM기술을 써도 쉽지 않다.
싸게 해결하는 방법은 클라우딩 서비스 같은 것을 빌리는 것이 있다.
그런데 사실 wchar_t 데이터를 LE->BE 전환은 매우 쉽다.
대충 리눅스용 a에서 파일저장할 때 end swap을 시켜서 저장하면 된다.
template <class T>
void endswap(T *objp)
{
  unsigned char *memp = reinterpret_cast<unsigned char*>(objp);
  std::reverse(memp, memp + sizeof(T));
}



그러나 변환시간을 고려해보면 그냥 바로 wchar_t로 읽어지도록 데이터파일을 개별로 저장하는 것이 좋아보인다.
정리하자면 데이터파일 자체가 반드시 호환되어야 하는 경우에는 iconv가 답으로 보인다.


기타 방법 : 새로운 c++ 표준을 사용한다.
아직 제대로 되는 컴파일러가 없는듯 하다.
표준이 어느정도 마련되었으나 오랜 시간을 기다려야할 것으로 보임.


기타 방법 : gcc에 각종 옵션을 써서 wchar_t가 2바이트가 되도록 해본다.
-fshort-wchar 라는 옵션이 있으나 사용하면 그 프로그램은 해당 플랫폼에서 작동 안한다고 보는게 .....
(내가 해봤을 땐 안됨)
잘은 모르겠지만 현재 안된다고 봄



코딩 꿀 팁들.

우선 locale을 프로그램 시작점에서 맞추는게 중요하다. 나는 아래와 같이 하였다.
한글판 윈도우가 cp949기본이고 리눅스는 보통 utf8로 셋팅하는 추세(?)니까 아래로 통일가능할 듯.

#ifdef WIN32
setlocale(LC_ALL, "Korean"); //윈도우는 cp949라서 이거인듯
#else
setlocale(LC_ALL, "ko_KR.utf8"); //리눅스는 일반적으로 utf8
#endif


소스 파일도 일종에 txt파일이며, 이 파일의 인코딩이 msvc와 gcc 둘 다를 호환시키려면
현재 gcc 4.8.2와 VS2013으로 테스트해본 바로는 utf8 bom( with signature ) 를 사용하는 것이 좋다.
예전에는 이래저래 문제가 많았는데 일단 지금 테스트해보니 잘 됨.


리눅스에서 터미널 화면에서 한글이 보이려면 코드변환 설정에 utf8이 되어야 한다.
예로 putty는
Window - Translation - Remote character set:
UTF8

그리고 글꼴 설정
Change settings
window - Appearance - Font settings - change...
굴림체. 스크립트 - 한글

우분투 로컬상태(터미널이 아니고 offline으로 pc 직접 제어) 콘솔에서 euc-kr의 한글 입력
마우스를 모니터 젤 위로 올리고
터미널 > 문자 인코딩 설정 > 추가/제거 > euc-kr 체크.
터미널 > 문자 인코딩 설정 > euc-kr 선택

기본 출력 방법. L 을 사용한다.
wprintf(L"wprintf 한글\n");

euc-kr을 유니코드용 함수에서 바로 사용하기. %hs를 사용한다.
wprintf(L"%hs\n", "여기는 한글입니다.");

안정적으로 wchar_t의 변수를 출력하는 방법. %ls를 사용한다. (소문자 엘 에스)
wchar_t w[100] = L"1직접w출력";
wprintf(L"%ls\n", w);
만약에 %s만 사용하면 윈도우에서만 정상 작동.


euc-kr 의 string을 wstring 으로 변환하는 함수.
(여러가지를 만들고 보고 분석해봤는데 그냥 다음이 젤 편하고 빠른듯)

void stringA2W(const string &str, wstring &wstr)
{
size_t size = str.length() +1;
wstr.reserve(size);
mbstowcs((wchar_t*)wstr.c_str(), str.c_str(), size);
}

참고 : mbstowcs를 이용하여 새로 생성될 wstring의 길이를 예측할 수 있는데, 이 방법이 항상 작동하는 것은 아니라고 한다.


이상한 현상 하나.
리눅스에서는 printf를 젤 처음에 사용하고나면 이후에 wprintf는 아무것도 출력하지 못함.
그리고 printf만 작동함.
반대로 wprintf를 젤 처음에 사용하면 이후에는 printf가 작동을 안하고 wprintf만 작동함.
이유는 모르겠음.





이상, 힘들게 알아낸 것들은 정리해보았다. 휴....

덧글

  • 지나가다 2015/03/27 23:44 # 삭제 답글

    한글이 유니코드로 리눅스에서는 4 byte인가 보군요.언듯생각해 보면 유니코드도 Hex로 변형된 것이고 협회에서 각 언어에 해당하는 Hex코드를 지정해 놨을텐데, 물론 윈도우나 리눅스에서 같은 코드표를 이용하겠지요, 왜 윈도우는 2byte고 리눅스는 4byte인지 이해가 좀 안가네요. 영어 이 외의 언어를 컴퓨터 언어로 알고리즘을 작성하는 일은 꽤나 귀찮고, 생각할 거리들이 많이 생기는 듯 싶네요. 물론 다른 분야도 마찬가지겠지만..
  • 오린간 2015/03/28 00:42 #

    윈도우와 리눅스가 wchar_t에 대해서는 hex로 표현해보면 최대 크기만 다르지 같은 코드를 쓴다고 볼 수 있습니다.
    그래서 윈도우의 wchar_t를 그대로 저장한 것을 리눅스가 읽을 때 중간값으로 uint16_t를 사용해서 바로 해결할 수 있습니다.

    다만 2bytes의 영역을 넘어가는 것을 윈도우는 표현이 불가능하고 리눅스는 4bytes까지 가능하구요.
    참, 2bytes를 넘어갈 경우에 ucs2를 안쓰고 UTF16을 쓴다면 wchar_t를 여러개 사용해서 표현할 수 있긴 합니다. 마치 euc-kr의 멀티바이트로 표현하는 것입니다.
    UTF16이 wchar_t를 여러개 사용해서 이것이 가능하게 하는 것을 서로게이트 라고 합니다. 자세한 것은 저도 모릅니다.

    현실적으로 2bytes를 넘어서는 영역은 잘 사용하지 않는 문자들이라서 그냥 포기하고 오직 2bytes로만 표현 가능한 문자를 사용하는 것을 ucs2라고 합니다.
    제가 알기로 대부분(java, c#, 윈도우 c/c++ 등)은 ucs2를 씁니다.
    이런 상황에서 보면 리눅스 c/c++이 4bytes를 쓰는 것은 여러모로 스트레스죠.... orz
  • 동네청년 2015/06/23 22:18 # 삭제 답글

    준철아. 글보니 잘사는거 같네ㅎㅎ 어렵다 뭔말인지 ㅎㅎ
  • 오린간 2015/06/24 02:11 #

    아 ㅎㅎ 태호형 안녕하세요 ㅋㅋ 잘 지내시나요?
  • 동네청년 2015/06/25 22:14 # 삭제 답글

    히히 잘살지... 그나저나 댓글 단 시각이 2시...뭘하며 사는거냐 도대체 ㅎㅎ 나 용인이여~ 올라오면 전화해 형이 맥주 사줄께
  • 오린간 2015/06/26 02:16 #

    ㅎㅎㅎ학교에서 자유롭게 연구하다보니 일케되네요! 용인 기억해두겠습니당
댓글 입력 영역
* 비로그인 덧글의 IP 전체보기를 설정한 이글루입니다.