이번학기에 비주얼프로그래밍 수업을 들으면서..(사실은 뭐 그의 MFC수업이었지만)
기초적인 WindowsAPI에 대해 '맛'을 봤었다.
사실, 이번학기시작전에 WINDOWS API책을 살짝 훑어보고 시작한지라 공부를 하지 않고도 그럭저럭 학점이 잘 나오리라 생각한다.
하지만, 수업을 듣던중가장 난감했던 것은. 바로 유니코드.
난 수업시간에 내노트북을 갖고 다니면서 프로그래밍을 했다.
하지만계속해서 유니코드때문에 막혔다. 첫번째 시간은 코드를그대로 쳤으나 코드가 돌아가지 않았다.
아예 해결방법을찾지못해 하루정도 내내 해결방안을 찾았던것 같다.
일단 해결방법 1은
Alt+ F7을 누르거나 메뉴에서 (프로젝트)- > (속성) 으로들어가
왼쪽프레임에서 (구성속성) -> (일반)을 클릭하고
오른쪽프레임에서 아래 문자집합 에 있는 (유니코드문자 집합 사용)을 (멀티바이트 문자 집합 사용)으로 해준다면 왠만큼 프로그래밈이 이 전 비주얼 스튜디오 에서 코딩하는것 처럼 비슷하게 돌아간다.
하지만, 이건 약간 룰위반이다.
우선 멀티바이트와유니코드를 이해해야 한다.
멀티바이트를 이해하기위해선 아스키 코드에 대해서도 알아야한다.
ASCII CODE란만약 이걸 보는 이들은 아마도 알고 있겠지만,
컴퓨터는 문자를인식하지 못한다. 1과 0으로 밖에 인식을 하지 못하기 때문에 인간은 그 문자를 일단 10진수 숫자와 16진수 숫자와 매치시켜 놓는다.예를들어, 숫자가 73이면 16진수로는 0x4C고 문자는 L 이 된다.
이모든걸 결정한 곳은 어느 말을 쓰는 나라 일까? 바로 미쿡. 따라서 모든 ASCII CODE는 영어로만 이루어져 있다. 사실ASCII 라는 말은 American Standard Code for InformationInterchange의 약자다.미국 표준이란 소리지. 걔네들이 한국어나 따른 나라 언어 알게 뭐가 있었겠어?
아무튼그렇게 10진수와 16진수로 옮겨놓으면 2진수로 만드는건 뭐 식은죽을 그냥 앉아서 천천히 TV보면서 먹는 그런 느낌이 되어버렸지.그리고
부호를포함한 1 Byte즉 8 bit의 2진수로 나타낼수 있는 수는 0000000 부터 1111111 까지. 즉 0부터 127까지다.
알파벳은 총 몇개?어이어이 세지 말고 ㅋㅋㅋ 26개다. A~Z까지 26개고 음... 그래 대소문자 다 합쳐봐야 52개 에다가... 음.. 그래 숫자도 넣어두면편하겠찌 그래서 숫자도 넣고. 음.. 또 뭐 없을까? 그래 따옴표하고 구두점하고 수학기호하고 뭐 그런것들좀 집어넣어서 채우다 채우다 좀 남아서비트들을 활용하여 (그래픽 문자)도 모자라 (제어문자)까지 꽉꽉 채워 넣었다 .제어문자란 음.. DEL키라던가Space라던가 ESC라던가 그런거 있잖아. 아 그런 공간 좀 남으면 한글이나 넣어줄것이지! 라고 말하고 싶지만 사실 인접 국가.미국에서 자주사용되는 스폐인어 고유 글자조차 부호없는 1byte(ASCII CODE Extended)에 겨우 포함이 될 정도다.
자,이제 아스키코드란걸 조금은 이해를 하겠나? 음... ASCII CODE가 궁금하다면 http://ko.wikipedia.org/wiki/Ascii
위키피디아를 찾아보아도좋음 :)
자, 이제 ASCIICODE란 걸 왠만큼 알았으니 멀티바이트로 넘어가 보자. 그럼 멀티 바이트란 무엇인가?
바로 이ASCII CODE는 잘 해봐야 1Byte 인데 왜 싱글 바이트가 아닌 멀티 바이트인가?
즉슨, 위에서 언급했던대로 Signed 1byte안에는 only english다. 뭐, 숫자나 뭐그런거 말고 다른 나라의 언어는 없다는이야기이다. 그리고 unsigned는 그외 프랑스, 스페인, 따깔, 더치어등 잉글리시와 비슷한 언어들만 확장형으로 넣어두고 나머지는 표그리기나그림그리용으로 채워 넣었다.
그럼 이제비주얼스튜디오에서 멀티바이트로 하고 hello world 코딩을 해보자. 아, 영어말고 printf("헬로 월드"); 요롷게당당하게 한국어로 해보자. 출력은 어떻게 나오는가? 한국어로 나오지 않는가?! 와우. 분명 ASCII CODE에는 영어 밖에 없다. 미국표준이므로. 하지만 어떻게 한글이 나오지? 물론 개중에는 한글이 지원하지 않아 이상한 상형문자들이 나오는 경우가 있다. 그 이유는 나중에점차점차 알아가도록 하고. 어찌되었든 한국어가 나온다. 그 이유가 멀티바이트에 있다. 이 문자를 인식할때 싱글바이트, 즉 1Byte로만 인식을하면 영어만 인식을 한다. 왜냐? 1Byte안에 영어, 한국어, 중국어, 일본어 같은 것들을 모두 넣다보면 그 옛날 꼬물 컴퓨터를 쓸때만들어진 표준으로써는 그당시 현실에 너무 안맞기 때문이다. 그렇게 몇년을 쓰다가 그걸 또 2Byte로 넘기기엔 표준이 송두리채 바뀌기가 쉽지않았다. 따라서 하나의 대안을 제시한다.
1Byte를 두개 해서문자를 표현하자고!!!
영어를 할땐1Byte만 하면 되고 만약 영어이외의 한국어나 일어, 한자를 표현할때는 1Byte 여러개를 붙여 문자를 표현하기로 한다. 그 것이ISO-2200(ISO-2200-KR, ISO-2200-JP, ISO-2200-CN) 에 정의 되어 있는 멀티바이트 문자열이다. 뭐조금 자세히 들여다 본다면 그 1byte의 21-7E를 2개 이어붙여 94*94=8836개의 글자를 표현할수 있다. 좀더 자세히들어가 유닉스쪽얘기를 한다면 유닉스에서는 그 표준이 약간다른데(EUC:Extended Unix Code로 EUC-KR, EUC-JP, EUC-CN으로표기한다.) 두개의 바이트를 이어붙이는게 A1-FE 까지라는것을 제외하면 표현할수 있는 글자수도 같아 뭐 비슷비슷하게 사용할수 있다.
자,이제 멀티바이트에 대해 약간이나마 감이 좀 잡히나? 간단히 말해 ASCII CODE 1Byte를 두개이상 붙여만들었기떄문에 멀티바이트라고 한다.
그렇다면 유니코드란무엇인가?
ASCII는 영어일경우는1Byte, 다른나라 언어는 +a를 하게된다. 그렇다면 어떤 일이 벌어지는가?
다른 나라 언어에+a를 한다고 해도, 만약 한국어로 된 문장을 일본어로 해석하게 되면 어떻게 되겠는가? 이런 연유로 가끔씩 페이지를 돌아다닐때 홈페이지 전체가상형문자로 덮혀있는 페이지를 발견하게 되는것이다. 즉, 글자가 깨진다고 하는것이다.
자, 이것을위해 유니코드가 태어났다. 이 American놈들의 지저분한 Standard를 꺠고 Universial한 Code를 만들어 냈다.그것이 바로 유니코드다. 이 유니코드는 두개를 이어 붙이는게 아니라 처음부터 2Byte를 할당하여 파일 하나에 컴퓨터로 표현하는 세상 모든문자를 담았다. 하지만 아직은 인터넷계의 주류를 담고 있는 영어에 뭔가 불리하지 않겠는가? 쓰지도 않을 일본어때문에 굳이 용량을 키우면 뭔가좀 거시기 하지 않겠는가?라는 생각이 들어 이 유니코드도 3가지로 나눠져 있다. 그거슨 UTF-8, UTF-16, UTF-32 로나눈다.
UTF-8이 그ASCII CODE와 같이 8비트만을 이용한 글자. UTF-16은 16비트를 사용하고 UTF-32는 32비트를 사용하여 글자를 나타낸다.하지만 이것은 말했듯이 ASCII CODE처럼 두개의 1 Byte를 이어붙이는 것이 아니라 순수하게 1byte면 1byte,2byte면 2 byte를 사용한다. 그래서 인터넷보면 UTF-8 인터넷 주소만 이용 뭐 이런게 있는데 즉, 한글주소나뭐그런거 다 빼고 영어만 쓰겠음. 뭐 그런거다.
자, 이렇게 ASCIICODE와 UNICODE는 그 근본적으로 차이가 있다. 따라서 개발자는 죽어나간다. 표준이 여러개라...
그렇다면 아까의 그비주얼 스튜디오 2010에서 유니코드로 윈도우즈 프로그래밍을 할수 있는 두번째 방법을 소개 하겠다. 개념은 그리 어렵지 않다.
그저멀티바이트 즉, ASCII CODE가 아닌 UNICODE를 사용하면된다.
하지만 방법은 막어렵지는 않지만 간단하지만은 않다. 왜냐하면, 이 C++이 만들어 질땐 ASCII CODE를 전제로 만들어져 기본적인 명령어는 대부분ASCII CODE를 기반으로 만들어 졌기때문에 그것을 요리해서 UNICODE도 사용할 수 있도록 변경해야 한다. 문자열이 들어가는 곳이라면대부분의 코드를 손을 보아야한다. 따라서 ASCII CODE, 즉 일반적인 C에 적응되어 있는 분들은 문자열이 나온다면 긴장을 해야 할것이다.
물론, C++에서 막문법을 떄고 오신분들이라면 우선 여기에 먼저 적응해지면 되긴 하겠지만 그래도 윈도우즈 프로그래밍에서는 char 가 직접적으로는 절대 안쓰인다고생각해야한다.
자, 그럼어떤걸 쓰느냐?
char는 TCHAR로사용한다. TCHAR의 정의를 보면
#ifdefUNICODE
typedefwchar_t TCHAR;
#else
typedef charTCHAR;
#endif
만약, UNICODE를사용한다면, wchar_t형의 TCHAR를 사용하고 유니코드가 아니면 그냥 char를 사용할께용. 이라는 뜻이다. 그렇다면 wchar_t는무엇인가? 음... int는 정수형을 담기위한 자료 저장 범위를 지정하기 위한 "자료형"이다. 그렇다면 UNICODE를 위한저장 크기를 제한한 자료형정도가 되겠다. 정확히 말하면 저거슨 unsigned short로 정의 되어있다.(for UTF-16)
사실 일반멀티바이트라면 char를 사용하고 유니코드라면 wchar_t의 자료형을 사용하면 된다. 하지만 xp이후 유니코드가 대세가 되어가고 있고마소또한 유니코드를 권장하고 있다.
또 몇개의 자료형을 더찾아보자면
TCHAR : char
LPSTR : char*
LPCSTR :const char*
LPWSTR :wchar_t*
LPCWSTR :const wchar_t*
LPCTSTR :const wchar_t * || const char*
이렇게 있겠는데 음..헝가리언 명명법이라고는 알고 있을란지... 아무튼 이름 붙이는 방법이다. 그 이름 붙이는 방법중 약어를 쓰는경우가 있는데 STR은 딱봐도string이라는거 알겠고 다른 약어들을 대충 찾아본다면
W : Widechar (2바이트를 의미. 대충 유니코드따라잡기용 ASCII Code라 생각하면 됨)
T : unicode|| ASCII
C : constant
LP : longpointer
STR :string자료형
그리고 이 유니코드를지원하는 문자열함수도 따로 있으니 아래의 함수들을 이용하면 된다.
strlenlstrlen
strcpylstrcpy
strcatlstrcat
strcmplstrcmp
sprintfwsprintf
자, 이제 실전이다. 뭐, 다들 책이나 기본적인 윈도우즈 프레임 툴은 어떻게 짜는지 알것이다.
내가 배운것은
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCSTR lpszClass="First";
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
HWND hWnd;
MSG Message;
WNDCLASS WndClass;
g_hInst=hInstance;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
WndClass.hInstance = hInstance;
WndClass.lpfnWndProc=WndProc;
WndClass.lpszClassName=lpszClass;
WndClass.lpszMenuName=NULL;
WndClass.style = CS_HREDRAW|CS_VREDRAW;
RegisterClass(&WndClass);
hWnd = CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,(HMENU)NULL,hInstance,NULL);
ShowWindow(hWnd,nCmdShow);
while(GetMessage(&Message,NULL,0,0))
{
TranslateMessage(&Message);
DispatchMessage(&Message);
}
return (int)Message.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
switch(iMessage)
{
case WM_DESTROY : PostQuitMessage(0);
return 0;
}
return (DefWindowProc(hWnd,iMessage,wParam,lParam));
}
요 코드다. 자, 그렇다면 유니코드를 해놓고 비주얼 스튜디오 2010에서 돌리면 어디가 에러가 나는가?
바로 hWnd=CreateWindow 요 줄에 있는 두번째 배개변수 lpszClass가 문제다. 왜냐하면 윈도우즈 함수에서는 분명히 유니코드를 이용하겠다고 했는데 이 lpszClass는 아직 유니코드가 아닌갑다. 그래서 나는 이 lpszClass의 유니코드명이 뭔지 한참이나 찾았으나 인터넷에는 도저히 나와있지 않다. 왜냐하면!! lpszClass는 내가 코드안에서 생성해준 변수명이잖아-_ -;;;;
자, 5번줄을 보면
LPCSTR lpszClass="First";
요게 있었어..
복습을 해보자.
LPCSTR은 Const하고 long pointer하고 str인것이다. 유니코드의 흔적조차 없다.
그러면 어떻게 해야할까?
그거야 바로 LPC와 STR사이에
W나 T를 넣어주면 된다. 다른 코드들과의 이식성을 위해 일부러 연습을 위해서라도 LPCTSTR을 사용해주기로 한다.
자, 이제 해결?!
5번줄만 바꿔서
LPCTSTR lpszClass="First";
자, 디버깅 해보자.
으악! 에러다. 에러다. 에러다. ㅠㅜㅠ 아 도대체 왜!!!
자, 읽어보자면
error C2440: '초기화 중' : 'const char [6]'에서 'LPCTSTR'(으)로 변환할 수 없습니다.
라고 뜬다.
즉슨, 그럼 "First" 가 5자리+NULL의 문자열 상수일테고 왼쪽은 LPCTSTR이다.
음.. LPCTSTR은 틀릴리가 없고, 그럼 const char [6]이 틀렸다는 건데.
일단 내가 짠것은 유니코드니 wchar_t const * != const char[6] 라는 소리겠지.
틀린말은 아니다, "First"는 분명한 문자열 상수고 그것을 다른 자료형에 넣을순 없는거니까.
자, 그러면 형변환이다!
자, 그럼 유니코드 안에서 LPCTSTR == wchar_t const * 이니까
LPCTSTR lpszClass == (wchar_t const *)"First";
라고 고 치고 해보자.... 와우!!! 디버깅 에러 없고, 컴파일 제대로 되..... 었지만 출력물이 이상하다. 무슨 이상한 한자하고 잡스러운게 많이 나온다. ㅠㅠㅠㅠㅠㅠ 앙데 ㅠㅠ
음... 이것의 이유는 찾아본 결과 일단, 강제 형변환에 있다. 일반적인 char 형과 유니코드의 공간 할당 크기가 다르고 이것을 강제적으로 형변환을 할때 그 할당공간 크기 차이의 빈자리를 쓰레기값이 들어가 쓰레기 글자가 나오게 되는 것이다.
그래서 자연스레 형변환을 해주는 접두어가 있었으니 그거슨 바로 L이다.
라이토는 아니고, 음... 정확히 찾아보진 못했지만. L이다.
사용방법은. 그냥 일반 접두어처럼 사용하면 된다.
LPCTSTR lpszClass=L"First";
요롷게 하면 디버깅도 되고 컴파일도 되고 실행도 된다. 제대로 출력도 된다.
하지만 이렇게 하면 사실 LPCTSTR을 쓴 의미가 없어진다. 왜냐하면 이 L은 유니코드형으로의 형변환이다. 선택적 형변환을 하기 위해선 TEXT()함수를 사용해야한다. 이 TEXT한수를 사용할 경우엔 이것도 T와 마찬가지로 알아서 유니코드면 유니코드, 일반 ANSI면 일반 char로 알아서 변환한다.
다시 말해
LPCTSTR lpszClass=TEXT("First");
가 최종 답안이라는거지 :)
아.. 여기까지 달려오시느라 참 수고 많으셨고(다 읽었을리는 없겠지만)
최종 코드를 공개하겠다.(거창하진 않지만)
아래 코드로 컴파일 하여 실행한다면 유니코드든 멀티바이트든 에러없이 실행 된다:)
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass=TEXT("First");
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
HWND hWnd;
MSG Message;
WNDCLASS WndClass;
g_hInst=hInstance;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
WndClass.hInstance = hInstance;
WndClass.lpfnWndProc=WndProc;
WndClass.lpszClassName=lpszClass;
WndClass.lpszMenuName=NULL;
WndClass.style = CS_HREDRAW|CS_VREDRAW;
RegisterClass(&WndClass);
hWnd = CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,(HMENU)NULL,hInstance,NULL);
ShowWindow(hWnd,nCmdShow);
//MessageBox(hWnd,L"하이",TEXT("Test"),NULL);
while(GetMessage(&Message,NULL,0,0))
{
TranslateMessage(&Message);
DispatchMessage(&Message);
}
return (int)Message.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
switch(iMessage)
{
case WM_DESTROY : PostQuitMessage(0);
return 0;
}
return (DefWindowProc(hWnd,iMessage,wParam,lParam));
}