파일검색 프로그램 Version 0.5



파일검색 프로그램 Version 0.5



1.제작환경
   - Windows xp professional SP 3
   - Visual Studio.NET(2003) Dialog based MFC
   - cLucene core 0.9.21


2. 목적
   - 개인 컴퓨터 내 특정 폴더를 인덱싱한 후 지정한 검색어를 가지는 텍스트 파일 검색


3. 내용


<메인화면>



메인화면이 조금 조잡하긴 하지만,

그래도 초기버젼 보다는 많이 개선되었다.

그리고 초기버젼에 포함되었던 콘솔창이 오류를 야기하기 때문에

콘솔창은 없애버렸다.

콘솔창을 없앰으로써 프로그램이 조금은 깔끔해진 것 같다.









현재 색인 생성은 되지만, 색인 업데이트는 제대로 되지 않는다.

폴더에 파일을 추가했을 경우에 색인을 새로 생성해야 하는

문제점이 있긴하지만,

아직 어떻게 업데이트를 해야 효율적으로 될지 잘 모르겠다.




<특정 폴더에 대한 색인 생성>




색인 생성 시 파일의 인코딩을 체크하고 모두 UTF-8 로 변환한 다음

Lucene에 입력할수 있도록 UTF-32로 다시 변환하는 과정을 거친다.

현재 영어, 한국어 뿐만 아니라

중국어, 일본어 에 대한 인코딩까지 감안해서 변환하게 되어있다.

iconv library를 사용해서 이렇게 쉽게 변환될 줄 알았다면,

미리 iconv 사용법을 공부해 놓을걸 하는 생각이....






<색인 한 폴더에 대해 검색>



색인한 폴더에 대해 검색어 입력을 통한 파일 검색을 한다.

검색한 파일 리스트를 선택하면 텍스트 파일 뷰어에서 열어볼수 있다.

텍스트 뷰어에서 입력한 검색어를 강조 해서 볼수 있다.

검색어 강조 부분을 개발하는데만 꼬박 이틀을 소요했다는....

그래도 가끔 버그가 발견 되네.



또한,

cLucene에 기본적으로 포함되어 있는 StandardAnalyzer 로는

한글, 일본어, 중국어 색인과 검색이 제대로 되지 않기 때문에,

mix1009 님이 만든 CJK Analyzer를 사용했다.
(mix 1009 님 감사합니다. ^^)

출처: http://mix1009.net:82/category/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D?page=4




<HTML Viewer>



검색된 파일이 HTML 파일일 경우 HTML 뷰어에 출력하게 했다.

그렇지만, HTML 뷰어에서 빈번하게 오류가 발견된다.

HTML 뷰어상에 검색어를 강조하기 위해 <font>와<b> 태그로

검색어를 Replace 하는데 검색어가 HTML 태그일 경우

문제가 발생하게 된다.

HTML 문서를 파싱해야 하는건가?

모르겠다. 어떻게 해야할지.......ㅜㅜ



<중국어 파일 검색>




<일본어 파일 검색>




중국어와 일본어 파일에 대한 검색 도 가능하다.

그렇지만, CJK Analyzer 또한 문자를 2개로 잘라서 색인하기 때문에

검색에 대한 효율은 그다지 높지않다.

현재로선 여기까지 검색되는 단계에서 만족해야 할듯하다.






4. 수정/보완해야 할 사항

   - 로그파일 생성

   - 색인 폴더 저장시 한글 폴더가 있는 경우 한글저장 안됨

   - 색인 생성 시 Multi Thread를 사용해서 중간에 중지 시킬수 있도록 수정

   - 색인 생성시 화면이 다시 그려지지 않는 것 수정

   - HTML 화면 출력 시 제대로 출력되지 않는 오류빈도가 잦음.




 
첨부파일 : FileSearcher_V0.5.zip




by 나도야 | 2009/11/05 16:59 | 트랙백 | 덧글(0)

생각하는 대로 살지 않으면 결국 살아온 대로 생각하게 된다



생각하는 대로 살지 않으면 결국 살아온 대로 생각하게 된다




Il faut vivre comme on pense, sans quoi l'on finira par penser comme on a vécu.
One must live the way one thinks or end up thinking the way one has lived.

-Paul Bourget-

생각하는 대로 살지 않으면 결국 살아온 대로 생각하게 된다.

프랑스 작가이자 비평가인 폴 부르제(Paul-Charles-Joseph Bourget, 1852~1935). 시인 Paul Valery(1871-1945)는 다른 인물임.





里立
에 세워진 나의 좌우명. 늦게 세워진 만큼 더 확고하고 확실해질테지...







출처: http://gostopgo90.tistory.com/493?srchid=BR1http%3A%2F%2Fgostopgo90.tistory.com%2F493

by 나도야 | 2009/10/30 11:00 | [기타] 나의 좌우명 | 트랙백 | 덧글(0)

VC++과 MySQL 연동하기


웹 크롤러와 RSS 리더기를 개발하면서 DB의 필요성을 절실하게 느꼈지만, 실제로 DB를 사용할 줄 몰라서 DB를 사용하지 않았더니 퍼포먼스가 너무 안좋아서, 결국 DB를 적용해서 수정해야 겠다.

간단하게 배울수 있는 SQlite3 이나 MySQL을 사용해서 수정할 예정이다.


출처 : http://ngps.net/ngpiki/index.php?display=MySqlForCApi
=============================================================================================================================
간단하게나마 DB에서 가장 많이 사용되는 ( 게임용 DB 로도 가장 많이 사용 되는 ) MySQL For C API 에 대해서 알아 보고 최후로 DB 접근을 하기 위한 종단 서버 구성으로 마쳐 볼까 합니다 :) 이것 역시 언제 끝날지는 알 수 없습니다 시간 날 때마다 하겠습니다. 개인적으로 sql query문이 제가 약한 관계로 :) 제대로 된 글이 될 거라는 생각은 안 합니다.

휴 정말 간만에 들어오네요 :) 제가 너무 정신이 없어서 ( 논다고 바빳습니다. 기다리는 분은 전혀 없었겠지만 ) 2번에 나놔서 정리 할까 합니다.

첫번째로는 함수 나열과 정리를 하고 두번째는 실전 소스로 나눠서 정리할까 합니다.

실제 많은 api함수들이 있지만 자주 사용하는 것 몇가지만 알아보겠습니다.

아.. VS6에서는 컴파일을 위해서는 MYSQL/INCLUDE 폴더를 포함시켜 주시고 libmysql.lib를 포함 시키지 않으면 반가운 링크에러들이 뜰겁니다. 리눅스에서는 make 파일에 여러가지를 추가 시켜야겠죠.

정확한 include 파일 경로를 찾으려면,

# find / -name mysql.h


정확한 공유 라이브러리 파일 경로를 찾으려면,

# find / -name libmysqlclient.so


하시면 됩니다.


INC     = -I/usr/local/mysql/include/       // 기본적인 mysql.h 위치입니다.
LIB     = -L/usr/local/mysql/lib/           // libmysql.lib 이 있는 곳의 위치 입니다.
LIBS    = -lmysqlclient                     // lib 파일이죠

$(TARGET) : $(OBJS)
        $(CXX) $(LDFLAGS) $(LIBS) -o $(TARGET) $(OBJS)




와 같이 하면 컴파일에 문제가 없을겁니다. 컴파일 얘기는 다음에 한번더 자세하게 makefile과 함께 하고 오늘은 자주 사용하는 함수만 정리 하겠습니다.


mysql_init()
mysql 초기화 함수 입니다. return 값은 연결식별값 (MYSQL*) 실패하면 FALSE 가 리턴 됩니다.
mysql_connect() 또는 mysql_real_connect()
mysql 접속 함수 입니다. mysql_real_connect() 함수만을 이용합니다.
mysql_select_db()
mysql DB 선택 함수입니다. 어떤 DB를 선택 할것인지 하는 것이죠
mysql_close()
mysql을 서버와의 접속을 끊습니다.
mysql_query()
쿼리를 실행 시킵니다.
mysql_fetch_row()
Result Set 에서 하나의 로우를 배열로 가져 옵니다.
mysql_store_result()
Result Set 을 저장 합니다.
mysql_free_reslut()
Result Set 을 메모리에서 제거 합니다.
mysql_errno()
에러 번호를 리턴합니다.
mysql_error()
에러에 대한 설명을 리턴합니다.



이제는 저 위에 함수들을 어떻게 사용 하는가에 대해서 간단한 예문을 통해서 알아 볼까 합니다. 지금 위키에서 바로 코딩을 하는 관계로 컴파일이 안됄수도 있습니다 (__) 그냥 어떻게 사용하는가만 알아 보자는 겁니다. 쿨럭;;

#define dDB_HOST  "아이피"
#define dDB_PORT  3306
#define dDB_ID    "아이디"
#define dDB_PW    "db 패스워드"
#define dDB_NAME  "DB 명 "

#include <mysql.h>

MYSQL* mysql;

MYSQL *DBConnect( char * host , int port , char *id , char *pw , char *dbName )
{
        MYSQL *db = NULL;

        db = mysql_init( (MYSQL*)NULL );                // 초기화 함수

        if( db )
        {
                if( mysql_real_connect( db, host, id, pw, NULL, port, (char*)NULL, 0 ) ) // DB 접속
                {
                        if( mysql_select_db( db, dbName ) != 0 ) // DB 선택
                        {
                                mysql_close( db );
                                return NULL;
                        }
                }
                else // connect error
                {
                        printf( "Error %d ( %s )n", mysql_errno( db ), mysql_error( db ) );
                        mysql_close( db );
                        return NULL;
                }
        }
        else
                return NULL;
        return db;
}

int main()
{
        mysql = DBConnect( dDB_HOST , dDB_PORT, dDB_ID , dDB_PW, dDB_DBNAME );
        if( ! mysql )
        {
                return -1 ;
        }

        char Query[128];
        sprintf( Query," select ......등등 필요한 쿼리들" );

        if( !mysql_query ( mysql, Query)  )
        {
                MYSQL_RES *result = mysql_store_result( mysql );

                if( result )
                {
                        MYSQL_ROW row;

                        row = mysql_fetch_row( result );
                            // 그리고 각 원하는 것들을 여기에다가 변수에 저장 한다.
                            // id  = atoi ( row[0] ) ; 이런 식으로 ...
                }
                mysql_free_result( result );
        }

        mysql_close( mysql );
        return 0;
}


정말 간만에 수정 하는 군요 -_-+ 몇마디 더 추가 하고자 이렇게 글을 올리게 됐네요


$ gcc -o mysql mysql.c -I/usr/local/include/mysql -L/usr/local/lib/mysql -lmysqlclient




이런식으로적으면 컴파일이 돼겠습니다요 이렇게 하면 mysql 이라는 게 생기겠죠 뭐 더 확실하게 알아 보고자 하면 연결 완료 나 종료에 대해서 printf 문등을 통해서 알아보던지 아니면 로그파일로 남기셔도 될듯하고요 여기서 주의 할점은 Mysql For C API를 사용할때는 -lmysqlclient 이 녀석입니다. 라이브러리를 연결 하는 녀석이죠 libmysqlclient.so 를 찾아서 링크 시켜 주는 역할을 합니다.

자 여기서 C API 의 자료형에 대해서 몇가지 알아 보겠습니다.
MYSQL
Database와의 연결을 관리하는 구조체입니다.
MYSQL_RES
SELECT 등 결과를 리턴하는 query의 결과를 나타내는 자료형입니다.
MYSQL_ROW
MYSQL_RES에서 하나의 레코드씩 값을 얻어 올때 쓰이는 자료형입니다.
MYSQL_FIELD
필드의 이름과 필드의 타입 등 필드에 관한 정보를 저장하는 자료형입니다.



(에러 문구는 mysql for C API 문서에서 발견을 했는데 출처를 모르겠군요 )

이번에는 Mysql For C API 를 이용하면서 나올 에러들에 대해서 대처해보기 위해서 에러에 대해서 알아 보겠습니다.

1) "msyql.c:2: mysql.h: 그런 파일이나 디렉토리가 없음"

이 경우는 mysql.h의 경로를 찾을 수 없어서 나는 에러입니다. 다시 한번 mysql.h가 어디에 있는지 확인을 하고, -I옵션으로 그 경로를 지정해야 합니다. 이럴때 whereis 나 find 등을 잘써서 찾아 봅시다. 뭐 기본적으로 기본 경로에설치하면 아무 문제 없습니다.

2) "mysql.o(.text+0x11): undefined reference to `mysql_init'"

위와 같이 'undefined reference......' 라고 나오는 에러는 -lmysqlclient 옵션을 안줘서 생기는 에러 입니다.

3) "ld: cannot open -lmysqlclient: 그런 파일이나 디렉토리가 없음"

위의 에러는 -L옵션 뒤에 붙은 라이브러리의 경로가 잘못 되었을때 생깁니다. libmysqlclient.so 파일의 경로를 찾아서 그 경로 지정을 해주시면 됩니다.

4) "Can't connect to local MySQL server "

위의 에러는 MySQL의 서버에 연결을 할 수 없다는 메시지로서, MySQL 서버의 데몬이 실행 중이지 않을 때 나오는 메시지입니다.

5) "Access denied for user: 'root@localhost' (Using password: YES)"

사용자 아이디를 잘못 입력하거나, 암호를 잘못 입력하였을 때 나오는 메시지입니다. MySQL의 사용자는 모두 mysql database의 user 테이블에 있습니다.

6) "./sql: error in loading shared libraries libmysqlclient.so.6: cannot open shared object file: No such file or directory"

MySQL의 라이브러리를 열지 못한다는 메시지입니다. 컴파일 할 때 MySQL의라이브러리를 사용하는데, 동적 라이브러리이므로 실행시에도 라이브러리가 필요하게 된다. ibmysqlclient.so가 /usr/lib 혹은 /usr/lib/mysql 디렉터리에 존재 하지 않을 경우에 발생하는 문제입니다. /etc/ld.so.conf 파일에libmysqlclient.so가 있는 경로를 적어 준 후에 ldconfig 라는 명령을 프롬프트에서 실행하여 주면 됩니다.

by 나도야 | 2009/10/26 11:51 | [開發] 프로젝트 관련 | 트랙백 | 덧글(0)

각종 문자열 변환 방법



프로그래밍하는데 문자열 처리하기가 너무 번거롭다.



출처: http://boyfox009.egloos.com/1144576
--------------------------------------------------------------------------------------------------------------

TCHAR --> CString

CStirng sSring;
TCHAR sTString = _T("가나다");

sString.Format(_T(%s), sTString);


CString --> TCHAR

TCHAR * des = new TCHAR[sString.GetLength() + 1]; // 1은 NULL을 위한 여유공간
_tcscpy(des, NowSaving.GetBuffer(0));


String --> UTF - 8

char buffer[20];
WCHAR Unicode[20];
char UTF8code[20];

int nUnicodeSize = MultiByteToWideChar(CP_ACP, 0, lpID, strlen(lpID),
                                       Unicode, sizeof(Unicode));

int nUTF8codeSize = WideCharToMultiByte(CP_UTF8, 0, Unicode, nUnicodeSize,
                                        UTF8code, sizeofUTF8code), NULL, NULL);

nUnicodeSize = MultiByteToWideChar(CP_UTF8, 0, UTF8code, nUTF8codeSize,
                                   Unicode, sizeof(Unicode));

memcpy(buffer, UTF8code, nUTF8codeSize);

이렇게 하면 buffer에 UTF-8로 변환

UTF-8 --> Unicode

void CCPimFONEDevice::UTF82UNICODE(char * strSrc, wchar_t *strTarget)
{
  int nSize = MultiByteToWideChar(CP_UTF8, 0, strSrc, -1, 0, 0);
  MultiByteToWideChar(CP_UTF8, 0, strSrc ,  -1 , strTarget, nSize);
}


char --> TCHAR

char charBuff[]="test";
TCHAR szUniCode[256]={0,};
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, charBuff, strlen(charBuff),
                    szUniCode, 256);


CString --> char*

//EVC는 기본적으로 유니코드를 사용하기 때문에 CString을 char*로 변환할 수 없음..
nToWrite = lpByte.GetLength();
char *pAnsiString = new char[nToWrite+1];

memset(pAnsiString,0,nToWrite+1);
wcstombs(pAnsiString, lpByte, nToWrite+1); // wchar_t배열을 multibyte 문자열로 변환한다.


=====================================================================================

유니코드로 저장하기..

 TCHAR szPath[] = _T("1234567890");
 int nLen = _tcslen(szPath);
 CFile file(_T("D:\\Down\\test.xml"), CFile::modeCreate|CFile::modeWrite);
 TCHAR szTmp[2];
 WORD wd = 0xfeff;
 memcpy(szTmp, &wd, 2);
 file.Write(szTmp, 2);
 file.Write(szPath, nLen*sizeof(TCHAR));
 file.Close();


여기서 핵심은 FEFF 이다.
유니코드의 식별자란 것인데, 파일의 처음부분에 넣어주면 유니코드라고 선언하는 셈이 된다.
(원래 규격은 FFFE 일것이지만, IBM PC 는 리틀엔디언 방식이므로 바이트 위치를 반대로 두어야 한다.)

메모장에 "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



UTF-8 로저장하기...

//UTF-8 로 저장하기
int UnicodeToUtf8(TCHAR* pUnicode, char** pUtf8)
{
 int len = ::WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)pUnicode, -1, NULL, 0, NULL, NULL);
 *pUtf8 = new char[len];
 ::WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)pUnicode, -1, *pUtf8, len, NULL, NULL);

 return len;
}
 // UTF 0xEFBBBF
 TCHAR szTmp[2];
 WORD wd = 0xbbef;
 memcpy(szTmp, &wd, 2);
 file.Write(szTmp, 2);
 BYTE byte = 0xbf;
 memcpy(szTmp, &byte, 1);
 file.Write(szTmp, 1);
 char *pUtf8 = NULL;
 nLen = UnicodeToUtf8(szXML, &pUtf8);
 file.Write(pUtf8, nLen-1);
 delete []pUtf8;


이번엔  메모장에 "한글" 라고 입력하고 각각의 타입으로 저장후 헥사에디터로 열어보았다.

참고로 '한글' 의 아스키값은 C7 D1 B1 DB.

ASCII: (순수한 값만 존재한다.)
C7 D1 B1 DB

UNICODE(Little Endian): (헤더는 알파벳과 같지만 내용부분의 코드가 확 바껴 버렸다! 당연한 결과인가.)
FF FE 5C D5 00 AE

UNICODE(Big Endian): (마찬가지로 한쌍씩의 바이트 위치가 바꼈다.)
FE FF D5 5C AE 00

UTF-8 : (헤더가 3바이트란것은 똑같지만 이게 어떻게 된일? 한글자에 3바이트가 사용되었다! 흐음... 이것은 좀 놀라운 일이다.)
EF BB BF ED 95 9C EA B8 80


by 나도야 | 2009/10/23 13:05 | [開發] 프로젝트 관련 | 트랙백 | 덧글(0)

MFC에서 UNICODE로 개발하기



MFC에서 Lucene을 사용해서 개발하는데 MULTIBYTE로 개발하면 피본다. UNICODE로 개발해야 문제가 안생긴다.





출처 : http://eslife.tistory.com/entry/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C%EB%A1%9C-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0

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

MFC 로 거의 10년이 넘게 유지 보수해 오던 회사 프로젝트를 드디어(이제야?) 유니코드로 이전한다고 합니다.  막상 유니코드로 이전하려고 하니, 그 동안 익숙하게 사용해 오던 함수들, 특히 strcpy, memcpy 와 같은 표준 C 라이브러리가 애물단지가 되고 말았습니다. 진작에 갔어야 하는데, 차일피일 미루다 보니 한번에 수정하기엔 너무 부담스러울 정도로 덩치가 커져 버렸네요.

유니코드를 위해 엄청난 소스 수정을 앞두고(저희 회사 프로그램 소스가 제가 생각해도 엄청(?)납니다)스스로 하나씩 배워가고 있는 내용(아주 초보적인 내용이긴 합니다만)을 조금 정리했습니다.

 

유니코드 에 대해서

 

유니코드를 알려면 현재 저희가 너무나 친숙하게 사용하고 있는 ASCII 코드를 알 필요가 있습니다. ASCII 코드는 한 바이트의 코드로 모든 영문자를 저장할 수 있는 아주 간단한 코드 테이블입니다. PC 가 주로 영문 권에서 만들어지다 보니 ASCII 는 당연히 표준처럼 쓰였고, 한 바이트로는 표현할 수 있는 글자 수가 너무 많은 우리나라와 같은 많은 나라들은 각기 ASCII 코드를 확장해서 2바이트로 표현하기 시작했습니다.

글자 한자를 표현하기 위해 너무나 복잡한 코드 체계가 존재했기 때문에 이러한 혼동을 잠재우기 위해 지구상의 모든 글자를 담을 수 있는 코드 테이블을 만든 게 유니코드입니다.

이러한 유니코드를 실제 바이트에 표현하기 위해 여러 가지 인코딩 방식이 등장했습니다.

그 중에 가장 널리 쓰이는 방식으로 하나의 글자 표현에 2바이트를 사용하는 UCS-2(2바이트 사용) 또는 UTF-16(16비트사용) , 영어 문자는 한 바이트만 사용하고 그 외 나라 글자들은 2바이트~최대 6바이트까지 할당하는 UTF-8 등이 있습니다.

 

유니코드 관련 조엘 온 소프트웨어: 유쾌한 오프라인 블로그” 4개발자가 꼭 알아둬야 할 유니코드와 문자 집합에 대한 고찰부분을 읽어 보시길 권해 드립니다 (이전에 읽었던 부분인데 이번에 유니코드 조사 하면서 다시 읽으니 느낌이 틀리더군요. 쉬우면서도 통쾌한 글쓰기에 한해서는 조엘을 따라올 사람이 없어 보입니다.)

 

유니코드 데이터 타입

1.     유니코드  - Wide Character 16비트 코드를 가집니다. 윈도우 NT 운영체제에서는 이러한 2바이트 짜리 유니코드를 내부적으로 사용하고 있습니다. 2바이트로 고정된 문자길이는 MBCS와 달리 프로그램으로 다루기 쉽게 되어 있습니다. (한 글자는 무조건 2바이트이니까) C/C++ 에서는 wchar_t 배열로 표현되고, 포인터는 wchar_t* 로 표현할 수 있습니다.

2.     MBCS/DBCS – MBCS(Multi-Byte Character Set)은 하나의 문자가 한 바이트 이상으로 구성된 문자열 셋을 말합니다. 이제 거의 사용되지 않는 윈도우 9x 계열의 운영체제는 MBCS로 글자를 표현하고 있습니다. DBCS MBCS 의 한 유형으로, 특정 문자는 한 바이트, 다른 문자는 여러 바이트를 사용해서 글자를 표현하고 있습니다. (주로 한국, 일본, 중국 등과 같이) C/C+ 에서는 MBCS/DBCS unsigned char 배열로 표현하고, 포인터는 unsigned char* 로 표현합니다. 문자가 한 바이트 또는 두 바이트로 이뤄지다 보니 한 글자 단위로 옮겨 다니기 위해 머리 아프게 계산해야 할 수 있는데 이를 위해 ChartNext, ChartPrev 같은 함수가 제공됩니다.
Visual C++
에서는 MBCS DBCS 를 의미하고, 한 글자는 한 바이트 또는 두 바이트로만 이뤄질 수 있습니다. (두 바이트 초과되는 글자는 지원하지 않습니다)

3.     ANSI – 8비트만으로 모든 영문 문자열과, 서유럽 문자열을 표시할 수 있습니다. 마이크로소프트 윈도우 ANSI 문자 셋은 ANSI 표준에 근거하여 ISO 8859/x 와 일부 문자열을 추가된 형태입니다. ANSI SBCS 로 불리고 C/C++ 에서는 ANSI 문자열을 char 배열, char* 로 표현할 수 있습니다.

4.     TCHAR/_TCHAR – 오늘의 주인공입니다. TCHAR 는 마이크로소프트에서 만든 문자열 TYPE 으로 컴파일 타임에 지정된 옵션에 따라 문자열을 유니코드, MBCS, ANSI 각각으로 매칭될 수 있도록 해 주는 타입입니다.  TCHAR 를 사용함으로써 상황에 맞는 문자열처리를 할 수 있게 됩니다.  마이크로소프트에 특화된 C 런타임 라이브러리 헤더 파일인 tchar.h 는 일반 텍스트 타입을 _TCHAR 로 선언하고 있습니다. (ANSI C/C++ 컴파일러 호환성을 유지하기 위해 컴파일러가 별도로 타입 정의한 내용은 앞에 언더바(‘_’) 를 붙여 표시하도록 하고 있기 때문에 _TCHAR 라고 합니다) 만약 Pre-define __STDC__ 를 선언하지 않으면(디폴트로 선언되어 있지 않습니다) ANSI 호환성을 사용하지 않겠다는 의미가 되고 다시 tchar.h 에는 _TCHAR TCHAR 로 선언해서 사용할 수 있도록 하고 있습니다.  32 운영체제용 헤더 파일인 winnt.h 에도 TCHAR 가 선언되어 있는데 이 헤더 파일은 ANSI 컴파일러 호환성과 상관이 없기 때문에 _TCHAR 대신 TCHAR 로만 선언하고 있습니다.

 

 

Win32 API 에서 텍스트 다루기

 

문자열을 입력으로 요구하는 모든 Win32 API 에는 MBCS 버전(MBCS를 사용하지 않는 윈도우는 ANSI 버전으로 취급), 유니코드 버전 2가지를 모두 제공합니다.
실제로 윈도우의 타이틀을 변경하는 SetWindowText API SetWindowText 라는 API 가 존재하는 것이 아니라 UNICODE 설정여부에 따라, SetWindowTextA(ANSI 버전) SetWindowTextW API 가 실제 불려지게 됩니다.


#ifdef UNICODE

#define SetWindowText  SetWindowTextW

#else

#define SetWindowText  SetWindowTextA

#endif // !UNICODE


 윈도우 NT 계열에서는 내부적으로 유니코드 문자열만 사용하기 때문에 SetWindowTextA 라고 호출하면, 내부적으로 문자열을 유니코드로 변경한 다음 다시 유니코드 버전인 SetWindowTextW 함수를 호출한다고 합니다. (API 호출 성능이라는 입장에서 보면 유니코드버전으로 개발하는 것이 성능적인 이점이 있습니다)
유니코드나 MBCS 용 모듈을 그때그때 필요에 따라 만들기 위해서는 char wchar 와 같은 변경하기 힘든 문자열 타입이 아니라, 컴파일 시점에 자동으로 타입 전환이 가능한 TCHAR 를 이용하는 것이 중요합니다.

 

유니코드 프로젝트 세팅하기

 

유니코드로 프로젝트를 빌드하기 위해서는 UNICODE   _UNICODE를 전처리기에 등록해야 합니다. UNICODE 는 윈도우 API 처리 함수에서 사용하는 전처리기이고, _ UNICODE C 런타임 라이브러리에서 판단하는 라이브러리입니다.  Visual Studio 2003 이상 버전(2005, 2008 포함) 에서는 프로젝트 속성에서 간단하게 사용할 문자 집합을 지정할 수 있습니다.

사용자 삽입 이미지
 

유니코드 텍스트 파일

 


일반적으로 메모장(notepad) 에서 텍스트를 작성하고 저장하면 ANSI 포맷으로 저장됩니다. 유니코드로 파일을 저장하려면 새 이름으로 저장하기를 눌러 인코딩 방식을 선택할 수 있습니다.


사용자 삽입 이미지


유니코드로 저장된 파일을 읽어 보면 재미있는 사실을 발견하게 됩니다.

Hello

라고 5자로 저장한 텍스트 파일을 바이너리 에디터로 열어 보면 아래와 같이 저장되어 있음을 알 수 있습니다.

ff fe 48 00 65 00 6c 00 6c 00 6f 00

여기에서 FF FE 는 이 텍스트 파일이 유니코드로 저장되어 있음을 얘기하고, 나머지 텍스트들은 UCS-2 2바이트 유니코드로 저장되어 있습니다.

유니코드임을 나타내는 문자는 원래 FE FF 인데, Big Endian 에서는 원래대로 FE FF 로 기록되고, IBM-PC 계열인 윈도우에서는 Little Endian 방식이라 FF FE로 바이트 위치가 역전되어 저장됩니다.

 

CStdioFile 과 같은 MFC 파일 클래스는 유니코드에서 어떻게 작동할까요?

MFC 에서 텍스트 파일 저장 및 불러오기에 자주 사용하는 CStdioFile 클래스의 WriteString 으로 기록한 파일은 유니코드형식일까요? 아니면 ANSI 버전일까요?  처음 생각은 유니코드 버전에서 이 함수를 호출하면 당연히 유니코드로 저장된다고 생각했습니다.


CStdioFile file;

file.Open(_T("c:\\work\\abc.txt"), CFile::modeWrite | CFile::modeCreate | CFile::typeText);

file.WriteString(_T("Hello"));


하지만, 유니코드 버전에서 저장을 해도 파일 형식은 여전히 ANSI 버전이었습니다.

마찬가지로 위에서 만든 텍스트 파일을 ReadString 을 통해 읽어오면 ANSI 텍스트 파일이 자동으로 wchar_t 로 문자열로 다시 저장되는 것을 확인할 수 있었습니다.


file.Open(_T("c:\\work\\abc.txt"), CFile::modeRead | CFile::typeText);

TCHAR tRead[100];

file.ReadString(tRead, sizeof(tRead) / sizeof(TCHAR));

file.Close();

 

마지막으로 abc.txt 파일을 메모장을 이용하여 유니코드 포맷으로 변경한 후 다시 CStdioFile:: ReadString 으로 호출하면 어떻게 되는지도 테스트 해 봤습니다. 결과는 역시 깨집니다. 결국 CStdioFile 에서는 ANSI 텍스트만 다뤄야 하는 것으로 보입니다.
관련해서 구글링을 해 보니 다음처럼 유니코드 파일을 지원하는 버전을 별도로 개발한 사람도 있네요

A UTF-16 Class for Reading and Writing Unicode Files

CStdioFile-derived class for multibyte and Unicode reading and writing

 

이와 유사하게 WritePrivateProfileString 함수로 저장한 ini 파일도 기대를 저버리지 않고(?) ANSI 버전으로 저장됩니다.

 

유니코드용 문자열 클래스

 

MFC CString 은베이스타입을 char 나 wchar 가 아닌 TCHAR 를 사용한 템플릿 클래스로 선언되어 있기 때문에 유니코드 선언 여부에따라 자동으로 해당 버전의 문자 클래스로 만들어 집니다. CString 을 많이 사용한다면 일단 선언하는 변수명부터 수정해야하는 부담은 없습니다.

typedef ATL::CStringT< TCHAR, StrTraitMFC_DLL< TCHAR > > CString;

반면에 STL string 은 자동으로 TCHAR 를 선택할 수 있도록 되어 있지 않습니다. 따라서 유니코드용 버전에서는 string 대신 wstring 을 선언해야 합니다. 이렇게 사용할 경우, TCHAR 를 사용함으로써 컴파일 옵션에 따라 처리할 문자열 타입을 쉽게 전환할 수 없기 때문에 아래처럼 기존 string/wstring 선언 뒤에 tstring 선언을 추가하는 방식으로 정의해서 사용하는 방법이 있겠네요

typedef basic_string<char, char_traits<char>, allocator<char> >

           string;

typedef basic_string<wchar_t, char_traits<wchar_t>,

           allocator<wchar_t> > wstring;

typedef basic_string<TCHAR, char_traits< TCHAR >,

           allocator< TCHAR > > tstring;


유니코드 문자열 처리하기

 

유니코드의 문자를 처리하기 위해서는 wcs* 로 시작하는 wchar_t 를 다루는 함수를 사용해야 합니다. 이 역시 TCHAR 형을 다루는 함수가 미리 준비되어 있는데, wcs* 부분을 _tcs* 로 변경해 주면 됩니다. 아래는 문자열을 다루는 C 런타임 함수들과 그에 상응하는 TCHAR 형 함수들입니다.

ANSI

UNICODE

TCHAR

strlen()

wcslen()

_tcslen()

strcat()

wcscat()

_tcscat()

strchr()

wcschr()

_tcschr()

strcmp()

wcscmp()

_tcscmp()

strcpy()

wcscpy()

_tcscpy()

strstr()

wcsstr()

_tcsstr()

strrev()

_wcsrev()

_tcsrev()

printf()

wprintf()

_tprintf()

sprintf()

swprintf()

_stprintf()

scanf()

wscanf()

_tscanf()


 


유니코드 문자열 처리 시 주의 사항

 

유니코드 문자열을 다룰 때 가장 혼동되는 부분 중에 하나는 배열의 개수와, 실제 배열의 크기를 다루는 부분입니다. 예를 들어 아래와 같은 코드를 실행하면


TCHAR szTemp[10] = _T("Hello");

int nLen = _tcslen(szTemp);

ASSERT(nLen == 5);


유니코드 환경이든, ANSI 환경에서든 _tcslen() 의 리턴값은 반드시 5여야 합니다.

유니코드 함수 API 도움말에 들어 있는 size_t 타입이 의미하는 것이 배열내 요소의 개수인지, 배열이 차지하는 메모리 공간의 크기인지 항상 확인하고 프로그램을 작성해야 합니다.

또한 memcpy 와 같은 함수는 당연히 실제로 복사할 바이트 수를 지정해야 합니다.


TCHAR szTemp2[10];

memcpy(szTemp2, szTemp, sizeof(szTemp));


sizeof(szTemp) 는 유니코드 하에서는 20바이트, MBCS 환경에서는 10이 됩니다.

 

아래 코드는 memcpy() 를 사용해서 szTemp2 의 문자열 끝에 szTemp 문자열을 붙이는 작업을 합니다.  MBCS 환경에서는 sizeof(char) 1 이었기 때문에 문자열을 다룰 때 sizeof(char) 를 빼먹은 경우가 많고 이 때문에 MBCS 를 유니코드로 변경할 때 알고도 당하는 실수를 여러 번 경험합니다.


TCHAR szTemp[10] = _T("Hello");

TCHAR szTemp2[10] = _T("Hey");

memcpy(szTemp2 + _tcslen(szTemp2), szTemp, _tcslen(szTemp) * sizeof(TCHAR));

// MBCS 환경에서라면

// memcpy(szTemp2 + strlen(szTemp2), szTemp, strlen(szTemp));


 윈도우 API 를 다룰 때도 주의가 필요합니다.
RegSetValueEx 함수를 호출할 때 맨 마지막 인자는 szValue 의 버퍼크기를 담아 보내야 하기 때문에 아래와 같이 sizeof(TCHAR) 를 계산해야 제대로 인자가 전달됩니다.


TCHAR szKeyName[30];

TCHAR szValue[30];

RegSetValueEx(hKey, szKeyName, 0, REG_SZ, (LPBYTE)szValue, _tcslen(szValue) * sizeof(TCHAR) + 1);


GetModuleFileName() 에서는 마지막 인자로 문자열의 배열 개수를 필요로 하기 때문에 아래와 같이 사용해야 합니다. (알고 나면 간단하지만 각 API 별로 의미를 충분히 파악해서 사용하지 않는다면 순식간에 프로그램이 죽을 수 있으니 조심해서 코딩해야 합니다.)

TCHAR szModuleName[MAX_PATH];

DWORD dwStrLen= GetModuleFileName(NULL, szModuleName, MAX_PATH);


유니코드 문자열 변환 함수

 

MBCS 에서 유니코드로 전환하려면 mbstowcs MultiByteToWideChar 2가지 함수를 사용할 수 있습니다.

 size_t mbstowcs(
   wchar_t *wcstr,
   const char *mbstr,
   size_t count
);


int MultiByteToWideChar(
  UINT CodePage,
  DWORD dwFlags,        
  LPCSTR lpMultiByteStr,
  int cbMultiByte,      
  LPWSTR lpWideCharStr, 
  int cchWideChar       
);


 

반대로 유니코드를 MBCS 형태로 변경하려면 wcstombs 또는 WideCharToMultiByte 함수를 사용하면 됩니다.


size_t wcstombs(
   char *mbstr,
   const wchar_t *wcstr,
   size_t count
);


int WideCharToMultiByte(
  UINT CodePage,
  DWORD dwFlags,
  LPCWSTR lpWideCharStr,
  int cchWideChar,
  LPSTR lpMultiByteStr,
  int cbMultiByte,
  LPCSTR lpDefaultChar,   
  LPBOOL lpUsedDefaultChar
);

 

위 함수가 조금 복잡하게 느껴지면 ATL 에서 제공하는 정말 간단한 매크로를 사용해서 컨버전하는 것도 괜찮은 방법입니다. (ATL COM 을 다룰 때는 A2W, W2A 를 애용했던 1 이었습니다 ^^)

// 컨버젼이전에항상임시변수를선언해야합니다

USES_CONVERSION;

 

TCHAR wTemp[10] = _T("Hello");

char sTemp[10];

strcpy(sTemp, W2A(wTemp));

ASSERT(strcmp(sTemp, "Hello") == 0);


by 나도야 | 2009/10/23 12:01 | [開發] 프로젝트 관련 | 트랙백 | 덧글(0)

◀ 이전 페이지          다음 페이지 ▶