« Previous : 1 : ... 15 : 16 : 17 : 18 : 19 : 20 : 21 : 22 : 23 : ... 31 : Next »

#define의 대체제

확실히 #define은 다른 걸로 대체 가능할 때는 가능한 한 안 쓰는 게 좋을 것 같다.
C++은 용도별로 다음과 같은 다양한 대체제를 제공한다.

1. 매크로 함수의 대체제: 인라인 함수로 대체 가능하며, 템플릿까지 동원하면 매크로 함수 만만찮은 유연한 메타프로그래밍이 가능하다.
또한 한 함수 안에서만 지엽적으로 반복되는 루틴을 정리하려면 C++0x부터는 람다 함수를 쓸 수도 있다.

2. 매크로 상수의 대체제: 정수의 경우 enum을 쓰면 같은 성격의 여러 심벌들을 한데 묶어 놓을 수도 있어서 좋다.
그리고 문자열은 그냥 const char/WCHAR 형태의 전역/클래스 static 변수로 처리함. 선언과 정의가 따로 존재해야 해서 불편할 수 있으나, 이것은 선언부에다 값을 다 집어넣고 확장 문법인 __declspec(selectany) extern const 를 지정해서 해결할 수도 있다.

아무 통제도 없이 너무 일방적으로 효력이 나타나는 #define보다는 저런 대체제들이 type-safety와 엄격한 scope 검증이 보장되기 때문에 "훨씬 더" 깔끔하다. 가능한 한 전처리기보다는 컴파일러에게 일을 맡기는 게 바람직하다.
내가 만든 명칭이 매크로로 이미 존재하여 딴 걸로 치환되고 있는 줄도 모르고 컴파일러가 자꾸 이상한 난독증을 보이며 에러를 뱉는 것 때문에 빡친 경험이 있는 사람.. 주변에 의외로 많다. ㅎㅎ

단, 그럼에도 불구하고 대체제가 존재하지 않아서 #define을 불가피하게 써야만 하는 경우는 아마도 다음과 같을 것이다.

1. #if #elif #endif 같은 조건부 컴파일 변수 지정

2. 함수 형태를 갖추기조차 민망할 정도로 너무 간단한 로직. 디버그 빌드에서도 독립된 함수 호출이 아니라 언제나 인라이닝이 반드시 보장되기를 바라는 부분

3. 호출하는 함수나 지정하는 변수 이름을 말 그대로 간단히 치환만 시키기를 원하는 경우

4. 대체제의 문법적 한도를 넘는 과격한 구문 치환을 해야 하는 경우. 특히 #나 ## 같은 연산자를 동원해서 완전히 새로운 토큰을 만들어 내야 할 때

5. __LINE__, __FILE__, __TIME__ 같은 빌드/디버그 정보를 그때 그때 삽입하고 싶을 때

6. 정수와는 달리 부동소숫점과 문자열은 여전히 #define이 유용한 경우가 있다.
부동소숫점은 enum이 지원되지 않고 static const 멤버도 클래스 선언부에서 바로 값 지정이 되지 않기 때문이다. (이걸 지원하는 컴파일러도 있긴 하나, 일단은 비표준임)
문자열은 매크로 상수의 경우, concatenate(연결)되는 문자열의 일부가 되는 게 가능하다. const 상수는 그렇지 않다.

#include와 #define이 너무 지저분하고 컴파일 시간을 증가시키는 요인이라며 없애자니.. 위와 같은 용도까지 부정하는 건 현실적으로 무리이긴 하다.

여담으로..
근래엔 남이 만든 코드를 읽다가 IID_PPV_ARGS라는 매크로를 보고 감탄하여 내가 짠 기존 코드에다가도 다 리팩터링을 해서 적용해 놨다.

CoCreateInstance와 IUknown::QueryInterface 때 꼴도 보기 싫던 void ** 형변환을 없애 주는 매우 편리하고 유용한 물건이다. COM이 등장한 건 무려 20년이 넘었고 C++에 템플릿이 추가된 것도 만만찮게 오래 됐을 텐데 이 매크로는 무려 Windows 7의 플랫폼 SDK에서야 정식 등장했다는 게 놀랍다.
매개변수 2개를 하나로 줄이는 역할까지 하니 이 정도라면 컴파일러가 아니라 전처리기 매크로밖에 선택의 여지가 없긴 하다.

Posted by 사무엘

2014/04/01 19:20 2014/04/01 19:20
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/947

윈도 GUI 환경에서 동작하는 프로그램이 자기 창을 띄우기 위해 먼저 해야 하는 일은 바로 자기 윈도우의 클래스를 운영체제에다 등록하는 것이다. WNDCLASS 구조체와 RegisterClass함수는 그야말로 기본 중의 기본 필수 과정이다.

WNDCLASS 구조체에서 중요한 멤버는 클래스 이름(lpszClassName), 윈도우 프로시저 주소(lpfnWndProc) 정도다. 나머지 값들은 전부 0 / NULL이어도 클래스 등록이 가능하다.

우리에게 친숙한 에디트 컨트롤, 리스트박스, 콤보박스 등등은 다 고유한 클래스 이름이 존재하기 때문에 사용자 프로그램이 이를 변경하거나 없앨 수 없다. 윈도우 클래스계의 일종의 예약어라 해도 과언이 아니다. 공용 컨트롤은 내장 컨트롤 급의 붙박이는 아니지만 그래도 공용 컨트롤 매니페스트를 사용하는 요즘 프로그램들에서는 사실상 붙박이다.
대화상자, 메뉴, Alt+Tab 전환 창처럼 운영체제가 내부적으로만 사용하는 known 윈도우들도 사실은 다 고유한 클래스 이름을 갖고 있다.

한편, 각각의 윈도우 클래스 명부는 고유한 기억장소를 갖고 custom 데이터를 보관할 수 있다(cbClsExtra). 그러나 이건 거의 필요하지 않으며 쓰이지 않는다. 여러 윈도우 클래스들이 한 윈도우 프로시저를 공유하면서 그 프로시저가 클래스별로 custom 데이터를 가려서 동작하기라도 하지 않는 이상 말이다. 그런 게 아니라 윈도우 클래스별로 완전히 따로 노는 공유 데이터라면 그냥 해당 프로그램이 자체적으로 static/전역 변수의 형태로 갖고 있으면 될 일이다.

차라리 클래스가 아니라 각각의 윈도우들이 custom 데이터를 저장할 공간이라면(cbWndExtra) 이건 그래도 종종 쓰이는 경우가 있다. 그러나 굳이 이게 0이더라도 포인터 하나 정도 집어넣을 공간은 모든 윈도우들이 기본으로 갖고 있기 때문에 HWND로부터 그 창에 대응하는 C++ 객체의 포인터를 저장하는 것 정도는 이런 방식으로 하면 된다.

그 다음으로 외형 관련 부가 정보들은 나중에 클래스 차원이 아닌 윈도우 차원에서 변경 가능한 것들이다.

프로시저의 주소만 있으면 충분할 텐데 굳이 인스턴스 핸들까지 따로 받는 건 16비트 시절의 잔재이긴 하다. 요즘 같으면 윈도우 프로시저 주소가 어느 영역에 있는지만 봐도 이 윈도우 클래스의 소속 모듈은 곧바로 알 수 있으니 굳이 그 핸들을 따로 줄 필요는 없기 때문이다.
하지만 호환성 문제도 있고, 또 외형 리소스(메뉴, 마우스 포인터, 아이콘 등)를 어디서 불러올지 기준으로 삼을 모듈이 필요하기도 하니 인스턴스 핸들을 받는 란이 있는 것이다.

아이콘(hIcon)은 시스템 메뉴와 두꺼운 프레임이 갖춰진 커다란 윈도우를 만들 때에나 필요할 텐데,
여기서 지정한 뒤에도 나중에 실행 중에 WM_SETICON 메시지를 운영체제에다 보내서 변경이 가능하다. 대화상자의 아이콘을 바꿀 때 주로 쓰인다.

text를 바꾸는 것과는 달리 아이콘을 변경하는 건 함수가 전혀 존재하지 않고 메시지만 쓰인다는 게 특징이다.
또한, HICON 자체를 여러 크기의 아이템 컬렉션/패밀리로 설정한 게 아니라 특정 크기의 그림 하나만을 나타내게 설정한 바람에 WNDCLASS에 이어 WNDCLASSEX까지 등장하는 등 API가 다소 지저분해진 건 아쉬운 점이다. 지금은 이분법적인 큰 아이콘/작은 아이콘뿐만이 아니라 다양한 크기의 아이콘까지 등장해 있는데 말이다.

마우스 포인터(hCursor)는 잘 알다시피 WM_SETCURSOR 메시지가 왔을 때 동적으로 변경 가능하다.
기본 배경색(hbrBackground)으로 화면을 지우는 동작도 WM_ERASEBKGND 메시지 때 변경 가능하다.
즉, WNDCLASS에 지정된 것만이 절대적이지는 않다는 뜻이다.

그것도 모자라서 클래스 구조체에 메뉴(lpszMenuName)까지 지정 가능한 것이 굉장히 뜻밖이다.
보통 윈도우를 만들 때 메뉴 정보는 CreateWindowEx 함수에다가 따로 지정해 주기 때문이다. 그렇기 때문에 WNDCLASS 구조체에 굳이 메뉴 핸들이 공급될 필요는 없다.
본인 역시 10여 년간 Windows API로 프로그래밍을 하면서 이 멤버에다가 값을 지정해 준 적은 한 번도 없었다.

자, 그럼 이제 스타일(style)만 남는데, 다음과 같은 것들이 있다. 이 역시 굳이 이 스타일을 안 줘도 동일한 기능을 코드를 통해 얼마든지 재연할 수 있는 게 대부분이고, 오늘날에는 거의 필요하지 않거나 사용이 권장되지 않는 잉여 옵션도 있다.

1. 정말 유의미한 차이가 있음 (유일!): CS_GLOBALCLASS

원래 윈도우 클래스 명칭은 해당 클래스를 등록한 스레드도 아니고 그 등록한 코드가 들어있는 모듈(EXE든 DLL이든)에서만 쓸 수 있다. 그러나 이 옵션이 지정된 채로 등록된 윈도우 클래스는 해당 프로세스 전체에서 사용할 수 있게 된다.
어떤 특수한 윈도우--custom 컨트롤이 대표적인 예--에 대한 코드가 DLL에 들어있고 그 윈도우를 그 DLL을 불러들인 EXE에서 사용하고자 한다면, 그 클래스는 당연히 이 스타일이 지정된 채로 등록되어야 한다.

쉽게 말해 이 윈도우 클래스를 작성하지 않은 다른 EXE/DLL에서 컴포넌트처럼 생성되고 사용되고자 하는 윈도우라면 이 스타일이 반드시 필요하고, 그냥 한 프로그램 모듈 안에서 내부적으로만 사용하고 말 local 윈도우라면 지정하지 않으면 된다.

16비트 시절에는 이 스타일의 여파가 훨씬 더 강력해서 한 EXE가 등록해 놓은 윈도우 클래스를 다른 EXE가 마음대로 사용할 수도 있었다. 인스턴스 핸들로 데이터 세그먼트를 구분하는 게 오늘날로 치면 그냥 응용 프로그램의 주소 공간을 마음대로 넘나드는 거나 마찬가지였기 때문이다. 그러나 오늘날은 그렇게까지는 할 수 없으며, 일반적으로는 DLL에다가 윈도우 프로시저를 구현한 뒤, 그 윈도우를 사용하고자 하는 EXE가 DLL을 매번 불러오고 클래스 등록을 저렇게 해 줘야 한다.

2. 다른 코드를 통해 대체 가능한 동작 방식의 차이

CS_DBLCLKS
좌든 우든 한 마우스 버튼을 충분한 시간 간격 이내에 빠르게 연타했을 때, 둘째 클릭은 WM_?BUTTONDOWN이 아니라 WM_?BUTTONDBLCLK라는 메시지로 달리 알리게 한다. 이것은 실행 시간에 매번 바뀔 만한 동작 방식은 아니니 윈도우의 스타일이 아니라 클래스의 스타일로 존재하는 게 적절하긴 하다.

굳이 이 스타일이 없어도 더블클릭을 인식하는 것을 우리가 직접 구현하는 건 어렵지 않다. 그러나 타이머 체크를 해야 하고 예전 클릭 시점을 저장해 놓는 등 별도의 시간· 공간 오버헤드가 필요하기 때문에 운영체제는 이걸 원하는 윈도우에다가만 더블클릭 메시지를 전해 주고 있다. 더블로 모자라서 트리플 클릭이라도 인식하려면 역시 사용자가 상태 전환 로직을 직접 구현하는 게 필수일 게다.

메뉴의 경우 마우스의 클릭에 따라 열렸다가 닫히는 게 토글되는 물건이다. 더블클릭에 따른 동작 구분이 필요하지는 않지만, 보통은 더블클릭 때는 열렸던 메뉴가 닫히지 않게 만들어져 있다. 지금 당장 XP~7의 운영체제의 시작 메뉴를 눌러 보시기 바란다. 초보자들은 더블클릭을 할 필요가 없는 물건도 불필요하게 더블클릭하는 경향이 있기 때문에 더블클릭은 클릭+클릭으로 인식하지 않고 메뉴를 닫는 동작으로 인식하지 않는다.

물론 이런 정책은 진짜로 시도 때도 없이 짧은 간격의 클릭 연타를 인식해야 하는 게임 같은 데서는 절대로 적용해서는 안 될 것이다. 정반대의 정책을 취해야 한다.

CS_VREDRAW, CS_HREDRAW
일반적으로 창의 크기가 예전보다 커지면 커져서 새로 생긴 오른쪽 내지 아래쪽의 신규 영역에 대해서만 WM_PAINT가 날아온다. 그러나 이 옵션이 적용되면 가로 and/or 세로 크기가 바뀌었을 때 창 전체가 갱신되고 WM_PAINT가 날아온다.

화면에 문자열이 가로 내지 세로 기준으로 중앙 정렬되어 출력된다거나, 화면 폭에 따라 자동 줄바꿈이 적용되어 출력되고 있다면 화면 크기가 바뀌었을 때 화면 전체가 갱신되어야 할 것이다. 동일 배율의 2차원 비트맵을 찍는 경우가 아닌 이상, 화면 전체의 갱신이 필요한 상황은 생각보다 많다.

하지만 그럼에도 불구하고 이 옵션은 생각만치 그렇게 독창적이거나 유용하지 않다. WM_SIZE 메시지가 왔을 때 InvalidateRect를 수동으로 호출하는 것만으로도 동일한 효과를 낼 수 있기 때문이다.

CS_NOCLOSE
바로 얼마 전에 쓴 글에서 다루었듯이, 창에 [X] 버튼과 Alt+F4를 사용할 수 없게 만든다.
그러나 이것은 (1) GetSystemMenu를 이용해서 시스템 메뉴에 있는 '닫기' 명령을 없애거나 disable시키고, (2) WM_CLOSE 메시지가 왔을 때 이를 무시하여 DefWindowProc에다 전달하지 않으면 클래스 스타일 없이도 역시 거의 똑같은 효과를 얻을 수 있다.

3. 외형/성능과 관련된 마이너한 차이

CS_SAVEBITS
창이 생겼을 때 우리 창이 가리고 있는 배경 영역을 저장해 둔다. 그리고 우리 창이 사라지면 아래의 가려졌던 창에다가 WM_PAINT를 보내는 게 아니라 그냥 저장된 놈을 도로 뿌려 준다. 언제나 무조건 그렇게 하라는 뜻이 아니며 어지간한 비디오 메모리가 남아 있고 할 만하다 싶을 때만 그렇게 하라는 권장 사항이다.

이 스타일을 사용하는 윈도우는 크기가 작고 생성된 후에 이동하지 않으며, 잠깐 동안만 존재했다가 곧 없어지는 휘발성 강한 용도인 게 바람직하다. 툴팁, 메뉴 같은 윈도우의 클래스에 이 옵션이 지정돼 있다. 우리 코드의 동작 방식을 바꾸는 스타일이 아니기 때문에 존재감이 적다.

Windows Vista와 7의 Aero에서는 어차피 모든 창들의 내용이 메모리에 따로 저장되어 있고 DWM에 의해 합성되어서 출력되기 때문에 이 스타일의 존재감이 없는 거나 마찬가지다. 큼직한 창이 가려졌다가 다시 나왔는데도 예전 내용이 알아서 자동으로 출력되지 WM_PAINT가 오지 않는다니..! Vista가 출시되었을 때, Windows의 역사상 최초로 벌어지는 광경에 놀란 개발자들이 많았을 것이다.

CS_DROPSHADOW
Windows XP에서 최초로 도입된 이 스타일은 창 주변에 은은한 그림자 효과를 넣는다. Windows 2000에서는 마우스 포인터의 주변에 은은한 그림자를 넣는 효과가 추가되었는데, 동일한 알고리즘이 이제 임의의 창에도 적용된 것이다.
용도면에서 앞의 CS_SAVEBITS와 비슷한 구석이 있는지라, Windows XP부터는 툴팁과 메뉴에 이 스타일이 적용돼 있다.

시스템 메뉴와 뼈대가 갖춰진 일반적인 창에도 적용을 못 하는 건 아니지만, Aero 환경에서는 어차피 자체적으로 창 테두리 주변에 큼직한 그림자 효과가 추가되어 있기 때문에 이 클래스 스타일이 딱히 유효하지 않다.
또한 Aero 없는 모드에서는 그림자가 붙은 커다란 창은, 움직이거나 크기를 조절할 때 화면을 다시 칠하는 부담이 굉장히 커진다.

4. DC의 생성 방식과 관련된 이상한 옵션

Windows에서는 GDI API를 이용하여 화면에다 그림을 그리려면 먼저 device context라고 불리는 DC 핸들을 얻어 와야 하는 게 정석이다. 보통은 WM_PAINT 메시지가 왔을 때 BeginPaint 함수를 이용하여 얻으면 되는데, 다른 상황에서도 GetDC를 호출해서 얻을 수 있긴 하다. 그러나 BeginPaint는 딱 정확하게 칠해야 하는 영역에만 클리핑 영역이 최적화된 DC를 넘겨 주기 때문에 성능을 생각한다면 전자만을 이용하는 게 더 좋다.

윈도우와 이 DC 사이의 대응 관계는 생각보다 미묘하다. 일반적으로 시스템에 존재하는 창의 개수보다는 운영체제가 관리하는 화면용 DC의 개수가 더 적다. 솔직히 어떤 창이든 하드웨어 차원에서는 동일· 단일한 비디오 메모리에다 출력되는 것이니 동시 요청이 아닌 이상 DC가 굳이 많이 있어야 할 필요가 없다. 이 DC는 그때 그때 내부 상태가 초기화되고 클리핑 영역만 바뀐 채 재활용된다.

그런데.. CS_OWNDC가 지정되면 이 스타일이 적용된 클래스의 모든 창별로 별도의 전용 DC가 할당된다. 이는 프로그래밍 패러다임을 크게 바꿔 놓는다.
GetDC를 아무리 여러 번 호출해도, 그리고 BeginPaint를 호출해도 돌아오는 화면용 DC 핸들은 동일하며, 이 DC는 다른 윈도우에서는 쓰이지 않는다.

이 DC는 생명 주기가 자기가 소속된 윈도우와 동일하다. 그렇기 때문에 그림을 그려 준 뒤 GetDC 다음에 ReleaseDC를 하지 않아도 된다. 그리고 한 WM_PAINT 타이밍 때 지정했던 내부 상태가 다음 WM_PAINT때도 고스란히 보존되어 있다. 글자색, current positon, 선택되어 있는 GDI 개체들이 모조리..

통상적으로 해야 하는 초기화나 뒷정리가 필요하지 않으니 일면 편리한 점도 있지만, 이것은 생각보다 그리 큰 장점이 아니다.
그에 반해 윈도우 하나가 생길 때마다 수백 바이트에 달하는 전용 DC가 추가로 생성되는 것은 운영체제의 입장에서는 상당한 부담이었다. 특히나 16비트 시절에는 리소스라고 불리던 GDI 힙의 크기가 겨우 64K밖에 안 됐는데 이건 그야말로 리소스 잡아먹는 하마나 마찬가지였으며 심지어 윈도 9x에서도 상황이 크게 나아지지 않았다.

이 때문에 이 옵션은 존재는 하되 사용이 절대로 권장되지 않는 물건으로 전락했다.
CS_CLASSDC는 CS_OWNDC보다 메모리를 좀 아껴 보자는 발상에서 유래되었는데, 한 전용 DC를 동일 클래스에 소속된 모든 윈도우들이 공유하는 방식이다. 한 윈도우가 글자색을 빨간색으로 바꿔 놓으면, 다음에 그려지는 같은 윈도우는 기본적으로 글자가 빨간색으로 찍힌다. 이것도 기괴한 사고방식이긴 하다.

요건 GDI 자원을 좀 아낄 수 있을지는 모르나 '전용 DC'라는 장점이 사라지는 상태에서 멀티스레드 환경에서는 상당히 위험한 결과를 초래할 가능성이 있기 때문에 32비트 이후에서는 단점만 더욱 부각되었으며, 역시 봉인된 옵션으로 전락했다. 이런 게 있다는 것 정도만 알면 된다.

다음으로 CS_PARENTDC는 위의 두 옵션과는 성격이 약간 다르고 약간 더 실용성이 있다. 자기를 그릴 때 부모 윈도우의 기존 DC를 활용해도 좋다고 알려 준다. 좀 더 구체적으로 말하자면 굳이 클리핑 영역을 자기 윈도우로 맞추지 않아도 된다고 알려 준다.

대화상자에 아기자기한 컨트롤이 굉장히 많이 있는데 그 컨트롤들이 CS_PARENTDC 스타일이 맞춰져 있다면 대화상자의 그리기 속도가 약간이나마 향상될 수 있다. 자신이 클리핑 기능 없이도 알아서 자기 클라이언트 영역을 안 벗어나고 똑똑하게 그림을 그릴 자신이 있다면 이 스타일을 사용하는 게 나쁘지 않으며, 실제로 운영체제의 표준 컨트롤들은 다른 잉여스러운 옵션 말고 이 옵션은 사용한다고 한다.

다만, 이런 꼼수를 허용하는 스타일이 존재하는 경우, layered 윈도우나 drop shadow 같은 특수 효과와는 충돌이 발생할 수 있으니 사용 전에 MSDN 설명을 참고하는 게 좋다. 요즘 같이 메모리와 성능이 풍족한 시대엔 그냥 저런 기괴한 옵션들은 다 잊어버리고 그냥 0으로만 지정해도 무관하다.

5. Windows 1~2.x 시절에나 유효하던 완전 캐잉여: CS_BYTEALIGNCLIENT, CS_BYTEALIGNWINDOW

이것은.. 거의 20년 이상 전부터 전혀 쓸모가 없어진 옛날 잔재이다.
잘 알다시피 옛날에는 컴퓨터 화면이 흑백이던 시절이 있었고 이때는 1바이트가 8비트, 즉 8개의 가로 픽셀을 담당했었다.

그러니 이 스타일은 창의 꼭지점 위치(외곽 모서리 또는 클라이언트 모서리 기준)를 강제로 8 또는 최소한 4의 배수 위치로 맞춰서 그림을 찍는 게 바이트 경계에 딱 걸쳐지는 게 보장되게 하는 역할을 했다.
당연히 256컬러, 혹은 16컬러가 지원된 시점부터 모든 픽셀은 자동으로 이미 바이트 align이 맞춰지기 시작했으며 이 옵션은 전혀 필요가 없어졌다.

또한 Windows 3.0부터는 영문이 가변폭 글꼴로 출력되기 시작한지라 그렇잖아도 글자를 찍을 때 어차피 바이트 align이 무의미해지기도 했다.
지금쯤이면 이 스타일이 갖던 값은 그냥 다른 용도로 재사용해 버려도 되지 않나 싶다. 하지만 extended 스타일까지 존재하는 윈도우와는 달리, 클래스에는 스타일이 그렇게 다양하게 많이 추가될 여지도 별로 없긴 하다.

Windows API를 심층 연구하는 건 재미있다. ^^;;

Posted by 사무엘

2014/02/26 19:27 2014/02/26 19:27
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/935

자, 오늘은 아주 간단한 Windows API 프로그래밍 퀴즈를 하나 내겠다.
다음과 같은 창을 만들려면 어떻게 해야 할까? (닫기 X 버튼이 사용 불가)

사용자 삽입 이미지

닫기 버튼 말고 최소화 및 최대화 버튼이야 윈도우의 스타일이라는 형태로 지정 가능하다.
WS_MINIMIZE/WS_MAXIMIZE와 WS_MINIMIZEBOX/WS_MAXIMIZEBOX가 있는데, BOX가 붙은 비트는 이 창에 해당 버튼과 기능을 제공하라는 뜻이다. 그리고 BOX가 없는 비트는 이 창이 현재 실제로 최소화됐거나 최대화돼 있음을 나타낸다.

즉, 프로그램이 지정하는 속성 정보와, 사용자가 변경하는 상태 정보가 비트값만 달리하여 동일 스타일에 한데 담겨 있다. 이건 개인적으로는 좀 이상한 설계라고 생각한다. 뭐 그건 그렇고...

그런데 닫기 버튼은 이런 스타일의 형태로 존재하지 않는다.
사실, UI 디자인 상으로도 응용 프로그램은 사용자에게 뭔가 강압적인 요소를 가능한 한 만들지 않아야 하며, 모든 창은 사용자가 언제라도 닫을 수 있어야 한다. [X] 버튼이 없는 창을 접할 일은 극히 드물다. 이는 오늘날처럼 언제라도 task switch가 가능한 선점형 멀티태스킹 환경에서는 더욱 그러하다.

창에 시스템 메뉴 자체를 완전히 없애서 [X]버튼을 없애는 것 말고 저렇게 [X] 버튼을 disable만 시키는 간단한 방법은 시스템 메뉴에서 SC_CLOSE를 없애거나 disable시키는 것이다. 창의 WM_CREATE 메시지에서 이렇게 해 주면 된다.

HMENU h = ::GetSystemMenu(hWnd, FALSE);
::RemoveMenu(h, SC_CLOSE, MF_BYCOMMAND);

요렇게 해 주면 캡션에 달린 [X]버튼도 같이 영향을 받게 된다. [X]버튼뿐만 아니라 시스템 메뉴를 더블클릭해도 창이 닫히지 않는다.

그런데, 외형상 닫기 버튼을 없애는 것은 마우스를 통한 닫기 동작을 차단해 주는 반면, 키보드 Alt+F4를 차단하지는 못한다.
물론, 이런 메시지가 왔을 때 닫는 반응을 안 하도록 WM_CLOSE 메시지나 IDCANCEL을 차단하는 식으로 별도의 처리를 할 수도 있지만, 운영체제 차원에서 창을 닫으려는 시도를 원천차단하는 방법은 따로 있다.

이것은 윈도우의 스타일에 있는 게 아니라, 윈도우 클래스의 스타일에 있다.
바로 CS_NOCLOSE 되시겠다.
아무 창에나 쉽게 쓰이는 속성이 아니라고 판단되었는지 윈도우의 스타일이 아니라 클래스의 스타일로 분류되었다. 뭐, 역사적으로는 [X] 버튼 자체가 윈도 95/NT에서 처음 등장한 것이기 때문에 최소화/최대화 버튼하고는 API가 들어갈 위치가 다르기도 했고 말이다.

윈도우 클래스를 등록할 때 저 스타일을 준 윈도우는 윈도우 프로시저에서 WM_CREATE 때 시스템 메뉴 조작 같은 걸 하지 않아도 시스템 메뉴에 '닫기' 명령이 등재되지 않으며, [X] 버튼이 자동으로 흐리게 표시된다. 그리고 Alt+F4를 눌러도 자동으로 닫히지 않는다.
응용 프로그램이 직접 제공하는 '종료' 명령으로 WM_CLOSE 메시지를 날려 줘야만 닫을 수 있다. 아니면 프로세스를 강제로 죽이든가.

MDI 차일드 윈도우도 이런 스타일이 지정된 클래스에 소속된 놈으로 지정할 수 있다. 그렇기 때문에 MDI 창들 중에도 절대로 닫히지 않고 언제나 떠 있어야 하는 고정 붙박이 윈도우는 요렇게 따로 배치하는 게 가능하다.

화면을 다 차지한 채 Z-순서가 바뀌지도 않고(WS_EX_TOPMOST) 닫히지도 않는 창이 떠 있다면, 거기에다 키보드/마우스/메시지 훅만 추가해서 다른 프로그램으로 작업 전환도 되지 않게 해서 포커스를 특정 프로그램이 사실상 독점할 수도 있다. 특정 프로그램만 뜬 채로 통제를 벗어나지 않아야 하는 공공장소의 특수 목적 PC에서 돌아가는 프로그램은 이런 기능을 활용하면 만들 수 있을 듯하다.

16비트 윈도 시절에는 특정 프로그램이 시스템의 자원을 독점하기가 훨씬 더 쉬웠고 그때는 그냥 modal이 아니라 system modal이라는 기능이 버젓이 존재했다. 이 창을 닫기 전에는 아예 다른 프로그램으로 Alt+Tab 작업 전환도 안 되는 거다.

물론 이런 강압적인 기능은 32비트 이후부터는 공식적으로 삭제되었지만 이것의 영향을 받았는지 윈도 NT 계열과는 달리 9x 계열은 메시지 대화상자에도 그 잔재가 여전히 남아 있었다. 취소 버튼이 없이 '확인'만 있는 메시지 박스는 시스템 메뉴에 '닫기'가 존재하지 않았으며 [X] 버튼이 클릭 가능하지 않았다. 단, 그래도 키보드의 ESC나 Alt+F4는 동작한다.

사용자 삽입 이미지


Posted by 사무엘

2014/02/07 08:38 2014/02/07 08:38
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/928

cursor/caret 이야기

기왕 말이 나온 김에 또 문자 입력과 관련된 사용자 인터페이스 얘기를 좀 계속해 보자.

글자 입력란에서 깜빡거리고 있는 길쭉한 세로줄(|)을 우리는 보통 '커서'라고 부르고 영어로 cursor이라고 한다.
그러나 이 명칭은 우리말로는 용언 '크다'의 활용형으로 보여서 보기가 안 좋고, 영어로는 마우스 포인터조차 cursor이라고 부르는 경우가 있다. 실제로 Windows API에서 사용되는 LoadCursor, ShowCursor 같은 단어는 전부 마우스 포인터를 가리키며, 내부 공식 용어는 캐럿(caret)이다. 함수명도 ShowCaret, HideCaret따위다.
용어가 대단히 혼란스럽다는 점을 인정하지 않을 수 없다. 이 글에서는 편의상 그냥 cursor이라고 적도록 하겠다.

텍스트 모드가 존재하던 도스 시절에는 이 cursor가 반각 폭을 차지하는 밑줄 모양(_)이었다. 그리고 일부 환경에서는 겹침 모드일 때는 cursor가 약간 두툼해지곤 했다. 그 일부 환경 중의 하나로는 GWBASIC의 대화식 환경도 포함돼 있다.
그때도 cursor를 보이게 하거나 감추는 도스 API가 있었다. 그리고 심지어 cursor의 굵기를 바꾸는 기능도 있어서 옛날 노턴 유틸리티의 구성원 중 하나이던 NCC(Norton Control Center)라는 프로그램을 통해 변경을 할 수 있었다.

그러고 보니 정말 옛날이구나.
NCC의 스크린샷이 궁금했는데 구글을 뒤져도 관련 그림 하나 찾을 수 없을 정도로 완전히 역사 속으로 묻혀 버렸다.
얘는 cursor 모양을 포함해서 키보드와 마우스의 속도를 바꾸는 액세서리 기능도 있었다.
지금은 Windows의 제어판을 통해서 키보드의 속도를 바꾸지만 도스 시절에는 외부 명령인 MODE CON을 이용하거나 별도의 그런 유틸리티를 썼다. 그런 프로그램들 역시 내부적으로는 도스 API를 호출하는 형태였겠지만 말이다.

GUI 기반인 Windows에도 cursor는 응당 존재한다.
기술적으로 볼 때 cursor는 특정 시간 간격으로 운영체제가 DC에다 비트맵을 XOR이라는 래스터 연산을 적용하여 그려 주는 동작일 뿐이다. WM_CREATE 때 cursor를 생성하고, WM_DESTROY 때 파괴한다. 그리고 WM_SETFOCUS 때 화면에 표시를 해 주고 WM_KILLFOCUS 때 감춰 주면 된다.

숨김/감춤 요청이 교대가 아니라 중첩해서 발생할 경우, 운영체제는 일종의 reference counting을 해 준다.
하지만 개인적으로는 창이 WM_SETFOCUS와 WM_KILLFOCUS에 대한 처리를 운영체제가 자동으로 하게 API를 설계하는 게 더 나았을 거라는 생각을 한다. 언제나 단 한 프로그램에서만 cursor가 깜빡이는 게 보장되도록 말이다.

cursor만을 위해 문서화되지 않은 전용 타이머 메시지가 쓰인다는 것은 Spy++ 같은 프로그램을 통해 이미 아는 개발자들이 많을 것이다. 0x118인데, 편의상 흔히 WM_SYSTIMER라고 이름을 붙이는 듯하다.

한글을 조합 중일 때 cursor가 조합 중인 한글 전체를 감싸며 깜빡이는 것은, 도스 시절의 자체한글 프로그램들로부터 전해지던 전통이다. 나름 우리나라에서만 볼 수 있는 관행인데 MS가 로컬라이즈 차원에서 이런 시각 피드백을 Windows에다가도 적극 도입했다. MS 워드 6.0이 이를 첫 지원했으며 Windows 95때부터 운영체제의 에디트 컨트롤까지 지원이 확대되었다.
그냥 일본어/중국어를 입력할 때처럼 조합 중인 한글을 대충 밑줄로 표시하는 걸로 때워도 됐을 텐데 이를 특별히 배려한 것이다. Windows 3.x 내지 맥 OS에서는 깜빡이는 네모 cursor를 볼 수 없다.

과거에 훈민정음 워드 프로세서는 한글 모드일 때 cursor가 흰색 배경에 대해서 붉은색으로 변하곤 했다.
그리고 MS 오피스 2010은 수평이 아닌 임의의 각도로 기울어진 텍스트 상자에 글을 입력할 때... 입력란도 실제로 그 기울어진 각도를 반영하여 동작하는 직관적인 UI를 구현해 냈다. 이때 cursor도 당연히 기울어진 상태로 나타난다. 이런 cursor는 어떻게 만들었을까?

사용자 삽입 이미지

이것들도 다 CreateCaret 함수에서 크기만 달랑 지정하는 게 아니라 비트맵을 지정해 줌으로써 구현 가능하다.
비트맵 인자로 NULL을 넣으면 해당 영역의 모든 비트맵의 비트가 1이어서 전체 영역이 반전된다. 즉 InvertRect 함수를 호출하는 것과 동일한 효과가 난다.
그리고 MSDN에서 볼 수 있듯이, (HBITMAP)1을 집어넣어 주면 비트 0과 비트 1이 번갈아가며 나타나는 그 50% gray가 지정된다.

이와 같은 맥락으로, 옥색으로 채워진 비트맵을 지정하면 흰색 바탕에서는 보색인 빨강 cursor가 나타나며
검정 배경에 기울어진 흰색 사각형 모양의 비트맵을 지정하면 기울어진 cursor도 만들 수 있다.
요컨대 운영체제의 caret은 무조건 직교좌표계를 따르는 직사각형만 가능한 게 아니다.
임의의 비트맵과 XOR 연산을 하는 형태로 구현되기 때문에 임의의 모양의 cursor를 만들 수 있다.

다만, 별도의 마스크 비트맵이 존재하지 않기 때문에 마우스 포인터나 아이콘 같은 완전히 opaque한 스프라이트를 깜빡이게 할 수는 없으며, 알파채널 같은 걸 줄 수 없을 뿐이다. 그런 효과를 만들려면 프로그래머가 직접 cursor의 깜빡임을 타이머를 써서 구현해야 할 것이다.

Posted by 사무엘

2014/01/29 19:44 2014/01/29 19:44
, ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/925

지금 여러분의 PC에는 비트맵 그래픽 에디터로 무엇이 설치되어 있는가?
포토샵, 페인트샵, 페인트 닷넷, 심지어 그림판, 비주얼 스튜디오가 자체 제공하는 그래픽 에디터 등등...
이런 것들을 떠올리면서 각 프로그램들이 텍스트를 삽입하는 텍스트 도구가 어떤 형태로 구현되어 있는지 생각해 보시기 바란다.

보통은 텍스트 도구를 실행하면 텍스트를 삽입할 지점 내지 영역을 지정할 수 있고, 그 뒤에는 어지간해서는 텍스트 입력란이 별도의 대화상자나 창을 통해서 뜨게 된다.
그런데 Windows가 기본 제공하는 그림판은 다소 참신하게 설계되어 있다.

사용자 삽입 이미지

텍스트를 입력받는 영역이 그림 내부에 일체형으로 생긴다. 그림 내부에 cursor가 생겨서 텍스트를 곧바로 입력할 수 있으며 심지어 블록까지 잡을 수도 있는데,
일괄적인 단색이 아니라 기존 그림이 그대로 배경에 깔린다. 즉, 텍스트를 입력했다가 지우면 원래 그림이 도로 보존된다는 뜻이다. 별로 깜빡거리는 현상도 없다. 게다가 윈도 95 시절부터 그림판은 이렇게 동작해 오고 있었다. 참고로 페인트 닷넷이나 과거 윈도 3.1 시절의 페인트는 텍스트가 그림에 바로 삽입은 되지만, 블록까지 잡을 수는 없다.

이건 보통일이 아님을 알 수 있다. 아무래도 운영체제의 일반 에디트 컨트롤로는 불가능한 일인 것 같다.
WS_EX_TRANSPARENT라는 스타일이 있고 자체적으로 배경을 지우지 않게 WM_ERASEBKGND 메시지를 서브클래싱한다 해도, 일단 글자에 가려졌던 배경을 깜빡거림 없이 지능적으로 복구하는 일은 해당 컨트롤이 알아서 해 줘야 하기 때문이다.

Spy++로 살펴보면, 텍스트를 입력받는 동안엔 그림 클라이언트 영역 내부에 리치 에디트 컨트롤이 하나 생기긴 한다.
잘은 모르겠지만 얘는 일반 에디트 컨트롤보다 기능이 더 많고 전문적인 만큼, 옵션을 줌으로써 투명하게 동작하는 것에 대비되어 있는 게 아닌가 싶다.

과거의 그림판은 글꼴이나 크기, 색깔을 바꾸면 그게 모든 텍스트에 일괄 적용되었다. 하지만 7의 그림판은 리치 에디트답게 속성을 글자별로 다 따로 줄 수 있다. 뭐, 일괄 적용되던 시절에도 어차피 리치 에디트 기반인 건 동일했으니 그건 단순히 개발 난이도를 낮추기 위해 사용되었던 정책인 것 같다. 그래픽 에디터는 전문적인 텍스트 에디터가 아니니까 말이다.

상황을 좀 더 일반화해서 생각해 보자.
사실, GUI 위젯의 구성요소로는 각종 버튼들, 리스트 박스, 콤보 박스 등 여러 물건들이 있는데,
그 중 기술적으로 가장 만들기 힘든 것은 단연 에디트 컨트롤이다.
키보드 입력을 가장 정교하게 처리해야 하고, 텍스트의 변경으로 인해 텍스트의 전체 레이아웃이 바뀌는 것을 그때 그때 처리해야 하며 그러면서 화면상으로 바뀐 부분만 다시 그리거나 스크롤해 줘야 한다.

에디트 컨트롤을 만들어야 하는데 이런 어렵고 복잡한 내부 처리는 다른 컴포넌트에다 맡기고, 주어진 레이아웃대로 화면에 글자를 출력하는 것만 사용자가 customize할 수는 없을까?

예를 들어서 게임을 만들 때 말이다. 채팅창이 있는데 글자는 그림자 같은 일반적인 리치 에디트 포맷에 없는 특수한 효과가 적용되고, 배경으로는 게임 배경 화면이 알파 채널로 겹쳐진다. 이런 그래픽 출력을 일반 윈도우 DC를 이용해서 할 수는 없는 노릇이다.

이런 경우, 별 수 없이 게임 내부의 GUI 라이브러리를 개발하는 사람이 야메로 에디트 컨트롤을 직접 구현한다.
직접 만들면 세세한 제어가 가능하니 속 편한 경우도 있지만, 외국 시장까지 생각했을 때 아무래도 높은 완성도를 기대하기 어렵다. 중국어· 일본어 IME의 시각 피드백을 출력한다거나 아랍어가 뒤섞인 텍스트를 제대로 처리하는 에디트 컨트롤을 혼자 다 만든다는 건 불가능하며 그럴 필요도 없다.

본인이 개발한 <날개셋> 타자연습에도 기술적으로 완전 구닥다리이긴 하지만 그래도 DirectDraw surface를 만들어서 동작하는 게임이 있다.
내 원래 계획은 입력 중인 글자는 게임 배경에 같이 오버랩되어 표시되는 것이었다.
하지만 그것까지 구현하는 게 어려워서 그냥 화면 하단에 날개셋 에디트 컨트롤을 때려박아 넣는 형태로 프로그램이 만들어졌다. 에디트 컨트롤의 내부 알고리즘이 내린 지시를 custom 출력 매체에다 효과적으로 임베딩하는 방법이 필요하다.

이런 생각을 마소의 개발자가 안 했을 리가 없다.
그래서 Windowless Rich Edit Control이라는 게 있다. 리치 에디트 컨트롤의 내부 알고리즘을 COM 객체 형태로 만든 것이다. 얘를 이용하면 굳이 독립된 윈도우를 만들지 않고도 리치 에디트 컨트롤처럼 동작하는 객체를 손쉽게 구현할 수 있다.

물론, 스펙을 보면 알겠지만 우리 쪽에서 처리해야 할 요청이 한두 개가 아닌 관계로, 마냥 쉽게만 사용할 수 있는 물건은 아닐 수도 있다.
우리는 ITextHost라는 인터페이스를 구현하여 “어디에다 cursor를 만들어라, 무슨 크기를 얻어 와라” 같은 요청를 처리하면 되고, 운영체제의 기본 서비스가 필요하면 ITextServices 인터페이스를 호출하면 된다. 이들 객체는 CreateTextServices 함수를 통해 주고받으면 된다.

본인은 그렇잖아도 문자 입력과 관련된 프로그래밍 경험이 많은 관계로, 운영체제에 이런 API가 있는 것은 진작부터 알고 있었고, 이걸 실전에서 활용도 몇 차례 해 봤다. 단, 정작 그림판은 이런 windowless 오브젝트를 사용한 게 아니라 내부에 특수 처리된 리치 에디트 컨트롤 윈도우를 직접 생성했다는 게 의외로 놀랍다.
text services라는 단어 자체는 이 때부터 있었던 셈이다. 그러니 IME를 대체하는 Windows의 차세대 문자 입력 프로토콜이 text services framework, 즉 TSF라고 명명된 것은 우연이 아니라 하겠다.

Posted by 사무엘

2014/01/26 19:31 2014/01/26 19:31
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/924

유니코드 1.x가 제정되었을 때는 믿거나 말거나 한글도 글자마디는 완성형 몇천 자만 지금과는 딴판의 영역에 등록되어 있었다. 단, 글자마디뿐만 아니라 옛한글을 포함한 자모도 자음 134개(물론 초성과 종성의 개수는 서로 다르고 따로 등록됨), 모음 66개가 등록되어서 이 자모들을 아래아한글 2.x가 한컴 2바이트 코드에서 그대로 채택해서 썼다. 한컴 2바이트 코드는 기존 조합형 코드와 유니코드를 적당히 짬뽕한 문자 코드였던 것이다.

유니코드는 첫 도입 배경이 저렇다 보니 “유니코드 = all 2바이트 단일 문자 체계 = wide character 문자 체계”라고 혼동하는 사람이 적지 않다. 그러나 이는 개념적으로 올바른 관계가 아니다. 2바이트 문자 체계로만 치자면 한컴 2바이트 코드도 이에 해당하며, 윈도 이외에 유닉스 계열 OS에서는 wide character는 16비트가 아니라 32비트인 경우도 있다. 왜 32비트이냐 하면 6만 5천여 개라는 집합 크기도 시간이 흐르다 보니 부족해졌기 때문이다.

1996년에 유니코드 2.0이 제정되면서 유니코드는 오늘날 우리가 아는 유니코드와 같은 뼈대가 갖춰졌다. 한번 등록한 문자는 이제부터 재배치나 제거 없이 영구불변으로 굳히기로 했으며, 이때 현대 한글 글자마디 11172자가 순서대로 고스란히 등록되었다. 2바이트 조합형처럼 비트 연산은 못 쓰지만 그래도 테이블 없이 뺄셈과 나눗셈, 나머지 연산으로 한글의 자소 정보를 얻을 수 있으니, 과거 조합형 한글 코드의 장점을 유니코드에서도 물려받은 셈이다.

외국 위원들의 반대를 무릅쓰고 한글에다 유니코드의 금싸라기 영역을 이만치 할당 받기 위해 한국 위원회 사람들이 애를 많이 썼다. 사실 확장완성형도 유니코드 등록 근거를 대기 위해 급히 만들어진 것이라고 한다.
이와 더불어 유니코드 2.0에서는 중요한 개념 내지 꼼수가 추가로 도입되었는데, 바로 surrogate이다.
16비트 공간 중 일부 영역은 그대로 쓰지 말고 따로 떼어 내서 두 글자를 뭉쳐서 더 많은 종류의 글자를 표현하는 데 쓰자는 것이다.

0xD800부터 0xDBFF는 high surrogat이고, 0xDC00부터 0xDFFF까지는 low surrogate이다. high는 언제나 앞에, low는 언제나 뒤에 오는 식으로 역할이 딱 고정되어 있다. 1024개의 앞짝과 1024개의 뒷짝이 만나니, 총 2048개의 독립 글자를 희생하여 2의 20승, 약 100만여 개의 글자 공간을 추가로 확보할 수 있게 된다. 요컨대 유니코드에 U+D800 같은 글자는 없고, U+D7FF 다음에는 곧바로 U+E000이 이어진다. 그 대신 0xD800과 0xDC00이 만나면 U+10000이라는 surrogate, 즉 확장 평면이 시작될 뿐이다. 예전의 16비트 단일 영역은 '기본 다국어 평면'(BMP)이라고 불리게 되었다.

비록 과거의 1바이트/2바이트 혼합 체계보다는 사정이 훨씬 낫고 문자열 처리가 수월한 건 사실이지만, 어쨌든 유니코드도 확장으로 인해 2바이트 단일 표현(UCS2)이라는 깔끔한 장점은 포기해야 하게 되었다. 이쯤 되니 유니코드라는 개념에서 문자 코드와 인코딩을 구분해서 표현해야 할 필요가 생겼다.

유니코드 자체는 UCS, 즉 universal character set이라 불리는 단일 문자 집합이다. 얘는 '가'라는 한글 글자에다가 U+AC00이라는 코드값을 부여해 줄 뿐, 그 코드값이 메모리나 파일에 어떻게 표현되는지에 대해서는 규정하지 않는다. 이를 표현하는 방식이 바로 Unicode transfer format, 즉 인코딩이 된다.

모든 유니코드 번호를 아무 뒤끝 없이 32비트 정수 하나로 균일하게 표현하면 그것은 UTF32이다. 아까 wide character가 4바이트인 플랫폼은 바로 UTF32를 구현하는 자료형인 것이다. 가장 깔끔하고 공간도 넉넉하고 모든 글자를 하나씩 쉽게 접근할 수 있지만 한 글자가 4바이트나 차지하여 메모리 낭비가 심하다.

BMP 영역에 있는 놈은 종전대로 16비트 정수 하나로 표현하고 확장 평면에 있는 놈만 surrogate 두 개로 쪼개서 표현하는 방식은 UTF16이라고 불린다. UCS2를 개념적으로 확장한 것이기 때문에 처음부터 2바이트 wide character를 표방하며 개발된 Windows 플랫폼이 매우 사랑하는 방식이다. 비록 Windows의 wchar_t는 이제 유니코드 코드 포인트 하나를 온전히 표현할 수 있을 정도로 충분히 크지 못한데도 말이다.

끝으로, 그 이름도 유명한 UTF8이 있다. 얘는 U+007F 안에 드는 전통적인 알파벳· 숫자, 제어 문자들은 1바이트로 유지하고, 나머지 BMP 영역의 외국어들은 2~3바이트로 표현하고 surrogate는 BMP와 동일한 4바이트로 늘여 표현하는 진정한 multibyte 인코딩이다.

n째 문자를 찾는 무작위 접근은 안 되겠지만, Windows NT처럼 커널을 처음부터 16비트 문자 단위로 설계하지 않고도 기존 8비트 문자 시스템에서 유니코드를 단절감 없이 표현할 수 있다는 엄청난 장점이 있다. CPU의 엔디언(비트 배열 순서)의 영향을 받지 않으며, tail byte가 기존 영문· 숫자와 충돌을 일으키지도 않는다는 점도 좋고 말이다.
그래서 얘는 웹에서 매우 사랑받고 있고 윈도 이외의 플랫폼에서는 파일뿐만 아니라 메모리 저장용으로도 UTF8이 즐겨 쓰인다. 사실 윈도만 이례적으로 UTF16을 너무 사랑하고 있는 것이고.. ㅎㅎ

여담이지만 UTF32, 16, 8 중 유니코드의 문자 집합이 먼 미래에 또 확장되어야 할 때, 공간이 가장 넉넉하게 남아 있는 놈은 두 말할 나위 없이 UTF32이다. UTF8이야 애초부터 들쭉날쭉 가변 길이 인코딩을 표방했기 때문에 한 글자당 4바이트보다 더 긴 5~6바이트까지 약간 더 확장할 여지가 있다. 지금 당장은 그것까지는 쓰이지 않지만 말이다.

하지만 UTF16은 유일하게 확장의 여지가 전혀 없다. 지금 surrogate 영역이 다 차 버리면 surrogate의 surrogate라도 또 만들지 않는 이상 답이 없다. 그리고 그런 헛짓을 할 바에야 차라리 UTF16을 폐기하고 UTF8 아니면 UTF32로 갈아타는 게 낫지..;; 이런 미묘한 면모가 있다.

다만, 한글 표현의 관점에서는 메모리가 가장 적게 드는 인코딩이 UTF16이라는 것도 감안할 점이다. 현대 한글 글자마디의 경우 UTF8은 3바이트, UTF32는 4바이트이지만 UTF16은 2바이트다. 초-중-종성이 모두 갖춰진 옛한글이라면 UTF8과 UTF32는 각각 9바이트, 12바이트를 차지하지만 UTF16은 6바이트이니 차이가 더 벌어지게 된다. 유니코드에는 한글이 완성형(글자마디+호환용 자모) 방식과 조합형(세벌 한글 자모) 방식이 모두 등록되었으며 완성형 방식도 11172자가 순서대로 모두 등록되었으니, 예전의 조합형· 완성형 논쟁을 완전히 종결짓는 데 성공했다.

Windows NT도 처음에 개발될 때 문자 처리 단위를 wide로 무리해서 넓히느라 고생하지 말고, 그냥 UTF8만 도입했으면 어땠을까 싶지만 이제 와서는 다 지난 일이다. 아무래도 UTF8보다야 UTF16이 컴퓨터의 입장에서는 더 빠르고 간편하게 각각의 문자를 인식할 수 있으며, 이것이 메모리와 성능 소모면에서 UTF32와 UTF8 사이의 완충지대 역할을 해 온 건 부인할 수 없기 때문이다. 덕분에 Windows API의 관점에서는 UTF8조차도 native 인코딩인 유니코드 UTF16으로부터 '변환'되어야 하는 여러 multibyte 인코딩 중의 하나일 뿐이다~! ㅎㅎ

옛날에 인터넷 익스플로러의 버전이 5~6이던 시절엔 한글로 된 URL이 인식이 잘 안 돼서 맨날 “URL을 UTF8로 보냄” 옵션을 끄라는 지시가 팁처럼 공유되곤 했다. 당시엔 Unicode-aware하지 않은 웹 서버가 아직 많아서 말이다. 오늘날로 치면 UAC(사용자 계정 컨트롤)를 끄거나 팝업 창 허용 기능을 사용하는 것과 비슷한 편법이다.
하지만 요즘은 UTF8 방식 URL이 표준으로 정착한 지 오래다. 호환성을 중요시하는 IE에나 그런 옵션이 있지, 크롬 같은 브라우저는 선택의 여지 없이 무조건 UTF8이기도 하고 말이다.

유니코드는 문자 처리 기술의 발전과 함께 더욱 덩치가 커졌으며, 한편으로 더욱 복잡해지고 지저분해지고 있다.
UTF32가 아닌 인코딩으로는 어차피 메모리 배열 인덱스만으로 실제 글자 인덱스를 얻을 수 없는 것은 물론이거니와, 메모리 상으로 존재하는 글자 코드 포인트 수와, 화면에 출력되는 글자의 수도 일대일 대응이 전혀 이뤄지지 않게 되었다. 옛한글이라는 것도 유니코드 관련 기술이 방대해지면서 덤으로 같이 처리 가능해졌을 뿐이다. 진짜 미치도록 복잡한 문자에 비하면 옛한글 쯤이야 양반이지... 동일한 글자를 나타내는 방법이 여러 가지 존재하게 되어서 정규화라는 것도 필요해졌다.

코드 포인트 값만으로 정렬을 하는 것 역시 상당수 의미를 잃었다. 옛한글 자모는 유니코드 5.2 때 한양 PUA 비표준에만 존재하던 것들이 추가 등록되었는데, 이것들의 코드 포인트상의 순서를 따지는 건 부질없는 짓이다. 가뜩이나 부족한 BMP 공간의 여러 틈새에 산발적으로 간신히 등록된 것 자체를 감지덕지해야 할 판이다.

한자는 더욱 가관이다. 유니코드에서 공간을 압도적으로 제일 많이 차지하고 있는 문자인 건 두 말할 나위도 없거니와.. BMP 영역에 이미 등록돼 있고 호환용 같은 이유도 없는데 작업자의 실수로 인해 나중에 surrogate 확장 평면에 중복 등록된 글자도 있고, 일본 문자 코드의 실수 때문에 문헌에 전혀 등장한 적이 없는 유령 한자가 등재돼 있기도 하다.

이것이 오늘날의 컴퓨터 문명을 지배하고 있는 가장 원초적인 규범에 속하는 유니코드의 현실이다. ㅎㅎ
수십 년 주기로 유니코드를 전면 reset하고 코드값을 재정리하면 어떨까 싶지만 그건 기존 컴퓨터 문명이 핵 전쟁 같은 걸로 폭삭 없어지지 않는 한, 상상 속에서나 가능한 일일 것이다.

Posted by 사무엘

2013/12/16 08:25 2013/12/16 08:25
, , , , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/909

디지털 컴퓨터는 전자 신호로 0과 1만을 인식할 수 있다. 그렇기 때문에 컴퓨터에서는 문자 역시 0과 1의 조합인 숫자로 표현되며, 문자를 그런 숫자에다가 대응시키는 규칙이 바로 문자 코드이다. 정보 교환을 원활히 하기 위해서는 보내는 쪽과 받는 쪽이 모두 문자 코드가 통일돼 있어야 함은 두 말할 나위가 없다.

정보량의 최소 단위는 1비트이지만 현실적으로 컴퓨터의 기억 장치들이 한 글자로 취급하는 정보량의 최소 단위는 8비트, 즉 바이트이다. 8비트, 2^8승, 즉 256이라는 정보량은 숫자와 알파벳을 정하는 데는 충분하고 상위 1비트를 잉여화하여 오류 검출용으로까지 사용할 수 있을 정도이지만, 한글이나 한자 같은 문자를 담는 데는 턱없이 부족하다.

한자는 글자를 체계적으로 부분글자로 분해할 뾰족할 방도가 없는 상태로 글자 수가 너무 많다. 한글은 글자 생성 체계는 한자보다 훨씬 더 유리한 위치에 있지만, 실용적으로 편하게 다루려면 여전히 1만여 개의 미리 조합된 음절 형태를 그대로 배당해야 한다.

예를 들어 현실에서 '학교'라는 단어는 두 글자로 간주되지, 데이터베이스나 문서 분량 같은 데서 5글자라고 계수되는 곳은 아무도 없다. 코드 차지 영역을 줄이자니 메모리 사용량이 늘며, 그리고 오늘날로 치면 화면에 보이는 글자 길이와 메모리를 차지하는 글자 길이가 서로 완전히 따로 노는 complex script 급의 복잡한 기술이 필요해진다.

결국 한글과 한자는 1바이트만으로 표현할 수 없고 통상 2바이트로 표현된다. 그런데 기존 1바이트 영문· 숫자를 건드릴 수는 없으니 1바이트와 2바이트 문자가 공존하는 multi-byte 코드 체계가 만들어진다. 영문권에서 패리티 비트 내지 유럽 특수문자를 표현하는 데 쓰이는 최상위 1비트는, 이 문자가 1바이트로 끝인지 아니면 2바이트 문자의 첫 바이트(lead byte)인지를 판별하는 비트로 쓰인다.
공교롭게도 한글· 한자는 폭도 균일한 2글자 분량을 차지하여 비주얼 상 잘 어울린다. 전각· 반각이라는 개념이 여기서 유래된다.

그런데 lead byte는 그렇다 쳐도 2바이트의 다음 둘째 바이트인 tail byte도 기존 순수 1바이트 문자들과(영문· 숫자 같은) 전혀 겹치지 않게 할 것인지? 아니면 어차피 얘는 lead byte에 의해 변별이 됐으니 좀 문맥 의존적으로 겹치는 걸 허용할 것인지가 문제가 된다. 한글 코드의 경우 완성형은 겹치지 않으나 조합형은 겹친다.

그렇기 때문에 조합형은 tail byte가 대문자 알파벳과 소뭇자 알파벳이 제각기 따로 될 수 있고 심지어 \ 역슬래시 문자가 나올 수도 있다. 이런 이유로 인해 조합형은 1비트 + 초중성 5비트씩 한글의 구성 원리를 매우 잘 반영하여 설계된 문자 코드임에도 불구하고 당시 8.3 제약이 있던 시절에 파일 이름을 사실상 한글로 지정할 수 없었으며, 심지어 이론적으로는 // 주석을 조합형 한글로 지정한 경우 \ 때문에 2-byte aware 하지 않은 컴파일러에서는 충돌이 발생할 수도 있었다.

옛날에 한글 MS-DOS 시절에 한글 도스 셸이나 QBasic처럼 텍스트 GUI(?)를 구현한 프로그램들을 실행해 보면 한국 마소가 굉장히 잘 만든 게 있었다. 메뉴나 대화상자 같은 게 표시되어서 배경에 있던 2바이트 텍스트를 반토막 내더라도 깨진 문자열을 보여주는 법이 결코 없었다. 늘 짝수 바이트가 유지돼야 하는 한글/한자 영역이 홀수 바이트로 줄어들면 가장자리를 하나 삭제해 주면 된다.

그러나 문맥 의존적인 문자 코드로 그런 걸 구현하려면 문자열을 처음부터 읽어 봐야 하고, 두 바이트 중 하나가 소실됐을 때의 뒷처리가 문맥 독립 코드보다 더 어려우면 어렵지 쉽지는 않을 것이다. 불가능하다는 뜻은 아니지만.

조합형을 옹호하고 완성형을 까기에만 바쁜 분들의 심정이야 세벌식+한글 에반젤리스트로서 본인은 적극 이해한다. 그러나 과거의 2바이트 한글 코드의 이면엔 이런 면모도 있었음을 지적하고자 한다. 완성형은 여러 이슈가 얽혀서 그렇게 만들어졌지, 작정하고 한글을 난도질하려는 악의적인 의도로 만들어진 건 아니었다. 문맥 독립성뿐만 아니라 굉장히 보수적인 국제 규격 권장 사항을 만족하는 방식으로 문자 코드를 만들려다 보니, 한글 11172자에다 한자와 각종 특수문자들까지 넣을 절대적인 공간 자체가 부족했기 때문이다.

나중에 완성형에다 어거지로 한글 부족분을 채워 넣은 cp949 확장완성형은 어차피 조합형처럼 문맥 독립성이 깨졌다. 물론, 어차피 이렇게 만들 거면 처음부터 조합형으로 가지 하는 비판은 피할 수 없게 된 게 사실이다. 그런데, 이런 비슷한 삽질을 일본도 문자 코드를 만들면서 했으며, 이를 우리나라도 그대로 답습했을 뿐이다.

조합형과 완성형은 11172자와 2350자의 차이뿐만 아니라 한글 낱자를 표현하는 방식에서 매우 큰 차이가 있다.
조합형은 글자마디와 자모의 차이가 사실상 없다. 그냥 초 중 종성 중 한 성분만 있으면 자모이고, 초성과 중성이 갖춰졌으면 글자마디이다. 초성 ㄱ과 종성 ㄱ을 구분해서 표현할 수 있고 초성+종성이나 중성+종성 같은 '미완성 한글'도 얼마든지 표현할 수 있다.

그러나 완성형엔 그런 개념이 없다. 한글 입력을 처음 시작했을 때 쓰라고 '호환용 한글 자모'라는 게 있는데 그건 한글이 아니라 명목상으로는 '특수문자' 영역이다. 그냥 한글 자모 모양인 그림일 뿐이다. 초성+종성 구분이나 미완성 한글 표현 같은 건 없다.

메모리 1바이트가 아깝던 16비트 도스 시절에 국내에서는 조합형 한글 코드+글꼴이 쓰였지 마소의 윈도처럼 2350자 완성형 코드+글꼴로 돈지랄을 하는 프로그램은 전혀 없었다. 이런 압도적인 인지도의 차이로 인해 조합형은 1992년에 한국의 복수 표준으로 승격되어서 cp1361이라는 이름으로 나름 운영체제의 코드 페이지에 등록도 됐다.

자, 이렇게 컴퓨터는 1바이트 인코딩에다가 한중일 문화권을 위한 1+2바이트 멀티바이트 인코딩 구도가 유지되었고 마소에서는 이를 도스 시절부터 코드 페이지라고 불렀다. 그런데 이런 문자 코드들은 (1) 알파벳· 숫자 같은 공통 문자를 제외하면 같은 문자라 해도 국가마다 배당된 코드값이 같을 수가 없고, (2) 문자 집합 자체의 크기가 너무 작아서 세계 모든 문자들을 한 코드 페이지에다 담을 수 없다는 문제가 있었다.

人(사람 인)이라는 한자가 한국· 중국· 일본의 표준 코드에서의 코드값이 같을 리가 없으니 (1)은 자명한 문제다. (2)의 경우, 2바이트 코드라 해도 앞서 보았듯이 1바이트 문자와의 충돌을 피하기 위해 매우 제한된 영역의 바이트들만 묶을 수 있다. 이 때문에, 문맥 의존까지 시킨다 해도 추가로 만들 수 있는 문자는 1만여 자에 불과하다. CJK에서 쓰는 상용 한자들이나 간신히 다 집어넣을 정도이다.

그래서 1980년대 말부터 이런 발상의 전환이 이뤄졌다. “앞으로 컴퓨터의 메모리는 증가하고 소프트웨어 국제화의 중요성은 커질 테니, 글자 하나의 크기를 16비트로 쿨하게 확장하고 6만여 자의 공간에다가 전세계 언어 문자들을 집어넣고 동일 문자 동일 코드값을 실현하는 게 어떨까?”

이것이 바로 유니코드의 존재 목적 되시겠다. 컴공 전공자가 아니더라도 컴퓨터에서 '유니코드'라는 말은 한 번쯤은 다들 들어 보셨을 것이다. 이건 그야말로 컴퓨터 문자 코드계의 바벨 탑 내지 에스페란토 같은 물건이다.

마이크로소프트는 소프트웨어의 국제화에 굉장한 혜안이 있던 기업이었다. 그래서 OS/2와 결별하고 Windows NT를 첫 개발할 때, 애초부터 8비트 문자가 아니라 유니코드를 담을 수 있는 16비트 문자를 기반으로 설계했다. 심지어는 NTFS 파일 시스템까지도. 그리고는 이를 독자적으로 wide character이라고 부르기 시작했다. 어차피 8비트 문자만으로 충분하던 라틴 문화권에서는 별로 득이 되는 것도 없이 메모리 사용량만 두 배로 뻥튀기되는 대가를 감수하고라도 말이다.

Win32 API는 기존 16비트 윈도 API를 최대한 닮게 설계되었지만, 문자열을 다루는 모든 API에 A 버전과 W 버전 구분이 생겼다. 다만, 32비트 시절에 처음으로 도입된 OLE/COM 같은 기술은 처음부터 오로지 유니코드(= wide) 문자열만 취급하게 만들어졌다.

PE 방식으로 만들어진 32비트 EXE/DLL은 string 테이블, 메뉴, 대화상자 같은 리소스의 저장 포맷도 다 유니코드 방식으로 바뀌었다. 윈도 3.1에다가 Win32s를 설치하면 32비트 커널 DLL뿐만 아니라 각종 코드 페이지 변환 테이블도 설치되는 걸 볼 수 있다. 그게 바로 리소스를 변환하기 위해서이다.

윈도 NT가 3.x나 9x보다 메모리 사용량이 매우 많았던 이유 중 하나도 유니코드 때문이며, 반대로 윈도 9x가 유니코드 API를 지원하지 않았던 이유도 가정용 PC의 부족한 메모리 문제 때문이다.
이 때문에 컴파일 스위치만 바꿔서 유니코드와 기존 멀티바이트를 모두 커버할 수 있는 TCHAR, _T 같은 개념도 그때 생겼다. 두 개의 문자 포맷을 모두 지원하는 작업은 정말 엄청난 대공사였을 것 같다.

Posted by 사무엘

2013/12/13 08:24 2013/12/13 08:24
, , , , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/908

16비트 시절에는 잘 알다시피 int 포함 machine word의 크기가 말 그대로 2바이트였다. 그리고 근거리/원거리 포인터가 존재했으며 복잡한 메모리 모델을 따져야 했다. 여기까지는 도스든 윈도든 공통이다. 그럼, 그 시절에 Windows 프로그래밍은 어떠했을까?

16비트 Windows는 잘 알다시피 모든 프로그램이 단일 스레드를 공유하면서 단일 메모리 주소 공간에서 실행되었다. 이런 열악한 환경에서 멀티태스킹을 구현하는 것은 결코 쉬운 일이 아니었다.
그래서 그 시절에는 콜백 함수를 운영체제에다 지정해 주는 것조차도 보통일이 아니었다. 오늘은 오랜만에 이 부분에 대해서 예전에 몰랐다가 최근에 본인이 추가로 알게 된 이야기를 좀 하겠다.

Windows API는 C언어 함수 형태로 만들어졌으니 그때는 지금으로 치면 C++ 가상 함수가 할 일을 함수 포인터로 다 처리하고 있었다. 윈도우 프로시저, 대화상자 프로시저 같은 것 말이다.

그런데 같은 프로그램이 두 번 중첩 실행되면, 코드는 동일하더라도 그 코드가 다루는 스택(데이터) context는 결국 프로세스별로 서로 달라질 수 있게 된다. 이를 어떻게 구분해야 할까?
32비트 이후부터는 가상 메모리 기술이 워낙 발달하여 그 context가 CPU 차원에서 알아서 따로 잘 처리된다. 그러나 16비트 기계엔 그런 개념이 없었다.

결국은, 실제 콜백 함수가 호출되기 전에 그 콜백 함수가 처리할 자신의 데이터 세그먼트 영역을 CPU 레지스터에다 써 주는 stub, 일명 썽킹(thunking) 코드를 소프트웨어적으로 별도로 만들어야 했다. C++로 치면, 클래스 멤버 함수 호출을 위해 this 포인터의 값을 EAX 레지스터에다 써 주는 것과 개념적으로 비슷하다.

우리가 만든 콜백 함수는 그 썽킹 함수를 통해서 호출해야 한 것이다. 운영체제에다가도 당연히 썽킹 함수를 알려 주고 말이다.
이것이 바로 16비트 시절 코드를 보면 밥 먹듯이 보이는 MakeProcInstance와 FreeProcInstance API 함수의 정체이다.
별도의 메모리를 추가로 할당해서 기계어 코드를 추가해 준 것이기 때문에, 메모리의 해제까지 해 줘야 한다.

대화상자 하나를 출력하더라도 아래처럼 말이다.

FARPROC pfnThunkProc = MakeProcInstance( (FARPROC)My_Actual_Dialog_Proc, hInstance); //내 대화상자 프로시저가 우리 인스턴스 핸들을 context로 동작하게 한다
DialogBox(hInstance, "Dialog_Resource_Name", hWndParent, (DLGPROC)pfnThunkProc );
FreeProcInstance(pfnThunkProc);

요즘 같으면 아래의 절차 하나만으로도 충분했을 텐데 말이다.

DialogBox(hInstance, "Dialog_Resource_Name", hWndParent, My_Actual_Dialog_Proc );

결국 My_Actual_Dialog_Proc라는 코드는 시스템 전체를 통틀어서 단 하나 유일하게 존재하겠지만,
이 프로그램을 여러 번 실행하더라도 각 프로그램들은 그 함수로부터 파생된 자신만의 고유한 콜백 함수를 통해서 대화상자를 호출하게 된다.

단, Windows 3.1에서는 함수명을 export해 주는 것만으로 이렇게 콜백 함수에 대한 썽킹을 매번 해 줘야 하는 번거로움을 생략할 수 있었다고 한다.

지금이야 프로그래밍에서 export 키워드라 하면, C++에서 템플릿의 선언과 정의를 번역 단위를 넘나들며 공유할 수 있게 하려 했던 비현실적인 흑역사 키워드일 뿐이다. Windows 플랫폼으로 문맥을 넓힐 경우, export는 클래스나 함수 심벌을 빌드되는 파일 차원에서 외부로 노출하여 GetProcAddress 함수로 노출이 되게 하는 속성 지정자이다. 비주얼 C++ 기준으로 __declspec(dllexport) 라고 쓴다.

지금이야 이런 export 속성은 말 그대로 DLL을 만들 때에나 쓰인다.
그러나 16비트 시절에는 EXE도 운영체제로부터 호출을 받아야 하는 콜백 함수를 넘겨 줄 목적으로 심벌 export를 즐겨 활용했었다.
export된 함수는 외부로부터 호출받는 게 당연시될 거라 여겨졌으므로, 링커와 운영체제가 자체적으로 데이터 세그먼트 보정을 하는 전처리 루틴을 추가해 줬다. 따라서 코드가 훨씬 더 깔끔해진다.

이런 이유로 인해 Windows 3.x 실행 파일들을 헥스 에디터로 들여다보면 대체로 ...WNDPROC, ...DLGPROC 같은 식으로 끝나는 웬 이상한 함수 이름들이 내부에 들어있는 걸 확인할 수 있다. 32비트 이후부터는 찾을 수 없는 관행이다. 자기 내부에서만 쓰는 콜백 함수들을 왜 저렇게 노출해 놓겠는가?

어떤 함수에 export 속성을 부여하기 위해서는 결국 C/C++ 언어에다가 없는 문법을 새로 추가하거나, 아니면 소스 코드는 그대로 놔 두고 컴파일러가 아닌 링커만이 인식하는 부가 정보 같은 게 주어져야 할 것이다. 전자는 소스 코드의 이식성을 떨어뜨린다는 부담이 있지만, 그래도 뒤끝이 없고 한 소스에 빌드에 필요한 모든 정보가 한데 모이니 편리하다. 그래서 그 당시에는 __export라는 비표준 MS 확장 키워드가 도입되었고, 이를 보통 EXPORT라는 매크로로 감싸서 사용하곤 했다. 이런 식으로 말이다.

long FAR PASCAL EXPORT MyWindowProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam); //그 당시 콜백 함수는 반드시 '파스칼' 방식이라는 함수 호출 규약대로 선언되어야만 했다.

한편 후자는 바로 그 이름도 유명한 DEF(모듈 정의) 파일이다.
지금도 DEF 파일이 안 쓰이는 건 아니지만 정말 DLL을 만들 때가 고작이다. 그 반면 16비트 시절에는 DEF가 훨씬 더 중요하게 쓰였던 모양이다.
export하는 콜백 함수뿐만 아니라 이 프로그램에 대한 소개문, 그리고 필요한 스택/힙 크기까지 정확하게 명시되어야 했다.

32비트 이후부터는 스택/힙이 부족하면, 프로그램 로딩 시간이 좀 더 걸릴 뿐이지 실시간으로 추가 할당하여 working set의 확장이 가능한 반면, 16비트 시절에는 메모리 양도 부족하고 또 한 주소 공간에서 여러 프로그램들이 뽁짝뽁짝 복잡하게 지내다 보니 이런 게 핀트가 어긋나면 프로그램 실행이 아예 불가능했을 것 같다.

옛날의 New Executable 포맷에는 자체적으로 name table이라 불리는 범용적인 문자열 테이블이 있었고, import/export하는 심벌 이름은 물론 이 EXE/DLL에 대한 간단한 소개문까지 그런 데에 들어갈 수 있었다.
지금으로 치면 버전 리소스에 들어가는 description과 유사한데 역할 중복인 셈이다. 그런 것까지 DEF 파일에다 명시해 줬었다.

자.. 설명을 들으니 어떤 생각이 드시는지?
16비트 Windows용 실행 파일을 분석하는 도구는 그리 충분치 않다. 일단 개발자들의 친구인 Dependency Walker가 이를 전혀 지원하지 않으며, 그나마 괜찮은 물건으로는 옛날에 Windows 98에 내장되어 있던 QuickView라는 프로그램이 적격이었다. 기본적인 사항은 다 잘 출력해 줬다.

16비트 시절에는 잘 알다시피 HINSTANCE는 각 프로그램의 데이터 세그먼트를 식별하는 번호표에 가까웠고, 개념적으로 지금 같은 포인터가 아니라 오늘날로 치면 오히려 프로세스를 식별하는 HANDLE처럼 널리 쓰였다. (비록 Close를 할 일은 없었겠지만 말이다) 게다가 EXE는 HINSTANCE, DLL은 HMODULE로 성격도 달랐다. 그래서 LoadLibrary 함수의 리턴값은 분명 HMODULE이다.

그때는 EXE/DLL로부터 리소스를 얻어 오는 과정도 정말 복잡했고 Load/Free 절차뿐만 아니라 lock/unlock까지 있었다. 메모리의 절대적인 양이 충분치 않기도 하고 가상 메모리 같은 시스템이 없던 관계로, 단편화를 방지하기 위해 평소에 즉각 쓰지 않는 메모리 블록은 재배치가 가능하게 놔 두는 관행이 더 보편적이었기 때문이다.

한편으로 그때는 시스템 전체에 영향을 끼치기가 쉬웠다. 특히 DLL이 이 분야에 관한 한 지존이었다.
DLL에서 선언한 전역변수는 모든 프로세스가 한데 공유되었으며, DLL이 실행하는 코드는 저런 EXE처럼 여러 컨텍스트에서 중첩 실행될 일이 없고 애초에 데이터 세그먼트 구분을 할 필요가 없었다!

그리고 16비트 시절엔 애초에 콜백 함수를 지정해 주는 과정이 처음부터 저렇게 번거로웠던 만큼, 시스템 전체의 동작 방식을 바꾸는 global 훅을 설치하더라도 훅 프로시저를 반드시 DLL에다가만 만들어야 한다는 제약이 없었다.
그럼에도 불구하고 16비트 시절은 32비트 시절보다 편한 것보다는 불편한게 더 많았다는 것이 부인할 수 없는 사실이다.

끝으로 여담.
이렇듯, 본인은 초등학교 아주 어릴 적부터 프로그래밍을 시작한 관계로, 그 시절에 대한 향수가 있고 '레트로'-_- 컴퓨팅 쪽으로 관심이 많다.
그런데, 그런 것에서조차도 양덕후들의 기상은 갑이다. the old new things 같은 블로그는 이 바닥의 지존이라고 봐야 하고, Windows 3.x로도 모자라서 2.x나 1.x용 프로그램을 만들었다고 인증샷 올리는 사람도 있다.

1985년에 출시된 Windows 1.x의 경우, VGA 카드조차도 없던 시절에 만들어진 관계로 가장 좋은 비디오 모드가 640*350 해상도짜리 EGA이다. 그런데 그걸 640*480 VGA 내지 심지어 800*600 SVGA에서 동작하게 드라이버 패치를 만든 사람도 있다.

참고로 Windows 1.x 정도 되면.. 프로그램 개발을 위한 컴파일러는 그야말로 1986년에 나온 MS C 4.x를 써야 하고, 얘는 함수 호출 인자도 ANSI 스타일이 아니라 K&R 스타일만 써야 하는 진짜 무지막지한 골동품이라고 한다. Windows 1.x는 파스칼로 개발되었다고 들었는데 그래도 그때부터 C 형태의 SDK는 있었던 모양이다.

Posted by 사무엘

2013/11/28 08:29 2013/11/28 08:29
, ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/903

Windows GUI 프로그램은 잘 알다시피 메시지로 운영체제와 의사소통을 하는 게 핵심이다. “이 창은 응답이 없음. 프로그램을 강제 종료할까요?”라고 판단을 하는 기준은 바로 n초 이상 GetMessage/PeekMessage 함수를 호출하지 않는 프로그램이다.

CreateWindowEx로 창을 하나 만든 뒤 메시지 loop은 GetMessage로 메시지를 받아서 그걸 DispatchMessage로 넘겨 주기를 반복하는 형태이다.
비록 메시지를 윈도우 프로시저로 전달하는 함수로는 CallWindowProc도 있겠지만, Dispatch는 타이머 메시지를 윈도우 프로시저 대신 lParam에 지정된 타이머 프로시저로 직통 전달하는 일까지 한다. 즉, 하는 일의 범위가 더 넓다. 그리고 Call은 메시지 큐를 처리하는 게 아니라 윈도우 프로시저의 서브클래싱용으로 쓰라고 있는 물건이다.

메시지를 Dispatch하기 전에 보통은 translation이라고 불리는 전처리 과정이 필요하다. TranslateMessage 함수가 그 일을 하는데, 키보드 WM_(SYS)KEYDOWN / WM_(SYS)KEYUP 메시지로부터 WM_(SYS)CHAR 메시지를 추가로 생성해 준다. 현재 설정되어 있는 키보드 드라이버의 정보를 토대로 가상 키코드를 해석하여, 그 키가 나타내는 입력 문자를 알려 준다는 뜻이다. 유럽 문자를 입력할 때 쓰이는 dead key를 처리하는 것도 translator의 몫이다.

이때 새로 생성된 메시지는 이 윈도우를 구동하는 스레드의 메시지 큐에 추가로 예약되어서 다음번 GetMessage 함수 호출 때 걸려 나온다. 인자로 전달된 MSG 구조체의 내용이 지금 당장 바뀌지는 않는다. 사실, 문자를 입력받는 윈도우가 아니라면 translation은 꼭 필요하지는 않은지도 모르겠다.

그런데 메시지를 굳이 윈도우 프로시저로 dispatch하기 전에, 응용 프로그램이나 다른 API 함수가 곧장 메시지를 가로채야 하는 경우는 굉장히 많이 있다. 액셀러레이터 단축키를 처리하는 TranslateAccelerator, 그리고 Ctrl+F6 같은 MDI 공용 단축키를 처리하는 TranslateMDISysAccel이 그 예이다.
TranslateMessage는 그런 관문들을 모두 통과한 메시지가 dispatch 직전에 최종적으로 거치는 관문일 뿐이다.

이런 절차가 한둘이 아니기 때문에 MFC의 CWnd 클래스는 그런 코드를 적절히 넣으라고 PreTranslateMessage라는 가상 함수까지 마련해 놓았다. 이 함수에서 키보드 메시지를 조작하여 엔터가 눌려도 대화상자가 종료되지 않게 하는 것 정도는 프로그래밍 초보 시절에 다들 해 보았을 것이다. 그러고 보니 이렇게 dispatch 전단계에서 조작하는 메시지는 다들 키보드 관련 메시지인 경우가 많다.

그리고 이런 식으로 전용 함수를 통해 미리 처리되는 또 다른 대표적인 메시지들은 바로.. 대화상자 관련 메시지들이다.
이걸 담당하는 물건으로는 IsDialogMessage라는 함수가 있다.

물론, MFC의 경우 저 함수를 포함해 위에서 언급한 각종 전처리, translation을 이미 알아서 전부 호출하며 메시지 loop과 심지어 WinMain 함수까지 몽땅 라이브러리 내부에다 짱박아 뒀기 때문에 MFC를 사용하는 프로그램이라면 자신이 직접 그런 함수를 또 호출해야 할 일은 거의 없을 것이다.
그러나 운영체제와 응용 프로그램 사이에 어떤 메커니즘으로 통신이 오가는지 알고는 있을 필요가 있다.

대화상자 안에서 사용자가 tab 키를 눌렀을 때 컨트롤들의 포커스가 바뀌는 것, Alt+알파벳을 눌렀을 때 특정 컨트롤로 바로 이동하는 것, 엔터를 눌렀을 때 '확인' 종료가 되고 ESC를 눌렀을 때 '취소' 종료가 되는 것 따위는 대화상자의 윈도우 프로시저의 관할이 아니다. 윈도우 프로시저가 메시지를 받기 전에 저 IsDialogMessage 함수가 일찌감치 처리한다. 심지어 default 버튼의 테두리를 굵게 칠하는 것조차도 저 함수가 담당한다.
물론 그런 전처리는 대화상자가 독단적으로 하는 게 아니라 자기 아래의 컨트롤에게 WM_GETDLGCODE 메시지를 보내서 컨트롤이 원하는 키보드 처리 양상에 대해서 물어는 보고서 한다.

DialogBox 함수로 modal 대화상자를 만들었다면야 그 함수가 자체적으로 메시지 loop을 돌면서 이런 처리를 모두 알아서 해 준다. 그러나 문제는 modeless 대화상자이다. modeless 대화상자는 그 창을 만든 응용 프로그램의 main 메시지 loop를 같이 쓰면서 돌아가기 때문에 그 loop에서 IsDialogMessage 함수를 호출하는 로직이 추가되어야 한다.

대화상자에서 엔터 키가 들어왔을 때의 동작에 대해서는 살짝 주의할 점이 있다.
일반 버튼은 자체적으로 엔터 키를 처리한다. 그래서 다른 모든 컨트롤들을 사용 중일 때 엔터를 누르면 '확인' 버튼이 눌러진 것으로 처리되고 대화상자가 곧장 종료되지만, '취소'나 '적용' 같은 다른 버튼에 키보드 포커스가 가 있을 때 엔터를 누르면 해당 버튼이 눌러진 것으로 처리된다.

즉, 엔터는 space와 사실상 동일하게 처리된다. 차이가 있다면 space는 key down 시점 때는 버튼이 눌러진 모습만 표시되고 key up 시점일 때 명령이 먹히는 반면, 엔터는 key down 때 곧바로 먹힌다는 것이 전부이다.
다만, 대화상자 안에 또 대화상자가 자식 윈도우로 생성되었고 자식 대화상자 안에 있는 버튼을 space가 아닌 엔터 키로 눌렀을 경우,

WM_COMMAND+BN_CLICKED 메시지는 자식 대화상자의 윈도우 프로시저로 전달되는 게 아니라 겉의 최상위 대화상자의 윈도우 프로시저로 전달된다.
그래서 그 버튼을 엔터 키로 누른 동작은 일반적으로 제대로 인식되지 않거나 최악의 경우 오동작--부모 대화상자와 자식 대화상자에 우연히 ID값이 같은 버튼이 공존한다면!--까지 하게 된다.

왜 그렇게 동작하는지 정확한 이유는 난 모르겠다.
물론, 일반적으로 엔터를 눌러서 대화상자를 닫는 메시지 자체는 자식 대화상자가 아니라 최상위 대화상자에다가 전달되는 게 개념상 올바르다.
하지만 그런 default 버튼이 아니라 일반 버튼을 엔터 키로 누르는 동작까지 왜 엉뚱한 곳으로 가는 걸까?

대화상자 안에 또 대화상자를 만드는 건 운영체제가 제공하는 프로퍼티 시트가 대표적인 예인데 그건 운영체제가 알아서 버튼의 처리를 잘 해 주는 듯하다. <날개셋> 타자연습 같은 프로그램에서 탭 안에 있는 버튼을 엔터로 눌러 보면 잘 안식된다.

그러나 자체적으로 자식 대화상자들을 표시하는 <날개셋> 한글 입력기의 제어판, 그리고 VMware, 반디집, EditPlus 등의 유수의 소프트웨어들이 제공하는 종합 설정 대화상자들을 살펴보면, 자식 대화상자 안의 버튼은 space만 인식되지 엔터 키로 인식되지 않는 걸 알 수 있다. (아래 그림에서 왼쪽)
단, Microsoft Visual Studio는 예외. '도구-옵션' 대화상자의 내부에 표시된 자식 대화상자들의 버튼은 엔터 키로 잘 인식된다. (아래 그림에서 오른쪽) 치명적인 버그 같은 부류는 아니지만 운영체제의 특성 때문에 발생하는 좀 이상한 면모라 하겠다.

사용자 삽입 이미지사용자 삽입 이미지
WM_COMMAND+BN_CLICKED 메시지는 lParam으로 이 메시지를 보낸 컨트롤의 윈도우 핸들을 친절하게 일일이 알려 준다.
그렇기 때문에 그런 버튼을 누르는 걸 인식하려면, 최상위 대화상자의 윈도우 프로시저에서 lParam을 검사하여 이것이 자식 대화상자가 아니라 내 대화상자의 버튼이 보낸 메시지가 맞는지를 확인해야 한다. 아니라면 그 버튼의 진짜 부모 윈도우--GetParent 함수로 확인--에다가 메시지를 도로 보내 버리면 된다.

Visual Studio의 옵션 대화상자도 Spy++로 동작을 확인해 보면, WM_COMMAND+BN_CLICKED 메시지가 두 번 가는 걸 보니 그런 식으로 동작하는 듯하다.

Posted by 사무엘

2013/11/22 08:36 2013/11/22 08:36
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/901

자고로 프로그래밍 언어는 구문이나 예약어 같은 원론적인 것만 정의하는 게 아니라, 그 문법을 토대로 프로그램의 작성에 필요한 각종 기본적인 자료구조와 알고리즘, 입출력 기능들도 라이브러리 형태로 제공한다. 후자의 디자인도 프로그래밍 언어의 정체성에서 차지하는 비중이 매우 크다.

예를 들어 printf, qsort, malloc, fopen 같은 함수를 사용했다면 그 함수들의 몸체도 당연히 프로그램 어딘가에 빌드되어 들어가야 한다. 아니, 애초에 main 함수나 WinMain 함수가 호출되고 각종 인자를 전해 주는 것조차도 그냥 되는 일이 아니다. 이런 것은 C/C++ 언어가 제공하는 런타임 라이브러리가 해 주는 일이며, 우리가 빌드하는 프로그램에 어떤 형태로든 코드가 링크되어 들어간다.

C/C++은 그나마 기계어 코드를 생성하는 언어이기 때문에 그런 런타임의 오버헤드가 작은 편이다. 그러나 비주얼 베이직(닷넷 이전의 클래식)이라든가 델파이는 RAD 툴이고 언어가 직접 GUI까지 다 커버하다 보니 언어가 제공하는 런타임 오버헤드가 크다.
자바나 C#으로 넘어가면 런타임이 단순 코드 오버헤드로 감당 가능한 정도가 아니라 아예 가상 기계 수준이다. 프로그램이 기계어 코드로 번역되지도 않으며, garbage collector까지 있다.

그렇게 프로그래머의 편의를 많이 봐 주는 언어들에 비해 '작은 언어'를 추구하는 C/C++은 도스나 16비트 윈도 시절에는 런타임 라이브러리가 static 링크되는 게 당연시되곤 했다. 오버헤드 자체가 수천~만 몇천 바이트밖에 되지 않을 정도로 매우 작은 편이고, 저수준 언어인 C/C++은 당연히 standalone 바이너리를 만들어야지 무슨 다른 고급 언어처럼 런타임 EXE/DLL에 의존하는 바이너리를 생성한다는 건 좀 안 어울려 보이는 게 사실이었다.

그래서 C/C++로 개발된 EXE 파일은 내부를 들여다보면 대체로, 링크되어 들어간 런타임 라이브러리의 이름과 개발사를 나타내는 문자열이 들어있었다. 볼랜드나 마이크로소프트 따위. 그래서 이 프로그램이 어느 컴파일러로 만들어졌는지를 얼추 짐작도 할 수 있었다.

C 런타임 라이브러리도 static이 아닌 DLL 형태로 제공하려는 발상은 Windows의 경우 아무래도 32비트 NT의 개발과 함께 시작된 듯하다. 그래서 윈도 NT 3.1의 바이너리를 차용해서 개발되었다는 과거의 Win32s를 보면 crtdll.dll이라는 파일이 있는데 이것이 운영체제가 기본 제공하는 프로그램들이 공용하는 C 런타임 DLL인 듯하다. 즉, 메모장, 문서 작성기, 그림판 등의 프로그램들이 호출하는 sprintf 같은 C 함수들의 구현체가 거기에 담겨 있다는 뜻이다.

재미있게도 윈도 NT의 경우, kernel32보다도 먼저 로딩되는 ntdll.dll이 내부적으로 또 각종 C 함수들의 구현체를 제공한다. 커널이 제대로 로딩되기도 전에 실행되는 프로그램이 공유하는 C 함수들이라고 한다.

과거의 윈도 9x의 프로그램들은 비록 32비트이지만 운영체제가 자체적으로 공유하는 C 런타임 DLL은 없다.
다만, 윈도 NT 3.x 이후로 비주얼 C++이 32비트 개발툴로 자리매김하면서 이 개발툴의 버전에 따라 msvcrt20.dll, msvcrt40.dll이 제공되기 시작했고 이들은 윈도 9x에서도 기본 내장되었다. 비록 같은 C 런타임이지만 버전별로 미묘한 차이가 있는 모양이다.

그러다가 1996년에 출시된 비주얼 C++ 4.2부터 C 런타임 DLL은 더 변경을 안 하려는지 파일 이름에서 버전 숫자가 빠지고, 그 이름도 유명한 msvcrt.dll이라는 이름이 정착되어 버전 6까지 쭉 이어졌다.
이 이름은 비주얼 C++ 4.2 ~ 6이 생성한 바이너리가 사용하는 C 런타임인 동시에, NT 계열에서는 기존의 crtdll을 대신하여 운영체제의 기본 제공 프로그램들이 공유하는 C 런타임의 명칭으로도 정착했다.

그리고 9x 계열도 윈도 98부터는 mfc42.dll과 더불어 msvcrt.dll을 기본 제공하기 시작했다.
NT와는 달리 운영체제가 msvcrt.dll을 직접 쓰지는 않지만, 비주얼 C++로 개발된 프로그램들을 바로 실행 가능하게 하기 위해서 편의상 제공한 것이다. 과거의 유물인 msvcrt40.dll은 msvcrt.dll로 곧바로 redirection된다.
그 무렵부터는 오피스, IE 같은 다른 MS 사의 프로그램들도 C 런타임을 msvcrt로 동적 링크하는 것이 관행으로 슬슬 정착해 나갔다.

그렇게 윈도, VC, 오피스가 똑같이 msvcrt를 사용하는 구도가 한동안 이어졌는데..
21세기에 비주얼 C++이 닷넷으로 넘어가면서 그 균형은 다시 깨졌다.
C 런타임 라이브러리도 한 번만 만들고 끝이 아니라 계속 버전업이 되어야 한 관계로 msvcr70, msvcr71 같은 DLL이 계속해서 만들어진 것이다. 결국, 비주얼 C++ 최신 버전으로 개발한 프로그램은 C 라이브러리를 동적 링크할 경우, DLL 파일을 배포해야 하는 문제를 새로 떠안게 되었다.

이것이 비주얼 C++ 2005/2008에서는 더욱 복잡해졌다. C 라이브러리를 side-by-side assembly 방식으로 배포하는 것만 허용했기 때문이다. 쉽게 말해, 레거시 공용 컨트롤과 윈도 XP의 비주얼 스타일이 적용된 공용 컨트롤(comctl32.dll)을 구분할 때 쓰이는 그 기술을 채택했다는 뜻이다.

그래서 msvcr80/msvcr90.dll은 윈도 시스템 디렉터리에만 달랑 넣는다고 프로그램이 실행되지 않는다. 이들 DLL은 DLLMain 함수에서 자신이 로딩된 방식을 체크하여 이상한 점이 있으면 고의로 false를 되돌린다. 그렇기 때문에 이들은 반드시 Visual C++ 재배포 런타임 패키지를 통해 정식으로 설치되어야 한다.

런타임 DLL간의 버전 충돌을 막기 위한 조치라고 하나 이것은 한편으로 너무 불편하고 번거로운 조치였다. C 라이브러리가 좀 업데이트돼 봤자 메이저도 아니고 마이너 버전끼리 뭐가 그렇게 버전 충돌이 있을 거라고..;; 나중에는 여러 프로그램들을 설치하다 보면 같은 비주얼 C++ 2005나 2008끼리도 빌드 넘버가 다른 놈들의 재배포 패키지가 설치된 게 막 난립하는 걸 볼 수 있을 정도였다. 가관이 따로 없다. 당장 내 컴에 있는 것만 해도 2008 기준으로 9.0.32729까지는 똑같은데 마지막 숫자가 4148, 6161, 17, 4974... 무려 네 개나 있다.

개발자들로부터도 불편하다고 원성이 빗발쳤다. MS Office나 Visual Studio급으로 수십 개의 모듈로 개발된 초대형 소프트웨어를 개발하는 게 아니라면, 꼬우면 그냥 C 라이브러리를 static 링크해서 쓰라는 소리다.
그래서 비주얼 C++ 2010부터는 C 라이브러리 DLL은 다시 윈도 시스템 디렉터리에다가만 달랑 집어넣는 형태로 되돌아갔다. 다시 옛날의 msvcrt20, msvrt40, msvcr71처럼 됐다는 뜻이다.

윈도 비스타 타임라인부터는 운영체제, VC, 오피스 사이의 관계가 뭔가 규칙성이 있게 바뀌었다.
오피스는 언제나 최신 비주얼 C++ 컴파일러가 제공하는 C 라이브러리 DLL을 사용하며, 운영체제가 사용하는 msvcrt도 이름만 그럴 뿐 사실상 직전의 최신 비주얼 C++가 사용하던 C 라이브러리 DLL과 거의 같다.

그래서 오피스 2007은 msvcr80을 사용하며, 오피스 2010은 비주얼 C++ 2008에 맞춰진 msvcr90을 사용한다. C 런타임 DLL도 꾸준히 버전업되고 바뀌는 물건이라는 것을 드디어 의식한 것이다. 비스타 이전에 윈도 2000/XP의 EXE/DLL들은 헤더에 기록된 링커 버전을 보면, 서로 사용하는 컴파일러가 다르기라도 했는지 통상적인 비주얼 C++이 생성하는 EXE/DLL에 기록되는 버전과 일치하지 않았었다. 9x는 더 말할 필요도 없고.

그럼에도 불구하고 msvcrt는 운영체제 내부 내지 디바이스 드라이버만이 사용하고, 비주얼 C++로 개발된 여타 응용 프로그램들은 언제나 msvcr##을 사용하게 용도가 확실하게 이원화되었다.
그래서 심지어는 똑같이 MS에서 개발한 한글 IME임에도 불구하고 Windows\IME 디렉터리에 있는 운영체제 보급용은 msvcrt를 사용하고, 한글 MS Office가 공급하는 IME는 Program Files에 있고 msvcr##을 사용한다. 둘은 거의 똑같은 프로그램인데도 말이다.

이것이 Windows와 C 런타임 라이브러리 DLL에 얽힌 복잡한 역사이다. 여담이지만 C 라이브러리 중에서 VC++ 2003이 제공한 msvcr71은 Windows 95를 지원한 마지막 버전이다. 2005가 제공한 msvcr80부터는 일부 보안 관련 코드에서 IsDebuggerPresent라는 API 함수를 곧장 사용함으로써 Windows 95에 대한 지원을 중단하였으며, 최하 98은 돼야 동작한다. VC++ 2008부터는 9x 계열에 대한 지원 자체가 중단됐고 말이다.

자, 여기서 질문이 생긴다. 그럼 C++은 사정이 어떠할까?

C보다 생산성이 훨씬 더 뛰어난 C++로 주류 프로그래밍 언어가 바뀌면서 표준 C++ 라이브러리에 대한 동적 링크의 필요성도 응당 제기되었다. 그러나 이것은 C 라이브러리보다는 시기적으로 훨씬 더 늦게 진행되었기 때문에 가장 먼저 등장하는 비주얼 C++의 DLL 이름이 msvcp50과 msvcp60이다. 즉, 비주얼 C++ 5.0 이전에는 선택의 여지 없이 static 링크만 있었다는 뜻이다.

더구나 이것은 전적으로 비주얼 C++ 소속이지, 운영체제가 따로 C++ 라이브러리 DLL을 제공하지는 않는다. MS에서 만들어진 제품들 중에 msvcp##를 사용하는 물건은 비주얼 C++ IDE와 컴파일러 그 자신밖에 없다.

C++ 라이브러리는 대부분 템플릿 형태이기 때문에 알고리즘 뼈대는 내 바이너리에 static 링크되는 게 사실이다. 그러나 그렇게 덧붙여지는 라이브러리 코드가 별도로 호출하는 함수 중에 DLL에 의존도를 지니는 게 있다. cout 객체 하나만 사용해도, 그리고 STL 컨테이너 하나만 사용한 뒤 빌드해 봐도 형체를 알 수 없는 이상한 메모리 관리 쪽 함수에 대한 의존도가 생긴다.

그래도 C 라이브러리 DLL은 사용한 함수에 따라서 printf, qsort 등 링크되는 심벌이 명확한 반면
C++ 라이브러리는 내부 구조는 이거 뭐 전적으로 컴파일러가 정하기 나름이고 외부에서 이들 심벌을 불러다 쓰기란 사실상 불가능하다는 큰 차이가 있다.

<날개셋> 한글 입력기는 C++ 라이브러리를 사용하지 않으며, 빌드 후에 생성된 바이너리를 인위로 수정하여 msvcr## 대신 강제로 운영체제의 msvcrt를 사용해서 동작하게 만들어져 있다.

Posted by 사무엘

2013/11/19 08:29 2013/11/19 08:29
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/900

« Previous : 1 : ... 15 : 16 : 17 : 18 : 19 : 20 : 21 : 22 : 23 : ... 31 : Next »

블로그 이미지

그런즉 이제 애호박, 단호박, 늙은호박 이 셋은 항상 있으나, 그 중에 제일은 늙은호박이니라.

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        

Site Stats

Total hits:
3042637
Today:
2264
Yesterday:
1700