컴퓨터 프로그램의 GUI 구성요소들 중에는 여러 아이템들을 한데 나열하는 리스트 박스(list box)라는 게 있고, 고정된 한 문장에 대해서 예/아니요, 참/거짓 여부를 지정하는 체크 박스(check box)라는 게 있다.

체크 박스는 프로그램이 고정 붙박이 형태로 제공하는 기능이나 옵션 하나에 대한 설정을 할 수 있다. 그리고 리스트 박스는 보통은 가변적인 개수의 항목들 중에 하나를 선택할 때 쓰인다.
그런데 가끔은 이 두 물건의 기능을 한데 합치고 싶은 상황이 생긴다. 리스트 박스의 각 아이템들에 대해서 1비트짜리 정보를 배당해서 선택 여부를 지정하는 것 말이다.

뭐, Windows의 리스트박스 컨트롤은 모든 아이템들에 대해서 1비트도 아니고 그냥 machine word 크기 하나로 custom 정보 data를 지정하는 기능이 있다. 또한 필요하다면 하나가 아닌 복수 개 multi-selection 모드로 동작하게 할 수 있고, 각 아이템에 대한 custom drawing도 가능하다.

하지만 딱 부러지게 아이템들 앞에 자동으로 운영체제의 check box 그림을 그려 주고 체크 박스의 리스트를 구현하는 기능 자체는 없다. 필요하면 사용자가 그걸 직접 구현해서 쓰게 여건만 만들어 놨을 뿐이다.
그래서 MFC의 경우 기존 리스트박스를 서브클래스 해서 CCheckListBox라는 걸 제공한다. owner drawing만 구현하는 것으로는 충분치 않고, space를 누른 키보드 입력과 check 버튼 주위를 누른 마우스 클릭도 감지하게 메시지 몇 개를 서브클래스 했다.

자고로 화면에 뭔가 길다란 리스트를 만들고 아이템들을 복수 선택할 수 있게 해 놓은 프로그램의 원조는 PC-Tools나 MDIR, Norton Commander, 심지어 Windows 3.x의 파일 관리자 같은 파일 관리 유틸리티이지 싶다. 복수 개의 파일을 복사하거나 삭제하는 기능을 제공해야 하니 리스트의 복수 선택 기능이 무조건 필수이기 때문이다.

그런데, selection이라는 것과 highlight 선택막대의 관계를 어떻게 보느냐에 따라서 프로그램의 동작이 달라지곤 했다. 도스용 프로그램들은 selection과 선택막대가 서로 따로 논다고 본 반면, Windows는 selection이 곧 선택막대의 연장선이라고 봤다.

그래서 Windows의 리스트박스는 화살표 키를 누르는 순간 기존 selection들이 다 사라지면서 선택막대가 움직이곤 했다. Shift+화살표로 연속된 영역을 한꺼번에 선택하는 것 말고 불연속적인 영역을 취사선택하려면 Shift+F8부터 눌러서 선택막대가 아닌 포커스 테두리가 깜빡거리는 상태로 들어간 뒤, 포커스 테두리만 움직이면서 Space로 아이템들을 선택하면 됐다.

굉장히 특이한 동작인데 Windows에서는 이게 기본이다. 기본적으로 포커스 테두리만 움직이게 하는 모드는 extended 플래그(LBS_EXTENDEDSEL)로 따로 있었다.
그에 비해 평소에는 선택막대와 selection이 다같이 움직이고 Ctrl+화살표로 포커스 테두리를 움직여서 Space로 선택하는 비교적 '직관적인 방법'은 훗날 리스트뷰 컨트롤이 도입하게 된다. 아이템을 복수 선택하는 방식은 이 두 컨트롤이 서로 호환되지 않는다.

또한, 각 아이템들에 대해 체크 플래그를 지원하는 건 아이템을 그냥 복수 선택할 수 있게 하는 것과는 UI의 관점이 다르다. 비록 내부적으로 본질적으로는 아이템별로 1비트짜리 boolean 정보를 지정한다는 점에서는 차이가 없겠지만 용도가 같지 않다는 것이다.

복수 선택은 대체로 아이템들이 진짜 가변적이고 사용자에 의해 아이템을 추가하거나 삭제까지 할 수 있는 상황에서 쓰이겠지만 체크 리스트는 그렇지 않은 경우가 대부분이다. 단순히 응용 프로그램이 제공하는 기능과 옵션이 많기 때문에 리스트 형태로 만들었을 뿐이다. 체크 리스트는 복수 선택과 달리, 선택 막대 selection과는 완전히 별개로 관리되기도 해야 할 것이고 말이다.

다음은 MFC의 CCheckListBox를 사용했던 먼 옛날 날개셋 한글 입력기 1.x의 옵션 대화상자이다.
Windows XP부터는 테마도 등장했기 때문에 지금 상황에 따라 체크 박스를 그리는 방법 역시 더 복잡해졌다.

사용자 삽입 이미지

다음은 리스트 박스가 아니라 무려 트리 컨트롤(공용 컨트롤)을 사용했던 날개셋 2.x의 옵션 대화상자의 모습이다.
Internet Explorer가 4인가 5에서부터 인터넷 고급 옵션들을 이렇게 트리 컨트롤로 구현해서 오늘날 최후의 11 버전에까지 이어져 오고 있다. 지원하는 옵션이 너무 많기 때문이다. 본인 역시 이 스타일을 따라해 보았다.

사용자 삽입 이미지

공용 컨트롤들은 owner-draw 안 쓰고도 자체적으로 아이템별 비트맵을 지정할 수 있으며, 더구나 트리 컨트롤은 아이템들을 카테고리별로 분류도 할 수 있으니 더욱 좋다.
날개셋 3과 그 이후부터는 이들 옵션이 상당수가 오토마타와 글쇠의 수식, 별도의 카테고리 옵션 등으로 떨어져나간 관계로, 저렇게 트리 컨트롤까지 써야 할 정도로 긴 옵션 리스트를 만들 일이 없어졌다.

사실, 트리 컨트롤은 IE 4 타이밍에서 TVS_CHECKBOXES라는 스타일이 추가되기도 했다. 기존 이미지 스타일을 활용하는 게 아니라 그건 놔두고 옆에 체크 박스를 별도로 추가해 주는 형태이다.

트리 컨트롤에서 체크 박스는 설치 프로그램에서 어떤 소프트웨어 제품의 구성요소들을 계층 구조로 나열한 뒤 설치· 제거할 부분을 선택받는 부분에서 유용하게 쓰일 듯하다. 이런 데서는 자식 노드가 하나라도 선택되면 부모 노드들은 중간 상태로 바뀌고, 부모 노드를 선택하거나 해제하면 자식들도 한꺼번에 선택이나 해제되는 동작이 필요할 것이다.

하지만 트리 컨트롤의 체크박스 기능은 깔끔하게 구현되지 않아서 잡음이 많다. 스타일을 윈도우를 생성한 뒤에 SetWindowLongPtr로 런타임 때, 그리고 아이템을 하나라도 추가하기 전에 적절한 타이밍에만 지정할 수 있다.
레이먼드 챈 아저씨는 저건 차라리 스타일이 아니라 메시지 형태로 구현하는 게 더 나았을 정도라면서 API 설계 구조를 비판한 바 있다. (☞ 링크) 실제로 콤보 박스의 extended UI 여부는 스타일이 적절해 보임에도 불구하고 덩그러니 CB_SETEXTENDEDUI라는 메시지를 통해 지정하게 돼 있다.

한편, 트리 컨트롤은 처음 도입됐을 때부터 지금까지 체크 박스와는 달리, '복수 선택'은 지원하지 않고 있는 것으로 유명하다. 리스트뷰 컨트롤처럼 아이콘을 Shift 및 Ctrl을 이용하여 복수 선택할 수 있지 않다는 뜻이다.
Windows 운영체제는 탐색기에서 볼 수 있듯이, UI 디자인 철학이 "트리로는 분야를 하나 선택만 하고", "리스트에다가 그 분야에 속하는 아이템들을 출력한 뒤 복수 선택해서 지지고 볶는다" 형태이긴 했다.

계층 구조를 나타낼 수 있는 복잡한 UI 컨트롤에서 복수 선택까지 가능하면 프로그램의 기능이 매우 복잡해지며, 리스트도 아니고 트리 컨트롤이 굳이 복수 선택까지 가능해야 할 일은 매우 드문 것도 사실이다.
하지만 그럼에도 불구하고 당장 Visual Studio IDE부터가 클래스· 리소스· 솔루션 뷰의 트리 목록이 진작부터 복수 선택을 지원한다. 걔들은 4.0 시절부터 공용 컨트롤 없이 진작부터 자체 구현 트리 컨트롤을 써 왔기 때문이다.

끝으로, 체크와 다중 선택을 짬뽕한 듯한 기괴한 UI가 Windows의 역사상 단 한 번, 8의 리스트뷰 컨트롤에서 잠시 등장한 적이 있었다.
아이템의 좌측 상단 같은 특정 부위를 마우스로 가리키고 있으면 체크 박스가 나타나고, 그걸 클릭하면 아이템을 복수 선택할 수 있었던 것이다.
보통 아이템을 클릭하면 기존 selection들은 다 없어지고 그것'만' 선택되곤 하는데, 체크 박스를 선택하면 기존 selection들을 놔두고 그걸 추가로 선택할 수 있었다.

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

그 당시엔 아마 터치 장치를 염두에 두고.. Ctrl/Shift+클릭이나 드래그 없이 클릭만으로도 아이템들을 복수 선택할 수 있게 고심 끝에 저런 기능을 넣었던 듯하다.
하지만 반응이 좋지 않았는지, 이런 기능은 내 기억이 맞다면 Windows 8.1에서 곧장 없어졌고 다시 등장하지 않았다. 하긴, Windows 8은 저 정도면 약과이지, 아예 시작 버튼을 없애 버렸을 정도로 엄청 과격한 모험을 한 물건이기도 했으니까.

이렇듯, 리스트 박스, 리스트뷰 컨트롤, 트리 컨트롤을 두고 아이템의 복수 선택 및 체크 선택과 관련하여 할 말이 무척 많은 걸 알 수 있다. 복수 선택은 단수 선택만치 일상적으로 자주 쓰이는 기능은 아닐 뿐더러 어떤 방식으로 구현할지 동작의 customization의 폭도 넓은 편이다. 그래서 운영체제의 GUI가 곧장 직통으로 지원하지 않고 구현을 사용자에게 맡기는 편이었던 것 같다.

Posted by 사무엘

2017/12/20 08:36 2017/12/20 08:36
, ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1439

Trackback URL : http://moogi.new21.org/tc/trackback/1439

Comments List

  1. 방문자 2017/12/21 20:01 # M/D Reply Permalink

    Windows 10에서도 탐색기에서 체크박스의 표출 여부가 옵션으로 남아 있습니다. 8.1에서 기본값이 바뀌었던 걸까요?

    1. 사무엘 2017/12/21 22:16 # M/D Permalink

      아하~ '기본적으로 꺼져 있는 상태'가 됐을 뿐, 그 UI가 완전히 없어진 건 아니었군요.
      옵션들 제~~일 밑에.. "확인란을 사용하여 항목 선택" (이것도 트리 컨트롤 기반 체크 리스트이군요~)...
      저는 처음 알게 됐습니다. 그렇다면 8.1에서 기본값이 바뀐 것이지 싶습니다. ^^

Leave a comment

1.
과거 2010년대 초에는 제로보드 4를 쓰지 말자, IE6을 쓰지 말자, ActiveX를 퇴출시키자 이런 운동이 벌어졌다. 그때는 같은 PC 안에서 Windows에 종속되지 말고 맥, 리눅스까지 잘 지원하는 웹 표준을 지키자는 게 아젠다였다.

그러다가 2010년대 중반부터는 구시대 관행들이 추가적으로 없어지는 게 눈에 띈다. 웹 상으로 주민 등록 번호 13자리 전체를 수집하는 게 금지되었으며, 기술적으로는 영원불변할 것만 같던 플래시마저 웹에서 퇴출 수순을 밟고 있다. 이건 PC 운영체제 간의 연동이 아니라, PC와 모바일이라는 상이한 플랫폼 간의 연동이 아젠다이다. (플래시 없이 현란한 광고 애니메이션은 어째 만드나 싶다.)

사실, 기술과 이론만 따지자면야 모바일에서도 플래시를 지원하지 못할 이유는 없다. 그러나 플래시는 근본적으로 현란한 벡터 애니메이션을 출력하려고 만들어진 물건이니 설계 이념이 딱히 화면 작고 전력 소비에 민감한 모바일과는 친화적이지 않다. 기기와 무관한 접근성의 보장이라는 웹 표준과도 어울리지 않는다.

더구나 플래시도 따지고 보면 그 본질은 3rd-party plug in이고 IE의 용어로 표현하자면 ActiveX의 일종이긴 했다. 플래시를 집어넣을 때 쓰는 태그가 여느 ActiveX 컨트롤을 집어넣을 때 사용하는 태그와 다를 바 없다. 다만, 얘는 독보적으로 너무 유명하고 널리 쓰이다 보니 타 ActiveX와는 지위가 다른 예외적인 물건으로 취급되어 왔을 뿐이다.

그러니, 좀 웹 표준을 어겨도 PC에서는 큰 문제가 되지 않던 것이 완전히 차원이 다른 컴퓨팅 환경인 모바일에서는 더욱 부각되게 되었다. 거기에다 보안 문제도 불거지고, 또 모종의 이유로 인해 아이폰 제조사인 애플과 플래시의 제조사인 어도비가 사이가 나빠지는 악재까지 겹치면서 플래시는 인터넷에서 퇴물로 전락했다. 급속도로 몰락의 길을 가게 됐다.

플래시가 독자적으로 제공하던 고급 기능들은 그냥 웹브라우저가 직통으로 지원하는 HTML5 표준으로 몽땅 이동했다. 인터랙티브하게 싹 나타났다가 사라지는 웹사이트 메뉴, 인터랙티브한 게임, 간단한 이미지 편집 등등이 몽땅 말이다.
Chrome 브라우저의 경우 플래시는 자동 실행은 개뿔 물 건너 갔으며, 사용자가 수동으로 켜 줘야만 실행되는 legacy가 됐다. 음.. 예전에 흑역사로 사라졌던 MS Office의 길잡이를 보는 듯한 느낌이다. 웹 환경이 이렇게 변할 줄이야..

2.
인터넷 IPv4 주소 고갈과 주소 할당 중단은(이미 2011년의 일임!) 마치 유니코드에서 BMP 영역의 고갈과 비슷한 성격의 현상 같다.
결국 요즘 사람들은 대부분 공유기를 사용해서 인터넷을 하고 고정 IP라는 개념이 사실상 없어졌는데,
공인 IP와 사설 IP가 따로 노는 것 때문에 계층이 좀 헷갈리고 복잡해지고 골치 아파진 것도 있다. 이건 마치 분당선 서현 역이 지하철 출구 번호(안)와 백화점 출구 번호(밖)가 서로 완전히 따로 놀아서 위치 식별이 어려운 것과 비슷한 양상이다.

초기에 설계되었던 공인 IP 주소 영역들을 보면 class A~C 이런 거 말고도 공유기를 위한 영역을 따로 떼어 놓은 게 있는데.. 이게 유니코드로 치면 UTF-16에 존재하는 surrogate와 개념상 정확하게 일치한다. (처음엔 유니코드에 UCS2만 있었지 UTF-16 같은 게 있지는 않았듯이, IP 주소도 처음부터 공유기 영역에 있지는 않았었다.)
아니, 애초에 유니코드가 없던 시절에 지원 글자 수를 늘리려고 사용하던 multibyte, lead byte, tail byte 따위와도 비슷한 개념이라 볼 수 있다.
또는 16비트 시절의 far pointer하고도 비슷하고 말이다.

결국 32비트, 64비트, IPv6처럼 본질적으로 더 우월하고 넉넉하고 나은 것, 완전한 것이 오기 전에 컴퓨터에서 불완전한 것으로 임시땜빵을 하는 방법은 분야를 불문하고 방법론이 다 비슷해 보인다~!

3.
예전에 PC에서는 ICQ, MSN, 그리고 국내 한정으로 네이트온 같은 온라인 메신저가 있었다. 얘들은 시대가 시대이다 보니 PC 전용이었는데, 2010년대 들어서는 다들 망하고 스카이프만이 MS에 인수되어 살아 있는 듯하다.
2010년대에는 카카오톡이 스마트폰 앱과 PC 통합으로 메신저 시장을 사실상 평정했다. PC는 사람이 기계가 있는 곳으로 가서 기계의 전원을 넣어야만 쓸 수 있는 반면, 스마트폰은 365일 24시간 내내 켜져 있으며 사람이 늘 들고 다닌다. 이런 결정적인 차이로 인해 카카오톡은 과거의 메신저와는 달리, 자기 상태를 표시하는 기능이 없다~! (available, away, out to lunch 같은)

카카오톡은 PC용이 있기 때문에 PC와 모바일을 지원한다.
한편, SNS 웹사이트로 출발한 페이스북도 메신저 기능이 있으니, 모바일과 '웹'을 지원하는 셈이다.
예전에 잠시 있었던 Google Talk은 PC용 프로그램, 모바일용 앱에다가 gmail 같은 Google 계열 사이트에서 실시간 대화도 지원하여 드물게 PC, 모바일, 웹을 모두 커버했던 걸로 기억한다.
이렇게 애플리케이션들이 실행 양상이 다양해지고 있는 게 흥미롭다.

그리고 2010년대부터는 웹 자체도 사용자 상호작용과 반응성이 워낙 좋아진 관계로 PC용 네이티브 프로그램까지는 몰라도 모바일 앱의 정체성을 크게 위협하고 있다..
물론 기기의 물리적인 기능에 아주 특화된 기능이라든가 방대한 게임 같은 건 모바일 기기에서 직통으로 돌아가는 프로그램이 필요하겠지만, 단순 서버와의 교신과 정보 공유, 조회, 열람이 목적이면 웹 페이지와 자바스크립트 자체를 그냥 기계와 운영체제를 초월한 통합 GUI 플랫폼으로 쓰지 말라는 법이 없게 되는 셈이다.

순수 웹 애플리케이션은 서버 주소만 알려주면 앱스토어 심사 같은 것도 필요 없고 곧장 배포가 가능하다. 웹 전체를 검열하는 빅 브라더 같은 건 세상에 존재하지 않으니 말이다.
그러나 웹을 기반으로 안드로이드와 iOS를 모두 통합하고, 부분적으로 각 모바일 플랫폼에 종속적인 코드를 끌어다 쓸 수 있는 형태인 '하이브리드' 앱이 있다. 그리고 그런 걸 만들어 주는 프레임워크도 있다. qt가 PC에서 Windows/리눅스/macOS 통합이라면, 아이오닉 같은 모바일 프레임워크는 안드로이드/iOS 통합인 셈이다.

'아이오닉'은 하이브리드 자동차의 이름이기도 한데 컴퓨터에서도 나름 하이브리드 모바일 프레임워크의 이름이구나(비록 영어 철자는 차이가 있지만). 디스크와 드럼(브레이크 vs 메모리 기술), 엑셀(자동차 이름 vs 스프레드시트 이름)처럼 들린다.

4.
웹은 기계와 CPU 아키텍처에 구애받지 않는 정말 universal한 프로그래밍 환경이다. 그 누구도 처음에 상상하는 것조차 쉽지 않았다.
인터넷에서 다른 형태의 프로토콜로 제공되는 서비스들도 거의 다 웹이 몽땅 독식해 버렸다. 글과 그림과 하이퍼링크만 있는 문서에 불과하던 웹은 한 2, 30년쯤 전의 옛날 얘기이고, 지금은 이게 그냥 인터넷의 알파와 오메가요, 문서와 코드의 짬뽕인 광활한 프로그래밍 환경이다. 그러니 웹 프로그래밍을 하나 잘 공부해 놓으면 그야말로 모든 기계에서 똑같은 결과가 나오는 프로그래밍 스킬을 얻게 된다.

웹이 그런 공룡 같은 거대한 환경으로 발전하는 과정에서 브라우저, 언어 등 각종 규격들이 찢어졌다가 표준화된 과정도 살펴보면 참으로 격세지감이 느껴지는 한편으로, 인간 사는 바닥은 어디든 다 정치와 밥그릇 싸움, 돈지랄이 있구나 하는 병맛스러움이 느껴진다.

HTML4의 지저분함을 참다못해 1990년대 말에 엄격한 XHTML이 제정되었지만, 결국 망하고 HTML5가 다시 옛날 관행 기반에서 제정된 건.. 1990년대 말에 IA64가 너무 과격한 변화를 추구하다가 망하고 기존 x86 호환성을 유지한 amd64로 64비트 컴퓨팅의 판도가 기운 것과 비슷해 보인다. 시기도 서로 비슷한 편이다.

또한 Windows 10이 2015년 가을에 나온 첫 판이 지금까지 그대로 유지되고 있는 게 절대 아니듯, HTML5도 보아하니 한번 정하고 영원히 고정이 아니라 찔끔찔끔 계속 뭔가 추가되고 있긴 한 모양이다. 그렇기 때문에 전통적인 ACID 테스트 말고 또 2017년 현재까지 만점을 받은 브라우저가 전혀 없는 다른 HTML5 점수 테스트 사이트도 있다.

한편으로 HTML4 이래로 무려 15년 가까이 뒤에야 HTML5가 제정되고 HTML이 급격히 발전하고 있는 건 C++98 내지 C++03 이후로 C++이 2000년대에 정체돼 있다가.. C++1x 이후로 C++이 함수형 패러다임도 받아들이고 네이티브 코드 생성 언어가 갑자기 약 빤 듯이 발전하고 있는 양상과 비슷해 보인다.

웹이 MS Office 문서라면 자바스크립트는 VBA 매크로와도 같은 물건이다. 그리고 내가 HTML, CSS, JS를 삼권분립과 비슷한 구도라고도 비유한 바 있다. 게임 개발에다 비유해도 기획자, 디자이너, 개발자에 착착 잘 대응하는 것 같다.
아 그래..! 옛날에는 이런 스크립트 자체가 단일화되지 않아서 HTML 주석 안에다가 스크립트 코드를 몰래 집어넣어야 했다. 마치 프레임만큼이나 "이 웹페이지를 제대로 표시하려면 자바스크립트를 지원하는 브라우저가 필요합니다" 이런 말이 출력되도록 참 기괴하게 HTML 문서를 작성했었다. 이거 도대체 언젯적 얘기냐..;;

또한, 웹 프로그래밍에 스크립트는 클라이언트 쪽만 있는 게 아니라 서버 쪽도 있다. 클라이언트는 처리가 빠른 대신 대외적으로 프로그램 소스 코드가 몽땅 공개돼야 한다. (뭐, 난독화는 가능하지만) 서버 쪽은 소스가 노출되지 않지만 데이터 입출력에 서버와 네트워크 트래픽을 감수해야 한다. 이것도 컴퓨터 한 대에서만 돌아가는 프로그램을 만들 때에는 고려할 필요가 없는 흥미로운 면모이다.

이런 와중에 마소는 20여 년 전 1990년대 중반에 Bob이라든가 MSN 이런 거 만들면서 좀 삽질을 했었다. Windows 95를 만들어서 개인용 PC의 운영체제는 자기 뜻대로 세계정복을 했지만, 인터넷까지 자기 서비스만으로 호락호락 세계를 정복할 수 있으리라 생각했던 건 큰 오판이었다. 그래서 잘 알다시피 IE를 수단과 방법을 가리지 않고 운영체제에다 끼워넣어서 웹 브라우저의 전면무료화 관행을 정착시켜 버리기도 했다. 하지만 IE 자체는 경쟁 브라우저와 모바일 환경 때문에 세계정복까지는 이루지 못했다.

또한 마소는 2000년대 말에 와서는 PC에 너무 안주하고 모바일 환경에 대해서도 대수롭지 않게 예상하다가 스마트폰 플랫폼의 주도권을 안드로이드와 iOS에 완전히 뺏겨 버렸다. 그 시기에 LG전자가 피처폰에 안주하다가 지금 같은 처지가 된 것과 비슷한 실수이다.
그런데 2000년대 중후반엔 나조차도 솔직히 말해 사고의 구조가 "그냥 디카 쓰면 되지 폰에다가 카메라를 왜 얹어?" 이러던 수준이었다. 어지간한 전문가라도 스마트폰이 이렇게 뜨고, 그 스마트폰 OS를 오픈소스로 전세계에 뿌려 버리는 괴수 용자 대인배가 등장할 거라고는 예상할 수 없었다.

즉, <미래로 가는 길> 같은 베스트셀러 책을 이미 1990년대 중반에 썼던 천하의 빌 게이츠조차도 웹과 모바일에 대한 전망이 다 적중하지는 못했다. 뭐, "손가락 끝으로 모든 정보를" 같은 큰 그림이야 물론 적중했지만, 그런 기술이 언제나 자기가 원하는 형태로 보급되고 마소 주도적으로 이뤄지지는 않았다는 것이다.

그나저나, IA64 하니까 떠오르는데.. 소프트웨어 업계에서는 2000년 가을쯤 Windows ME와 아래아한글 워디안도 비슷하게 세기말의 흑역사 삽질을 좀 했다는 공통점이 있다.

Posted by 사무엘

2017/12/17 08:31 2017/12/17 08:31
, , ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1438

Trackback URL : http://moogi.new21.org/tc/trackback/1438

Comments List

  1. Lyn 2018/02/02 02:28 # M/D Reply Permalink

    역시 세기말은 위험하군요

    1. 사무엘 2018/02/02 06:15 # M/D Permalink

      그러게 말입니다. 자기 실제 상황을 제대로 파악 못 하고 붕 뜬 분위기 쫓아가느라 분야별로 여기 저기 삽질이 속출했었습니다.
      (Lyn 님, 오랜만이네요~! 올해의 첫 댓글을 남겨 주셔서 고맙습니다~ ^^)

Leave a comment

1. 주 UI 스레드와 배경 작업 스레드

대화상자를 텅 빈 깡통 상태로 일단 띄워 놓은 뒤, 시간이 좀 오래 걸릴 수 있는 초기화 작업을 백그라운드에서 하고서 그게 끝나면 작업 결과를 대화상자의 각종 컨트롤에다가 반영하고 표시하기..
본인은 이런 형태로 동작하는 GUI를 구현한 적이 있었다. 리스트/콤보 박스에 들어갈 아이템들을 파일을 탐색하면서 수집하는 것이 대표적인 예이며, 당장 날개셋 한글 입력기 프로그램에도 이렇게 동작하는 UI가 한두 군데 있다.

그런데 그 백그라운드 작업이 언제나 수행되는 건 아니고, 조건에 따라서는 전혀 해당사항이 없는 경우도 있었다. 이때는 작업 결과의 표시와 관계가 있는 컨트롤들은 그냥 숨기거나 disable 시켜 놓으면 됐다.

그러니, 그런 컨트롤은 괜히 만들었다가 도로 숨기는 게 아니라, 스레드 함수가 자기 작업이 다 끝나고 마지막 부분에서 대화상자에다가 동적으로 생성하게 로직을 고치는 게 어떨까 생각을 했는데... 그렇게 하다가 더 피봤다.
당연한 말이지만 특정 스레드가 생성한 윈도우는 그 스레드의 실행이 끝남과 동시에 소멸되기 때문이다. 머리에 나사가 하나 빠지기라도 했는지 왜 그걸 생각을 못 했나 순간 "아차~!" 했다.

컨트롤 자체는 주 UI 스레드에서 미리 만들어 놓은 뒤, 백그라운드 작업 스레드에서는 그걸 ShowWindow / EnableWindow 정도의 제어만 할 수 있다. 컨트롤을 굳이 조건부로 동적 생성하고 싶다면, 백그라운드에서는 주 UI로 하여금 컨트롤을 생성하라고 메시지나 타이머 요청 정도를 보내는 간접적인 방법만 사용 가능할 것이다.

이렇게 윈도우의 생명 주기는 스레드와 관계 있는 반면, 접근 가능한 윈도우 클래스의 범위는 드물게 스레드가 아니라 RegisterClass를 호출한 모듈과 관계가 있다. 한 프로세스 안의 모든 모듈에서 접근 가능한 윈도우 클래스를 구현하려면 클래스 스타일에 CS_GLOBALCLASS를 지정해 줘야 한다. CreateWindowEx 함수는 현 스레드의 함수 호출 스택을 추적해서 자신을 호출한 모듈이 무엇인지를 따지기라도 하는가 보다. (인자로 받은 HINSTANCE 값은 무시하고 사용하지 않음.)

Windows 말고 안드로이드 프로그래밍을 해 보니 거기서는(Java)는 네트워크 통신은 무조건 백그라운드 스레드에서만 가능하고, 각종 GUI 요소의 조작은 반대로 주 스레드에서만 가능하게 해 놓았다. 이 규칙을 어기면 바로 예외가 발생한다. 그래서 Windows에서와 같은 혼동이 발생할 일이 없게 해 놨지만.. 간단한 통신 결과가 왔을 때 이를 GUI에다 표시하는 걸 한 함수에서 바로 못 하고 매번 스레드에, 메시지+핸들러로 실행 주체를 분리해야 하는 게 좀 번거로웠다.

2. 스레드의 강제 종료와 스택 상태

프로세스가 종료되는 가장 무난하고 좋은 방법은 main / WinMain 함수가 실행이 끝나서 자연스럽게 return하는 것이다. 그와 마찬가지로 스레드가 종료되는 가장 무난하고 좋은 방법 역시 해당 스레드 함수가 실행이 끝나서 자연스럽게 return하는 것이다.
하지만 Windows API에는 Exit...내지 Terminate...로 시작하는 프로세스· 스레드 종료 함수가 따로 있다.

두 단어 모두 뜻은 비슷하나, 전자는 자동사이고 후자는 목적어를 받는 타동사이다. 이로부터 유추할 수 있듯, 전자는 그 함수를 호출하는 자신을 종료하고 후자는 임의의 다른 프로세스나 스레드를 강제 종료시킨다.
어떤 프로세스가 이런 함수에 의해 종료되면 그 프로세스 하에서 돌아가던 모든 스레드들은 강제 셧다운된다. 그리고 반대로, 어떤 프로세스에서 모든 스레드들이 종료되어서 돌아가는 스레드가 하나도 없는 지경이 되면 빈 껍데기 프로세스는 자동 종료되고 소멸된다.

main 함수의 실행만 끝나면 자동으로 주변 잔여 스레드들의 실행도 강제로 다 끝나는 것 같지만 원래부터 그렇지는 않다. main을 호출한 하단의 C 런타임 라이브러리가 내부적으로 ExitProcess를 호출하기 때문에 그렇게 되는 것일 뿐이다. 운영체제 차원에서는 모든 스레드들의 실행이 끝나야 프로세스가 종료된다.

어쨌든, 프로세스나 스레드 같은 실행 주체는 자기 스스로 곱게 종료하는 게 좋다. 강제 종료 대상인 프로세스나 스레드는 자신이 강제 종료 당한다는 어떤 통지도 받지 못하며 이를 회피· 거부할 수도 없다. 뭐, 강제 종료를 막는 방법 뒷구멍이 있다면 악성 코드가 이를 마음껏 오· 남용, 악용할 것이니 저건 불가피한 면모도 있다. 강제 종료를 요청하는 프로세스가 적절한 권한만 갖고 있다면 강제 종료 작업 자체는 성공이 반드시 보장된다.

강제 종료는 파일이나 메모리, 스레드 동기화 오브젝트를 포함해 해당 스레드가 할당하고 선점해 놓은 그 어떤 자원도 제대로 수습· 회수하지 않은 채 말 그대로 해당 실행 주제만 없앤다. 그러니 엄청난 리소스 누수를 야기한다. 그나마 프로세스는 독립성이 높은 실행 단위인 덕분에 강제 종료되더라도 자기가 사용하던 모든 자원들이 자동으로 반납되는 게 보장이라도 되는 반면, 스레드는 그렇지 않다.

그렇기 때문에 TerminateThread는 TerminateProcess보다도 가능한 한 더욱 사용하지 말아야 한다.
I/O 관련 병목이나 데드락 같은 게 걸려서 해당 스레드의 코드 자체가 전혀 돌아가지 않을 때.. 옛날 같았으면 컴퓨터 리셋을 했을 피치 못할 상황에서나 극도로 제한적으로 사용해야 한다. 자기 스스로 실행 가능한 스레드라면 외부에서는 중단· 종료 플래그만 걸어 주고, 그 스레드가 알아서 실행이 종료되게 하는 것이 절대적으로 바람직하다.

그렇기 때문에 작업 관리자 같은 유틸에서 응용 프로그램을 강제 종료하는 기능은 일단 그 프로그램의 주 윈도우에다 WM_CLOSE만 살짝 보내 보고, 그 프로그램이 거기에 불응하면 API 차원의 극약 처분을 내리는 식으로 동작한다. 기왕이면 주먹보다는 말로 곱게 해결하는 게 좋으니까...

스레드가 강제 종료된 경우, 코드 실행 차원에서 발생하는 리소스 leak이야 어쩔 수 없는 귀결이다. 그런데 Windows는 전통적으로 exit 말고 terminate로 강제 종료된 스레드에 대해서는 그 스레드가 사용하던 스택에 속하는 메모리 주소도 해제· 재사용하지 않고 내버려 뒀다. 그러니 heap이 아닌 stack에 속하는 메모리가 leak이 발생하게 됐다.

이것은 스레드가 강제 종료되더라도 그 스레드의 스택에 속하는 메모리를 참조하던 다른 스레드가 뻗지 않게 하려고 성능보다는 안전을 고려해서 시행한 정책이었다. 어차피 TerminateThread를 할 정도이면 온갖 리소스들이 누출되었을 가능성이 높고 이왕 버린 몸에 비정상적인 상황이니 스택도 해제하지 않고 일부러 놔뒀던가 보다.
그러나 이 정책이 Windows Vista부터는 바뀌어서 이제는 terminate된 스레드의 스택도 곧장 해제된다. 흥미로운 점이다.

3. 열악하던 시절에 동시작업 구현하기

CPU 차원에서의 멀티스레드라는 게 없던 시절에 UI와 백그라운드 작업이 동시에 돌아가는 프로그램을 짜는 건 상당한 고역이었다.
옛날에는 컴퓨터 하드웨어 차원에서 관리되는 키보드 버퍼라는 게 있었다. 컴퓨터가 바빠서(busy) 정신없는 상태에서 사람이 키보드를 누르면 그게 일단 버퍼에 들어갔으며, 나중에 컴퓨터가 정신을 차리면 먼저 온 글쇠부터 밀린 처리를 했다. 일종의 queue 자료구조처럼 말이다.

이 키보드 버퍼는 크기가 15타 남짓밖에 안 됐다. 그러니 컴퓨터가 바쁜 상태에서 키보드를 조금만 많이 누르면 그 글쇠는 버퍼에조차 추가가 못 되고 컴퓨터가 시스템 전체를 잠시 멈추면서 높은 톤의 '삐~' 경고음을 냈다. "나 건드리지 마세요..!" 물론 ctrl, shift 같은 비문자 글쇠 말고 문자 글쇠들 한정으로. pause 키를 누르면 컴퓨터 전체의 실행을 일시정지 시킬 수 있던 시절의 얘기이다.

Windows 시대가 되면서 하드웨어와 소프트웨어 사이에 무슨 계층이 덧붙여졌는지, 컴퓨터에서 저런 걸 볼 일은 없어졌다. 하지만 9x 시절에는 운영체제가 대미지를 심하게 입고 반쯤 뻗어서 다운+재부팅 징조가 농후할 때면, 윈도우들이 메시지 큐가 다 차 버리고 메시지에 아무런 응답도 처리도 할 수 없는 상태가 되곤 했다. 이때는 그 윈도우로 마우스 포인터를 갖다대서 옮기기만 해도 짤막한 비프음이 났다. 이게 옛날에 키보드 버퍼가 다 차서 경고음이 나던 것과 같은 맥락의 현상이다.

옛날에 컴퓨터 속도가 왕창 느릴 때는 사용자가 화살표 키를 눌러서 화면을 스크롤 하던 도중에도 끊임없이 글쇠 입력 체크를 해야 했다. 그래서 화면 갱신 속도가 글쇠 연타 속도를 따라가지 못한다면 지금 갱신하던 것은 때려치우고 글쇠 처리부터 모두 한 뒤, 이로 인해 화면 위치가 바뀌었으면 스크롤을 처음부터 다시 하고, 변동 사항이 없으면 아까 하다 말았던 스크롤을 마저 계속하게 코딩을 했다.

오늘날은 단순히 2차원 스크롤을 위해서 저렇게 헝그리 코딩을 할 필요는 없을 것이다. 그러나 화면에다 아주 복잡한 3D 그래픽을 점진적으로 렌더링 하거나, 고해상도 만델브로트 집합 같은 프랙탈 그래픽을 실시간으로 그린다면.. 동일한 테크닉이 여전히 필요할 것이다.

CPU를 많이 소모하는 계산 위주의 작업 말고, 주변 기기와의 I/O 비중이 큰 작업도 생각해 보자. 워드 프로세서에서는 인쇄 중 동시작업(일명 스풀링), PC 통신 프로그램에서는 전화 연결 중에, 업· 다운로드 중에 동시작업.. 지금은 너무 당연해서 일도 아닌 게 옛날 도스 시절에는 해당 프로그램의 완전 첨단 고급 기능이라고 소개되곤 했지 않는가?

Windows는 여러 프로그램들을 동시에 띄워서 구조적으로 동시작업이 기능하다고 하지만, 16비트 시절엔 여건이 도스에 비해 막 좋을 건 없었다. 빡센 작업을 하는 중에도 여전히 사용자 반응성을 잃지 않으려면 message loop 차원에서 PeekMessage와 OnIdle 같은 로직이 추가돼야 하고, 작업 역시 UI의 반응성을 해치지 않을 정도로 연산을 짧게 끊어서 찔끔찔끔 해야 하는 건 변함없었다. 이런 정신없는 상태에서 트리 구조 순회나 순열 생성 같은 건 당연히 쌩 재귀호출로 구현할 수 없으며, 사용자 스택 자체 구현이 필수였다.

더구나 이런 idle time processing은 내가 아닌 Windows 내부의 고유한 message loop 하에서 구동되는 modal 대화상자 내지 메뉴 표시 중에는 중단된다는 문제가 있다. 타이머 메시지는 저렇게 modality와 관련된 끊김 현상은 없지만, CPU를 활용하는 효율이 일반적인 idle time processing 메커니즘에 비해 좋지 못하다.

이런 걸 생각하면 멀티스레드가 없었으면 지금처럼 사용자가 입력하는 텍스트의 맞춤법을 실시간으로 검사해서 빨간줄을 그어 주는 기능, C++ 같은 문맥 의존적인 언어 코드를 사용자가 입력하는 걸 인클루드 파트까지 실시간으로 구문 분석해서 자동 완성과 syntax coloring을 구현하는 건 불가능에 가깝게 힘든 일이 될 수밖에 없을 것이다.

4. 파이버: 스레드의 변종

사실, time slicing을 운영체제가 자기 재량껏 하는 게 아니라 내가 원할 때 하도록 thread의 변종인 fiber라는 게 있다. Windows의 경우, 일단 자기 자신을 일반 스레드에서 fiber로 먼저 변환해서(ConvertThreadToFiber) 초기화를 한 뒤, 다른 파이버들을 생성하고(CreateFiber), 파이버들끼리 필요한 타이밍 때 서로 전환(SwitchToFiber)을 하면서 열심히 제 할 일을 하면 된다.

이 경우 스레드 동기화 같은 건 전혀 필요하지 않으며, 멀티스레딩을 표방하면서도 프로그래밍 패러다임은 멀티스레드 성향이 전혀 아니게 된다. 사실, 이건 유닉스 기반의 서버 프로그램의 포팅을 돕기 위해 일부러 도입된 기능이지 실용적으로 딱히 쓰일 일도 없다. 하지만 복잡한 재귀호출이 여러 곳에서 동시다발적으로 일어날 때 자기 스택 상태를 고스란히 보존하면서 작업 context들을 원하는 때에 전환할 수 있다는 점에서는 뭔가 독특한 용도가 있을 것 같기도 하다.

개인적으로 thread가 원래 실타래라는 뜻이니, 컴퓨터 용어로서 thread는 '일타래'라고 번역해서 쓰면 꽤 그럴싸하겠다고 생각해 왔다. 서로 꼬일 수 있는 것까지도 동일한 개념이니까. 그런데 thread의 변종으로서 아예 '섬유'라는 뜻인 fiber는 우리말로 어찌 번역해야 할지 모르겠다. 우리말 순화· 번역이라는 게 이런 추가적인 조어력과 확장성까지 갖추지는 못하는 편이니 대부분 실패하곤 한다.

오늘날 운영체제에서 module이라는 건 EXE, DLL 등 실행 가능한 코드와 데이터, 리소스가 담긴 한 이미지 파일을 식별하는 개념이다. process는 자신만의 독립된 주소 공간을 가진 실행 공간으로, EXE만이 새로 생성할 수 있다. thread는 한 process 안에서 하나 이상 존재할 수 있는 실행 주체이다.
이들에 비해 instance, task는 좀 16비트스러운 용어이다. 32비트 이상부터는 프로세스들이 기본적으로 자기 주소에서 다 혼자 따로 노는 형태이기 때문에 한 모듈(HMODULE)의 여러 instance (HINSTANCE)라는 개념 구분이 별 의미가 없어져 있다.

운영체제에 따라서는 여러 개의 프로세스도 parent/child 관계를 맺고 job이라는 집단을 형성할 수 있다. Windows도 이를 API 상으로 흉내는 내는 걸 지원하지만 막 널리 쓰이지는 않는다. 마치 C가 함수 안에 함수를 공식적으로 지원하지 않는 것처럼(람다 내지 지역 클래스 같은 편법 말고..), 프로세스들도 굳이 계층 구조가 존재하지 않더라도 뭔가 심각하게 불편하거나 불가능해지는 건 없기 때문으로 보인다.

Posted by 사무엘

2017/11/20 08:38 2017/11/20 08:38
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1429

Trackback URL : http://moogi.new21.org/tc/trackback/1429

Comments List

  1. 비밀방문자 2017/11/22 13:42 # M/D Reply Permalink

    관리자만 볼 수 있는 댓글입니다.

    1. 사무엘 2017/11/22 15:31 # M/D Permalink

      우와, 그 책 얼마 만에 다시 듣나 모르겠다..? (10몇 년 전 대학 시절의 전공 서적..)
      님도 컴공이 일취월장하고 있는 게 느껴지는구나? ㅋㅋ

Leave a comment

1. Windows 부팅

Windows 8 내지 10부터던가.. 요즘 Windows에서는 예전까지 오랫동안 쓰이던 통상적인 부팅 전 F8 메뉴가 사라졌다. 하긴, 메뉴가 여럿 있긴 했지만 고르는 건 안전 모드(F5) 또는 네트워크 되는 안전 모드 둘 중 하나밖에 없긴 했다.
그 대신, 부팅 후에 컴을 팩토리 리셋 초기화시키는 메뉴가 곧장 들어간 것은 일단 꽤 유용하며, F8을 눌러야 할 필요를 이걸로 상당수 대체하긴 했다.

하지만 뭐가 꼬여서 애초에 부팅이 안 되고 있을 때는 이 기능에 접근할 수 없어서 더 불편해졌다. 부팅 전의 OS 재설치 및 복구 UI는 사용자가 특정 글쇠를 눌렀을 때 바로 뜨는 게 아니라, 수차례 컴퓨터를 혹사시키면서 부팅이 실패한 것을 얘들이 인지했을 때에 그제서야 슬그머니 띄워 준다. 로직을 왜 이런 식으로 만들었나 모르겠다.

전에도 얘기했듯이, 본인은 업데이트를 받은 뒤에 운영체제가 꼬이고 커널 패닉 뜨는 걸 몇 번 겪은 뒤부터는 진절머리가 나서 업데이트 받는 걸 레지스트리까지 조작해서 강제로 끊어 버렸다. 지금 네트워크는 유료 종량제이니 니 멋대로 함부로 업데이트 받아서 설치하지 말라고 말이다.

CPU와 메모리와 네트웍 트래픽 잡아먹고 하드디스크 용량 잡아먹고, 컴퓨터를 꺼야 할 때 바로 곧이곧대로 꺼지질 않고.. 민폐가 너무 심한 데다, 그런 민폐가 시도 때도 없이 너무 자주 발생하고.. 설치 후에도 뭐가 크게 달라지고 좋아지기는커녕 저런 부작용만 있으니 이 상황에서 누가 업데이트 꼬박꼬박 받고 싶은 마음이 들겠는가?

그거 안 하고도 내 컴은 악성 코드고 뭐고 지금까지 보안 문제 같은 건 하나도 없었다. 이러다가 업데이트에 대해서조차도 현대 서양 의학 불신, 백신 불신 같은 이상한 풍조가 생기지는 않으려나 모르겠다만.. 브라우저를 선택할 권리 운운하면서 웹 표준 외치는 것만큼이나, 필요하지 않은 업데이트를 안 받거나 원하는 타이밍에만 받을 권리도 좀 보장됐으면 좋겠다.

2. 바이오스

뭐, 이건 운영체제 얘기였고 그 전에 롬에 탑재된 컴퓨터 고유의 소프트웨어 계층을 일명 BIOS라고 부른다. 이것도 2010년대에 와서는 UEFI라는 새로운 규격으로 바뀌었다.
운영체제 부팅 전에 BIOS 셋업(CMOS 셋업이라고도 불렀던 듯..)을 들어가려면 ESC, F2, F10, Del 이런 글쇠를 죽어라고 누르곤 했는데, 이 글쇠도 좀 Ctrl+Alt+Del 재부팅처럼 통일이 됐으면 하는 생각이 든다. 바이오스는 어차피 만드는 업체도 피닉스나 아메리칸 메가트렌드 요렇게 아주 소수이지 않던가?

살면서 BIOS setup으로 들어가야 하는 상황은 몇 년에 한 번꼴로 운영체제 변경· 재설치를 위해 부팅 매체 선택 순서를 변경할 때.. 혹은 하이퍼-V 가상화 같은 옵션을 켜고 끌 때 정도이지 싶다. 살다가 병원이나 법원, 경찰서, 주민센터 같은 델 들르는 빈도와 비슷한 격이다.

옛날에는 컴퓨터를 켠 직후에 숫자가 쫘르륵 올라가면서 주 메모리 테스트를 하는 게 관행이었는데 그 광경도 참 오래 전에 사라졌다. 램의 용량이 수백 MB 수준으로 넘어간 시기, 대략 21세기 초쯤부터 없어진 것 같다. 글쎄, 카운트만 안 보여줄 뿐, 내부적으로 여전히 테스트는 하는 걸 수도 있음.

그리고 요즘 컴퓨터는 바이오스 셋업 화면도 텍스트가 아닌 그래픽 모드로 바뀌었고 마우스가 지원된다. 저기는 한글화의 영원한 불모지라고 여겨졌는데 이젠 그것도 아니다. 마소처럼 11172자 완성형 글립을 다 때려박은 게 아니라 이야기체 같은 8*4*4 조합형 비트맵 글꼴을 쓴 것도 있어 더욱 반갑다.

바이오스 차원에서 하드디스크에 predefined type이 40몇 가지 정도 있던 시절도 있었는데.. 안 변하는 것 같아도 이 바닥도 하드웨어의 발전을 따라서 많이 변하고 있다.
그나저나 애플 제조 컴퓨터들은 바이오스 계층도 자체 개발인 거겠지? (켠 직후에 흰 화면에 빵~~! 소리, 그리고 alt 눌러서 부트캠프 동작..)

3. 글꼴

수 년 전부터 개인적으로 '감지'는 했던 현상인데, 어떤 컴퓨터는 글꼴 대화상자를 열어 보면 황당하게도 Times New Roman이나 Courier New 같은 필수 기본 글꼴이 목록에서 빠져 있고 선택 가능하지 않았다. 물론 그 컴퓨터에 실제로는 해당 글꼴이 멀쩡하게 잘만 설치돼 있다.
게다가 더 황당하게도 MS Word 같은 프로그램에서는 그 글꼴을 선택할 수 있었고 메모장이나 워드패드에서만 안 나왔다. 내 경험상 이건 컴퓨터마다 케바케로 발생하는 듯하고 Windows 7 이상 2010년대부터 종종 보였다.

처음엔 글꼴 목록에 영향을 끼치는 악성 코드가 있기라도 한지 의심을 했다. 하지만 알고 보니 이 현상에 대한 해답이 따로 있었다.
"제어판-글꼴-글꼴 설정"으로 들어가서 "언어 설정에 따라 글꼴 숨기기" 옵션을 끄면 사라졌던 Times, Courier 같은 글꼴을 다시 선택할 수 있다. (Designed for your language settings)

Windows 7부터 등장한 옵션은 맞아 보인다. 그런데 멀쩡한 글꼴을 선택할 수 없게 만드는 옵션은 대관절 도대체 왜 도입됐는지 모르겠다. 그런 옵션을 굳이 넣을 거면 글꼴 선택 대화상자 내부에다가 넣거나 show all 같은 버튼이라도 넣어야지 왜 제어판 깊숙한 곳에다가 짱박아 놓았는지도 알 길이 없다.

4. PNG

한때 GIF라는 그림 파일 포맷이 있어서 웹에서 정말 많이 쓰였다. 압축률 좋고 투명색과 애니메이션, progressive 렌더링 같은 독보적인 장점 기능이 많았다. 그러나 GIF는 내부의 압축 알고리즘에 특허가 걸려 있어서 아무나 활용하기가 곤란했다.

물론 이미 만들어진 그림 파일을 보기만 하는 사용자 입장에서는 제약이 걸리는 게 전혀 없고, GIF 파일을 생성하거나 디코딩하는 프로그램을 상업용 제품에다 직접 얹는 제조사의 입장에서 로얄티 같은 게 들었던 것 같다. 휴대용 MP3이나 WMA 재생기를 만들 때처럼 말이다. 전자는 프라운호퍼 연구소에, 후자는 마소에 특허든 저작권이든 뭐든 걸려 있다.

그래도 이 특허는 저작권 자체의 보장 기간(70년)보다는 기간이 훨씬 짧았던 모양이다. 2005년경에 특허가 풀리긴 했다. 그러나 gif는 트루컬러를 지원하지 못한 채 256색에서 발전이 멈춰 버렸기 때문에, 오늘날은 대체제인 PNG에 밀려 서서히 사장되는 중이다. 웹에서 사진용 손실 압축은 JPG가, 그 밖의 범용적인 비손실 이미지는 PNG가 시장을 나란히 양분하는 중이다.

PNG는 GIF보다 압축률이 더 좋고 트루컬러도 지원하며, 단순 color key 기반 투명보다 더 발전한 알파채널도 지원한다. 심지어 고화질 아이콘을 저장하는 컨테이너로도 이용되고 있다.
그러니 GIF의 대체물이 되기에 손색이 없다. 다만, 알파 채널은 1990년대 PNG의 첫 버전과 동시에 등장한 게 아니라 나중에 추가된 확장 규격이기라도 한지, 제대로 지원하는 프로그램이 여전히 적어서 아쉽다.

또한 PNG도 애니메이션 기능은 없다. APNG라는 규격이 있기는 하지만 웹 표준이 아니기 때문에 파이어폭스 같은 극소수의 브라우저 말고는 지원하지 않는다. 플래시가 없어지고 브라우저 자체의 동영상 코덱 규격조차 표준화가 논의되고 있는 와중에 애니메이션용 이미지도 더 늦기 전에 표준이 마련되고 모든 브라우저들이 지원해 줘야 하지 않나 생각이 든다. 하긴 옛날에는 동영상도 코덱 파편화가 무진장 심해서 '통합 코덱' 이러면서 혼란이 말도 아니긴 했었다.

5. high DPI 관련

Windows에서 high DPI 지원 정책은 한번 만들어 놓은 걸로 끝이 아니고 버전을 거듭할수록 마개조를 거듭하고 있다. 심지어 같은 Windows 10에서도 새 업데이트에서는 새 기능이 들어갔다.

8.1에서인가 그때부터 per-monitor high DPI이라는 게 도입된 걸로도 모자라서 이제는 아예 스레드별로 high DPI-aware 여부를 지정하고 그걸 on-the-fly로 변경하는 기능까지 추가됐다.
DPI의 변경은 너무 파격적인 변화여서 원래는 재부팅이 필요했으며, Windows Vista 시절에는 믿어지지 않지만 관리자 권한까지 필요하던 작업이었다. 그리고 어차피 제대로 대비가 돼 있는 유연한 프로그램도 매우 드물기 때문에 변경이 권장되지 않기도 했다.

그랬는데 그게 그래픽 카드의 성능 발달 덕분에 실시간 변경이 가능한 기능으로 서서히 바뀌어 간다. 그래도 마소는 레거시 호환성에도 목숨을 거는 곳이니, DPI 변화의 대비가 안 돼 있는 레거시 프로그램은 그냥 가상화 샌드박스빨로 통째로 속이고 말이다. Windows의 역사상 동일 기능이 위상이 이렇게 드라마틱하게 변한 다른 예는 찾기 어려울 것이다.
원래 화면 해상도나 색깔수를 바꾸는 것조차 먼 옛날에 Windows 3.x 시절에는 재부팅이 필요한 작업이었지만 9x/NT부터는 실시간 변경이 가능해졌지 않던가? DPI 변경도 그렇게 바뀌었다.

그도 그럴 것이, high DPI라는 게 원래는 시력 나쁜 사람을 위한 장애인 접근성에 가까운 잉여 기능이었다. 하지만 21세기에 모니터의 해상도가 급격히 올라가면서 이건 모든 사람에게 필요한 편의 기능이 됐다.

이 점에서 high DPI는 마치 자동차의 자동 변속기와도 비슷한 구석이 있다. 원래 자동 변속기 전용 면허는 왼발용 페달, 오른손용 다기능 스위치, 시청각 장애인용 볼록 거울처럼 장애인의 운전을 위한 여러 면허 조건 중 하나였다. 자동 변속기가 운전하기가 훨씬 더 쉬우니 장애인에게도 더 유리하니까 말이다. 그랬는데 자동 변속기가 워낙 대중화되고 나니 자동 전용 면허만은 1997년부터 일반인도 취득 가능하게 바뀐 것이다.

원래 화면 확대 배율이 기본값인 동시에 최소값일 때의 DPI 값은 96이었다. 이건 사실상 하드코딩된 채 쓰여 온 값인데, 언제부턴가 Windows SDK 헤더 파일을 보니 요게 USER_DEFAULT_SCREEN_DPI 라는 상수 명칭으로 추가되었다. 마치 마우스 휠의 기준값인 WHEEL_DELTA (120)과 성격이 비슷해 보이는데, 아무튼 GetDeviceCaps(hDC, LOGPIXELSX)의 리턴값과 저 96의 비율을 계산하면 확대 배율을 얻을 수 있다.

그런데 Windows가 존재하는 한 LOGPIXELSX와 LOGPIXELSY의 값이 서로 달라질 일은 설마 없겠지..?
모니터가 일부러 종횡비가 더 큰 와이드 화면으로(4:3 → 16:9) 바뀐 와중에, 논리적인 화면 종횡비를 또 보정해야 하는 건 옛날 CGA 640*200이나 허큘리스 720*348 해상도 시절 이래로 이제 없으리라 여겨진다.

옛날에는 멀티 모니터조차 예견을 못 하고 WM_CONTEXTMENU 메시지에서 마우스 클릭이 아닌 키보드 글쇠는 x, y 좌표가 모두 -1인 것으로 구분하게 스펙을 설계한 적이 있었는데.. 그때와 지금은 참 격세지감이다.
또한, 해상도 차이가 많이 나는 모니터를 둘 이상 연결해서 사용할 때를 대비해서, 이제는 두 모니터가 DPI 설정이 서로 다른 것까지 다 지원해야 한다. 그러니 high DPI를 제대로 지원하는 일이 더욱 복잡해진 것이다.

6. 네트워크가 안 될 때

집에서는 이런 현상이 없는데 회사에서는 가끔씩 멀쩡히 랜선이 꽂혀 있는데도 유선 인터넷이 안 되는 경우가 있다. 이때는 ipconfig /renew를 해 주면 문제가 해결되는 편이다.
때로는 /release도 해야 하고, 한번은 '설정'(제어판 말고)으로 들어가서 네트워크 관련 메뉴 맨 아래의 '전면 초기화'+재부팅까지 한 뒤에야 문제가 해결된 적이 있었다. 그 당시에 무엇이 정확하게 문제였는지 나로서는 알 길이 없지만, 아마 공유기 쪽 문제인 듯하다.

7. USB 메모리 관련

USB 메모리를 꽂아 쓰다 보면.. 분명히 작업을 마쳤고 관련 프로그램들을 다 종료한 뒤, 메모리를 빼려고 하는데 안전하게 분리가 안 된다고 운영체제가 꼬장을 부려서 난감해지는 경우가 있다.
Windows 2000 시절에만 해도 USB 메모리를 무단으로 뽑으면 경고 메시지가 나왔지만, 그게 그렇게까지 위험하지는 않고 괜히 사용자를 불안하게 만들 필요는 없다고 판단되었는지 XP와 그 이후부터는 경고 메시지가 사라졌다. 그러나 그렇다고 해서 무단 분리가 전혀 위험하지 않다는 얘기는 아니다.

USB 메모리를 분리하는 명령은 시스템 트레이에만 있는 게 아니라 의외로 해당 드라이브의 셸 우클릭 메뉴에도 존재한다. 마치 CD롬 드라이브의 우클릭 메뉴에 '디스크 꺼내기' 명령이 있는 것처럼 USB 메모리 드라이브도 우클릭하면 'eject'가 있으며, 이 명령을 이용해서 분리하면 트레이 메뉴 명령보다 성공률도 더 높다고 그런다.

경험상 macOS는 지금까지 쓰면서 USB 메모리 제거가 바로 안 되는 경우를 거의 못 본 거 같다.
그런데 pkg 파일을 열어서 설치하는데.. USB 메모리의 것을 바로 여니까.. insert the "(null)" disc to continue installation 이러면서 설치가 제대로 되지 않았다.
저건 %s 포맷 문자열에다가 null 포인터를 주기라도 했는지 메시지의 형태도 비정상일 뿐만 아니라, 배포 패키지 파일이 깨지고 문제가 있을 때에나 나타날 법한 메시지이다.

하지만 파일이 실제로 깨진 건 전혀 아니었으며, pkg 파일을 하드디스크에다 복사한 뒤에 설치하니까 아무 문제 없이 됐다.
왜 저런 현상이 있는지는 잘 모르겠다.

8. USB가 없던 시절의 컴퓨터 단자

그러고 보니 먼 옛날에.. USB 포트가 없던 시절에는 컴퓨터에 주변기기를 인식시키는 용도로 직렬 포트와 병렬 포트라는 게 있었다.
정확하게 뭐가 직렬 내지 병렬이어서 이런 명칭이 붙었고 서로 어떤 장단점이 있는지 잘 모르겠다. 라디오에 AM과 FM, 인터넷 프로토콜에 TCP와 UDP처럼 일장일단이 있는 관계가 아닌가 생각된다.

직렬 포트는 COMn 이런 이름이 붙어서 주로 마우스나 모뎀이 연결되었다. 그리고 병렬 포트는 LPTn 이런 이름과 함께 프린터나 스캐너 같은 기기가 연결되었으며, 과거에 쓰이던 하드웨어 방식 불법 복사 방지 장치 '락'도 대체로 병렬 포트에다 꽂는 형태였던 것 같다.

으음.. 그럼 외장 하드는 어디에다 꽂았지? 옛날에 하드 디스크끼리 꽂아서 데이터를 복사하는 건 정말 아무나 할 수 있는 일이 아니었다.;;
꽂고 나서는 바이오스 설정을 들어가서 이게 무슨 기기인지 설정을 수동으로 해 줘야 했다. plug and play 그런 건 없었다. 코덱이 너무 난무해서 동영상 보는 데 애로사항이 꽃폈고, 유니코드가 없어서 문자 인코딩이 어느 장단에 맞춰 춤을 춰야 할지 알 수 없던 그런 열악하던 시절의 추억이다.

Posted by 사무엘

2017/11/09 08:37 2017/11/09 08:37
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1425

Trackback URL : http://moogi.new21.org/tc/trackback/1425

Comments List

  1. 김 기윤 2017/11/09 09:13 # M/D Reply Permalink

    1. 평소 단순 업데이트할때는 문제가 없는데 가끔 대형 업데이트 할 때마다 입력기 설정이 리셋되어서 당황하는 경험이 종종 있었습니다. 재부팅 여러번 하고 Windows.old 폴더가 생기는 것을 볼때 이름만 업데이트고 내부적으로는 아예 새 OS 를 설치하는 동작을 하는게 아닌지 추정합니다.

    2. 지난달까지 2009년에 맞춘 PC 를 사용하다가 이번에 새 PC 를 조립하면서 드디어 UEFI 로 된 바이오스를 처음 사용해 보았습니다.(..) 이에 맞추어(레거시 부팅이 아닌 UEFI 부팅을 위해) SSD도 MBR 에서 GPT 로 다시 맞추어 포맷하고 이것저것 세팅했군요. 가끔 파워 유저는 오버클럭때문에라도 BIOS 들어갈 일이 많다고 하기는 합니다....만 이것도 안정화되면 들어갈 일이 사라지기는 마찬가지군요..;

    4. 트위터에서는 gif 를 올리면 그걸 그대로 저장하고 보여주는게 아니라, 동영상으로 인코딩해서 보여준다고 하는군요. gif 보다도 오히려 동영상이 더 유리하다고 판단했겠지만, 기술 발전이 느껴지기도 하는 부분입니다.

    1. 사무엘 2017/11/09 10:20 # M/D Permalink

      기윤 님! 정말 오랜만에 뵙네요. 반갑습니다. ^^;; 잘 지내시죠?
      1. 네, 그 정도면 서비스 팩 설치 이상급의 중대한 업데이트라고 봐야겠네요.

      2. PC는 최신 사양 게임을 돌린다거나 특별히 생업용으로 왕창 큰 동영상· 이미지(2D/3D) 편집하는 게 아니면, 이제 자주 업데이트 할 일이 없어졌지요. 저도 지금 맥북을 언제 교체하게 될지 모르겠습니다. ^^

      4. 미디어 플레이어 ActiveX 없이, 플래시 우클릭 메뉴 없이 아무 브라우저에서나 웹페이지에서 동영상을 볼 수 있게 된 지가 얼마 안 됐지요. 신기한 일입니다.
      gif만큼이나 Windows의 애니메이션 컨트롤도 위치가 굉장히 어정쩡해진 감이 있습니다. (무압축 또는 완전 초간단 런렝쓰-_-;; 방식으로만 압축된 avi..)

Leave a comment

본인은 초딩 시절에 어렴풋이 경험했던 옛날 컴퓨터 환경의 추억을 회상하는 일에 관심이 많다. 16비트 환경은 베이직 이외에 C/C++ 같은 급의 언어로 프로그램을 직접 개발한 경험이 전무하기 때문에 더 관심이 간다.
그래서 블로그에서 API 썽킹 얘기도 한번 했고, 1년 남짓 전에는 Windows 9x 시절에 악명 높던 리소스 퍼센티지를 32비트 프로그램에서 직접 구하는 방법까지 옛날 책을 뒤져가며 복습해 봤다. 이번에는 이와 관련하여 지금까지 다룬 적이 없었던 다른 기술 얘기를 좀 해 보겠다.

시대를 풍미했던 Windows 9x는 왜 그리도 블루 스크린(BSOD)이 자주 뜨고 불안정했을까? 흔히 지저분한 16비트 코드 잔재 때문이라고들 그런다.
80386급 이상의 CPU에서 제공되는 보호 모드, 가상 메모리 같은 기술을 적극적으로 활용하면 굳이 그렇게 허접한 운영체제가 만들어질 일이 없다. 하지만 문제는 단 하나.. CPU가 원론적으로 제공하는 기능들을 제대로 활용해서 이상적인 운영체제를 만들려면, 당시 서민들이 범접할 수 없던 엄청난 메모리와 속도빨이 필요하다는 것이다.

Windows 9x는 Windows NT와 같은 최신 32비트 선점형 멀티태스킹 환경뿐만 아니라 기존 16비트 도스/Windows 프로그램과의 호환성도 놓치지 말아야 하고, 이런 모든 미래와 과거 이념을 Windows 3.1과 크게 차이 나지 않는 1990년대 중반의 서민들 컴에서 그럭저럭 구현해야 했다. 그러니 얘는 태생적으로 아주 기괴한 방향으로 개발될 수밖에 없었다.

그래서 파일 시스템이나 메모리처럼 반드시 32비트 덕을 봐야 하는 엔진 부분인 kernel은 32비트 코드 위주로 개발했지만, user나 gdi처럼 운영체제의 단순 외형과 관련된 부분은 16비트 코드를 그대로 답습했다. 이쪽 함수는 비록 32비트 프로그램에서 user32.dll, gdi32.dll을 통해 호출했다 하더라도 결국은 argument들을 thunk 해서 16비트 user.exe와 gdi.exe로 내려가서 기능이 수행된다는 뜻이다.

개량된 트루타입 글꼴 래스터라이저처럼 GDI 계층에도 32비트 코드로 다시 작성된 게 없지는 않지만 기본적인 틀은 여전히 16비트 기반이다.
Windows NT는 16비트 썽킹은커녕 훨씬 더 안정적인(하지만 속도는 좀 느려지는) 별개의 API layer가 있는 지경인데.. 9x와 NT가 서로 설계 이념과 처지가 얼마나 극과 극인지를 알 수 있다.

이런 설계 방식 때문에 Windows 9x는 16비트 heap 크기에 맞춰진 리소스 퍼센티지 한계가 여전히 걸려 있으며, GDI 좌표계도 기본적으로 32비트가 아닌 16비트 크기이다. 그래도 이런 건 그냥 한계와 제약일 뿐, 딱히 운영체제의 안정성과 관련된 문제는 아니다.
Windows 9x는 메모리 절약과 하위 호환성을 위해 (1) 16비트 코드를 많이 재활용했는데, 이걸 온전한 가상 머신 샌드박스 안에서 구동하는 게 아니라 (2) 32비트 코드와 동일한 주소 공간과 위상에서 동일한 권한으로 섞인 채 최대 성능으로 실행하는 것을 허용했다. 16비트 코드가 운영체제 차원에서 대놓고 돌아가는 부분도 있으니 저렇게 안 해 주면 안 된다.

그리고 이를 구현하는 과정에서 Windows 9x는 NT와 같은 급의 (3) 엄격한 메모리 보호를 포기했다. 스레드 동기화나 가상 메모리 같은 현대적인 개념이 없이 쑤제 어셈블리어로 코딩된 레거시 코드가 한둘이 아니니, 걔네들이 있는 그대로 돌 수 있게 시스템의 안정성을 일부 희생하게 된 것이다.

32비트 프로세스가 독자적으로 사용하는 최신식 private 메모리야 9x도 NT의 동작 방식을 물려받았기 때문에 각 프로세스별로 동일하게 보호가 잘 된다.
그러나 9x에서는 32비트 프로세스라 해도 16비트 프로그램과의 호환을 위해 남겨놓고 있는 4MB 이내 주소대의 영역은 보호가 적용되지 않으며, 반대로 운영체제 시스템 DLL이 로딩되는 상위 영역도 성능 오버헤드 간소화를 위해 모든 프로그램들이 씨크하게 같은 주소로 공유된다. 보호 같은 거 없다.

나쁜 마음 먹은 프로그램이 이런 16비트 호환 영역이나 시스템 DLL 영역 주소를 0으로 덮어써 버린다거나 하면 운영체제를 BSOD와 함께 곧장 다운시킬 수 있다. Windows NT에서는 메모리 덮어쓰기만으로는 이런 사태가 절대로 발생하지 않고 문제의 프로그램만 강제 종료되고 운지하는 것으로 끝난다. 하지만 9x는 그 정도로 튼튼하지 못했다. 메모리 보호 강도가 반쪽짜리일 뿐이라는 뜻이다. 반쯤은 응용 프로그램이 선할 거라고 믿고 곧이곧대로 돌려 주는 셈이었다.

이렇게 메모리 보호 말고 Windows 9x가 안정성이 결정적으로 취약한 분야는 멀티스레드와 관련하여 또 있었다.
Windows 9x/NT는 3.1에서는 꿈도 꿀 수 없던 선점형 멀티태스킹이라는 것을 도입했다. 한 프로그램이 자발적으로 CPU 자원을 반납하지 않고 무한 뺑뺑이를 돌더라도 운영체제가 강제로 CPU 자원을 뺏어서 다른 프로그램에게 골고루 분배해 줄 수 있다. 또한 한 프로그램 안에서 UI 스레드 따로, 작업 스레드 따로 운용이 가능하다. 작업 스레드가 재귀호출까지 마음대로 하는 동안 사용자의 입력에도 매끄럽게 응답할 수 있다는 뜻이다.

즉, 가상 메모리가 메모리 주소를 잘못 건드리는 것만으로 타 프로그램이나 운영체제를 뻗게 하지 않게 하는 공간 보호막이라면, 선점형 멀티태스킹은 한 프로그램이 CPU를 독식해서 시스템 전체의 동작을 먹통으로 만들지 않게 하는 일종의 시간 보호막이다.

선점형 멀티태스킹 환경에서는 내 스레드가 받고 있던 CPU 포커스가 싹 바뀌어서 타 스레드로 이동하는 게 정말 예고 없이 불시에 될 수 있다. 컴퓨터의 모든 코드가 자기 자신의 스택과 지역변수만 갖고 놀면서 고립된 채 돌아가는 게 아니며, 한 자원을 여러 스레드들이 공유하는 경우가 많다. 그런 코드에 둘 이상의 실행 주체가 동시에 진입했다간 프로그램 실행이 왕창 꼬여 버린다. 이건 마치 화장실에 문을 잠그는 기능이 없어서 누가 볼일이 아직 안 끝났는데 아무 예고 없이 문이 확 열리고 딴 사람이 들어오는 것과도 같다.

결국 스레드 사이에는 교통 정리 기법이 필요하며, 이를 위해 크리티컬 섹션, 뮤텍스 등 다양한 커널 오브젝트들이 존재한다. 여기까지는 아무 문제가 없다.
그런데 문제는.. 저 16비트 레거시 코드들도 선점형 멀티태스킹 통제의 대상이라는 것이며, 그 코드들은 레거시답게 멀티스레드 동기화 같은 대비가 전혀 돼 있지 않다는 점이다. 그런 걸 고려할 필요가 없는 환경에서 개발되고 구현된 코드이니까 말이다.

이것도 메모리와 마찬가지로 16비트 코드를 완전히 자기 가상 머신에서 따로 돌게 하면 별 문제될 게 없으며, Windows NT는 실제로 ntvdm이라는 16비트 가상 머신을 돌렸다.
하지만 Windows 9x는 가상 머신을 돌릴 여력이 안 되는 PC를 대상으로 성능을 얻는 대신 안정성을 희생하는 방법을 택했다. 바로, 16비트 GUI 쪽 코드는 GetMessage, PeekMessage 같은 함수로 명시적으로 CPU 자원을 반납하지 않는 동안에는.. 전체를 동기화 오브젝트로 둘러싸서 어떤 경우에도 여러 스레드들의 동시 진입이 되지 않게 한 것이다. 이름하여 Win16Mutex라는 무시무시한 시스템 동기화 메커니즘이다.

이게 무슨 뜻인가 하면.. Windows 9x도 16비트 프로그램만 줄곧 돌린다면 과거의 Windows 3.x와 별 차이 없이 동작하게 된다는 뜻이다. 제 기능을 활용할 수 없다.
물론 32비트 코드도 16비트로 thunk하는 gdi/user 함수를 호출할 수 있다. 걔네들은 그 함수가 실행되는 동안에만 Win16Mutex 안에 잠시 들어갔다가 나온다. 그러나 16비트 프로그램은 Get/PeekMessage를 호출하지 않고 실행되고 있는 동안 계속해서 Win16Mutex를 붙들고 있게 된다. 그리고 그 동안 운영체제의 그 어떤 프로그램도 16비트 코드를 수행할 수 없으며, 앞 프로그램의 실행이 끝날 때까지 기다려야 한다.

어떤 프로그램이 창을 띄웠다가 while(true) 같은 데라도 빠져서 응답이 멎었다고 치자. 그렇다면 32비트 프로그램은 ctrl+alt+del을 누른 뒤 작업 목록에서 그럭저럭 강제 종료를 할 수 있었다. 이 기능 자체는 NT뿐만 아니라 9x 계열에서도 있었다.
하지만 옛날 기억을 꺼내 보면, 16비트 프로그램은 그렇게 깔끔하게 강제 종료가 잘 되지 않았다. 16비트 프로그램이 응답 불가 상태가 되면 운영체제 전체가 실행이 불안정해졌으며, 해당 프로그램을 강제 종료한 뒤에도 매우 높은 빈도로 블루 스크린이 계속되다가 운영체제가 뻗었다. 그 이유가 바로 (4) 16비트 코드는 애초에 선점형 멀티태스킹 대상이 아니며, 16비트 코드의 실행이 32비트 코드의 gdi/user계층 기능에까지 직통으로 영향을 끼치기 때문이다.

이제야 퍼즐 조각이 끼워맞춰진다. 저건 메모리 보호와는 별개의 영역의 문제이다.
그럼에도 불구하고 Windows 95는 안정성이 더 헬게이트이던 Windows 3.x보다 많이 나아지지는 못해도 최소한 더 나쁘게 만든 건 없다. 그러니 제품으로 나와서 1990년대 중반의 PC 환경을 획기적으로 바꿔 놓을 수 있었다.

물론 Windows 9x도 Windows API와 무관하게 완전히 분리된 환경인 도스용 프로그램에 대해서는 그럭저럭 샌드박스를 지원하고 있었다. Windows NT의 ntvdm가 지원하지 못하는 더 다양한 도스용 프로그램을 직통으로 구동해 주면서도 말이다. Windows 3.x때부터 386 확장 모드가 도입되면서 Virtual 8086 모드를 이용해 도스창을 여러 개 동시에 여는 게 가능해졌다. 9x부터는 도스창을 강제 종료도 할 수 있게 됐다.

단지, 하드웨어를 너무 저수준으로 제어하는 도스용 프로그램이라면 대책 없으며, 16비트 Windows GUI 프로그램은 32비트 프로그램과 자원을 어중간하게 직통으로 공유하다 보니, 운영체제 전체에 여파가 끼치는 잠재적인 위험이 상존했던 것이다.

Windows NT, 9x, 그리고 더 과거의 win32s를 비교하면 다음과 같다.

  Windows NT Windows 9x Windows 3.1 + Win32s
PE 방식의 32비트 EXE/DLL 실행, 32비트 메모리 접근 O O O
프로세스 별 고정되고 독립된 주소 공간 보장 O O X
DLL도 인스턴스별 독립된 공간 보장 O O X
선점형 멀티태스킹과 멀티스레드 O O X
레지스트리, 32비트 파일 시스템, 최신 TTF 래스터라이저 O O X
온전한 메모리 보호 O △ (private area만. 도스 호환 영역과 커널 영역은 보호되지 않음) X
유니코드 API O △ (코드 변환, 메시지, 기본적인 문자 찍기 정도만 한정) X
유니코드 기반 O X X
user, gdi 계층이 완벽하게 32비트. 리소스 제약 없음 O X X
16비트 프로그램 가상화 O (안정적임) X (도스용 프로그램만 가상화. 느리고 메모리 적은 컴 친화적) X
하드웨어 계층 추상화 O (이식성. 하지만 느림) X (x86 전용, 저사양에서 성능 최적화) X
NTFS 파일 시스템 O X X
시스템 요구사항 압도적으로 제일 높음 win32s보다 더 높지만, NT보다는 훨씬 낮음 제일 낮음

이상.
지금 생각해 보면 컴퓨터가 성능이 정말 열악하던 시절에 어째 저런 유리몸 Windows를 어떻게 쓰고 지냈나 싶다. 특히 9x 계열 중에서도 1호인 Windows 95는 다음과 같은 점에서도 안정성이 굉장히 안습했으며, 지나치게 고성능인 컴퓨터를 감당(?)하지 못하는 물건이었다.

  • 잘 알다시피 디렉터리명에 CON 같은 예약어가 들어간 채로 파일 목록 조회 같은 요청을 하면 운영체제 전체가 뻗는다. 탐색기이든 dir 명령이든 마찬가지. 95/98에서는 별도의 버그 패치가 나왔고, ME에서야 버그가 처음부터 완전히 수정되었다.
  • 부팅 후에 밀리초 단위로 부호 없는 32비트 정수 범위를 초과하는 대략 7주(49.x일) 이상 계속 켜져 있으면 역시 뻗음. Windows 95는 알고 보면 7주짜리 시한폭탄이었던 셈이다. 하지만 이게 NT도 아니고, 무려 7주가 지나기 전에 십중팔구 어차피 다른 이유로 다운되고 재부팅이 일어났기 때문에 이 문제가 별로 부각되지 않았을 뿐이다. 이 문제 역시 별도의 버그 패치가 추후 공개되었다.
  • 저사양 PC에서 가상 메모리를 관리하는 방식의 한계로 인해, 램이 512MB보다 더 많은 컴에서는.. 단순히 초과 잉여 영역이 인식되지 않고 무시되는 게 아니라 그냥 부팅되지 않고 뻗는다.
  • 클럭 속도가 대략 2.1GHz보다 더 높은 컴에서는 Network Driver Interface Specification라는 계층에서 오류가 발생하고 뻗었다고 한다. 너무 빠른 컴퓨터에서 레거시 코드가 문제를 일으킨 유명한 다른 사례로는 볼랜드 파스칼의 crt 유닛이 266MHz보다 더 빠른 컴에서 오류를 일으켰던 것이 있다. 이런 것들은 대체로 내부적으로 시간 측정을 하다가 0으로 나누기 오류가 발생하는 형태인데, Windows의 저 문제도 마찬가지였던 것 같다.

그러니 옛날부터 컴덕들은 OS/2나 Windows NT 같은 신세계를 갈망하긴 했지만, 그걸 돌리려면 흙수저 가정에서는 엄두를 못 낼 정도의 비싼 고성능 컴퓨터가 필요했다. 윈95가 나왔던 시절에는 램 16MB도 돈지랄 감지덕지이던 시절이고 32~64MB는 가히 꿈의 영역이라 여겨졌었으니 말이다.
그러다 1990년대 후반에 가정용 보급형 PC들도 램 용량이 급증하여 100수십 MB급이 되었다. 그제서야 하드디스크 스와핑/thrashing을 볼 일이 없어졌으며, 마소의 입장에서는 NT와 별개로 굳이 헝그리한 9x 커널이 존재해야 할 명분이 사라졌다.

지금까지 옛날 이야기를 많이 늘어놓았는데..
본인은 우리나라 역사 내지 이념을 Windows의 개발 내력에다 비유해서 설명하기도 한다. 매우 적절한 비유라고 개인적으로 생각하기 때문이다.

우리나라가 이 승만 대통령을 비롯해 핵심 내각은 독립 운동가· 광복군 위주로 철저하게 깨끗하게 건국되었음에도 불구하고 중간과 말단의 군· 경 간부는 친일 부역자들도 불가피하게 그대로 재등용하여 운용되었다. 독립 운동가를 적극 무료 변론하다가 일제에게 찍혀서 면허 정지까지 당했던 애산 이 인 선생이 해방 후에 법무부 장관이 되고 나서는 오히려 반민특위의 해체에 앞장섰을 정도였다.

이게 Windows 95가 일단 겉으로는 32비트 코드 기반으로 개발되고 32비트 주소 공간과 선점형 멀티태스킹을 제공함에도 불구하고 내부적으로 16비트 코드를 잔뜩 재등용하고 메모리 보호가 완전하지 못했던 이유, 수시로 파란 화면 뜨고 불안정했던 이유와 정확하게 일맥상통한다.

한 마디로 그 시절에 가정용 컴퓨터에서 Windows NT 같은 운영체제를 돌리는 건 불가능했기 때문이다. 아직 조선과 일제 강점기 사고방식에 쩔었고 공산주의가 뭔지도 모르던 사람이 태반이던 시절에.. 게다가 북괴의 위협과 비열한 공작까지 횡행하던 시절에, 일제 치하에서 행정 유경험자를 재등용하지 않고서 치안을 유지하고 자유 민주주의 국가를 FM대로 이상적으로 세우고 굴리는 것도 동급으로 절대 불가능했다.

Windows 95의 한계에 대해서 정리해 보니 이 생각이 더욱 굳건하게 확신이 든다. 그 시절의 운영체제든, 194~50년대의 우리나라 사정이든 흑역사와 한계는 불가피하게 발생했던 것이지 악의적으로 일부러 발생한 것이 절대 아니었다. 아폴로 17호 이후로 인간이 달에 안/못 가고 있는 이유는 다른 악의적인 거짓이나 음모 때문이 아니라, 그저 단순히 재정이 부족하고 가성비가 안 맞기 때문인 것과도 같은 이치이다.

쓸데없이 자국 비하를 조장하고 사람 정신건강을 해치고 일제보다 더 나쁜 악을 정당화하고 무마하는 이 악하고 해로운 생각은 보이는 족족 반박하고 뿌리뽑아야 하며, 거기에 사로잡혀 있는 사람들을 산업화하고 일깨워 줘야 한다.

Posted by 사무엘

2017/10/14 08:36 2017/10/14 08:36
, ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1416

Trackback URL : http://moogi.new21.org/tc/trackback/1416

Comments List

  1. 비밀방문자 2017/10/19 04:09 # M/D Reply Permalink

    관리자만 볼 수 있는 댓글입니다.

    1. 사무엘 2017/10/19 12:46 # M/D Permalink

      오랜만이구나, 반갑다? 잘 지내고 계신가!
      이 2, 30년 묵은 옛날 Windows 프로그래밍 배경 얘기가 이해가 될 정도이면 대단한 한편으로 님도 슬슬 아재력이 늘어 가는 것 같다? ㅋㅋㅋ

Leave a comment

Visual Basic 6은 이제 개발사로부터 지원이 중단된 지 무려 10년이 돼 가는데(나온 지는 20년..!) 아직도 현업에서 쓰는 경우가 있는지 모르겠다. Visual C++ 6도 업계에서 도를 넘는 노인학대를 당해 온 물건이긴 하지만, 그래도 얘는 이제는 거의 은퇴한 듯하다. 그리고 VB6과 VB .NET은 VC6과 VC .NET하고는 처지가 완전히 딴판으로 다르다.

비주얼 베이직이 오늘날까지 인류에게 남긴 독보적인 GUI 유산은 바로 property grid이지 싶다. 이거 원조가 바로 VB이다.

사용자 삽입 이미지

이건 운영체제의 공용 컨트롤로 제공되지는 않는다. 하지만 닷넷에서는 자체 구현한 컴포넌트가 있는 듯하며, 네이티브 환경에서는 그냥 3rd-party GUI 툴킷에서 구현해 놓은 레플리카 내지 짝퉁이 쓰인다.

property grid는 오늘날까지 Visual Studio IDE에서 Alt+Enter 속성 창과 프로젝트 속성 대화상자에서 고스란히 볼 수 있다. 수십 개의 설정들이 추가되더라도 번거롭게 대화상자를 디자인할 필요 없이 설정을 뒤에다 추가만 하면 되니 참 편하다.
이에 비해 VC6의 옛날 속성 대화상자는 얼마나 추레하게 생겼는가?

단, 외형이 깔끔하긴 해도 너무 사무적이고 재미없게 생겨서 그런지, 개발툴이나 DBMS 말고 일반 사용자용 Office 제품 같은 데서는 property grid가 등장하는 걸 여전히 본 적이 없는 것 같다.

Visual Basic은 1991년 5월에 Windows용으로 1.0이 첫 출시됐다. 드래그 앤 드롭 방식으로 폼을 디자인하고 곧장 이벤트를 추가하는 방식으로 코딩을 하는 굉장히 획기적인 개발툴이라고 찬사를 받았음이 틀림없다. Windows용의 호평에 힘입어 그 해 9월에는 처음이자 마지막으로 도스용 비베도 1.0이 나와서 QuickBasic과 MS Basic PDS의 라인을 종결시켰다. 하지만 VB의 UI 엔진은 경쟁작이던 볼랜드 Turbo Vision 라이브러리에 비해서는 인지도가 매우 낮다.

그 뒤 VB 2와 3은 16비트 Windows용으로 나와서 인기를 얻다가 95년에 나온 4.0은 16비트용과 32비트용이 나란히 동시에 출시되었다. 마소에서 제품을 이런 식으로 동일 버전을 16비트용과 32비트용으로 동시에 내놓는 건 극히 드물었고 아마 VB4가 거의 유일했다. Office나 VC++는 그냥 상위 버전에서 곧장 32비트용이 나오면서 16비트 지원을 중단하는 형태였기 때문이다.
물론 VB도 5부터는 당연히 32비트 전용으로 갈아탔다. VB6 이후의 .NET에 맞춘 언어 마개조의 역사는 굳이 여기서 더 말할 필요가 없을 것이다.

델파이(네이티브 코드 지원 RAD), Java(압도적으로 넓은 플랫폼 지원, 인지도, 점유율)와 C#(닷넷 지원 킹왕짱) 같은 경쟁 솔루션이 너무 쟁쟁한테 비주얼 베이직 프로그래머 수요가 국내에 얼마나 되는지는 잘 모르겠다. 그나저나 ASP도 비베와 비슷한 문법인 걸로 아는데 그건 살아 있나?
또한 비베가 .NET 으로 바뀌면서, 기존 Office와 Visual Studio IDE에서 제공되던 VBA 매크로 언어까지 반쯤 낙동강 오리알 레거시로 전락한 것도 좀 아쉬운 점이다. 덕분에 Visual Studio 201x 최신 IDE는 지금도 제대로 된 키/스크립트 기반 매크로가 없는 걸로 본인은 기억한다.

이런 비주얼 베이직과 달리 C/C++ 컴파일러 라인은 원래 IDE 같은 게 없다 보니 도스/Windows 플랫폼은 그리 타지 않았다. C/C++은 베이직과는 완전히 다른 저수준 고성능 시스템 프로그래밍 언어이지 않던가? Windows는 NT 이전엔 애초에 자체적인 명령 프롬프트라는 게 없던 물건이었고, C 컴파일러는 도스 환경에서 스위치만 바꿔서 도스뿐만 아니라 Windows, 그리고 그 당시 중요한 플랫폼이던 OS/2용 프로그램을 크로스 컴파일했다.

그러다 1990년대 초에 이쪽은 C++ 언어 추가 → MFC 도입 → MS C/C++ 8.0 대신 Visual C++ 1.0으로 명칭 변경 같은 중요한 사건을 겪었으며, 리소스 편집기와 간단한 소스 코드 에디터가 16비트 Windows용으로 나왔다.
그리고 1993년, Windows NT가 출시되면서 NT용 32비트 Visual C++ 1.0이 별도로 나왔지만 이때는 NT는 시장 점유율이 아주 미미했으니 별 재미를 못 봤다.

그 뒤 1993~94년 사이에 Visual C++은 16비트와 32비트가 서로 약간 엇갈린 길을 갔다. 16비트용은 1.5 ~ 1.52c가 나온 뒤 지원이 중단됐고, 32비트용으로는 2.0이 나왔다. 하지만 아직 Windows 95도 없던 시절에 NT밖에 지원하지 않는 32비트용 VC++ 2는 정말 존재감이 없다. 이 32비트 바이너리를 Windows 3.1에서도 아쉬운 대로 돌릴 수 있게 하기 위해 Win32s라는 런타임이 이 시기에 개발되기 시작했는데, 얘 역시 본격적으로 이름이 부각된 건 Windows 95가 나온 뒤부터였다. 요컨대 Win32s는 95의 등장 이전부터 NT 3.1과 오리지널 3.1 사이의 gap을 메우기 위해 존재해 왔던 물건이다.

그 뒤, Windows 95가 나오고 1995년 말에 출시된 Visual C++ 4가 대박을 치면서 마소의 개발툴이 볼랜드 같은 타사 컴파일러를 슬슬 제치기 시작했다. Developer Studio라는 통합 IDE도 이때 처음으로 등장했다(텍스트 에디터, 리소스 에디터, 디버거, 빌드 툴, 도움말 레퍼런스 모두 한데 통합). VC4 시절에는 UI상으로 생뚱맞게도 맥용 크로스 컴파일이 있었던 모양이나, 본인이 직접 써 본 적은 없다.

이 당시에는 지금 같은 인터넷 기반 제품 업데이트가 없다 보니 소숫점 첫째나 둘째 자리가 0이 아닌 제품 버전을 심심찮게 볼 수 있었다. Win32s는 Visual C++ 4.1까지 지원되다가 96년 가을에 출시된 4.2에서부터 지원이 중단됐다. 설치할 때부터 "이 버전부터는 Win32s를 지원하지 않으니 이걸 타겟으로 개발하려면 구버전을 쓰고 이건 설치하지 마세요"라고 확인 질문이 뜬다.

비베는 4.0에서야 32비트 에디션이 등장하고 16비트와 32비트가 공존했던 반면, C++은 진작부터 32비트가 존재했고 그 대신 Win32s라는 과도기를 거쳤다는 차이가 있다.
또한 비베는 21세기부터는 닷넷 기반 언어로 완전히 탈바꿈해 버린 반면, C++은 이전부터 위상이 위상이다 보니 닷넷의 공세에 영향을 받지 않있다. 차라리 C++/CLI 같은 파생형 확장이 나오면 나왔지, 네이티브 코드 개발 부분은 바뀐 게 없다.

비베는 5와 6에서 잠시 MS Office 97 기반 GUI 엔진을 사용했고, 닷넷 200x에서는 그 기반을 계승하여 Office XP 및 파생 변종 GUI를 사용했다. VC++의 4~6에서 쓰인 IDE는 MFC를 써서 Office와 비슷한 외형이 나오게 자체적으로 만든 GUI 엔진 기반이었다.
그러던 것이 Visual Studio 201x부터는 WPF 기반의 완전히 독자적인 고유한 GUI를 사용하여 오늘날에 이르고 있다. 버전이 올라갈 때마다 매번 외형을 바꾸던 것도 이제는 지쳤는지(?) 2013 이후쯤부터는 안 하고 있다.

Posted by 사무엘

2017/10/12 08:35 2017/10/12 08:35
, , , ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1415

Trackback URL : http://moogi.new21.org/tc/trackback/1415

Comments List

  1. 비밀방문자 2017/10/19 04:10 # M/D Reply Permalink

    관리자만 볼 수 있는 댓글입니다.

    1. 사무엘 2017/10/19 12:46 # M/D Permalink

      임베디드나 그런 바닥은 기계의 수명이 다할 때까지 소프트웨어 업데이트/업그레이드란 없을 테니 그럴 수도 있을 것이다? 뭐, 인터넷 연결 같은 것도 없고 애초에 안정화돼서 잘 쓰던 프로그램만 죽도록 쓰면 될 테고.

Leave a comment

Windows에서 메뉴, 리스트박스, 콤보박스처럼 세부 항목이 존재하는 고전적인 UI 컨트롤에는 기본 글꼴로 문자열을 찍는 기능뿐만 아니라 임의의 크기로 임의의 그림도 그리는 owner draw 기능이 있다. 한두 개 정도 특수하게 쓰이는 owner draw 기능이라면 해당 UI 컨트롤을 구동하는 대화상자 등 부모 윈도우에서 메시지를 받아서 처리한다.

그러나 매 아이템들마다 check box가 달린 리스트라든가, 트리 계층 구조를 owner draw 기능을 이용해서 얼추 구현한 리스트처럼.. 특정 owner draw 기능과 동작을 컴포넌트화해서 여러 곳에서 동시에 사용하고 싶다면 그 UI 컨트롤 자체가 개조 대상이 된다. 윈도우 프로시저를 서브클래싱한 후, owner draw 메시지를 부모 윈도우로부터 되받아서 자신이 직접 처리하면 된다. 이건 뭐 16비트 시절부터 존재해 온 아주 고전적인 Windows 프로그래밍 테크닉이다.

owner draw는 개념적으로 모든 아이템의 크기가 동일한 owner-draw fixed와, 각각의 아이템 크기가 모두 다를 수 있는 owner-draw variable이 존재하는데, 개인적으로 후자는 전혀 다뤄 본 적이 없다.

그리고 string 버퍼를 사용하는 owner-draw가 있고(LBS_HASSTRINGS 내지 CBS_HASSTRINGS 스타일), 그런 게 없는 owner-draw도 있다. 문자열의 옆에다가 아이콘 같은 걸 추가로 그리거나 문자열 자체를 좀 색다른 색깔과 폰트로 출력하기 위해서 owner-draw를 사용하는 것이라면 전자를 선택해야 할 것이고, 그게 아니라 완전히 생판 다른 그림만을 찍거나, 자체 버퍼에 있는 문자열을 직통으로 찍으려면 후자를 선택하면 된다.
문자열 없는 owner draw 리스트박스는 일일이 LB_ADDSTRING을 호출할 필요 없이 LB_SETCOUNT만으로 간단하게 아이템 수를 뻥튀기할 수도 있다.

owner draw 컨트롤이 동작을 시작하면 아이템을 손수 직접 그리라는 WM_DRAWITEM 메시지가 오기에 앞서, 그림을 그릴 영역을 정하기 위해 WM_MEASUREITEM 메시지가 부모 윈도우로 날아온다. 그런데 여기서 꽤 재미있는 동작 특성이 있다. WM_MEASUREITEM는 DRAWITEM과는 달리, 굉장히 일찍 날아온다. 대화상자의 경우, MEASUREITEM은 WM_INITDIALOG보다도 먼저 날아온다.

WM_INITDIALOG는 대화상자 내부의 모든 컨트롤들이 생성되었고 모든 준비가 완료되어서 대화상자가 화면에 표시되기 직전에 날아온다. 그러나 MEASUREITEM은 그렇게 내부 컨트롤이 생성될 때마다, WM_CREATE 타이밍에서 자신의 스타일에 owner draw 속성이 주어져 있으면 곧장 부모 윈도우로 전달된다고 생각하면 된다. 그러니 자기 주변의 다른 대화상자 컨트롤들이 다 생성되기도 전의 굉장히 이른 타이밍에 날아온다.

대화상자 윈도우(HWND)를 그에 상응하는 C++ 개체 같은 사용자 정의 오브젝트(LPARAM)와 연결하기 위해서는 CreateDialog나 DialogBox 같은 함수에다가 연결할 그 오브젝트 포인터를 넘겨주는 편이다. 그리고 HWND와 LPARAM이 실제로 만나는 타이밍이 WM_INITDIALOG이다. 즉, 이 메시지가 대화상자계에서 WM_CREATE나 마찬가지인 셈이다.

하지만 WM_MEASUREITEM은 이런 통상적인 초기화 메커니즘이 수행되기 전에 부모 윈도우로 호출된다. 그렇기 때문에 MFC 말고 자체적인 Windows API 프레임워크를 구현하고 있다면 이 메시지의 처리를 좀 특수하게 해 줄 필요가 있다.
리스트박스나 콤보박스가 좀 지연 초기화를 지원해서 대화상자의 초기화가 다 끝나고, 자기가 WM_PAINT를 받아서 화면에 그려지기 직전(WM_DRAWITEM)처럼 정말로 폭을 알아야 할 때에나 저런 메시지를 보냈으면 사용자가 UI 프로그래밍을 하기 약간 더 수월했을 텐데 싶은 아쉬운 생각이 좀 든다.

그리고 WM_MEASUREITEM의 도착 타이밍이 너무 일러서 부담된다면, 아이템의 폭을 꼭 이때 지정해 주지 않아도 된다. 뒤늦게라도 부모 윈도우에서 LB_SETITEMHEIGHT(리스트박스), CB_SETITEMHEIGHT(콤보박스) 메시지를 보내서 아이템 전체(ower-draw fixed), 또는 개별 아이템(owner-draw variable)의 폭을 지정해 줄 수 있다.
리스트박스의 경우 경험상 둘의 차이는 거의 없다. 콤보 박스는 WM_MEASUREITEM 메시지의 결과에 따라서 drop list 내부에서의 아이템 높이뿐만 아니라 한 줄짜리 자기 본체의 높이도 그에 맞춰 자동으로 조절되는 반면, CB_SETITEMHEIGHT 메시지는 그런 효과까지는 없다는 차이가 있다.

또한, 메뉴야 대화상자의 내부 컨트롤 같은 존재가 아니니 저런 대체제가 존재하지 않으며 owner-draw 메뉴 아이템의 폭을 지정하는 타이밍은 WM_MEASUREITEM밖에 선택의 여지가 없다. 딱히 MENUITEMINFO 같은 구조체에 자신의 높이를 지정하는 곳은 존재하지 않는다.

요즘 운영체제의 옵션에 따라서는 콤보 박스의 drop list가 튀어나올 때, 또는 메뉴가 출력될 때 바로 툭 튀어나오는 게 아니라 fade in으로 서서히 나타나거나 위-아래 내지 대각선 방향으로 슬라이딩 하듯이 튀어나오곤 한다. 이건 임의의 윈도우에 대해서 AnimateWindow라고 이런 애니메이션 효과를 구현해 주는 함수가 따로 있다.

그런데, 과거의 Windows 9x에서는 owner-draw 아이템이 들어있는 콤보박스나 메뉴에 대해서는 그런 애니메이션이 지원되지 않았다. 기본 스타일로 문자열을 출력하는 컨트롤만 애니메이션이 나오던 것이 2000/XP 같은 NT 계열에 와서야 owner-draw 방식의 컨트롤에 대해서도 동등하게 애니메이션이 지원되기 시작했다. 그림을 화면에다 바로 그리는 게 아니라 내부 버퍼 DC에다가 그려 놓고 그런 처리를 하게 된 듯하다.

참고로 AnimateWindow는 애니메이션 대상인 윈도우에다가 WM_PRINTCLIENT라고 좀 생소하게 생긴 메시지를 보낸다. 이것은 WM_PAINT와 비슷하게 창의 내용을 그리라는 메시지이지만, WM_PAINT 때와는 달리 BeginPaint나 EndPaint 호출이 필요하지 않다. invalid 영역이나 클리핑 처리 같은 개념도 없으며 주어진 DC에다가 언제나 윈도우 내용을 처음부터 끝까지 그려 주면 된다.

Posted by 사무엘

2017/09/18 08:37 2017/09/18 08:37
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1406

Trackback URL : http://moogi.new21.org/tc/trackback/1406

Leave a comment

본인은 몇 년 전에 쓴 글을 통해 Windows API에서 비트맵을 출력할 때 사용하는 GDI API 몇 개를 브러시와 비트맵의 관계라는 관점에서 비교하고 살펴본 적이 있었다. 이번에는 픽셀 포맷과 DDB/DIB라는 관점에서 관련 API들과 이들의 특성을 살펴보도록 하겠다.

1.
먼저, 비트맵은 CPU의 관점에서 봤을 때 빅 엔디언 형태이다.
모노크롬 비트맵에서는 128, 64 같은 큰 비트 자리수가 왼쪽을 나타내고 작은 비트로 갈수록 오른쪽으로 간다.
색깔을 나타내는 RGB야 숫자의 대소 구분이 무의미하겠지만, 일단 RGB 매크로(메모리)에서의 색상 배열 순서와 RGBQUAD 구조체(파일 저장)에서의 색상 배열 순서는 서로 정반대이다. 전자는 R이 최하위 비트이지만 후자는 R이 최상위 비트이다. 그러니 여기서도 이념이 빅 엔디언임을 확인할 수 있다.

2.
일반적으로 비트맵 폰트 파일 내부의 비트맵들은 한 줄이 바이트 단위로 align이 돼 있다. 그러나 CreateBitmap 함수가 받아들이는 DDB(장치 종속 비트맵)는 역사적인 이유 때문인지, 한 줄이 2바이트, word 단위로 align돼 있어야 한다.
compatible bitmap이 아니라 CreateBitmap으로 직통으로 만들 수 있는 비트맵이 사실상 모노크롬밖에 없다는 점을 감안하면, 저기에 전달되는 가로 크기는 사실상 언제나 16의 배수 단위여야 한다.

한편, BMP 파일과 직통 대응하는 DIB(장치 독립 비트맵)는 이런 제약이 더 커져서 한 줄이 4바이트 단위로 align돼 있어야 하며, 얘는 또 상하가 뒤집혀 있기까지 하다. y축 양수가 위로 올라가는 좌표계를 염두에 뒀기 때문이다. DIB를 취급하는 함수들은 다 이런 형태의 비트맵을 입력으로 받는다.

3.
Create(Compatible)Bitmap 함수로 만들어진 비트맵은 성능이 가장 좋고 속도가 빠르지만, 한번 초기화한 뒤에 내부 비트맵 메모리에 직접 저수준 접근을 할 수 없다. GetDIBits 같은 함수로 내부 메모리 컨텐츠에 대한 복사본만을 얻을 수 있을 뿐이며, 이 내부 메모리는 철저하게 장치 종속적이다. 즉, portable하지 않다. 컨텐츠를 조작하는 건 BitBlt 같은 타GDI 함수를 써서 해야 한다.

비트맵을 출력하는 다른 함수로는 SetDIBitsToDevice가 있다. 얘는 받는 인자가 많고 사용이 좀 복잡하긴 하지만, BitBlt와는 정반대로 그냥 아무 메모리가 가리키는 임의의 BMP 헤더와 컨텐츠를 통째로 받아서 그 내용을 화면에다 찍어 준다. 원본 비트맵에 대해서 뭐 메모리 DC 만들고 비트맵 만들고 SelectObject 할 필요가 없으며, 메모리에 직통으로 접근해서 픽셀, 팔레트 테이블, 크기 따위의 수정도 얼마든지 가능해서 매우 좋다.

하지만 BMP 헤더를 매번 해석해서 DIB를 DDB로 변환해서 찍을 준비를 해야 하기 때문에 이 함수는 비트맵을 뿌리는 속도가 DDB 전용 함수만치 빠르지는 않다. 구형 운영체제의 16/256색 구닥다리 비디오 환경에서는 성능 열화의 폭이 더욱 크다.

그런데 알고 보니 저 둘의 중간 역할을 하는 함수도 있다.
CreateDIBSection은 내부적으로 반쯤 DIB로 취급되는 HBITMAP을 되돌린다. 이 비트맵을 사용하기 위해서는 BitBlt를 쓸 때처럼 원본 메모리 DC를 만들고 SelectObject를 해 줘야 한다. 하지만 픽셀을 직접 조작할 수 있는 메모리 포인터도 되돌리기 때문에 이를 응용 프로그램이 사용 가능하다.

이 메모리는 운영체제가 내부적으로 직접 할당해서 준 것이다. SetDIB*처럼 아무 메모리에 있는 비트맵을 찍을 수 있는 게 아니며, 그림의 크기나 색상 수 같은 헤더 정보는 한번 정해진 뒤에 변경 가능하지 않다. (그게 달라진다면 그냥 비트맵을 새로 만들어야..) 단지 픽셀 데이터에만 접근 가능하며, 색깔 변경은 SetDIBColorTable라는 별도의 함수로 해야 한다.

하지만 픽셀 데이터에 직접 접근과 조작이 가능한 것만 해도 어디냐. 기존 HBITMAP의 특성은 다 가지고 있기 때문에 BitBlt, DrawText, LineTo 같은 GDI 함수들을 고스란히 사용하면서 그림이 그려진 결과를 메모리 포인터 레벨에서 바로 확인 가능하니 실로 놀라운 일이 아닐 수 없다. 이런 DIB의 특성을 반쯤 가지면서 비트맵을 뿌리는 성능도 SetDIB*보다는 약간 더 좋다.

지금까지 얘기했던 이 세 가지 API를 표로 정리하면 다음과 같이 요약된다.

  CreateBitmap + BitBlt SetDIBitsToDevice CreateDIBSection + BitBlt
픽셀 포맷 2바이트 패딩 4바이트 패딩 + 상하 반전 4바이트 패딩 + 상하 반전
사용하는 메모리 내부 전용 사용자 임의 지정 가능 내부 전용
픽셀 메모리에 직접 접근 가능 X O O
BMP 헤더에 직접 접근 가능 X O X
단색 비트맵의 색깔 지정 SetTextColor / SetBkColor BMP 헤더 구조체 값 직통 수정 SetDIBColorTable
성능 제일 빠름 제일 느림 약간 느림

* 참고로, CreateDIBitmap은 DIB 함수들처럼 BMP 헤더를 인자로 받긴 하지만, HDC까지 인자로 받아서 DIB를 완전히 DDB 형태로 변환해 버린다. 이 함수를 통해 생성된 HBITMAP은 외부에서 내용 수정이 가능하지 않다.

* 그리고 HBITMAP의 내부 컨텐츠를 얻어 오는 함수로 GetDIBits 말고 GetBitmapBits도 있는데, 얘는 그냥 레거시 잔재이다. BITMAPINFO 헤더 정보를 받는 부분이 없기 때문에 그냥 모노크롬 비트맵 데이터를 얻을 때나 쓰는 간소화 버전이라고 생각하면 된다.

예전에 Windows 95부터 2000/ME까지는 시스템 종료 명령을 내리면 화면 전체에 50% 검은 음영 픽셀이 깔리면서 시스템 종료, 재시작 같은 세부 기능을 선택하는 대화상자가 떴다. 지금은 그런 효과는 관리자 권한을 요청하는 UAC 확인 대화상자가 뜰 때에나 그렇게 배경이 어두워질 텐데 그때는 시스템 종료 대화상자가 그 비주얼 이펙트 역할을 담당했다. (XP에서는 그 효과가 "흑백으로 서서히 fade out"이라는 더 화려한 형태로 바뀌었다가, 후대 버전부터는 이펙트가 사라졌다.)

그런데.. 그렇게 50% 검은 음영을 뿌리는 게 바로 래스터 오퍼레이션을 가미한 BitBlt 내지 PatBlt 실행으로 구현되었다. 최신(당대 기준) 그래픽 카드에서야 즉시 전체 화면에 음영 뿌려졌겠지만, 하드웨어 가속 없이 640*480 VGA 내지 그에 준하는 구린 그래픽 환경에서는 음영이 위에서 아래로 뿌려지는 게 눈으로 보일 정도로 속도가 느렸다. 그건 나름 수십만 개에 달하는 픽셀이 바뀌는 거니까..

그리고 그게 바로.. 그 컴퓨터에서 BitBlt 함수로 화면을 가득 채우는 속도와 같다 생각하면 된다. 그때는 이 따위 느린 그래픽 함수로는 답이 없으니, Windows에서 게임을 돌리려면 발상의 전환을 달리한 DirectX 같은 API를 만들어야겠다는 생각을 응당 안 할 수 없었을 것이다. 하드웨어 계층 추상화+통합이 아니라, 하드웨어 직통 제어를 지원하게 말이다.

DirectX 쪽 그래픽 프로그래밍이 재래식 GDI 그래픽 프로그래밍과 다른 점은..

  • 하드웨어의 발전에 따라 프로그래밍 방법론의 변화 기복이 매우 큼.
  • 하려는 일(도형 그리기, 글자 찍기..)보다는 그래픽 하드웨어의 기능 위주로 API가 설계돼 있다. 사실, 이걸 수용하라고 애초부터 이념이 이런 식인 API를 따로 만든 거다.
  • 이런 이유로 인해, GDI처럼 프린터, 플로터, 메타파일 같은 디바이스까지 다 통합하는 추상화 계층 건 전혀 안중에 없음. 오로지 화면 아니면 화면 출력용 메모리 버퍼 위주이다.
  • BeginPaint/EndPaint로 대표되는 invalid 영역 그딴 개념이 없고, 그 대신 '서피스 소실'이라는 개념이 존재한다.

정도로 요약되겠다.
예전에는 GDI와는 완전히 다른 기술 계층을 거쳤기 때문에 화면 캡처도 특수한 프로그램을 써서 했을 정도이지만 이제는 그런 유별난 점이 점점 없어지고 통합돼 가고 있는 것도 인상적이다.

Posted by 사무엘

2017/09/15 19:31 2017/09/15 19:31
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1405

Trackback URL : http://moogi.new21.org/tc/trackback/1405

Leave a comment

컴퓨터에서 돌아가는 프로그램들에는 각각 current directory라는 개념이 있다. 그래서 파일이나 디렉터리를 지정할 때 매번 드라이브 또는 볼륨의 이름부터 쓰는 게 아니라 그걸 생략하고 이름만 달랑 적거나, ..₩ 처럼 간편하게 ‘상대 경로’를 지정해 줄 수 있다.

기술적으로 봤을 때 current directory는 프로세스 전체 단위로 공유되는 속성이다. 스레드 단위가 아니다.
한 디렉터리 아래에 있는 모든 파일과 디렉터리를 조회하는 건 보통 SetCurrentDirectory를 이용해서 함수의 재귀호출로 구현하는 편인데(이름을 줘서 하위 디렉터리로 갔다가 앞으로 되돌아갈 때는 간편하게 ".."만 지정하면 됨), 이건 여러 스레드가 동시에 수행되지 않게 해야 한다.

여러 군데에서의 디스크 수색을 굳이 동시다발적으로 하려면 해당 함수가 경로 문자열 관리를 자체적으로 해서 FindFirstFile에 언제나 절대경로만 전해 주거나, 아니면 상대 경로를 쓸 거면 아예 별도의 프로세스를 만들어서 돌리게 해야 한다.

그런데 여기서 한 가지 의문이 생긴다. 각 드라이브별로 직전까지 작업하던 디렉터리 정보가 운영체제 차원에서 자동으로 보존될까, 그렇지 않을까?

C:\>cd windows

C:\Windows>d:

D:\>cd doc

D:\doc>c:

C:\Windows>d:

D:\doc>


드라이브별 커런트 디렉터리란, 위의 예에서 C에서는 Windows가 보존되고, D에서는 doc가 보존되는 것을 말한다.

그런데 정답부터 말하자면 그건 운영체제가 일일이 자동으로 기억하고 챙겨 주지 않는다.
당장 탐색기나 파일 열기 대화상자의 주소창에서 c: 나 d: 라고만 달랑 쳐 보아라. 이 경우 언제나 해당 드라이브의 루트 디렉터리로만 가지, 명령 프롬프트일 때처럼 직전에 해당 드라이브에서 마지막으로 살펴보던 디렉터리를 기억하지 않는다. 오히려 명령 프롬프트가 예외적으로, 유일하게 그걸 별도로 지원해 주고 있다.

그럼 질문의 초점이 이렇게 바뀔 것이다. 명령 프롬프트만 왜 그러는 걸까?
물론 명령 프롬프트는 GUI와 달리 '뒤로' 같은 버튼이 없으니 디렉터리를 기억해 주는 게 사용자의 입장에서 편리하다. 그리고 더 큰 이유는 먼 옛날 MS-DOS와의 호환을 위해서이다.

MS-DOS의 최초 버전인 1.0은 무려 1981년에 출시되었으며, 얘는 파일 시스템에 디렉터리라는 개념을 지원하지 않았었다. 즉, 모든 디스크는 루트 디렉터리만 존재했으며, 파일 이름에 (역)슬래시 기호가 들어갈 일이 없었다.

마치 Windows 1.0이 프로그램 창을 겹치게 배열하는 게 지원되지 않았던 것과 동급으로 정말 믿어지지 않는다. (뭐, 기술적인 한계 때문은 아니고, 애플 사와의 특허 분쟁을 피해 가느라 일부러 기능을 cripple시킨 것이지만) 1980년대 초의 열악한 컴퓨터는 무슨 매체든 디스크의 공간이 상상하기 힘들 정도로 작고 좁았으니 굳이 디렉터리 계층 구조의 필요가 존재하지 않았던 듯하다.

그러다가 DOS 2.0부터는 드디어 파일 시스템 차원에서 디렉터리가 도입됐다.
그런데 DOS 1.0용으로 개발된 프로그램은 디렉터리라는 걸 전혀 인식하지 않고 역슬래시 문자도 아예 사용하지 않으니 2.0에서 루트가 아닌 다른 디렉터리에 있는 파일을 읽고 쓸 방법이 없다.

그러니 이 문제를 최대한 호환성을 존중하며 해결하기 위해, :₩로 시작하지 않는 경로는 이제부터 상대 경로로 간주시켰다. 그리고 각 드라이브별로 커런트 디렉터리라는 개념을 도입하여, 상대 경로는 루트 고정이 아닌 커런트 디렉터리에 있는 파일에 접근하는 것으로 정책을 바꿨다. 운영체제가 일종의 state machine 역할을 대신해 주는 셈이다.

Windows는 앞서 살펴보았듯이 모든 드라이브를 통틀어서 단일 current directory만 관리하지 DOS처럼 동작하지 않는다. 단지 명령 프롬프트에서는 특수한 환경변수를 운용해서 사용자가 돌아다닌 디렉터리를 드라이브별로 추적하여 도스의 동작을 흉내 내 준다. 이건 물론 오늘날까지도 전적으로 호환성 차원에서 해 주는 것일 뿐이다. the old new thing 블로그를 보면 더 자세한 설명을 볼 수 있다. 환경변수를 사용하는 이유는 이 프로세스로부터 새로 실행된 child 프로세스에게까지 current directory 변경의 여파가 자동으로 이어지게 하기 위해서라 한다.

“타 드라이브의 current directory”라니, 지금까지 한 번도 진지하게 생각해 본 적이 없었는데.. 굉장히 흥미로운 사실을 알 수 있었다. 예전에 Windows 9x에서 존재하던 CD ... (점 3개 이상)처럼 뭔가 호환성과 관련된 사연이 있었던 것이다.

1.
컴퓨터에서 옛날에는 하나밖에 없는 게 당연하다고 여겨졌으나 나중에는 여러 개 존재할 수도 있게 된 것의 예로는 디렉터리뿐만 아니라 CPU 코어(멀티코어!)라든가 모니터(최소한 듀얼..)도 해당되지 싶다.
그러니 하나밖에 인식을 안 하는 소프트웨어에 대해서는 무조건 붙박이가 아니라 현재 default로 지정되어 있는 것 하나를 기준으로 동작하게 운영체제가 샌드박스 처리를 잘 해 줘야 할 것이다.

하드웨어 말고 소프트웨어적인 요소 중에서도 클립보드 같은 건 운영체제 API 차원에서 다변화될 가능성이 있다. 그것 말고는... 설마 한 컴퓨터에 마우스 포인터 같은 게 둘 이상 존재할 일이 있을지는 모르겠다.
마우스 말고 터치스크린은 여러 손가락이 동시에 눌러질 수 있다. Windows 98에서 멀티모니터 지원이 최초로 도입됐다면 Windows 7부터는 멀티터치 지원 기능이 최초로 추가됐는데, 본인은 지금까지 멀티터치 관련 기기나 API를 접할 일이 전~혀 없었다. 문자 입력과도 분명 연계가 가능할 텐데 그쪽으로 연구할 기회가 없었다.

2.
그러고 보니 시스템 전체 차원에서의 current 설정 vs 특정 항목별 current/default 설정이라는 양대 구도는 Windows의 IME에서도 동일하게 찾아볼 수 있다.
Windows에서 돌아가는 모든 UI 스레드들은 어떤 입력 언어/로케일과 연결돼 있다. 이것은 영어 드보락, MS 일본어 IME, 날개셋 등등 중 하나로.. 키보드 드라이버, IME/TSF 모듈을 모두 통합하는 개념이다.

각 스레드들이 서로 다른 입력 언어와 연결 가능하지만(Alt+Shift, Ctrl+Shift, 또는 도구모음줄 클릭), 어떤 스레드가 새로 생성되었을 때 맨 처음 기본으로 지정되는 'default 입력 언어'라는 건 따로 있다. 이건 제어판에서 변경 가능하다. 이게 디렉터리로 치면 current directory에 가깝다.

그런데, 사실은 한국어, 중국어, 일본어 등 각 언어별로도 말 그대로 default 입력 언어가 있다. 한 언어에 속하는 IME들이 여러 개 있을 때, 사용자가 Alt+Shift로 언어만 그걸로 전환하면 그 언어의 default IME에 속하는 놈이 기본 선택된다. DOS에서 존재하던 드라이브별 current directory처럼 말이다.

내 경험상 전체 default IME라든가, 언어별 default IME 같은 건 프로그래밍을 통해 알아 내거나 변경하는 뾰족한 방법이 없다. MSDN을 뒤져 보면 비슷한 기능을 하는 API가 있긴 하지만 current, active, default 등 용어도 혼란스럽고 기능들이 문서화된 대로 정확하게 동작하질 않는다. 더구나 Windows 8부터는 Win+Space를 통해 IME들을 언어 구분 없이 한 리스트에서 쭉 고르게 UI가 바뀌어서 언어별 default IME라는 건 개념이 굉장히 모호해지기도 했다.

이 방식은 운영체제에 설치된 입력기가 적을 때는 깔끔하지만 10개 가까이 많아지면 화면이 굉장히 난잡해진다. 언어별로 구분하는 Windows 7 이하 기존 방식도 여전히 필요하다고 생각된다.

Posted by 사무엘

2017/08/04 08:35 2017/08/04 08:35
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1389

Trackback URL : http://moogi.new21.org/tc/trackback/1389

Leave a comment

* 3년 전에 썼던 글을 내용을 보충하여 리메이크 한 것이다.

Windows 운영체제에서 생성하는 윈도우들은 그 본질이 크게 overlapped, popup, 그리고 child 이렇게 셋으로 나뉜다. 이해를 돕기 위해 아래의 Windows 1.0 사진을 한번 살펴보도록 하자. 그때는 이 세 종류의 구분이 지금보다 훨씬 더 명확했기 때문이다.

사용자 삽입 이미지

1. overlapped

1985년에 발표된 Windows 1.0 첫 버전은 기술적인 한계 때문..은 아니고, 애플 사와의 이상한 특허 분쟁에 얽히는 바람에 응용 프로그램 창들이 서로 겹치지를 못하고 타일 형태의 배치만 가능한 정말 괴상한 형태로 개발되었던 걸로 유명하다.
그러다 Windows 2.0에서는 타일 제약 봉인이 풀렸기 때문에 이 윈도우들은 겹쳐지는 게 가능해졌으며 Z-order라는 개념도 생겼다. 그게 워낙 뜻깊은 일이었던지라 명칭에까지 OVERLAPPED가 붙은 것이다.

그리고 저렇게, 타일 형태의 배치가 가능한 응용 프로그램의 최상단 껍데기 윈도우가 바로 오늘날의 개념으로 치면 overlapped 윈도우이다. 캡션이라고 불리는 제목 표시줄이 달려 있고 크기가 언제든지 유동적으로 바뀔 수 있으며, CreateWindow(Ex) 함수에다 위치와 크기를 지정할 때 CW_USEDEFAULT(대충 적당히 알아서)를 줄 수 있는 유일한 타입의 윈도우이다.

사실, WS_OVERLAPPED의 값은 그냥 0이다. popup이나 child 같은 속성이 따로 지정되지 않은 윈도우는 기본적으로 overlapped 속성이 지정된다. 여기에다가 최소화/최대화(WS_M??MIZEBOX)/닫기(시스템 메뉴 WS_SYSMENU) 버튼, 크기 조절 가능한 굵은 껍데기(WS_THICKBORDER) 비트들이 합쳐진 것이 바로 WS_OVERLAPPEDWINDOW 스타일이다.

2. popup

그럼 popup은 무엇이냐 하면 저 위의 About 대화상자처럼, overlapped window의 위에 겹쳐져서 배치될 수 있는 윈도우이다.
그런데 당장 Windows 2.0부터 오버랩은 말 그대로 overlapped window에서도 다 가능해졌으니, 둘의 실질적인 차이가 없어졌다고 볼 수도 있다. 하지만 둘은 여전히 완전히 동일하지는 않다.

popup 윈도우는 기본적으로 캡션이 없는 형태이며, WS_CAPTION 같은 별도의 옵션을 줘야만 캡션이 달린다. 그러나 overlapped 윈도우는 옵션을 주지 않아도 캡션이 무조건 달려 나온다. Windows 2~3 시절까지만 해도 응용 프로그램에서 캡션이 없고 제목이 없는 대화상자는 지금보다 훨씬 더 흔하게 볼 수 있었다.

지금은 대화상자들도 다 캡션이 달려 있으며 일반적인 응용 프로그램처럼 아이콘에다 최소· 최대화 버튼과 두꺼운 프레임까지 별도로 스타일로 주고 나면.. popup 형태의 대화상자 프로그램과, overlapped 형태의 일반 프로그램 창과 외형상의 구분은 사실상 다 사라지는 건 사실이다.

그럼에도 불구하고 popup과 overlapped의 구분이 원래 저런 데서 시작되었다는 것을 알면 되겠다. 다른 창의 내부에 종속되지 않고 독자적으로 화면에 떠 있으면서 캡션 같은 외형이 없거나 취사선택 가능한 모든 custom 윈도우라면, 묻지도 따지지도 말고 그냥 WS_POPUP을 주면 된다.

대화상자 리소스 편집기에서도 이 대화상자의 초기 스타일을 지정해 줄 수 있다. 프로퍼티 페이지처럼 다른 대화상자의 내부에 들어가는 대화상자이면 WS_CHILD를 주면 되고, 나머지 경우에는 WS_OVERLAPPED는 신경 쓸 필요 없고 그냥 WS_POPUP을 지정하면 된다.
여담이지만, 인터넷을 하면서 수시로 튀어나오는 웹브라우저 팝업창은 명칭과는 달리 사실은 overlapped 윈도우라고 생각하면 된다. 팝업창에도 웹브라우저 창 고유의 캡션과 프레임은 그대로 남아 있기 때문에 overlapped 윈도우의 정의에 훨씬 더 부합하는 걸 알 수 있다.

3. child

끝으로, WS_CHILD는 동작 방식이 위의 둘과는 굉장히 다르니 이해하기 쉽다.
자기의 위상이 독자적이지 않고 외형상 부모 윈도우의 내부에 종속된 모든 윈도우들은 child 윈도우이다. 대화상자의 내부 컨트롤들이 대표적인 예임.

얘는 컨트롤 ID라는 정보도 갖는다. HWND는 운영체제가 창들을 식별하기 위해 부여하는 가변적인 번호인 반면, ID는 창을 생성하는(= 운영체제에다 생성을 요청하는) 주체 측에서 고정붙박이로 부여하는 번호라는 차이가 있다. GetDlgItem은 이름처럼 굳이 대화상자의 자식 컨트롤뿐만 아니라 부모-자식 관계를 갖는 아무 윈도우에서나 ID값으로부터 자식 창을 얻을 때 사용 가능하다.

popup이나 overlapped 윈도우에는 저런 ID라는 개념이 존재하지 않으며, 그 대신 메뉴를 표시하는 기능이 있다.
뭐, child 윈도우도 비록 메뉴는 태생적으로 없을지언정 마치 overlapped 윈도우처럼 캡션과 프레임, 그리고 시스템 메뉴를 갖는 건 불가능하지 않다. 그 대표적인 예는 MDI 프레임 윈도우이긴 한데.. 그래도 그걸 빼면 캡션과 프레임을 갖춘 child 윈도우는 매우 드물다. 캡션과 프레임 자체가 최상위 윈도우의 상징과도 같으니 말이다.

이렇게 보면 overlapped와 popup이 한 묶음이고, 성격이 다른 child가 혼자 좀 따로 노는 것처럼 보인다. 하지만 동일한 클래스의 윈도우가 상황에 따라서 popup과 child 속성을 취사선택해서 동작하는 경우도 의외로 있다. 콤보 박스에서 내부적으로 쓰이는 ComboLBox라는 리스트 박스가 대표적인 예이다.

콤보 박스의 타입이 Simple이어서(대표적인 예는 글꼴 선택 대화상자) 리스트가 언제나 표시되어 보일 때는 얘는 콤보 박스에 딸려 있는 child 윈도우이다.
그러나 콤보 박스를 클릭하거나 F4를 눌렀을 때만 리스트가 표시되는 drop list 상태일 때는 그 리스트는 대화상자의 위에 별도로 표시되는 popup 윈도우 형태로 생성된다. 이해가 되시겠는가?

차일드 윈도우의 표시 위치는 자기 부모 윈도우의 클라이언트 위치를 기준으로 상대적으로 산정된다. 그런데 자기가 현재 부모 윈도우의 클라이언트 위치 기준으로 어디에 있는지를 한 번에 얻는 게 은근히 힘들다. 대화상자 크기에 따라 차일드 컨트롤들을 적절하게 재배치하는 코드를 작성해 보았다면 이 말이 무슨 뜻인지 잘 알 것이다.

이 경우 GetWindowRect를 한 후에 부모 윈도우를 기준으로 ScreenToClient를 하여 화면 좌표를 한번 거쳐야 하거나, 아니면 번거로운 구조체 초기화를 해야 하는 GetWindowPlacement 함수를 호출해야 한다. 후자 함수의 경우, 최대화된 윈도우라도 원래 있던 위치와 크기까지.. 그 윈도우의 위치와 관련된 모든 정보를 되돌려 주기 때문에 유용하다. 응용 프로그램이 종료 후 나중에 재실행될 때 원래 위치를 100% 그대로 실행되기를 원할 때 이 구조체 값을 백업해 두면 된다.

4. 윈도우 간의 부모/자식 관계

child 윈도우야 그 정의상 태생적으로 부모 자식 관계가 명백하게 존재할 수밖에 없다. 하지만 popup 윈도우도 비록 child처럼 표시되는 위치와 영역이 부모 윈도우 내부로 한정되는 급까지는 아니더라도, 부모 자식 관계 비스무리한 개념이 물론 존재한다.

popup 윈도우는 Z-order상으로 자기 부모 윈도우를 가리고 언제나 더 앞에 출력되며, 부모 윈도우가 소멸될 때 자기도 같이 없어진다. 요렇게 child가 아닌 popup 윈도우의 부모 역할을 하는 윈도우를 개념상으로 owner 윈도우라고 따로 부르기도 한다.

그럼 popup 말고 overlapped 윈도우는? 지금까지 살펴보았듯이 쟤는 애초에 주 용도가 응용 프로그램의 최상단 프레임 껍데기이다. 그러니 태생적으로 부모 윈도우 같은 걸 지정하지 않고 생성되며 부모 자식 관계를 따지는 건 딱히 의미가 없다고 봐야 할 것이다.

그런데, 여기서 유의해야 할 점이 있다. EnumChildWindow나 GetWindow(GW_CHILD) 함수에서 찾아 주는 건 순수하게 child 윈도우들뿐이다. Spy++를 실행하면 계층 구조로 표시된 윈도우 트리를 볼 수 있는데, 이것도 child 윈도우들의 관계만 볼 수 있다.
쉽게 말해 어떤 대화상자 내부의 대화상자(프로퍼티 페이지)라든가 각종 컨트롤들은 계층 구조로 표시되지만, 대화상자에서 얘를 owner로 삼아서 또 다른 modal 대화상자를 꺼내 놓은것을 계층 구조로 보여주지는 않는다는 뜻이다.

자신을 부모(정확히는 owner)로 갖는 서열상 하위의 popup 윈도우들을 한번에 찾아 주는 API는 의외로 존재하지 않는다. 난 이게 당연히 있을 줄 알았는데 없는 걸 발견하고는 개인적으로 굉장히 놀랐다.
일단 top-level 윈도우들을 다 enumerate 한 뒤, 얘들의 owner가 일치하는 놈을 일일이 뒤져 봐야 한다. 그래서 Spy++가 표시해 준 윈도우 리스트가 생각보다 직관적이지 않고 top-level 윈도우가 많은 것이었구나.

이상이다. Windows 프로그래밍을 15년 가까이나 판 본인도 몇 년 전까지만 해도 child는 그렇다 치더라도 popup과 overlapped는 도대체 왜 존재하는 구분인지를 잘 몰랐다. 그리고 parent 윈도우와 owner 윈도우의 관계도 정확하게 모르고 있었고 owned 윈도우는 child 윈도우 조회하듯이 곧장 조회가 가능하지 않다는 것도 미처 생각을 못 하고 있었다. 그러다가 요 근래에야 어렴풋이 이해하게 된 것들을 이렇게 정리해 보았다.

Posted by 사무엘

2017/05/10 08:35 2017/05/10 08:35
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1358

Trackback URL : http://moogi.new21.org/tc/trackback/1358

Leave a comment
« Previous : 1 : 2 : 3 : 4 : 5 : ... 16 : Next »

블로그 이미지

철도를 명절 때에나 떠오르는 4대 교통수단 중 하나로만 아는 것은, 예수님을 사대성인· 성인군자 중 하나로만 아는 것과 같다.

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2018/02   »
        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      

Site Stats

Total hits:
919346
Today:
462
Yesterday:
459