« Previous : 1 : ... 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 : ... 31 : Next »

1. disabled 스타일 개론

소프트웨어 GUI에서 대화상자나 메뉴 같은 구성요소를 보면, 상태를 나타내는 속성 중에 논리값으로 enabled 여부라는 게 존재한다.
이게 false여서 disable된 물건은 비록 화면에 보이긴 하지만 흐리게 표시되며 완전히 없는 물건으로 취급된다. 사용자의 키보드나 마우스 조작에 반응을 하지 않으며 선택할 수도 없다.

Windows 운영체제에서는 윈도우에서 WS_DISABLED라는 스타일 비트가 이런 역할을 한다. 기본 스타일에 이 비트가 지정되어 있는 윈도우는 키보드 포커스를 받을 수 없으며, 거기에다 대고 마우스 포인터를 움직여도 통상적인 WM_MOUSEMOVE, WM_?BUTTONDOWN 같은 메시지가 오지 않는다.

즉, availability는 어느 정도 운영체제가 직접 관리를 해 주는 예약된 속성이다.
어떤 윈도우가 enabled인지 여부를 알려면 IsWindowEnabled 함수를 호출하면 된다.
IsWindowEnabled(hWnd)는 !(GetWindowLongPtr(hWnd, GWL_STYLE)&WS_DISABLED)와 동치라고 생각하면 된다.

enabled 여부를 설정하는 함수는 EnableWindow이다. 이때 대상 윈도우는 WM_ENABLE라는 메시지를 받음으로써 자신의 availability 속성이 바뀌었다는 통지를 받는다.
Get과 마찬가지로 SetWindowLongPtr를 통해 스타일을 수동으로 바꿔 줘도 거의 같은 효과를 낼 수 있다.
단, 이 방법은 간단히 전용 함수를 호출하는 것보다 번거로우며, 이렇게 속성이 바뀌면 대상 윈도우는 WM_STYLECHANGED만을 받지 WS_DISABLED 비트에 차이가 있더라도 WM_ENABLE 메시지가 오지는 않는다.

2. 화면에 그리기

대화상자 컨트롤이라면 WM_ENABLE 메시지가 왔을 때 자신을 화면에 다시 그리는 처리를 한다.
가령, 평소에는 COLOR_WINDOWTEXT라는 시스템 색상으로 글자를 찍은 반면, disable된 뒤부터는 COLOR_GRAYTEXT 색상으로 글자를 다시 찍는다.

지금이야 Windows 8 때부터 고전 테마라는 게 사라져서 점차 과거의 유물이 돼 가지만..
옛날에 Windows UI를 보면, 메뉴나 도구모음줄에서 사용할 수 없는 항목은 글자가 단순히 회색이 아니라 흰색 위에 회색이 깔려서 뭔가 음각 엠보싱처럼 그려지곤 했다.

사용자 삽입 이미지

그거 처리를 해서 disable 상태를 화면에 표현하는 건 DrawState라는 함수 호출 한 방이면 바로 된다. 이건 딱 회색 3D 대화상자 스타일이 도입된 Windows 95와 NT4에서 첫 추가된 함수이다. 게다가 텍스트와 비트맵(아이콘) 모두 그렇게 그리는 걸 지원한다.

비트맵의 경우, 마스크 비트맵을 바탕으로 엠보싱을 만들며, 이 테크닉은 지금도 그대로 쓰인다. 그렇기 때문에 요즘은 32비트 비트맵 내부의 알파 채널이 투명색을 대신하는 지경이 되었음에도 불구하고 DrawState 함수로 disable 상태의 엠보싱 아이콘을 그리려면 비트맵에 모노크롬 흑백 배경 마스크 비트맵도 넣어 줘야 한다.
뭐, 궁극적으로는 트루컬러 아이콘이라면 구시대스러운 비트 연산이 아니라, 투명도를 높이고 채도를 낮춰서 그림을 더 엷고 탁하게 만드는 '현대적인' 방식으로 disable 상태를 그려야 하겠지만 말이다.

3. disabled 윈도우의 또 다른 용도

그러면 이런 disabled 속성은 오로지 대화상자 내부의 컨트롤 같은 WS_CHILD급 윈도우에서만 쓰이는가 하면 그렇지 않다. 타 윈도우의 자식이 아니라 top-level이 될 수 있는 WS_POPUP급 윈도우도 WS_DISABLED 속성을 줘서 생성할 수 있다. 이 윈도우는 화면이 달랑 떠 있기만 하지 사용자가 포커스를 줘서 키보드 입력을 할 수가 없게 된다.

사실, 한 프로세스 안에서 child 윈도우는 클릭했다고 해서 딱히 포커스가 자동으로 거기로 옮겨지지는 않는다. 포커스가 가는 건 해당 윈도우가 WM_LBUTTONDOWN이 왔을 때 SetFocus를 자체적으로 호출했기 때문이다.
그러나 소속된 프로세스가 다른 top-level 윈도우의 경우, 클릭하면 일단 그 창으로 WM_ACTIVATE 메시지가 가고 컨텍스트 전환이 발생한다. 이것은 프로그램의 의사와 무관하게 운영체제가 일방적으로 하는 일이었는데, disabled 윈도우는 그걸 막을 수 있다.

물론 disabled 윈도우는 앞서 말했듯이 정상적인 마우스 메시지가 오지 않는데, 그래도 이것도 받는 방법이 있다.
disabled라고 해도 top-level 윈도우에는 기본적으로 마우스 포인터 설정을 위해서 WM_SETCURSOR 메시지 정도는 온다. 이 메시지의 lParam에는 이 윈도우에 원래 오려고 한 메시지가 담겨 있기 때문에 이를 토대로 비록 disable 상태이지만 마우스 동작에 반응을 하도록 프로그래밍이 얼마든지 가능하다. child 윈도우가 아닌 top-level 윈도우이기 때문에 이런 메시지가 온다.

disabled 편법을 쓰지 않고 '클릭해도 반응만 하지 포커스가 바뀌지는 않는 간단한 윈도우'라는 개념은 비교적 늦게 등장했다. Windows 2000부터 WS_EX_NOACTIVATE라는 확장 스타일이 정식으로 도입된 것이다. 이런 윈도우는 ShowWindow에다가도 단순히 SW_SHOW가 아니라 SW_SHOWNOACTIVATE를 줘야 한다.

4. 상태 변경과 관련된 연계 작업들

대화상자에서 컨트롤을 enable/disable시키는 상황은 크게 선천적인 것과 후천적인 것으로 나뉜다. 전자는 WM_INITDIALOG에서 단 한 번 enable 여부가 결정되고 그 뒤에 대화상자가 닫힐 때까지 그 상태가 바뀔 일이 없는 경우를 말한다.
후자는 한 컨트롤의 조작 여부나 값에 따라 다른 컨트롤의 enable 상태가 인터랙티브하게 수시로 바뀌는 경우를 가리킨다. 예를 들어 라디오 버튼이 특정 항목으로 맞춰져 있을 때만 조건부로 사용 가능해지는 하부 조건 체크박스 같은 것. UI 프로그래밍을 해 본 분이라면 이 분류가 수긍이 갈 것이다.

그런데 이렇게 컨트롤들의 상태를 바꾸는 건, 단순히 한 윈도우에 대해 EnableWindow를 호출하는 것 이상으로 이와 결합된 반복 패턴이 여럿 존재한다.

(1) 첫째, 대상 컨트롤의 이전에 있는 label 컨트롤을 같이 enable/disable시키는 경우이다. 대상 컨트롤이 버튼이라면--push, check, radio 모두 포함-- 그 자체가 &로 시작하는 Alt 액셀러레이터 글쇠를 갖는다. 그러나 나머지 edit, list, combo 박스 같은 것들은 자신의 액셀러레이터가 없으며 그 이전의 static 라벨로부터 액셀러레이터를 넘겨받는 형태이다.

따라서 그런 컨트롤이 disable됐다면 자기의 앞의 컨트롤도 같이 disable되는 게 이치에 맞다. 앞의 단순 label은 보통 독자적인 컨트롤 ID가 없이 그냥 IDC_STATIC인 경우도 많으므로 핸들값을 GetWindow(hCtrl, GW_HWNDPREV)로 얻어 오는 수밖에 없다.

(2) 둘째, 대상 컨트롤을 화면에서 감출 때에도 ShowWindow(hCtrl, SW_HIDE)만 할 게 아니라 disable을 시켜 줘야 한다. 왜냐 하면 enable 상태인 컨트롤은 비록 화면에 없더라도 Alt 액셀러레이터에는 반응을 해서 사용자가 여전히 기능 접근이 가능하기 때문이다.
본인은 개인적으로는 이게 바람직한 설계가 아니라고 생각하며, Windows가 왜 그렇게 동작하는지 알지 못한다. 하지만 어쨌든 Windows 95부터 8.1에 이르기까지, 조건부로 컨트롤들을 보였다가 숨겼다가 하는 UI의 경우, 감춰지는 컨트롤은 disable도 시켜 줘야 한다.

(3) 셋째, 지금 키보드 포커스를 받고 있는 컨트롤이 disable되는 경우, 포커스를 자신의 다음 컨트롤로 옮기는 일을 해당 프로그램이 수동으로 해 줘야 한다. 이걸 안 해 주면 그 컨트롤이 disable된 뒤부터 키보드 상태가 꼬여 버린다.
이 단서의 주된 적용 대상은 push-버튼이다. 대표적인 예로는 프로퍼티 페이지에 있는 '적용' 버튼. 이 버튼을 누른 순간 이 버튼은 사용 불가 상태가 되며, 사용자가 다른 설정을 또 건드려 줘야 다시 사용 가능해지니 말이다.

본인의 개인적인 생각은 이것도 역시 운영체제가 자동으로 처리해 줘야 하는 게 아닌가 싶지만, 현실은 시궁창이다. 생각보다 자비심이 없다..;;
대화상자에서 특정 컨트롤에다 포커스를 주는 건 SetFocus를 해서는 안 되며, 대화상자 부모 윈도우에다가 WM_NEXTDLGCTL 메시지를 보내는 방식으로 해야 한다. 그렇게 해야 대화상자의 default 버튼에 굵은 테두리가 그려지는 처리가 올바르게 된다.

그래서 본인은 위의 세 시나리오를 모두 감안하여 대화상자 컨트롤을 enable/disable시키는 함수를 별도로 만들어서 사용하고 있다.
그림을 좀 더 곁들이면 전반적으로 설명하기가 더 편하겠다는 생각이 드는데.. 귀찮아서 생략한다. ㄱ-

Posted by 사무엘

2014/12/08 08:29 2014/12/08 08:29
, ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1037

여러분은 다음과 같은 서로 완전히 다른 분야의 관행들에서 공통된 패턴이 존재한다고 생각하시는가? 만약 존재한다면 공통점이 무엇일까?

  • 컴퓨터에서 각종 계정의 암호를 설정하는데, 암호는 무조건 n글자 이상에 대소문자와 숫자 등이 반드시 골고루 섞여야 한다고 프로그램이 사용자에게 강요를 한다.
  • 출퇴근 시간엔 서울 지하철 사당 역은 2호선과 4호선 사이의 지름길 직통 환승 통로를 폐쇄하고 먼 우회 통로로만 환승이 가능하게 만든다. 또한 사람이 지나치게 많이 몰리는 행사가 열리면 가까운 지하철 역이 통째로 폐쇄되고 열차가 무정차 통과한다.
  • (종교 얘기. 비기독교인은 skip해도 좋음) 예루살렘에서 성령이 강림한 후 신약 기독교회가 갓 태동했다. 그러나 하나님은 역설적으로 그 기독교 성지에서 맹렬한 기독교 박해와 스데반의 순교를 허락하셔서 신자들을 뿔뿔이 흩어 버렸다.

내가 생각하는 이것들의 공통점은... 한데 몰려서는 안 되는 곳에 사람들이 지나치게 많이 몰릴 때 “그 몰리는 선택지 자체를 없애 버려서 분산을 강제로 유도”했다는 점이다. 그리고 이로써 집단 내부의 잠재적 부작용이나 병폐를 해결했다.

먼저 종교의 경우다. 먼저 믿고 구원받은 크리스천은 주님의 명령대로 세상 방방곡곡에 흩어져서 복음을 전해야 하는데 그게 말처럼 쉽지 않다. 어지간하면 그냥 신자들끼리만 기득권을 형성하고 교제라는 명목의 친목질만 하면서 고향에서 편하게 살고 싶다. 그러니 하나님이 저런 역경을 허락하신 것이다. 물리적으로만 보자면 그건 자기 신자를 줄이고 세력을 약화시키는 팀킬인데 기독교는 오히려 그런 역경을 통해서 역설적으로 잡초처럼 더 강해지고 널리 퍼져 온 것이다.
(단, 그렇다고 해서 기독교 박해 행위 자체를 정당화할 생각은 하지 마시길.)

교회사뿐만 아니라 바벨 탑 사건도 비슷한 맥락으로 사람들을 강제로 뿔뿔이 흩어 버린 경우에 속한다. 온 인류가 단일 민족 단일 언어이면 지금처럼 복음 전할 때에도 “아프리카 원주민이나 세종대왕, 이 순신 같은 사람도 다 예수 전해듣지도 못했는데 지옥 갔냐?” 이런 쓸데없는 질문을 받을 일이 없었을 텐데.. 하나님이 왜 그런 비효율적인 자충수를 일부러 두신 걸까?

두 말할 나위도 없이 인류가 단일 언어 단일 체계이면.. 다같이 하나님을 믿는 것보다 다같이 순식간이 부패하고 타락하고 썩어 버리는 게 훨씬 더 빨리 진행되기 때문이다. 다 합당한 이유가 있다.
그 대신, 교회가 태동하던 무렵에는 복음이 빨리 퍼져 나가라고 바벨 탑 사건 때와는 정반대로 언어 장벽을 잠시 극복해 주신 것이다. 그게 바로 '방언 은사'이라는 거다. 성경의 방언은 알아들을 수 있는 외국어이지, 울랄날따따따 잡소리가 절대 아니다.

자 그건 그렇고, 교통 얘기로 오면..
사람이 한 장소에 너무 몰리면 꼼짝달싹 못 하고 아무도 이동을 못 하게 될 뿐만 아니라 압사 사고 등 안전상의 위험도 매우 커진다. 제일 가까운 지하철역을 폐쇄하는 것은 사람들을 더 먼 곳까지 강제로 이동시킴으로써 밀집도를 낮추는 효과를 내며, 우회 환승 통로 역시 환승 승객을 수용할 공간을 확보하여 밀도를 낮춰 준다.

새해에 타종 행사를 하고 나면 종각/시청 일대의 지하철역이 폐쇄되고 불꽃 축제가 있을 때는 여의도 근처의 지하철역이 폐쇄되는 이유가 이로써 설명된다. 지난 여름에 교황이 왔을 때에도 광화문, 시청 근처의 지하철역은 당연히 폐쇄크리를 먹었다. 정말 상상을 초월하는 수의 인파가 몰렸기 때문이다.

서로 가깝고 같은 기간에 동일한 십자형으로 건설된 천호 역은 환승 거리가 짧은 반면, 군자 역은 거리가 일부러 꽤 길게 만들어져 있다. 7호선이 8호선보다 더 수요가 많고 혼잡하기 때문일 것이다.
그런데 천호도 마냥 짧기만 한 건 아니다. 천호에서 8호선을 타는 승객의 압도 다수가 잠실 역에서 내리는데, 환승 지점은 열차의 뒷부분이고 잠실에서 빨리 갈아타려면 암사 방면 열차의 맨 앞까지 이동을 해야 한다. 이것 때문에 사람들이 많이 걷는다. 지하철 8호선이 만들어질 때 이런 것까지 다 지능적으로 고려를 했는지는 모르겠다.

자, 그 다음으로 암호 얘기를 하겠다.
모든 사람들이 정말 정보 엔트로피가 높은 완전 무작위한 숫자· 문자를 암호로 사용한다면..
저런 제약은 오히려 암호 공격자에게 좋은 단서로 작용할 수 있다. 비록 암호 조합 문자열이라는 파이 전체에서 차지하는 비중은 여전히 미미하겠지만, 어쨌든 n글자 이하는 절대로 거들떠보지 않아도 되고, 한 종류의 문자만으로 이뤄진 문자열은 처음부터 탐색 대상에서 제끼면 되니까 말이다.

그런데도 굳이 저런 제약이 존재하는 건.. 불행히도 매우 많은 사람들이 저 작은 표본을 벗어나지 않는 범위에서 암호를 허술하게 만들고, 그게 공격자에게 왕왕 털리기 때문이다.
password, qwerty, q1w2e3, asdf, letmein, love 등...;;
그러니 차라리 그 표본을 명시하고서 사용자들로 하여금 강제로 배제하게 하는 게...
공격자에게 새 발의 피 정도의 단서를 던져주고서 전체 보안은 넘사벽급으로 훨씬 더 강화하는 효과를 낸다. 내 발뒤꿈치를 주고 상대방의 머리를 공격하는 전략 되겠다.

암호라는 건 마치 캡챠만큼이나 서로 모순되는 두 이념을 적당히 잘 충족해야 한다.
캡챠가 사람은 쉽게 알아보고 컴퓨터는 못 알아보는 그림이라면, 암호는 공격자--공격자의 주 도구인 컴퓨터도 포함--는 유추하기 무진장 어려우면서 당사자는 기억하기 쉬워야 한다.
주인이 기억하기 쉬우려면 결국 주인은 자기 개성을 표현하는 문자열을 떠올리게 되는데, 이렇게 되면 암호 공격은 거의 사회과학의 영역으로까지 확장되게 된다.

(더 극단적으로는, 기계적으로 완전 철통같은 암호라 해도 암호를 아는 사람을 돈이나 미인계로 매수한다든지, 혹은 아예 물리적으로 잡아 족침으로써 어이없게 뚫어 버리는 예도 있다.;; brute force 테크닉 그딴 것도 필요 없다. 성경에도 삼손의 지인들이 삼손의 수수께끼를 어떻게 풀었던가? 삿 14 참고)

일반적으로 컴퓨터 소프트웨어의 암호 입력란은 IME가 동작하지 않아서 영문· 숫자 이외의 문자를 입력할 수 없다. 이건 여러 모로 바람직한 조치라 여겨진다.
암호는 무슨 문자를 입력하느냐보다는 결국 무슨 keystroke를 입력했느냐가 더 중요하며, 지금 입력하는 글자가 일반적으로 화면에 보이지 않기 때문에 복잡하게 입력 모드 같은 걸 따질 처지가 못 된다.
또한 암호 입력란에서 IME 같은 별도의 소프트웨어 계층이 동작할 경우, 암호 문자열을 악성 프로그램이 가로채는 보안 문제가 커질 수도 있다.

하지만 이런 점에도 불구하고, 운영체제의 자체 GUI를 쓰지 않는 프로그램 중에는 IME가 동작하는 암호 입력란을 가진 경우도 있다. 굳이 한글로 입력을 안 하고 영문 자판에서 한글 입력을 하면.. 무질서도가 상당히 높은 알파벳 문자열이 생성되기 때문에 외국인 공격자가 알아내는 데는 애로사항이 꽃핀다. 이 사용자가 한국인이라는 단서가 없다면 말이다.
특히 세벌식은 사용자가 매우 적은 데다가 자체적으로 4단의 숫자· 기호까지 일부 활용하기 때문에 이런 보안 면에서 아주 좋다. Mac OS가 악성 코드가 별로 안 들끓는 이유도 딴 거 없고 사용자가 심히 적어서 해커들에게 별로 돈이 안 되기 때문이다. 간단하다.

아무도 모르고 내가 기억하기 쉬운 문자열이라는 특성상, 국가를 막론하고 욕설을 암호로 사용하는 사람도 있다. 뭐, 나쁠 것 전혀 없는 발상이긴 하지만 너무 대중적인(?) 욕설은 공격자들도 이미 다 파악하고 있으니 이 역시 조심해야 한다.

암호는 닥치고 20글자 이상으로 엄청 길면.. 무질서도가 기하급수적으로 치솟는다. 어지간히 단순무식한 형태의 암호라고 해도 엄청나게 긴 것에는 답이 없다. 글자수가 몇 자 늘어날 때마다 0.n초이던 예상 공격 시간이 그야말로 수천, 수만 년 이상으로 뻥튀기된다. 그러니 암호라는 건 pass-word가 아니라 최하 phrase나 sentence 정도의 규모로 만드는 게 좋다.

Microsoft Iphone 내지 언어학의 Colorless green ideas sleep furiously처럼 서로 개연성이 없는 생뚱맞은 단어들(저 문장 자체는 절대 쓰지 말 것! ㅋㅋ), 내가 좋아하는 무리수나 엄청 방대한 소수의 x~y째 자리수의 base64 인코딩 등을 섞으면 공격자가 뚫기 대단히 어려운 암호를 만들어 낼 수 있다. 그리고 까먹었더라도 그 암호를 생성하는 공식을 기억하고 있으니 나중에라도 컴퓨터를 돌려 언제든지 다시 만들 수 있다.

그리고 굳이 저런 식으로 머리를 안 굴리더라도, 본인 같은 사람은 직업 특성상 맨날 숫자와 특수문자와 알파벳이 뒤죽박죽 섞인 문자열을 취급하는 게 일이니... 저런 조건을 모두 만족하는 진짜 암호스러운(cryptic) 암호(password)를 의외로 금방 떠올릴 수 있었다.
요즘은 암호 관리자 전용 앱도 많이 나와 있는데, 이런 식으로 암호 생성기가 같이 연계되고 각 포털 사이트별로 암호 변경 주기를 관리도 해 주는 똑똑한 앱이 있으려나 궁금하다.

터치스크린 입력 방식이 시각 장애인에게 악재인 것만큼이나(점자!)..
PC와는 구조적으로 다른 스마트폰의 문자 입력 방식은 무질서도가 높은 암호를 입력하는 데 악재인 것 같다.
작은 화면에서 알파벳, 숫자, 특수문자를 섞어서 수월하게 입력하는 게 압도적으로 불편하고 까다롭기 때문이다.
물론 그 때문에 거기에는 패턴 제스처 같은 다른 암호 입력 방법도 등장했겠지만, 아무래도 문자 입력만치 보안이 강력하지는 못하다.

끝으로 글을 맺으며 든 생각이 있다.
우리말은 엄연히 다른 개념인 password와 encrypt/cryptic이라는 두 의미가 '암호(화)'에 모두 담겨 있다.
이건 어찌 보면 '다른'에 different와 another가 모두 포함돼 있고, 조사 '과/와'에 and뿐만 아니라 with가 섞여 있으며
단순히 시계의 표시 시각이 이르거나 늦은 것까지 다 '빠르다/느리다'로 표현하는 것만큼이나..
일면 좀 부정확하고 어정쩡하게 들린다.

하지만 한국어만 저렇게 한데 싸잡아 표현하는 건 아닌 듯하다.
또한 '암호'는 정보에 대한 접근 가능 여부 자체를 binary로 통제하는 것이고 '암호화'는 정보에 접근했더라도 해독을 못 하게 하는 것인데.
암호화를 해독하기 위한 암호가 있기도 하니, 목표면에서는 굳이 서로 떼어서 생각할 필요가 없는 비슷한 개념인지도 모르니까 말이다.

Posted by 사무엘

2014/11/04 08:37 2014/11/04 08:37
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1025

오늘은 오랜만에 옛날 GWBASIC 추억 얘기를 또 늘어놓아 보겠다. 예전에 했을 법도 해 보이는데 여러 키워드로 검색을 해 보니 안 한 것 같다. 베이직 얘기를 전문적으로 하는 건 한 2년 만의 일이다.

GWBASIC은 초딩이었던 본인을 프로그래밍의 세계로 이끈 추억의 장난감이다.
본인은 어릴 때부터 컴퓨터가 다른 전자 기기와는 뭔가 차원이 다른 대단한 물건이라는 걸 실감했다.
텔레비전은 오로지 일방적으로 전달만 하는 물건인 반면, 컴퓨터는 내가 직접 명령을 내려서 모니터에 찍히는 글자의 색깔을 바꿀 수 있고, 내가 원하는 화면을 구성할 수 있고, 그림도 그릴 수 있고 소리도 내고 파일로부터 정보를 읽고 쓰면서 뭔가 '나만의 능동적인 세계'를 표현할 수 있기 때문이다.

본인은 딱히 머리가 빨리빨리 잘 돌아간다거나 수학 덕후 최적화 덕후 기질이 있지는 않았다. 단지, 새로운 세계를 표현하는 것 자체에 집착했다. 그래서 정보 올림피아드도 경시에서는 영 재미를 못 보고 그 대신 공모 부문에서 다 입상했다.

GWBASIC은 결과를 즉시 확인할 수 있는 대화식 구조라는 게 굉장히 인상적이다. 행번호에 GOTO문 남발은 굉장히 기괴하고 거추장스러운 개념이긴 하지만, 행번호가 없는 명령은 곧장 실행되고, 행번호가 붙은 명령은 메모리에 등록되어서 나중에 행번호 순으로 한꺼번에 실행된다는 그런 발상은... 참 아무나 할 수 있는 게 아닌 듯하다. RUN, MERGE, DELETE, CHAIN처럼 기억된 프로그램 자체를 확장하거나 바꾸는 명령이 있다는 것도 기괴하고 말이다.

GWBASIC에는 프로그램을 불러오거나 저장하는 명령으로 LOAD, SAVE가 있다. 그런데 GWBASIC은 좀 특이한 게, 여느 프로그래밍 툴처럼 소스 코드를 plain text로 저장하는 게 아니라 내부 바이너리 바이트코드로 저장하는 게 기본 옵션이다. 바이트코드는 같은 소스를 저장했을 때 plain text보다 크기가 작고, 로딩/저장 속도도 더 빠르다는 이점이 있다.

세월이 워낙 많이 흘렀기 때문에 지금은 그 바이트코드의 포맷이 다 알려져서 인터넷에 굴러다닌다. 포맷이 정식으로 공개된 건지 아니면 해커들이 리버스 엔지니어링을 해서 알아낸 건지는 잘 모르겠다.
다른 프로그램에서도 소스 코드를 볼 수 있게 저장하려면 SAVE"파일이름", A라고 뒤에 A를 덧붙여야 한다.

그리고 한편으로 P라는 옵션이 있다. P 옵션은 A와는 반대로 소스 코드를 내부 바이너리 코드로 저장하되 그걸 XOR 기반의 간단한 암호화까지 해서 저장한다.
P 옵션으로 저장된 소스는 불러와서 실행은 가능하지만, LIST로 내용을 열람하거나 코드를 수정할 수 없다. 따라서 비록 GWBASIC에 소스 코드를 EXE로 컴파일하는 기능은 없지만, 다 만든 프로그램을 남에게 인계할 때는 P 옵션으로 저장된 프로그램 파일을 전해 주면 소스 코드 유출을 막을 수 있다. 이론적으로는 말이다.

그러나 그 시절의 GWBASIC에 무슨 전문적인 코드 암호화나 난독화 기능이 있었던 것도 아닌데, 겨우 그 정도의 허접한 보호 기능은 당연히 뚫리고도 남았다.
P 옵션의 암호화 방식도 다 알려져 있고, GWBASIC의 버그를 이용하여 보호 기능 자체를 뚫어 버리는 방법도 존재한다. 이것은 아주 오래 전부터 베이직 프로그래머들 사이에 나돌던 공공연한 비밀 테크닉이었다.

자, 0xFF 문자 2개로 구성된 2바이트짜리 파일(가칭 UN.BAS)을 만든다. 간단하지만 키보드로 바로 입력할 수 없는 문자이긴 한데.. 헥사 에디터를 쓰든지 아니면 GWBASIC 자체를 이용해서 이런 파일을 생성하는 프로그램을 짜서 돌려도 된다.

그 뒤, P 옵션이 붙은 임의의 소스를 LOAD한 뒤, 그 상태에서 UN.BAS를 뒤이어 LOAD하고 나면..
기존 소스의 프로텍션이 풀리고 LIST 열람이 가능해지는 걸 볼 수 있다. 마법과 같은 일이 벌어진다.

사용자 삽입 이미지

그 뿐만이 아니다.
NEW를 입력해서 기억되어 있던 소스를 다 지운 뒤에도 UN.BAS를 LOAD하면.. 방금 지워졌던 소스가 되살아난다.

사용자 삽입 이미지

이게 도대체 어째서 가능할까? (UN.BAS는 0xFF 0xFF일 뿐, 저 소스 코드 자체가 들어있는 거 절대 아님.. -_-)
사실, GWBASIC은 내부적으로 기억하고 있는 바이트코드를 디코딩해서 LIST로 출력하고 소스 코드를 고치는 것을 허용할지 여부를 간단한 boolean 변수 하나로만 판단하는 듯하다. 저장할 때 XOR 인코딩 여부 역시 그 변수로 판단하며, 불러올 때의 XOR 디코딩 여부는 파일 앞부분에 있는 헤더로 판단한다.
그러니, 그 메모리 주소의 값만 바꿔 버리면 프로텍션을 곧바로 풀 수 있다. GWBASIC의 보안 체계는 근본적으로 허술했던 것이다.

그리고 GWBASIC의 고유 파일 포맷에 따르면, 프로텍션이 걸리지 않은 파일은 0xFF로 시작하고, 걸린 파일은 0xFE로 시작한다.
그러므로 0xFF 0xFF 2바이트짜리 파일은 GWBASIC으로 하여금 프로텍션 플래그는 해제하지만 그 뒤에 거의 즉시 파일이 끝나 버리기 때문에 메모리 상의 다른 소스 코드는 건드리지 않는 역할을 하는 듯하다.

원래는 그렇게 파일이 갑작스럽게 끝났을 때의 처리를 GWBASIC이 깔끔하게 해야 하지만 그렇지 못하기 때문에 졸지에 프로텍션만 풀어 버리는 게 가능한 듯하다.
NEW를 한 것까지 어떻게 undo를 하는지까지는 잘 모르겠지만 말이다.

아무튼, 이런 게 오늘날의 소프트웨어 보안 용어로 치자면 일종의 버그이고 exploit이다.
만약 GWBASIC이 Windows, Office, Visual Studio처럼 오늘날까지 살아 있는 제품이고 GWBASIC이 세계 기업들의 돈줄을 좌지우지하는 솔루션이었다면 이건 뭐 당장 긴급 업데이트/패치감이 됐을 것이다. 회사의 자산인 소스 코드가 간단한 해킹으로 죄다 유출되게 생겼으니 말이다.

업데이트 명분으로 맨날 귀가 따갑도록 나오는 “악의적으로 조작된 파일을 열 경우 임의의 코드가 실행... 까지는 아니어도 뭐가 어찌될 수 있는 보안 취약점이 Microsoft 모 제품에서 발견되었습니다” 문구가 가리키는 게 바로 이런 거다.
자그맣게 조작된 파일이 GWBASIC의 저장 프로텍션을 풀어 버리니, 이 꼼수가 보안의 관점에서 오늘날 시사하는 바가 크다고 생각되어서 문득 글을 써 보았다.

옛날에, 286 XT/AT를 갖고 '교육용 컴퓨터' 이러던 시절에는 단색 그래픽 모드에서 동작하는 여러 '교육용 소프트웨어'들도 있었다. CAI라고 들어 보셨는가?
'약수와 배수', '컴퓨터 개론' 같은 타이틀이 있었는데, 개중에는 정말 놀랍게도 GWBASIC으로 개발된 것도 있었다.
물론 런타임인 GWBASIC.EXE와 소스 코드들은 다 파일 이름과 확장자를 교묘하게 바꿨고, 실행은 CAI.BAT라는 파일로 했다.

소스 코드를 열어 보니 당연히 프로텍션이 걸려 있었다. 그러나 본인은 저 테크닉을 이용하여 프로텍션을 풀고 코드를 열람해 보기도 했다. 분량이 상당히 방대했으며 지금 다시 봤으면 여러 재미있는 아이디어들을 발견했을 법도 해 보지만, 본인은 그 당시엔 프로그래밍 실력의 부족으로 인해 그다지 충분한 재미를 못 봤다.

그 느리고 허접한 GWBASIC으로 자체 한글 출력과 그것도 모자라서 입력까지 구현했는데 과연 어떻게 구현했을지가 궁금해지지 않는가?

GWBASIC의 후신인 QBasic이야 고릴라 내지 NIBBLES 같은 예제 게임 프로그램이 MS-DOS 5.0에 같이 곁들여 제공되기도 했다.
순수 GWBASIC으로 근성으로 만들어진 프로그램은? 먼 옛날에 무슨 허접한 자동차 경주 게임 같은 걸 본 게 마지막이다. 각 스테이지의 이름은 태양계의 행성 이름이었는데... 기억하는 분이 있으려나 모르겠다.

상용 제품인 QuickBasic도 GWBASIC의 전통을 이어받아 소스 코드를 자기 고유 포맷으로 저장하는 기능이 있었다. 물론 GWBASIC과 호환되는 포맷은 아니었다. 그리고 축소판인 QBasic은 그런 기능이 없다.
지금은 '큐베'라고 하면 음악 DAW 프로그램인 큐베이스(Cubase)가 먼저 떠오르는 세상이 됐으니 이것도 격세지감이다.

* 그리고 GWBASIC과 관련된 추가 여담.
IBM PC(=도스)용으로 이식된 GWBASIC이야 기본 프롬프트가 Ok이지만, 더 구닥다리 8비트 롬 베이직 같은 걸 보면 프롬프트가 READY인 경우가 있다. 빌 게이츠 아저씨가 GWBASIC을 최초로 만들 때 원래 의도했던 메시지는 ready였다고 한다.
그 사고방식은 오늘날 같은 Windows+GUI 시대에까지도 남아 있다. Excel이나 Visual Studio, 심지어 MFC 기본 어플이.. 내부적으로 더 처리할 메시지가 없이 사용자의 입력만 기다리는 idle 상태로 진입했을 때 아래의 상태 표시줄에 나타나는 메시지는 바로 Ready이며, 우리말로는 그냥 '준비'이다.

그랬는데 ready가 ok로 바뀐 이유는.. 메모리를 단 3 바이트라도 더 확보하기 위해서였다고 한다. 뿌우우... 묵념.

단, Ok도 다 대문자 OK도 아니고, 대문자 O에 소문자 k로 정해진 이유는 본인으로서는 지금도 알 길이 없다.

Posted by 사무엘

2014/10/15 08:23 2014/10/15 08:23
, ,
Response
No Trackback , 6 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1018

회사 업무 때문에 구질구질한 재래식 Windows API 기반 네이티브 데스크톱 프로그램이 아니라 일련의 신문물들을 접할 일이 있었다. 바로 지금까지 말로만 듣던 Windows Phone 플랫폼 개발 때문이었다.

1. Windows 8.1

Windows 8로 넘어가면서 부팅부터 UEFI라는 기술이 도입되면서 뭐가 좀 바뀌었다. 운영체제를 다시 설치하려고 부팅 디스크 탐색 순서를 바꾸려고 해도 BIOS Setup에서 좀 번거로운 절차를 거쳐야 하게 되었다.

Windows Phone 에뮬레이터를 돌리려면 역시 BIOS Setup을 들어가서 기본적으로 꺼져 있는 CPU 가상화 기능을 켜야 하며, OS도 아무거나 쓰면 되는 게 아니라 8.1 Pro 이상급이 반드시 필요하다. Hyper-V 기능이 home급에서는 지원 안 되고 Pro나 엔터프라이즈 급 이상부터만 지원되기 때문이다.
Pro 이상에서만 지원되는 대표적인 기능이 바로 원격 데스크톱 서버 기능인데.. 그런 비슷한 기능이 하나 더 생긴 것이다.

요즘은 스마트폰 CPU도 PC와 별 차이 없을 정도로 굉장한 고사양이기 때문에 에뮬레이션을 위해서는 CPU 차원에서의 온갖 첨단 기능이 덩달아 필요해진 듯하다. 덕분에 이젠 가상 머신에서도 Windows Aero 효과가 돌아가는 세상이 되기도 했다.

Windows 8 이상의 그 각지고 단순해진 GUI를 보면, 비스타/7에 비해 퇴화한 것 같고 화면을 왜 저렇게 만들었나 싶은 생각이 처음에 들었다. Windows 8부터는 아시다시피 고전 테마가 없어지고 화면 scheme은 오로지 "표준 아니면 고대비"로 극도로 단순화됐다.
하지만 이젠 저것도 그럭저럭 적응이 돼 간다. 외형이 단순해진 대신, 단조로움을 덜기 위해 창틀의 색깔이 시시각각 변하는 기능이 생기기도 했고. ㅎㅎ

운영체제를 설치하는 중에 전체 화면에서 배경색이 서서히 알록달록하게 변하는 건, 마치 도스 시절 VGA mode 13h에서 전체 화면 게임 프로그램이 팔레트 스크롤을 하는 것 같은 느낌이 들었다.

2. Visual C++ 2013

외형이 별로 바뀐 게 없고 시간 간격도 2012에 비해 겨우 1년 차이밖에 안 나는지라 변화량을 만만하게 봤었는데, 실제로 써 보니 그렇게 만만하지는 않다. 아기자기한 기능들이 많이 강화되고 좋아졌다.

색깔 scheme이 하양과 검정 말고 '파랑'이 하나 더 생겼는데 파랑은 옛날의 우중충한 2010 분위기를 내는 스타일이어서 개인적으로는 비호감.
운영체제의 기본 컨트롤을 쓰는 게 아니라 모든 걸 자기가 직접 그린다는 특성상, 스크롤 바가 굉장히 똑똑해졌다. cursor가 속한 줄 위치가 스크롤 바에도 언제나 표시되어 나오고, 스크롤 중에 페이지 썸네일을 표시하는 기능도 있다.

옵션(프로젝트 옵션과 프로그램 옵션 모두) 대화상자가 드디어 크기 조절이 가능해졌으며, C++도 코딩 중의 자동 서식과 자동 완성 기능이 제법 강화되었다.

Visual Studio (C++ 포함)는 지난 2005 버전 때부터 Express라는 무료 버전이 정식으로 배포되고 있다. 그래서 예전에는 플랫폼 SDK(= 무료 배포)도 자체적으로 컴파일러를 포함하고 있었는데 그것까지 express 에디션으로 완전히 대체되었다. 상업용 버전과는 달리 2013 Express 버전은 Windows 8용 Metro/Phone 앱만 만들 수 있는 'for Windows' 에디션과, 예전의 재래식 native 프로그램만 만들 수 있는 'for Windows desktop' 에디션이 따로 나뉘었다.

3. C++/CX

드디어 그 이름도 유명한 '요물'을 만져 보게 됐다. 처음에는 단순히 C++을 닷넷용으로 마개조한 Managed C++와 C++/CLI의 후신인줄로만 알았는데 그렇지 않다. C++/CX와 Windows RT API는.NET 내지 C++/CLI하고 무늬는 비슷하지만 내부 구조는 완전히 다르다.
예를 들어, 옛날의 C++/CLI에서는 일반 C++ 개체(new)와 관리되는 새로운(__gc new) 개체는 서로 has-a 관계조차도 맺을 수 없었다. 취급되는 방식이 서로 다른 개체를 다른 개체의 멤버로 가질 수 없었다는 뜻이다. C++/CX는 그런 제약이 없다.

.NET 그쪽 바닥은 전통적인 바이트코드 기반 런타임이지만 C++/CX는 엄연한 네이티브 코드 기반이다. 가장 큰 차이로 후자에는 garbage collector가 없다. ref new로 할당하는 ^ 라는 이상한 포인터가 있긴 하지만 얘는 내부적으로 그냥 레퍼런스 카운팅으로 관리될 뿐이다. .NET과 비슷한 API를 차용하고, C#에서 partial도 가져오고 델리게이트나 boxing 같은 것도 가져왔지만, 내부는 여전히 native라는 게 참 인상적이다. 게다가 이제 퇴물 신세가 됐나 싶던 COM 인터페이스까지 다시 끄집어냈다니!

Visual C++ 200x 시절에만 해도 이제 MS가 C++을 버렸네(특히 MFC!!), 네이티브 코드 시절이 끝났네, 심지어 Windows 차기 버전은 닷넷 같은 바이트코드 기반으로 완전히 새로 만들어진다네 하는 온갖 낭설이 떠돌았는데.. 201x로 와서는 그런 낭설이 완전히 불식된 듯한 느낌이다. MFC는 2008 feature pack 때부터 잘 알다시피 환골탈태하였으며, C++ 언어 자체도 C++11 같은 온갖 확장 규격에 힘입어 한없이 강력하고 복잡해졌다. 거기에다 Windows RT의 코드 기반도 네이티브 코드에 힘을 실어 줬으니 C++은 예나 지금이나 건재한 언어 인증을 하게 됐다.

이런 요물의 등장으로 인해 도리어 .NET의 위상이 굉장히 어중간해졌다. 비슷한 시기에 등장한 GDI+도 너무 금세 버림받고 낙동강 오리알 신세가 됐고 말이다. 얘는 이제 하드웨어 가속 지원도 못 받는다니, 안티앨리어싱이 되는 그래픽이 필요하다면 얄짤없이 Direct2D라도 새로 공부해야 하게 생겼다.

뭐 내부 메커니즘이야 어떻든, 네이티브 코드 C++에서도 delete 따질 필요 없이 new를 막 남발해도 된다는 게 무척 신기하며, 마치 자동차로 치면 수동을 몰다가 자동을 모는 듯한 느낌이다. 하지만 세상에 다 썼으면 반드시 반납을 해야 하는 리소스가 메모리만 있는 건 아닌데, 파일이나 다른 커널 오브젝트들은 어떻게 관리되며 생명 주기가 어떻게 되는지 궁금해질 때도 있다. 참고로, 레퍼런스 카운팅도 GC에 비해서 마냥 가볍고 편리하기만 한 물건은 아니며 약점이 있다.

Windows RT API들은 정말 복잡한 namespace와 클래스, 추상 계층들이 넘쳐난다. 지저분한 Windows API를 정말 허접하게 감싼 MFC 정도를 생각했다가 요즘 프레임워크들을 보면 입이 떡 벌어질 수밖에 없다.
멤버뿐만 아니라 클래스 자체에다가도 public 같은 접근성을 지정할 수 있으며, 더 상속이 안 되게 하는 sealed 속성을 줄 수 있다. 일반 C++에서는 허용되지만 C++/CX에서는 “무슨 클래스에서는 생성자가 public이어서는 안 된다, 데이터 멤버가 뭐여서는 안 된다”는 식의 까다로운 제약도 굉장히 많아서 처음엔 답답함을 느꼈다. (상속이라는 게 없는 언어에다가도 클래스를 제공할 수 있게 하기 위해 들어간 제약이라 함.)

이 기회에 delegate라는 게 뭔지도 다시 살펴보게 되었다. 선언 자체는 C++로 치면 함수 포인터 내지 멤버 함수 포인터에 대한 typedef를 선언하는 것과 비슷한 개념이며, 이놈의 인스턴스는 따로 new로 선언해야 한다. 그때 생성자에다가 다른 함수 명칭라든가 람다 함수를 집어넣어 주면 된다.
람다의 경우 this를 캡처로 주면 자연스럽게 멤버 함수도 대리자가 될 수 있으니 매우 유연하다. 물론 그 유연성은 성능 대가를 치르고 얻어진 것이겠지만 말이다.

Windows RT API에는 비동기적으로 행해지는 동작이 많으며, Concurrency Runtime 라이브러리와 밀접한 관계가 있다. 표준 C++ 라이브러리의 일부인 모양인데 create_task에다가 할 일들을 넣어 주고, 그 일이 반드시 다 끝난 뒤에 수행할 일은 저 함수의 리턴값에다가 .then 메소드를 호출하고 거기에다가 또 람다 형태로 넣으면 된다. 기본적으로 코딩 패턴이 그러하다.
.wait 메소드를 이용해서 동기화를 시켜도 되지만, 이 경우 Windows Phone은 UI 스레드까지 멈춰 버려서 데드락이 발생하는 듯하다. 참고로 C#의 경우 언어 차원에서 await이라는 전용 키워드가 존재한다고 함.

도대체 저 라이브러리는 어떻게 구현되었나? 소스 내부에 CreateThread, WaitForSingleObject 같은 Windows API라도 썼는지 궁금했지만.. 디버거로 내부 추적이 전혀 되지 않을 뿐더러 온갖 암호 같은 복잡한 템플릿은 도저히 분석 가능하지 않았다. 그래서 분석을 포기했다.
아무튼, C++은 람다 함수가 도입되어서 코드를 값으로 집어넣는 게 가능해지고, 이게 템플릿과도 결합하는 바람에 그야말로 예전의 C++에서는 상상도 할 수 없던 무궁무진한 활용이 가능해지긴 했다.

Windows RT 환경에서는 재래식 Windows API는 쓸 수 있는 것도 있고 그럴 수 없는 것도 있다. 이걸 일일이 다 분류하는 것도 마소의 엔지니어들의 입장에서는 엄청 고된 일이었겠다.
실행을 잠깐 멈추는 Sleep 함수도 누락되고 없기 때문에 Concurrency::wait를 써야 한다고 한다. 난 저걸 알기 전에는 이벤트 오브젝트를 만들어서 WaitForSingleObjectEx 함수를 쓰곤 했다.

끝으로, Windows RT의 C++/CX 환경은 네이티브 코드를 표방하는 관계로 재래식 static library와 DLL을 모두 만들어 쓸 수 있다. 단, 불가능하지는 않지만 static library의 경우 링커가 경고를 띄운다. 그건 오로지 같은 C++ 프로젝트에서만 활용 가능하니 재사용성이 크게 떨어지기 때문이라고.

RT 환경에서는 Windows Runtime Component라는 특수한 형태의 DLL을 만들어서 코드를 재사용하는 것이 권장되는 방법이다. Windows RT계의 COM 같은 물건인데, 그렇다고 COM 정도로 문법이 크게 제약되고 경직된 건 아니다. C#으로 표현 가능한 언어 요소들을 모두 표현할 수 있고, 아무래도 원시적인 인클루드와 라이브러리보다는 더 깔끔한 빌드/재사용 시스템인 듯하다.

이런 것들을 경험하고 나니 뭔가 미래에 갔다 온 듯한 느낌이었다.
XAML은 Win32 개발로 치면 rc 파일 같은 것이고
public ref class는 Win32에서 __declspec(dllexport) 같은 건가?

예나 지금이나 완전한 형태의 Windows 프로그램을 만들기 위해서는 무슨 언어든 문법 확장이 불가피했지만 지금은 더 체계적이고 조직적이고 더 노골적으로 하는 듯하다.
시대를 불문하고 불변인 자기만의 전문 영역이 있어야겠지만, 한편으로 최신 시대 조류도 놓치지 말고 따라갈 줄 알아야겠다는 생각이 새삼 들었다.

'독립 개발자 네트워크'를 운영하고 계신 깁뿔 님께서 오래 전에 Windows 8 개발 공부를 하면서 올려 놓으신 팁들을 이 기회에 뒤늦게나마 유용하게 활용했다.

Posted by 사무엘

2014/10/07 08:36 2014/10/07 08:36
, ,
Response
No Trackback , 6 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1015

예전에 에디트 컨트롤에 대해서 글을 한번 쓴 적이 있었는데 그것들 말고도 또 재미있는 이야깃거리가 많아서 글을 추가로 올리게 됐다.

1.
Windows의 에디트 컨트롤에는 ES_AUTOHSCROLL, ES_AUTOVSCROLL이라는 옵션이 있어서 이 옵션이 없으면 에디트 컨트롤은 가로나 세로로 스크롤이 되지 않는다. 그리고 스크롤만 안 되는 게 아니라 지금 화면 영역을 벗어나는 크기로는 텍스트가 입력 자체가 전혀 되지 않게 된다. 가령, 가변폭 글꼴을 쓴다면 W는 몇 개 입력 못 하지만 i는 꽤 많이 집어넣을 수 있다.

차라리 W든 i든 글자 수 자체에 대한 제약을 거는 거라면 모를까, 저런 제약 기능이 실생활에서 쓸 일이 있는지는 본인은 좀 회의적이다. 한 줄짜리 에디트 컨트롤이 메모리 상의 글자 수도 아니고 픽셀 길이가 초과했는데 스크롤이 안 되는 경우는 거의 찾을 수 없기 때문이다. (물론, 글자 수에 제약을 거는 방법은 EM_SETLIMITTEXT라고 방법이 따로 있긴 하다)

2.
에디트 컨트롤은 잘 알다시피 자체적으로 Ctrl+C, X, V 글쇠를 처리하여 텍스트에 대한 Copy/Cut/Paste 기능을 제공한다. 그런데 운영체제나 프로그램에 따라서는 "텍스트 전체 선택"을 의미하는 Ctrl+A도 지원되는 것 같기도 하고 안 되는 것 같기도 하다. 도대체 어찌 된 일일까?

실상은 이러하다. 내가 여러 조건을 달리하여 실험을 해 보니, 공용 컨트롤 6.0이 제공하는 새로운 에디트 컨트롤만이 single-line 방식에 한해서 Ctrl+A도 자체 처리한다. 나머지 일반 에디트나 multi-line 에디트는 아마 호환성 차원에서 이를 지원하지 않는다.

물론, 응용 프로그램이 Ctrl+A를 액셀러레이터에다 등록해서 자체적으로 에디트 컨트롤에다가 EM_SETSEL(0, -1)을 날려 준다면 어디서나 Ctrl+A가 동작하게 된다. 컨트롤이 아니라 그 컨트롤을 사용하는 응용 프로그램이 직접 Ctrl+A를 구현한 대표적인 예는 바로 메모장이다.

3.
에디트 컨트롤은 자신이 키보드 포커스를 받으면 텍스트 전체를 선택해 놓는다. 사용자가 기존 텍스트를 완전히 무시하고 입력을 새로 시작할지, 아니면 기존 입력을 그대로 유지하거나 살짝만 고칠지를 선택 가능하게 하자는 차원에서이다.
그런데 대화상자에서 굳이 tab 키로 포커스를 바꿨을 때뿐만이 아니라 Alt+? 액셀러레이터를 눌렀을 때도 이 동작이 일어나며, 심지어 지금 포커스를 받고 있는 동일한 컨트롤을 Alt+?로 다시 선택했을 때도 동일한 동작이 일어난다. 이것은 WM_SETFOCUS나 WM_KILLFOCUS하고는 관계가 없는 동작인데 어떻게 이런 일이 가능할까?

정답은 에디트 컨트롤이 WM_GETDLGCODE 메시지에 대해서 DLGC_HASSETSEL 비트 플래그를 되돌리기 때문이다.
대화상자는 자기 밑에 있는 컨트롤들에 대해서 이런 세부적인 메시지를 보내어 정보를 파악하는데, 저 플래그가 있는 컨트롤은 문자열 입력란으로 간주하여 액셀러레이터 키를 받았을 때도 EM_SETSEL 메시지를 보내 준다. 저 플래그만 쓰면, 운영체제의 표준 에디트 컨트롤이 아니어도 똑같은 동작을 하는 컨트롤을 얼마든지 만들 수 있다. <날개셋> 한글 입력기의 자체 에디트 컨트롤도 당연히 이 방식을 따랐다.

Posted by 사무엘

2014/09/20 08:38 2014/09/20 08:38
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1009

1. WM_QUERYDRAGICON 메시지

제목에 언급돼 있는 저 메시지는 도대체 뭘 하는 물건일까?
얘는 20여 년 전에 Winows 95가 등장한 이래로 쓸 일이 사실상 전혀 없어진 잉여이다.

그 주된 이유로는 첫째, 그때부터 minimized icon이라는 개념 자체가 운영체제에서 완전히 없어졌기 때문이다.
95 이래로 바탕 화면에는 '내 컴퓨터'나 '휴지통' 같은 걸 제외하면, 바탕 화면이라는 디렉터리에 있는 파일들만이 표시된다. 자주 쓰는 프로그램의 바로가기 정도나 바탕 화면에 표시되며 그것들도 엄밀히 말해서는 그 디렉터리에 있는 파일의 일종인 것이다.

최소화된 프로그램은 작업 표시줄의 제목 말고는 화면에 아무것도 보이지 않는다. 시작 메뉴와 작업 표시줄을 구동하는 explorer 셸 자체가 죽었다면 최소화된 프로그램이 진짜로 제목 한 줄만 달랑 보이는 최소화 상태로 있을 수 있지만, 이건 운영체제가 완전 막장이 됐을 때에나 발생하는 상황이며, 그냥 그 제목 텍스트를 드래그하면 되지 별도의 드래그용 아이콘이 필요하지는 않다.

둘째로, WM_SETICON / WM_GETICON이 그나마 남아 있던 아이콘 관련 기능을 완벽하게 대체해 버렸기 때문이다.
클래스를 등록하던 시절에 대표 아이콘이 지정되지 않았던 윈도우라 하더라도 가끔은 외형에 별도의 custom 아이콘이 필요할 때가 있다. 대화상자 단독으로 달랑 실행되는 프로그램이 대표적인 예다. 대화상자는 윈도우 프로시저는 거의 언제나 우리가 지정한 custom 버전이 쓰이지만, 그 윈도우 자체의 클래스 등록은 우리가 한 게 아니기 때문이다.

Windows 3.x 시절에는 창의 아이콘이 표시될 때가 최소화됐을 때 정도밖에 없지만, 95부터는 창 제목 왼쪽의 시스템 메뉴가 있는 곳에 창의 아이콘이 언제나 표시되어 있다. 그렇기 때문에 클래스 아이콘과 다른 아이콘을 별도로 공급하는 것은 운영체제가 나중에 응용 프로그램에다가 메시지를 보내는 형태가 아니라, 응용 프로그램이 사전에 운영체제에다 메시지를 보내는 것으로 디자인이 바뀌었다. 그 변경의 산물이 바로 WM_SETICON. 이 아이콘이 대외적으로 표시되며 심지어 Alt+Tab을 누른 동안 프로그램 리스트에도 뜨게 된다.

그럼에도 불구하고 MSDN과 구글 따위를 뒤져 보면, 이 메시지에 대해서는 20년도 더 전에나 유효하던 낡은 설명만이 기계적으로 그대로 소개되어 있으며, 이 정보는 오늘날 outdated됐다는 말은 어디에도 없다. 심지어 Visual C++ 2012의 MFC 마법사에서 대화상자 기반 응용 프로그램을 만들면, CDialog(Ex)의 파생 클래스는 저 메시지에 대한 핸들러도 여전히 참 친절하게도 만들어 준다. 뭐지 이건..?

2. 잉여 WM_SIZE 파라메터

잉여 요소가 의외의 가까운 곳에 또 있다.
WM_SIZE야 Windows 프로그래머치고 모르는 사람이 있을 수가 없는데.. wParam에는 최소화/최대화와 관련된 부가 정보가 따라온다. 최소화되었다면 SIZE_MINIMIZED가, 최소화되었다면 SIZE_MAXIMIZED가 오며, 그 밖의 일반 상황에서는 SIZE_RESTORED (0)가 된다. 딱 이 정도만 알고 있으면 된다.

그런데
SIZE_MAXHIDE: Message is sent to all pop-up windows when some other window is maximized.
SIZE_MAXSHOW: Message is sent to all pop-up windows when some other window has been restored to its former size.

라고 문서화돼 있는 이 값이 온 걸 본 적 있으신 프로그래머는 한번 손 들어 보시길..
저 조건을 최대한 만들어서 디버거 붙이거나 Spy++로 확인해 봐도 저런 건 좀체 안 온다.
어떤 프로그램 창이 최대화됐거나 해제됐다고 해서 다른 프로그램 창에 저 메시지가 올 거라고 생각한다면 경기도 오산이다.

구글, MSDN 다 뒤져도.. 저 기계적인 설명 말고 다른 용례는 안 나온다. 외국의 포럼에서 딱 하나 질문이 올라온 게 있긴 한데, 딱히 답변 없다. (☞ 링크 클릭)

Windows 운영체제의 레거시들 분석에는 세계 톱급의 전문가라 할 수 있는 레이몬드 챈 아저씨의 블로그, MFC와 Windows GUI 프로그래밍에서 한 가닥 했던 Paul DiLascia 등.. 거기에 설명이 없으면 아무데도 없는 거다.;;
나도 진지하게 굉장히 궁금하다. 저 설명과 매크로 상수값은 그저 잉여인지를? WINE 같은 데서는 저게 실제로 구현돼 있을까?

Posted by 사무엘

2014/08/22 08:21 2014/08/22 08:21
,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/998

예전에도 몇 차례 얘기했듯이 비주얼 C++은 지금까지 내 인생에서 가장 재미있는 장난감이요, 친구요, 자아실현 매체요, 생계 수단 역할을 톡톡이 해 왔다.

비주얼 C++은 여느 프로그래밍 툴과는 다르게 뭐랄까, standalone, independent이고 자가생성이 가능하다. 쉽게 말해서 비주얼 C++ 자신과 같은 레벨의 컴파일러/런타임/IDE 같은 프로그램을 비주얼 C++로 또 만들 수 있다는 뜻이다. 실제로 마소에서 비주얼 C++은 이전 버전의 비주얼 C++로 만들고 있기도 하고. 이렇듯, 이 툴은 가장 배우기 어렵지만 가장 강력하고 군더더기 없는 프로그램을 만들 수 있다.

2014년 현재, 난 한 컴퓨터에 다음과 같은 세 버전을 깔아 놓는다. 제각기 필요와 쓸모가 있기 때문이다.

1. 2003

  • 2010에서 새로 도입된 Help Viewer가 완전 거지 같아서.. 단순 윈도 API나 MFC 레퍼런스를 조회하는 덴 200x 구버전 document explorer 기반의 msdn이 짱이다. (1) 색인이 처음에 뜨는 데 시간이 너무 오래 걸리는 것, (2) 가끔 목차/색인을 클릭해도 해당 항목 문서가 안 나타나는 것--정확히는 수 초 뒤에 한참 뒤에 뜸.. 이 두 버그 때문에 학을 뗐다. (단, 2012 이후의 Help Viewer 2.0은 불편하던 게 좀 개선된 거 같기도 하고..)
  • 2003은 MFC가 지금처럼 말도 안 되게 bloat되기 전이며, 굉장한 legacy 운영체제에도 돌아가는 바이너리를 만들 수 있는 버전이다. <날개셋> 타자연습을 여전히 10년 전의 구닥다리 컴파일러로 빌드하는 이유가 이것 때문이다.
  • 다만 2003은 IDE가 빌드 내지 리소스 편집 중에 잘 뻗는 편이고(불안정!) Vista 이후 OS에서는 일부 기능이 충돌도 함. 조심해서 써야 한다.

2. 2010

  • 닷넷 이래로 Visual Studio가 기본 제공하던 msi 설치/배포 프로젝트 기능이 2012에서 갑자기 없어져 버린 관계로, 2010을 도저히 제거할 수가 없게 됐다. 대체품이라는 InstallShield 번들 에디션은 어마어마한 덩치와 복잡한 사용법 때문에 곧바로 gg 치고 언인스톨해 버렸다.
  • 또한 <날개셋> 한글 입력기는 빌드와 관련된 특이한 이슈 때문에 2012가 아닌 2010 컴파일러 툴체인을 사용하고 있다.
  • 다만, 2010은 IDE의 비주얼이 역대 VC++ 역사상 제일 구리고 우중충 칙칙하고 안 좋았다. -_-;;

3. 2012

난 201x가 다음과 같은 점에서 마음에 든다. (1) 크게 강화된 인텔리센스 엔진 (2) 람다 같은 C++ 최신 문법 (3) 빌드나 리소스 편집 중에 IDE가 이제 거의 뻗지 않음
2012는 이를 바탕으로 2010보다 훨씬 더 깔끔한 GUI에, 신택스 컬러링도 훨씬 더 강화되어 몹시 마음에 든다. 몇 가지 크리티컬만 없었으면 2012가 2010을 완전히 대체할 수도 있었을 텐데. ㅜ.ㅜ
다만 2012 얘만 꼭 남겨 둘 이유 역시 없기 때문에 이것보다 더 최신 버전이 나오면 그걸로 대체할 수도 있다. 즉, 2012는 2003/2010과는 달리 고정 보존 상태는 아니다.

위와는 달리, 보존 대상에서 제외되고 안 쓰는 버전은 다음과 같다.

1. 6.0

VC6은 그야말로 개발툴계의 IE6이나 마찬가지다. 출시 시기는 다르지만 공교롭게도 버전 번호도 동일하고 말이다. IE가 윈도 비스타의 출시 지연 때문에 6 이후로 5년 가까이 버전업이 없었다면, VC는 닷넷이 첫 개발되느라 4년 가까이 6 이후로 버전업이 없었다. 그 뒤 지나치게 오랫동안 현역을 뛰어 왔다.

웹 개발자들이 제발 IE6 좀 퇴출시키자고 캠페인 하는 것만큼이나 PC 클라이언트 개발자들은 업계에서 VC6 좀 퇴출시키자고 캠페인이라도 해야 할 판이다. 단지, IE는 모든 PC 사용자들이 쓰는 웹브라우저인 반면, VC는 극소수 프로그래머만이 쓰는 개발툴이라는 점이 다르다.

VC6은 이제 해도 해도 너무하다 싶을 정도로 심하게 후지고 낡았다. IDE가 IME-aware하지도 않고, 특히 한글 윈도에서는 기본 글꼴이 윈도 3.1 스타일의 완전 추레한 System으로 나옴! 인텔리센스는 지금에 비하면 완전 안습 크리 수준이고. 최신 C++ 표준이나 멀티코어 같은 건 아웃 오브 안중이다.

VC6이 아니면 도저히 빌드시킬 수 없는 비표준 코드가 이미 수십만 줄 이상 작성되어 버려서 도저히 수습을 못 할 지경이 된, 한 20년 묵은 불가피한 프로젝트가 아니라면 아직까지 VC6을 고집할 이유란 없어야 정상일 것이다. for문 변수 scope 정도는 후대의 컴파일러로도 옵션을 바꿔서 수용시킬 수 있을 텐데.

굳이 장점을 찾자면, VC6은 생성되는 바이너리가 운영체제의 MSVCRT와 MFC42를 직통 지원한다는 점이 매우 유리하다. 그러나 이것도 어차피 64비트는 지원 안 하기 때문에 장점이 반쪽짜리 이하로 의미를 크게 상실한다.

2. 2005

MS 오피스 2003이 아닌 독자 GUI 비주얼을 선택한 첫 버전 되시겠다. (VC 2005가 오피스 2003 같은 시퍼런 비주얼 기반이었다면? 상상만 해도 ㅎㅎ)
난 얘는 일단 sp1과 운영체제 패치를 설치하는 시간이 2005 자체를 설치하는 데 걸리는 시간보다 더 길어서 인상이 매우 안 좋다. 게다가 CRT/MFC DLL 배포 방식도 구리게 바뀌었고. 장점은 어차피 (1) 2003이나(msdn 등) (2) 이후 버전(64비트 지원 등)에 다 포함돼 있기 때문에 굳이 얘가 필요하진 않다. out.

3. 2008

2005보다는 훨씬 더 괜찮은 물건이고 쓸 만하다. 그리고 은은한 연보라색 톤(비스타/7 기준)의 IDE 외형은 역대 버전들 중 가장 깔끔하고 괜찮았다고 생각한다.
200x 중에서는 가장 훌륭했지만, 역시 얘만 보존해야 할 필요는 존재하지 않는다. 플러스 팩의 등장과 함께 MFC가 완전 bloatware로 바뀌어 버렸고, CRT/MFC DLL 배포 방식은 여전히 아쉬운 점이다.

위의 두 카테고리 말고 본인이 special case로 예우하는 골동품 버전이 있는데, 그건 6.0보다도 더 옛날 버전인 4.2이다. mfc42의 원조인 바로 그 버전이다.
본인이 난생 처음으로 구경한 비주얼 C++ 버전이어서 애착이 간다.

Posted by 사무엘

2014/08/07 08:28 2014/08/07 08:28
, , ,
Response
No Trackback , 5 Comments
RSS :
http://moogi.new21.org/tc/rss/response/993

C/C++에서 구조체나 클래스는 통상적으로 global scope에서 선언되거나 기껏해야 다른 클래스 내지 namespace의 내부에서 선언된다. 즉, 어차피 비실행문들만 있는 곳에서 선언되는 편이다.
그러나 실행문으로 이뤄져 있는 함수 안에서 이들을 새로 선언하는 것도 문법적으로 가능하다. 다시 말해, 변수를 선언하는 것뿐만 아니라 그 변수들의 type을 결정하는 구조체나 클래스를 즉석에서 선언해 쓰는 것도 가능하다는 뜻이다.

다른 곳에서 두고두고 재사용하는 구조체가 아니라 함수 한 곳에서 튜플 같은 형태로 잠깐만 사용하고 마는 구조체라면, 이런 식으로 함수 안에서 간단히 선언해서 사용하면 좋다. 하긴, 그러고 보니 struct, class, union, enum뿐만 아니라 typedef도 실행문과 비실행문 문맥에서 모두 사용 가능한 물건이다.

함수 안에서 클래스 같은 걸 따로 선언하는 건 C#에서는 가능하지 않으며 C++만의 전유물인 듯하다.
이렇게 함수에서 선언된 자료형은 유효 범위도 마치 지역변수처럼 그 함수 안으로 완전 local하게 한정된다. 그래서 각종 IDE 같은 데에 명칭이 뜨지도 않는다. 지금부터는 이와 관련해서 좀 더 기괴한 이야기들을 풀어 보겠다.

1. 무명 자료형

C/C++에는 '이름 없는' 구조체/클래스/공용체 따위의 개체가 있을 수 있다. '이름 없는' 함수 그 자체만의 선언은 지원되지 않아서 함수형 프로그래밍 패러다임이 도입된 C++0x 이후에서야 람다와 함께 등장한 반면, 이름 없는 복합 자료형이라는 개념은 있었던 것이다.

class {
public:
    int x,y,z;
} obj;

C#이나 자바 스타일이라면 상상도 할 수 없는 일이겠지만, C/C++은 자료형의 선언과 해당 자료형에 속하는 변수의 선언을 동시에 할 수 있다. class OBJ { ... } a,b,c; 도 OBJ a,b,c;나 심지어 int a,b,c;와 개념적으로 같다.
class, struct, enum, union 등을 선언한 뒤에는 닫는 중괄호 다음에 세미콜론을 반드시 붙여야 하는 이유가 바로 이 때문이다.

그런데 이름 없는 자료형은 자료형의 선언과 함께 변수 선언도 같이 해 주는 게 선택이 아닌 '필수'라는 차이가 있다. 그도 그럴 것이, 얘는 이름이 없는 일회용 자료형인 고로 그 자료형을 선언하는 구문이 끝난 뒤에는 그걸 지칭할 방법이 없기 때문이다. 변수가 단 하나라도 같이 선언돼 있어야 나중에 C++11의 auto 라도 써서 그것과 동일한 자료형의 변수를 추가로 만들 수 있을 것이다.

이런 무명 자료형이라는 개념은 대개 한 자료구조 내부에서 구조체와 공용체를 섞어 가며 쓸 때 유용하지만, 그렇잖아도 일회용 성격이 강한 local 자료형에서도 더욱 의미가 있다. 굳이 이름을 생각할 필요 없이 내가 생각하는 복합 자료형을 간단하게 만들어서 쓰게 해 주기 때문이다.
물론 local뿐만 아니라 global scope에서도 무명 자료형을 얼마든지 선언해서 쓸 수 있다. C/C++의 오묘한 면모 중 하나이다.

2. 함수 안에 함수

C/C++은 복합 자료형은 앞서 살펴보았듯이 무명으로 선언할 수 있고, 그 안에 또 다른 복합 자료형을 nested된 형태로 선언하고 집어넣을 수 있다. 그러나 실행되는 코드의 집합인 함수를 그렇게 일종의 값처럼 자유자재로 다룰 수 있지는 않았다.

함수 자체를 다른 함수에다가 전달하는 것은 그나마 함수 포인터가 있으니 불가능하지는 않지만, 그건 자료형, 함수명 등에 대한 작명이 필요하며 기계 중심적이고 융통성이 부족했다. 또한 함수 안에다가 또 일회용으로 간단히 쓰고 마는 함수를 잠깐 선언하는 것도 가능치 않아서 global/class scope 차원에서의 선언이 필요했다. 남는 건 매크로 함수밖에 없지만 이게 얼마나 구조적으로 허접한 물건인지는 역시 설명이 필요하지 않는 수준이고.

void func()
{
    void simple_func(int x) { }

    simple_func(0);
    simple_func(1);
}

nested function은 C와 파스칼의 큰 차이 중 하나이기도 했다. 파스칼은 지원하지만 C/C++ 계열은 지원하지 않았기 때문이다. 마치 가변 길이 배열만큼이나 언어 차원에서 결코 지원되지 않을 금기 봉인 사항이기라도 한가 궁금하다. 다만, 옛날에 gcc던가 극소수 C 컴파일러에서 확장 옵션을 통해서 nested function을 지원하는 걸 본 것 같다.

물론, 중첩 함수를 써서 할 수 있는 일은 중첩 함수라는 개념이 없이도 완전히 똑같이 할 수 있기 때문에 상호 등가 교환이 가능하다. 마치 클래스에서 public과 private 구분을 해 주든, 아니면 전부 싸잡아 public인 struct로 코드를 작성하든.. 이것은 코드의 유지 관리의 편의성 내지 정보 은닉하고만 관계가 있지 프로그래밍 언어의 구조적인 계산 능력과는 무관한 것하고 같은 맥락이다. 그래서 C/C++은 nested 함수라는 개념을 도입하지 않은 듯하다. 정수 타입에 subrange 같은 개념도 없을 정도이니 뭐~

지금이야 람다 덕분에 함수 안에 함수의 선언이 사실상 가능해졌다. 캡처 같은 새로운 개념도 같이 도입됐다. 하지만 이건 일반적인 함수와 개념적으로 같은 물건은 아니다.
C++에서는 (1) 중첩 namespace 안에 들어있는 함수가 얼추 비슷한 개념일 수 있으며, 이것 말고도 좀 더 직접적으로 함수 안에 함수를 만드는 것이 편법 우회 경로로 가능하다. (2) 바로 함수 안에 클래스를 선언하고 멤버 함수를 정의하는 것이다. 이런 식으로.

int main(int argc, char* argv[])
{
    class A {
    public:
        static void Func() { puts("function inside function"); }
    };
    A::Func();
    return 0;
}

특히 static 함수는 this 포인터를 사용하지도 않으니 진짜로 일반 함수와 다를 바가 없다.
함수 안에다 구조체를 정의하는 것으로도 모자라서 완전한 형태의 클래스를 정의하고 멤버 함수를 정의하는 것까지도 가능하다니 놀랍지 않은지?

단, 이런 지역 클래스에서 멤버 함수를 선언할 때는 논리적으로 당연한 제약이 하나 걸린다. 함수의 몸체는 반드시 그 클래스 안에서 저렇게 정의되어야 한다. 안 그러면 아까 무명 자료형에서 변수 선언을 바로 안 해 줄 때처럼 경고가 뜬다.

비주얼 C++의 경우 일단 C4822 경고만 뜨고 그걸 실제로 호출까지 한 경우 링크 에러가 났지만, 요즘은 그 즉시 C3640 에러도 같이 나오는 듯. 링크 에러가 더 친절하게 컴파일 에러로 바뀌었다.
클래스의 밖인 그 함수 몸체 안에서 또 void A::Func() { } 이런 식으로 함수 몸체를 따로 정의하는 건 문법적으로 허용되지 않기 때문이다.

또한, 이런 이유로 인해, 지역 클래스는 static 멤버 함수는 가질 수 있는 반면 static 멤버 변수(=데이터)는 가질 수 없다.
그건 함수 안의 일반 static 변수와 같은 취급을 받으려나 궁금했는데, 만들어 보니 그건 언어 문법 차원에서 허용되지 않으며 곧바로 컴파일 에러가 난다. static const도 허용되지 않는다.

그러고 보니 이름 없는 클래스도 static 멤버 변수를 사실상 가질 수 없을 듯하다. 사실, 이름 없는 클래스에다가 그런 것까지 바라는 것 자체가 변태 도둑놈 심보이긴 하다. ㅎㅎ
멤버 함수야 몸체를 클래스의 선언부 안에다 강제로 집어넣는 식으로 정의할 수 있지만 static 변수는 결국 클래스 밖에서 따로 정의를 해야 하는데, 클래스 이름이 없으니 정의를 할 수 없어서 링크 에러가 나기 때문이다.
이거 정말 복잡한 문제다. C++이 C#/Java하고는 다른 독특 기괴한 면모가 이런 데서 또 발견된다.

Posted by 사무엘

2014/07/25 08:32 2014/07/25 08:32
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/988

라벨, 에디트, 리스트 박스, 콤보 박스, 일반 버튼, 라디오/체크 박스.
이름만 들어도 친숙한 이 물건들은 응용 프로그램의 대화상자에 들어가는 그야말로 필수 GUI 구성요소들이다.

여기에다 리스트 컨트롤, 트리 컨트롤 같은 더 고급스러운 공용 컨트롤까지 있으니 실무에서는 사실 99%에 가까운 일은 기존 컨트롤만으로 다 해치울 수 있다.
사소하게 마음에 안 들고 동작 방식을 좀 고쳐야 하는 건, (1) 이들이 제공하는 owner-draw 옵션(주로 외형 외주) 내지 일부 메시지에 대한 (2) 윈도우 프로시저의 서브클래싱으로 customize 가능하다.

그러나 아주 가끔은, 기존 컨트롤과는 동작 방식이 완전히 다른 새로운 윈도우를 내가 머리부터 발끝까지 직접 구현하여 대화상자에다 집어넣어야 하는 경우가 있다. 기존 컨트롤을 약간만 고치는 정도로는 성이 안 찬다.

본인이 개발한 <날개셋> 한글 입력기의 GUI를 보면 이런 custom 컨트롤들이 적지 않게 보인다. 당장 비트맵 글꼴을 사용하는 자체 에디트 컨트롤은 편집기의 클라이언트 영역뿐만 아니라 대화상자에서 입력란으로도 쓰인다. 그리고 한글 낱자를 뽑아 오는 독특한 스타일의 콤보 박스라든가 문자표 리스트, 글쇠배열 편집 윈도우도 다 자체 개발한 custom 컨트롤이다.

꼭 그렇게까지 특이한 용도의 윈도우가 아니더라도.. 이걸 생각해 보자.
자체적인 키보드 포커스를 받으며, 화살표 key나 마우스 휠로 2차원적인 스크롤이 가능한 형태로 그림 같은 owner-draw 컨텐츠를 출력하는 컨트롤.
쉽게 말해 MFC로 치면 CScrollView에 해당하는 컨트롤이다. 특이할 것 없는 아주 기본적인 기능임에도 불구하고 기존 컨트롤의 서브클래싱만으로는 구현 가능하지 않다.

키보드 포커스를 받지 않으며 스크롤 같은 것도 없이 고정적인 owner draw 그림만을 화면에 달랑 그리는 거라면 쉽다. 기성 컨트롤인 STATIC 윈도우에다 owner draw 스타일을 줘서 WM_DRAWITEM 메시지를 처리하거나, 아예 서브클래싱을 해서 WM_PAINT 메시지를 통째로 가로채면 된다.

그러나 스스로 키보드와 마우스 입력을 처리하는 윈도우라면 독립된 형태로 자체 구현이 불가피하다. 여러 메시지들에 대한 동작을 인위로 고쳐야 하기 때문이다.
<날개셋> 편집기에서 화면 인쇄를 시켰을 때 화면 인쇄 결과를 출력하는 윈도우가 바로 이런 용도로 만들어진 custom control이다.

custom control을 MFC를 써서 개발한다면 응당 CWnd에서 파생된 새로운 클래스를 떠올리게 될 것이다. 이 C++ 클래스는 자신만의 윈도우 클래스 이름을 갖고 있을 것이고 자신을 운영체제에다 등록하는(RegisterClass 호출) 함수도 static 형태로 갖추고 있어야 할 것이다. (응용 프로그램이 실행 초기에 호출함)
그리고 대화상자 리소스에는 그 클래스 이름을 지정해 놓은 custom 컨트롤을 마음껏 생성해 놓는다.

그런데 대화상자의 컨트롤들은 내가 CWnd::Create를 호출하여 만드는 게 아니라 운영체제의 대화상자 관련 함수들이 CreateWindowEx에다가 윈도우 클래스 이름만 달랑 줘서 만들어진다. 이렇게 C++ 언어 체계의 통제를 받지 않고 밖에서 만들어진 HWND에다가 MFC 기반의 C++ 개체는 어떤 메커니즘을 통해 상호 연결시켜야 할까?

단순히 CWnd::FromHandle 함수 같은 걸로 임시 CWnd만 달랑 생성해서 연결하는 게 아니다. 이 custom 컨트롤은 구현체 클래스가 존재하니, 그냥 CWnd가 아니라 내가 만든 전용 CWnd 파생 클래스가 정확히 연결돼야 한다. 그렇게 해야 응용 프로그램 해당 윈도우끼리는 type-safe하지 않고 불편한 메시지를 쓸 필요 없이 C++ 멤버 함수/변수에 직통으로 가능해지니 좋다.

연결하는 방법은 크게 두 가지가 있다. 먼저, C++ 클래스의 생명 주기를 윈도우 자체의 생명 주기와 일치시키는 것이다. 만드는 윈도우 클래스에다가 윈도우 프로시저를 아래와 같이.. MFC의 AfxWndProc와 거의 똑같은 형태로 작성해 준다.

LRESULT CALLBACK CHeapCtrl::_MyWndProc(HWND hWnd, UINT msg, WPARAM w, LPARAM l)
{
    CWnd *pWnd = CWnd::FromHandlePermanent(hWnd);
    if (pWnd==NULL) { pWnd=new CHeapCtrl; pWnd->Attach(hWnd); }
    return AfxCallWndProc(pWnd, hWnd, msg, w, l);
}

이 개체는 언제나 new 연산자를 통해 heap에만 생성된다. 그러므로 PostNcDestroy 함수를 다음과 같이 구현하여 윈도우와 함께 C++ 개체도 같이 소멸되게 해 줘야 한다.

void CHeapCtrl::PostNcDestroy()
{
    delete this;
}

또한 대화상자 내부에서는 CHeapCtrl을 언제나 포인터를 통해 접근해야 한다.

CHeapCtrl *p = DYNAMIC_DOWNCAST(CHeapCtrl, GetDlgItem(IDC_MYCONTROL));

둘째 방법은.. 대화상자이니까 가능한 더 간단한 방법이다. 대화상자 클래스에다가 해당 컨트롤 클래스의 개체를 선언한다. 윈도우 클래스를 등록할 때 WNDCLASS 구조체에다가 윈도우 프로시저는 그냥 쿨하게 DefWindowProc이라고 주면 된다.

CStackCtrl m_wndCustomCtrl;

그리고 윈도우와 C++ 개체를 그냥 이렇게 컨트롤 변수로 연결해 버린다.

void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_MYCONTROL, m_wndCustomCtrl);
}

이 경우, CStackCtrl은 스택, 아니 최소한 대화상자 클래스와 동일한 메모리에 생성된다. 그래서 개체를 포인터를 거치지 않고 더 간편하게 접근할 수 있을 뿐만 아니라, 대화상자 같은 윈도우 개체가 사라진 뒤에도 C++ 객체가 갖고 있던 내부 정보에 접근을 할 수 있다. PostNcDestroy 처리가 필요하지 않음은 두 말할 나위도 없고. 여러 모로 더 속 편하다.

윈도우 클래스상으로 이 윈도우는 프로시저가 아무 특수한 일도 하지 않는 DefWindowProc로 지정되어 있지만, DDX_Control 함수는 윈도우 프로시저를 서브클래싱하고 저 윈도우의 핸들을 우리가 준 CStackCtrl에다가 연결해 준다. 그래서 각종 메시지가 발생했을 때 CStackCtrl의 메시지 맵에 등록된 핸들러 함수가 호출되는 것이다.

윈도우 클래스와 MFC 클래스를 연결한 건 시작일 뿐이고 본격적인 코딩은 지금부터이다. 그야말로 산 넘어 산이다.
WM_PAINT와 필요하다면 WM_SIZE를 처리해야 할 것이고, 키보드 포커스를 받는 물건으로 계획을 했으니 WM_GETDLGCODE를 잡아서 스크롤을 위해 최소한 DLGC_WANTARROWS 정도는 되돌려야 할 것이다. 다른 단축키 같은 걸 추가로 인식하려면 DLGC_WANTCHARS도 필요할 테고.

스크롤이 되는 윈도우라면 요즘은 마우스 휠 인식은 필수다. WM_MOUSEWHEEL을 처리해야 한다. SystemParametersInfo(SPI_GETWHEELSCROLLLINES, ...) 을 호출하여 마우스 휠의 움직임 단위를 감지하여 동작할 필요가 있다. 특히 n줄이냐, 아예 페이지 단위냐(WHEEL_PAGESCROLL)를 잘 판단해야 한다.

만드는 컨트롤에 확대/축소 배율이 존재한다면 Ctrl+휠로 배율 조절을 시키는 게 요즘 트렌드다.
또한, 전통적인 세로 스크롤용 마우스 휠뿐만이 아니라 가로 마우스 휠 WM_MOUSEHWHEEL (wheel 앞에 H 추가)이라는 게 있다는 것도 알아 두면 좋다. 마우스 현물보다는 손가락 제스처를 이용한 스크롤 기능이 있는 노트북 터치패드로 생성되는 듯하다.

어디 그 뿐이랴? 표준 인터페이스에는 아예 마우스 휠을 눌러서 구동하는 '자동 스크롤' 모드도 있다. WM_MBUTTONDOWN(마우스 가운데 버튼) 되시겠다. 이건 아까와는 반대로 마우스 실물이 아닌 노트북 터치패드로는 구경하기 힘든 모드이다.

.마우스로 화면을 끌어서 스크롤이 되게 하려면 WM_LBUTTONDOWN, WM_MOUSEMOVE 같은 메시지들을 처리하면 될 것이고.
아, 이런 것보다 더 중요하면서도 스크롤 윈도우에서 상당히 번거로운 작업이 하나 있는데 그건 바로 WM_HSCROLL 및 WM_VSCROLL 메시지이다. 우리의 편견과는 달리,처음에 SetScrollInfo 함수 하나로 전체 스크롤 크기와 영역만 지정해 준다고 해서 그 다음부터 모든 스크롤 처리가 자동으로 되는 게 아니기 때문이다.

스크롤 메시지는 사용자가 스크롤 바의 화살표(한 칸씩)를 눌렀을 때, 스크롤 바를 드래그하고 있을 때, 스크롤 바 옆을 눌렀을 때(한 페이지 씩) 등등의 상황별로 서로 다른 정보가 담긴 채로 전달된다. 그리고 이때 실제로 화면을 얼마만치 스크롤시키고 어떤 처리를 할지는 전적으로 해당 응용 프로그램에 달려 있다. 운영체제가 자동으로 해 주는 일은 없다. 이걸 처리하지 않으면 사용자가 스크롤 바를 눌러도 다른 반응이 발생하지 않는다.

바로 이런 특성 때문에 사용자가 스크롤 바를 끌고 있는 동안 화면이 바로 갱신될지, 혹은 곧장은 아니고 스크롤 바의 드래그가 끝난 뒤에야 화면을 갱신할지 같은 것도 응용 프로그램마다 달리 동작할 수 있다. 물론 반응성이 좋은 프로그램이라면 어지간하면 즉각 화면이 갱신되는 게 좋겠지만 말이다.

그리고 요즘은 화면 캡처 유틸리티들이 스크롤 캡처를 지원하는데, 이 역시 생각보다 꼼수를 써서 구현돼 있다. 화면에다 별도의 표식을 그려 넣은 뒤, 캡처 대상 윈도우에다 스크롤 메시지를 보내고 그 표시가 얼마나 이동했는지를 수동으로 점검한다. 위와 같은 높은 자유도로 인해, 저렇게 하지 않으면 그 스크롤 분량을 정량적으로 알아낼 수 없기 때문이다. (스크롤 바가 이동한 양과 그에 따라 지금 화면이 실제로 이동한 픽셀수 사이의 인과관계)

화면이 스크롤되면 ScrollWindowEx 함수를 호출해서 이미 그려진 화면은 운영체제가 제공하는 스크롤 기능으로 넘기고 새로 칠해져야 하는 최소한의 부분만 새로 칠하는 '평범한 프로그램'이라면.. 저 꼼수가 통한다.
그러나 스크롤 될 때마다 화면을 몽땅 지우고 새로 그리는 프로그램이라면, 저 표식도 지워지기 때문에 스크롤 캡처를 할 수 없게 된다.

이상이다. 윈도우의 스크롤 기능까지 얘기하다 보니 말이 또 길어졌다. 여기에도 뭔가 정량적인 동작 패턴이 분명 있는 듯한데, 그것만 추려내기는 쉽지 않아 보인다.
공통된 기능을 운영체제가 API 함수로든, 공용 컨트롤 윈도우로든 뭘로든 좀 제공을 하지 않는다면 역시나 프로그래머들이 비슷한 기능을 여전히 자체적으로 중복 구현할 수밖에 없을 것이다.

Posted by 사무엘

2014/07/22 08:36 2014/07/22 08:36
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/987

컴퓨터 화면에 그려진 어떤 물건(주로 사각형 모양)의 경계 테두리에다 마우스를 갖다대면 포인터가 그 경계 테두리와 수직인 방향의 화살표로 변한다. 그 상태에서 마우스를 클릭하여 끌면, 그 물건의 크기를 마우스 포인터가 움직이는 방향으로 변경할 수 있다.
이것은 GUI에서 매우 흔히 볼 수 있는 기능이다. 특히 크기 조절 가능한 창--운영체제가 정식으로 제공하는 GUI 구성요소--의 경우 이런 기능은 운영체제가 non-client 영역에서 알아서 자동으로 처리해 준다.

그런 창이야 운영체제가 처리를 알아서 해 주지만, 대화상자 내부의 임의의 영역에 대해서, 혹은 클라이언트 영역에 내가 그려 주는 임의의 객체에 대해서 이런 처리를 구현하려면 어떻해야 할까? 뭔가 공통된 패턴의 알고리즘을 처리해야 할 텐데 코딩량이 적지는 않으며 왠지 귀찮고 번거로워 보인다.

크기 조절 내지 화면 분할 UI와 관련해서는 다음과 같은 여러 상황을 생각할 수 있다.

1. 한 윈도우 내부에 그려지는 개체의 크기 조절

2차원 벡터 그래픽 프로그램 내지 RAD 툴의 폼 에디터가 정확하게 여기에 속한다. 요즘은 워드 프로세서도 자체적인 벡터 그래픽이나 하다못해 OLE 개체라도 취급하니 마우스를 이용한 크기 조절 기능을 제공해야 한다. 개체를 클릭하면 8군데의 앵커 사각형이 생기며, 경계 아무 곳이라기보다는 그 앵커 사각형을 드래그했을 때 크기 조절이 된다. 다른 곳을 드래그하면 크기 조절이 아니라 이동이 되고. 그리고 요즘 MS 오피스 제품은 회전용 앵커까지 덤으로 제공한다.

비주얼 C++에는 CRectTracker라는 고전적인 클래스가 있어서 마우스 드래그로 임의의 사각형 영역을 화면에다 그리고 마우스가 클릭됐을 때의 일체의 처리를 알아서 해 준다. 이것만 쓸 줄 알아도 상당히 편리한데, 본인은 지금까지 그런 분야의 프로그램을 개발할 일이 없다 보니 실제로 써 본 적은 전혀 없다. 이런 게 있다는 것만 안다.

그리고, 지금도 있나 모르겠다만, 비주얼 C++에는 DrawCli라고 딱 개체 기반 벡터 드로잉과 드래그 드롭, 크기 조절과 이동을 모두 시연해 놓은 걸출한 예제 프로그램이 있다.
학창 시절 나의 친구였던 <비주얼 C++ 완벽 가이드>(김 용성)에도 '트래커'라는 예제 프로그램이 있으니 참고할 것.

사용자 삽입 이미지

참고로, 저렇게 테두리를 그리고 사각형 안의 내용물은 대각선 사선으로 칠하는 건.. embed된 OLE 개체를 외부 프로그램이 수정 중일 때 클라이언트 프로그램이 표시하는 표준 모양이기도 하다.

사용자 삽입 이미지

2. 부모 윈도우가 자식 윈도우를 동적으로 분할하고 관리

규모깨나 좀 있는 문서 편집 프로그램을 보면, view를 분할하는 기능이 있다. 이것은 한 문서 컨텐츠에 대해 뷰 윈도우를 여러 개 둬서 한 컨텐츠를 여러 가지 다른 방식, 다른 위치를 표시할 수 있게 한다. 사용자의 입장에서 매우 편리한 기능이지만, 컨텐츠와 뷰 윈도우가 완전히 일심동체라고 전제하고 만들어져 버린 프로그램이라면 추후에 이러 기능을 추가하기가 쉽지 않을 것이다. MDI 프로그램이라면 아예 별도의 독립된 창을 만드는 기능도 있겠지만 SDI에서는 한 창을 분할하는 것만 가능하다.

이건.. 생각보다 만들기 어려운 기능이다. 경계(splitter) 부분을 마우스로 끌었을 때의 처리도 처리거니와--이를테면 XOR 연산으로 자취를 그렸다가 지우는 것도..--, 내가 무엇보다도 힘들겠다고 느끼는 건 스크롤 바를 자체적으로 따로 만들어서 관리하는 부분이다. 이게 무슨 뜻인지를 설명하자면 이렇다.

Windows 운영체제에는 어떤 윈도우가 자체적으로 스크롤 바를 가질 수 있고, 한편으로 스크롤 바 자체가 별도의 컨트롤로 존재할 수 있다.
윈도우가 자체적으로 갖는 native 스크롤 바는 당연히 운영체제가 모든 처리를 알아서 해 준다. 창의 크기가 바뀌어도 스크롤 바를 자동으로 우측(상하) 내지 하단(좌우)에 배치해 주고, 스크롤을 할 필요가 없어지면 알아서 스크롤 바가 없어지고 그 영역까지 클라이언트 영역이 확대된다.

그러나 splitter가 존재하는 view를 보면, 스크롤 바가 있던 자리의 구석 일부에 창을 분할시키는 앵커가 자리잡고 있다. 이건 native 스크롤 바로 구현 가능하지 않기 때문에 스크롤 바 컨트롤을 따로 만들어서 앵커 밑이나 옆에다 두고, 스크롤 바의 위치· 크기와 관련된 모든 처리를 수동으로 해야 한다. 이 얼마나 복잡하고 손이 많이 갈까? 그러니 MFC가 CSplitterWnd라는 클래스에다 전부 구현해 놨다.

사용자 삽입 이미지

MFC AppWizard에서 '뷰 분할 기능 사용'을 체크하면, 프레임 윈도우는 밑에다 저 splitter 윈도우를 생성하고 걔가 또 자기 밑에다 어떤 view를 생성할지를 따로 지정해 준다. 다시 말해 프레임 윈도우와 view 사이에 splitter라는 중간 계층 윈도우가 하나 또 생긴다는 것이다. 그리고 요놈이 스크롤 바와 앵커의 위치를 관리하고 앵커 드래그에 대한 처리도 담당한다.

사용자 삽입 이미지

내가 MFC의 splitter 윈도우에 대해 꽤 놀란 것은, 분할을 2개씩뿐만 아니라 그 이상도 얼마든지 할 수 있다는 점이다. 위의 그림처럼 가로 3개, 세로 3개를 분할해서 무려 9개나 되는 view 윈도우를 뻥튀기시킬 수도 있다. 가로와 세로의 splitter 중 어느 것 하나의 위치가 바뀌었을 때, 혹은 창 전체의 크기가 바뀌었을 때 splitter가 총체적으로 조율하고 해야 할 일의 양도 그에 비례해서 많아질 것이다.

그런데 각각의 창들이 다 독자적인 가로· 세로 스크롤 바를 갖는 건 아니고.. 위의 스크린샷에 보듯이 한 column에 해당하는 view들은 가로 스크롤 바를 하나 다같이 공유한다. 그리고 한 row에 해당하는 view들은 세로 스크롤 바를 다같이 공유한다. 특이한 점임.

3. 부모 윈도우가 자식 윈도우를 '정적'으로 분할하고 관리

위의 2번과 마찬가지로 부모 윈도우가 자식 윈도우의 분할을 관리하는 경우이긴 한데, 위처럼 성격이 비슷한 윈도우가 아니라 별개의 윈도우를 고정적으로 관리하는 경우를 추가적으로 생각할 수 있다. 처음엔 1개였다가 2개 이상으로 자유자재로 분할되는 건 아니기 때문에, 스크롤 바나 앵커 같은 건 없다.

왼쪽에 트리 컨트롤, 오른쪽에 리스트 컨트롤을 두고 가로로 크기 조절이 가능한 탐색기 같은 프로그램도 좋은 예이고, 그보다 좀 더 복잡한 경우로는 개발자들의 친구인 Dependency Walker가 있다.

사용자 삽입 이미지

Spy++ 같은 프로그램으로 들여다보면, 창 구조가 생각보다 복잡하다는 걸 알 수 있다.
가장 겉에 있는 창은 화면을 상하로 나눈다. 위에 있는 창은 화면을 또 좌우로 나눠서 왼쪽은 모듈 트리 구조가 나오며, 오른쪽은 또 상하로 나누어서 각각 이 모듈이 import하는 심벌들, 그리고 대상 모듈이 export 하는 전체 심벌들이 표시된다.
한편, 아래의 화면은 또 상하로 나뉘어서 위에는 전체 모듈 리스트가 있고 아래에는 메시지 log가 있다.

즉, 한 윈도우에 여러 개의 컨트롤들이 sibling 관계로 대등하게 늘어서 있는 게 아니라, 또 분할 윈도우가 있고 그 아래에 또 분할 윈도우가 자식 윈도우로 있는 형태다. 한 분할 윈도우는 언제나 좌우로든 상하로든 2개의 윈도우만을 담당한다.

이렇게 이분법적으로 접근하면, 제아무리 복잡하게 화면이 좌우 상하로 분할되어 있는 창이라 해도 전체 크기가 바뀌었다거나 할 때 자기가 맡은 두 개의 창만 비율 분배를 잘 하고 나머지는 자가반복적인 재귀 처리에 맡기면 되니 문제가 단순해진다는 장점이 있다.

저런 윈도우가 활용의 자유도가 더욱 올라간다면 아예 별도의 창으로 분리하거나 docking까지 가능해진다. Visual Studio의 각종 보조 윈도우들처럼 말이다. 그건 우리 같은 평범한 프로그래머가 밑바닥부터 할 짓이 못 되며, 이미 있는 GUI 라이브러리의 사용법을 익히는 것만으로도 충분할 것이다. MFC 없이 Windows API만으로 docking toolbar를 구현한 소스를 외국 사이트에서 본 적이 있는데, 가히 근성이 느껴졌다.

사용자 인터페이스라는 건 컴퓨터로 하여금 의미 있는 작업을 하게 만드는 실질적인 알고리즘이 아니다. 없던 걸 처음 시도하여 만드는 게 아닌 이상, 베끼기만 하느라 시간 낭비할 필요는 없을 것이다.

4. 대등한 위상의 윈도우끼리 분할 관리를 해야 하는 경우

어휴, 글을 이렇게 길게 쓸 생각은 없었는데... 마지막 아이템도 언급을 안 할 수가 없구나. -_-;;
자, 지금까지 얘기한 것들을 정리하자면 1번은 그냥 한 윈도우 내부에서 그리기를 하는 것뿐이며, 2번과 3번은 부모 윈도우가 자식 윈도우들의 공간 관리를 하는 경우를 특별히 MFC의 document-view 아키텍처의 예를 들어 소개한 것이다.

그러나 실무에서는 그것만이 전부가 아니다. 대화상자처럼 document-view 아키텍처가 적용되지 않는 창에 대해서도 수평/수직 splitter 같은 물건을 만들어야 할 때가 있다.
예를 들어, <날개셋> 제어판 같은 경우, '분야'를 나타내는 왼쪽의 트리 컨트롤과, 오른쪽의 여타 컨트롤들 사이에 수직 splitter를 둬서, 트리 컨트롤의 폭을 좀 더 넓힌다거나 반대로 좁히는 경우를 생각할 수 있다.
이 경우 splitter는 여타 컨트롤들과 마찬가지로 대화상자 안에 존재하는 여러 자식 윈도우의 하나일 뿐이지 나머지 컨트롤들을 모두 통솔하는 부모 윈도우의 지위는 아니게 된다. Spy++로 들여다보면, 가로나 세로로 길쭉한 고유한 splitter 윈도우가 잡히는 걸 볼 수 있다.

이런 상황에 대해서는 MFC는 딱히 제공해 주는 있는 클래스가 없다. codeguru 같은 데서 splitter dialog 정도로 검색해 보면 예제 소스나 관련 튜토리얼들이 쭉 나온다. 예전에 아주 괜찮은 코드를 하나 구해서 유용하게 쓴 적이 있었는데 지금 다시 검색하려니까 못 찾겠다.

이런 일을 하는 범용적인 클래스를 만들 때 염두에 둬야 하는 사항으로는, 좌우나 상하든 보장해 줘야 하는 최소 크기를 인자로 받아야 할 것이고, 좌우 상하 중 한쪽에다 뒀으면 하는 윈도우를 배열 같은 자료구조로 관리해야 한다. 윈도우 핸들이 아닌 ID로 받으면.. ID로부터 실제 핸들값(HWND)을 얻어야 하는 번거로움이 있지만, 그 컨트롤이 중간에 재생성된다거나 해도 여전히 식별이 가능하기 때문에 범용성이 좀 더 향상된다.

다른 splitter조차 자기의 크기 조절에 영향을 받게 하고 WM_SIZE 메시지에 반응한다면, 아까 Dependency Walker 같은 복잡다단 splitter도 얼마든지 구현 가능하다.
splitter를 구현하려면 당장 크기를 조절하는 것 처리는 둘째치고라도, 창의 크기가 바뀌었을 때 각 분할 화면들의 공간 배분을 어떻게 할지 같은 것도 생각해야 하니 여러 모로 골치가 아픈 건 사실이다.

끝으로 언급하고 싶은 이슈가 있다. 1~4번들은 다 마우스가 클릭되었을 때 캡처를 잡고 마우스 움직임을 추적하다가 버튼이 떼졌을 때 마무리 처리를 한다는 공통점이 있다.
이 경우, WM_LBUTTONDOWN, WM_MOUSEMOVE, WM_LBUTTONUP을 모두 메시지 맵에다 등록하고 각 상황별 코드를 메시지 핸들러 함수에다 제각기 따로 작성하는 방법을 생각할 수 있지만..

좀 더 능숙한 프로그래머라면, 그런 드래그 드롭 처리 정도면 WM_LBUTTONDOWN에다가 아예 별도의 message loop을 만들어서 거기에다 WM_MOUSEMOVE와 WM_LBUTTONUP에 해당하는 코드를 다 집어넣는 방법을 선택한다. 한 함수에다가 한 기능에 대한 처리를 몰아서 넣는 게 훨씬 더 깔끔하기 때문이다.

Posted by 사무엘

2014/07/02 08:32 2014/07/02 08:32
, ,
Response
No Trackback , 6 Comments
RSS :
http://moogi.new21.org/tc/rss/response/980

« Previous : 1 : ... 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 : ... 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:
3042671
Today:
2298
Yesterday:
1700