Windows에서 응용 프로그램의 창을 최대화하면 그 창은 화면에 말 그대로 꽉 차서 자신 주변의 테두리는 보이지 않게 된다. 이렇게 말이다.

사용자 삽입 이미지

그런데 머언 옛날, Windows 95에서는 일부 프로그램이 그렇게 일반적인 형태로 최대화가 되지 않았다. 테두리가 화면의 밖으로 밀려난 게 아니라 화면에 “포함된” 형태로 최대화되곤 했다. 그림판, 그리고 Internet Explorer 2가 대표적인 예였다. 너무 옛날 운영체제와 옛날 프로그램이어서 기억이나 실감을 못 하는 분도 계시겠지만 실제로 그랬다.

사용자 삽입 이미지

도대체 이 프로그램들은 왜 이렇게 동작했던 것일까? 그리고 어떻게 하면 이런 동작을 구현할 수 있을까?

Windows에는 창의 크기 내지 최대화 상태와 관련된 동작을 제어하는 WM_GETMINMAXINFO라는 요긴한 메시지가 있다. 이 메시지를 통해 이 창의 최소 및 최대 크기를 지정할 수 있으며, 창이 최대화되었을 때에 화면을 차지할 영역 범위도 지정할 수 있다.

크기 조절이 가능한 대화상자를 만들었는데 특정 한계 이하로는 가로나 세로 크기가 더 줄어들지 않게 하고 싶을 때, 그리고 가로로만 키울 수 있고 세로로는 더 키울 수 없게 하고 싶을 때 이 메시지를 처리하면 된다.

요즘은 좀 드물어졌지만 옛날에 Visual Basic 4라든가 델파이 같은 RAD 툴은 프로그램의 메인 윈도우 자체는 메뉴와 도구상자, 컴포넌트 팔레트 같은 것만 있었다. 폼 디자이너나 코딩 에디터는 메인 윈도우와 대등한 위상인 별도의 창으로 존재했다. 이 메인 윈도우는 가로로만 크기 조절이 되지 세로로는 되지 않았다.

사용자 삽입 이미지

그리고 과거의 ‘매체 재생기’ 역시 동영상은 별도의 창으로 출력됐지 메인 윈도우에는 메뉴와 위치 슬라이더, 재생 버튼 같은 것만 있었기 때문에 크기 조절은 가로로만 가능했다.
이런 프로그램들은 최대화가 가능하지 않거나, 최대화를 하더라도 가로로만 최대화가 됐다. 이런 동작을 위해서 WM_GETMINMAXINFO에다 크기 한계치를 지정해 주면 된다.

또한 이 메시지는 한계를 지정하는 것뿐만 아니라 한계를 초월하는 창 크기를 지정할 때도 쓰인다. ‘전체 화면’ 기능을 구현하는 게 대표적인 예이다.

위와 같은 활용을 하기 위해서는 메시지와 함께 전달된 MINMAXINFO 구조체에서 ptMaxSize, ptMinTrackSize, ptMaxTrackSize의 값을 고치면 된다. 다만 이들은 개념적으로 POINT(x, y)가 아니라 SIZE(cx, cy)에 해당하는 값인데 왜 구조체를 POINT로 지정했는지는 개인적으로 모르겠다. 시각과 시간을 헷갈린 것과 비슷한 격이다.

자, 그럼 크기 조절이 아니라 처음 주제인 최대화 이야기로 돌아온다. Windows 95의 그림판이나 IE2와 같은 동작을 하려면 ptMaxPosition이라는 멤버의 값을 고치면 된다. 창이 최대화됐을 때 이 창이 있을 곳을 지정하는 정보이므로 얘는 SIZE가 아닌 POINT의 정의에 부합하며, 디폴트로는 테두리를 가리기 위해서 화면을 살짝 벗어난 음수값이 설정되어 있다. 이것을 (0, 0)으로 설정하면 테두리가 화면에 보이게 된다.

MINMAXINFO *lpMMI = reinterpret_cast<MINMAXINFO *>(lParam);
lpMMI->ptMaxSize.x += lpMMI->ptMaxPosition.x*2;
lpMMI->ptMaxSize.y += lpMMI->ptMaxPosition.y*2;
lpMMI->ptMaxTrackSize=lpMMI->ptMaxSize;
lpMMI->ptMaxPosition.x=lpMMI->ptMaxPosition.y=0;

그런데 놀랍게도 여기에는 반전이 있다.
95 이후 오늘날의 Windows에서는 ptMaxPosition의 x, y 값을 모두 0으로 지정하면 값이 인식되지 않는다. 테두리가 밖으로 가려지는 디폴트 방식으로 창이 최대화된다. (0, 0)일 때만 의도적으로 보정을 한다는 것은 (0, 1), (1, 0) 이나 (-1, -1) 같은 비슷한 값을 줘 보면 금방 눈치챌 수 있다.

오로지 Windows 95만이 (0, 0)을 주면 창의 최상단 좌측 꼭지점이 말 그대로 (0, 0)으로 잡힌다. 이것은 본인이 프로그램을 직접 작성해서 돌려 보면서 확인한 사항이다.

이런 이유 때문인지, Windows 98(그 이후도 물론 포함)의 그림판은 95와 외형과 기능의 차이가 거의 없음에도 불구하고 최대화했을 때 95처럼 테두리가 보이는 형태로 커지지 않는다.
쉽게 말해 저건 오로지 95에서만 볼 수 있었던 추억의 특이한 동작인 것이다. NT4는 사정이 어떠했나 모르겠다.

사용자 삽입 이미지

WM_GETMINMAXINFO는 DefWindowProc의 영향을 받지 않는다는 점을 참고하도록 하자.
우리가 이 메시지를 받은 순간부터 lParam이 가리키는 MINMAXINFO 구조체에는 디폴트 값들이 이미 들어있으며, 우리는 필요한 경우 이 값들을 고치기만 하면 된다.
DefWindowProc가 구조체에다 디폴트 값을 넣어 준다거나 하는 건 없기 때문에 이 메시지는 그리로 전달을 하건 말건 동작이 달라지지 않는다.

훗날 Windows XP에서는 응답이 없이 죽어 버린 프로그램 창에 대해서 최소한의 반응성을 보장하기 위해 ghost 윈도우라는 걸 도입했다. 그런데 최대화된 상태에서 고스트가 됐다가 다시 살아난 윈도우는 최대화 이전 상태의 크기 정보가 사라져서 딱 저 95처럼 화면에 테두리가 보이는 최대 크기로 바뀌는 버그가 있었다. 물론 sp1 무렵에 곧바로 고쳐지긴 했다.

Posted by 사무엘

2015/12/28 19:37 2015/12/28 19:37
, ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1176

Windows의 공용 컨트롤 열전.
날짜/시간 컨트롤에 이어 오늘은 툴바(toolbar)라고 불리는 '도구상자/도구모음줄' 컨트롤에 대해서 좀 얘기를 해 보겠다.

메모장 같은 급의 초간단 프로그램이 아닌 이상, 표준 GUI 기반인 대다수의 프로그램들은 상단에 클릭 가능한 그림 버튼들이 가로로 쭉 늘어서 있다. 도구모음줄은 바로 그 그림 버튼들의 표시를 책임진다. 각 그림 내지 아이콘은 그 프로그램에서 자주 쓰이는 명령들을 나타내며, 이를 클릭하면 일일이 메뉴를 열어서 글자 형태로 된 명령문을 읽고 선택하는 것보다 명령을 더 빠르게 내릴 수 있다.
자주 쓰이는 명령에 대해서 키보드에 단축키가 있다면 마우스에는 도구모음줄이 있는 셈이다.

사용자 삽입 이미지

사실, 도구모음줄은 컴퓨터의 성능 관점에서는 그렇게 효율적인 도구가 아닐지도 모른다. 요즘이야 제약이 덜해지긴 했지만, 과거에 화면 해상도가 충분하지 못하던 시절에는 텍스트나 그림 같은 문서 컨텐츠를 한 줄 표시할 공간이 아까운데 도구모음줄을 늘어놓는 건 화면 낭비였다. 수십 종류에 달하는 명령 아이콘들도 다 메모리를 수십 KB 이상씩 잡아먹는다는 건 역시 두 말할 나위가 없고.

하지만 어떤 프로그램을 실행했더니 그냥 빈 화면에 cursor만 달랑 깜빡이는 것보다는, 아무래도 컬러풀한 아이콘들이 가득한 도구모음줄 하나라도 좀 놓여 있는 게 사용자에게 친근하다. 이것이 마우스 사용자에게는 뭔가 클릭할 거리를 제공함으로써 프로그램을 더 편리하게 사용하는 데 실질적인 도움이 된다.

1990년대 초, Windows 3.x 시절에도 MS Word나 Excel의 까마득한 옛날 버전을 보면 파일, 편집, 보기 같은 자주 쓰인 명령이 등재된 도구모음줄이 있었다. 도구모음줄이라는 개념은 그때 처음으로 등장한 것으로 보인다. 그 시절에는 도구모음줄은 자체 구현이었고 MFC조차도 그걸 자체 구현해 줬었는데, Windows 95로 넘어오면서 운영체제의 공용 컨트롤로 형태가 바뀌었다. MFC의 ToolBar 클래스도 4.0 32비트 버전부터는 운영체제가 제공하는 놈을 쓰는 걸로 형태가 바뀌었다.

그리고 1990년대 말, MS Office 97부터는 버튼의 모양이 마우스로 가리키고 있는 놈만 얇은 입체 테두리가 나타나는 flat 스타일로 바뀌었으며, 메뉴도 도구모음줄 버튼이 있는 명령은 왼쪽에 그 도구모음줄 아이콘이 같이 뜨게 되었다. 이건 당시로서는 나름 굉장히 참신한 디자인이었다.
Office야 운영체제의 표준 GUI를 안 쓰는 걸로 악명(?)이 높았으니, flat 스타일은 Windows 98 타이밍 때 공용 컨트롤에도 도입되었다.

사용자 삽입 이미지

운영체제 차원에서 공용 컨트롤이 등장했으니 Visual C++과 MFC가 독자적으로 하는 일은 이제 없어졌느냐 하면 여전히 그렇지 않다. MFC가 하는 일은 다음과 같다.
(1) 먼저, 도구모음줄의 컨테이너격인 Control bar를 제공한다. 도구모음줄의 폭을 자유롭게 지정하고 위치도 자유롭게 옮기고 심지어 부모 윈도우의 상하좌우 등 어디든 자유롭게 붙이거나 떼는 것은 운영체제가 알아서 해 주는 일이 아니다. MFC의 도움 없이 직접 구현하는 건 머리에 쥐가 나는 노가다이다. 심지어 드래그하기 편하게 왼쪽에 그려 주는 gripper 세로줄 공간도 MFC가 그려 준 결과물이다.

개발하는 프로그램이 덩치가 MS 오피스 내지 포토샵 같은 상업용 프로그램 급으로 커지면 도구모음줄이 2개 이상 존재하게 된다. 보기 메뉴의 '도구모음줄' 항목은 체크 하나만 달랑 있는 게 아니라 '표준, 서식, 그리기' 등 도구모음줄의 종류를 가리키는 부메뉴를 갖게 된다.
도구모음줄이 하나밖에 없을 때는 겨우 그것만 이리저리 옮기고 붙였다 떼는 기능이 좀 잉여스럽게 느껴지겠지만, 그게 여러 개가 존재하게 되면 이들의 위치를 관리하는 기능은 필수가 된다. MFC는 그런 필수 기능을 구현해 준다.

도구모음줄이 한두 개도 아니고 무려 10~20개씩 달려 있는 방대한 프로그램은 도구모음줄의 버튼들을 사용자 정의(customize)하는 기능도 전문적으로 갖추고 있다. 공용 컨트롤이 기본으로 제공하는 customize 기능도 있지만, 그건 전체 아이콘들 집합에서 자기 도구모음줄에다가 추가할 버튼을 선택하고 순서를 바꾸는 것 정도가 전부이다. 그 반면 MS Office의 경우, 2007 이전 버전은 메뉴의 텍스트, 도구모음줄의 버튼 그림까지 전부 사용자가 바꿀 수 있어서 가히 개발자의 근성을 짐작케 하는 엄청난 customize 기능을 제공했다. 나중에는 MS Office는 리본 UI 기반으로 바뀌고 Visual Studio도 WPF 기반으로 UI가 싹 바뀌면서 이런 기능은 더 찾아보기 어렵게 됐다.

이런 컨테이너 기능 말고도 또 Visual C++가 MFC와 연계하여 제공하는 기능은 바로 (2) IDE가 제공하는 리소스 편집기이다.
MFC로 응용 프로그램을 만든다면 우리는 리소스 편집기를 이용해서 리소스에다가 Toolbar를 추가하고 도구 버튼과 아이콘, 연계 명령들을 넣곤 한다. 그런데 이 Toolbar라는 리소스 카테고리는 Windows가 제공하는 표준 리소스 포맷이 아니다. 비트맵, 아이콘, 메뉴, 문자열과는 달리 표준 포맷이 아니며, MFC가 자체적으로 정의해서 사용하는 포맷이다. 여기에 지정된 데이터를 바탕으로 도구모음줄을 초기화하는 것은 응당 MFC의 몫이다. LoadIcon, LoadMenu, LoadString 따위와는 달리, LoadToolBar는 MFC 클래스의 멤버 함수로나 존재하지 Windows API에는 없다.

게다가 이 toolbar 리소스는 단독으로 있는 것도 아니다. 얘가 정의하는 것은 한 도구모음줄에 몇 개의 버튼이 있고 각 버튼이 의미하는 명령 ID는 무엇인지, 혹은 이것이 구분자인지 같은 정보가 전부이다. 그 도구모음줄이 참조하는 비트맵은 같은 ID의 Bitmap 리소스에 있다.
하지만 Visual C++ IDE는 도구모음줄과 연계하는 비트맵은 비록 비트맵이라 할지라도 표준 비트맵 리소스에서 따로 표시를 하지 않으며, 그 비트맵은 도구모음줄 리소스를 편집하는 곳에서 버튼 구조와 함께 편집하게 돼 있다. 프로그램이 내부적으로 이런 보정 처리까지 하고 있는 것이다.

내부적으로 MFC는 한 프로그램 윈도우에 대해서 한 리소스 ID를 부여하여 이걸로 문자열(프로그램 제목), 아이콘, 액셀러레이터 단축키, 표준 도구모음줄(비트맵 포함)까지 한데 관리를 하기까지 한다. 이것이 바로 CFrameWnd::LoadFrame 함수가 하는 일이다. 참 대단한 발상이다.

다음으로, 도구모음줄에 대해서 프로그래머가 반드시 짚고 넘어가야 할 기술적인 사항이 하나 있다.
도구모음줄의 버튼 그림은 작고 아담한 게 아이콘을 닮았지만, 실제로 이건 아이콘이나 마우스 포인터와는 달리 그냥 비트맵이다.
'아이콘'은 이미지 비트맵과 마스크 비트맵으로 구성되어서 태생적으로 래스터 오퍼레이션을 통해 투명 배경이나 반전 같은 걸 표현할 수 있다. 그러나 이미지 비트맵 한 장만 갖고는 그런 걸 표현할 수 없다. 그렇다면 도구모음줄 버튼은 어떤 방식으로 투명색을 표현하는 걸까?

이 방식은 생각보다 굉장히 원시적이고 단순무식하다. TB_ADDBITMAP 메시지라는 재래식 방식을 쓰는 경우, 도구모음줄은 이미지 비트맵 한 장만 달랑 받아들이고 투명 처리 같은 걸 해 주지 않는다. 비트맵을 생으로 있는 그대로 출력만 한다.
그렇기 때문에 MFC의 도구모음줄 클래스는 자신의 리소스 비트맵 이미지에 대해서 보정을 한다. 비트맵에 RGB(192,192,192)에 속하는 은색/회색 픽셀이 있으면 그걸 현재 운영체제의 COLOR_BTNFACE 시스템 컬러로 바꾸고, 은색 말고도 짙은 회색이나 검정, 하양은 그에 상응하는 시스템 컬러로 바꾼다. 그렇게 보정된 비트맵을 도구모음줄로 보내서 은색이 편의상 투명 배경색인 것처럼 보이게 한다. 보정을 안 하면 바로 이런 꼴 난다..;;

사용자 삽입 이미지

시스템 색상이 바뀌어서 WM_SYSCOLORCHANGE 메시지가 오면? 당연히 도구모음줄 비트맵도 매번 다시 만들어서 지정한다.
MFC를 뒤져 보면 이 일을 하는 AfxLoadSysColorBitmap라는 함수가 bartool.cpp에 있다. 아니, 이렇게 색깔 치환을 한 비트맵을 생성하는 함수가 Windows 95 시절부터 comctl32.dll에 CreateMappedBitmap이라고 있어 왔다. 도구모음줄 전용이기 때문에 user32도, gdi32도 아닌 comctl32에 있는 것이다.

그리고 이런 색깔 보정이 마냥 삽질인 것만은 아닌 것이..
시스템 색상이 고대비 검정 같은 걸로 바뀌었을 때는 검은색을 흰색으로 바꾸는 작업도 어차피 필요하기 때문이다. 도구모음줄의 비트맵은 반쯤은 이런 유동적인 환경 변화에도 대비가 돼 있어야 한다는 점이 흥미롭다.

도구모음줄용 비트맵으로 원시적인 생 비트맵뿐만 아니라 image list를 지정하는 TB_SETIMAGELIST 메시지는 Internet Explorer 4는 아니고 3과 함께 약간 나중에 추가됐다.
image list는 자체적으로 마스크 정보도 포함할 수 있으니 예전보다는 상황이 좀 낫다. 또한 ImageList_LoadImage 함수는 은색이 아닌 임의의 색깔을 투명색으로 지정할 수 있고, 아예 default로 (0,0) 화면 최상단 좌측 픽셀을 투명색으로 지정하게 할 수도 있다.
평소에는 흑백 이미지이다가 마우스 포인터가 가리키고 있는 버튼만 컬러 이미지로 출력하는 일명 hot image를 지정하는 것은 이렇게 image list 형태로만 지정 가능하다.

이렇게 특정 한 색깔을 투명색으로 끌어다 쓰는 건 아무래도 16색/256색 시절의 그래픽 패러다임을 벗어나지 못한 발상이다. MFC feature pack을 이용해서 트루컬러 아이콘이 들어간 도구모음줄을 만들면 당장은 보기 좋지만 시스템 색상을 고대비 검정으로 바꿔 보면 경계가 완전 뭉개지고 보기 좋지 않은 걸 알 수 있다. 어느 배경색에도 경계가 부드럽게 나오려면 근본적으로 알파채널이 쓰여야 할 텐데 그렇지 못하기 때문이다.

요컨대 오늘날 도구모음줄은 아이콘들이 그런 것처럼 트루컬러와 알파 채널에 대비가 돼 있어야 하고, 그러면서 고대비 모드도 지원해야 하며, 더 욕심을 부리자면 고해상도 DPI에서는 약간 큰 비트맵도 준비돼 있어야 한다. MS Office의 리본 UI는 고대비 모드에서는 그냥 16컬러로 간략화한 비트맵을 대신 출력하는 것 같다.
그리고 <날개셋> 편집기는 겨우 그 덩치의 프로그램에서 저런 것까지 일일이 대비하는 건 너무 낭비라 여겨지기 때문에 도구모음줄의 아이콘은 그냥 16*16 크기의 16색에 머물러 있다.. ^^

그나저나,

  • 공용 컨트롤을 쓰지 않고 자체 구현된 도구모음줄은 대화상자를 띄우는 명령을 클릭한 경우, 대화상자가 떠 있는 동안에 버튼이 여전히 눌러져 있기도 했던 것 같다. MS Office 2007 이전의 옛 버전과 Visual C++ 6 등이 그 예다.
  • 전무후무하게 도구모음줄 배경에 solid color가 아니라 무늬가 있었던 IE 3의 도구모음줄은 어떻게 구현되었는지가 새삼 궁금해진다. 얘는 표준 공용 컨트롤 기반인데, 아마 얘 때문에 image list 기능이 도입되었지 싶다. 이쯤 되면 버튼 비트맵에 자체적으로 마스크 정보가 있어야만 도구모음줄 배경과도 합성이 가능하지 않았겠는가.

사용자 삽입 이미지

사용자 삽입 이미지

Posted by 사무엘

2015/11/16 08:36 2015/11/16 08:36
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1160

컴퓨터에는 자체적으로 달력과 시계 기능이 포함되어 있으며, 운영체제에는 그걸 설정하는 기능이 있다.
대략 197, 80년대 엄청 미개하던 시절에는 개인용 컴퓨터의 시계에 배터리가 없었다. 그래서 컴퓨터가 꺼지면 현재의 날짜· 시각 정보가 사라졌으며, 매번 부팅 때마다 사용자가 그걸 다시 지정해 줘야 했다. 옛날에 도스가 autoexec.bat가 없으면 디폴트로 하던 일이 바로 date, time 명령을 실행하여 날짜· 시각을 입력받는 것일 정도였다.

그 반면 지금은 컴퓨터가 자체적으로 날짜· 시각을 관리하고 있을 뿐만 아니라 Windows의 경우 XP부터는 인터넷 서버로부터 시각을 자동으로 동기화하는 기능이 추가됐다. 그러니 긴 시간이 흐른 뒤에 시계가 오차가 쌓여서 부정확해질 일이 없어졌다. 기지국으로부터 받은 시각을 표시하는 휴대전화처럼 말이다. 다만, 휴대전화는 자체 시계는 없는지, 기지국과의 통신이 끊어지면 컴퓨터와는 달리 시각을 아예 표시를 못 하는 것 같다.

XP를 넘어 Windows Vista부터는 날짜· 시각을 사용자가 변경하는 건 관리자 권한이 필요한 일로 바뀌었다. 보안과 권한 개념이 발달해 있는 유닉스 계열 컴퓨터에서는 진작부터 그랬다.
거기에다 자동 동기화 기능까지 있으니 일반인이 컴퓨터에서 날짜· 시각을 손수 건드릴 일은 거의 없어졌으며, 소프트웨어의 사용 기한 같은 걸 속이려고 컴퓨터의 날짜를 조작하는 일도 예전보다는 쉽지 않게 됐다.

Vista 이전의 과거엔 Windows에서 작업 표시줄의 시계를 더블 클릭하면 곧장 날짜· 시각을 입력받는 제어판 대화상자가 떴다. 그러나 지금은 간단하게 달력과 아날로그 시계만 뜬 뒤, 날짜· 시각을 바꾸는 대화상자는 추가로 버튼을 클릭해야만 나오게 바뀌었다.

사용자 삽입 이미지

이 창은 정식 대화상자가 아니며, 마우스로 이동을 시킬 수가 없고 바깥을 툭 건드리기만 하면 바로 사라진다. 그렇기 때문에 예전에 비해 UI가 좀 불편해진 면모도 있다. 하지만 read-only 형태의 달력· 시계 UI만 먼저 제공하고 수동 변경 기능을 뒤에 숨겨 놓는 것 자체는 디자인상 필요했으며 바람직한 조치이다. 또한 변경 기능의 접근성을 떨어뜨린 대신, 시차를 고려해 최대 2개의 custom 시계를 추가로 표시할 수 있게 한 건 예전 버전에는 없었던 편리한 기능이다.

아무튼, 어떤 형태로든 사용자로부터 날짜와 시각을 입력받는 UI가 필요하니, 태초에 Windows 95의 날짜· 시각 제어판 애플릿은 이런 형태였다.

사용자 삽입 이미지

여기서 중요한 사실은, Windows 95 시절부터 제어판의 날짜· 시각 대화상자에는 커스텀 컨트롤이 은근히 많았다는 점이다.
이 대화상자는 IE 4 내지 공용 컨트롤 4.7이 등장하기 전부터 존재해 왔다. 그렇기 때문에 저 달력은 SysMonthCal32 공용 컨트롤이 아니다. 공용 컨트롤은 컨트롤 자체에 달력뿐만이 아니라 년과 월을 선택하는 기능이 존재하는 반면, 저 커스텀 컨트롤은 년 월 선택은 위의 콤보 박스에서 따로 하고 있다.
아날로그 시계는 두 말할 나위도 없고, 그 아래의 시간 선택 컨트롤도.. 에디트 컨트롤 몇 개를 내부적으로 갖다 붙인 커스텀 컨트롤이지 SysDateTimePick32가 아니다.

Windows Vista부터는 제어판의 날짜· 시각 대화상자가 구닥다리 자체 구현 달력이 아니라 공용 컨트롤을 사용하는 것으로 구조가 바뀌었다. 하지만 시각을 입력받는 에디트 컨트롤은 공용 컨트롤이 아니라 여전히 재래식 방식이다.
또한, 일부 Windows 버전에는 시간대를 선택하는 UI에 세계 지도 그림이 곁들어진 것이 있는데, 이것도 커스텀 컨트롤로 구현되어 있다.

사용자 삽입 이미지

세계 지도는 내력이 좀 복잡하다. Windows 95에 있었다가 98/ME에서는 잠시 빠졌다. NT 계열인 2000/XP에서는 줄곧 있었지만 Vista부터는 도로 제외된 채 오늘날에 이르고 있다.

Windows 95가 처음 개발되던 당시에는 콤보 상자뿐만 아니라 사용자가 지도의 지역을 클릭하면 그 지역의 시간대가 자동으로 선택되는 기능까지 있었다. UN에서 발행한 지도 자료를 토대로 국가 영역을 다 입력해 넣었으나..
영토 분쟁을 겪고 있는 모 국가들로부터 국경 인식을 왜 그 따위로 하느냐는 항의 메일이 빗발쳤다고 한다. 그 나라에서 제시하는 경계를 다른 쪽 나라에서는 당연히 인정하지 않았고.. 이 기능이 그대로 들어갈 경우 국가적으로 Windows 95를 보이콧 하겠다고 서로 난리도 아니었다. 우리나라의 확장완성형 논란은 이에 비하면 약과로 보일 정도로 자세가 강경했다.

역시 세상에서 무서운 건 정치이다. 고민 끝에 마소에서는 결국 이 기능 자체를 빼 버리고 오로지 콤보 박스로만 시간대를 선택할 수 있게 했다고 한다. 이 일화는 오래 전에 the Old New Thing 블로그에서 운영자가 증언한 내용이다. 세계구급 소프트웨어를 개발하는 데 관여한 사람만이 경험할 수 있는 일이니 참 흥미롭다.

* 여담: 과거 Windows의 동작 방식

믿어지지 않는 사실인데..
과거 Windows 95/98은 제어판의 '날짜/시간'에 들어가서(저 설정 대화상자를 연 뒤) 달력 컨트롤에서 임의의 날짜를 찍거나 연도· 월을 변경하면..
그것만으로도 시스템의 날짜가 즉시 그 날짜로 바뀌었다고 한다! 본인이 가상 머신으로 테스트 해 보니 진짜로 그렇다.

사용자들은 시스템의 날짜를 변경할 의도가 없이, 지금으로부터 가까운 과거나 미래의 달력을 잠시 조회만 할 목적으로 그 대화상자를 종종 꺼내곤 했다. 그도 그럴 것이 작업 표시줄의 우측 하단에 있는 시계만 클릭하면 되기 때문이다. 그런데 달력 컨트롤의 날짜를 클릭하는 것만으로 시스템의 날짜까지 덩달아 바뀌었다니..

물론 대화상자를 ESC/'취소'를 눌러서 닫으면 변경 전의 원래 날짜로 원상복구 되긴 했다. 그러나 일시적으로 날짜가 과거나 미래로 바뀐 동안에는 시스템 날짜에 의존해서 동작하는(업데이트 체크, 알람 등..) 프로그램이 심각한 오동작을 일으킬 위험이 다분히 있었다.

내 경험상, 애플 macOS 진영은 저장이나 인쇄 같은 동작이 수반되는 기능 말고, 단순 설정 기능들은 '취소 cancel/undo'가 딱히 갖춰져 있지 않고 모든 설정들이 변경 즉시 바로 적용되는 편이다.
그러나 마소는 소프트웨어의 UI 디자인 원칙 차원에서 원상복귀/"빠꾸"에 무한 관대할 것을 요구한다. 사용자가 OK라고 최종 승인을 하지 않는 한 말이다.

Windows에서 설정을 변경하는 것만으로 변화를 즉시 경험할 수 있는(preview) 기능은 스피커의 볼륨, 키보드와 마우스의 반응 속도처럼 사용자와 직접 소통하는 컴퓨터의 입출력 장치와 관련이 있는 옵션 정도밖에 못 봤다. 그리고 이마저도 당연히 '취소' 가능하다. 하지만 시스템 날짜도 그렇게 직접 실시간으로 바뀌었다는 건.. 음~ UI를 왜 그렇게 만들었는지 의문이 들게 된다.

그리고 더 이상한 것은 '날짜'(date)만 그렇게 즉시 반응했다는 것이다. '시각'(time)은 사용자가 새로운 시· 분· 초 값을 입력해도 즉시 바뀌지 않았다. 프로그램을 왜 그렇게 만들었는지도 개인적으로 이해되지 않는다.

이런 동작 방식은 Windows 2000/ME에 와서야 개선되어서 달력을 막 건드리더라도 '확인'이나 '적용'을 누르기 전에는 날짜가 절대로 바뀌지 않게 되었다. 날짜· 시각 변경은 DPI 변경과 더불어 20여 년 사이에 Windows에서 위상이 드라마틱하게 달라진 기능에 속한다.

* 여담 하나 더: 컴퓨터 내부에서 날짜와 시각을 표현하는 방식

일상생활에서는 날짜와 시각이 모두 쓰이거나 둘 중 하나만 사용될 때가 있다.

  • 날짜와 시각 모두: 특정 날짜와 시각에 발생하는 알람이나 일회성 약속을 나타낼 때
  • 날짜만: 구체적인 시각이 중요하지 않은 역사 사건을 나타낼 때 (1994년 9월 1일, 분당선 개통)
  • 시각만: 부산 오전 8시 정각, 대전 10시 38분, 서울 12시 10분처럼 날짜가 중요하지 않은 정규 스케줄을 나타낼 때

하지만 컴퓨터 프로그램들은 이들을 모두 동일한 방식으로 저장하고 표현만 필요에 따라 다르게 한다. 무슨 말이냐 하면, 1이 하루를 나타내는 날짜 전용 수 체계와 1이 1초를 나타내는 시간 전용 수 체계를 따로 운용하는 게 아니라, 둘 다 동일한 수 체계를 사용한다는 뜻이다. 기준 연도로부터 얼마의 시간이 경과했는지가 기준이며, 수 체계는 과거에는 32비트 정수가 주로 쓰였지만 요즘은 응당 범위가 훨씬 더 넓은 64비트가 대세이다.

Windows에는 FILETIME이라는 유명한 자료형이 있다. 이것은 1601년 1월 1일 자정으로부터 경과한 시간을 나타내며, 1은 100나노초, 즉 0.1 마이크로초 단위이다. 왜 하필 1601년을 선택했는지는 본인은 모르겠다.
그 반면 Java 언어는 내부적으로 시각을 유닉스 원년인 1970년 1월 1일 자정으로부터 경과한 시간으로 나타내며, 1은 마이크로초를 나타낸다. C 언어에 있는 time() 함수는 기준이 1970년 1월 1일이긴 한데 그냥 초 단위라는 차이가 있다. 이렇듯 시간을 나타내는 방식도 언어와 플랫폼 사이에 미세하게 차이가 있다.

유닉스 원년은 지질학 원년인 1950년과는 딱 20년이라는 차이가 존재한다는 게 흥미롭다. 쉽게 말해, 지질학에서 뭐 "지금으로부터 6500만 년 전에 공룡 멸망", "몇억 몇천만 년 전쯤에 중생대" 이러는 것은 1950년보다 그만치 전이라는 뜻이다. 지질학에서는 방사성 원소 연대 측정법이 본격적으로 쓰인 때를 원년으로 삼았고, 컴퓨터 업계에서는 유닉스 운영체제와 C 언어가 막 개발된 그 시기를 원년으로 삼은 듯하다.

한편, 이런 날짜· 시각과는 달리, 전화번호는 숫자처럼 보이지만 그 형태 그대로 문자열로 저장된다는 점에서 날짜· 시각과는 성격이 다르다. 얘는 아무래도 대소 비교를 하거나 차이를 계산하는 대상은 아니니 말이다. 이런 건 데이터베이스나 엑셀 같은 프로그램으로 주소록 같은 것만 만들어 봐도 바로 공감할 기본적인 내용이다.

Posted by 사무엘

2015/11/13 19:34 2015/11/13 19:34
,
Response
No Trackback , 5 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1159

사람이 일생상활에서 취급하는 정보 중에는 일반적인 숫자나 문자열만치 자주 쓰이지는 않지만 날짜· 시각도 있다. 얘들은 컴퓨터 내부에서는 커다란 정수 하나로만 달랑 표현되겠지만 사람에게 보일 때는 일정한 형식을 갖춘 부분숫자의 나열로 표현된다. 또한, 그걸 표현하는 형식 내지 부분숫자들(년월일, 시분초)을 나열하는 순서는 언어나 문화에 따라 대동소이한 차이가 있다.

날짜 문자열은 마치 프로그래밍 언어 문법처럼 형식이 있으며, 문법과 의미 차원에서 정오(맞고 틀림)가 존재한다. 가령, 2015*5#20은 구분자 문자열이 잘못 지정되어서 문법에 어긋나며, 2015/5/100은 문법엔 맞지만 5월 100일이라는 게 존재하지 않기 때문에 의미가 잘못됐다.

날짜· 시각은 그냥 이런 문자열로 입력받게 할 수도 있다. 하지만 필요하다면 달력 같은 걸 내려서 사용자가 전체 날짜를 보면서 내가 원하는 날짜를 마우스로 콕 찍을 수도 있게 하는 게 좋을 것이다. 이 날짜가 무슨 요일인지, 그리고 지금으로부터 과거나 미래로 얼마나 떨어져 있는지를 달력을 통해 시각적으로 확인 가능하다면 날짜를 정확하게 선택하는 데 큰 도움이 되니까 말이다.

그러니 날짜· 시각을 입력받는 GUI 컨트롤은 에디트와 리스트를 겸하여 뭔가 콤보 박스 같은 형태로 만드는 게 좋다.
흔히 date picker라고 불리는데, 이런 컨트롤은 당장 웹사이트에서 더 많이 보는 것 같다. 무슨 예약· 예매 기능이 있는 사이트들이 대표적인 예이다.

대충 만든 사이트라면 년월일을 그냥 각각 콤보 박스에다가 집어넣어 놓곤 한다. 뭐, 생년월일이나 신용카드 유효 기간처럼 수 년 이후의 미래나 수십 년 이전의 과거를 입력받는 거라면 달력을 일일이 스크롤하는 게 번거로우며 단도직입적으로 날짜를 고르는 방식이 나쁘지 않다. 게다가 이런 날짜는 내 의지와는 상관없이 답정너 형태로 존재하는 날짜들이다.
그 반면, 현재로부터 비교적 가까운 날짜에 있을 스케줄을 '내 자유의지'에 따라 잡는 상황에서는 달력이 있는 게 좋을 것이다.

자바스크립트로 만들어진 웹 컴포넌트 말고, Windows의 로컬 프로그램에서 사용 가능한 공용 컨트롤은 95때부터 바로 있었던 게 아니다. 나중에 Internet Explorer 4와 함께 도입되었다(공용 컨트롤 버전 4.7). 바로, 날짜/시각 선택 컨트롤과 달력 컨트롤이며, 클래스 이름은 각각 SysDateTimePick32와 SysMonthCal32이다.

사용자 삽입 이미지

날짜/시각 선택 컨트롤은 스타일을 뭘로 주느냐에 따라서 날짜와 시각 중 하나를 입력받게 할 수 있다. 시각 모드일 때는 컨트롤의 오른쪽에 up/down 버튼이 붙지만, 날짜 모드일 때는 오른쪽에 콤보 버튼이 붙는다. 각 숫자들은 숫자 키로 직접 입력하거나 상하 화살표로 증가· 감소를 시킬 수 있다.

날짜 모드일 때 콤보 버튼을 누르거나 F4 키를 누르면 달력이 나타나서 달력의 날짜를 클릭하는 방식으로 날짜를 지정할 수도 있다. 그리고 달력 컨트롤은 날짜 선택 컨트롤이 일시적으로 표시해 주는 그 달력을 상시 표시해 놓는다.
하긴, 본인 역시 <날개셋> 한글 입력기에서 낱자 선택 콤보 박스를 이런 식으로 만들곤 했다. F4를 눌렀을 때 나타나는 셀 리스트는 drop list일 뿐만 아니라 그걸 단독으로 list box 형태로 쓸 수도 있게 말이다.

그런데 달력 컨트롤은 공용 컨트롤 6.0 것을 쓸 때와 그렇지 않은 재래식 버전을 쓸 때 동작이 살짝 다르다. 재래식은 년과 월을 클릭하면 그냥 년 또는 월을 선택하는 단순한 콤보 박스가 나타나는 반면, 새것은 그걸 클릭하면 달력 자체가 일(day) 대신 월, 1년, 10년 단위로 스케일이 커진다. 마치 지도를 확대/축소하듯이 말이다. 이 새로운 동작 방식은 Windows Vista에서 처음으로 추가되었다.

사용자 삽입 이미지
사용자 삽입 이미지

실행 파일의 호환성 옵션으로 들어가서 시각 테마를 끄더라도 공용 컨트롤 6.0의 달력 컨트롤은 그렇게 동작한다. 즉, 이것은 외형의 차이가 아닌 코드 로직의 차이이다.

그러니 날짜/시각 선택 컨트롤과 달력 컨트롤은 ComboBox와 ComboLBox의 관계와 같으며, 리스트 컨트롤과 헤더 컨트롤의 관계와도 얼추 비슷하다.
얘는 공용 컨트롤 중에서도 조금 나중에 도입된 놈이기 때문에 InitCommonControls만 호출해 준다고 해서 초기화가 되지 않는다. 반드시 Ex 버전을 써서 이렇게 초기화해야 한다.

INITCOMMONCONTROLSEX ics;
ics.dwSize=sizeof(ics); ics.dwICC=ICC_DATE_CLASSES;
::InitCommonControlsEx(&ics);

물론, 공용 컨트롤 6.0 매니페스트가 지정된 프로그램들은 저 함수를 호출하지 않아도 모든 공용 컨트롤들을 곧장 사용할 수 있다.
그럼 다음 시간에는 운영체제의 날짜· 시각 설정 UI를 이 컨트롤과 연계해서 살펴보도록 하겠다.

* 여담: 언어적인 이슈

number가 '수'도 되고 '번호'도 되는 것만큼이나, time은 시각도 되고 시간도 돼서 개념이 언어적으로 무척 혼란스럽다. 두 시각의 차가 시간이니 날짜에 대응하는 current time은 '현재 시각'이 돼야 맞다.
'째'(얼추 nth)와 '번째'(nth time)도 이와 비슷한 맥락에서 쓰임이 무척 혼동스럽다. 한국어에서 '째'는 서열이나 순위 개념이고 '번째'는 횟수 정도에 대응한다.
x시 y분에 A역을 발차해서 z시 w분에 B역을 도착한다는 식으로 열차의 운행 스케줄을 적어 놓은 도표도 시간표가 아니라 시각표라고 한다.

Posted by 사무엘

2015/11/11 08:33 2015/11/11 08:33
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1158

과거 Windows XP 시절부터 눈치 챈 분이 계시려나 모르겠다.
XP에서부터 아시다시피 대화상자 컨트롤들에 테마가 적용되어서 얘들의 비주얼이 알록달록 한결 예뻐졌다. 단, 모든 프로그램에 테마가 자동 적용되는 건 아니며 공용 컨트롤 6.0을 사용한다는 표식이 존재하는 프로그램에 대해서만 테마가 적용된다. 이것은 Windows 프로그래머라면 추가적으로 알고 있을 것이다.

그런데, 콤보 박스의 경우 공용 컨트롤 6.0 것을 사용하면 동작이 약간 달라졌다. ▼ 버튼을 눌러서 drop list를 꺼내면 drop list의 크기가 예전보다 훨씬 더 길쭉해져 있다. 프로그램이 리소스 차원에서 지정해 준 것보다 훨씬 더 길어졌다.
이것은 테마의 적용 여부와는 무관하게 공용 컨트롤 6을 적용하는 것만으로도 나타나는 변화였다.

다음은 <날개셋> 편집기의 문자표 대화상자를 Windows 2000에서 구동한 것과 XP(및 그 이후 모두 포함)에서 구동한 것의 차이를 나타낸 스크린샷이다. 테마나 글꼴과는 무관하다는 것을 보이기 위해 XP에서도 고전 테마를 사용했고, 글꼴 역시 Tahoma로 통일시켰다.
문자표뿐만이 아니라 “보기-편집화면 설정”에서 이미 30개가 넘는 글꼴이 존재하는 한글 글꼴 콤보 박스를 내려 봐도 같은 현상을 볼 수 있다.

사용자 삽입 이미지사용자 삽입 이미지

그리고 이것은 비단 내 프로그램에서만 발생하는 차이가 아니다. 다음은 Windows 2000과 XP의 워드패드에서 글꼴 콤보를 내린 모습이다. 워드패드는 Windows 7에서 크게 바뀌었지 2000과 XP는 외형의 차이가 거의 없으니 말이다.

사용자 삽입 이미지사용자 삽입 이미지

콤보 박스에서 drop list의 크기는 원래 콤보 박스가 생성될 때 CREATESTRUCT에 지정되어 있던 크기로 결정되어 왔다. 그 크기는 따로 저장된 뒤, 지금 당장 콤보 박스는 아이템을 1줄만 표시 가능한 정도로 세로 크기가 자동으로 조정되었다.

하지만 공용 컨트롤 6에 있는 콤보 박스는 알 수 없는 이유로 인해 이런 전통적인 동작이 바뀌었다. 원래 지정되었던 세로 크기는 무시되고 drop list는 아이템을 최대 30개까지 표시하는 크기로 지정된다. 그래서 XP에서부터 공용 컨트롤 6을 사용하는 프로그램은 콤보 박스의 drop list가 굉장히 커진 것이다.
그 대신 이 개수를 얻어 오고 바꾸는 메시지가 새로 추가되었다. CB_(GET/SET)MINVISIBLE이 그것이다. 이 메시지는 공용 컨트롤 6 기반의 콤보 박스에서만 사용 가능하다. 개수의 기본 초기값이 30인데, 이것은 이례적으로 상당히 큰 값이다.

단, 공용 컨트롤 6이라 해도 CBS_NOINTEGRALHEIGHT 스타일이 지정된 콤보 박스에는 이런 새로운 정책이 전혀 적용되지 않고 이전의 콤보 박스와 동일한 크기로 drop list가 튀어나온다. 이 스타일은 크기 보정을 전혀 하지 않고 마지막 줄에 아이템의 일부가 잘려 나오는 것도 감수한다는 옵션인데.. 개인적으로 이런 옵션이 왜 있는지 모르겠다. 이걸 사용하는 콤보 박스는 난 지금까지 전혀 못 봤다. 마치 extended UI만큼이나 실용적인 의미가 거의 없음.

여러모로 Windows GUI에서 콤보 박스의 동작과 메시지 API가 왜 이렇게 설계되었는지 본인으로서는 이해할 수 없다. (1) 공용 컨트롤 6이 됐다고 해서 저런 동작이 함부로 바뀌어서는 안 되며, (2) 콤보 박스의 drop list 크기는 자신의 원래 크기를 기반으로 동작해야 한다. 그리고 (3) drop list의 크기를 알아 오거나 바꾸는 메시지는 진작부터 있어야 했다.
XP에서부터 콤보 박스의 drop list 크기가 갑자기 커졌다는 건 오래 전부터 알고 있었지만 왜 그런지는 10년 가까이 제대로 모르고 있었다. 이유를 알게 됐다고 해서 그 이유를 수긍하는 것도 아니긴 하지만.

이것 말고도, Windows XP 첫 버전의 공용 컨트롤 6에서는 owner draw 방식이어서 문자열이 필요하지 않은 콤보 박스에다가도 아이템을 추가할 때 그냥 NULL을 주면 프로그램이 뻗는 어이없는 버그가 있었다. 원래는 그렇게 안 해도 돼야 되는데. 그래서 꼭 0-length 문자열 ""이라도 집어넣어 줘야 했다. 테마 내지 공용 컨트롤 6 기반이 아닌 기존 컨트롤에서는 문제가 없었으니 명백한 운영체제의 버그였다.

<날개셋> 한글 입력기가 최초로 XP 테마를 도입한 버전이 2.3이었는데, 바로 이 버그 때문에 엄청 곤혹을 치렀었다. 이 버그는 XP sp1에서 바로 고쳐지긴 했지만, 비주얼 말고 도대체 뭘 고쳤길래 같은 콤보 박스에 이런 동작이 차이가 났는지 본인으로서는 알 길이 없다. 요컨대 공용 컨트롤 6의 컨트롤들은 설령 다른 테마 없이 고전 테마로 표시된다 하더라도 근간이 기존 컨트롤과는 근본 출신이 다르다.

Posted by 사무엘

2015/11/02 08:33 2015/11/02 08:33
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1155

Windows XP에서부터 내가 생성하는 exe 바이너리의 내부에 xml 문서 리소스 형태의 메타데이터를 집어넣는 게 필수 관행이 됐다.
가장 대표적인 용도는 (1) 내가 사용하는 시스템 DLL의 버전을 지정해서 로딩 방식을 시스템 디렉터리 말고 딴 곳으로 강제로 바꾸는 것이다.

이 방식으로 (1-1) 공용 컨트롤을 6.0 버전을 사용한다고 명시해야 각종 컨트롤에 XP의 새끈한 테마가 적용되어 나왔다. 테마 기능을 추가하는 과정에서 comctl32.dll이 XP 이전과 XP 이후 것은 하위 호환성이 없이 서로 단절됐기 때문이라 한다.
내 프로그램의 비주얼이 시대에 뒤떨어진 구닥다리처럼 보이지 않게 하려면 이거 지정은 선택의 여지가 없는 필수이다.

XP 이후로 지금까지 comctl32.dll이 7.0으로 올라간다든가 해서 또 breaking changing을 겪지는 않았다.
Windows 8부터는 명목상 고전 테마와 표준 테마의 구분이 없어지긴 했지만, 그래도 공용 컨트롤 6.0 지정을 안 해도 되는 건 아니다. 구닥다리 비주얼 자체가 호환성 때문에 완전히 없어진 건 아니기 때문이다.

뭐 comctl32가 가장 대표적이긴 하지만 이 외에 (1-2) GDI+ (gdiplus.dll)와 비주얼 C++의 CRT DLL, 그리고 MFC DLL이 지난 200x년도에 이 방식을 사용해 로딩된 적이 있다.
Windows XP 시절엔 WinSxS 디렉터리에 파일이 몇 개 없었지만 Vista 이후부터는 도대체 뭐가 들어갔는지 여기에 디렉터리 수가 무려 수천~만수천 개에 달할 정도로 많아졌다. 199x년대에 한번 DLL hell을 겪은 뒤, 너무 난장판이 돼 가는 시스템 디렉터리를 이런 식으로 정리를 하려 했던 모양이다. 같은 DLL이라도 버전별로 쫙 따로 분류를...

하지만 DLL을 배포하기가 너무 불편하다는 원성이 빗발치면서 VC++ 201x부터는 CRT와 MFC DLL 로딩 방식이 다시 재래식 시스템 디렉터리 기반으로 복귀했다.
또한 GDI+도 오늘날은 기존 GDI만큼이나 재래식 유물로 전락했고... 요즘은 딱히 이 WinSxS 기반 DLL 로딩이 활발히 쓰이고 있는지 모르겠다.

DLL 버전 설명이 좀 길어졌는데, 이것 말고도 xml에 들어가는 정보로는
(2) 내 프로그램이 고해상도+가변 DPI를 인식한다는 플래그
(3) 반드시 관리자 권한이 필요하다는 식의 권한 플래그

가 Vista에서 추가되었다. 시스템 차원에서의 고해상도 DPI 설정이야 더 말할 것도 없고 Windows 8.1부터는(8도 아님) 실행 중에 on-the-fly로 모니터 단위 DPI 설정이 변경되는 것에도 추가로 대비가 돼 있어야 한다. 그런 표식이 없으면 그냥 그래픽 카드의 힘으로 프로그램 화면이 그대로 기계적으로 확대 축소된다.

먼 옛날, Windows NT 3.x 내지 95 시절엔 화면 해상도를 재부팅 없이 on-the-fly로 바꾸는 것만 해도 굉장한 혁신이었는데 참 격세지감이다. 시스템 DPI도 예전에는 재부팅이 필수였지만 그래도 Windows Vista쯤부터는 그냥 재로그인만 하면 되게 규제가 완화됐다.

그리고 Vista부터인지 7부터인지는 모르겠지만 (4) 이 프로그램이 인식하는 운영체제 목록도 xml에다 명시하게 되었다. PE 실행 파일 포맷에 명시된 OS 버전의 역할을 어느 정도 대신하게 됐다.
Windows 8.1부터는 GetVersionEx가 알 수 없는 이유로 인해 봉인되어서, 프로그램이 인식하는 걸로 등록되지 않은 최신 운영체제의 버전은 Windows 8 이상으로는 알려주지 않게 바뀌었다. 도대체 이런 만행을 왜 무슨 이유 때문에 저질렀는지 난 모르겠지만.. 도대체 버전을 숨겨서 뭐 하게?

그리고 Windows 7도.. 자기 버전이 명시되지 않은 EXE 프로그램이 이름이 setup이라거나 하면, 실행 후에 "이 프로그램은 혹시 설치 프로그램인가요? 뭔가가 제대로 설치/제거되지 않았을 수 있습니다" 이런 꼬장을 부린다. 무슨 호환성과 관련된 조치인 듯하다.

결국 처음에는 공용 컨트롤 때문에 사용하기 시작한 xml인데 이제는 무슨 실행 프로그램을 제대로 만들려면 메타데이터 xml을 집어넣는 건 거의 필수가 되었다. 이 프로그램이 미래에 운영체제에서 새로 도입되는 시스템이나 기능을 받아들일 준비가 됐다는 것을 나타내는 증서 역할을 하다 보니, 들어가는 내용이 점점 증가하고 있다. 저 네 가지 아이템 말고 내가 또 빠뜨린 게 있는지 모르겠다.
그리고, PE 헤더에 운영체제 버전과 서브시스템 버전은 왜 따로 존재하고 둘의 차이가 무엇인지도 본인은 궁금하다.

* 추가 설명

사실, 메모장이나 워드패드처럼 Windows에 포함되어 있는 기본 프로그램들은 내부의 매니페스트 XML을 열어 보면 공용 컨트롤, 고해상도 DPI, 실행 권한 같은 설정은 있지만 굳이 운영체제 정보가 들어있지는 않다. 특정 버전의 운영체제에 포함되어 있는 프로그램들이 또 그 운영체제 GUID가 내장되어 있다거나 하지는 않다.

이때는 PE 헤더 차원에서 명시된 운영체제 버전이 활용된다. 이 버전이 Windows 8.1에 달할 정도로 높은 값이 들어있으면 GetVersionEx 함수도 Windows 8.1의 버전도 정확하게 되돌려 준다. 다만 이 경우, 그 실행 파일은 Windows 8.1 미만의 운영체제에서는 전혀 실행되지 않는다는 것을 감안해야 한다. 심지어 7에서도 실행이 거부된다.

Posted by 사무엘

2015/09/03 08:30 2015/09/03 08:30
, , ,
Response
No Trackback , 3 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1134

Windows 10 사용기 외

1. Windows 10

그 이름도 유명한 Windows 10을 본인도 드디어 입수했다. Mac OS와 Windows 모두 10을 최종 메이저 버전으로 설정했다는 게 무척 흥미롭다.

개인 컴은 물론이고 회사 컴도 평소에 활발하게 쓰던 물건은 "Windows 10 다운로드 준비가 끝났는데 설치할까요?"가 진작부터 뜬 반면,
평소에 잘 안 쓰고 "여기에나 한번 10을 깔아 볼까?"라고 정작 비워 놨던 컴은 한참을 기다려도 Windows 10 설치 말이 없었다. "준비되면 알려 주세요"를 몇 번이나 클릭했는데도 감감 무소식. 이것도 평소에 로그인 횟수나 네트워크 트래픽을 감안해서 업데이트 순서를 짠 건지는 모르겠다.

참다못해 Windows 10 설치 프로그램을 직접 다운로드 해서 실행한 뒤에야 운영체제를 7에서 10으로 바꿀 수 있었다. 다운로드와 설치는 물론 금방 끝난 건 아니지만, 그렇다고 Windows 8.1을 설치하던 시절보다 터무니없이 오래 걸리지는 않았다.

설치 직후에는 바탕 화면의 personalize가 되지 않았다. 인증이 필요하대서.. "으잉? Windows 10은 완전 무료 아니었나? 자동 업데이트를 안 하고 수동 설치를 해서 그렇나?" 어리둥절했는데 재부팅을 한번 하고 나니 인증은 저절로 패스 됐고, 까맣던 배경 그림은 10을 설치하기 전의 모습으로 되돌아왔다.

비주얼에 대한 소감은..
프로그램의 제목이 가운데가 아니라 왼쪽 정렬로 돌아왔으며, 글씨 크기도 약간 더 크다가 다시 메뉴 글씨 크기와 동일하게 돌아왔다. 또한 화면을 부분만 차지하는 시작 메뉴가 되돌아왔으며 프로그램 창 주변에 그림자 그러데이션이 생긴 것은 일면 과거의 Windows Vista/7 시절로의 회귀를 떠올리게 한다.
창의 최소/최대화와 닫기 버튼이 굉장히 커지긴 했는데 정작 _ □ X 모양은 너무 대충 그려 넣은 듯. -_-;;

명령 프롬프트가 가로폭 조절이 가능해진 것이 무척 인상적이다. 하지만 글꼴은 여전히 제대로 customize가 안 된다. 하다못해 먼 옛날 9x 시절에는 코드 페이지가 무엇이냐에 따라 Courier New나 Lucida Console이라도 볼 수 있었는데 너무 칙칙한 글꼴은 오히려 NT 계열이 9x보다도 퇴보했다.

운영체제를 업데이트 한 직후, 이전까지 사용하던 프로그램들은 10에서도 거의 그대로 곧장 사용이 가능했다. 그러나 <날개셋> 한글 입력기는 편집기만 남아 있고 외부 모듈은 운영체제의 IME 목록에서 사라졌다.
허나 이건 프로그램을 재설치만 하면 해결되니 그리 심각한 문제는 아니다. 또한 지금까지 파악한 바로는 Windows 10은 문자 입력에 관해서 딱히 기술적으로 크게 바뀐 게 보이지 않는다. 심지어는 세벌식 파워업도 10의 MS 한글 IME를 대상으로 잘 동작한다.

2. <날개셋> 한글 입력기에서 발견된 문제

다만, <날개셋> 한글 입력기가 Windows 10에 대비하여 개선해야 할 사항은 크게 두 가지가 발견됐다.

(1) 첫째, Windows 10은 Metro 앱도 데스크톱에서 뭔가 일체형으로 연계하며 동작하게 된 듯하다. Windows 10의 상징이라 해도 과언이 아닌 엣지(Edge) 브라우저도 기술적으로는 Metro 앱이다. Metro 모드에서는 클래식 데스크톱 GUI를 사용하는 기능들이 동작하지 않는다. 고로 <날개셋> 제어판이나 About 대화상자 같은 것도 못 띄운다. (그걸 시도하면 그냥 씹히거나 프로그램 전체가 그냥 튕긴다)
8 시절에는 메트로 앱들은 데스크톱의 입력 도구모음줄에 접근을 할 수 없으니 신경 쓸 필요가 없었는데, 이제는 메트로 모드에서는 이런 기능들을 건드릴 수 없도록 도구모음줄 버튼이나 메뉴를 흐리게 처리해 줘야겠다.

날개셋 말고 다른 IME들은 이 문제를 어떻게 해결했느냐 하면 한글 IME는 아예 대화상자가 출력되는 명령들을 모조리 없애 버렸다. 심지어 8에는 있던 About 명령조차도 없앴다. 일본/중국어 IME는 GUI가 나오는 기능들은 몽땅 다른 프로그램을 통해 실행되게 되어 있다.

사실, 한글 IME도 부수/획수 한자나 필기 인식 같은 보조 입력 도구는 별도의 프로그램을 통해 구동한다. 그러나 내 프로그램은 이런 것들이 다 in-process 형태로 구동된다. 그래서 Metro 모드 같은 데서는 여러 제약이 걸리는 것 같다. 금방 해결 가능한 문제는 아닌 것 같고 프로그램 구조를 앞으로 어떻게 유지할지 생각을 해 봐야겠다.
참고로, 엣지 브라우저 자체는 크롬의 마소 버전이라고 생각해도 좋을 정도로 "빠르고" 산뜻해서 느낌이 굉장히 좋았다. 그거 하나는 인정한다.

(2) 둘째, 엣지에서 페이스북에 접속해서 댓글을 써 보니, 한참 글자를 입력하다가 비한글 문자를 입력하는 순간, 예전에 입력 중이던 글자가 몽땅 사라지는 문제가 있었다. "가을에." -> "에."만 남는 식.
또한 명령 프롬프트에서도 내 프로그램으로 한글을 입력해 보면, 조합하던 글자가 덮어써진다. "가" "가을" "가을에"가 되는 게 아니라 "가" "을" "에". 신기하게도 MS IME로 한글을 좀 입력하다가 다시 날개셋으로 돌아오면 명령 프롬프트는 이 문제가 없어짐.

100% 재연 가능하고, MS IME는 안 그런데 내 프로그램만 그렇고.. 현상 자체는 완벽하게 파악이 됐지만 이건 또 MS가 무슨 농간을 부린 건지 허무한 생각이 든다. 아마 둘 다 같은 원인 때문일 거라고 추측만 하는데.. 제일 골치 아픈 부류의 문제이며 해결이 쉽지 않을 수도 있다.

3. key의 인식 방법과 관련된 customization

이건 Windows 10과는 관계가 없는 다른 얘기이다.
<날개셋> 한글 입력기는 한글을 생성하기에 앞서서 key 입력을 인식하는 방식 자체를 사용자화 가능하다. 크게 다음과 같은 시나리오가 있다.

(1) 원래는 먹던 특정 key를 먹지 않게 할 수 있다: 표준 두벌식의 경우 오로지 A~Z 26개 key만 사용하기 때문에 나머지 숫자와 기호는 따로 IME가 가로채는 게 아니라 그냥 응용 프로그램으로 넘겨서 숫자나 기호를 입력하게 한다.

(2) 원래는 먹지 않던 특정 key를 IME 입력으로 바꿀 수 있다: space의 경우, 굳이 IME가 처리할 필요가 없는 key이다. 하지만 인디자인 버그 같은 걸 해결하기 위해서 IME가 굳이 가로채어 공백을 되돌리게 할 수 있다.

(3) 특정 key를 IME 입력 대신 다른 key로 바꿀 수 있다: 내 프로그램으로 드보락 글쇠배열을 사용하는 경우, 보통은 드보락 방식대로 IME가 알파벳을 보내게 한다. 하지만 글쇠 누름 날개셋문자를 사용하면 아예 해당 알파벳 key 입력을 응용 프로그램에다가 보내게 할 수도 있다.

쉽게 말해 비한글 알파벳이나 공백 같은 문자를 IME 방식으로 보내느냐, 그렇지 않고 더 원초적인 key 메시지 형식으로 보내느냐를 제어할 수 있다는 뜻이다.
단축키 같은 것은 key 입력 메시지 형태로 온 것만 인식되지 IME 방식으로 전달된 문자로는 인식되지 않는 경우가 많기 때문이다. MS Excel의 경우, 영문을 key 입력 형태로 입력했을 때에만 함수의 목록 자동 완성이 처리되지만 IME 방식으로 입력된 것에는 반응하지 않는다.

(1)과 (2)는 6.5 버전에서 기본 입력 스키마에 해당 기능이 추가되고 구현됐다. 한편, '글쇠 누름' 날개셋문자는 비교적 최근인 7.9 버전에서 추가되었다.
현재 개발 중인 8.2 버전은 (3)이 소개하는 '글쇠 누름' 날개셋문자의 사용을 홍보하기 위해, 제어판의 글쇠배열 편집 창에서 숫자/문자를 입력하는 '일반 문자' 날개셋문자를 그에 상응하는 '글쇠 누름' 날개셋문자로 자동으로 모두 바꾸는 기능을 추가했다.

그리고 더 간단히, 사용자가 누른 key에 해당하는 글쇠 누름 날개셋문자를 자동으로 입력시키는 모드도 추가했다. 예전에는 세벌식 한글, 두벌식 한글, 이동, 영문 알파벳 같은 모드가 있었는데 그런 것처럼 새로운 모드를 넣었다는 뜻이다. 아주 자연스럽게 기능 확장이 가능하다.

또한 (2)를 더 쉽게 설정할 수 있게 하기 위해, 단축글쇠를 입력받는 대화상자에다가는 지금 누른 가상 키코드의 문자값에 해당하는 '일반 문자' 날개셋문자를 자동으로 배당하는 기능도 추가했다.
알파벳이나 숫자를 key 입력으로 보내는 것과 IME 문자열로 보내는 것의 차이에 대해서는 도움말에다가도 더 자세한 설명을 넣을 예정이다.

4. 고기 먹고 싶음 -_-

아직 어쩔 수 없는 여름이긴 하지만 그래도 날씨가 예전보다 덜 더워져서 무척 좋다.
난 어릴 때부터 왜 태양이 긍정적인 심상이고 그늘이 부정적인 심상인지를 이해를 못 했을 정도로 몸에 열이 많고 더위를 싫어했다.

이제 좀 가을이 되고 나면 날씨 탓할 일 없이 더 집중해서 코딩을 진행하고 싶은데.. 프로그램 완성의 꿈은 요원하기만 하다.
도깨비불 현상 없는 입력 방식이 심리적으로 무척 도움이 된다. 세벌식은 없어질래야 없어질 수 없고 없어져서는 안 되는 한글 입력 방식이다. 뭐 그건 그렇고..

사용자 삽입 이미지
나만 저렇게 생각하는 게 아니었구나.
돼지는 훌륭한 단백질 공급원이고, 단백질은 훌륭한 스트레스 해소원이다.

사용자 삽입 이미지
그래서 위대한 령도자 세종대왕도 약을 빨거나 한 건 아니고 고기를 드시면서 한글을 창제하시였다. 한글이 없었으면 내 프로그램도 없었겠지.
(다만, 그 덕분에 저분은 세자 시절부터 굉장한 비만이었으며, 당뇨 같은 성인병도 평생 달고 살았다고 함. 본인도 키에 비해서는 좀 과체중인 상태..;;)

Posted by 사무엘

2015/08/28 08:31 2015/08/28 08:31
, , ,
Response
No Trackback , a comment
RSS :
http://moogi.new21.org/tc/rss/response/1132

한때 Windows에서 바탕 화면에 배경 그림을 표시하는 방식은 '바둑판, 화면 중앙, 화면 크기에 맞춤'이라는 딱 세 가지 중 하나를 선택할 수 있었다.
이것은 GDI에서 직사각형 영역에다 비트맵을 뿌리는 함수로 치면 각각 PatBlt, BitBlt, 그리고 StretchBlt에 대응한다. 지금은 몇 가지 방식이 더 나오는데, 그건 그림의 종횡비와 화면의 종횡비가 다를 때 확대를 어떻게 할지를 결정하는 것이므로 개념적으로 StretchBlt에 대응하는 셈이다.

GDI에서 비트맵 그래픽을 표현하는 추상적인 핸들 자료형은 잘 알다시피 HBITMAP이다. 그러나 위의 세 *Blt 함수들 중 어느 것도 HBITMAP을 인자로 받지 않는다. 이는 어찌 된 일일까?
이들은 비트맵을 자기들이 처리하기 용이한 형태로 바꾼 파생 자료형을 대신 사용한다. PatBlt를 사용하려면 뿌리려는 비트맵을 브러시로 바꿔야 하며, BitBlt와 StretchBlt는 해당 비트맵에 대한 그래픽 조작이 가능한 메모리 DC를 추가로 준비해야 한다. 그럼 그 구체적인 내역을 살펴보자.

모노크롬 아니면 16색 그래픽이 있던 시절, 도스용 그래픽 라이브러리에는 8바이트로 표현되는 8*8 단색 패턴이라는 게 있었다. 그 작은 공간으로도 벽돌, 사선 등 생각보다 기하학적으로 굉장히 기발한 무늬를 표현할 수 있었다.
Windows는 2000/ME까지만 해도 배경 그림은 오로지 BMP만 지원했으며(액티브 데스크톱을 사용하지 않는 한), 배경 그림이 차지하지 않는 나머지 영역은 그런 무늬로 도배하는 기능이 있었다. 물론 이것은 트루컬러 그래픽과는 영 어울리지 않는 낡은 기능이기에, XP부터는 깔끔하게 없어졌다.

사용자 삽입 이미지

PatBlt는 직사각형 영역을 주어진 브러시로 채우는 함수이다. 즉, 이 함수가 사용하는 원천은 함수의 별도 인자가 아니라 해당 DC에 선택되어 있는 브러시이다. 그럼 얘는 Rectangle이나 FillRect와 하는 일이 거의 차이가 없는 것 같아 보인다. 이 세 함수의 특성을 표로 일목요연하게 정리하면 다음과 같다.

  PatBlt FillRect Rectangle
경계선을 current pen으로 그음 X X O (유일)
경계면을 current brush로 채움 O No, brush를 따로 인자로 받음 O
사각형 좌표 지정 방식 x, y, 길이, 높이 RECT 구조체 포인터 x1, y1, x2, y2 (RECT 내용을 풀어서)
래스터 오퍼레이션 지정 O (유일) X (= PATCOPY만) X (왼쪽과 동일)

다들 개성이 넘쳐 보이지 않는가? =_=;;
Rectangle은 선을 긋는 기능이 유일하게 존재하며, FillRect는 유일하게 사용할 브러시를 매번 인자로 지정할 수 있다. 그 반면 PatBlt가 유일하게 갖추고 있는 기능은 래스터 오퍼레이션인데, 사실 이것이 이 함수의 활용도를 크게 끌어올려 주는 기능이다. 이에 대해서도 앞으로 차차 살펴보도록 하겠다.

브러시는 '2차원 면을 바둑판 형태로 채우는 어떤 재질'을 나타내는 GDI 개체이다. 가로선· 세로선· 대각선 같은 간단한 무늬는 CreateHatchBrush로 지정 가능하지만 이건 오늘날에 와서는 영 쓸 일이 별로 없는 모노크롬 그래픽의 잔재이다.
CreateSolidBrush는 아무 무늬가 없는 순색 브러시를 표방하긴 하지만, 그래도 16/256컬러 같은 데서 임의의 RGB 값을 넘겨 주면 단순히 가장 가까운 단색이 아니라 ordered 디더링이 된 무늬가 생성된다.

그리고 다음으로 비트맵으로부터 브러시를 생성하는 함수는 바로 CreatePatternBrush이다.
여기에서 사용할 비트맵은 가장 간단하게는 CreateBitmap이라는 함수를 통해 생성할 수 있다. 이 함수가 인자로 받는 건 비트맵의 가로· 세로 크기와 픽셀 당 색상 수, 그리고 초기화할 데이터가 전부이다. 아주 간단하다.

그러나 이 비트맵은 그냥 2차원 배열 같은 픽셀 데이터 덤프 말고는 그 어떤 정보도 담겨 있지 않으며, 이걸로 만들 수 있는 건 구조가 극도로 단순해서 어느 그래픽 장비에서나 공통으로 통용되는 모노크롬 비트맵뿐이다. 즉 그 도스 시절의 8*8 패턴 같은 극도로 단순한 비트맵만 만들 수 있다. 오늘날에 와서 CreateBitmap은 모노크롬 비트맵 생성 전용이라고만 생각하면 된다.

모노크롬 비트맵을 기반으로 만들어진 DC나 브러시는 다른 solid/hatched 브러시와는 달리 자체적으로 색상 정보가 담겨 있지 않다. 그렇기 때문에 이때는 그래픽을 뿌리는 DC가 갖고 있는 텍스트의 글자색(값이 0인 곳)과 배경색이(값이 1인 곳) 양 색깔로 선택된다는 점도 참고하자. MSDN에 명시되어 있다. (0과 1 중 어느 게 글자인지 이거 은근히 헷갈린다. 빈 배경에서 뭔가 정보가 있다는 관점에서는 1이 글자 같아 보이기도 하니 말이다.)

그리고 브러시는 origin이라는 게 있어서 어떤 경우든 이를 원점으로 하여 바둑판 모양으로 뿌려진다. oxoxoxox라는 무늬가 있다면, 0,0부터 8,0까지 뿌린다면 oxoxoxox로 뿌려지지만 1,0부터 9,0까지 뿌린다면 ox가 아니라 xoxoxoxo가 된다는 뜻이다.

모노크롬이 아닌 컬러 비트맵을 저장하고 찍는 절차는 좀 복잡하다. 이미 컬러를 표현할 수 있는 DC로부터 CreateCompatibleDC와 CreateCompatibleBitmap을 거쳐서 비트맵을 생성해야 한다. 아니면 CreateDIBitmap를 써서 DIB라 불리는 '장치 독립 비트맵' 정보로부터 HBITMAP을 생성하든가.. 얘는 그냥 비트맵 데이터뿐만 아니라 팔레트 정보 같은 것도 담긴 헤더를 인자로 받는다. 출력할 그래픽 데이터와 출력 매체의 픽셀 구조가 다를 때를 대비해서 추상화 계층이 추가된 것이다.

원래 패턴 브러시는 8*8의 아주 작은 비트맵만 취급할 수 있었다. 그러나 NT 내지 95 이후의 버전부터는 그 한계가 없어지면서 브러시와 오리지널 비트맵 사이의 경계가 좀 모호해졌다. 그래도 PatBlt는 작은 비트맵 무늬 위주의 브러시를 래스터 오퍼레이션을 적용하여 그리는 용도에 원래 최적화돼 있었다는 점을 알아 두면 되겠다.
윈도우 클래스를 등록할 때 우리는 WNDCLASS의 hbrBackground 멤버를 흔히 (HBRUSH)(COLOR_WINDOW+1) 이런 식으로 때워 버리곤 하는데, 여기에다가도 저런 패턴 브러시를 지정해 줄 수 있다. 그러면 그 윈도우 배경에는 자동으로 바둑판 모양의 비트맵이 배경으로 깔리게 된다. 이런 식의 활용도 얼마든지 할 수 있다.

한편, 비트맵을 찍는 동작에는 그냥 있는 그대로 뿌리는 것뿐만이 아니라 래스터 오퍼레이션을 통해 반전을 해서 찍기(PATINVERT), 타겟 화면을 무조건 반전시키기(DSTINVERT), 타겟 화면을 무조건 검거나 희게 바꾸기 같은 세부 방식의 차이가 존재할 수 있는데, 앞서 언급한 FillRect뿐만 아니라 InvertRect나 DrawFocusRect 같은 함수도 사실은 PatBlt의 기능을 이용하여 다 구현 가능하다. cursor를 깜빡거리는 건 두 말할 나위도 없고 말이다.

임의의 색깔로 음영을 표현하는 것이라든가, 특히 이동이나 크기 조절을 나타내는 50% 반투명 검은 음영 작대기/테두리는 모두 이 함수의 xor 래스터 오퍼레이션으로 표현된다. 그걸 구현하는 데는 PatBlt 말고는 선택의 여지가 없다는 뜻. 흑백을 xor 연산 시키면 "원래 색 & 반전색"이 교대로 나타나니까 말이다.

사용자 삽입 이미지
물론 요즘은 (1) 걍 테두리 없이 해당 개체를 즉시 이동이나 크기 조절시키는 것으로 피드백 또는 (2) 알파 블렌딩을 이용한 음영이 대세가 되면서 전통적인 xor 음영은 점점 비중이 줄어들고 있긴 하지만, PatBlt 함수는 그래도 이렇게 유용한 구석이 있다.

이런 PatBlt에 반해 BitBlt는 비트맵을 SelectObject시킨 DC를 원본 데이터로 사용하기 때문에 컬러 비트맵의 출력에 더 최적화되어 있다. PatBlt처럼 비트맵을 바둑판 모양으로 반복 출력하는 기능은 없으며, 딱 원본 데이터의 크기만큼만 출력한다. PatBlt와는 달리 고정 origin이 없고 사용자가 찍으라고 한 위치가 origin이 된다. StretchBlt는 거기에다가 확대/축소 기능이 추가됐고 말이다.

이 정도면 비트맵 API에 대한 개념이 충분히 숙지될 수 있을 것이다. 각종 아이콘과 마우스 포인터들도 다 마스크 비트맵 AND와 컬러 비트맵 XOR이라는 래스터 오퍼레이션을 통해 투명 배경 내지 반전을 구현한다는 건 두 말하면 잔소리이다. 물론 오늘날은 알파 채널로 투명도를 구현하면서 래스터 오퍼레이션의 의미는 다소 퇴색했지만 말이다.
그럼 이제 비트맵 API들에 대한 개인적인 의문점과 아쉬운 점을 좀 나열하며 글을 맺겠다.

(1) GDI는 후대에 등장한 다른 그래픽 API들과는 달리, 글꼴을 제외하면 벡터와 래스터 모든 분야에서 안티앨리어싱과는 담을 싼 구닥다리 API로 전락해 있다. 그러니 비트맵을 정수 배가 아닌 확대/축소를 좀 더 부드럽게 하거나, 아예 임의의 일차변환을 한 모양으로 출력하려면 최소한 GDI+ 같은 다른 대체제를 써야 한다.

(2) 운영체제가 가로줄, 세로줄 같은 몇몇 known pattern에 대해서 CreateHatchBrush 함수를 제공하긴 하는데, 50% 음영 정도는 오늘날에도 많이 쓰이기 때문에 known 패턴이 좀 제공되어야 하지 않나 싶다. 그게 없어서 수많은 프로그램들이 내부에 0x55, 0xAA 배열을 일일이 생성해서 패턴을 만드는 것은 낭비이다.
오히려 cursor는 CreateCaret 함수에 (HBITMAP)1을 줘서 50% 음영을 만드는 기능이 있는데, 정작 그건 별로 쓸 일이 없다.

(3) 브러시 말고 펜으로 선을 그리는 걸 xor 반전 연산으로 하는 기능은 없는지 궁금하지 않으신지? 임의의 사선이나 원 테두리를 그렇게 그리는 건 그래픽 에디터를 만들 때도 반드시 필요하니 말이다.
물론 그런 기능이 없을 리가 없다. SetROP2라는 함수로 그리기 모드를 바꿔 주면 된다. 단, 여기서 입력받는 래스터 오퍼레이션 코드는 BitBlt가 사용하는 코드 체계와는 다르다. 비트맵 전송 API들은 화면의 원본 픽셀(D), 그리려는 픽셀(S)뿐만 아니라 패턴(P)이라는 변수가 또 추가되어서 원래는 3변수 코드를 사용한다. BitBlt는 PatBlt가 하는 일까지 다 할 수 있는 모양이다.

Posted by 사무엘

2015/08/12 08:33 2015/08/12 08:33
, , , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1126

타이머 API 이야기

컴퓨터 프로그램이라는 건 원래 처음부터 끝까지 컴퓨터가 그야말로 눈 깜짝할 사이에 전속력으로 실행해 버리는 물건이다. 그러나 컴퓨터에는 정밀한 시간 측정 기능이 있으며, 프로그램이 원하는 경우 자신이 실행되는 주기를 그에 맞춰 인위로 조절할 수 있다.
일명 타이머 기능인데, 이것은 컴퓨터가 액세서리 차원에서 제공하는 부가 기능이 아니라 컴퓨터 자체의 내부 동작 방식의 특성상 컴퓨터가 반드시 갖추고 있는 기능이다. 단적인 예로 난수 생성을 위한 씨앗(= 매번 달라야 하는 초기값)도 내부적으로 재고 있는 시각으로부터 얻을 정도이다.

컴퓨터가 속도가 매우 느리고 자원이 부족하고, 한 프로그램이 컴퓨터의 전체 자원을 독점할 수 있던 옛날에는 매번 타이머를 측정하면서 0.n초가 경과하는 것을 프로그램이 일일이 감시하는 방식으로, 즉 polling 방식으로 동작했겠지만, 지금은 그건 어림도 없는 소리이다. 자기에게 time slice를 주는 운영체제에다가 '알람' 요청을 해서 알람이 왔을 때 동작하게 해야 한다.

Windows에서 이 기능을 사용하는 아주 대표적인 방법은 타이머 API이다. SetTimer, KillTimer와 그 이름도 유명한 WM_TIMER 메시지.
타이머는 그 성격에 따라 게임이나 멀티미디어 재생기 등에서 프레임 간격 유지를 위해 1초에 수십 번씩 돌아가는 (1) 아주 정밀한 놈부터 시작해, 수백 밀리초~수 초 정도의 간격으로 사용하는 (2) 일반적인 타이머, 그리고 드물게는 수 시간~수 일 주기를 갖는 (3) 장기 타이머도 있다. 운영체제의 보급 타이머는 일단은 가성비가 적당히 우수한 '일반적인' 용도에 가장 적합하게 설계돼 있다. 이게 무슨 뜻인지를 설명하면 이렇다.

보급 타이머도 명목상으로는 수십 밀리초 정도의 정밀도를 지원한다. 하지만, WM_TIMER는 WM_PAINT만큼이나 메시지 큐에서 처리 우선순위가 무척 낮은 메시지이기 때문에 컴퓨터가 아주 바쁘고 윈도우 메시지 트래픽이 아주 많을 때는 정밀도가 떨어질 수 있다. 더구나 Windows는 근본적으로 리얼타임 운영체제가 아닌 관계로, 커널의 시간 스케줄링을 초월해서까지 무조건적인 초정밀도는 애초에 보장되지도 않는다. 타이머의 정밀도가 올라갈수록 필요한 시스템 자원과 부하도 더 커질 테니, 초정밀 타이머가 필요하다면 QueryPerformanceCounter나 멀티미디어 타이머 같은 다른 전문 API를 쓰고 동기화도 커널 오브젝트 같은 다른 방법을 써서 해야 한다.

한편, 다른 쪽 극단에 있는 장기 타이머는 응용 프로그램 자체의 동작이라기보다는 업데이트 주기를 체크하거나 사용자에게 적당히 덜 성가신 주기로 뭔가를 알리는 용도로 사용된다. 이 정도면 타이머라기보다는 알람에 더 가깝다.
개인적으로는 지금으로부터 "5000밀리초 간격으로" 같은 것 말고, 절대적인 시각.. 예를 들어 1970년 1월 1일 0시 정각 이래로 40억 5800만 초가 딱 경과했을 때처럼 절대적인 시각을 기준으로 trigger되는 진정한 '알람' 타이머도 필요하다고 생각한다.

시계 프로그램을 만들 때는 이런 타이머 API가 더 유용하지 않겠는가? 그리고 장기 타이머를 사용할 정도의 상황이라면 지금으로부터 시간이 얼마만치 지났는지보다는 매일 몇 시가 됐는지가 더 중요한 경우가 많을 것이기 때문이다.

이렇게 극단적으로 짧은 주기의 타이머나 극단적으로 긴 타이머 말고, 보통 주기의 타이머는 여러가지 용도로 쓰인다. 가령, 키보드는 누르고 있는 동안 키 입력이 하드웨어 차원에서 자동으로 반복 전달되는 반면 마우스는 그런 게 없는데, 마우스를 누르고 있는 동안 자동 스크롤이 되는 것은 타이머로 처리가 가능하다. 그리고 간단한 비동기적인 처리를 위해서도 타이머가 약방의 감초처럼 쓰인다.

이게 도스의 제약인지 아니면 인텔 x86 CPU 차원의 제약인지 구체적인 내역은 기억이 안 나지만, 도스 시절에는 컴퓨터의 타이머 해상도가 1/18.2초여서 최소 주기가 약 55밀리초였던 것 같다. Windows 9x 시절에만 해도 운영체제의 타이머의 정밀도는 그 정도였다고 MSDN에 기록돼 있었는데 NT 계열은 하드웨어를 또 어떻게 튜닝했는지 타이머가 그것보다 훨씬 더 정밀해졌다.

자, 그럼 이 글에서는 Windows의 일반 타이머 API에 대해서 더 자세히 알아보자.
SetTimer 함수의 인자로는 타이머의 발동 주기뿐만 아니라 (1) 타이머를 메시지로 받을지 아니면 함수 호출로 받을지, (2) 그리고 메시지로 받는 경우 동일 메시지에서 이 타이머만을 식별할 번호를 지정하면 된다. SetTimer 함수는 사용하는 방법이 생각보다 좀 복잡하다.

(1) SetTimer에다가 뭔가 윈도우 핸들을 전해 주는 경우, 타이머는 메시지로 받을 수도 있고 콜백 함수로 받을 수도 있다. 두 가지 선택의 여지가 있으며, 타이머 식별 번호는 우리가 임의의 자연수로 일괄 지정해 줄 수도 있다.
(2) 그 반면 윈도우 핸들이 없이, 윈도우를 전혀 생성하지 않고도 타이머를 사용할 수 있다. 그 대신 이때는 몇 가지 제약이 따른다. 메시지가 아닌 콜백 함수로만 통지를 받을 수 있으며, 타이머 식별자는 우리가 지정할 수 없다. SetTimer 함수가 되돌린 값을 별도의 변수에다 보관하고 있어야 한다.

마치 에디팅 엔진의 기능만을 따로 떼어서 windowless 리치 에디트 컨트롤이 존재하는 것처럼 타이머도 windowless 타이머가 존재하는 셈이다. 물론 SetTimer가 무슨 스레드를 만들기라도 해서 따로 돌아가는 건 아니기 때문에, 비록 windowless 타이머를 사용한다 하더라도 메시지 loop은 돌리고 있어야 타이머가 동작할 수 있다.

개인적으로는 (1)과 (2)의 특징을 취합하는 방법이 없는 게 아쉽다. 윈도우 핸들을 지정해 줘서 WM_TIMER 메시지를 받는데 타이머 식별자는 내가 일괄 지정한 게 아니라 운영체제가 기존 타이머들과의 충돌을 피해서 동적으로 배당한 값이 오는 형태 말이다.
서브클래싱 내지 후킹을 한 윈도우에 대해서 타이머를 걸 때는 하드코딩된 타이머 ID를 써서는 안 된다. 원래의 윈도우 프로시저가 사용하는 고정 타이머 ID와 충돌을 일으킬 수 있기 때문이다. 마치 윈도우 메시지가 서로 충돌하는 것처럼 말이다.
이때는 충돌이 없음이 보장되는 windowless 타이머를 써야 한다. 하지만 windowless 타이머는 다음과 같은 이유로 인해 사용이 불편하다.

첫째, 콜백 함수에 user data를 넘겨 주는 추가 인자가 없다. 그래서 user data는 전역 변수나 TLS 값 같은 불편한 방법으로 얻어 와야 한다.
둘째, 윈도우가 붙은 타이머는 같은 ID값으로 타이머를 지정하는 경우, 기존 타이머가 새 타이머로 자동으로 대체된다. 그러나 windowless 타이머는 그런 기능이 없기 때문에 기존 ID에 대해서 KillTimer를 하고 다시 SetTimer를 해서 새 ID를 얻는 작업을 수동으로 해 줘야 한다. 다시 말해 기존 타이머의 재지정이 어렵다.

결국, 충돌을 피하기 위해서는 windowless 타이머를 써야 하는데 이 타이머도 윈도우가 붙은 타이머하고 비슷하게 동작하도록 추가 군더더기 기능을 구현한 클래스를 만든 뒤에야 그럭저럭 쓸 만하게 됐다.
윈도우가 있는 타이머와 없는 타이머에서 서로 필요한 기능을 취합하는 방법이 없어서 불편하다는 걸 다시 한 번 확인할 수 있었다.

그나저나 SetTimer 함수에서 ID를 받는 부분은 포인터나 핸들을 넘기는 용례가 없는데 자료형이 왜 UINT가 아닌 UINT_PTR로 잡혀 있는지 이것도 개인적으로는 의문이다.

Posted by 사무엘

2015/08/09 08:21 2015/08/09 08:21
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1125

Windows의 역사 회상

1. 공용 대화상자

먼 옛날, Windows 3.0은 최초로 VGA를 지원하고 팔레트 API, 장치 독립 비트맵, MDI 관련 API가 추가되고, RTF 기반 winhelp 도움말이 추가되고, 버튼이 3D 회색으로 바뀌고 시스템 글꼴까지 가변폭으로 바뀌는 등 장족의 발전을 이뤘다. (386 확장 모드는 2.1때 미리 도입됐다고 하니 그건 논외로 하더라도)
그런데, 3.0에 없다가 3.1에서 새로 추가된 기능들도 만만찮았다. 트루타입 글꼴과 OLE야 워낙 잘 알려진 3.1의 신규 기능이다만.. 이것 말고도 오늘날 당연하게 여겨지고 있는 '공용 대화상자' 컬렉션들이 역시 3.1에서 처음 도입되었다.

3.1 이전에는 GetOpenFileName 함수가 Windows API에 없었다는 뜻이다. 파일 열기/저장 대화상자는 응용 프로그램들이 전부 직접 따로 구현해야 했다. MS Office 제품들이 한동안 독자적인 파일 열기/저장 대화상자를 갖추고 있었던 건 운영체제도 Windows 3.1 이전까지는 어차피 해당 기능을 제공하지 않았기 때문이지 싶다. Word, Excel은 이미 1980년대부터 개발되었던 프로그램이니까.
그리고 파일 대화상자뿐만 아니라 색깔 선택, 텍스트 검색, 인쇄 같은 잘 알려진 공용 대화상자들도 3.1에서 처음으로 도입됐다.

옛날 도스 시절에 TUI 내지 GUI를 직접 구현하면서 파일 열기/저장 대화상자도 손수 만들어 본 프로그래머라면 공용 대화상자가 얼마나 혁신적인 물건인지 이해가 될 것이다.
그리고 내 생각엔 아마 ShellAbout 함수도 3.1에 와서야 용례가 완전히 정립되지 않았나 싶다. 3.0 때는 응용 프로그램별로 About 대화상자도 서로 다르게 생긴 경우가 있었기 때문이다.

공용 대화상자에 이어 리스트/트리 컨트롤 같은 추가적인 "공용 컨트롤"은 Windows 3.1보다 한 박자 뒤인 Windows 95 내지 NT 3.51과 함께 도입됐다.
물론 일반 사용자에게 와 닿는 Windows 3.0과 3.1의 큰 차이는 저런 기술적인 요소가 아니라... 보조 프로그램으로 리버시(오델로 게임)가 짤리고 그 이름도 유명한 '지뢰찾기'가 대신 도입된 게 아닌가 싶다.

2. 9x와 NT가 따로 놀던 API

과거에 Windows 95와 NT가 공존하던 시절에는 일반적으로 95의 API는 NT의 API에 부분집합으로서 완전히 포함되는 것으로 여겨졌다. 보안이나 유니코드, 일부 고급 기능들이 빠져 있을 뿐, 공통 기능은 동일한 형태로 사용 가능하다는 것이다.
하지만 일부 기능은 95에도 전혀 없는 건 아닌데 NT와는 완전히 다른 형태로 따로 구현되어 API가 파편화되고, 이 때문에 프로그래머들 사이에서 번거로움으로 인해 악명을 떨치기도 했다. 그만큼 Windows 95팀과 NT 팀이 마치 MFC 팀과 Office 팀(리본 UI), Windows 팀과 Visual C++ 팀(CRT DLL)만큼이나 생각만치 교류가 없었다는 뜻이다. 이거 무슨 일본군 육군과 해군도 아니고.

그런 기능으로 무엇이 있느냐 하면 첫째는 사용 중인 파일을 다음 재부팅 때 지우도록 예약하는 기능이요, 둘째는 실행 중인 프로세스와 모듈들을 조회하고 heap 메모리 상태를 조회하는 기능이다.
전자는 NT에서는 MoveFileEx 함수를 쓰면 됐지만 95에서는 그 함수가 지원되지 않았다. 95에서는 wininit.ini라는 살생부 리스트를 수동으로 건드려 줘야 했는데, 이게 처리가 Windows가 아닌 도스 계층에서 행해지는지라 긴 파일 이름을 쓸 수 없어서 더욱 불편했다.

다음 후자의 경우, NT는 커널 API의 연장선 차원에서 EnumProcesses, EnumProcessModules, HeapLock, HeapWalk 같은 함수가 제공되었다. 카테고리의 명칭은 Process status API (PSAPI)라고 불렸다.
그러나 95는 Tool Helper라는 특수한 디버그용 라이브러리 개념으로 CreateToolhelp32Snapshot 이후 [Heap/Module/Process/Thread]32[First/Next] 이런 식으로 함수를 제공했다. 함수를 초기화하고 사용하는 방법이 서로 완전히 딴판이라는 얘기다.

공교롭게도 이 두 기능은 모두 설치/제거 프로그램을 만들 때 필요한 기능이다. "이 DLL은 다음 프로그램이 사용하고 있습니다. 다음 재부팅 때 제거하시겠습니까?"를 구현하려면 말이다. Windows Installer 런타임은 당연히 9x용과 NT용이 이런 점을 감안하여 제각각 구현되어 있었을 것이다.
결국 Windows 2000에 가서야 지금까지 9x에만 있던 tool help library를 NT 계열이 마저 흡수하는 걸로 문제가 종결되었다. 마치 95에서 첫 도입되었던 Plug & play를 드디어 2000이 수용했듯이 말이다. 게다가 궁극적으로는 9x 계열 자체가 없어지기도 했고.

3. 그래픽과 사운드 성능 향상

1990년대 중후반에서 2000년대 초반에 이르기까지 컴퓨터의 성능이 향상됨으로써 Windows에 생긴 3대 변화를 들자면 난 다음을 꼽는다. 예전에 한 번씩은 다 언급한 적이 있었을 것이다.

(1) 화면이 막 고쳐지는 곳으로 마우스 포인터를 가져가도 깜빡임이 없게 되었다. 그래픽 카드가 마우스 포인터 주변은 건드리지 않게 하드웨어적인 처리를 진작부터 하기 시작했기 때문이다. 이것은 요즘 형광등이 깜빡임 없이 바로 켜지기 시작한 것만큼이나 신기한 일이다.

초창기에는 흑백의 기본 포인터만 처리가 되지, 컬러 내지 심지어 애니메이션이 있는 custom 포인터, 그리고 마우스 포인터 자취까지는 차마 깜빡임 방지 처리를 다 못 했다. 그러나 이것도 2000년대부터는 제약이 없어졌다.
Windows 2000은 아예 안전 모드에서 16컬러 VGA로 동작할 때에도 마우스 포인터의 깜빡임이 없는 게 무척 신기하다. NT가 원래 그랬는지 아니면 2000부터 그렇게 된 건지는 모르겠다.

(2) 멀티웨이브가 되기 시작한 것도 아주 신기한 일이다. 지금으로서는 도저히 믿을 수 없는 일이지만 Windows에 사운드/멀티미디어 지원이 처음으로 도입됐던 3.1/95 초창기에는 한 번에 한 프로그램만 사운드 카드의 사용이 가능했다. 그리고 다른 프로그램은 사운드를 이용할 수 없었다! PC에 사운드 카드가 버젓이 달려 있음에도 불구하고 사운드 초기화가 실패하는 상황에 대한 대비를 해야 했던 것이다.

9x 시절에는 일부 고급형 사운드 카드만이 멀티웨이브가 가능했다가 2000부터는 드디어 그냥 아무데서나 멀티웨이브가 가능해졌다. 이쯤에서 미디 역시 노래방 수준의 소프트웨어 신시사이저로 대체되었고 XP쯤부터는 오디오 CD까지 모든 사운드의 음원이 waveform으로 통합되었으며, Vista부터는 장치가 아닌 스피커/응용 프로그램별로 구분해서 볼륨을 지정하는 게 가능해졌다.

오늘날도 PC에 따라서는 출력 단자에 헤드폰/스피커 같은 게 전혀 연결돼 있지 않으면 사운드의 초기화가 실패하는 경우가 있다. 물론 PC 자체에 스피커가 달려 있는 노트북 PC에서는 해당사항이 없는 얘기. 옛날에도 입력 단자를 감지해서 녹음 버튼의 성공/실패를 감지하는 것 정도는 가능했던 것 같다.

(3) 그리고 제일 늦게 생겼고 Windows Vista가 이뤄낸 쾌거 중 하나는 역시 동영상 장면도 Print screen으로 간단히 캡처가 가능해졌다는 점이다. 창을 움직였는데 동영상 영역은 제대로 움직이지 않는다거나, 화면 캡처를 하면 그냥 컬러 키를 나타내는 이상한 단색만 캡처된다거나.. 이런 것도 이미 10년쯤 전부터 옛날 추억이 됐다.
기술적으로 따지고 보면 동영상만 추가적인 하드웨어 가속을 받는 게 아니라 아예 모든 그래픽이 동등하게 하드웨어 가속을 받기 시작했기 때문이다. GDI조차도 그 위에서 돌아가니까 BitBlt 같은 GDI API로 간단하게 캡처가 되기 시작한 것이다. 게다가 Vista가 처음으로 선보인 flip3d나 live preview에도 동영상이 실시간으로 표시되기 시작했다.

4. Windows 10

그리고 그 Windows 95가 출시된 지 거의 20년이 지난 지금, Windows 10이 출시되었다. 95 출시 당시에 중학생이던 본인은 뭐 이미 30대 중반의 성인이 됐고.
2015년에 마소 소프트웨어의 최대의 이슈는 단연 새 운영체제와 새 개발툴이다. Windows 10과 Visual Studio 2015.

IE가 11에서 종결되고 Edge로 넘어가는 것만큼이나 마소에서는 Windows 10이 독립된 브랜드 형태로는 Windows의 마지막 버전이 될 것이고 그 뒤로는 그때 그때 인터넷 업데이트만으로 유지보수를 할 것이라고 밝혔댄다.. 그 정책이 실제로 언제까지나 유지될지는 모르겠다.

하긴, 매번 XP, Vista 같은 브랜드명에다 숫자에다.. 이런 발상 자체가 식상해지고 아이디어가 고갈될 때도 되긴 했다.
허나 과거에 마소 내부에서는 IE 팀이 Windows 팀으로 합병될 뻔한 적도 있었고, 또 이미 윈도 7 시절부터 이건 NT 커널 기반 Windows의 마지막 버전이고 그 뒤로는 Midori던가 뭐던가 완전히 새로운 기반의 운영체제가 나온다는 식의 설레발도 나돌았다. 트렌드라는 건 언제든지 얼마든지 바뀔 수 있는 것이니 변화를 신중하게 지켜봐야겠다.

그래도 마소에서 이번 Windows 10을 뭔가 완결판이라는 컨셉을 두고 만들었다는 티가 벌써부터 팍팍 느껴진다.
외형이 8하고 별 차이가 없는 줄 알았는데, 프로그램의 제목이 가운데 정렬이던 것이 다시 왼쪽으로 복귀한 건 좀 사소한 점일 테고. ㅋㅋ
그리고 운영체제의 버전뿐만 아니라 커널의 내부 버전 번호도 Vista 이래로 지금까지 6이던 것이 7~9를 건너뛰고 10으로 맞춰졌다.
Windows 10이 저런다고 하니까 마치 Mac OS X 같은 느낌도 든다. 저 X도 10을 나타내니까.. 인터넷을 뒤져 보니 당연히 나만 그렇게 생각한 게 아니었다.

한편, Visual Studio의 경우, 2012 이래로 외형 색상의 변화는 크게 없다. 그럼 그렇지, 매 버전마다 비주얼을 다 뒤집어 엎는 것도 언제까지나 가능한 건 아니겠지 싶었다. ^^ 2013 커뮤니티 에디션이 나온 것부터가 굉장히 놀라웠는데, 갈수록 개방적으로 바뀌는 한편으로 이클립스 내지 xcode의 전통적인 영역까지 넘보고 있다.
운영체제, 브라우저, 개발툴에서 모두 마소가 종전의 소프트웨어 개발 방식 내지 패러다임을 종결하고 단절하겠다는 의지를 표현한 듯하다. 확실히 변해야만 살아남을 수 있다.

Posted by 사무엘

2015/08/03 19:38 2015/08/03 19:38
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1123

« Previous : 1 : ... 5 : 6 : 7 : 8 : 9 : 10 : 11 : 12 : 13 : ... 21 : Next »

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2026/10   »
일 월 화 수 목 금 토
        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:
4198261
Today:
1903
Yesterday:
2133