« Previous : 1 : ... 141 : 142 : 143 : 144 : 145 : 146 : 147 : 148 : 149 : ... 215 : Next »

윤곽선 글꼴과 아이콘 이야기

1. 윤곽선 글꼴의 기술 디테일

옛날에 인쇄가 물리적인 활자로 행해졌고 컴퓨터에서도 비트맵 글꼴이 대세이던 시절에는, '폰트 한 벌'이라 하면 여기에는 서체뿐만 아니라 고정된 크기라는 개념까지 포함되어 범위가 더욱 제한적이었다.
진짜 말 그대로 활자 한 벌이다. 그 폰트가 제공하는 서너 종류의 크기로만 글자를 쓸 수 있는 것이다. 더욱이 컴퓨터용 서체의 경우 화면용과 인쇄용이 따로 있기도 했고 말이다. 한 서체를 나타내는 그런 각 크기별 폰트들을 모두 통틀어서 자족(font family)이라고 불렀다.

오늘날처럼 트루타입, 오픈타입 같은 베지어 곡선 기반의 윤곽선 폰트 기술이 보편화된 관점에서 보면, 다양한 크기를 얻는 건 너무 당연하고 하나도 특별할 게 없는 특성이지 않은가. 과거의 관행은 상상조차 하기 어려운 것 같다.
허나 과거에는 오히려 한 폰트가 혼자서 다양한 크기의 font family의 역할을 다하는 게 보통일이 아니었다. 그래서 글꼴 대화상자를 보면 트루타입 글꼴을 선택했을 경우 “이 글꼴은 트루타입 글꼴로, 화면과 프린터에서 동일한 글꼴이 사용됩니다”가 떴었다.

윤곽선 기술을 통해 크기 문제가 해결되고서야 영문 서체의 경우, font family는 다양한 크기의 집합이 아니라 bold나 italic 같은 변형의 총칭을 일컫는 개념으로 변모했다. 트루타입 글꼴이 도입되기 전엔 bold/italic은 그냥 원글꼴을 산술 연산으로 변형해서 구현했을 뿐, 별도의 글꼴로 만든다는 걸 생각하기 어려웠다. 조합 가짓수가 감당을 못 할 정도로 너무 많아지니 말이다.

인간이 쓰는 글자는 글자 하나하나가 생각보다 꽤 정교한 벡터 드로잉이다. 수많은 윤곽선 글꼴 자형을 화면에다 래스터라이즈 하려면 비트맵 글꼴만 상대하면 될 때보다 훨씬 더 많은 계산량과 메모리가 필요하다. 쉽게 말해 게임에서 2D 스프라이트가 3D 폴리곤으로 바뀌는 것과 비슷하다.

(지금이 아무리 컴퓨터의 성능이 좋아져도 3D 폴리곤으로 옛날의 스타크래프트 같은 수십· 수백 마리의 저글링 개떼 블러드를 구현하는 건 좀 찰진 맛이 안 난다. ㄲㄲㄲㄲㄲㄲ 또한 둠 2에서 둠 3으로 넘어가면서 가장 먼저 사라진 게, 예전 같은 광활한 개방된 맵에서 쏟아져 나오던 몬스터 개떼들이지 않던가. 뭐 어쨌든..)

그래서 자동차에 변속기가 필수인 것처럼 윤곽선 글꼴을 찍는 시스템은 운영체제든 무슨 프로그램이든간에 글꼴 캐시(font cache)가 반드시 있어야 한다. 쉽게 말해 자주 쓰이는 윤곽선 글꼴은 래스터라이즈된 비트맵 결과를 미리 저장해 놓고 재사용하라는 소리다.

제아무리 강한 엔진이라도 3~4단 기어에서 바로 출발 가능한 자동차는 없듯, 제아무리 날고 기는 고성능 폰트 엔진이라도 글꼴 캐시 없이 윤곽선 글꼴을 비트맵과 별 차이 없는 속도로 찍을 수는 없다.
폰트 캐시는 각종 운영체제나 소프트웨어가 잡아먹는 메모리에서 생각보다 많은 비중을 차지하고 있다. PC의 성능이 시원찮던 1990년대 중반에는 운영체제의 한글판과 영문판의 요구 시스템 사양의 차이를 만들 정도였다.

2. 힌팅

윤곽선 글꼴을 찍는 시스템은 이것만으로도 굉장히 복잡해지는데 또 하나 간과할 수 없는 것은, 바로 저해상도에서 심각하게 보기 안 좋아지는 품질 문제였다.
수백~수천 픽셀의 EM 크기에서 만들어진 매끄러운 윤곽선 패스를 수~수십 픽셀대로 축소하여 래스터라이즈하다 보면 획이 빠지거나 뭉개지거나 굵기가 뒤죽박죽이 되어 버린다. 컴퓨터의 래스터 디스플레이는 연속적인 실수가 아니라 유한한 정수 개의 픽셀로 구성되어 있으니 말이다.

요즘 유행하는 서브픽셀(ClearType)이라든가 그레이스케일은 한 픽셀에 담을 수 있는 정보량 자체를 흑백보다 더 늘려서 글자를 좀 더 부드럽게 보이게 하는 anti-aliasing 방법이다. 그러나 그 전에는 monochrome 디스플레이에서도 최대한 글자가 예쁘게 래스터라이즈 되게 하려면...

일단, 싱거운 결론이지만 이것은 원론적으로 100% 완전한 해결이 불가능한 문제이다.
래스터라이저를 아무리 귀신같이 잘 만든다 해도 이 획과 저 획이 어느 크기로 scale했을 때 간격이 같고 굵기가 같아야 할 기준을 스스로 찾을 수는 없다. 그 기준 자체가 아주 모호하고 인위적이기 때문이다.

결국, 서양에서 만들어 낸 것은 '힌팅'이라고 불리는 기술이다.
트루타입 폰트의 경우 이 힌팅이 특허로 등록되어 있었을 정도로 고급 핵심 기술이었다.
폰트 래스터라이저의 동작 알고리즘을 다 안다고 가정하고, 특정 크기에서 특정 글자는 윤곽점을 빼거나 추가하거나 위치를 옮겨서 인위적으로 이런 식으로 래스터라이즈되게끔(= 사람 눈에 보기 좋게) 윤곽선을 변조한다.

쉽게 말해 부가 정보를 덧붙인다는 뜻이다. 그래서 명칭도 힌트, 힌팅이다. 이건 100% 자동화를 할 수 없으며 장인의 정교한 수작업이 동원해야만 넣을 수 있다.
Times New Roman 같은 서체를 6~11포인트 크기로 anti-aliasing이 없이 보면 정말 하나하나 수작업으로 비트맵을 만든 게 아닌가 하는 생각이 들 정도로 모양이 예쁜 걸 볼 수 있는데, 그건 내장 비트맵이 아니라 힌팅만으로 주어진 윤곽선을 변형하여 만들어 낸 결과물이다. 래스터라이저의 범용적인 알고리즘만으로 만들 수 있는 결과물이 결코 아니다!

오늘날은 픽셀 자체를 anti-alias하는 기술이 발달하여 예전보다는 힌팅의 필요성이 줄어들었지만, 그래도 힌팅을 해 주면 윤곽선의 제어점이 래스터라이즈 기준 지점에 더 가까이 옮겨지기 때문에 뿌옇게 찍힐 것이 더 깔끔하고 배경과 글자 사이가 더 높은 채도로 찍히는 긍정적인 효과를 얻을 수 있다.

다만, 화면의 해상도까지 예전의 도트 프린터 수준으로까지 올라간다면 힌팅은 정말로 할 필요가 없고 그냥 anti-aliasing만으로 충분한 지경이 될 수 있다. 힌팅은 마치 옛날의 256색 팔레트 제어 기술만큼이나 legacy로 전락하는 날이 올지도 모른다.

그리고 한글이나 한자처럼 문자 집합의 크기가 커서 일일이 수제 힌팅을 도저히 줄 수 없는 문자는.. 애시당초 크기별로 내장 비트맵을 일일이 만들어 넣는 게 속 편하다. 뭐 요즘은 그 관행도 '맑은 고딕'을 시작으로 서서히 변하고 있긴 하지만 말이다.

3. 아이콘과 아이콘 패밀리

글꼴이 비트맵에서 윤곽선으로 넘어가면서 겪은 변화와 비슷한 맥락의 변화를 겪고 있는 곳이 또 있으니, 그건 바로 아이콘이 아닌가 싶다.

원래 응용 프로그램의 아이콘은 32*32 16컬러 크기만 있었다. 그러던 것이 Windows 95 이래로 16*16이 활발히 쓰이기 시작해서 메뉴나 작업 표시줄 같은 데서 좋은 인상을 주려면 오히려 16*16을 심혈을 기울여 잘 만들어야 하는 지경이 되었다. 아이콘 하나 때문에 Windows API에는 윈도우 클래스 등록용으로 WNDCLASSEX라는 구조체가 새로 만들어졌고 RegisterClassEx 함수가 도입되었다.

그러다 아이콘의 색깔은 256색 이상으로 늘고, 윈도우 XP에서부터는 트루컬러 정도가 아니라 알파 채널이 들어간 32비트 색상 아이콘이 등장했다. 덕분에 아이콘 하나도 크기가 수만 바이트로 늘고 프로그래머가 대충 발로 만들 수 없는 물건이 되어 버렸다. Visual Studio IDE는 최신 2012버전까지도 32비트 아이콘은 내용을 볼 수만 있지 고칠 수는 없다. 전용 그래픽 에디터가 필요해졌다.

그리고 윈도우 비스타부터는 32*32보다도 더 큰 48*48이 표준 크기로 또 추가되고, 아예 크기가 세 자리 수로 진입한 png 이미지가 아이콘 안에 들어가는 경지가 되었다. 이젠 아이콘 이미지도 예전 관행처럼 압축 없이 저장했다간 크기가 너무 커지기 때문이다.
XP까지만 해도 수만 바이트에 불과하던 간단한 메모장 프로그램이(notepad.exe) 별로 기능이 추가된 것도 없는데 비스타 이후부터 크기가 세 배로 뻥튀기 된 건 전적으로 아이콘 이미지 때문이다.

이렇게 아이콘의 크기가 커졌음에도 불구하고 아이콘은 역시나 본문 글자만큼이나 16*16의 작은 크기에서도 자신의 본분에 충실해야 한다. 이 점에서 아이콘은 글꼴과도 비슷한 구석이 있다. 단지, 크기뿐만 아니라 색상까지 고려해야 한다는 차이가 있을 뿐.

요즘은 굳이 16색까지 갖출 필요는 거의 없어졌지만, 프로그램의 한 아이콘은 똑같은 컨셉이더라도 자고로 최소한 256색과 32비트 트루컬러, 그리고 16, 32, 48과 심지어 경우에 따라서는 24나 40 같은 그 중간 크기까지 따로따로 갖추고 있어야 한다. 옛날에 크기별로 비트맵 글꼴을 따로 만들던 거랑 정확히 같은 맥락이니, 그야말로 font family에 착안하여 icon family라는 말을 만들어야 할 판이다.

그리고 작은 크기일 때는 수제 도트 노가다 말고는 정말 답이 없다. 큰 이미지를 무식하게 축소시켰다가는 품질이 그야말로 개판이 되기 때문. 아이콘에 무슨 힌팅 같은 게 있는 것도 아니니 말이다.
오늘날 아이콘은 점점 벡터 이미지처럼 되고 있는 면모가 있지만, 그렇다고 도트 노가다가 필요하지 않은 것도 아니니 참 오묘한 존재가 되어 간다는 생각이 든다.

language bar에 표시되는 각종 IME 아이콘들의 경우, 담겨 있는 정보는 단색이더라도 무조건 32비트 알파 채널 이미지로 만들지 않으면 운영체제가 아이콘을 화면에 제대로 표시해 주지를 않는다(Vista 이상 기준). 아이콘 출력을 재래식 GDI가 아니라 GDI+ 같은 다른 계층으로 하는 것 같다.

Posted by 사무엘

2013/03/16 19:31 2013/03/16 19:31
, ,
Response
No Trackback , 3 Comments
RSS :
http://moogi.new21.org/tc/rss/response/807

MFC와 View 오브젝트 이야기

1. 들어가는 말: MFC에 대한 큰 그림

MFC는 Windows API를 단순히 C++ 클래스 형태로 재포장만 한 게 아닌 독창적인 기능이 다음과 같이 최소한 세 가지 정도는 있다.

  • 가상 함수가 아니라 멤버 함수 포인터 테이블을 이용하여 메시지 핸들러를 연결시킨 메시지 맵. MFC 프로그래머 치고 BEGIN/END_MESSAGE_MAP()을 본 사람이 없다면 간첩일 것이다.
  • 운영체제가 제공하는 핸들 자료형들과 C++ 개체를 딱 일대일로 연결시키고, 특히 MFC가 자체적으로 생성하지 않은 핸들이라도 임시로 C++ 개체를 생성해서 연결했다가 나중에 idle time 때 자동으로 소멸을 시켜 주는 각종 handle map 관리자들. 절묘하다.
  • 20년도 더 전의 MFC 1.0 시절부터 있었던 특유의 document-view 아키텍처. 상당히 잘 만든 디자인이다.

양념으로 CPoint, CRect, CString 같은 클래스들도 편리한 물건이긴 하지만, 그건 너무 간단한 거니까 패스.

사실, MFC는 Windows API를 객체지향적으로 재해석하고 포장한 수준은 그리 높지 않다. 본디 API가 prototype이 구리게 설계되었으면, MFC도 해당 클래스의 멤버 함수도 똑같이 구린 prototype을 답습하고 내부 디테일을 그대로 노출했다.

이와 관련하여 내가 늘 드는 예가 하나 있다. 당시 경쟁작 라이브러리이던 볼랜드의 OWL은 radio button과 check button을 별도의 클래스로 분리했다. 그러나 MFC는 그렇게 하지 않았다. 운영체제 내부에서 둘은 똑같은 버튼 윈도우이고 스타일값만 다를 뿐이기 때문이다. 그러니 MFC로는 동일한 CButton이다. 그리고 CStatic도 마찬가지.
아마 기존 응용 프로그램의 포팅을 용이하게 하려고 의도적으로 이런 식으로 설계한 것 같긴 하지만, 이것 때문에 MFC를 비판하는 프로그래머도 물론 적지 않았던 게 사실이다.

그러나 인간이 하루 하루 숨만 쉬고 똥만 만드는 기계가 아니듯, MFC는 단순한 API 포장 껍데기가 아니라 다른 곳에서 더 수준 높은 존재감을 보여준다. 오늘 이 글에서는 document-view 아키텍처 쪽으로 얘기를 좀 해 보겠다.

2. view가 일반적인 윈도우와 다른 점

MFC는 뭔가 문서를 생성하여 작업하고 불러오거나 저장하는 일을 하는 업무용 프로그램을 만드는 일에 딱 최적화되어 있다. 그렇기 때문에 MFC AppWizard가 FM대로 생성해 주는 기본 코드는 아주 간단한 화면 데모 프로그램만 만들기에는 구조가 필요 이상으로 복잡하고 거추장스러워 보인다.
그냥 프레임 윈도우의 클라이언트 영역에다 바로 그림을 그려도 충분할 텐데 굳이 그 내부에 View라는 윈도우를 또 만들었다. 그리고 View는 Document 계층과 분리돼 있기 때문에, 화면에 그릴 컨텐츠는 따로 얻어 와야 한다.

이런 계층 구분은 소스 코드가 몇십~몇백만 줄에 달하는 전문적인 대형 소프트웨어를 개발할 걸 염두에 두고 장기적인 안목에서 해 놓은 것이다.
먼저, View와 Document를 구분해 놓은 덕분에, 동일한 Document를 여러 View가 자신만의 다양한 설정과 방법으로 화면에 동시에 표시하는 게 가능하다. 텍스트 에디터의 경우, 한 문서의 여러 지점을 여러 창에다 늘어놓고 수시로 왔다 갔다 하면서 편집할 수 있다. 한 창에서 텍스트를 고치면 수정분이 다른 창에도 다같이 반영되는 것이 백미.

일례로, MS 워드는 기본, 웹, 읽기, 인쇄, 개요 등 같은 문서를 완전히 다른 방식으로 렌더링하는 모드가 존재하지 않던가(물론, MS 워드가 MFC를 써서 개발됐다는 얘기는 아님). 게다가 이 중에 실제로 위지윅이 지원되고 장치 독립적인 레이아웃이 사용되는 모드는 인쇄 모드뿐이다. 인쇄를 제외한 다른 모드들은 인쇄 모드보다 문서를 훨씬 덜 정교하게 대충 렌더링하는 셈이다.

이렇듯, view는 그 자체만으로 독립성이 충분한 특성을 가진 계층임을 알 수 있다. view는 프레임 윈도우와도 분리되어 있는 덕분에, 한 프레임 윈도우 내부에 splitter를 통해 하위 view 윈도우가 여러 개 생성될 수도 있다.
CWnd의 파생 클래스인 CView는 윈도우 중에서도 바로 저런 용도로 쓰이는 윈도우를 나타내는 클래스이며, 부모 클래스보다 더 특화된 것은 크게 두 가지이다. 하나는 CDocument와의 연계이고 다른 하나는 화면 출력뿐만 아니라 인쇄와 관련된 기능이다.

SDI형 프로그램에서는 view 윈도우 자체는 계속 생성되어 있고 딸린 document만 수시로 바뀌기 때문에, document를 처음 출력할 때 view가 추가적인 초기화를 하라고 OnInitalUpdate라는 유용한 가상 함수가 호출된다. 그리고 화면 표시와 프린터 출력을 한꺼번에 하라고 WM_PAINT (OnPaint) 대신 OnDraw라는 가상 함수가 호출된다. 하지만 프린터 출력이 화면 출력과 기능면에서 같을 수는 없으니 CDC::IsPrinting이라든가 OnPrepareDC 같은 추가적인 함수도 갖고 있다.

그러고 보니 MFC의 view 클래스는 운영체제에 진짜 존재하는 '유사품' 메시지인 WM_PRINT 및 WM_PRINTCLIENT와는 어떻게 연계하여 동작하는지 모르겠다. 화면의 invalidate 영역과 긴밀하게 얽혀서 BeginPaint와 EndPaint 함수 호출을 동반해야 하는 WM_PAINT와는 달리, PRINT 메시지는 invalidate 영역과는 무관하게 그냥 창 내용 전체를 주어진 DC에다가 그리면 된다는 차이가 존재한다. 거의 쓰일 일이 없을 것 같은 메시지이지만, AnimateWindow 함수가 창 전환 효과를 위해 창 내용 이미지를 미리 내부 버퍼에다 저장해 놓을 때 꽤 유용하게 쓰인다.

3. CView의 파생 클래스들

MFC에는 CView에서 파생된 또 다른 클래스들이 있다. 유명한 파생 클래스 중 하나인 CCtrlView는 MFC가 자체 등록하는 클래스 말고 임의의 클래스에 속하는 윈도우를 그대로 view로 쓰게 해 준다.
그래서 운영체제의 시스템 컨트롤을 view로 사용하는 CTreeView, CListView, CEditView, CRichEditView 등등은 다 CCtrlView의 자식들이다.

  • 프로그램의 클라이언트 영역에다 CTreeView와 CListView를 splitter로 나란히 배열하면 '탐색기' 내지 레지스트리 편집기 같은 외형의 프로그램을 금세 만들 수 있다.
  • <날개셋> 편집기가 MFC를 써서 개발되던 버전 2.x 시절에는 문서 창을 CCtrlView로부터 상속받아 만들었다.

CCtrlView 말고 CView의 또 다른 메이저 파생 클래스로는 CScrollView가 있다. 얘는 이름에서 유추할 수 있듯, view에다가 스크롤과 관련된 기본 구현들이 들어있다. 텍스트 에디터 같은 줄 단위 묶음 스크롤 말고, 픽셀 단위로 컨텐츠의 스크롤이 필요한 일반 워드 프로세서, 그래픽 에디터 같은 프로그램의 view를 만들 때 매우 유용하다. 마우스 휠과 자동 스크롤 모드(휠 클릭) 처리도 다 기본 구현돼 있다.

인쇄 미리 보기 기능은 온몸으로 scroll view를 써 달라고 외치는 기능이나 다름없으며, 실제로 MFC가 내부적으로 구현해 놓은 '인쇄 미리 보기' view인 CPreviewView 클래스도 CScrollView의 자식이다.
단, 요즘은 Ctrl+휠을 굴렸을 때 확대/축소 기능도 구현하는 게 대세인데 배율까지 관리하는 건 이 클래스의 관할이 아닌 듯하다. 그건 사용자가 직접 구현해야 한다.

그럼 스크롤 가능한 view로는 오로지 자체 윈도우만 설정할 수 있느냐 하면 그렇지는 않다. CFormView는 대화상자를 view 형태로 집어넣은 클래스인데 그냥 CView가 아니라 CScrollView의 파생 클래스이다. 워낙 설정할 게 많아서 환경설정 대화상자 자체가 세로로 쭈욱 스크롤되는 프로그램은 여러분의 기억에 낯설지 않을 것이다.

옛날에 윈도우 3.x 시절의 PIF 편집기처럼 클라이언트 영역에 대화상자 스타일로 각종 설정을 입력 받는 게 많은 프로그램을 만들 때 CFormView는 대단히 편리하다. 대화상자는 여느 윈도우들과는 달리, 자식으로 추가된 컨트롤들에 대해 tab 키 순환과 Alt+단축키 처리가 메시지 처리 차원에서 추가되어 있다.

4. CScrollView 다루기

처음에는 CView로부터 상속받은 view를 만들어서 프로그램을 열심히 만들고 있다가, 뒤늦게 view에다가 스크롤 기능을 추가해야 할 필요가 생기는 경우가 종종 있다.
이미 수많은 프로그래밍 블로그에 해당 테크닉이 올라와 있듯, 이것은 대부분의 경우 base class를 CView에서 CScrollView로 문자적으로 일괄 치환하고 몇몇 추가적인 코드만 작성하면 금세 구현할 수 있다.

클래스 이름을 치환한 뒤 가장 먼저 해야 할 일은 스크롤의 기준이 될 이 view의 실제 크기를 SetScrollSizes 함수로 지정해 주는 것이다. OnInitialUpdate 타이밍 때 하면 된다. 안 해 주면 디버그 버전의 경우 아예 assertion failure가 난다.

여기까지만 하면 반은 먹고 들어간다. OnDraw 함수의 경우, 전달되는 pDC가 아예 스크롤 기준대로 좌표 이동이 되어 있다! 즉, 내부적으로 (30, 50) 위치에다가 점을 찍는 경우, 현재 스크롤 시작점이 (10, 20)으로 잡혀 있으면 화면상으로 이 위치만치 뺀 (20, 30)에 점이 찍힌다는 뜻이다. 내가 수동으로 스크롤 좌표 보정을 할 필요가 없다. 아, 이 얼마나 편리한가! invalid 영역의 좌표도 화면 기준이 아닌 내부 기준으로 다 이동된 채로 전달된다.

그러니 CView 시절에 짜 놓은 그리기 코드를 어지간하면 수정 없이 CScrollView에다 곧바로 써먹을 수 있다. 다만, 최적화만 좀 신경 써 주면 된다. 당장 화면에 표시되는 영역은 수백 픽셀에 불과한데 수천 픽셀짜리의 전체 그림을 몽땅 불필요하게 계산해서 그리는 루틴을 OnDraw에다 때려박지 않도록 주의해야 한다.
이때 유용한 함수는 RectVisible이다. 이 영역이 invalidate되었기 때문에 반드시 그려 줘야 하는지의 여부를 알 수 있다.

그 다음으로 신경을 좀 써야 하는 부분은 마우스 클릭이다.
마우스 좌표는 화면 기준으로 오지 내부 기준으로 오지는 않으므로, 내부 개체에 대한 hit test를 하려면 마우스 좌표에다가 GetScrollPosition(현재 스크롤 위치) 함수의 값을 더하면 된다.
그리고 화살표 키로 무슨 아이템을 골랐다면, 그 아이템의 영역이 지금의 화면 범위를 벗어났을 경우 스크롤을 시켜 줘야 한다. 수동 스크롤은 ScrollToPosition 함수로 하면 된다.

화면의 일부 영역을 다시 그리도록 invalidate하는 것도 스크롤 위치 반영이 아닌 그냥 지금 화면 기준의 좌표를 지정하면 된다. 그러면 OnDraw 함수에서는 스크롤 위치가 반영된 내부 좌표 기준으로 refresh 위치가 전달된다.

끝으로, 마우스로 어떤 개체나 텍스트를 눌러서 끌든, 혹은 단순 selection rectangle을 만들든 그 상태로 포인터가 화면 밖으로 나갔을 때, 타이머를 이용한 자동 스크롤도 구현해야 할 것이다. 이 역시 자동화하기에는 customization의 폭이 너무 넓기 때문에 MFC가 알아서 해 주는 건 없다. 알아서 구현할 것. 이 정도면 이제 스크롤 기능을 그럭저럭 넣었다고 볼 수 있을 것이다.

이 정도면 어지간한 개발 이슈들은 다 나온 것 같다.
참, 혹시 재래식 GDI API가 아니라 GDI+를 쓰고 있는 프로젝트라면 CScrollView로 갈아타는 걸 신중히 해야 할 것 같다. GDI+는 MFC가 맞춰 놓은 GDI 방식의 기본 스크롤 좌표를 무시하고 DC의 상태를 난장판으로 만들어 버리기 때문이다. GDI+는 재래식 GDI보다 느리지만 곡선의 안티앨리어싱과 알파 블렌딩이 뛰어나니 아무래도 종종 사용되게 마련인데..

간단한 해결책 중 하나는, GDI+ 그래픽은 CreateCompatibleDC / CreateCompatibleBitmap을 이용한 메모리 DC에다가 따로 그리고, 본디 화면에다가는 그 결과를 Bitblt로 뿌리기만 하는 것이다. 그렇게 하면 아무 문제가 발생하지 않고, 심지어는 속도도 내 체감상으로는 더 빨라지는 것 같다.

Posted by 사무엘

2013/03/13 19:34 2013/03/13 19:34
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/806

1. 메뉴 -- 긴 역사를 자랑하는 GUI 구성요소

'메뉴'(menu)라는 단어는 순우리말로는 흔히 차림표라고 하고, 식당의 음식 메뉴 아니면 컴퓨터 소프트웨어의 GUI 요소라는 꽤 이질적인 두 심상이 결합해 있는 독특한 단어이다. 이런 점에서 '메뉴'는 '마우스'하고도 비슷한 구석이 있는 것 같다.

메뉴는 GUI라는 개념이 컴퓨터에 도입된 이래로 굉장히 오랜 시간을 인간과 함께해 왔다. 워낙 중요하고 필수적인 기능이기 때문에 Windows 운영체제는 아예 API 차원에서 창을 하나 만들 때 메뉴 핸들을 같이 넘겨 줄 수 있게 돼 있다. (CreateWindowEx 함수) Windows는 그래도 보급 메뉴(?) 지원을 무시하고 GUI 툴킷이 자체 구현한 싸제 메뉴를 붙일 여지라도 있지만, Mac OS는 메뉴 bar가 무조건 화면 위에 붙박이로 고정이고 게다가 운영체제의 시스템 메뉴와 일심동체로 통합되어 있기 때문에 싸제 메뉴 같은 건 있을 수 없다.

물론, 너무 무난하고 밋밋한 관계로 요즘 만들어지는 응용 프로그램에서는 메뉴가 천덕꾸러기처럼 취급되는 면모가 없지는 않다. 메뉴+툴바가 리본 UI로 대체된 것은 물론이고, 메뉴가 있더라도 메뉴 bar를 평소에는 감춰 버리고 Alt키를 눌러야만 마지못해 보여 준다. 글쎄, 이러다가 나중에 또 복고풍으로 메뉴로 돌아가지는 않을지?
그리고 어떤 경우든 사각형 안에서 선택막대로 기능을 선택하는 전통적인 메뉴 개념 자체가 없어지는 일은 없을 것이다.

난 닷넷 프레임워크는 그냥 운영체제의 보급 메뉴를 자기 고유 API로 감쌌는줄 알았는데, 그렇지 않다는 걸 알게 되어 개인적으로 놀란 적이 있다. 닷넷 기반 GUI 프로그램은 기본적으로 Office XP 스타일을 적당히 따라 한 싸제 메뉴가 나온다.

보급이든 싸제든, 어쨌든 GUI에서 전통적인 메뉴는 F10을 눌렀을 때 화면 상단에 나타나는 가로줄 메뉴, 혹은 main 메뉴를 가리키는 경우가 많다.
그러나 이것 외에 어떤 개체를 마우스로 우클릭했을 때 나타나는 Context 메뉴, 혹은 팝업 메뉴는 좀 더 나중에, 1990년대 중반에 도입되었다. 윈도우 95 이전에 3.x 시절에는 그림판으로 두 색깔을 번갈아가며 쓸 때 말고는 마우스를 우클릭할 일 자체가 거의 없었던 것 같다. 팝업 메뉴를 띄우는 기능 자체는 3.x 시절에도 있었을 텐데도 불구하고 말이다.

2. HMENU

자, 그럼 Windows 플랫폼 프로그래밍의 관점에서 운영체제의 메뉴 개체에 대해서 좀 더 살펴보자.

이 메뉴라는 놈을 관리하는 개체는 바로 HMENU이다. 얘는 메뉴에 표시시킬 각종 아이템들과 그것들의 상태들을 보관하고 있는 일종의 연결 리스트의 포인터라고 생각하면 된다. 어떤 메뉴 항목에는 또 부메뉴가 딸려 있을 수 있으므로 메뉴는 일종의 재귀성까지 갖추고 있다.

메뉴는 잘 알다시피 리소스의 형태로 쉽게 만들어 내장시킬 수도 있다. 그러나 HMENU 값은 아이콘이나 액셀러레이터, 마우스 포인터 같은 여타 리소스들과는 달리, read-only 리소스가 아니다. 이게 무슨 말인지 배경을 좀 설명하자면 이렇다.

16비트 Windows 시절에는 EXE/DLL에 있는 리소스 데이터를 얻기 위해서 별도로 파일을 열고 메모리를 할당하고 고정하는 등의 절차가 필요했다. 그러나 운영체제가 32비트 환경으로 바뀌면서 실행 파일의 로딩 방식이 memory mapping 방식으로 바뀌었기 때문에, 모듈에 내장된 리소스를 찾는 건 그냥 이미 로딩된 메모리의 주소만 되돌리는 형태로 아주 간단해졌다.

그래서 예전과는 달리, 이제는 한번 fetch해 온 리소스 데이터에 대해서 FreeResource 같은 함수를 호출할 필요가 없어졌다. 그 리소스를 제공하는 EXE의 실행이 종료되거나 DLL이 Unload될 때 어차피 자동으로 한꺼번에 해제되기 때문이다.

일반적인 읽기 전용 리소스는 그런 간소화의 혜택을 입게 되었다.
그러나 메뉴의 경우는 모듈에 내장된 메뉴 데이터의 포인터만 얻어 오는 걸로 끝이 아니라, 그 데이터를 토대로 메뉴 연결 리스트를 별도로 재구성한다. 사용자는 그 연결 리스트의 데이터를 변경함으로써 메뉴에 별도의 항목을 추가하거나 삭제하고, 체크 표시나 disable 처리를 할 수 있다.

그렇기 때문에 LoadIcon, LoadCursor 등의 리턴값은 Free를 할 필요가 없지만, LoadMenu 핸들의 리턴값은 반드시 DestroyMenu를 해 줘야 한다. (물론, 아이콘 같은 리소스라 해도 모듈 내장이 아니라 직접 동적으로 생성한 놈이라면 Destroy*함수를 호출해서 수동으로 소멸해야 하는 건 변함없음.)

HMENU는 내부적으로 딱히 reference counting을 하지는 않는 단순한 구조이다.
윈도우와 연결되어 있는 메뉴는 윈도우가 소멸될 때 같이 자동으로 소멸되며(물론 부메뉴들도 재귀적으로 다 같이), 한 메뉴 인스턴스가 여러 윈도우에서 공유되지는 않는다. '이동', '닫기' 같은 명령이 있는 시스템 메뉴가 있는데, 필요하다면 사용자가 이 메뉴 역시customize할 수 있다.

3. API 디자인

(1) Windows API의 설계 관점에서 흥미로운 것은, 정수로 식별하는 ID를 받는 곳에다가 필요에 따라 메뉴 핸들도 같이 집어넣게 한 게 종종 보인다는 점이다.
CreateWindowEx 함수의 경우, HMENU는 생성하려는 윈도우가 팝업 같은 메이저 윈도우이면 메뉴 핸들이고, 메뉴를 갖는 게 의미가 없는 자그마한 마이너 자식 윈도우이면 정수 ID를 의미한다.

물론 메뉴 핸들과 ID가 동시에 쓰일 일은 없는 건 사실이다. 윈도우의 ID는 대화상자의 차일드 컨트롤들을 식별할 때에나 쓰는 것이니 말이다.
하지만 어째 이 둘을 실제로 공유시킬 생각을 했는지 궁금하다. 어지간하면 그냥 내부 구조체에다 별도의 멤버를 따로 둘 법도 한데, Windows 1.x 시절의 헝그리 정신을 살려, 메모리 절약을 위해 공용체를 썼는가 보다.

또한 메뉴 API도 AppendMenu나 InsertMenu를 보면, 일반 메뉴 아이템에 대해서는 명령 ID를 전달하는 항목에, MF_POPUP이 지정된 하위 메뉴 아이템에 대해서는 또 HMENU를 typecast하여 전달하게 되어 있다.

(2) CreateMenu와 CreatePopupMenu 함수를 왜 따로 만들어 놨는지 영 이해가 안 된다. HINSTANCE와 HMODULE만큼이나 사실상 의미 없는 구분이 돼 있다.
응용 프로그램의 main 메뉴나 우클릭 팝업 메뉴는 화면에 보이는 형태만 다를 뿐, 부메뉴를 가질 수 있는 재귀적인 형태인 것도 똑같고 내부 자료 구조가 달라야 할 것은 없다.
하긴, 그러고 보니 HCURSOR도 HICON하고 내부적으론 거의 같은 자료구조라고 하지. (핫스팟 위치만 추가됐을 뿐)

(3) 메뉴의 상태를 나타낼 때 MF_GRAYED와 MF_DISABLED를 따로 만들어 놓은 건 개인적으로 무척 기괴하게 여겨진다.
MF_GRAYED는 우리가 흔히 보는 '사용할 수 없는' 메뉴 아이템이다. 흐리게 표시되고 선택도 되지 않는다. 그러나 MF_DISABLED는 선택만 안 될 뿐 흐린 표시는 아니다.
이건 솔직히 말해서 잉여력이 넘치는 구분이다.

그래서 심지어는 MS 내부의 개발자들조차도 이를 혼동해 있다.
고전 테마를 쓰고 있을 때는 MF_DISABLED를 설정한 메뉴가 '일반 글자'로 표시된다.
그러나 Luna나 Aero 같은 테마가 적용되어 있을 때는 이게 MF_GRAYED와 동일하게 '흐린 글자'로 표시된다! 문서화된 바와도 다르고 일관성 없게 동작한다는 뜻이다. 내 말이 믿어지지 않으면 당장 프로그램을 짜서 확인해 보기 바란다.
일상생활에서는 MF_DISABLED는 전혀 신경 쓸 필요 없고 MF_GRAYED만 쓰면 될 것 같다.

(4) RemoveMenu, DeleteMenu, DestroyMenu의 차이가 뭘까?
먼저 DestroyMenu는 HMENU 자체를 완전히 소멸시키는 함수이다. 메뉴와 부메뉴들이 모두 다 사라지고 해당 핸들은 사용할 수 없게 된다.
RemoveMenu와 DeleteMenu는 메뉴 안에 있는 한 항목을 제거한다. 제거할 항목을 순서 인덱스 또는 명령 ID로 지정할 수 있다. 부메뉴를 가진 항목이나 항목 구분용 separator는 명령 ID를 갖고 있지 않으므로 반드시 순서 인덱스만 지정 가능할 것이다.

둘의 차이는 딱 하나. 부메뉴를 가진 항목을 지울 때 부메뉴 핸들을 재귀적으로 destroy하느냐(Delete) 안 하느냐(Remove)이다. 마치 '프로젝트 목록에서 파일 제거'와, '파일 제거 + 실제로 디스크 상에서도 삭제'의 차이와 비슷한 맥락이다.

(5) 사실, Windows의 메뉴 API가 좀 더 객체지향적으로 설계되었다면, HMENU뿐만 아니라 각각의 메뉴 아이템을 나타내는 HMENUITEM 같은 자료형도 또 만들었을 것이다.
지금은 그렇지 않기 때문에 메뉴 아이템을 식별할 때마다 매번 HMENU와 UINT nID, 그리고 nID가 명령 ID인지, 순서 인덱스인지를 나타내는 플래그를 넘겨줘야 한다. 메뉴 항목을 편집하거나, 어디 뒤에 삽입하거나 삭제하는 함수들이 전부 저 인자들을 일일이 받는다. 내가 보기엔 무척 지저분하다.

또한 동일한 기능을 하는 API가 구 API, 그리고 좀 더 기능이 확장되고 구조체를 인자로 받는 신 API가 섞여서 중구난방스러운 것도 어쩔 수 없는 일이다. 가령, 예전에는 CheckMenuItem 같은 함수가 있었지만 지금은 SetMenuItemInfo가 있는 식. 새로운 함수는 범용적이긴 하지만 매번 구조체를 만들어서 초기화해 주는 작업이 몹시 성가신 것도 사실이다.

32비트 Windows부터는 각각의 메뉴 아이템에 대해서 명령 ID와는 별개로 임의의 UINT_PTR 데이터 값을 갖는 게 가능해졌다. 마치 리스트박스에서 item data와 비슷한 맥락이다. 이 값을 읽고 쓰는 함수로 지저분하게 SetMenuItemData 같은 함수를 또 추가하느니, 차라리 메뉴와 관련된 모든 속성을 읽고 쓸 수 있는 SetMenuItemInfo라는 종결자 함수를 만들게 됐을 것이다.

Posted by 사무엘

2013/03/10 19:15 2013/03/10 19:15
, ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/805

커티스 르메이(1906-1990).
20세기 중반에 활동한 미국의 군 장성이다. 군사, 세계사, 현대 전쟁사 쪽에 관심이 있는 사람이라면 아마 이름을 들어서 알 것이다.

사용자 삽입 이미지

이 양반 정말 골때리면서도 재미있는 사람이다.
그는 정말 뼛속까지 군인 타입으로, 닥치고 폭격기 화력 덕후였으며 그의 주특기는 쑥밭 만들기였다.
하긴, 그 당시 미국은 워낙 물자가 풍족하게 넘쳐나는 부자 나라였으니 그의 전투 이념은 나름 적절했다.

게다가 그는 '석기 시대'를 굉장히 좋아한 매니아였다. ㅋㅋㅋㅋㅋ
미국 앞에서 깝치는 적국들은 본진을 폭격으로 다 쑥밭으로 만드는 것도 아니고, 모조리 '죽탕치는(?)' 것도 아니고, '석기 시대'로 되돌려 놓겠다는 으름장을 공석에서 입버릇처럼 뇌까렸다. 영어로는 Stone Age.
호전적이고 입이 험악한 걸로 악명 높은 북한도 공식 석상에서 석기 시대 공갈을 친 적은 없는 것 같다.

오죽했으면 르메이 장군에 대해서 이런 패러디짤이 나돌 정도이다.

사용자 삽입 이미지

그의 지휘 하에 일본 도쿄는 2차 세계 대전 말기에 그 이름도 유명한 '도쿄 대공습'을 당했다. 미군 폭격기가 우박처럼 떨어뜨리는 소이탄에 시내 전체가 말 그대로 시뻘건 불바다가 되어 버렸다. 사진에서 보듯, 목조 건물은 형체도 없이 그냥 주저앉아 없어졌고, 일부 석조/콘크리트 건물도 새까맣게 탄 흉측한 몰골만 남았다.

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

이 폭격으로 인해 죽은 사람은 10만여 명에 달해서 사실은 히로시마 원자 폭탄 투하로 죽은 사람보다도 수가 더 많았다고 한다. 도쿄를 그런 석기 시대로 되돌리는 데는 겨우 3시간 남짓밖에 걸리지 않았다.

그러나 이게 무슨 원폭을 성층권 고도에서 투하하는 식이 아니라 상당한 저공에서 위험한 자세로 소이탄을 떨어뜨리다 보니, 미군도 폭격기가 총 12기나 일본의 대공포로부터 반격을 받아 격추되고, 42기는 피탄 당하는 손해를 입었다. 이에 몇몇 미군 파일럿들은 권총을 들고 르메이를 직접 찾아가서 이렇게 따졌다.

“왜 이런 무모한 저공 비행 폭격 명령을 내렸는가? 귀관 때문에 우리가 전우를 얼마나 많이 잃었는지 아는가?”

하지만 르메이는 그 말은 들은 체도 하지 않고 이렇게 응수했다고 한다.

“제군들은 단 하루 만에 일본 제국의 수도를 잿더미로 만들고 놈들을 최소 10만 명이나 없앴다! (사실, 미군 전사자는 많아 봤자 수십~수백 명에 지나지 않을 것이다) 그러니 오늘 작전은 대성공이다. 이런 식으로 내일은 나고야, 모레는 오사카, 그 다음은 고베.. 1주일 동안 일본 전체를 잿더미로 만들 것이다. 모두들 오늘의 성공을 자축하도록!

전쟁을 치르면서 전사자가 나오는 건 어쩔 수 없는 일이고 작전 자체는 르메이의 말대로 미군의 승리이긴 하지만... 저건 좀.. ^^;; 정말 그의 머리에 든 건 오로지 폭격밖에 없었다.
일본이 원폭을 맞고 나서 일찌감치 항복을 하지 않았다면, 그래서 르메이가 생각한 작전들이 모두 시행되었다면 일본이라는 나라는 진짜로 지도에서 없어지고 일본 열도는 석기 시대로 퇴화했을지도 모른다.

이 양반의 무자비한 작전은 훗날 6·25 때도 계속되었다. 북한 중에서도 평양 시내는 그야말로 형체가 남은 건물이 손에 꼽을 정도였을 정도로 그냥 말 그대로 모조리 잿더미가 되었다. 오로지 미국이니까 가능한 돈지랄로 폭탄을 그냥 때려 박았다고 생각하면 된다.

이 때문에 그 당시 북한의 내부에서는, 평양을 재건할 게 아니라 아예 이 기회에 수도를 다른 데로 옮기는 게 낫지 않겠냐는 논의도 오갔다고 한다. 비록 그렇게 되지는 않았지만 말이다.
그리고 이때의 공습과 폭격의 악몽 때문에 지금 평양 시내는 이에 대비하느라 지하 방공망이 굉장히 깊고 정교하게 구축되어 있다. 평양 지하철이 무지막지하게 깊게 건설된 것도 이런 맥락에서이다.

그 뒤에도 르메이의 버릇은 어디 가지 않았다. 케네디 대통령 시절에 있었던 월남전 땐 베트남도, 그리고 쿠바 사태가 벌어졌을 때도, “베트남이건 쿠바건 다 폭격해서 석기 시대로 되돌려 놓겠다. 대통령 각하는 명령만 내려 달라”는 식으로 일관되게 나섰다.
마치 게임 해설자 김 태형 씨가 캐리어를 좋아하듯 그는 석기 시대가 자기 상징이 되어 버렸다.

그래서 나중에는 미국의 정치인들도 그의 말투를 따라하는 지경에 이르렀다. 미국은 걸프전 때 이라크를 석기 시대로 되돌리겠다고 공갈을 쳤고, 나중에 9· 11이 터졌을 때는 파키스탄을 상대로도 대테러전에 협조하지 않으면 너네 나라를 석기 시대로 되돌려 놓겠다고 그랬다. 뭐, 반미 성향이 있는 사람이라면 이런 협박 멘트에 심기가 불편함을 느낄 것이다.

르메이 같은 사람도 미군에 있는데 맥아더 장군은 차라리 양반이라는 생각이 문득 들었다.
맥아더가 훌륭하고 존경스러운 군인이라지만 그도 인간이고 신은 아니기에, 맨날 인천 상륙 작전 같은 성공만 한 게 아니며 실수도 저질렀다. 처음엔 북한과 중공군을 얕잡아보다가 1· 4 후퇴를 당하고 호되게 데인 뒤에야, “이 자식들 안 되겠어.”라고 하면서 정말 상상을 초월하는 강경책을 쓰려 했다.

어떻게든 빨갱이들을 없애 버리겠다는 의도 자체는 좋지만, 그렇다고 한반도에다 핵을 또 터뜨린다거나, 전쟁을 아예 3차 세계 대전 급의 대규모 장기전으로 키우는 것도 불사하겠다는 식이었으니... 이런 강경한 생각이 화근이 되어 맥아더가 트루먼 대통령과의 사이가 틀어진 건 유명한 사실이다.

그런데 르메이도 그 당시 “이 좋은 핵무기를 왜 안 써?” 급의 생각을 하고 있던 건 마찬가지였다. 맥아더보다 더하면 더한 꼴통이지 못하지는 않았다.
미국 대통령이 이런 호전적인 군 장성 양반들의 근성을 이성적으로 잘 통제하지 않았다면, 과거에 소련과의 냉전이 냉전으로 끝나지 않았을 수도 있었다. 굳이 한반도가 아니어도 어디에서 핵이 한두 발 터지는 바람에 특정 국가가 지도에서 사라지거나 진짜 석기 시대로 돌아갈지도 몰랐다.

오죽했으면 아인슈타인도 “3차 세계 대전 때 인간이 무슨 신무기를 쓰고 있을지는 난 잘 모르겠습니다. 하지만 그 다음 세계 대전이 일어난다면, 그때 인간은 새총(slingshot) 같은 냉병기를 쓰고 있겠죠?”라고 얘기했겠는가. 성문 종합 영어에도 등장하는 유명한 지문이다. 아인슈타인 역시 영락없이 석기 시대 회귀-_-를 염두에 두고 있었다는 뜻이다.

석기 시대 드립 말고 르메이 장군이 자신의 호전성을 또 드러낸 유명한 어록으로는 “세상에 무고한 민간인이란 없다”(There are no INNOCENT civilians)이다.
사실, 도쿄 대공습 같은 경우 미국이 연합국이고 전쟁에서 이겼으니 망정이지, 저렇게 대놓고 시내를 폭격하여 비전투 민간인들을 무차별 학살하는 건 전쟁 범죄로 간주될 수 있는 짓이었다.
대놓고 말해, 나치 독일이 영국이나 미국의 본토 도심지를 소이탄 폭격으로 다 불태워서 민간인을 대량 학살했다면 그 후폭풍이 어찌 됐겠는가? 그럼에도 불구하고 르메이는 작전을 강행했다.

일례로, 히로시마에 원자 폭탄을 투하하는 작전에 참여했던 폴 티베트 대령은 훗날 다음과 같은 요지로 회고한 바 있다.
“난 그 당시 그저 명령에 따랐을 뿐이다. 그리고 원폭은 전쟁을 더 일찍 종결시키고 더 많은 인명의 희생을 막은 차선이며 필요악이었다고 생각한다. 군인으로서 나의 행동에 대해 양심의 가책이나 후회는 없다.”

허나, 르메이는 한 술 더 떠서 민간인의 죽음에 대해 아예 그 정도의 책임이나 죄책감마저도 가질 필요가 없다는 식이었으니, 더 할 말이 없다. ^^
“사실 저 밑에 곤도네는 군용 볼트를, 옆집 스즈키네는 군용 너트를 만들고 있을 뿐이다. (무고해 보인다고 저 민간인들을 안 죽이면, 그게 다 우리를 죽이는 병력이 되어 돌아온다. 그러니 마음껏 폭격하고 닥치는 대로 죽이고 불태워 버려라.)”

그리고 결정적으로는 르메이의 말이 어느 정도 사실이었다! 일본은 도시 구조가 민간인 거주지와 군수 업체 영역의 구분이 그다지 분명하지 않았기 때문이다.

그의 생전 어록으로는 이 외에도
“전쟁이란 총알 많은 쪽이 많이 죽이면 이기는 것이다.”
“충분히 많이 죽이면 다시는 덤벼들지 못할 것이다.”

처럼, 틀린 말은 아니지만 좀 우악스럽고 꼴통 같은 방면으로 주로 전해 내려오고 있다.

그래도 아무려면 어때, 천조국 미국 소속이지 않은가. 그리고 그는 최소한

“보급이란 원래 적에게서 취하는 법이다.”
“포탄은 자동차 대신 소나 말에 싣고 가고, 그러다 포탄을 다 쓰면 필요 없어진 소나 말을 먹으면 된다.”
“정글에서 비행기를 어디에다가 쓰냐?”
“일본인은 원래 초식동물이니 가다가 길가에 난 풀을 뜯어먹으며 진격하라.

같은 이런 진짜 미친 개소리를 하지는 않았다. 저건 잘 알다시피 '무다구치 렌야'라고 일본 역사상 최악의 무능한 장군이 남긴 훈시.. -_-
그도 그럴 것이 물량이 풍족한 곳에서 그냥 물량으로 밀면 된다는 교리가, 없는 여건 속에서 닥치고 근성과 정신력만으로 돌격하라는 교리보다는 훨씬 나은 게 자명하지 않은가?
그는 스타크래프트를 했으면 무한 맵에서 저그 가디언 굴리는 걸 좋아했을 것 같다. (대공 유닛으로 대지상 폭격 -_-)

뭐, 르메이 장군은 맥아더 장군과 마찬가지로 우리 같은 사람으로서는 미워할 구석이 없는 인물이다.
우방국의 장군답게 일본, 북한 등 대한민국의 적들하고만 싸웠으니 말이다.
(아 하긴, 무다구치 렌야도 자기 군대를 말아먹은 행적이 가히 대한 독립 유공자 급이니, 우리가 딱히 미워할 구석이 없는 건 마찬가지이고.. -_-;;;)

그는 그런 호전적인 기질답지 않게 미군의 전술 체계 수립에 큰 공을 세운 바 있으며, 심지어 적국인 일본으로부터도 훗날 자위대의 재건에 기여한 공로를 인정받아 훈장을 받기도 했다. 괜히 장성까지 진급한 게 아니다.

다만, 그 막강한 화력으로 미국이 전투는 이겼을지 몰라도 한국전과 베트남전은 적군을 완전히 몰아내는 깔끔한 '전쟁 승리'를 이루지 못한 것은 아쉬운 점이다.
그리고 도쿄 대공습 때는 재일 동포도 많이 희생된 게 사실이다. 비록 뭐 어쩔 수 없는 일이지만 말이다.

그리고 글을 맺으면서 마지막 한 마디.
이 사람의 상징은 잘 알다시피 '석기 시대'이다. 허나 아담 이래로 6천 년 인류 역사를 믿는 크리스천은 인류에게 딱히 석기 시대라 불릴 만한 긴 원시 시대가 존재했다고 믿지 않는다.
문명의 이기가 존재하지 않는 시절로의 퇴보를 언급하려 한다면, 차라리 노아의 홍수 직후 상태로 되돌리겠다고 하는 게 말이 될 텐데.. 아무래도 '석기 시대'보다는 우악스러움이 덜하다. ^^

Posted by 사무엘

2013/03/07 19:26 2013/03/07 19:26
, , , , , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/804

1.

본인은 비주얼 C++ 2012로 갈아탄 뒤부터 예전에는 본 적이 없는 이상한 현상을 겪곤 했다. 내가 만들고 있는 프로그램을 IDE에서 곧장 실행하자(Ctrl+F5 또는 F5) 프로세스는 분명히 실행되어 있는데 창이 화면에도, 작업 표시줄에도 전혀 나타나 보이지 않았다.

Spy++를 돌려 보니 프로그램 창이 생기긴 생겼는데 어찌 된 일인지 WS_VISIBLE 스타일이 없이 숨겨져 있다는 걸 알게 되었고, 문제의 원인은 생각보다 금방 발견할 수 있었다.
프로세스에 전달되는 STARTUPINFO 구조체의 wShowWindow 멤버 값은, dwFlags에 STARTF_USESHOWWINDOW 플래그가 있을 때에만 유효하다는 걸 깜빡 잊고 있었던 것이다.

일반적으로 프로그램을 실행할 때 운영체제가 그 구조체에다 ShowWindow 플래그를 안 넣는 적은 사실상 없기 때문에 지금까지 그 로직이 별로 문제가 되지 않았었다. 하지만 비주얼 C++ 2012는 이례적으로 그 구조체의 거의 모든 멤버들을 그냥 0으로만 집어넣은 채 프로세스를 생성하고, 0은 SW_HIDE와 같기에 창이 화면에 나타나지 않았다.

2.

<날개셋> 한글 입력기 외부 모듈을 debug 형태로 빌드한 뒤 디버거를 붙여서 실행해 보면, 때에 따라서는 호스트 프로세스가 종료될 때 memory leak 로그가 뜨는 경우가 종종 있었다. 하지만 이것이 항상 나타나는 건 아니고 leak의 양이 심각하게 많은 건 아니었기 때문에, 본인은 크게 신경 쓰지는 않았다.

그런데 우연히 추가 디버깅을 한 결과, 응용 프로그램에 따라서 아예 COM 개체들의 reference count가 달라지고 TSF 모듈의 소멸자 함수의 실행 여부가 달라지는 걸 발견하였고, 이에 본인은 이 현상에 대해 좀 더 심혈을 기울여 디버깅을 실시하게 되었다.

이건 꽤 특이한 현상이었다. <날개셋> 편집기에서도 leak이 발생했기 때문에 가장 먼저 'TSF A급 지원' 옵션을 꺼 봤다. 그리고 외부 모듈은 아예 날개셋 커널을 로딩하지 않고 아무 기능도 사용할 수 없는 panic 상태로 구동했다. 그렇게 프로그램의 주요 기능들을 다 끄고 절름발이로 만들었는데도 <날개셋> 외부 모듈을 한 번이라도 로딩을 하고 나면 leak이 없어지지 않았다.

이런 식으로 COM 오브젝트의 reference count가 꼬이는 버그는 여간 골치 아픈 문제가 아니기에 각오 단단히 하고 디버깅을 계속할 수밖에 없었다. 그 결과 무척 신기한 점을 발견했다. MFC를 사용하는 GUI 프로그램과, MFC든 무엇이든 대화상자(DialogBox)를 사용하는 프로그램에서는 leak이 안 생기는데, Windows API로 message loop을 직접 돌리면서 윈도우를 구동하는 프로그램에서는 memory leak이 발생한다는 것이었다.

오히려 방대하고 복잡한 MFC를 쓰는 프로그램에서 메모리가 새면 샜지, 왜 더 간단한 프로그램에서 문제가 발견되는 걸까?
이 정도까지 밝혀지니 궁금해 미칠 지경이 됐다. leak이 있는 프로그램과 없는 프로그램을 종료할 때 외부 모듈 개체의 Release 함수가 어떻게 호출되고 reference count가 어떻게 변하는지를 검토했다.

그리고 드디어 leak이 있는 프로그램과 없는 프로그램의 차이가 밝혀졌다.
MFC는 프로그램 창이 WM_CLOSE 메시지를 받아서 창의 소멸 단계로 들어서기 전에, 프로그램 창을 강제로 한번 감춰 주고 있었다( ShowWindow(SW_HIDE) ). CFrameWnd::OnClose()에서 CWinApp::HideApplication을 호출함. 이걸 함으로써 운영체제의 TSF 시스템 내부는 객체에 대한 Release가 일어나고 메모리 해제가 완전히 이뤄졌다. 소스가 없는 대화상자도(DialogBox 함수) 잘은 모르지만 종료될 때 비슷한 call stack을 갖는 Release 호출이 있었다.

그 반면 창이 없어질 때 따로 별다른 처리를 하지 않는 프로그램에서는 외부 모듈 개체의 reference count가 1 남게 되었고, 이것이 memory leak으로 이어졌다. MS에서 직접 만든 다른 입력 프로그램들도 마찬가지다. 도대체 왜 그럴까?.

MFC가 WM_CLOSE에서 자기 창을 감추는 이유는 그냥 자식 윈도우들이 순서대로 닫히는 모습이 사용자에게 티가 나 보이지 않게 하고, 겉보기로 창이 당장 없어져 버렸으니 프로그램 종료에 대한 사용자 반응성을 향상시키려는 목적으로 보인다. 그게 반드시 필수는 아니다. 내가 보기에 그렇게 하지 않는 게 잘못이라 볼 수는 없다.

OS별로 살펴보니, 이런 leak은 윈도우 XP와 비스타에서는 없었다가 그 후대인 7과 8에서 생겼다. 즉, XP/Vista에서는 hide를 안 해 줘도 원래 leak이 없는데 7부터는 hide를 해 줘야 한다는 뜻. 아무튼 난 여러 모로 윈7의 문자 입력 체계가 별로 마음에 안 든다. 이쪽 부분 담당자가 갑자기 바뀌었는지, 혹은 대대적인 리팩터링을 한 후유증이기라도 한지 자잘한 버그들이 너무 많이 들어갔기 때문이다.

결국 이것은 IME 문제가 아니라 운영체제 내지 응용 프로그램의 문제라는 결론을 내리고 편집기의 소스를 고쳤다. 문제를 피해 가는 법을 발견하긴 했으나 뒷맛이 개운하지 못하다.

* Windows 환경에서의 4대 디버깅 도구와 테크닉

  • 문자열을 printf 스타일로 포맷하여 OutputDebugString 함수로 전달하는 TRACE 함수 (디버거 로그)
  • 별도의 디버거 로그가 아니라 그냥 화면 desktop DC에다가 로그를 찍는 깜짝 함수
  • 프로그램이 특이한 환경에서 뻗을 때 call stack을 확인할 수 있는 miniDumpWriteDump와 SetUnhandledExceptionFilter 함수
  • memory allocation number에다가 breakpoint를 거는 _crtBreakAlloc 변수. 정체불명의 memory leak 잡을 때 필수

Posted by 사무엘

2013/03/02 19:24 2013/03/02 19:24
, , ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/802

새마을호 디젤 동차의 퇴역

1987년 7월 6일.
민주화를 갈망하며 전국적으로 벌어진 6월 항쟁이 마무리되어 가던 이 날, 한국 철도계에는 일대 혁신이 시작되었다.

“철도청은 오늘부터 서울 부산간의 전후동력형 새마을호 열차 운영을 시작했습니다.
이 열차는 승차감이 좋으며 안전감이 높고 예비동력이 확보돼 고장과 사고를 줄일 수 있습니다.” (MBC 뉴스 보도)


바로, 전통적인 기관차+객차 형태를 탈피하여 전후동력형 새마을호 디젤 동차가 첫 운행을 개시했기 때문이다.
그리고 이 열차를 최초로 제작하여 납품한 업체는 그 이름도 유명한 대우 중공업이었다. 대우 중공업은 이 사실에 대해 무척 자부심을 갖고 있는 듯하다.

사용자 삽입 이미지

승차감이 더욱 좋아졌습니다.
대우 중공업이 자체 개발한 최신 유선형의 전후 동력 새마을 열차가 뛰어난 승차감으로 더욱 쾌적하고 안락한 철도 여행을 약속합니다.


사용자 삽입 이미지
그런데 7월 1일이라니? 지금도 이렇게 전시되어 있는지는 모르겠으나, 철도 박물관에 적혀 있는 저 날짜는 잘못되었다. 6일로 바로잡혀야 한다.

이때 새마을호는 지금의 KTX를 능가하는 초호화 귀족 열차였다.
열차의 동력차부터가 아주 특이하고 심상찮게 생겼는데 서울에서 부산까지 가면서 도중에 대전과 동대구밖에 정차를 안 한다니!
나도 이 새마을호 디젤 동차를 안 봤으면 철덕이 됐을 가능성이 아주 낮아졌을 것이다.

그 당시 전후동력형 디젤 동차가 내세웠던 장점은 기관차보다 엔진이 작고 가볍고 조용하면서도 성능이 탁월했다는 점이다. 디젤-전기 형태가 아니라 유압 변속기로 동력비를 조절했다. 엔진이 앞뒤로 두 개 달린 대칭형이기 때문에 전차대 없이도 회차가 용이하며, 두 엔진 중 하나가 퍼지더라도 속도가 느려질지언정 그럭저럭 운행을 할 수 있어서 더욱 좋다. '예비 동력'이라는 게 그런 의미이다.

하늘을 날아가는 비행기나 로켓이야 닥치고 무조건 가벼운 게 진리이겠지만, 구름 마찰력에 의존하는 육상 교통수단은 너무 가볍기만 해서는 바퀴가 헛돌기 쉽다. 이 때문에 동차형 새마을호는 빨리 잘 달릴 수 있는 평지에서는 성능이 탁월한 반면, 지형이 험준한 곳에서는 투입이 곤란했다는 특성도 존재한다.

이 계열의 차량은 1994년까지 대우, 현대, 한진 중공업 생산분이 총 세 차례에 걸쳐 도입되었다.
동력차의 말단 부분에 저렇게 날렵한 45도 경사면을 처음으로 만드는 것부터가 1980년대 후반에는 어려운 일이었다고 한다. 비록 그때 고속철이 다닌 건 아니었지만 새마을호의 디자인에서부터 이미 프랑스 떼제베의 외형을 벌써 참고한 거라는 썰이 나돈다.

초기 도입분은 6량 1편성이었으나, 나중에는 8량으로 늘고 엔진의 출력도 향상되었다. 차량 말고 엔진은 독일제이며, 우리나라 해군이 운용하는 잠수함에서도 동일한 엔진이 쓰이는 기체가 있다고 한다.

이 열차는 분명 매우 획기적인 열차였으나, 이 열차가 새마을호의 최초 원조는 아니다. 가령,

  • 서울-부산 4시간 10분은 이 열차보다 먼저 특대형 디젤 기관차가 견인하는 새마을호가 1985년 11월에 달성했다. 디젤 동차가 최초가 아니다.
  • 새마을호 특유의 동그란 창문 모양의 객차는 1986년 제작된 '유선형 새마을호 객차'의 디자인을 물려받은 것이다. 디젤 동차가 최초가 아니다.
  • 새마을호도 처음부터 지금과 같은 완전 으리으리한 좌석이었던 건 아니다. 1991년에 우등 고속이 생긴 뒤인 1992~1994년의 후기형 객차에서야 드디어 종아리 받침대까지 갖춰진 좌석이 도입되었다.

기존의 기관차형 새마을호 객차와, 새로 개발된 동차형 새마을호의 객차는 객실 인테리어는 거의 차이가 없지만, 전기 배선 규격이 호환되지 않아서 서로 혼합 편성을 할 수는 없다고 한다.

승승장구하던 새마을호는 1990년대 중반부터 정차역이 급격히 늘고 위상이 하락하기 시작했으며, KTX가 개통한 뒤부터는 진짜 처참하게 몰락하고 말았다. KTX를 보조하는 한 수 아래 열차로 굴리기엔 내장재가 너무 좋아서 뭘 어찌 할 수가 없다. 게다가 오래 된 엔진은 종종 퍼지고 고장을 일으켰다. KTX에 올인을 해야 하는 철도 경영자의 입장에서는 진짜 계륵 그 이상도 그 이하도 아닌 열차로 전락했다.

1987년 초기 도입분은 20년이 지난 2007년부터 슬슬 퇴역하기 시작했다. 그 신호탄이 바로 2006년부터 잠시 다녔던 임진강 라이너 새마을호. 그리고 지난 2013년 1월 5일의 마지막 운행을 끝으로, 전후동력형 새마을호 동차가 1월 6일부로 모두 퇴역하여 자취를 감췄다. 아아..! 내 너를 어찌 잊으리!

새마을호라는 열차 등급은 2014년 말까지 유지되다 폐지될 예정이다. 고로 지금 다니는 새마을호는 모두 기관차+객차형 새마을호이다. 비둘기호, 통일호에 이어 새마을호까지 없어지면 1980년대부터 시행되었던 '-호' 열차 등급 체계는 사실상 다 무너지는 셈이다. 사실, '-호'라는 체계는 배에서 모티브를 딴 옛날 스타일 작명법에 가깝다.

* 아, 그러고 보니 여담 하나. 1987년 7월 6일은 연세대에 재학 중이던 고 이 한열 열사가 최루탄을 맞은 뒤 치료를 받다가 끝내 사망한 바로 다음날이기도 하다. (7월 5일)

Posted by 사무엘

2013/02/28 08:40 2013/02/28 08:40
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/801

마이크로소프트 Windows라는 운영체제는 GUI 요소인 '창'(window)에서 모티브를 따서 작명되었다. 그 이름이 암시하듯, Windows는 창을 만들고 제어하는 것이 프로그래밍에서 큰 비중을 차지하며, 창과 창끼리의 의사소통은 메시지라는 놈을 통해서 행해진다. 이건 프로그래머라면 이미 다 잘 아는 내용일 것이다.

메시지는 굳이 GUI를 만들지 않더라도 응용 프로그램간에 데이터를 공유하고 스레드 동기화가 갖춰진 통신을 하는 데 상당히 유용한 수단이다. 오늘날 같은 보호 모드 멀티태스킹/멀티스레드 환경에서도 과거의 16비트 시절 같은 직관적인 통신 메커니즘을 제공하기 위해 운영체제가 밑에서 알아서 신경 써 주는 게 많기 때문이다. 그래서 그 기능만 쓰라고 message-only 윈도우라는 것도 있다.

메시지는 자신이 어떤 메시지인지를 나타내는 정수와, 덧붙일 수 있는 추가 숫자 정보 두 종류로 구성된다. 일명 wParam, lParam인데, 16비트 시절에는 메시지, wParam, lParam의 크기가 각각 16, 16, 32비트였다. 그것이 32비트 기계에서는 모두 32비트 크기로 확장되었고, 64비트에 와서는 msg만 그대로이고 나머지 둘은 64비트로 더 커졌다.

이론적으로 아무 숫자나 담아서 메시지로 전달할 수 있다. 그러나 운영체제는 내부적으로 다음과 같은 방식으로 메시지의 용도를 정해 놓고 있다. 이는 마치 운영체제가 메모리 주소의 용도를 영역별로 나눠서 정해 놓은 것과 동일한 맥락이다. (MS-DOS 호환용, 응용 프로그램용, 커널용 등)

첫째, 0부터 WM_USER-1까지 총 1024개의 메시지는 시스템 메시지로서 그 의미가 예약되어 있다.
0인 WM_NULL은 의도적으로 아무 일도 하지 않는 메시지로 비워 놨지만, 그 뒤부터 WM_CREATE(1), WM_DESTROY(2) 같은 것은 아마 윈도우 1.0 시절부터 있었을 기초 메시지들이다..

글자 입력란에는 cursor라고 하여, 공식적으로는 caret이라고 불리는 반전 사각형이 깜빡거린다. 이건 WM_TIMER로 구현했을 법도 해 보이는데 Spy++ 같은 프로그램으로 확인해 보면 그렇지 않다. 메시지 코드는 0x118이고 winuser.h에 WM_* 형태로 문서화되지 않은 비공개 내부 메시지에 따라 동작한다. 신기하지 않은가? (그 주변의 0x117이나 0x119대엔 당연히 공개된 WM_*메시지들이 꽉 차 있음.) 게다가 의미가 뭔지는 모르겠지만 wParam과 lParam에도 그냥 0이 아니라 뭔가 메모리 주소처럼 보이는 값들이 있다.

사용자는 0x1000 이내의 영역에 있는 숫자에다가 나만의 의미를 부여해서는 안 된다. 지금은 쓰이지 않아도 나중에 운영체제가 찜할 가능성이 있다. 가령, 마우스 휠의 움직임을 감지하는 WM_MOUSEWHEEL은 윈도우 98에서 정식으로 새로 추가되었고, 터치스크린 입력을 감지하는 WM_TOUCH 같은 메시지는 윈도우 7에서 추가되었다.

이런 식으로 Windows가 버전업되면서, 메시지가 미래에 자꾸 추가될 수 있다. 개인적으로 최소한 4096개도 아니고 1024는 공간이 너무 부족하지 않나 하는 생각도 든다. 나중에는 이 공간이 메시지들로 다 차 버리고, 추가 메시지는 WM_EXTEND_MSG 같은 최후의 메시지 하에서 부가 정보는 wParam과 lParam에 담겨 오게 되지 않을까? =_=;;

운영체제 메시지 중에는 WM_SETTEXT, WM_GETTEXT이라든가 심지어 WM_COPYDATA처럼 포인터를 통한 데이터 전달이 필요한 것도 있다. 운영체제의 SendMessage 함수는 그런 메시지를 다른 프로세스에다가 보내라고 사용자가 요청할 경우, 자체적으로 공유 메모리를 생성하여 메모리 주소 변환을 하고, 텍스트의 경우 심지어 ANSI/유니코드 변환까지 자동으로 한다. 그러니, lParam을 포인터로 인식하는 시스템 메시지에다가 엉뚱한 숫자를 집어넣어서 보냈다간 큰일난다. 아울러 포인터를 전달해야 하는 메시지는 SendMessage로만 전달 가능하지, PostMessage로는 되지 않게 운영체제가 막는다.

또한 일부 메시지는 반드시 특정 방법만 이용하여 생성해야 하는 것도 있다. 가령, WM_PAINT는 invalidate region을 만드는 함수를 호출해서 운영체제가 생성하도록 해야 하지, 응용 프로그램이 메시지 자체만을 인위적으로 만들어 내서는 안 된다. 실제로 실험을 해 보지는 않았지만, 없는 WM_PAINT를 페이크로 사칭하여 생성하는 것은 운영체제가 아마 안전을 위해 금지하지 않을까 싶다.

요컨대 WM_USER 이내의 메시지는 용도가 운영체제에 의해 예정되고 그에 따른 특수 처리가 추가될 여지가 있는 영역이므로, 사용자가 사칭하거나 조작해서는 안 된다.

그 다음 둘째 계층은 WM_USER부터 WM_APP까지 3만여 개 남짓한 영역이다.
이 메시지는 각 윈도우들이 자체적으로 의미와 용도를 마음대로 정해서 쓸 수 있다. 즉, 윈도우 클래스(RegisterClass)별로 의미가 완전히 private하다.

내가 뭔가 새로운 커스텀 컨트롤을 개발해서 이 컨트롤을 조작하는 수단을 윈도우 메시지라는 형태로 제공하고 싶다면, 각종 커스텀 메시지들을 (WM_USER + xxx)의 형태로 정의하면 된다.
임의의 크기의 데이터를 다른 프로세스끼리 전달하려면 프로그래머가 알아서 주소 marshalling를 하든가, WM_COPYDATA로 주고받을 구조체 스펙을 정하든지, 아니면 짤막한 문자열만 잠시 주고받으려면 atom에다 등록하여 atom 번호만 주고받든지 해야 한다. 뭐, atom은 오늘날에 와서는 거의 구닥다리 메커니즘으로 전락하긴 했지만.

리스트 박스나 콤보 박스는 Windows 1.0 시절부터 있었던 워낙 붙박이이다 보니 LB_ADDSTRING이나 CB_GETCURSEL 같은 메시지는 놀랍게도 앞의 시스템 메시지 영역에 들어있다. 그러니 그 메시지는 값만 보고도 대상 윈도우가 뭔지 볼 필요도 없이 문맥 독립적으로 용도를 추측할 수 있다. 대상 윈도우가 무엇이든 간에 LB_ADDSTRING의 lParam에는 언제나 포인터가 들어있다고 가정할 수 있다.

그러나 사용자 메시지부터는 얘기가 달라진다. WM_USER+1이라는 값을 갖는 메시지는 어느 윈도우가 받느냐에 따라서 처리가 완전히 달라진다. 붙박이 시스템 컨트롤 말고, 32비트 시절에 나중에 도입된 공용 컨트롤도 이제는 아이템을 추가하고 삭제하는 등의 자신의 메시지들은 시스템 영역에 있지 않고 이 사용자 영역에 있다.

따라서 메시지가 하는 일에 따라 부가정보를 변조하는 hook 같은 걸 만든다면, 메시지의 값만 볼 게 아니라 그 메시지를 받는 대상 윈도우의 클래스 이름도 확인해야 한다. 이건 철저하게 문맥 의존적인 메시지인 셈이다.

운영체제(시스템) 메시지, 그리고 사용자 메시지 이렇게 둘이 갖춰지면 끝인 것 같은데 플랫폼 SDK를 보니 셋째 계층인 WM_APP라는 것도 있다. 이건 도대체 뭘까?
이것은 내부적인 처리 방식의 차이에 따른 구분이 아니라 그냥 용도에 따른 명분상의 구분이다.

결론부터 말하자면 이 계층은 응용 프로그램이 어떤 컨트롤에다 서브클래싱을 한 뒤, 응용 프로그램이 새로운 윈도우 프로시저에다 보내 주는 '반사'(reflect) 메시지를 여타 메시지들과 구분하기 위해 존재하는 영역이다. 에디트 컨트롤을 예로 들면, 글자색과 배경색을 바꾼다거나 25자리 제품 시리얼 번호를 입력받는데 5자리마다 '-'를 자동으로 추가하는 것 같은 자잘한 동작 방식을 변경하고 싶을 때 서브클래싱을 이용한다.

일반적으로 컨트롤은 어떤 일이 일어났다는 통지를 부모 윈도우에다 WM_COMMAND(붙박이 컨트롤)나 WM_NOTIFY(공용 컨트롤)의 형태로 보내 주는데, 그때 해야 하는 처리가 천편일률적으로 정해져 있기 때문에 부모 윈도우가 아니라 해당 컨트롤의 서브클래스 프로시저 자신이 도로 받아서 알아서 하게 하고 싶을 때가 있다.

이때 그 통지 메시지는 WM_APP 이후의 영역으로 더해서 보내고, 그 메시지에 대한 처리를 내 custom 윈도우 프로시저에다 넣으면 된다. 이 영역의 메시지는 WM_USER 영역의 메시지, 즉 기존 컨트롤의 메시지와 겹치지 않는다는 보장이 있기 때문이다.

요컨대 시스템 메시지는 그냥 닥치고 global, WM_USER 메시지가 RegisterClass에 종속이라면, WM_APP 메시지는 CreateWindow 종속이라고 생각하면 된다. WM_USER급 메시지의 경우, 해당 윈도우 클래스가 CS_GLOBAL 스타일이 있다면 그 윈도우를 사용하는 모든 프로그램들에서 global 종속이 보장될 것이다.

다음 넷째 계층은 RegisterWindowMessage 함수를 통해 등록된 custom 메시지들에 배당된다.
운영체제 전체를 통틀어서 uniqueness가 보장되는 나만의 고유 메시지를 만들고 싶으면 아무래도 숫자만으로는 무리가 있다. Windows 메시지가 무슨 방대한 128비트짜리 GUID급도 아니니 말이다. 그래서 문자열로부터 0xC000 ~ 0xFFFF 영역에 있는 숫자를 메시지 값으로 얻어 낸다. 아마 hash 연산 같은 걸 쓰겠지.

단, 같은 문자열을 등록하더라도 돌아오는 숫자는 그때 그때 다르다. 그렇기 때문에 RegisterWindowMessage의 리턴값은 프로그램의 컴파일 시점 때 하드코딩으로 박을 수 없다. C++ 언어로 치면 switch문으로 판단을 할 수 없으며 번거롭지만 if를 써야 한다. 하지만 한번 등록된 값은 운영체제가 부팅되어 있는 한 불변이므로, 전역변수의 초기값으로 지정하는 것 정도는 가능하다.

이 custom 메시지는 상당히 유용하다.
시스템 전체에다 메시지 hook을 걸어서 나만의 처리를 하는 프로그램을 만들었다고 치자. 그리고 hook을 건 응용 프로그램과 여타 프로세스의 주소 공간에 침투한 hook 프로시저 사이에 통신을 해야 하는데 이때 가장 효과적으로 쓰일 수 있는 수단이 바로 custom 메시지이다. 내가 만든 프로그램이니 나만 아는 문자열로 custom 메시지를 생성하고, 그걸로 EXE와 hook DLL이 통신을 하면 된다는 뜻이다.

뭐, EXE로 보낼 때야 그냥 WM_USER나 WM_APP급의 고정된 상수만으로 충분하겠지만, 다른 수많은 임의의 프로세스들을 상대하는 훅 DLL로 보내는 건 여타 메시지들과 전혀 충돌하지 않는 게 보장되는 고유 메시지를 써야 할 테니 말이다.

윈도우 95/NT4 초창기 시절에 WM_MOUSEWHEEL 메시지가 운영체제 차원에서 없었던 시절엔, 마우스 휠을 인식하는 드라이버 내지 추가 프로그램을 실행한 뒤, 휠이 굴렀다는 메시지 값을 RegisterWindowMessage(MSWHEEL_ROLLMSG)로부터 얻게 하던 시절이 있었다. 이 문자열의 값은 다음과 같았다.
#define MSWHEEL_ROLLMSG  _T("MSWHEEL_ROLLMSG")

그리고 오늘날 custom 메시지가 쓰이는 또 다른 대표적인 분야는 시스템 트레이라고 불리는 notification area이다. 트레이에다가 자기 아이콘을 추가하는 프로그램들은 _T("TaskbarCreated")라는 메시지를 받았을 때 아이콘을 다시 등록해 줘야 한다.

운영체제의 셸은 자기가 갖고 있던 아이콘들을 자체 보관하지 않는다. explorer 프로세스가 에러가 나서 뻗었거나 강제 종료되었다가 다시 실행되었다면, 아이콘들이 싹 다 날아가게 된다. 이때 셸은 모든 프로그램들을 대상으로 저 메시지를 보내서, 프로그램들로 하여금 알아서 트레이에다 아이콘을 다시 등록하게 한다. 마치 WM_PAINT 메시지를 받았을 때 창이 알아서 자기 내용을 다시 그려야 하듯이 말이다.

저건 너무 유명한 메시지가 되어 버렸기 때문에 장기적으로는 WM_TASKBAR_CREATED 같은 시스템 메시지로 승격이라도 돼야 하지 않나 싶다. 그리고 응용 프로그램들이 늘어날수록 이런 custom 메시지의 공간도 부족해지지는 않으려나 우려가 된다. 16000여 개만으로 충분하겠지? custom 클립보드 포맷이라든가 스레드별로 할당되는 TLS 슬롯의 개수와 비슷한 맥락으로 공간의 한계가 존재하는 영역이라고 볼 수 있다.

Objective C는 언어 차원에서 생으로 문자열 메시지를 객체들 사이에 주고받는 걸 지원한다. C++ 일반 멤버 함수를 호출하는 것보다 오버헤드는 당연히 훨씬 더 크지만, 함수 프로토타입이 하나 바뀌었다고 프로그램 모듈간의 바이너리 호환성이 박살 난다거나, 재컴파일을 해야 하는 그런 불편함은 없다. 내가 옵C를 잘은 모르지만 Windows의 custom 메시지를 보니 문득 옵C 생각도 났다.

이렇게 윈도우 메시지의 계층 4개를 모두 살펴보았다. 시스템 메시지만 1024개로 영역이 매우 좁아서 WM_USER의 영역이 넓은 편인 반면, 나머지 계층은 16비트 정수에서 1/4에 해당하는 16384개를 사이좋게 나눠 쓰고 있다.
그리고 메시지를 담는 공간 자체는 진작부터 32비트로 커졌지만, Windows는 16비트 크기의 범위를 벗어나는 영역은 여전히 예약만 해 놓고 쓰지 않고 있다.

허나, 개인적인 생각은 이들 중에서 그래도 custom(registered) 메시지가 16비트 이상의 범위로 확장되거나 이동하기 가장 용이한 영역이 아닌가 싶다.
일단 얘는 upper bound가 없는 가장 마지막 계층인 데다, MSG 구조체를 포함해서 메시지 값을 담는 모든 자료형이 32비트 UINT로 이미 다 확장되어 있고, custom 메시지는 언제나 함수가 되돌리는 변수값으로 활용하지 하드코딩이 없으니, 확장에 가장 유동적으로 대처 가능하기 때문이다.

Posted by 사무엘

2013/02/25 08:39 2013/02/25 08:39
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/800

철도 사진 분석 퀴즈

1. 다음 사진은 보다시피 경의선 지하 가좌 역의 승강장 역명판이다. 이 역명판이 있는 승강장은 서울(홍대입구)과 문산(DMC) 중 도대체 어느 방면 승강장일까?

사용자 삽입 이미지

정답과 해설


2. 아래의 사진은 대략 언제 어디에서 촬영되었을까? 물론 국내이다. (힌트: 레일의 개수가 심상찮다)

사용자 삽입 이미지

정답과 해설


 

Posted by 사무엘

2013/02/22 15:26 2013/02/22 15:26
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/799

내가 타인을 따라서가 아니라 스스로 서울 여행을 하고 제 발로 지하철/전철을 이용하기 시작한 건 2001~2002년 사이부터이다.
그리고 서울 지하철 중에서 5호선은 전동차가 아주 중독성 있는 특이한 가속 구동음을 낸다는 걸 스스로 인지한 게 한 2003년쯤이다.

서울 지하철 5호선!
인간이 발명한 교통수단에서 어떻게 이런 구수한 소리가 날 수 있는지 나는 골똘이 생각하지 않을 수 없었다.
이건 자동차처럼 연료를 폭발시키는 소리도 아니고, 비행기처럼 공기를 뿜는 소리도 아니고.. 도대체 무슨 기계를 만들어야 이런 소리가 날 수 있을까?
나팔을 부는 듯한 관악기 소리에 가까운지, 아니면 현을 켜는 듯한 현악기 소리에 가까운지는 내 음악 지식으로는 판단을 못 하겠다.

그래서 내부 디테일을 좀 공부하다 보니 몇 가지 용어를 알게 됐다. 전동차의 동력비 변환 메커니즘은 저항 제어, 쵸퍼 제어에 이어 반도체를 이용한 최신 기술인 VVVF(가변 전압 가변 주파수) 제어 방식으로 변모해 왔는데, 바로 VVVF 초창기에 속하는 차량이 이런 독특한 소리를 내는 것이었다.

그리고 사실은 VVVF도 다 같은 물건이 아니다. 초기의 VVVF-GTO 방식은 윙~윙 하는 소음이 큰 편이지만, 나중에 등장한 VVVF-IGBT 방식은 구동음이 조용해진 편이다. 서울 지하철 5호선 전동차는 물론 GTO 방식이다. 이에 대한 더 자세한 설명은 이곳에서.. 그리고 또 이곳 설명도 꼭 봐라, 두 번 봐라.

철덕들이야 이 소리를 아주 좋아하지만, 일반인들은 5호선 열차가 주행 중에 너무 시끄럽다고 불만이 많은 편이었다.
자갈 대신 콘크리트 노반, 좁은 터널, 굴곡이 많은 선형 등 여러 다른 이유들도 있지만, 당시 첫 도입되었던 VVVF 인버터도 소음을 가중시키는 요인 중 하나이긴 했다. 게다가 5호선 차량의 인버터는 원래 지하도 아니고 지상 전철용 부품이 납품된 거라고도 하고.

그래서 5호선의 운영사인 도철에서는 장기적으로 5호선 전동차의 인버터를 더 조용한 것으로 차츰 교체하기로 계획을 세웠으며, 지금으로부터 1년쯤 전인 2012년 2월, 제 502편성 열차 하나를 시범적으로 독일제 VVVF-IGBT 인버터로 교체해서 굴리기 시작했다.

난 그 소식을 인터넷을 통해 접하기는 했다. 그러나 지금까지 그 열차와 마주칠 일은 없었다. 수십 편성짜리 열차 중에 달랑 하나만 바뀐 거니까 말이다.
그랬는데.. 며칠 전에.. 드디어 조우했다!

환승을 위해 겨우 두 정거장 구간밖에 이용을 못 해서 구동음을 충분히 감상하지는 못했지만, 내 기억이 맞다면 지금 2-3-9호선 신형 전동차보다도 더 조용한 듯하다. 그쪽 계열 소리가 아니다. 첫음은 G와 G# 중에서 G에 더 가까웠지 싶다.
내가 지난 10년간 탔던 5호선답지 않게 전동차의 구동음이 너무 조용해져서 적응이 안 된다.

마치 예전에 6호선에서 잠깐 다니던 전설의 609편성을 보는 듯한 느낌이다.
그것처럼 지금 5호선에는 혼자 구동음이 튀는 열차가 하나 다니기 시작해 있다.
이런 식으로 이제 5호선의 마스코트인 ABB사 기존 구동음도 점점 듣기 어려워질 수 있으니, 이제 서둘러서 녹음하려면 녹음하고 구동음을 실컷 감상해 둬야겠다.

어쨌든 철도는 이렇게 아름다운 구동음을 내면서 달리는 전동차도 있으니, 참 웰빙 교통수단임이 틀림없다.

Posted by 사무엘

2013/02/20 08:39 2013/02/20 08:39
, , , ,
Response
No Trackback , 6 Comments
RSS :
http://moogi.new21.org/tc/rss/response/798

여러 기호(또는 문장 부호)들 중에서 따옴표는 컴퓨터로 입력할 때 좀 유별나게 처리되고 다뤄지는 면모가 있는 문자이다.
일단 따옴표는 기호가 쌍으로 붙은 큰따옴표와, 하나만 있는 작은따옴표로 나뉜다. 전자는 어떤 발언을 직접 인용할 때, 그리고 후자는 발성이 타인에게 실제로 들리지는 않는 혼잣말이나 생각을 인용할 때 통상 쓰인다.

그러나 이런 물리적인 구분이 명확하지 않은 경우도 있다. 재미있는 것은, 따옴표로 둘러싸인 인용문의 내부에 또 인용문이 또 등장할 때는 물리적인 구분과는 무관하게 맞은편 따옴표가 작은→큰→작은..의 순으로 번갈아가며 쓰인다는 점이다. 또한 따옴표들은 완전한 문장이 아니라, 문장 내부의 특정 단어를 단순히 강조하는 용도로도 쓰인다.

따옴표는 괄호와 마찬가지로 열고 닫는 구분이 있다. 그러나 타자기나 컴퓨터의 글자판에는 괄호와는 달리 여닫는 따옴표가 따로 배당되어 있지 않다. 방향 구분이 없는 "와 '가 각각 큰따옴표와 작은따옴표의 역할을 대신한다.

텍스트 에디터는 코딩용으로도 쓰이고 키보드에 배당된 아스키 문자가 그대로 입력되는 게 중요한 경우가 많기 때문에, 따옴표도 있는 그대로만 입력받는다. 그러나 워드 프로세서 정도 되면, 지금 입력하는 문맥에 따라 해당 문자를 여닫는 따옴표로 알아서 치환해서 입력해 준다. 여기서 아주 재미있는 차이가 존재하는데, 아래아한글은 전통적으로 이런 구분을 한글 글자판을 쓸 때만 하는 반면 MS 워드는 글자판 구분 없이 언제나 그렇게 동작한다.

글쎄, 한글 입력 방식에서는 세벌식 최종 글자판이 여는 큰따옴표와 닫는 큰따옴표가 따로 배당되어 있고 그걸로도 모자라 "까지도 있다. 이것은 컴퓨터에서는 다소 잉여스러운 설계인지도 모르겠다. 그 대신, 영미 문화권의 텍스트 문서에서는 tilde 글쇠의 아래에 있는 `가 종종 여는 작은따옴표로 쓰이고 기존 '(어퍼스트로피)는 닫는 작은따옴표로만 쓰이는 경우가 있다. 한국에서는 보기 힘든 관행인 듯.

얼마 전에 본인은 여닫이 구분이 없는 따옴표만 쓰인 빽빽한 영어 성경 텍스트의 따옴표들을 여닫이 구분이 있는 형태로 일괄 변환할 수 있겠느냐는 부탁을 주변으로부터 받은 적이 있었다. 그냥 "만 달랑 들어있으면 왠지 아마추어스러운 티가 팍 날 테니, 인쇄해서 책으로 낼 텍스트라면 그 정도 배려는 해 주는 게 좋을 것이다.

"를 계속 찾으면서 “와 ”로 번갈아가며 대치만 하는 걸로 일이 끝이면 얼마나 좋았겠냐만, 이거 패턴이 의외로 간단하지만은 않음을 알 수 있었다.
작은따옴표는 어퍼스트로피가 존재하기 때문에 닫는 따옴표의 개수가 더 많아진다. 더구나 s로 끝나는 단어의 뒤에 붙은 어퍼스트로피는 닫는 작은따옴표와 형태적으로 구분할 방법이 없다.

그리고 큰따옴표와 작은따옴표 모두, 인용문이 여러 문단에 걸쳐서 길게 반복되는 경우라면, 이전 문단에서는 닫는 따옴표로 마무리가 되지 않은 채 다음 문단에서 또다시 여는 따옴표가 나온다. 물론, 그렇지 않고 앞 문단에서는 여는 따옴표로 시작해서, 다음 문단에서는 닫는 따옴표로 곱게 끝나는 인용도 따로 있고 말이다.

그리고 아까 얘기했던 중첩 인용도 문제가 될 수 있다. 열큰(여는 큰따옴표)와 작큰(작은 큰따옴표) 다음에 입력되는 큰따옴표는 여는 큰따옴표가 되어야 하는데, 이런 점을 고려하지 않고 기계적으로 따옴표를 치환하면 “(열큰) “(열큰) ”(닫큰) ”(닫큰)의 순으로 등장해야 할 따옴표가 “(열큰) ”(닫큰) “(열큰) ”(닫큰)으로 잘못 짝지어질 위험이 있다.

텍스트에는 최대 3단계의 중첩 인용이 존재하더라. 이거 생각보다 꽤 복잡하지 않은가?
텍스트 매크로, 찾기/바꾸기 등 여러 방법 중 과연 어느 것이 가장 적합할지 고민했는데 결국은 여러 차례의 바꾸기 기능을 사용하기로 결정을 내렸다. 여는 따옴표가 확실한 조건을 지정해 준 뒤, 나머지 따옴표는 모두 닫는 것으로 간주하는 게 가장 속 시원하다. 구체적인 절차는 다음과 같다.

1. 줄의 맨 첫 칸에 등장하는 따옴표는 반드시 여는 따옴표이다. 상식적으로 문단이 닫는 따옴표로 시작하지는 않을 테니 말이다. 그래서 줄의 처음을 의미하는 정규 표현식 ^를 사용하여, 첫 칸의 따옴표부터 다 여는 걸로 모두 바꾼다.

2. 그리고 공백(whitespace) 다음에 등장하는 따옴표는 반드시 여는 따옴표이므로 그렇게 바꿈.

3. 끝으로, 여는 따옴표의 다음에 등장하는 따옴표도 반드시 여는 따옴표이다. (여는 큰따음표 다음의 작은따옴표. 그리고 vice versa)

4. 위의 조건들을 만족하지 않고 아직도 방향이 결정되지 않은 따옴표들은 모두 닫는 따옴표이다. 치환.

이 작업을 큰따옴표/작은따옴표별로 다 해야 하기 때문에 '모두 바꾸기'를 무려 8번이나 해 줘야 한다.
그러나 <날개셋> 편집기에서는 '일괄 치환' 텍스트 필터에다가 다음과 같은 조건을 주면 일격에 상황 종료. 굉장히 유용하다.

"\n\"","\n“"
"\n'","\n‘"
" \""," “"
" '"," ‘"
“',“‘
"‘\"",‘“
',’
"\"",”

따옴표가 특별한 의미로 쓰이는 곳에서 따옴표 자체를 지정해야 하다 보니, 글자를 알아보기가 몹시 힘들다. 게다가 코딩용 폰트가 아닌 폰트는 여는 따옴표와 닫는 따옴표도 형태를 분간하기가 쉽지 않은 경우가 태반.
타자기 내지 컴퓨터 키보드에서 따옴표의 방향 구분이 별 의미 없다고 간주하여 괜히 없앤 게 아닌 듯하다.

아울러, 데이터베이스 같은 곳에서는 문자열 내부에 또 큰따옴표가 들어있을 때, 문자열의 시작을 '큰따옴표 두 개'로 표현하는 경우가 있다. 다음은 그런 문자열을 원래대로 복원하는 일괄 치환 조건이다.

"\"\"","\""
"\"",

그러고 보니 방향 구분이 없는 큰따옴표는 "위와 같음"(상등)을 의미하는 기호로도 쓰이며, 심지어 작은/큰따옴표가 각각 분과 초를 의미하는 단위로도 쓰인다. 신기하다. 본인은 이 경우까지 고려하지는 않았다.

Posted by 사무엘

2013/02/18 08:28 2013/02/18 08:28
,
Response
No Trackback , 10 Comments
RSS :
http://moogi.new21.org/tc/rss/response/797

« Previous : 1 : ... 141 : 142 : 143 : 144 : 145 : 146 : 147 : 148 : 149 : ... 215 : Next »

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

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

Site Stats

Total hits:
2676371
Today:
939
Yesterday:
2124