본인은 평소에는 15년 넘게 개발하고 있는 날개셋 한글 입력기의 개발에 대부분의 역량이 집중되기 때문에 타 유명 프로그래머 고수들에 비해 타 플랫폼· 언어· 최신 프로그래밍 기술에 대한 개인적인 관심은 덜한 편이다. 뭐, 자주 언급을 안 할 뿐이지 직장에서는 아무래도 갑님이 시키는 대로 해야 하니, 무엇이건 업무와 생존에 필요한 최소한의 맛보기 정도는 한다. 다만 그런 생소한 분야는 본인이 특장점이 없이 그냥 여느 평범한 프로그래머 A, B의 역량과 다를 바 없다.

먼 옛날에 Windows API와 MFC, Visual C++를 처음으로 공부할 때 그러했고, macOS나 안드로이드 개발을 처음으로 익힐 때도 마찬가지이다. 코드와 리소스가 어떤 방식으로 연결되는지 감을 잡는 게 참 어려웠다. 이건 그야말로 프로그래밍 언어뿐만 아니라 각 플랫폼별 바이너리 실행 파일(DLL/EXE)의 구조, 개발툴의 기능에 대한 총체적인 이해가 필요한 부분이니까 말이다.

그래도 리소스(대표적으로 대화상자/화면 레이아웃)의 기술을 위해 XML을 쓰는 요즘 플랫폼에 비해, Win32 API의 rc 파일은 정말 구닥다리이구나 싶은 생각이 든다. 뭐, resource.h와 R.java처럼 개념상 일말의 공통점이 발견되는 것도 있다(개발툴이 자동으로 생성해 주는 리소스 ID 리스트).

또한 안드로이드의 경우, 굉장한 뒷북이긴 하다만 Eclair니 Froyo니 하던 시절과 비교했을 때 개발 환경이 몇 년 사이에 정말 엄청나게 달라져 있었다. 여전히 이클립스를 쓰는가 했더니 Android Studio라고 전용 개발툴로 진작에 갈아탔으며, 무엇보다 에뮬레이터도 x86과 arm이라는 엄청난 CPU 구조 차이를 어떻게 극복했는지 속도가 꽤 빨라졌다.
그도 그럴 것이 그 구글 내부에서 안드로이드 OS에만 달라붙어 있는 세계구급 날고 기는 프로그래머 엔지니어들이 도대체 얼마나 되며, 이들이 매일 생산하는 코드의 양은 또 얼마나 될까?

2010년대 이후에나 등장한 IDE가 copyright이 왜 엄청 옛날인 2000부터 시작하는지 궁금해서 검색을 해 봤더니.. 이건 그 옛날부터 개발되어 온 타 회사의 IDE를(이클립스 말고) Google이 인수해서 자체적으로 발전시킨 것이어서 그렇다고 한다. 으음..

이럴 때마다 늘 드는 생각인데, 새로운 문물이나 지식을 아주 빨리빨리 잘 익히고 남에게 가르치는 것까지 가능할 정도로 머리가 좋은 사람들이 개인적으로 굉장히 부럽다. 난 굳이 말하자면 애초에 남이 안 하는 짓을 골라서 하는 일에 일가견이 있다. 그래서 정보 올림피아드도 공모 부문에서만 입상하고, 코딩과 논문으로 그럭저럭 지금까지 지내 왔다.
그게 아니라 남과 똑같은 조건에서 뭔가를 빨리 달달 외우고 응용하는 능력이라면 본인은 남들 평균보다 못하면 못하지 결코 뛰어나지는 않다.

컴퓨터 쪽에 우글거리는 수많은 고수 괴수들 중에.. 김 상형 님이라고 한때 winapi.co.kr 이라는 사이트를 운영했고 지금은 '소프트웨어 공학'을 일본어 스타일로 축약한 '소엔'이라는 사이트로 여러 유용한 프로그램 개발 정보를 무료로 공유 중인 대인배가 계신다.
사이트 이름에서 유추할 수 있듯 한때 이분의 전문 분야는 Windows API였다. 텍스트 에디터를 그냥 C++만으로 혼자서 처음부터 끝까지 다 만들었고, 그 테크닉을 소스까지 통째로 책을 출간한 바 있다..;;

한 분야의 기술만 통달하기에도 벅찬데 이분은 안드로이드, HTML, 자바스크립트 등 온갖 분야를 다 탐독해서 책을 쓰고 학원 강사로 뛰고 있다.
그냥 위에서 내려오는 회사 업무나 감당하기 위해서 여러 기술들을 찔끔찔끔 서바이벌 수준으로 익히는 게 아니다. 그야말로 남을 가르치고 책을 쓸 정도로 전문가가 되기 위해서 혼자서 도대체 공부를 어떤 방식으로 얼마나 한 걸까? 비결이 궁금해지지 않을 수 없다.

이렇게 강의와 저술만으로 먹고 사는 데 지장 없는 분들은 굳이 회사 들어가서 조직에 매일 필요가 없다. 물론 프리랜서는 월급쟁이보다야 소득이 훨씬 불안정하고 복불복이 심하다. 보통은 자기 친구들에게도 "걍 회사에서 월급 받으며 지내는 게 짱이야, 아무리 엿같은 동료나 상사가 있더라도 어지간해서는 거기서 절대로 뛰쳐나올 생각 마라" 이렇게 권유를 할 정도라고는 하지만..
이것도 자기 하기 나름이다. 엄청난 능력자라면 을임에도 불구하고 여러 기업들을 상대로 갑질을 하면서 자유롭고 편하게 일을 할 수도 있을 것이다.

그리고 컴퓨터가 나왔으니 영어도 빠질 수 없다.
지금보다 자료 접근성이 훨씬 열악했던 옛날에 독학으로 이를 악물고 영어를 마스터해서 198, 90년대에 이미 유명 영어 교재의 저자로 등극한 사람들이 참 대단하다는 생각이 든다.
최 은경 어린이 영어, 오 성식 생활 영어/pops English, 김 인환, 정 철 ... 그리고 최근에는 Arrow English로 유명한 최 재봉 이런 분들.

난 무슨 영문과 교수나 영어 교사, CNN 리포터-_-;; 이런 거 지향하는 게 아닌 이상, 국내에서 영어 때문에 스트레스 받을 일은 없는.. "반도 토박이치고는 뭐 그럭저럭 하네" 딱 그 정도까지만 영어가 된다. 자막 없이 영화를 다 알아듣거나, 토익 만점 이런 경지는 아니다. 그리고 그마저도 나이는 자꾸 먹고 있는데 영어를 당장 쓸 일은 없으니 감이 점점 쇠퇴-_-하는 중이다.
도무지 들리지가 않는 것, 그리고 아무리 머리를 짜내도 독해 속도를 도저히 더 올릴 수 없는 건 그냥 내 머리의 한계인 것 같다.

영어를 잘하려면 뭐 영어식 사고방식과 어순 감각을 익혀야 되고 무슨 발상의 전환을 해야 하고.. 이런 것들은 그냥 기초가 없고 첫 단추부터 완전 잘못 끼운 생짜 영어 포기자한테는 꽤 유효한 조언일지 모른다. 영어 점수 2~30점을 6~70점으로 올리는 데는 도움이 될 것이다.

하지만 90점을 95점으로 올리는 건 무리임. 저런 기초적인 문법과 어순 감각은 이미 다 갖춰져 있고, 거기서 상위권에서 최상위권으로 가려면 그냥 닥치고 영어라는 빅데이터에 수시로 많이 노출돼서 감을 유지하는 것밖에 답이 없다. 외국 어학 연수는 개나 소나 아무나 가는 게 아니라 딱 이 정도 기초가 갖춰진 애들이 가야지 효과가 높아진다.

그런데, 저런 여러 영어 전문가들이 공통으로 말하는 영어 마스터 비결은.. 학창 시절에 영어 교과서 텍스트들을 몽땅 통째로 암송· 암기했다는 것이다. 사실 인간의 언어에는 굉장히 무작위하고 arbitrary하고, 그냥 문맥이 곧 용례를 결정하는 그런 정보가 많다. 암송· 암기는 학습자에게 괴로운 과정이긴 하지만 그래도 그거 효력은 확실한가 보다.
나도 테이큰의 전화 통화 대사 40초 분량은 통째로 줄줄 외우고 있긴 하다만.. -_- I don't know who you are ... I will find you. And I will kill you. 같은 거.. 그런데 영어를 잘하려면 그런 거 암기를 더 많이 해야 한다.

일본은 개개의 국민들이 다 영어를 못 하더라도 국가 차원에서 번역을 엄청 많이 잘 해 놨다고 그런다. 하지만 우리나라는 모든 국민들이 다 영어를 잘하는 것도 아니고, 번역을 깔끔하게 잘한 것도 아니니 뭔가 문제가 있어 보인다.

끝으로, 어려운 과목의 끝판왕인 수학이 있다. 수학은 영어와 달리 유행을 별로 안 탄다. 한편으로는 노력한 만큼 그대로 결과가 나오는 참 정직한 과목 같으면서도, 한편으로는 타고난 머리 지능빨을 타니 불공평한 면모가 느껴지기도 하는 과목이다.
수학에는 '정석' 책 하나로 그야말로 억만장자가 되고 우리나라에서 최고로 성공한 사람이 있다. 물론 이분 역시 머리가 공부벌레 괴수급이었으며, 굳이 책 안 쓰고 학원과 과외 강사료만으로도 그 옛날에, 겨우 20대 나이로도 왕창 잘나갔을 정도로.. 비범했다.

그런 정석의 저자가 말하는 수학 잘하는 비결은.. 수학은 처음에 느리고 시간이 걸리더라도 직접 계산해 보고 손으로 일일이 쓰면서 감을 익혀야 한다는 것이다. 그런 감이 생겨 있지 않은 사람이 눈으로만 보고 넘어가서는, 그리고 덥석 해설과 풀이를 봐서는 진짜배기 수학 실력이 절대 늘 수 없다고.. 참 너무 원론적이고 당연한 조언을 한다. 그건 게임으로 치면 그냥 무한 맵에 치트키 쓰는 것이나 마찬가지니까.

그리고 저 말을 프로그래밍에다가 적용하자면.. 일일이 직접 코딩해 보고 돌려 봐야 실력이 는다는 말과 일맥상통한다. 그 점은 본인 역시 적극 동의한다.
아무 감도 없는 사람이라면 노가다 코딩이라도 해 봐야 된다. 그런 경험을 많이 해 봐야 노가다 코딩을 왜 '노가다'라고 부르는지 그것부터 좀 알게 된다.

개발자, 프로그래머로 먹고 살려면 솔까말 트리 구조 순회 같은 재귀호출을 스택 배열로 직접 구현하기, 포인터 조작으로 연결 리스트의 원소 배열을 역순으로 바꿔치기 정도는 머릿속에서 로직이 어느 정도 암산이 돼야 하고, 굳이 컴퓨터가 없이 화이트보드 앞에서도 의사코드를 쓱쓱 적을 수 있어야 하지 않는가?

사실, 유수의 IT 업체들이 학-석사 급의 엔지니어를 뽑을 때 코딩 면접도 딱 이 정도 수준의 난이도가 나온다. 무슨 "B+ 트리를 구현하시오, 동영상 압축 알고리즘의 모든 과정을 설명하시오"가 아니다. 그리고 크고 유명하고 재정 넉넉한 기업일수록 당장 현업에서 쓰이는 HTML5니 자바스크립트니 언어 문법 지식보다는 저런 미래의 잠재성과 응용력, 새로운 기술을 더 본다. 능력 함수에서 현재의 f(x) 값보다 도함수 f'(x)를 말이다.

다시 말해, 최신 자바스크립트나 HTML5 API 지식이 필요하지 않으니까 당장 그런 걸 모르는 사람도 OK 하고 뽑는 게 아니다.
오히려 그 반대로.. 하나도 모르는 상태로 입사했더라도 현업에서 그런 것쯤은 30분 만에 즉석에서 공부하고 숙달될 능력이 있으니까 뽑는다는 뜻이다. 요구 사항이 훨씬 더 고차원적이다.

컴공과 수학의 관계는 어떨까? 물론 완벽하게 동치는 아니다. 기하 알고리즘을 구현하고 있는데 삼각형 넓이나 세 점의 방향을 구하는 공식, 3차원 공간에서 두 벡터에 대한 나머지 기저를 구하는 세부적인 외적 공식 같은 거야 당연히 까먹을 수 있다. 하지만 기억이 안 나면 당장 검색이라도 할 수 있으면 아무 문제될 것 없다.

단지, 수학은 그렇게 문제를 쓱쓱 풀어 나갔던 경험, 단 한 가지 경우라도 놓쳐서는 안 되고 논리적으로 완벽해야 한다는 그 관념이 나중에 프로그램을 짜는 데 낯설지 않은 정신적 자산으로 작용할 수 있다고 본다.
물론 그런 관념이 오로지 반드시 학창 시절의 수학 문제 풀이를 통해서만 형성될 수 있다는 건 아니겠지만 말이다. 기본적인 머리가 있고 필요를 느끼면 결국은 나중에 다른 경로를 통해서라도 적응은 하게 돼 있다.

어휴.. 나도 말은 이렇게 써 놨지만.. 당장 어떻게 풀어야 할지 모르는 어려운 문제를 대면하면 이게 도대체 지금까지 수업 시간에 배웠던 기본 수학 공식이나 법칙과 무슨 관계가 있고 무엇부터 적용해야 할지 막막한 게 많다. 맨날 이런 기억과 경험만 쌓이다 보면 그 누구라도 수학이 싫어질 수밖에 없고 수학을 포기할 수밖에 없을 것이다.. -_-;; 세상에는 나랑 나이 차이도 별로 안 나던 시절에 그런 문제를 생각해 내고 '만든' 사람도 있구만.. 참 자괴감이 든다~!!

Posted by 사무엘

2017/10/22 19:35 2017/10/22 19:35
, , , ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1419

본인은 초딩 시절에 어렴풋이 경험했던 옛날 컴퓨터 환경의 추억을 회상하는 일에 관심이 많다. 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

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

컴퓨터 프로그램이 뻗는 방식을 분류하면 크게 다음과 같이 정리된다.

1. 아무 뒤끝 없이 그냥 뻗음(crash)

제일 단순하고 흔한 형태이다. 코딩을 잘못해서 잘못된 메모리에 접근하다가 튕긴 것이다. 그 예로는 null 포인터(null로부터 유도된 인근의 잘못된 주소 포함), 초기화되지 않은 포인터, 초기화되지 않은 배열 첨자 인덱스, 이미 해제된 메모리 포인터 등 참 다양하다.
혹은 애초에 메모리를 할당하는데 할당량에 엉뚱한 값이 들어와서 뻗은 것일 수도 있다. 가령, 음수만치 할당은 저 문맥에서는 대체로 부호 없는 정수로 바뀌면서 도저히 감당 불가능한 엄청난 양의 메모리 요청으로 바뀌기 때문이다.

2. CPU 사용 없는 무한루프

단독으로 돌아가는 프로그램이 제발로 이렇게 되는 경우는 잘 없다. 이건 스레드 내지 프로세스 간에 서로 아귀가 안 맞는 상호 대기로 인해 deadlock에 걸려서 마취에서 못 깨어난 상황이다. 그러니 엄밀히 말해 무한루프보다는 무한대기에 더 가깝겠다.
굳이 커널 오브젝트를 직접 취급하지 않고 윈도우 메시지를 주고받다가도 이렇게 될 수 있다. 가령, 스레드 A가 타 프로세스/스레드 소속의 윈도우 B에다가 SendMessage를 해서 응답을 기다리고 있는 중인데, B는 또 스레드 A가 생성한 윈도우에다가 SendMessage를 했을 때 말이다. 요 데드락을 해소하려고 ReplyMessage라는 함수가 있다.

3. CPU 쳐묵과 함께 무한루프

종료 조건을 잘못 명시하는 바람에 loop에서 빠져나오지 못하는 경우이다. 부호 없는 정수형으로 변수를 선언해 놓고는 while(a>=0) a--; 이런 식으로 코딩을 해서 무한루프에 빠지는 경우도 있다. 얘는 그래도 다행히 메모리 관련 문제는 없는 상황이다.

4. stack overflow와 함께 뻗음

이건 단순 뺑뺑이가 아니라 재귀호출을 종료하지 못하고 비정상적으로 반복하다 이 지경이 된 것으로, 컴에 메모리가 무한하다면 3번 같은 무한루프가 됐을 상황이다. 하지만 현실에서는 물리적인 자원의 한계가 있고, 또 컴이 취급 가능한 메모리 주소 자릿수 자체도 무한하지 않기 때문에 언젠가는 뻗을 수밖에 없다.

재귀호출도 반드시 A-A-A-A-A... 이렇게 단일 함수만 쌓이는 게 아니라 마치 유리수 순환소수처럼 여러 함수 호출이 주기적으로 쌓이는 경우도 있다.
스택은 다음에서 다룰 heap 메모리와는 달리, 그래도 그 정의상 할당의 역순으로 회수되고, 회수가 반드시 된다는 보장은 있다.

5. 메모리 쳐묵과 함께 뻗음

이건 heap memory의 leak을 견디다 못하고 프로그램이 뻗은 것이다. loop 안에서 계속해서 leak이 발생하면 꽤 골치아프다. 또한, 금방 발견되는 leak은 그나마 다행이지, 프로그램을 몇 주, 몇 달째 돌리다가 뒤늦게 발견되는 것은 더 답이 없고 잡기 어렵다. 프로그램이 뻗은 지점이 실제로 문제가 있는 지점과는 전혀 관계 없는 곳이기 때문이다. 뭔가 컴파일 에러와 링크 에러의 차이와도 비슷한 것 같다.

요약하면, 메모리 쪽 문제는 가능한 한 안 마주치는 게 낫고, 마주치더라도 프로그램이 곧장 뻗어 주는 게 디버깅에 유리하다. 1과 5는 포인터를 대놓고 취급하지 않는 C/C++ 이외의 언어에서는 프로그래머가 직접 볼 일이 드물다.
요즘은 그래도 디바이스 드라이버 급이 아닌 평범한 양민 프로그램이라면 메모리 문제로 뻗는 경우 전적으로 혼자만 뻗지, 컴퓨터 전체를 다운시키는 일은 없으니 세상 참 좋아졌다. 이게 다 가상 메모리와 보호 모드 덕분이다.

Posted by 사무엘

2017/10/03 19:34 2017/10/03 19:34
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1412

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

본인은 몇 년 전에 쓴 글을 통해 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

C/C++, 자바, 파이썬 등 프로그래밍 언어를 하나 배워서 초보 딱지를 뗄 정도가 되면, 프로그래밍을 할 줄 모르던 때보다 컴퓨터를 훨씬 더 유용하고 창의적으로 활용할 수 있게 된다. 초보 딱지를 뗐다는 건 한 변수로부터 복수 개 + 다중 계층 형태로 된 숫자나 문자열에 접근하는 '복합 자료형(composite type)'을 다룰 수 있고, 함수와 반복문과 재귀호출로 반복 절차를 구현할 수 있음을 의미한다. 거기에다 Windows API에 대한 약간의 지식이 필요하다.

뭐, C/C++보다 더 고수준 언어를 쓴다면 날포인터(raw pointer)를 써서 수동 메모리 관리까지 직접 다룰 일은 없겠지만, 거기는 거기 고유한 방식으로 리스트나 시퀀스처럼 복합 자료형을 제공하는 게 있을 것이다. 복합 자료형과 실행 시간 조건부 반복 및 분기가 튜링 완전한 계산 모델의 본질이며, 자연어로 치면 그냥 Hello world나 I am a boy를 넘어서 길고 복잡한 안은 문장과 이어진 문장을 자유자재로 구사하는 것과 같다.

모든 사람이 전산학과 코딩을 전공으로 삼아 생업 수준으로까지 할 필요는 전혀 없다. 굳이 번듯한 GUI 갖추고 제3자가 쓸 만한 번듯한 소프트웨어를 개발하는 경지에 이르지는 않아도 된다. 그냥 일상생활에서 내가 당면한 문제를 코딩으로 스스로 해결하는 '자가용' 프로그래밍 스킬만으로 충분하다. 원하는 웹사이트 내용을 크롤링 해서 텍스트를 추출하거나, 방대한 무슨 데이터 파일을 내 입맛에 맞게 변환· 가공하거나, 특정 시간대에나 주기적으로 컴퓨터로 하여금 특정 작업을 수행하게 하는 게 대표적인 예이다.

물론 프로그래밍을 공부하는 대신, 그런 일을 수행해 주는 유틸리티(특히 매크로 같은..)를 찾아서 사용법을 익히는 것도 방법이 될 수 있다. 그러나 본인의 경우는 간단한 건 그냥 직접 만들어 쓴다. 그게 더 빠르다.
옛날 직장에 다니던 시절엔 이튿날 아침 9시 3분 전에 회사 인트라넷에 접속해서 출근 도장을 자동으로 찍게 하는 프로그램을 짜 놓고 퇴근 후, 정작 나는 다음날 느긋하게 출근하기도 했었다. 이 정도 잔머리야 뭐 직업 프로그래머라면 완전 껌(piece of cake)일 것이고, 반대로 회사에서 작정하고 오토의 부정 사용을 단속하려 한다면 키보드 드라이버 차원의 보안 프로그램들로 직원들의 컴을 도배시켜 놓겠지만 말이다.

잡다한 서론이 좀 길어졌으니 본론으로 들어가도록 하겠다. 컴퓨터 프로그래밍에는 저렇게 고정된 입력에 대해서 언제나 고정된 답만 출력하는 작업 말고 의외로 재미있고 유용한 분야가 있는데, 바로 난수(random number) 생성을 이용한 시뮬레이션, 무작위 표본 추출 등이다.

이 글은 난수 생성 방법 자체에 대해 다루지는 않을 것이다. 그래도 말이 나왔으니 잠시 언급하자면, 난수란 그 정의상 등장 패턴을 예측할 수 없으면서(혹은, 몹시 어렵고) 각 숫자들의 등장 빈도에 치우침이 없어야 할 것이다. 파이나 자연상수 같은 유명한 무리수가 파면 팔수록 끝없이 생성하는 소수점들은 난수의 범주에 든다고 볼 수 있으려나 모르겠다.

품질 좋은 난수를 값싸고 빠르게 많이 생성하는 알고리즘에 대한 수요는 매우 많으며, 이건 정수와 관련된 응용 수학에서 매우 중요하게 다뤄지는 분야이다. 옛날에 CACM에서 Random numbers: good ones are hard to find라는 논문을 봤던 기억이 나는데... 거기는 그 정도 퀄리티의 논문이 그야말로 상상도 할 수 없을 정도로 옛날에(1988년!) 이미 게재되었다는 게 전율이 느껴진다.
시뮬레이션도 좋고 각종 게임도 좋지만 추첨 역시 단순 유흥이 아니라 그야말로 사람의 인생과 진로를 결정하는 매우 사무적이고 크리티컬한 분야에 쓰인다.

추첨의 가장 간단한 형태는 A명의 사람에게 B개의 물건을 무작위로 배분하거나(단, A>B) 그냥 B명을 무작위로 답정너 선발하는 것이다. 그리고 이를 일반화하면 단순히 "당첨 B개 vs 꽝 A-B개"라는 이분법적인 상태를 넘어서 3개 이상의 상태를 배분하는 것도 생각할 수 있다.
이런 추첨을 종이와 연필만으로 수행하는 대중적인 방법 중 하나는 사다리 게임이다. 이 정도 추첨이야 언제 어디서든 필요할 때 하라고 사다리를 무작위로 생성해 주는 스마트폰 앱도 진작에 나와 있다.

그러나 현실에서는 이보다 더 복잡한 조건을 주고 추첨을 해야 할 때도 있다. 조 추첨이 대표적인 예인데, 각 조별로 인원과 성별이 비록 조의 수로 나눠 떨어지지 않더라도 최대한 균일하게 유지돼야 하며, 그 밖에 구성원들별로 다른 내부 속성도 최대한 균일하게 유지돼야 한다.
본인은 고등학교 시절에 반 내지 학교 행사 때 테이블별 인원 추첨을 컴퓨터 프로그램을 짜서 실시한 적이 있다. 하긴, 반 편성 자체도 일단 컴퓨터가 뒤섞어 놓은 결과에다가 각 반 담임들이 보정을 해서 뽑는다고 들었다. 가령, 문제아들은 한 반에 몰리지 않고 최대한 서로 다른 반에 찢어지게 말이다.

그 뒤 본인은 최근에는 교회 청년부의 소그룹 기도 모임의 인원을 분기별로 새로 추첨해 주는 프로그램을 작성했다.
이 역시 기본적으로 조별 인원과 성별부터 균등하게 맞추지만, 거기에다가 모임에 활발히 참여하는 사람과 그렇지 않은 사람도 나눠서 특정 성향의 사람이 한 조에 너무 몰리지 않고 최대한 분산되게 하는 조건을 추가했다.
그리고 또 중요한 것으로, 동일 집안의 친형제· 친자매· 친남매는 같은 조에 결코 걸리지 않게 했다. 흥미롭지 않은가?

처음에 인원과 성별은 무조건 균등하게 나오게 틀을 먼저 짜서 했다. 그러나 나머지 필터링은 알고리즘으로 구현한 게 아니라 무식한 방법을 썼다. 추첨 결과가 조건을 전체 만족하는지 검사해서 안 그러면 그냥 빠꾸 시키고 될 때까지 추첨을 다시 한다. 그러니 이건 프로그램의 실행 종료와 성공 여부를 전적으로 난수 생성 알고리즘의 품질에다 맡기는 셈이다.

물론 이렇게만 해도 소규모 인원의 조편성 결과쯤이야 운이 나빠 봤자 몇십 번 정도 뺑뺑이 만에 답이 즉시 잘 튀어나온다. 허나, 진지한 프로그램이라면 추첨 결과에 anomaly가 존재하면 조의 인원을 무작위하게, 적절하게 교환하고 보정을 해서 그걸 해소해야 할 것이다. 난수 생성 결과와 무관하게 수행이 유한 시간 만에 끝난다는 게 보장되는 알고리즘으로 말이다.

더 나아가면 이렇게 추첨이라는 computation을 위한 범용적인 '로직 선언형 프로그래밍 언어'를 생각할 수 있을 것 같다. 어찌 보면 SQL처럼 select A from B where 같은 문법 구조를 가질 수도 있겠다. 10명의 인원에다 무엇을 배당하되 무엇과 무엇에는 무엇이 같아서는 안 되고..
마치 "A와 B의 사이에는 C가 있지 않다. C의 오른쪽에는 D가 있다." 이런 단서들 주고 나서 "A~D의 가능한 정렬 순서는 무엇인가?" 이런 문제를 풀듯이 추첨 조건을 쫙 명시할 수 있다.

모든 조건의 충족이 불가능하다면 무식하게 무한 루프에 빠지는 게 아니라 저 조건들만 분석해 보고는 일찌감치 "성립 불가능, 답 없음"이라고 에러가 깔끔하게 튀어나와야 한다.
조건들 중에는 일단 추첨 뒤에 사후 보정을 해야 하는 것도 있겠지만, 여러 가지 속성 변수들을 균등하게 분할하는 것은 변수의 개수만큼 n차원 공간을 만들어서 거기에다가 차곡차곡 무작위로 숫자들을 채워 넣는 선형대수학 같은 방법론을 동원해서 구현할 수도 있을 것 같다. 아무튼 추첨· 배분과 관련된 수학 패키지나 프로그래밍 언어 솔루션이 있는지 궁금하다.

그리고 다음으로.. 컴퓨터 추첨은 추첨 알고리즘에 인위적인 조작이 없다는 걸 어떻게 보장하느냐고 결과에 대한 불신이 있을 수 있다.
이걸 해소하기 위해서는 제3자 참관인을 두는 게 바람직할 듯하다. 그래서 1부터 N회 중 가추첨을 몇 번 할지를 결정하게 한 뒤, 그 횟수를 공언한다. 그리고 그 횟수만큼 그 사람이 실제로 추첨을 돌리고 N회째의 결과를 최종 결과물로 선택하는 것이 모두에게 공정할 것 같다.

프로그램을 개발하는 사람 입장에서는 몇 회째에 조작된 결과를 내놓아야 할지 알 수 없으며, 참관인은 자기가 원하는 추첨 결과가 나왔을 때가 아니라, 먼저 약속했던 횟수만큼만 가추첨을 돌리다가 최종 결과에는 승복해야 한다. 그리고 가추첨의 결과도 계속 공개되므로 각 가추첨의 결과가 충분히 무작위하지 않고 이상하다면 이의 제기가 가능하다.

빵 같은 걸 두 사람이 먹게 반으로 나눌 때, 한 사람은 칼로 빵을 나누고 다른 한 사람은 나눠진 결과물 중 원하는 것(= 더 큰 것)을 취사선택하게 한다면 그야말로 두 사람이 모두 만족하는 결과가 나올 수밖에 없을 것이다. 이와 비슷한 시스템을 구현하는 것으로 논란을 잠재우는 게 합리적이어 보인다.

Posted by 사무엘

2017/07/29 19:33 2017/07/29 19:33
, , ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1387

1. 나랏말씀

이런 프로그램이 과거에 개발되어 나왔다는 것을 머리로는 알고 있었지만, 인터넷에 굴러다니는 걸 구해서 도스박스에서 개인적으로 직접 돌려 본 건 굉장히 최근의 일이었다.
얘는 개발 목표와 이념이 완전히 일치하는 건 아니지만, 어찌 보면 날개셋 편집기의 먼 조상뻘 되는 프로그램이라 해도 과언이 아니다.

그리고 얘는 시대를 굉장히 앞서 갔던 프로그램이다. 1993~94년 사이에 개발됐는데 무려 유니코드 1.1 방식 옛한글을 세벌식 글자판과 글꼴로 입· 출력하는 텍스트 에디터이기 때문이다. 비록 에디터로서의 기능은 매우 빈약하고(찾기 기능도 없다!) 글자 모양도 심히 열악하지만 그래도 모아쓰기 출력과 글자 단위 cursor 이동 같은 최소한의 처리는 옳게 지원된다.

사용자 삽입 이미지

예문으로 훈민정음 서문이 포함돼 있다. 방점도 자형 자체는 있지만 오늘날 OpenType 규격처럼 글자의 뒤에다가 쳐 넣으면 글자의 왼쪽에다 알아서 찍어 주지는 않는다.

그 옛날에 국내에서는 겨우 2바이트 조합형이니 완성형이니 하면서 논쟁이 진행 중이었을 뿐, 유니코드를 아는 사람은 일부 극소수 계층 말고는 없었다. 대다수 사람들에게 유니코드는 최소한 2.0이 제정되고 나서 Windows 98 이후의 시간대는 돼서야 갑자기 툭 튀어나온 물건이다.

하물며 지금 같은 인터넷도 없고 "유니코드가 뭐야? 먹는 거야? 주변에 지원하는 프로그램도 아무것도 없구만?" 이러던 초창기였으니.. 이 프로그램은 서식이나 레이아웃 기능이 없는 텍스트 에디터임에도 불구하고 저장한 파일을 읽을 수 있는 프로그램이 사실상 자기 자신밖에 없었다.

이건 평범하게 터보 C 공부한 어느 똑똑한 대학생이 뚝딱 만들어서 PC 통신에다 올릴 만한 물건은 아니고, 사실은 부산 대학교 김 경석 교수 연구실에서 개발해서 배포한 프로그램이다. 지도교수는 그 당시 유니코드 위원회의 우리나라 대표였고, 동료 학자들과 함께(아마 국어학자들과도..) 문헌을 뒤져서 옛한글 자모들을 선별했으며 <컴퓨터 속의 한글 이야기>라는 책을 쓰기도 했다. 이 프로그램은 그 책의 부록 디스켓에 포함돼 있다.

물론, 프로그램의 실제 개발은 제자인 대학원생이 했다. 프로그램 개발자들은 다들 부족한 시간과 여건 속에서 코딩을 하는 편인데, 이 프로그램은 의외로 화면 비주얼은 신경을 썼는지 VGA 기본 팔레트가 아니라 연보라색 계열의 자체 색상을 쓰고 있다.

완성된 글자들의 모양은 볼품없지만, 이미 찍힌 낱자의 모양이 입력 도중에 다른 성분의 낱자에 의해 바뀌지 않기 때문에 뭔가 타자기를 쓰는 것 같다. 세벌식 글자판에다 세벌식 글꼴의 묘미를 제대로 경험할 수 있다. 더구나 이 프로그램에서 최초로 채택한 '세벌식 옛한글' 글쇠배열은 오늘날까지 아래아한글이나 날개셋이 그대로 계승하고 있기도 하다.

유니코드 1.1 옛한글 자모 집합은 그 전 1992년에 발표된 아래아한글 2.0이 사용하는 한컴 2바이트 코드에도 반영된 바 있다. 하지만 아래아한글은 아직 2바이트 완조형에 묶여 있었기 때문에 옛한글의 표현 능력이 온전하지 못했다.

굉장히 의외의 사실인데, 이 프로그램은 텍스트를 빅 엔디언 방식으로 저장한다. 다시 말해 UTF-16BE라는 것이다. LE가 아니라. (물론 저 당시에는 UTF라는 계층은 존재하지 않았기 때문에 UCS2만 있음)
저기서 입력하고 저장한 텍스트는 MS Word처럼 UTF-16BE를 지원하는 소수의 프로그램에서 인코딩을 수동으로 지정해 줘서 연 뒤, 날개셋 편집기에서 한글 형태 정규화를 해 줘야 오늘날 통용 가능한 텍스트 형태로 바뀐다. 저 프로그램이 개발되던 시절에는 지금 같은 U+AC00으로 시작하는 현대 한글 글자마디 11172자가 아직 없었다. byte order mark 같은 것도 없었다.

한편, 나랏말씀은 옛날에 만들어진 프로그램이지만 문서 저장 확인 질문의 Yes/No가 "예/아니오"가 아니라 "예/아니요"인 게 인상적이었다. 그 시절에 본인은 '아니요'를 그 어떤 프로그램의 UI에서도 보지 못했다. 표준어가 나중에 개정되기라고 했나 싶었는데, 그건 아니고 '아니오'가 오랫동안 컴퓨터 프로그램들 사이에서 잘못 내려온 관행이라고 한다.
아래아한글은 2002부터, Windows은 Vista부터 '아니요'로 바뀌었다. 단, 요즘 프로그램들은 대답 자체에 '저장함/저장 안 함'처럼 동작을 일일이 집어넣는 게 대세가 되어 가다 보니 간단한 "예/아니요"를 볼 일이 예전보다 드물어져 있다.

나랏말씀 같은 프로그램이 있다는 걸 내가 더 일찍 알았으면 날개셋 한글 입력기도 한컴 2바이트 코드 같은 걸 거칠 필요 없이, 3.x보다 더 이른 1이나 2.x 버전부터 유니코드 옛한글 기반으로 개발됐을지 모르겠다.
내 프로그램이 1990년대 초· 중반에 개발돼 나왔으면 도스를 겨냥해서 자체한글 텍스트 에디터(편집기) 내지 도스용 한글 바이오스(외부 모듈), 혹은 간단한 램 상주 키보드 훅킹 유틸(입력 패드) 형태로 나왔지 싶다. 흥미로운 상상이 아닐 수 없다. 아예 한글 Windows용 3rd-party IME나 영문 Windows를 통째로 한글화하는 시스템은 만들기 너무 어려웠을 것 같고.

비록 내 프로그램은 기본적인 한글 입출력 인프라는 운영체제 차원에서 다 갖춰지고 보장된 뒤에야 개발되어 나왔지만, 그래도 기성 IME들이 지원하지 않는 기능들이 매우 많으며 구현체 차원에서도 Windows IME의 온갖 지저분한 꼼수 동작들을 이 정도로 보정하는 3rd-party IME라는 존재 역할을 수행하고 있다.

2. 신의손

본인은 한글 IME/텍스트 에디터뿐만 아니라 고유한 타자연습 프로그램도 개발하고 지금까지 유지보수 중이다.
타자연습을 처음 개발하던 시절에는 당연히 그 당시 국내에 이미 나와 있던 다른 타자연습 프로그램들을 먼저 쭉 살펴보면서 벤치마킹을 했다.

그런데 그 중 압도적으로 높은 완성도로 제일 잘 만들어진 프로그램을 꼽자면.. 단연 '신의손'이었다. 지금까지도 이 생각은 변함없다.
20년 전 하이텔 게임 제작 동호회 공모전에서 '삭제되었수다'가 혼자 튀면서 압도적인 1등을 했듯이, 타자연습 프로그램 중에서는 신의손이 지존이었다고 개인적으로 생각한다.
상업용 내지 셰어웨어로 만들어도 됐을 퀄리티였다.

사용자 삽입 이미지

디자이너가 따로 없이 1인 개발자의 해골만으로 VGA 16색에서 어지간한 게임에 준하는 저 정도의 미려한 그래픽과 UI를 만든 거라면 정말 보통 실력이 아니다.
게임도 스토리나 설정 같은 게 센스가 철철 넘지고.. (신 중의 신 고무신 ㄲㄲㄲ)
내 경험상 16컬러에서 팔레트를 자체적으로 조작해서 자기만의 독자적인 색깔톤을 만들 정도의 프로그램이라면 비주얼에 신경을 꽤 쓴 프로그램이다. 예전의 도스용 Packard Bell desktop 셸처럼 말이다.

사용자 삽입 이미지

(보통은 저런 파란 톤이지만 최고 어려운 마지막 난이도에서는 화면이 전반적으로 시뻘건 톤으로 바뀐다. 우와~!)
도스용이지만 그래도 나온 때가 1995년이다 보니, 보다시피 Windows에서 그 퍼런 키보드 배경 그림(95에서만 존재하던!)과 각종 아이콘과 굴림체 글자를 따서 도스용 프로그램에다 접목한 것도 꽤 독특한 분위기를 만들어 냈다.
식상한 윗줄 따라 치기 말고 좌우/상하 대조 같은 연습 방식을 도입한 것도 얘가 원조였지 싶다.

손가락 모양의 포인터로 각종 버튼과 UI 요소들을 선택하는 GUI 비주얼을 자랑하면서 정작 마우스를 지원하지 않은 건 조금 의외였다. 허나, 키보드 뚜드리는 연습만 하라고 만들어진 프로그램이 굳이 마우스까지 지원할 필요는 없긴 하지..

신의손의 개발자는 백 승찬 씨로 알려져 있다. ('신자'이신 분은 옹기장이 백 승남 씨와 혼동하지 말 것.)
이분은 정말 진지하게 게임 개발자로 가도 되지 않을까 생각했는데..
근황을 검색해 보니.. 아아~ P2P 유틸인 프루나도 만들었으며 2010년대부터는 이미 모바일로 전향하여 어썸노트라는 앱을 개발해서 여전히 1인 기업 하고 계신다.

신의손은 그냥 대학교 재학 시절에 만든 것인데, 그 프로그램을 상업용으로 판매하려고 유통처를 알아보고 고생도 했다고 한다. (그러나 현실적인 한계에 부딪혀서 실제로 하지는 못했음)
내가 겨우 날개셋 2.x 갖고 깨작거리던 나이 때 벌써 저런 프로그램을 만들 정도였으니 지금은 뭘 못하겠냐..;;
나는 17년 전이나 지금이나 최소한 플랫폼과 외형은 진~짜 바뀐 거 없는 투박하고 공대감성 충만한 프로그램만 죽어라고 파고 있는걸. ㄲㄲㄲㄲㄲ

세상 참 많이도 바뀌었다. 창의적인 일을 하는 모든 분들에게서 경외감을 느끼는 하루이다.

그러고 보니 옛날 프로그램들 중에는 메뉴를 열어 놓은 상태에서도 단축키가 곧장 동작하는 것들이 있었다. 당장 떠오르는 예는 도스용 아래아한글, PowerBasic IDE처럼.
Windows의 기본 UI는 구조적으로 이게 전혀 지원되지 않고 그 대신 메뉴 자체의 액셀러레이터만이 동작하게 돼 있다.
어디서든이 단축키가 동작하는 게 편하긴 한데 그래도 지금은 바뀐 프로그램에 사용자가 적응해야 할 때이긴 하다.

Posted by 사무엘

2017/07/27 08:38 2017/07/27 08:38
, , ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1386

1. 트리플

  • 저그 해처리 레어 하이브 : 학사 석사 박사
  • 워드 엑셀 파워포인트 : 서울 지하철 1호선 2호선 3호선 (힌트: 색깔의 유사성)
  • 크레용-크레파스-파스텔 : 짜장면-짜파게티-스파게티
  • 레코드 SP EP LP : 그래픽 카드 CGA EGA VGA =_=;;

세상에는 3개로 이뤄진 진영이 속성이 서로 다 동일하거나 제각기 다 다른 경우가 있다.
스타크래프트의 플토, 테란, 저그가 좋은 예이다. 미네랄과 가스, 서플라이를 사용하며 기본적인 공방 업그레이드가 3단계씩 있는 건 공통이지만, 건물을 짓는 방식이나 각종 스킬 같은 건 전부 제각기 다르다. 두 종족은 완전 공통인데 한 종족만 다른 경우는... 일단 없다.

요런 체계를 모델링한 세트(Set)라는 아주 재미있는 머리 싸움 보드 게임도 있다. 4가지 속성(색깔, 도형 개수, 채움 패턴, 도형 모양)이 전부 같거나 전부 다른 카드 트리플을 빨리 찾는 게 목적이다. n개의 카드가 있을 때 세트가 하나라도 존재할 확률 내지 전혀 존재하지 않을 확률도 구할 수 있을 텐데 내 수학 지식으로는 모르겠다.

필기 내지 그리기 도구로서 색연필-크레용-크레파스-파스텔-콩테로 갈수록 특성이 어떻게 달라지나 모르겠다. 파스텔은 그냥 딱딱한 분필 같고, 크레파스는 왁스가 들어가서 그런지 좀 끈적거렸던 것 같다.
콩테나 목탄 같은 건 본 적 없다. 목탄화는 <플란다스의 개>의 주인공이 화가 지망생이라 쓰던 물건이라는 것 정도만 알고 있다.

2. 트윈

  • 지하철역 중에서 종로3가와 종로5가는 영락없이 삼겹살과 오겹살을 떠올리게 한다. -_-
  • 농사에 비닐하우스가 있다면, 야구에는 돔구장이 있는 듯..
  • 서울 강북에 청와대 뒷산인 북악산이 있다면 강남에는 국정원 뒷산인 대모산이 있다. 그리고 강북에 거대한 군사 시설인 용산 미군 기지가 있다면, 강남에는 국군정보사가 있어서 서초대로에 길이 끊겨 있고 gap이 존재한다. 서로 비슷한 심상이 느껴지는데, 얘들은 가까운 미래에 서울 밖으로 이전할 계획이 잡혀 있다.
  • 스타크래프트에나 있어야 할 옵티컬 플레어의 실사판: 공중으로 쏘는 레이저 포인터(특히 녹색), 육지 자동차에는 HID 불법 개조.

크리스천들이 하나님에 대해서는 그분이 우리가 원하는 것을 주시는 게 아니라 우리에게 "필요한" 것, 우리에게 유익한 것을 주신다고 믿는다.
허나, 사람에 대해서는 능력껏 벌어서 "필요한" 만큼 가져가는 세상은 필연적으로 다같이 망하고 거지 되는 세상을 부른다.
이것이 신과 인간에 대해서 '필요'라는 개념이 작용하는 방식의 차이점이다. 이건 성악설이 성립하는 한 반박 불가능할 것이다.

그리고 한편, 컴퓨터와 관련해서는..

  • 옛날에 컴퓨터를 다루던 사람들은 디스크의 배드 섹터를 걱정했지만 요즘 사람들은 모니터의 불량 화소를 신경 쓰는 듯하다.
  • 옛날 사람들은 컴퓨터를 오래 쓰면 Windows 3.x 내지 9x의 리소스 퍼센티지가 줄어드는 걸 보고 바싹 긴장했지만, 요즘 사람들은 스마트폰의 배터리 퍼센티지가 줄어드는 걸 보고 바싹 긴장한다.

3. 부정적인 예

  • C++ 템플릿의 문제(소스 코드가 노출된 채 모든 번역 단위에 매번 인클루드 돼야 함)를 해결하려고 고민했는데 기껏 나온 게 export
  • 남북이 통일하랬더니 기껏 나온 게 고려연방제 (1국가 2체제.. 그냥 전쟁만 없는 반쯤 적화통일)
  • ActiveX를 없애라고 하자 나온 게 EXE 프로그램

이들이 무슨 공통점이 있는지는 설명이 필요하지 않을 것이다. 본질적인 문제는 전혀 해결하지 않은 채 그냥 눈 가리고 아웅일 뿐이다.

4. 긍정적인 예

  • 건축업계· 학계에서는 '철근 + 콘크리트'가 신이 건축· 재료공학계에 내린 천혜의 재료 궁합이라고 그런다. 열팽창 계수가 거의 같아서 혼합 가능하면서도 서로 장점을 부각시키고 단점은 보완하면서 최고의 건축 자재 역할을 하기 때문이다.
  • 항공업계에서는 하필 여객기의 최적 순항 고도에 제트 기류라는 게 존재하는 게 기적적인 행운이라고 한다.
  • 1970년대에 인류가 우주 개발을 하고 있을 때 마침 태양계 행성들이 가까이 일렬로 배열돼 있어서 보이저 2호는 단독으로 천왕성과 해왕성을 동시에 탐사하는 대박을 경험할 수 있었다. 이것도 못해도 백수십 년 만에 한 번 찾아오는 기회였다고 그런다.

이런 예가 더 있을지 궁금하다. 혹시 반도체를 만드는 데에도 무슨 천혜의 자연 광물 특성이 활용되는 게 있지 않을까?

5. 예상치 못한 대박

예전에 교통수단 관련 글을 쓰면서 한 번씩 언급한 적이 있는 내용이긴 하지만 복습 차원에서..

  • 서울 지하철 9호선은 잠재적 수요를 인정받은 덕분에 서울 3기 지하철 중 거의 유일하게 얘 혼자만 노선 계획이 온전히 살아남아서 건설되고 개통되었다. 그런데 한편으로는 국가에서는 이거 만들어 봤자 지상의 올림픽대로를 달리는 자동차들의 적수가 못 될 것이고 공기수송 적자이면 어쩌나 지금의 입장에서는 참 쓸데없는 걱정을 했다. 그래서 전동차도 달랑 4량으로 편성하고, 최대한 메리트를 끌어올리려고 급행도 만들었다. 그랬는데, 실제로 뚜껑을 열어 보니 9호선은 초대박을 쳐서 최악의 가축수송 혼잡도를 보이는 노선이 됐다.
  • 지난 1960년대 말, 보잉 사에서는 유럽에서 초음속 여객기 콩코드가 개발되는 걸 예의주시하면서 "이거 초음속 여객기가 대박을 치면 어쩌나" 생각을 했다. 그래서 자기들도 초음속기인 보잉 2707을 개발 준비만 하면서 간을 보는 한편으로, 이미 개발 중이던 보잉 747 아음속 여객기는 주류에서 밀려날 경우 화물기로 언제든지 개조 가능하게 만반의 대비를 해 놓았다. 그러나 뚜껑을 열어 보니 초음속기는 가성비가 심각하게 부족했고 오일 쇼크와도 맞물려 영 재미를 못 봤다. 그 대신 747은 대형 여객기로 수십 년간 엄청난 성공을 거뒀다.

6. 2단계 계층

컴퓨터 프로그램 내지 알고리즘을 보면 작업을 수행하는 양상이 명백하게 독립된 두 phase로 나뉘는 것이 있다.

  • 힙 정렬: 정렬 알고리즘 중에는 얘가 꽤 독특하다. 배열을 기반으로 heap을 생성하는 단계와, 그 heap으로부터 최종적으로 정렬된 리스트를 하나씩 뽑아내는 단계로 나뉜다.
  • 컴파일러: 소스 코드를 구문 분석을 해서 내부 representation으로 변환하는 프런트 엔드, 그리고 이를 토대로 최적화와 기계어 코드 생성을 하는 백 엔드로 단계가 분명하게 나뉜다.
  • 일본어 IME: 일본 문자 자체를 입력하는 방식과, 그 일본어 문자열을 NLP 관점에서 분석해서 어절을 나누고 한자 변환 후보를 제시하는 것은 서로 완전히 별개의 단계이다. 그러니 전자와 후자를 분리해서 일부 파트만 서로 다른 알고리즘 내지 DB 제품으로 교체해서 사용할 수 있지 않나 싶다.

그리고 데이터 압축이 있다. 흔히 간과하기 쉬운데, 압축이라는 절차도 두 단계로 나뉜다. 먼저, 원소나열법을 간단한 조건제시법으로 바꿀 만한 규칙성, 반복 패턴을 찾아서 더 간결한 방식으로 표현 방식을 바꾸는 것이 전자이다. 전자를 수행하는 방법은 그야말로 무궁무진하며, 손실 압축과 비손실 압축도 이걸 수행하는 방식의 차이에 지나지 않는다. 압축된 데이터는 데이터 + 탈출문자 + 약어에 대한 번문 명령(expansion instruction) + 사전 참조 오프셋 같은 게 뒤죽박죽 섞여 있다.

그 다음으로, 이런 인코딩 결과를 정보 이론 관점에서 빈틈 없는 아주 compact한 형태로 물리적인 표현 방식을 바꿔서 최종 출력하는 것이 후자이다. 후자는 이론적인 압축률의 한계도 다 증명돼 있고 전자에 비해 더 발전할 게 별로 없는 상태이다.
압축 알고리즘이라 하면 이 둘을 싸잡아서 한데 일컫는 경향이 있으나, 이 두 단계는 엄연히 용도와 성격이 다르다. 가령, jpg 이미지 포맷의 경우 이산 코싸인 변환은 전자요, 결과를 허프만 코딩으로 출력하는 것은 후자에 대응한다.

요즘은 보기 힘든데 1990년대에 Windows Installer가 아직 없던 시절에는 마소에서는 확장자가 cab이던가? 독자적인 압축 파일 포맷을 써서 프로그램을 배포했다. 더 옛날에는 원본 디스크를 보면 설치되는 파일들이 확장자만 다 _xe, _ll 혹은 ex_, dl_ 이런 식으로 바뀌고 안에 내용은 어설프게 압축되어 있곤 했다. Lempel-Ziv 같은 알고리즘으로 압축되긴 했는데, 코딩 방식을 조밀화하는 '후처리'는 하지 않아서 가끔씩 원본 파일에 들어있는 문자열이 드문드문 보이곤 했다.

파일을 압축하면 기본적으로 전자 과정을 거쳐서 크기가 줄어드는데, 후처리까지 거치면서 크기가 좀 더 감소할 뿐만 아니라 이때 진짜 난수표 같은 뒤죽박죽 비트 나열로 바뀐다. 둘은 마치 사이다에서 (1) 단 맛을 내는 향신료와 (2) 탄산, 에어컨에서 (1) 온도를 낮추는 압축기와 (2) 송풍기하고 얼추 비슷한 관계가 아닌가 싶다.

7. 혈액형과 상속 개념

코딩 하니까 드는 생각인데..
중등학교 때 혈액형과 수혈 가능성에 대해 배울 때 우리는 객체지향 프로그래밍에서 말하는 상속이라는 개념을 어렴풋이 접했다고 볼 수 있을 듯하다. 수혈 가능성은 형변환 가능성이고.

O형이 베이스 클래스이고 A, B형은 O형으로부터 상속이며 AB는 A와 B 다중 상속이다.
A형과 B형이 O형을 가상 상속을 한 건지는 잘 모르겠다.
하지만 현실은 그렇게 매끄럽지가 않기 때문에 어지간해서는 반드시 같은 유형끼리만 수혈을 하지, A/B계열형에다가 O형 피를 수혈하고 AB형에다가 A나 B형 피를 수혈한다거나 하지는 않는다고 한다. 이런 얘기는 어렸을 때 과학 책이나 교과서에서 접하지 못했다.

아무쪼록 다중 상속은 포인터의 형변환이 이뤄질 때 오프셋 보정이 필요하게 하며, pointer-to-member도 포인터 하나 형태로 간단하게 구현할 수 없게 만드는 주범이다.
다중상속 받은 한 클래스의 포인터를 다른 상속 파생 클래스의 포인터로 바꾸는 건 굉장히 조심해서 해야 한다. 이럴 때 C-style cast는 reinterpret_cast와 개념적으로 다를 바 없어지기 때문에 반드시 static_cast를 써야 실수를 예방할 수 있다.
그러니 혈액형간 typecast도 가능한 한 안 하는 게 좋아 보인다. 아무래도 위험해 보인다.

Posted by 사무엘

2017/05/22 08:27 2017/05/22 08:27
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1362

* 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

« Previous : 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : ... 18 : Next »

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2020/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 29

Site Stats

Total hits:
1331109
Today:
121
Yesterday:
377