본문 바로가기
개발 TIP/Error 모음

CStdioFile, CFile 한글 미인식 오류

by izen8 2012. 11. 15.
반응형




 

상단에 보이는 화면의 목록 데이터를 'CStdioFile' 을 이용해 텍스트(*.Csv) 형식으로 저장해야 하는 상황이 있어 사용했더니 정상 동작을 하지 않았다.


 

위화면에 보이듯이 목록 데이터를 CSting로 저장한 상태에서 다이얼로그에 뿌렸을때는 정상적으로 데이터를 가지고 있었지만


 

'**.Csv ' 로 내보내기 한 후에는 아무런 데이터도 저장되지 않았다.




원인을 몰라 막 헤매다 구글에서 "cstdiofile 오류"으로 검색을 해보니

http://blog.naver.com/PostView.nhn?blogId=corea139&logNo=50037792782&redirect=Dlog&widgetTypeCall=true

해결책을 제시해주었다.

 

CStdioFile 유니 코드 미인식 의한 문제

 

 #include <locale.h>

CStdioFile xxx;

char* pLocale = setlocale(LC_ALL, "Korean");

 

로 사용하시면 읽고 쓰기가 가능합니다.

로케일 문제인데 "Korean" 이 기본이라 바뀌는 것이 없지만 LC_ALL은 기본이 아닌것 같습니다.

필요하신 부분은 MSDN을 참고 바랍니다.

하지만 유니코드로 저장하면 메모장에서 바로 보이지 않기 때문에 멀티 바이트로 저장하는 것이 좋아 보이네요.

ps.

참고로 'COleDateTime' 에서 날짜 전체 이름을 가져오면 요일이 영문으로 표시되는데(시스템에 따라 다를수 있음) 이것 또한 로케일을 바꾸어주면 한글로 나오게 됩니다.







 

http://www.codeproject.com/Articles/4119/CStdioFile-derived-class-for-multibyte-and-Unicode

http://www.codeproject.com/Articles/7958/CTextFileDocument

 




//////////////////////////////////////////////////////////////////////////////////////////////
  로그기록 , 완성화일 (테스트 마침 )
//////////////////////////////////////////////////////////////////////////////////////////////
CFile file ;
USHORT nShort = 0xfeff;  // Unicode BOM(Byte Order Mark).
file.Open( strFileName , CFile::modeCreate | CFile::modeNoTruncate | CFile::modeReadWrite  );
int nLen = 0 ;
nLen = strMsg.GetLength() ;
    file.SeekToEnd();  
file.Write(&nShort,2);
file.Write( (LPCSTR)(LPCTSTR)strMsg , nLen*2 );
file.Write(&nShort,2);
file.Write("\n", 2 );
file.Close();

//////////////////////////////////////////////////////////////////////////////////////////////
VS2008 유니코드 환경 

리스트컨트롤의 내용을 CSV로 저장할 때 숫자나 영어는 잘 저장되는데 한글만 깨져서 저장되는 경우가 있다.

해결법 1

BOM 사용.
유니코드 파일로 저장하기 위해서는 파일 맨 처음으로 2 바이트(UTF8 이라면 3 바이트)의 BOM 값이 추가되어져야 한다

CFile file;
if(file.Open("c:\\temp.csv", CFile::modeCreate | CFile::modeWrite))
{
    return;
}

//추가 부분 (2줄)
USHORT nShort = 0xfeff;  // Unicode BOM(Byte Order Mark).
file.Write(&nShort,2);
file.Write(pStrWideChar, nSize*2);
file.Close();

//////////////////////주석문입니다////////////////////////////////////////
//  Byte-order mark   Description 
//  EF BB BF    UTF-8 
//  FE FF     UTF-16/UCS-2, little endian   windows
//  FF FE     UTF-16/UCS-2, big endian 
//  FF FE 00 00    UTF-32/UCS-4, little endian. 
//  00 00 FE FF    UTF-32/UCS-4, big-endian. 
//////////////////////////////////////////////////////////////////////////

메모장에 "abcd" 라고 입력하고 각각의 타입으로 저장후 헥사에디터로 열어보았다.
(코드값 사이의 스페이스는 무시해서 보면 된다. 가령, "FF FF" 는 2바이트일뿐이다.)
참고로 'a' 의 아스키값은 61.
ASCII: (순수한 값만 존재한다.)
61 62 63 64

UNICODE(Little Endian): (헤더가 존재하고 한바이트 알파벳이 두바이트로 표현되었다. 가령 'a' 는 61 00)
FF FE 61 00 62 00 63 00 64 00
UNICODE(Big Endian): (리틀엔디안과 다른점은 두바이트씩의 짝이 앞뒤로 바뀐점뿐. 가령 'a' 는 00 61. 헤더도 앞뒤가 바뀐점에 유의.)
FE FF 00 61 00 62 00 63 00 64
UTF-8 : (헤더가 3바이트란것에 유의. 알파벳은 1바이트로 표현되었다.)
EF BB BF 61 62 63 64

이 방법을 쓸경우 한글도 정상적으로 저장되는데 리스트컨트롤 내용을 CSV파일로 저장하는경우, 왼쪽 한 열에 모두뭉쳐서 들어가 있다. 엑셀파일에 각 콤마(,)별로 행열을 잘 구분지어서 저장되게 하려면 아래의 해결법2를 사용하면된다.

해결법2

주의 : 리스트컨트롤 내용 가져오는 소스에서 줄바꿈할때 "\r\n"을 하면 두줄 바꾼다 "\n" 이렇게 써줄것

#include "locale.h"
char* pLocale = _tsetlocale(LC_ALL, L"Korean");
//생성자에 _tsetlocale(LC_ALL, L"Korean"); 만 추가해줘도 된다

CStdioFile file;
if(file.Open("c:\\temp.csv", CFile::modeCreate | CFile::modeWrite))
{
    return;
}

file.WriteString(pStrWideChar);
file.Close();

리스트 컨트롤 내용이  csv 파일로 행과 열을 잘 구분지어서 저장된걸 확인할 수 있다.


 

http://www.bomuldanji.com/jacob/bank/1__VC/VC_2010%EB%B2%84%EC%A0%84/201204112_%ED%8C%8C%EC%9D%BC%EB%81%9D%EC%97%90%EC%B6%94%EA%B0%80%ED%95%98%EB%8A%94%EB%B0%A9%EB%B2%95CFile%EB%B2%84%EC%A0%84fSeek_%ED%95%9C%EA%B8%80%EA%B9%A8%EC%A7%90%EC%97%90%EB%9F%AC.txt

 






_wsetlocale(LC_ALL, L"korean" );


유니코드 프로젝트에서, CFile을 이용하여서 유니코드 문자를 파일에 입력하면

깨지는데요, 이를 해결하기 위한 방법입니다.

CFile에서 유니코드를 정상적으로 표현하기 위해서는 파일 맨 앞에, 2바이트의 Byte Order Mark를 추가하여서 이 파일이 유니코드 파일이라는것을 운영체제에 알려주어야합니다.

유니코드의 경우는 바이트오더마크가 0xfeff 입니다. 파일 맨 앞에 0xfeff 를 적어주시면 유니코드가 깨지지 않고 정상적으로 나오시게 됩니다.

예제
    CFile file(directory,CFile::modeCreate | CFile::modeNoTruncate |CFile::modeReadWrite);
    USHORT sBOM = 0xfeff;  

    file.Write(&sBOM,2);  
    file.Write(L"가나다라", lstrlen(L"가나다라")*2);  
    file.Close();  

file.Write에서 두번째 인자 (문자열의 길이를 의미함)에 *2 를 해준 이유는, 유니코드는 한 글자당 2바이트이기 때문입니다.



c++에서 ifstream이나 ofstream으로 파일을 열고 생성할 때, 경로명 또는 파일명에 한글이 포함되면 파일 입출력이 동작하지 않는다. 경험이 없다면 찾기 힘든 문제이다.


해결법은 setlocale(LC_ALL, "")을 호출해 주면 된다. 파일 입출력 루틴 앞에 해 주어도 되고 프로그램 시작 부분에 해도 되고 아무 곳에서나 한번만 호출해 주면 된다.


setlocale(LC_ALL, "Korean")을 해 주어도 동작하지만 컴퓨터의 시스템 지역 설정을 따르게 하는 setlocale(LC_ALL, "")를 사용하는게 좋을 것 같다. 한글을 사용하는 사람들은 지역설정(locale)을 한글로 해 놓았을 테니까 ^^


일시적으로만 locale을 바꾸고 싶다면 다음과 같이 처리한다.


// 현재 locale을 저장

char *old_locale_tmp = setlocale(LC_ALL, NULL); // 임시 locale 문자열 주소 반환

char *old_locale_saved = strdup(old_locale_tmp); // 반환받은 old locale 문자열 주소가 이후 setlocale 호출로 변경되지 않도록 별도로 메모리를 할당받아서 저장


// locale을 변경하고 필요한 작업을 수행

setlocale (LC_ALL, new_locale);

...


// locale을 복구

setlocale(LC_ALL, old_locale_saved);

free(old_locale_saved);



반응형

댓글