« Previous : 1 : ... 138 : 139 : 140 : 141 : 142 : 143 : 144 : 145 : 146 : ... 221 : Next »

디지털 컴퓨터는 전자 신호로 0과 1만을 인식할 수 있다. 그렇기 때문에 컴퓨터에서는 문자 역시 0과 1의 조합인 숫자로 표현되며, 문자를 그런 숫자에다가 대응시키는 규칙이 바로 문자 코드이다. 정보 교환을 원활히 하기 위해서는 보내는 쪽과 받는 쪽이 모두 문자 코드가 통일돼 있어야 함은 두 말할 나위가 없다.

정보량의 최소 단위는 1비트이지만 현실적으로 컴퓨터의 기억 장치들이 한 글자로 취급하는 정보량의 최소 단위는 8비트, 즉 바이트이다. 8비트, 2^8승, 즉 256이라는 정보량은 숫자와 알파벳을 정하는 데는 충분하고 상위 1비트를 잉여화하여 오류 검출용으로까지 사용할 수 있을 정도이지만, 한글이나 한자 같은 문자를 담는 데는 턱없이 부족하다.

한자는 글자를 체계적으로 부분글자로 분해할 뾰족할 방도가 없는 상태로 글자 수가 너무 많다. 한글은 글자 생성 체계는 한자보다 훨씬 더 유리한 위치에 있지만, 실용적으로 편하게 다루려면 여전히 1만여 개의 미리 조합된 음절 형태를 그대로 배당해야 한다.

예를 들어 현실에서 '학교'라는 단어는 두 글자로 간주되지, 데이터베이스나 문서 분량 같은 데서 5글자라고 계수되는 곳은 아무도 없다. 코드 차지 영역을 줄이자니 메모리 사용량이 늘며, 그리고 오늘날로 치면 화면에 보이는 글자 길이와 메모리를 차지하는 글자 길이가 서로 완전히 따로 노는 complex script 급의 복잡한 기술이 필요해진다.

결국 한글과 한자는 1바이트만으로 표현할 수 없고 통상 2바이트로 표현된다. 그런데 기존 1바이트 영문· 숫자를 건드릴 수는 없으니 1바이트와 2바이트 문자가 공존하는 multi-byte 코드 체계가 만들어진다. 영문권에서 패리티 비트 내지 유럽 특수문자를 표현하는 데 쓰이는 최상위 1비트는, 이 문자가 1바이트로 끝인지 아니면 2바이트 문자의 첫 바이트(lead byte)인지를 판별하는 비트로 쓰인다.
공교롭게도 한글· 한자는 폭도 균일한 2글자 분량을 차지하여 비주얼 상 잘 어울린다. 전각· 반각이라는 개념이 여기서 유래된다.

그런데 lead byte는 그렇다 쳐도 2바이트의 다음 둘째 바이트인 tail byte도 기존 순수 1바이트 문자들과(영문· 숫자 같은) 전혀 겹치지 않게 할 것인지? 아니면 어차피 얘는 lead byte에 의해 변별이 됐으니 좀 문맥 의존적으로 겹치는 걸 허용할 것인지가 문제가 된다. 한글 코드의 경우 완성형은 겹치지 않으나 조합형은 겹친다.

그렇기 때문에 조합형은 tail byte가 대문자 알파벳과 소뭇자 알파벳이 제각기 따로 될 수 있고 심지어 \ 역슬래시 문자가 나올 수도 있다. 이런 이유로 인해 조합형은 1비트 + 초중성 5비트씩 한글의 구성 원리를 매우 잘 반영하여 설계된 문자 코드임에도 불구하고 당시 8.3 제약이 있던 시절에 파일 이름을 사실상 한글로 지정할 수 없었으며, 심지어 이론적으로는 // 주석을 조합형 한글로 지정한 경우 \ 때문에 2-byte aware 하지 않은 컴파일러에서는 충돌이 발생할 수도 있었다.

옛날에 한글 MS-DOS 시절에 한글 도스 셸이나 QBasic처럼 텍스트 GUI(?)를 구현한 프로그램들을 실행해 보면 한국 마소가 굉장히 잘 만든 게 있었다. 메뉴나 대화상자 같은 게 표시되어서 배경에 있던 2바이트 텍스트를 반토막 내더라도 깨진 문자열을 보여주는 법이 결코 없었다. 늘 짝수 바이트가 유지돼야 하는 한글/한자 영역이 홀수 바이트로 줄어들면 가장자리를 하나 삭제해 주면 된다.

그러나 문맥 의존적인 문자 코드로 그런 걸 구현하려면 문자열을 처음부터 읽어 봐야 하고, 두 바이트 중 하나가 소실됐을 때의 뒷처리가 문맥 독립 코드보다 더 어려우면 어렵지 쉽지는 않을 것이다. 불가능하다는 뜻은 아니지만.

조합형을 옹호하고 완성형을 까기에만 바쁜 분들의 심정이야 세벌식+한글 에반젤리스트로서 본인은 적극 이해한다. 그러나 과거의 2바이트 한글 코드의 이면엔 이런 면모도 있었음을 지적하고자 한다. 완성형은 여러 이슈가 얽혀서 그렇게 만들어졌지, 작정하고 한글을 난도질하려는 악의적인 의도로 만들어진 건 아니었다. 문맥 독립성뿐만 아니라 굉장히 보수적인 국제 규격 권장 사항을 만족하는 방식으로 문자 코드를 만들려다 보니, 한글 11172자에다 한자와 각종 특수문자들까지 넣을 절대적인 공간 자체가 부족했기 때문이다.

나중에 완성형에다 어거지로 한글 부족분을 채워 넣은 cp949 확장완성형은 어차피 조합형처럼 문맥 독립성이 깨졌다. 물론, 어차피 이렇게 만들 거면 처음부터 조합형으로 가지 하는 비판은 피할 수 없게 된 게 사실이다. 그런데, 이런 비슷한 삽질을 일본도 문자 코드를 만들면서 했으며, 이를 우리나라도 그대로 답습했을 뿐이다.

조합형과 완성형은 11172자와 2350자의 차이뿐만 아니라 한글 낱자를 표현하는 방식에서 매우 큰 차이가 있다.
조합형은 글자마디와 자모의 차이가 사실상 없다. 그냥 초 중 종성 중 한 성분만 있으면 자모이고, 초성과 중성이 갖춰졌으면 글자마디이다. 초성 ㄱ과 종성 ㄱ을 구분해서 표현할 수 있고 초성+종성이나 중성+종성 같은 '미완성 한글'도 얼마든지 표현할 수 있다.

그러나 완성형엔 그런 개념이 없다. 한글 입력을 처음 시작했을 때 쓰라고 '호환용 한글 자모'라는 게 있는데 그건 한글이 아니라 명목상으로는 '특수문자' 영역이다. 그냥 한글 자모 모양인 그림일 뿐이다. 초성+종성 구분이나 미완성 한글 표현 같은 건 없다.

메모리 1바이트가 아깝던 16비트 도스 시절에 국내에서는 조합형 한글 코드+글꼴이 쓰였지 마소의 윈도처럼 2350자 완성형 코드+글꼴로 돈지랄을 하는 프로그램은 전혀 없었다. 이런 압도적인 인지도의 차이로 인해 조합형은 1992년에 한국의 복수 표준으로 승격되어서 cp1361이라는 이름으로 나름 운영체제의 코드 페이지에 등록도 됐다.

자, 이렇게 컴퓨터는 1바이트 인코딩에다가 한중일 문화권을 위한 1+2바이트 멀티바이트 인코딩 구도가 유지되었고 마소에서는 이를 도스 시절부터 코드 페이지라고 불렀다. 그런데 이런 문자 코드들은 (1) 알파벳· 숫자 같은 공통 문자를 제외하면 같은 문자라 해도 국가마다 배당된 코드값이 같을 수가 없고, (2) 문자 집합 자체의 크기가 너무 작아서 세계 모든 문자들을 한 코드 페이지에다 담을 수 없다는 문제가 있었다.

人(사람 인)이라는 한자가 한국· 중국· 일본의 표준 코드에서의 코드값이 같을 리가 없으니 (1)은 자명한 문제다. (2)의 경우, 2바이트 코드라 해도 앞서 보았듯이 1바이트 문자와의 충돌을 피하기 위해 매우 제한된 영역의 바이트들만 묶을 수 있다. 이 때문에, 문맥 의존까지 시킨다 해도 추가로 만들 수 있는 문자는 1만여 자에 불과하다. CJK에서 쓰는 상용 한자들이나 간신히 다 집어넣을 정도이다.

그래서 1980년대 말부터 이런 발상의 전환이 이뤄졌다. “앞으로 컴퓨터의 메모리는 증가하고 소프트웨어 국제화의 중요성은 커질 테니, 글자 하나의 크기를 16비트로 쿨하게 확장하고 6만여 자의 공간에다가 전세계 언어 문자들을 집어넣고 동일 문자 동일 코드값을 실현하는 게 어떨까?”

이것이 바로 유니코드의 존재 목적 되시겠다. 컴공 전공자가 아니더라도 컴퓨터에서 '유니코드'라는 말은 한 번쯤은 다들 들어 보셨을 것이다. 이건 그야말로 컴퓨터 문자 코드계의 바벨 탑 내지 에스페란토 같은 물건이다.

마이크로소프트는 소프트웨어의 국제화에 굉장한 혜안이 있던 기업이었다. 그래서 OS/2와 결별하고 Windows NT를 첫 개발할 때, 애초부터 8비트 문자가 아니라 유니코드를 담을 수 있는 16비트 문자를 기반으로 설계했다. 심지어는 NTFS 파일 시스템까지도. 그리고는 이를 독자적으로 wide character이라고 부르기 시작했다. 어차피 8비트 문자만으로 충분하던 라틴 문화권에서는 별로 득이 되는 것도 없이 메모리 사용량만 두 배로 뻥튀기되는 대가를 감수하고라도 말이다.

Win32 API는 기존 16비트 윈도 API를 최대한 닮게 설계되었지만, 문자열을 다루는 모든 API에 A 버전과 W 버전 구분이 생겼다. 다만, 32비트 시절에 처음으로 도입된 OLE/COM 같은 기술은 처음부터 오로지 유니코드(= wide) 문자열만 취급하게 만들어졌다.

PE 방식으로 만들어진 32비트 EXE/DLL은 string 테이블, 메뉴, 대화상자 같은 리소스의 저장 포맷도 다 유니코드 방식으로 바뀌었다. 윈도 3.1에다가 Win32s를 설치하면 32비트 커널 DLL뿐만 아니라 각종 코드 페이지 변환 테이블도 설치되는 걸 볼 수 있다. 그게 바로 리소스를 변환하기 위해서이다.

윈도 NT가 3.x나 9x보다 메모리 사용량이 매우 많았던 이유 중 하나도 유니코드 때문이며, 반대로 윈도 9x가 유니코드 API를 지원하지 않았던 이유도 가정용 PC의 부족한 메모리 문제 때문이다.
이 때문에 컴파일 스위치만 바꿔서 유니코드와 기존 멀티바이트를 모두 커버할 수 있는 TCHAR, _T 같은 개념도 그때 생겼다. 두 개의 문자 포맷을 모두 지원하는 작업은 정말 엄청난 대공사였을 것 같다.

Posted by 사무엘

2013/12/13 08:24 2013/12/13 08:24
, , , , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/908

본인은 중학생이던 1996년 말, 친구 집의 컴퓨터를 통해 우연히 툼 레이더라는 게임을 접했을 때의 충격을 난 지금도 잊을 수 없다.
페르시아의 왕자 같은 파쿠르 액션이 full 3D로 구현되어 나오고 게다가 주인공이 듀크 뉴켐스러운 마초 근육맨이 아닌 아리따운 아가씨라니!
툼 레이더 시리즈는 전세계적인 히트를 쳤으며 게임 주인공인 라라 크로프트는 역사상 가장 성공한 사이버 모델이 되었다.
이번 글에서는 롤스로이스에 이어 또 '영국'이 만들어 낸(냈던) 명작에 대한 리뷰를 좀 써 보겠다.

본인은 예전에도 몇 차례 글을 썼듯이, 비디오 게임의 역사 중에서 3차원 그래픽 기술이 막 도입되던 1990년대 중· 후반의 과도기가 가장 흥미진진한 시절이었다고 생각한다.
id 소프트웨어의 엔진이 울펜슈타인, 둠, 퀘이크를 거치면서 발전해 가던 시절,
2.5D와 완전한 3D 사이의 과도기이던 켄 실버맨의 빌드 엔진을 쓴 게임(듀크 뉴켐, 섀도우 워리어),
목각인형에서 진짜 사람으로 발전하던 버추어 파이터 시리즈,
실사 사진을 쓰다가 최초로 3D 폴리곤으로 바뀐 모탈 컴뱃 4 등등 말이다.

그런 맥락에서 툼 레이더도 예외가 아닌 것이다.
안 그래도 엔하위키나 한국어 위키백과는 각 시리즈 별 설명이 부실한 편이기도 하니 한번쯤 이런 내력을 나열해 보는 것도 흥미롭지 싶다.

사용자 삽입 이미지

※ 1

그야말로 전설이 아닌 레전드를 창조한 첫 작품이며, PC 플랫폼은 처음이자 마지막으로 도스를 지원했다. 눈이 종종 쌓여 있는 숲, 광활한 고대 유적지, 피라미드 등을 돌아다니면서 말 그대로 묘지를 터는 본분에 충실한 형태였다. 몹 중에 인간은 흔치 않았던 걸로 기억하며, 혼자서 퍼즐을 풀어 나가는 비중이 컸다.
1부터 3까지는 같은 엔진 기반으로, 그래픽이나 게임 진행이 그렇게 큰 차이가 없다.

※ 2 (starring Lara Croft)

1이 대성공을 거둔 뒤, 2부터는 도스 대신 Windows용으로 나오기 시작했다. 2는 1에 비해 폴리곤 수가 늘어서 라라가 아주 약간 더 예뻐졌으며, 기술적으로 무리였던 팔랑거리는 말총머리가 드디어 구현되었다.
그리고 동적 광원을 구현하면서 flare(섬광탄) 아이템이 첨가되었다. 그리고 물에는 언제나 수영만 있던 것과 달리 얕은 물에서 걷는 wading이 추가되었으며, 암반을 오르는 climbing이 추가되었다.

2는 첫 시작을 만리장성에서 하고, 결말부에서 최종 보스도 용일 정도로 배경에 중국에 대한 비중이 가장 높다. 1보다 대인 전투의 비중이 높아졌으며 무덤 말고 도시, 선박 등 인간이 만든 시설도 많이 등장한다. 무기는 샷건뿐만 아니라 M16 소총, 작살총도 추가되었다.
보트나 스노우모빌 같은 탈것을 이용하는 동작도 처음으로 추가되었다. 생각해 보면, 주인공이 탈것을 이용할 수 있는 아케이드 게임은 매우 드물다. 용 정도나 타는 옛날 황금도끼 말고 딴 게 뭐 있었던가??

※ 3 (라라 크로프트의 모험)

3은 2와 같은 맥락으로 더 큰 변화가 이뤄졌다. 그래서 단순한 유적지 털기라는 타이틀이 무색할 정도로 전세계 방방곡곡을 배경으로 한 액션 어드벤처로 바뀌었다. 덕분에 게임 배경이 역대 툼 레이더 시리즈 중 가장 넓어서 인도, 남태평양 섬, 남극, 미국 AREA 51, 런던 자택 근처가 레벨로 모두 등장한다. 그리고 각 레벨별로 라라의 복장도 가장 다양해졌다. 지역별로 레벨 플레이 순서를 사용자가 임의로 선택할 수 있는 시스템도 3이 역사상 전무후무하게 갖추고 있었다.

기술적으로 보자면 라라에게는 그냥 달리는 게 아니라 기력을 소비하면서 잠시 동안 전력질주를 하는 sprinting이 추가되었으며 엎드리기(crouching), 천장을 잡고 건너기 같은 동작이 추가되었다. 그래픽이 개선된 건 총기 격발 후에 발생하는 탄피와 연기 흔적, 그리고 물에서 나타나는 특수효과가 일부 개선된 정도다. 슬슬 엔진의 약발이 다할 때가 되고 있었고, 라라 팬으로부터 불만도 들어오고 있었다. 3이 이룬 것은 같은 시스템 하에서 단순 양적 팽창 위주일 뿐이었으니 말이다. 그래서 다음 편부터는 시대에 맞추어 엔진이 교체되었다.

※ 4 (마지막 계시)

프로그램이 처음부터 싹 다시 개발되었다. 시작 화면, GUI 글꼴 같은 게 전부 바뀌었다. 여권 수첩 형태의 메뉴나 동그란 고리 형태로 나오던 인벤토리 링도 다 없어졌다.
4는 오로지 이집트 주변만 공략하여 순수하게 유적지 털기 미션으로 되돌아갔으며, 라라의 의상도 시종일관 기본 단일 복장으로 바뀌었다. 그렇잖아도 툼 레이더 4가 나온 1999년은 <미라>(Mummy)라는 영화가 인기를 끌었던 해이기도 해서 더욱 이집트스러운 기억이 깊게 남아 있다.

4는 엔진이 교체되면서 그래픽이 예전보다 대대적으로 향상되었다. 드디어 모든 텍스처에는 하이컬러 안티앨리어싱이 적용되기 시작했고 라라의 외형도 더욱 매끄럽고 예뻐졌다. 복잡한 지형에서 발생하던 1~3 엔진 특유의 그래픽 glitch가 거의 다 사라졌다. 3에서 바뀌었는지 4에서 바뀌었는지 모르겠지만 어쨌든 아이템이 여전히 2D 스프라이트로 표현되던 것들도 드디어 순수 폴리곤으로 바뀌었다.

4에서는 예전 시리즈에서 전통적으로 등장하던 라라 저택 연습 미션이 없어졌고, 그 대신 게임 전반부에서 라라가 어렸을 때의 모습이 잠깐 나온다. 그리고 줄을 타고 오르는 동작이 추가되었으며 여러 아이템을 조립해서 새로운 아이템을 만드는 시스템이 도입됐다. 또한, 4는 역대 툼 레이더 시리즈 중 레벨 수가 가장 많고 플레이 시간이 가장 길었다고 한다. 그 중 열차 안에서 미션을 수행하는 사막 열차 레벨은 상당히 참신한 디자인.
이 게임의 엔딩은 라라가 죽는 걸(최소한 그걸 암시하는)로 끝난다. 왜 그 잘 나가는 캐릭터를 죽이는지, 시리즈를 벌써 종결지으려는 건지 제작사의 의도를 알 수 없는 대목이다.

※ 5 (크로니클 연대기)

1부터 5까지는 각각 1996년부터 2000년까지 1년 간격으로 그 해의 하반기에 제품이 출시되었다. 5편인 크로니클은 4편과 거의 같은 엔진 기반에 컨텐츠만 다르다. 라라 크로프트는 죽고 없는데 그녀가 살아 생전에 남긴 무용담을 회상하며 플레이한다는 설정. 그래서 3편만큼이나 다양한 복장으로 과거 유물과 현대 도시를 아우르면서 다양한 미션을 수행한다.

5는 이상한 설정으로 인해 예전 시리즈만 한 대박은 못 친 걸로 본인은 기억한다. 다만, 이때 제작사가 무슨 생각이 있었는지 상당히 대인배적인 조치를 취했는데, 바로 레벨 에디터를 게임과 더불어 공식 배포했다. 스타크래프트로 치면 걸출한 캠페인 에디터인 StarEdit인 셈.

덕분에 툼 레이더 4~5 엔진은 내가 알기로 역대 툼 레이더 시리즈들 중 단순 의상 패치 이상으로 full-featured custom 레벨 MOD가 존재하는 최후의 엔진이다. 그래서 인터넷엔 라라 크로프트 하앍하앍 하는 전세계의 양덕후들이 만들어 올린 custom 레벨 파일 및 플레이 동영상들이 즐비하다.
1~3 엔진은 에디터가 있다 하더라도 요즘 기준으론 그래픽이 너무 심하게 후져서 별로. 4~5 엔진 정도는 돼야 그나마 할 만하다.

※ 6 (어둠의 천사 AOD)

툼 레이더의 제작사인 코어 디자인은 라라 크로프트라는 희대의 대박 아이템이자 황금알 낳는 거위를 창조해 놓고는 영업이랄까 운영을 썩 잘했다고 볼 수는 없었다. 3에서 4로 넘어갈 때 좀 혁신을 한 걸 제외하면, 계속 같은 엔진과 게임 시스템만 지겹게 우려먹는 걸 좋아하는 편이었다. 그리고 배가 산으로 가는 듯한 스토리를 전개하며 4편에서 라라 크로프트를 덥석 죽여 버린 건 팬들의 원성을 사기에 충분했다.

그런 와중에 거의 3년간의 침묵을 깨고 툼 레이더의 다음 시리즈인 어둠의 천사가 나왔다. 시기가 시기인 만큼 기술은 분명 크게 발전했다. 물리 엔진이 도입되었고 라라의 손발 모션은 지형을 반영하여 더욱 자연스럽게 나오기 시작했다. 그래픽 디테일 레벨을 올리면, 그림자도 그냥 바닥에 시꺼먼 타원으로만 나오는 게 아니라 실제 광원과 라라의 체형대로 나오기 시작했다.

다만, 이 작품에서 라라 크로프트는 과거에 죽었다가 아무 개연성 없이 불쑥 살아서 돌아왔으며, 고대 유적지를 돌아다니는 묘지 도굴꾼 컨셉에서도 더욱 멀어졌다. 전통적인 복장 대신 군복과 청바지 차림으로 의상이 완전히 바뀌었다.
6은 기술의 진보는 분명 있었으며 국내에서는 툼 레이더 시리즈 중 최초로 완전 한글화가 되기도 했다. 하지만 저런 괴상한 정체성과 많은 버그들 때문에 완성도가 떨어지고 뭔가 핀트가 안 맞는 작품으로 전락했으며, 큰 성공을 거두지 못했다.

※ 7 (레전드)

툼 레이더의 본디 제작사이던 코어 디자인은 6 이후로 제작사 타이틀을 반납하고 그저 그런 게임 개발사로 남아 있다가 2007년쯤에 망했다. 그 뒤 툼 레이더의 개발은 영국이 아닌 미국의 크리스탈 다이나믹스로 넘어갔는데 얘가 툼 레이더 시리즈를 다시 물건으로 회복시켜 놓았다.

2006년 봄에 출시된 레전드는 툼 레이더의 '제2기' 시대를 열었다. 툼 레이더의 기본 복장이 되돌아왔으며 유적지 탐험 패턴도 복귀했다. 그러는 한편으로 조작도 다 뭔가 현대적인 감각으로 바뀌었다. 언제나 자동 세이브가 되고 굳이 별도의 키를 누르지 않아도 절벽에서는 자동으로 매달리는 등, 퍼즐 난이도는 예전보다 더 쉬워졌는데 이건 요즘 게임기용 게임들의 추세가 다 그런 게 아닌가 싶다.

레전드에 대한 반응은 전반적으로 좋았다. 하지만 예전하고는 너무 이질적으로 바뀐 게임 시스템에 대해서는 게이머들 사이에 호불호가 갈리기도 했다. 맨날 동료하고 얘기를 주고받는 것도 예전에는 없던 관행이었으니까.
아무튼, 이렇게 레전드로 희망찬 데뷔 선언을 한 크리스탈 다이나믹스는 그 이듬해에 Anniversary라고 불리는 시리즈도 내놓았다. 이것은 툼 레이더 1을 오늘날 스타일로 리메이크한 작품이다.

※ 8 (언더월드)

레전드의 명성을 계승한 후속 시리즈이다. 라라 크로프트가 배를 타고 세계 각지를 이동하면서 미션을 수행한다. 덕분에 레전드에 비해 잠수와 수영의 비중이 더 높다. 고전 복장은 사라졌으며, 그나마 이것과 가장 비슷한 복장은 태국 숲 미션에서 입는 까만 셔츠와 핫팬츠 복장이다.

라라 크로프트의 집이 난장판이 되는 건 옛날에 2의 맨 마지막 레벨 Home Sweet Home에서 침략자들과 총격전을 벌이는 게 전부였는데... 이번 언더월드에서는 첫 미션에서 집이 불바다가 되는 걸로 시작한다. 그래서 집에서 겨우 빠져나왔는데.. 이번엔 레전드 시절의 동료가 라라에게 무슨 원한이 생겼는지 권총을 쏴 댄다. 도대체 어찌 된 일일까? 다행히 원한을 살 만한 짓은 라라가 아니라 라라의 도플갱어의 소행으로 밝혀져 오해는 나중에 풀리지만... 스토리 전개가 재미있다.

도플갱어라.. 옛날에 1편에서 베이컨 라라가 있긴 했고.. 더 옛날 고전 게임 중엔 페르시아의 왕자 1에서도 왕자를 골탕먹이다가 나중에 합체하는 영혼 도플갱어가 있었다. 정말 별의 별 걸 게임에다 다 집어넣었다.

※ 9

자... 1996년 이래로 근 15년간 라라 크로프트를 쌍권총 찬 고고학자로 우려먹어 온 건 약발이 다한 모양이다.
크리스탈 다이나믹스는 5년간의 잠수 끝에, 이제 예전의 스토리를 다 뒤집어엎은 리부트작을 내놓았다. 이 작품은 1편과 마찬가지로 부제가 없이 이름이 그냥 툼 레이더이다.

라라는 더욱 어려졌으며, 도도한 특수요원 같은 이미지가 아니라 이제 막 생존술을 터득해야 하는 연약한 여고생/여대생 컨셉으로 바뀌었다. 어린 라라 떡밥은 지난 4편에서 잠시 등장한 적이 있지만 그걸 떠올려서도 물론 곤란하겠다. 예전의 라라가 인디아나 존스 컨셉이었다면, 지금의 라라의 위상은 거의 생존왕 로빈슨 크루소나 심지어 베어 그릴스-_-를 생각해도 될 것 같다.

9의 설정상 배경은 일본 열도 인근에 있는 어느 섬이다.
사실, 툼 레이더 시리즈에서 일본이 등장하는 것 자체가 코어 디자인 시절에는 없었고 크리스탈 다이나믹 때 레전드와 9에서 딱 두 번이었다. 코어 시절에 툼 레이더의 배경은 오히려 중국, 네팔, 티베트 같은 대륙이 더 자주 등장했었다.
중국과 일본 사이에 끼어서 한국은 영토가 너무 작기도 하고 게임이나 영화 같은 매체에 선뜻 등장하기에는 정치적 민감성도 크고 역시나 콩라인이라는 생각이 들었다.

그래픽이야 뭐, 피부에 핏자국이 묻고 각각의 머리카락들이 찰랑거리는 걸 볼 수 있을 정도로 사실에 가깝게 발전했다.
AOD에서 레전드로 넘어갈 때를 능가하는 엄청난 쇄신이 있었는데, 반응은 역시 좋은 편이다. 9 이후로 툼 레이더 시리즈가 과연 또 어떻게 변할지가 궁금해진다.

Posted by 사무엘

2013/12/07 08:32 2013/12/07 08:32
,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/906

롤스로이스 이야기

롤스로이스(Rolls Royce)는 잘 알다시피 영국의 명차로, 세계 톱클래스급의 간지를 자랑하는 대형 초호화 고급 승용차이다. 우리나라에서는 삼성 그룹 회장님이 마이바흐와 더불어 개인용으로 굴리는 차 중 하나라고 알려져 있다.
그리고 웹툰에서는 <입시명문 사립 정글고>에서 정 안봉 이사장의 자가용이 저 차라고 설정되어 있다..;; (220화 참조)

사용자 삽입 이미지

앞의 엔진룸이 매우 두툼한데 비해 헤드라이트는 모양이 작다. 수 년 전 모델은 헤드라이트 아래에 있는 미등이 원형이었는데 지금은 그게 길쭉한 직사각형 모양으로 바뀐 듯하다.
참고로 지금으로부터 한 30년쯤 전에는 차 모양이 이랬었다. 그때나 지금이나 라디에이터 그릴은 파르테논 신전을 형상화한 형태라고 한다.

사용자 삽입 이미지

롤스로이스는 공장에서 마구 찍어내고 재고를 쌓아 놓는 양산이 아니라 주문 생산만 되었으며, 그 공정도 다 장인 수작업이었다고 한다. 더구나 지금 당장 돈만 있다고 해서 아무에게나 차를 덥석 팔아 주는 것도 아니었고 안정적인 소득과 지위, 평판이 있는 고객에게만 팔았다.
그도 그럴 것이, 명차를 구입만 덥석 해 놓은 뒤에 차주가 쫄딱 망해 버리면 차는 처분해야 하는 처지가 되기 때문이다. 롤스로이스 같은 차가 겨우 중고 매물로 나도는 건 롤스로이스 제조사의 입장에서는 체면상 용납할 수 없는 모습인 것이다.

그 대신 한번 고객에게 판매하여 넘겨 준 차는 폐차하는 순간까지 제조사에서 끝까지 책임을 졌다고 한다. 그래서 롤스로이스가 소재로 등장하는 이런 예화가 있을 정도이다.

롤스로이스가 사막 한가운데에서 갑자기 퍼져 버려서 차주가 무슨 보험사 긴급 출동도 아닌 차량 제조사에다 연락을 했다... 그랬더니 제조사에서는 곧바로 헬기를 띄워 다른 멀쩡한 롤스로이스를 공수해 줬는데, 나중에 그에 대한 비용을 청구하기는커녕 그런 일 자체가 없었다며 완전히 입 싹 씻고 함구했다. “롤스로이스는 애초에 고장이 나지 않는다”고 말이다(고장을 공식적으로 고장이라고 취급하지 않으며, 따라서 고장 수리비 같은 개념도 없다).

물론 오늘날은 롤스로이스가 그 정도까지 극단적으로 도도하지는 않다. 이런 극소수 엘리트 고급차는 양산차에 비해 수지가 안 맞기 때문에 생존을 위해서는 언제까지 그런 고집만 부릴 수는 없다.
마치 오늘날은 슈퍼컴도 저가 양산형 CPU를 병렬로 연결해서 쓰지, 슈퍼컴만의 전용 아키텍처 같은 개념은 없어진 것과 비슷한 맥락으로 볼 수 있겠다. 과거의 수제형 슈퍼컴인 크레이 시리즈가 맥이 끊어진 것을 생각해 보시라.

그래서 요즘은 돈만 내면 누구라도 롤스로이스를 사서 굴릴 수 있다. 그래서 국내에서도 예전보다야 이 차 구경하기가 쉬워졌다. 게다가 롤스로이스 역시 뒷좌석만 고급화시키는 게 아니라, 차주가 직접 앞좌석에서 운전을 하는 오너 드라이빙 트렌드를 더욱 반영하는 쪽으로 변모하고 있다.

비록 그 정도로 격이 낮아졌다(?) 해도 롤스로이스의 가격은 여전히 최하 수억 원대로, 서울 강남의 아파트 한 채 내지 에쿠스 네댓 대(5000cc 최상위 모델 기준으로!) 이상 값은 충분히 하는 비싼 가격이다. 게다가 구매한 후에도 어마어마하게 깨질 세금과 기름값, 보험료 따위는 어찌 감당하려고?

롤스로이스는 리무진 형태가 아니라 4명밖에 못 타는 세단 주제에 차의 길이가 5.6m에 달한다. 1톤 트럭 특장차보다는 확실히 더 길고, 2.5톤 트럭의 길이와 얼추 비슷하거나 약간 더 짧다. 그러니 일반적인 승용차 자리엔 주차도 제대로 못 할 정도로 크다. 그 대신 뒷좌석에 탄 사람은 앉은 채로 다리를 쫙 들어서 뻗어도 될 정도로 공간이 완전 넉넉하다. 좌석에 앉은 채로 다리를 다 뻗을 수 있는 교통수단은 새마을호 특실, 비행기 1등석 등 극히 드물다.

엔진으로 말할 것 같으면 롤스로이스는 예로부터 V형 12기통 엔진에 6000~7000cc에 달하는 배기량을 자랑한다. 3000cc대의 6기통 엔진만 달아도 대형 승용차인데 롤스로이스는 그 크기의 두 배라는 뜻이다. 차의 무게는 1톤대는 당연히 아니고 공차 중량만 약 2.5톤가량. 여러 통계를 보면 공인 연비는 1리터에 거의 5~6km대라고 한다. 마티즈를 보고 출력이 약하다고 탓해서는 안 되듯, 롤스로이스는 확실히 경제성을 보고 굴려서는 안 되는 차임이 분명하다.. ㅎㅎ

롤스로이스는 전통적으로 엔진의 정확한 출력 한계를 함구하고 외부에 공식적으로 알리지 않았던 것 같다. 옛날에 자동차생활 잡지에서 취재를 할 때도 회사 관계자는 “충분히 큽니다”라고만 얼버무렸지 정확한 숫자 얘기를 안 했었다. 일종의 신비주의 전략인 걸까?
그래도 지금은 롤스로이스에 대한 베일이 예전에 비해서는 많이 벗겨졌다. 제원을 검색해 보면 460마력대의 최대 출력과 70kg대의 최대 토크가 곧장 뜬다. 최대 성능이 나오는 rpm은 여느 가솔린 엔진 차량과 별 차이가 나지 않는다.

다만, 롤스로이스의 계기판에는 엔진 회전수를 표시하는 통상적인 타코미터는 지금까지도 존재하지 않는다. 단지, 지금이 엔진 최대 성능의 몇 %를 뽑아 쓰고 있는지 백분율만이 표시되며 이것이 타코미터의 역할을 대신한다고 그런다. Windows Vista부터는 작업 관리자에서 메모리 사용량을 바이트 단위가 아니라 백분율 단위로 보여주는데, 마치 그런 걸 보는 것 같다.

롤스로이스는 뒷좌석 문이 앞좌석과 같은 앞쪽으로 열리는 게 아니라 뒷쪽으로 열리는 게 특이하다. 그리고 타이어 휠의 중앙에 있는 휠캡은 바퀴와 연결되어 있지 않기 때문에, 차가 움직일 때도 데굴데굴 같이 따라 구르지 않고 그대로 있다고 한다. 차 문과 트렁크는 버튼 하나만 눌러서 전동 개폐가 되며, 뒷좌석엔 좌석별 개인 비디오 장비와 우산 거치대도 따로 있다.

사용자 삽입 이미지

명차, 고급차 소리를 들으려면 단순히 내장재만 호화로운 게 아니라 닥치고 승차감이 좋아야 할 것이다.
승차감에 관한 한 롤스로이스는 정말 본좌급이라고 한다. 탑승자는 엔진음을 도무지 들을 수 없으며 주행 중에도 워낙 진동이 없어서 차가 가고 있는지도 모를 정도라니 말 다 했다.

오죽했으면 롤스로이스는 정숙성을 내세우기 위해 내부 모델명을 다 유령과 관계 있는 이름으로 정해 왔다. 그래서 고스트, 레이쓰, 팬텀 따위. 팬텀은 <오페라의 유령>에 나오는 그 단어이며, 고스트와 레이쓰는 스타크래프트 테란 유닛 이름이다. 공교롭게도 둘 다 클록킹 스킬이 있는 유닛이라는 공통점도 있고 말이다. 이들 중 '팬텀'이 바로 가장 비싼 최상위 기함급 모델이다.

끝으로, 명차 고급차의 앞부분에 관례적으로 상징처럼 붙어 있는 마스코트(hood ornament)를 생각해 보자.
현대 차 중에는 제네시스에도 없고 오로지 에쿠스에만 그런 마스코트 비스무리한 액세서리가 붙어 있다.
롤스로이스는 '환희의 여신상'이라고 불리는 마스코트가 달려 있으며 내력도 굉장히 길다. 공식 명칭은 the spirit of ecstasy.
 

사용자 삽입 이미지

그런데 그 이름을 보니까 역시나 이런 생각이 들었다. 진정한 spirit of ecstasy는 도로가 아닌 철도에 있지 않던가!
새마을호 Looking for you를 빼 놓고서 교통수단에서의 황홀감, 엑스터시를 논한다는 건 어불성설이고 불가능이다.

정말, 새마을호에서 Looking for you 음악이 흘러나왔던 건 한글 창제 내지 예수님의 부활의 복음에 버금가는 엄청난 사건이다. 혁명, 혁신, 그 어떤 단어로도 설명하기 어렵다.
하필 저 음악을 골라 넣은 그 당시의 철도청 고위 간부는 그야말로 심리학, HCI, 인지과학 분야의 어마어마한 전문가였을 것이다. 그야말로 사람을 낚기 위해, 미래의 철덕 양성을 위해 치밀한 음모를 꾸미면서 Looking for you를 선곡했을 것이다. 이 현상에 대해서는 정말 그렇게밖에 설명이 안 된다.

철도가 나의 삶을 어떻게 바꿨는지를 기회가 된다면 강연, 저술을 통해 널리 알리고 싶다.
아아, 롤스로이스 얘기를 하면서도 철도가 연결됐구나.. ㅋㅋㅋㅋㅋ
아무튼, 롤스로이스를 직접 타면서 열차와의 승차감을 상호 비교해 볼 기회가 있으면 좋겠다.

Posted by 사무엘

2013/12/04 08:30 2013/12/04 08:30
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/905

두벌식 한글 입력 방식에는 초성과 종성의 구분을 위해 도깨비불 현상이라는 게 존재한다.
이것은 <날개셋> 한글 입력기의 내부에서는 다음과 같은 조건에서 발생한다. 쉽게 말해 두벌식 종성 + 두벌식 중성 + 오토마타 0상태라고 요약된다.

1. 현재 입력 중인 글자의 직전 마지막 타가 두벌식 타입으로 입력되었고, 종성이 존재한다. (초성이나 중성은 있든 없든 무관)

2. 그 상태에서 초성이 없고 중성이 존재하는 두벌식 타입의 날개셋문자가 새로 입력되었다. (종성은 있든 없든 무관)
여기서 두벌식 타입이라 함은, 오리지널 두벌식 또는 6.7에서 새로 추가된 두벌식 종성 타입이 모두 해당한다.

3. 종성까지 입력된 그 상태에서 새로 입력된 날개셋문자에 대한 오토마타 수식 계산 결과가 0이다. 보통 종성의 경우 이어치기 오토마타 수식은 n -> C ? n : 0인데, 이때는 중성이 입력되면 C가 아닌 B가 nonzero가 되므로 어차피 계산 결과는 0으로 귀착된다.

4. 혹은 양수 상태라 해도 그 상태로는 중성 and/or 종성의 낱자 결합이 불가능해서 어차피 다음 글자로 넘어가야 한다. (implied zero state)


이런 상태에서 다음 글자의 조합이 계속될 때는, 그냥 넘어가는 게 아니라 앞 글자의 종성을 적당히 떼어서 다음 글자의 초성에 미리 넣어 주는 게 도깨비불 현상이 하는 일이다.
자음을 분할하는 방식은 그냥 마지막 한 타만 떼는 기본 동작이 있고, 그 외에 특수 도깨비불 규칙이나 초-종성 공유 낱자 결합 규칙이 관여하기도 한다. 이에 대한 더 자세한 설명은 프로그램 도움말을 참고하도록 하시고.

<날개셋> 한글 입력기는 여러 낱자를 한꺼번에 입력하는 걸 지원한다. 초-중성, 중-종성을 한꺼번에 입력하는 건 물론이고 지금 글자의 종성과 다음 글자의 초성을 곧바로 입력하는 것조차 가능하다.
이와 비슷한 맥락으로 두벌식에서는 '굴' 상태에서 ㅡ를 입력해서 '구르'까지 만드는 것뿐만 아니라 ㅡ+ㅁ을 한꺼번에 입력해서 '구름'을 바로 만들 수도 있다.

다만, 그게 오리지널 두벌식 타입으로는 가능한데, 비교적 따끈한 새 기능인 '두벌식 종성' 타입에서는 중성과 종성을 중첩 입력하는 게 제대로 지원되지 않았다. 이것이 지난 7.11 버전에서 개선되었다.
오리지널 두벌식은 비록 종성이라 하더라도 다음 글자로 넘어갈 때 종성이라는 정체성이 없어지고 초성과 완전히 다름없게 처리된다. 그렇기 때문에 그 상태에서 중성+종성을 한꺼번에 입력하는 것이 자연스럽게 가능하다.

그러나 두벌식 종성은... 비록 다음 글자로 넘어가서 모음과 결합함으로써 외형상 초성으로 바뀌긴 하지만, 원래 자신이 종성이었다는 본디 정체성은 변함없이 유지되어야 한다.
자신이 이미 불변 종성인데 중성과 더불어 또 다른 종성까지 한꺼번에 입력되면, 이것은 도깨비불을 의도하는 건지 아니면 그냥 중성과 종성의 평범한 결합이나 분리를 의미하는 건지 프로그램이 논리적으로 명확하게 결정해야 한다.

오리지널 두벌식 타입의 종성 ㅁ (H2|_M)을 조합하는 상태에서 두벌식 ㅏ+ㅂ(H2|EO|_B)을 배당해 주면 응당 '맙'으로 바뀐다. 단지 bksp를 한번 누르면 ㅁ이 이제는 종성이 아니라 초성으로 되어 있을 뿐이다.
그러나 두벌식 종성 타입의 종성 ㅁ (H2J|_M)에다가 같은 입력을 공급하면 '받침ㅁ/ㅏㅂ'이 되고 ㅏ+ㅂ을 따로 조합하는 상태가 된다.

그리고 받침 ㅁ 대신 받침 ㄹ을 조합하는 상태에서 같은 입력을 공급하면 그 중성과 종성이 지금 글자와 결합하여 ㅏ+ㄻ을 조합하는 상태가 된다. 이것은 받침 ㄹ이 오리지널 두벌식이든 두벌식 종성이든 결과가 동일하다. 왜 그럴까?

가장 큰 이유는 입력 날개셋문자에 중성뿐만 아니라 종성이 존재하기 때문이다. <날개셋> 한글 입력기가 제공하는 기본 오토마타들은 한 번에 초-중-종이든 낱자가 하나씩만 들어온다고 가정하고 설계되어 있다. 그래서 C ? n : 0 수식의 값은 nonzero가 되어, 도깨비불 현상 대신 지금 글자의 조합이 계속 유지되게 되는 것이다.

물론, ㄹ+ㅂ의 경우와는 달리 ㅁ+ㅂ은 결합이 성립하지 않는다. 세 성분 중 한 성분이라도 결합이 되지 않으면 결합은 실패하므로 도깨비불 현상이 발생한다. 이때 오리지널 두벌식은 ㅁ을 초성으로 넘겨 주기 때문에 '맙'을 성공적으로 만들어 준다. 그 반면 두벌식 종성은 ㅁ을 종성으로 처리한다. 그래서 도깨비불 현상 후에도 두 종성은 서로 충돌하며 결합이 실패한다.

이 경우 프로그램은 최종적으로 도깨비불 없이 ㅁ이 마치 세벌식 종성이었던 것처럼 변경 없이 놔 두고, ㅏ+ㅂ만 다음 글자로 따로 떼어 내 준다. 이번 7.11이 취한 정책이다. 예전에는 그냥 조합이 끊어져 버렸었다.

두벌식 종성으로도 중성+종성이 충돌 없이 도깨비불 현상 처리가 되려면, 도깨비불은 결합 실패로 인해 뒤늦게 발생하는 게 아니라 오토마타 수식 차원에서 0이 돌아옴으로써 이 타이밍 때 곧바로 발생해야 한다.
애초에 중성+종성 날개셋문자가 C ? n : 0 수식을 nonzero로 통과했던 게 화근이었다. 이 수식을 !B && C ? n : 0으로 바꿔 주면 문제가 해결된다. 종성이 한번 입력된 뒤부터는 오로지 종성만 받아들이도록 말이다.

이렇듯, 두벌식 종성에서 중성뿐만 아니라 중성+종성 복합 입력의 도깨비불 현상을 처리하기 위해서는 프로그램의 내부 알고리즘도 개선되어야 하고, 사용자의 설정도 미묘하게 바뀌어야 함을 알 수 있다. 한글 입력기 하나에도 오묘한 면모가 은근히 많다.

<날개셋> 한글 입력기는 13년 동안 개발되면서 버전이 무려 7.x대까지 올라갔다. 편집기나 외부 모듈의 외형은 엄청 옛날 버전이나 지금이나 거의 바뀐 게 없지만, 이 프로그램이 진짜로 꾸준히 바뀌고 있는 부분은 입력 엔진과 이를 제어하는 제어판 쪽이다. 이 프로그램은 그야말로 어떤 변칙적인 한글 입력 방식도 만들 수 있고 한자 변환이나 bksp 동작 같은 주변 기능까지 전부 세밀하게 제어 가능한 기술 통합형 엔진을 추구하고 있기 때문이다.

살짝만 스포일링을 하자면, 현재 구상하고 있는 것은 고급 입력 스키마와 고급 입력기이다.
글쇠를 길게 눌렀다 떼는 건, 여러 글쇠를 한꺼번에 누르는 것, 타이밍까지 세벌식 자판에 최적화해 주는 입력 스키마..
그리고 지금의 최종 변환 규칙 같은 것을 입력기 단위로도 적용하여 한글로 일본어나 구결을 곧바로 입력할 수 있는 문자 생성기. 한글과 비한글 문자를 조합 상태에서 끌어들일 수 있는 그런 시스템을 생각 중이다.

이 정도까지 만들어 놓으면 프로그램의 버전은 7.x대 중후반은 올라갈 수 있을 것이고, 그 뒤에 입력기 개발은 진짜로 반쯤 접은 채 다음 글꼴 연구를 시작할 수 있을 것 같다.
NLP 쪽의 연동이 없이 한국어와 무관하게 한글 자체만의 engineering도 할 일이 정말 많다.

Posted by 사무엘

2013/12/01 08:22 2013/12/01 08:22
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/904

16비트 시절에는 잘 알다시피 int 포함 machine word의 크기가 말 그대로 2바이트였다. 그리고 근거리/원거리 포인터가 존재했으며 복잡한 메모리 모델을 따져야 했다. 여기까지는 도스든 윈도든 공통이다. 그럼, 그 시절에 Windows 프로그래밍은 어떠했을까?

16비트 Windows는 잘 알다시피 모든 프로그램이 단일 스레드를 공유하면서 단일 메모리 주소 공간에서 실행되었다. 이런 열악한 환경에서 멀티태스킹을 구현하는 것은 결코 쉬운 일이 아니었다.
그래서 그 시절에는 콜백 함수를 운영체제에다 지정해 주는 것조차도 보통일이 아니었다. 오늘은 오랜만에 이 부분에 대해서 예전에 몰랐다가 최근에 본인이 추가로 알게 된 이야기를 좀 하겠다.

Windows API는 C언어 함수 형태로 만들어졌으니 그때는 지금으로 치면 C++ 가상 함수가 할 일을 함수 포인터로 다 처리하고 있었다. 윈도우 프로시저, 대화상자 프로시저 같은 것 말이다.

그런데 같은 프로그램이 두 번 중첩 실행되면, 코드는 동일하더라도 그 코드가 다루는 스택(데이터) context는 결국 프로세스별로 서로 달라질 수 있게 된다. 이를 어떻게 구분해야 할까?
32비트 이후부터는 가상 메모리 기술이 워낙 발달하여 그 context가 CPU 차원에서 알아서 따로 잘 처리된다. 그러나 16비트 기계엔 그런 개념이 없었다.

결국은, 실제 콜백 함수가 호출되기 전에 그 콜백 함수가 처리할 자신의 데이터 세그먼트 영역을 CPU 레지스터에다 써 주는 stub, 일명 썽킹(thunking) 코드를 소프트웨어적으로 별도로 만들어야 했다. C++로 치면, 클래스 멤버 함수 호출을 위해 this 포인터의 값을 EAX 레지스터에다 써 주는 것과 개념적으로 비슷하다.

우리가 만든 콜백 함수는 그 썽킹 함수를 통해서 호출해야 한 것이다. 운영체제에다가도 당연히 썽킹 함수를 알려 주고 말이다.
이것이 바로 16비트 시절 코드를 보면 밥 먹듯이 보이는 MakeProcInstance와 FreeProcInstance API 함수의 정체이다.
별도의 메모리를 추가로 할당해서 기계어 코드를 추가해 준 것이기 때문에, 메모리의 해제까지 해 줘야 한다.

대화상자 하나를 출력하더라도 아래처럼 말이다.

FARPROC pfnThunkProc = MakeProcInstance( (FARPROC)My_Actual_Dialog_Proc, hInstance); //내 대화상자 프로시저가 우리 인스턴스 핸들을 context로 동작하게 한다
DialogBox(hInstance, "Dialog_Resource_Name", hWndParent, (DLGPROC)pfnThunkProc );
FreeProcInstance(pfnThunkProc);

요즘 같으면 아래의 절차 하나만으로도 충분했을 텐데 말이다.

DialogBox(hInstance, "Dialog_Resource_Name", hWndParent, My_Actual_Dialog_Proc );

결국 My_Actual_Dialog_Proc라는 코드는 시스템 전체를 통틀어서 단 하나 유일하게 존재하겠지만,
이 프로그램을 여러 번 실행하더라도 각 프로그램들은 그 함수로부터 파생된 자신만의 고유한 콜백 함수를 통해서 대화상자를 호출하게 된다.

단, Windows 3.1에서는 함수명을 export해 주는 것만으로 이렇게 콜백 함수에 대한 썽킹을 매번 해 줘야 하는 번거로움을 생략할 수 있었다고 한다.

지금이야 프로그래밍에서 export 키워드라 하면, C++에서 템플릿의 선언과 정의를 번역 단위를 넘나들며 공유할 수 있게 하려 했던 비현실적인 흑역사 키워드일 뿐이다. Windows 플랫폼으로 문맥을 넓힐 경우, export는 클래스나 함수 심벌을 빌드되는 파일 차원에서 외부로 노출하여 GetProcAddress 함수로 노출이 되게 하는 속성 지정자이다. 비주얼 C++ 기준으로 __declspec(dllexport) 라고 쓴다.

지금이야 이런 export 속성은 말 그대로 DLL을 만들 때에나 쓰인다.
그러나 16비트 시절에는 EXE도 운영체제로부터 호출을 받아야 하는 콜백 함수를 넘겨 줄 목적으로 심벌 export를 즐겨 활용했었다.
export된 함수는 외부로부터 호출받는 게 당연시될 거라 여겨졌으므로, 링커와 운영체제가 자체적으로 데이터 세그먼트 보정을 하는 전처리 루틴을 추가해 줬다. 따라서 코드가 훨씬 더 깔끔해진다.

이런 이유로 인해 Windows 3.x 실행 파일들을 헥스 에디터로 들여다보면 대체로 ...WNDPROC, ...DLGPROC 같은 식으로 끝나는 웬 이상한 함수 이름들이 내부에 들어있는 걸 확인할 수 있다. 32비트 이후부터는 찾을 수 없는 관행이다. 자기 내부에서만 쓰는 콜백 함수들을 왜 저렇게 노출해 놓겠는가?

어떤 함수에 export 속성을 부여하기 위해서는 결국 C/C++ 언어에다가 없는 문법을 새로 추가하거나, 아니면 소스 코드는 그대로 놔 두고 컴파일러가 아닌 링커만이 인식하는 부가 정보 같은 게 주어져야 할 것이다. 전자는 소스 코드의 이식성을 떨어뜨린다는 부담이 있지만, 그래도 뒤끝이 없고 한 소스에 빌드에 필요한 모든 정보가 한데 모이니 편리하다. 그래서 그 당시에는 __export라는 비표준 MS 확장 키워드가 도입되었고, 이를 보통 EXPORT라는 매크로로 감싸서 사용하곤 했다. 이런 식으로 말이다.

long FAR PASCAL EXPORT MyWindowProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam); //그 당시 콜백 함수는 반드시 '파스칼' 방식이라는 함수 호출 규약대로 선언되어야만 했다.

한편 후자는 바로 그 이름도 유명한 DEF(모듈 정의) 파일이다.
지금도 DEF 파일이 안 쓰이는 건 아니지만 정말 DLL을 만들 때가 고작이다. 그 반면 16비트 시절에는 DEF가 훨씬 더 중요하게 쓰였던 모양이다.
export하는 콜백 함수뿐만 아니라 이 프로그램에 대한 소개문, 그리고 필요한 스택/힙 크기까지 정확하게 명시되어야 했다.

32비트 이후부터는 스택/힙이 부족하면, 프로그램 로딩 시간이 좀 더 걸릴 뿐이지 실시간으로 추가 할당하여 working set의 확장이 가능한 반면, 16비트 시절에는 메모리 양도 부족하고 또 한 주소 공간에서 여러 프로그램들이 뽁짝뽁짝 복잡하게 지내다 보니 이런 게 핀트가 어긋나면 프로그램 실행이 아예 불가능했을 것 같다.

옛날의 New Executable 포맷에는 자체적으로 name table이라 불리는 범용적인 문자열 테이블이 있었고, import/export하는 심벌 이름은 물론 이 EXE/DLL에 대한 간단한 소개문까지 그런 데에 들어갈 수 있었다.
지금으로 치면 버전 리소스에 들어가는 description과 유사한데 역할 중복인 셈이다. 그런 것까지 DEF 파일에다 명시해 줬었다.

자.. 설명을 들으니 어떤 생각이 드시는지?
16비트 Windows용 실행 파일을 분석하는 도구는 그리 충분치 않다. 일단 개발자들의 친구인 Dependency Walker가 이를 전혀 지원하지 않으며, 그나마 괜찮은 물건으로는 옛날에 Windows 98에 내장되어 있던 QuickView라는 프로그램이 적격이었다. 기본적인 사항은 다 잘 출력해 줬다.

16비트 시절에는 잘 알다시피 HINSTANCE는 각 프로그램의 데이터 세그먼트를 식별하는 번호표에 가까웠고, 개념적으로 지금 같은 포인터가 아니라 오늘날로 치면 오히려 프로세스를 식별하는 HANDLE처럼 널리 쓰였다. (비록 Close를 할 일은 없었겠지만 말이다) 게다가 EXE는 HINSTANCE, DLL은 HMODULE로 성격도 달랐다. 그래서 LoadLibrary 함수의 리턴값은 분명 HMODULE이다.

그때는 EXE/DLL로부터 리소스를 얻어 오는 과정도 정말 복잡했고 Load/Free 절차뿐만 아니라 lock/unlock까지 있었다. 메모리의 절대적인 양이 충분치 않기도 하고 가상 메모리 같은 시스템이 없던 관계로, 단편화를 방지하기 위해 평소에 즉각 쓰지 않는 메모리 블록은 재배치가 가능하게 놔 두는 관행이 더 보편적이었기 때문이다.

한편으로 그때는 시스템 전체에 영향을 끼치기가 쉬웠다. 특히 DLL이 이 분야에 관한 한 지존이었다.
DLL에서 선언한 전역변수는 모든 프로세스가 한데 공유되었으며, DLL이 실행하는 코드는 저런 EXE처럼 여러 컨텍스트에서 중첩 실행될 일이 없고 애초에 데이터 세그먼트 구분을 할 필요가 없었다!

그리고 16비트 시절엔 애초에 콜백 함수를 지정해 주는 과정이 처음부터 저렇게 번거로웠던 만큼, 시스템 전체의 동작 방식을 바꾸는 global 훅을 설치하더라도 훅 프로시저를 반드시 DLL에다가만 만들어야 한다는 제약이 없었다.
그럼에도 불구하고 16비트 시절은 32비트 시절보다 편한 것보다는 불편한게 더 많았다는 것이 부인할 수 없는 사실이다.

끝으로 여담.
이렇듯, 본인은 초등학교 아주 어릴 적부터 프로그래밍을 시작한 관계로, 그 시절에 대한 향수가 있고 '레트로'-_- 컴퓨팅 쪽으로 관심이 많다.
그런데, 그런 것에서조차도 양덕후들의 기상은 갑이다. the old new things 같은 블로그는 이 바닥의 지존이라고 봐야 하고, Windows 3.x로도 모자라서 2.x나 1.x용 프로그램을 만들었다고 인증샷 올리는 사람도 있다.

1985년에 출시된 Windows 1.x의 경우, VGA 카드조차도 없던 시절에 만들어진 관계로 가장 좋은 비디오 모드가 640*350 해상도짜리 EGA이다. 그런데 그걸 640*480 VGA 내지 심지어 800*600 SVGA에서 동작하게 드라이버 패치를 만든 사람도 있다.

참고로 Windows 1.x 정도 되면.. 프로그램 개발을 위한 컴파일러는 그야말로 1986년에 나온 MS C 4.x를 써야 하고, 얘는 함수 호출 인자도 ANSI 스타일이 아니라 K&R 스타일만 써야 하는 진짜 무지막지한 골동품이라고 한다. Windows 1.x는 파스칼로 개발되었다고 들었는데 그래도 그때부터 C 형태의 SDK는 있었던 모양이다.

Posted by 사무엘

2013/11/28 08:29 2013/11/28 08:29
, ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/903

Windows GUI 프로그램은 잘 알다시피 메시지로 운영체제와 의사소통을 하는 게 핵심이다. “이 창은 응답이 없음. 프로그램을 강제 종료할까요?”라고 판단을 하는 기준은 바로 n초 이상 GetMessage/PeekMessage 함수를 호출하지 않는 프로그램이다.

CreateWindowEx로 창을 하나 만든 뒤 메시지 loop은 GetMessage로 메시지를 받아서 그걸 DispatchMessage로 넘겨 주기를 반복하는 형태이다.
비록 메시지를 윈도우 프로시저로 전달하는 함수로는 CallWindowProc도 있겠지만, Dispatch는 타이머 메시지를 윈도우 프로시저 대신 lParam에 지정된 타이머 프로시저로 직통 전달하는 일까지 한다. 즉, 하는 일의 범위가 더 넓다. 그리고 Call은 메시지 큐를 처리하는 게 아니라 윈도우 프로시저의 서브클래싱용으로 쓰라고 있는 물건이다.

메시지를 Dispatch하기 전에 보통은 translation이라고 불리는 전처리 과정이 필요하다. TranslateMessage 함수가 그 일을 하는데, 키보드 WM_(SYS)KEYDOWN / WM_(SYS)KEYUP 메시지로부터 WM_(SYS)CHAR 메시지를 추가로 생성해 준다. 현재 설정되어 있는 키보드 드라이버의 정보를 토대로 가상 키코드를 해석하여, 그 키가 나타내는 입력 문자를 알려 준다는 뜻이다. 유럽 문자를 입력할 때 쓰이는 dead key를 처리하는 것도 translator의 몫이다.

이때 새로 생성된 메시지는 이 윈도우를 구동하는 스레드의 메시지 큐에 추가로 예약되어서 다음번 GetMessage 함수 호출 때 걸려 나온다. 인자로 전달된 MSG 구조체의 내용이 지금 당장 바뀌지는 않는다. 사실, 문자를 입력받는 윈도우가 아니라면 translation은 꼭 필요하지는 않은지도 모르겠다.

그런데 메시지를 굳이 윈도우 프로시저로 dispatch하기 전에, 응용 프로그램이나 다른 API 함수가 곧장 메시지를 가로채야 하는 경우는 굉장히 많이 있다. 액셀러레이터 단축키를 처리하는 TranslateAccelerator, 그리고 Ctrl+F6 같은 MDI 공용 단축키를 처리하는 TranslateMDISysAccel이 그 예이다.
TranslateMessage는 그런 관문들을 모두 통과한 메시지가 dispatch 직전에 최종적으로 거치는 관문일 뿐이다.

이런 절차가 한둘이 아니기 때문에 MFC의 CWnd 클래스는 그런 코드를 적절히 넣으라고 PreTranslateMessage라는 가상 함수까지 마련해 놓았다. 이 함수에서 키보드 메시지를 조작하여 엔터가 눌려도 대화상자가 종료되지 않게 하는 것 정도는 프로그래밍 초보 시절에 다들 해 보았을 것이다. 그러고 보니 이렇게 dispatch 전단계에서 조작하는 메시지는 다들 키보드 관련 메시지인 경우가 많다.

그리고 이런 식으로 전용 함수를 통해 미리 처리되는 또 다른 대표적인 메시지들은 바로.. 대화상자 관련 메시지들이다.
이걸 담당하는 물건으로는 IsDialogMessage라는 함수가 있다.

물론, MFC의 경우 저 함수를 포함해 위에서 언급한 각종 전처리, translation을 이미 알아서 전부 호출하며 메시지 loop과 심지어 WinMain 함수까지 몽땅 라이브러리 내부에다 짱박아 뒀기 때문에 MFC를 사용하는 프로그램이라면 자신이 직접 그런 함수를 또 호출해야 할 일은 거의 없을 것이다.
그러나 운영체제와 응용 프로그램 사이에 어떤 메커니즘으로 통신이 오가는지 알고는 있을 필요가 있다.

대화상자 안에서 사용자가 tab 키를 눌렀을 때 컨트롤들의 포커스가 바뀌는 것, Alt+알파벳을 눌렀을 때 특정 컨트롤로 바로 이동하는 것, 엔터를 눌렀을 때 '확인' 종료가 되고 ESC를 눌렀을 때 '취소' 종료가 되는 것 따위는 대화상자의 윈도우 프로시저의 관할이 아니다. 윈도우 프로시저가 메시지를 받기 전에 저 IsDialogMessage 함수가 일찌감치 처리한다. 심지어 default 버튼의 테두리를 굵게 칠하는 것조차도 저 함수가 담당한다.
물론 그런 전처리는 대화상자가 독단적으로 하는 게 아니라 자기 아래의 컨트롤에게 WM_GETDLGCODE 메시지를 보내서 컨트롤이 원하는 키보드 처리 양상에 대해서 물어는 보고서 한다.

DialogBox 함수로 modal 대화상자를 만들었다면야 그 함수가 자체적으로 메시지 loop을 돌면서 이런 처리를 모두 알아서 해 준다. 그러나 문제는 modeless 대화상자이다. modeless 대화상자는 그 창을 만든 응용 프로그램의 main 메시지 loop를 같이 쓰면서 돌아가기 때문에 그 loop에서 IsDialogMessage 함수를 호출하는 로직이 추가되어야 한다.

대화상자에서 엔터 키가 들어왔을 때의 동작에 대해서는 살짝 주의할 점이 있다.
일반 버튼은 자체적으로 엔터 키를 처리한다. 그래서 다른 모든 컨트롤들을 사용 중일 때 엔터를 누르면 '확인' 버튼이 눌러진 것으로 처리되고 대화상자가 곧장 종료되지만, '취소'나 '적용' 같은 다른 버튼에 키보드 포커스가 가 있을 때 엔터를 누르면 해당 버튼이 눌러진 것으로 처리된다.

즉, 엔터는 space와 사실상 동일하게 처리된다. 차이가 있다면 space는 key down 시점 때는 버튼이 눌러진 모습만 표시되고 key up 시점일 때 명령이 먹히는 반면, 엔터는 key down 때 곧바로 먹힌다는 것이 전부이다.
다만, 대화상자 안에 또 대화상자가 자식 윈도우로 생성되었고 자식 대화상자 안에 있는 버튼을 space가 아닌 엔터 키로 눌렀을 경우,

WM_COMMAND+BN_CLICKED 메시지는 자식 대화상자의 윈도우 프로시저로 전달되는 게 아니라 겉의 최상위 대화상자의 윈도우 프로시저로 전달된다.
그래서 그 버튼을 엔터 키로 누른 동작은 일반적으로 제대로 인식되지 않거나 최악의 경우 오동작--부모 대화상자와 자식 대화상자에 우연히 ID값이 같은 버튼이 공존한다면!--까지 하게 된다.

왜 그렇게 동작하는지 정확한 이유는 난 모르겠다.
물론, 일반적으로 엔터를 눌러서 대화상자를 닫는 메시지 자체는 자식 대화상자가 아니라 최상위 대화상자에다가 전달되는 게 개념상 올바르다.
하지만 그런 default 버튼이 아니라 일반 버튼을 엔터 키로 누르는 동작까지 왜 엉뚱한 곳으로 가는 걸까?

대화상자 안에 또 대화상자를 만드는 건 운영체제가 제공하는 프로퍼티 시트가 대표적인 예인데 그건 운영체제가 알아서 버튼의 처리를 잘 해 주는 듯하다. <날개셋> 타자연습 같은 프로그램에서 탭 안에 있는 버튼을 엔터로 눌러 보면 잘 안식된다.

그러나 자체적으로 자식 대화상자들을 표시하는 <날개셋> 한글 입력기의 제어판, 그리고 VMware, 반디집, EditPlus 등의 유수의 소프트웨어들이 제공하는 종합 설정 대화상자들을 살펴보면, 자식 대화상자 안의 버튼은 space만 인식되지 엔터 키로 인식되지 않는 걸 알 수 있다. (아래 그림에서 왼쪽)
단, Microsoft Visual Studio는 예외. '도구-옵션' 대화상자의 내부에 표시된 자식 대화상자들의 버튼은 엔터 키로 잘 인식된다. (아래 그림에서 오른쪽) 치명적인 버그 같은 부류는 아니지만 운영체제의 특성 때문에 발생하는 좀 이상한 면모라 하겠다.

사용자 삽입 이미지사용자 삽입 이미지
WM_COMMAND+BN_CLICKED 메시지는 lParam으로 이 메시지를 보낸 컨트롤의 윈도우 핸들을 친절하게 일일이 알려 준다.
그렇기 때문에 그런 버튼을 누르는 걸 인식하려면, 최상위 대화상자의 윈도우 프로시저에서 lParam을 검사하여 이것이 자식 대화상자가 아니라 내 대화상자의 버튼이 보낸 메시지가 맞는지를 확인해야 한다. 아니라면 그 버튼의 진짜 부모 윈도우--GetParent 함수로 확인--에다가 메시지를 도로 보내 버리면 된다.

Visual Studio의 옵션 대화상자도 Spy++로 동작을 확인해 보면, WM_COMMAND+BN_CLICKED 메시지가 두 번 가는 걸 보니 그런 식으로 동작하는 듯하다.

Posted by 사무엘

2013/11/22 08:36 2013/11/22 08:36
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/901

자고로 프로그래밍 언어는 구문이나 예약어 같은 원론적인 것만 정의하는 게 아니라, 그 문법을 토대로 프로그램의 작성에 필요한 각종 기본적인 자료구조와 알고리즘, 입출력 기능들도 라이브러리 형태로 제공한다. 후자의 디자인도 프로그래밍 언어의 정체성에서 차지하는 비중이 매우 크다.

예를 들어 printf, qsort, malloc, fopen 같은 함수를 사용했다면 그 함수들의 몸체도 당연히 프로그램 어딘가에 빌드되어 들어가야 한다. 아니, 애초에 main 함수나 WinMain 함수가 호출되고 각종 인자를 전해 주는 것조차도 그냥 되는 일이 아니다. 이런 것은 C/C++ 언어가 제공하는 런타임 라이브러리가 해 주는 일이며, 우리가 빌드하는 프로그램에 어떤 형태로든 코드가 링크되어 들어간다.

C/C++은 그나마 기계어 코드를 생성하는 언어이기 때문에 그런 런타임의 오버헤드가 작은 편이다. 그러나 비주얼 베이직(닷넷 이전의 클래식)이라든가 델파이는 RAD 툴이고 언어가 직접 GUI까지 다 커버하다 보니 언어가 제공하는 런타임 오버헤드가 크다.
자바나 C#으로 넘어가면 런타임이 단순 코드 오버헤드로 감당 가능한 정도가 아니라 아예 가상 기계 수준이다. 프로그램이 기계어 코드로 번역되지도 않으며, garbage collector까지 있다.

그렇게 프로그래머의 편의를 많이 봐 주는 언어들에 비해 '작은 언어'를 추구하는 C/C++은 도스나 16비트 윈도 시절에는 런타임 라이브러리가 static 링크되는 게 당연시되곤 했다. 오버헤드 자체가 수천~만 몇천 바이트밖에 되지 않을 정도로 매우 작은 편이고, 저수준 언어인 C/C++은 당연히 standalone 바이너리를 만들어야지 무슨 다른 고급 언어처럼 런타임 EXE/DLL에 의존하는 바이너리를 생성한다는 건 좀 안 어울려 보이는 게 사실이었다.

그래서 C/C++로 개발된 EXE 파일은 내부를 들여다보면 대체로, 링크되어 들어간 런타임 라이브러리의 이름과 개발사를 나타내는 문자열이 들어있었다. 볼랜드나 마이크로소프트 따위. 그래서 이 프로그램이 어느 컴파일러로 만들어졌는지를 얼추 짐작도 할 수 있었다.

C 런타임 라이브러리도 static이 아닌 DLL 형태로 제공하려는 발상은 Windows의 경우 아무래도 32비트 NT의 개발과 함께 시작된 듯하다. 그래서 윈도 NT 3.1의 바이너리를 차용해서 개발되었다는 과거의 Win32s를 보면 crtdll.dll이라는 파일이 있는데 이것이 운영체제가 기본 제공하는 프로그램들이 공용하는 C 런타임 DLL인 듯하다. 즉, 메모장, 문서 작성기, 그림판 등의 프로그램들이 호출하는 sprintf 같은 C 함수들의 구현체가 거기에 담겨 있다는 뜻이다.

재미있게도 윈도 NT의 경우, kernel32보다도 먼저 로딩되는 ntdll.dll이 내부적으로 또 각종 C 함수들의 구현체를 제공한다. 커널이 제대로 로딩되기도 전에 실행되는 프로그램이 공유하는 C 함수들이라고 한다.

과거의 윈도 9x의 프로그램들은 비록 32비트이지만 운영체제가 자체적으로 공유하는 C 런타임 DLL은 없다.
다만, 윈도 NT 3.x 이후로 비주얼 C++이 32비트 개발툴로 자리매김하면서 이 개발툴의 버전에 따라 msvcrt20.dll, msvcrt40.dll이 제공되기 시작했고 이들은 윈도 9x에서도 기본 내장되었다. 비록 같은 C 런타임이지만 버전별로 미묘한 차이가 있는 모양이다.

그러다가 1996년에 출시된 비주얼 C++ 4.2부터 C 런타임 DLL은 더 변경을 안 하려는지 파일 이름에서 버전 숫자가 빠지고, 그 이름도 유명한 msvcrt.dll이라는 이름이 정착되어 버전 6까지 쭉 이어졌다.
이 이름은 비주얼 C++ 4.2 ~ 6이 생성한 바이너리가 사용하는 C 런타임인 동시에, NT 계열에서는 기존의 crtdll을 대신하여 운영체제의 기본 제공 프로그램들이 공유하는 C 런타임의 명칭으로도 정착했다.

그리고 9x 계열도 윈도 98부터는 mfc42.dll과 더불어 msvcrt.dll을 기본 제공하기 시작했다.
NT와는 달리 운영체제가 msvcrt.dll을 직접 쓰지는 않지만, 비주얼 C++로 개발된 프로그램들을 바로 실행 가능하게 하기 위해서 편의상 제공한 것이다. 과거의 유물인 msvcrt40.dll은 msvcrt.dll로 곧바로 redirection된다.
그 무렵부터는 오피스, IE 같은 다른 MS 사의 프로그램들도 C 런타임을 msvcrt로 동적 링크하는 것이 관행으로 슬슬 정착해 나갔다.

그렇게 윈도, VC, 오피스가 똑같이 msvcrt를 사용하는 구도가 한동안 이어졌는데..
21세기에 비주얼 C++이 닷넷으로 넘어가면서 그 균형은 다시 깨졌다.
C 런타임 라이브러리도 한 번만 만들고 끝이 아니라 계속 버전업이 되어야 한 관계로 msvcr70, msvcr71 같은 DLL이 계속해서 만들어진 것이다. 결국, 비주얼 C++ 최신 버전으로 개발한 프로그램은 C 라이브러리를 동적 링크할 경우, DLL 파일을 배포해야 하는 문제를 새로 떠안게 되었다.

이것이 비주얼 C++ 2005/2008에서는 더욱 복잡해졌다. C 라이브러리를 side-by-side assembly 방식으로 배포하는 것만 허용했기 때문이다. 쉽게 말해, 레거시 공용 컨트롤과 윈도 XP의 비주얼 스타일이 적용된 공용 컨트롤(comctl32.dll)을 구분할 때 쓰이는 그 기술을 채택했다는 뜻이다.

그래서 msvcr80/msvcr90.dll은 윈도 시스템 디렉터리에만 달랑 넣는다고 프로그램이 실행되지 않는다. 이들 DLL은 DLLMain 함수에서 자신이 로딩된 방식을 체크하여 이상한 점이 있으면 고의로 false를 되돌린다. 그렇기 때문에 이들은 반드시 Visual C++ 재배포 런타임 패키지를 통해 정식으로 설치되어야 한다.

런타임 DLL간의 버전 충돌을 막기 위한 조치라고 하나 이것은 한편으로 너무 불편하고 번거로운 조치였다. C 라이브러리가 좀 업데이트돼 봤자 메이저도 아니고 마이너 버전끼리 뭐가 그렇게 버전 충돌이 있을 거라고..;; 나중에는 여러 프로그램들을 설치하다 보면 같은 비주얼 C++ 2005나 2008끼리도 빌드 넘버가 다른 놈들의 재배포 패키지가 설치된 게 막 난립하는 걸 볼 수 있을 정도였다. 가관이 따로 없다. 당장 내 컴에 있는 것만 해도 2008 기준으로 9.0.32729까지는 똑같은데 마지막 숫자가 4148, 6161, 17, 4974... 무려 네 개나 있다.

개발자들로부터도 불편하다고 원성이 빗발쳤다. MS Office나 Visual Studio급으로 수십 개의 모듈로 개발된 초대형 소프트웨어를 개발하는 게 아니라면, 꼬우면 그냥 C 라이브러리를 static 링크해서 쓰라는 소리다.
그래서 비주얼 C++ 2010부터는 C 라이브러리 DLL은 다시 윈도 시스템 디렉터리에다가만 달랑 집어넣는 형태로 되돌아갔다. 다시 옛날의 msvcrt20, msvrt40, msvcr71처럼 됐다는 뜻이다.

윈도 비스타 타임라인부터는 운영체제, VC, 오피스 사이의 관계가 뭔가 규칙성이 있게 바뀌었다.
오피스는 언제나 최신 비주얼 C++ 컴파일러가 제공하는 C 라이브러리 DLL을 사용하며, 운영체제가 사용하는 msvcrt도 이름만 그럴 뿐 사실상 직전의 최신 비주얼 C++가 사용하던 C 라이브러리 DLL과 거의 같다.

그래서 오피스 2007은 msvcr80을 사용하며, 오피스 2010은 비주얼 C++ 2008에 맞춰진 msvcr90을 사용한다. C 런타임 DLL도 꾸준히 버전업되고 바뀌는 물건이라는 것을 드디어 의식한 것이다. 비스타 이전에 윈도 2000/XP의 EXE/DLL들은 헤더에 기록된 링커 버전을 보면, 서로 사용하는 컴파일러가 다르기라도 했는지 통상적인 비주얼 C++이 생성하는 EXE/DLL에 기록되는 버전과 일치하지 않았었다. 9x는 더 말할 필요도 없고.

그럼에도 불구하고 msvcrt는 운영체제 내부 내지 디바이스 드라이버만이 사용하고, 비주얼 C++로 개발된 여타 응용 프로그램들은 언제나 msvcr##을 사용하게 용도가 확실하게 이원화되었다.
그래서 심지어는 똑같이 MS에서 개발한 한글 IME임에도 불구하고 Windows\IME 디렉터리에 있는 운영체제 보급용은 msvcrt를 사용하고, 한글 MS Office가 공급하는 IME는 Program Files에 있고 msvcr##을 사용한다. 둘은 거의 똑같은 프로그램인데도 말이다.

이것이 Windows와 C 런타임 라이브러리 DLL에 얽힌 복잡한 역사이다. 여담이지만 C 라이브러리 중에서 VC++ 2003이 제공한 msvcr71은 Windows 95를 지원한 마지막 버전이다. 2005가 제공한 msvcr80부터는 일부 보안 관련 코드에서 IsDebuggerPresent라는 API 함수를 곧장 사용함으로써 Windows 95에 대한 지원을 중단하였으며, 최하 98은 돼야 동작한다. VC++ 2008부터는 9x 계열에 대한 지원 자체가 중단됐고 말이다.

자, 여기서 질문이 생긴다. 그럼 C++은 사정이 어떠할까?

C보다 생산성이 훨씬 더 뛰어난 C++로 주류 프로그래밍 언어가 바뀌면서 표준 C++ 라이브러리에 대한 동적 링크의 필요성도 응당 제기되었다. 그러나 이것은 C 라이브러리보다는 시기적으로 훨씬 더 늦게 진행되었기 때문에 가장 먼저 등장하는 비주얼 C++의 DLL 이름이 msvcp50과 msvcp60이다. 즉, 비주얼 C++ 5.0 이전에는 선택의 여지 없이 static 링크만 있었다는 뜻이다.

더구나 이것은 전적으로 비주얼 C++ 소속이지, 운영체제가 따로 C++ 라이브러리 DLL을 제공하지는 않는다. MS에서 만들어진 제품들 중에 msvcp##를 사용하는 물건은 비주얼 C++ IDE와 컴파일러 그 자신밖에 없다.

C++ 라이브러리는 대부분 템플릿 형태이기 때문에 알고리즘 뼈대는 내 바이너리에 static 링크되는 게 사실이다. 그러나 그렇게 덧붙여지는 라이브러리 코드가 별도로 호출하는 함수 중에 DLL에 의존도를 지니는 게 있다. cout 객체 하나만 사용해도, 그리고 STL 컨테이너 하나만 사용한 뒤 빌드해 봐도 형체를 알 수 없는 이상한 메모리 관리 쪽 함수에 대한 의존도가 생긴다.

그래도 C 라이브러리 DLL은 사용한 함수에 따라서 printf, qsort 등 링크되는 심벌이 명확한 반면
C++ 라이브러리는 내부 구조는 이거 뭐 전적으로 컴파일러가 정하기 나름이고 외부에서 이들 심벌을 불러다 쓰기란 사실상 불가능하다는 큰 차이가 있다.

<날개셋> 한글 입력기는 C++ 라이브러리를 사용하지 않으며, 빌드 후에 생성된 바이너리를 인위로 수정하여 msvcr## 대신 강제로 운영체제의 msvcrt를 사용해서 동작하게 만들어져 있다.

Posted by 사무엘

2013/11/19 08:29 2013/11/19 08:29
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/900

※ 기독교가 세상적으로는 도저히 성공할 수 없고, 인간의 머리로 만들어 낼 수 없는 종교(?)인 이유

1. 여타 종교들을 한 치의 타협도 없이 단호히 배척하여 '따'를 자처한다.

2. '원수를 사랑하라' 등, 육신적인 가식· 위선으로는 도저히 흉내조차 낼 수 없는 요구를 한다.

3. '부활' 같은 도저히 믿어지지 않는 황당한 걸 가르친다. 사실 교주가 부활해서 무덤이 비어 있다고 가르치는 종교도 여기 말고는 없고..;;

4. 잘 믿으면 부귀영화 성공은커녕, 박해를 받을 거라고 대놓고 버젓이 예고한다.


이건 내가 정리한 건 아니고 L.E.맥스웰의 <우리는 십자가에 못 박힌 채 태어났다> 책에 있는 내용이다.

세상엔 복음에 반감을 갖고 있는 똑똑하고 '이성· 합리적인' 무신론자들이 엄청 많다. 인간이 만들어 낸 종교들의 각종 폐단들을 지적하면서 자신들은 반종교가 아니라 비종교를 표방한다고 그러는데...
그에 대한 나의 생각/반론은 이러하다.

1. 글쎄, 리처드 도킨스가 나처럼 있지도 않은 신을 무작정 쫓아다니는 불쌍한 무지렁이들을 너무 사랑한 나머지, 무신론을 설파하기 위해 나 대신 죽어 주기까지 했으면(그리고 부활까지 했으면).. 그 사람 주장도 좀 고려해 보겠다.

2. 내가 왜 태어났고 인생의 목적이 무엇이고 죽어서는 어떻게 되고, 절대적인 선과 악이 무엇인지.. 이런 것에 대한 관념이 없는 사람치고 건전하게 잘 살고 있는 사람은 난 못 봤다. 절대자 없이 인간이 과연 그렇게 자기끼리 질서정연하게 잘 살고 있었을까? 글쎄? 난 그렇게 보이지 않는데. ㅎㅎ

3. 위에도 잘 설명돼 있지만, 인간이 자기 머리로 종교를 만들었다면 나 같으면 단언하건대 철도교를 만들었지 기독교 같은 건 미쳤다고 만들겠나. 절대로 안 한다.

Looking for you 같은 마약 같은 황홀경 음악이 있고, 수인선 복선 전철과 동해남부선 광역전철의 부활 신앙이 있고, 국토 사랑 정신과 희락과 화평과 사랑이 넘치는 철도교를 믿으면 되지 뭐 하러 쓸데없이 사탄 마귀, 지옥, 심판, 휴거 같은 걸 논하겠나? 왜 골치아프게 영적 전투를 치러야 하나? 나도 종교에 대해서 도킨스만치는 그래도 생각을 해 보고 예수 믿는다. ㅎㅎ

이성· 합리적인 무신론자라면 이에 대한 진지한 고찰이 필요하다. 세상에 인간들이 당신보다 똑똑하지 못해서, 머리 쓸 줄 몰라서 기독교가 안 없어지고 있는 게 아니다.

※ 크리스천이 겪는 슬픔과 고통

크리스천이 아무리 죽음에 대한 준비가 다 된 사람이라 해도, 이와 별개로 죽는 과정은 충분히 두려울 수 있다. 그리고 고통 때문에 울부짖을 수 있다. 예수쟁이도 총 맞으면 죽는 보통 사람이며, 고문 당하면 괴로운 건 마찬가지다.
또한 크리스천이 아무리 부활의 소망이 있고 하늘나라에서 다시 만날 걸 기약한다 해도, 사랑하는 사람이 당장 죽었는데 당연히 슬퍼서 울 수 있다. 성경에도 그 유명한 Jesus wept가 있다.

그러나 크리스천은 뭔가 천추의 한이 맺히고 서러워서, 혹은 증오심과 원망이 맺혀서 “이놈/이것만 아니었으면 내가 요 모양 요 꼴 되지는 않았을 텐데” 같은 식으로 울 일은... 영적으로 잘 성장했다면 없는 게 정상일 것이다.

물론, 크리스천 중에 여전히 그런 것 때문에 세상 비관하고 앓는 소리 하는 사람이 있더라도 무작정 남을 판단하고 정죄하지는 말아야 할 것이다.
기독교 신앙이 모 아니면 도 식인 것은 진리이나, 그렇다고 해서 “이러지 못할 거면 걍 때려치워라” 같은 돌직구는 정말 신중하게 날려야 한다. 그런 걸로 기껏 자라려던 남의 믿음을 완전히 무너뜨리고 실족하게 해서는 안 된다.

Posted by 사무엘

2013/11/16 08:30 2013/11/16 08:30
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/899

내 주변의 크리스천 지인 중에는 크리스천의 ‘인격, 행실’에 대해서 정말 많은 관심을 갖고 거기에 심각하게 고민하는 분이 있다. 왜 있잖은가, “크리스천이 되기에 앞서 인간부터 돼야지!” 같은 식의 주장 말이다.

크리스천으로의 사명감과 책임감이 얼마나 투철했으면, 세상이 복음을 받아들이지 않는 이유에 대한 해석도 다르다. 세상이 악하고 마음이 강퍅해져서가 아니라, 기존 교회들이 병신같이 선교 행위를 하고 예수 간증을 잃었기 때문이라는 점을 아주 강조한다. 성경의 표현을 빌리자면, 고후 4:4보다 롬 2:23-24를 선호한다는 뜻.

이런 성향이 종교적으로 극단으로 치달으면, 결국 선행이 드러나 있지 않은 사람은 구원받은 것도 아니라는 식으로 예정론을 잘못 적용한 주권 구원 내지 행위 구원 쪽으로 빠진다.

그리고 이런 성향이 정치적으로 극단으로 치달으면, 오로지 대형 교회들을 흠집 내고 잡아먹기 위해서 불신자들하고까지 손잡을 지경인 진보 성향으로 바뀐다.

뭐, 저 정도의 잘못된 극단으로 빠진 게 아니라면, 그런 성향에도 일리가 있는 지적이 분명 있다. 예전에 같은 교인들로부터 몰상식적이고 무례하고 육신적인 언행 때문에 상처를 많이 입었거나, 아니면 기성 교회의 비성경적인 관행에 트라우마가 생긴 경험 때문에 그런 성향이 깊어진 게 아닐까 싶다. 이해는 한다.

그런데 이 이슈에 대한 나의 내면의 생각을 좀 말해 볼까?

첫째, 기독교의 교리가 “크리스천이 되기에 앞서 인간부터 돼야지!”였다면, 나는 크리스천이 절대로 못 됐으며 인간은 더욱 못 됐을 것 같다. 나 자신부터 옛날에는 인간성· 사회성, 인격 따위가 지금보다 훨씬 더 거칠고 더러웠다. 그나마 예수 믿고 나서 같은 성도들끼리 부대끼면서 아주 차츰차츰 개선된 것이다.

둘째, 난 기독교의 탈을 쓴 종북이 존재한다는 사실 하나만으로도 ‘교인’들의 행실에 대한 기대 따위는 진작에 안드로메다에다 보내고 왔다. 이건 무슨 대형 교회의 부정부패나 몰상식한 선교 행위 같은 것하고는 아예 레벨이 다르고 차원이 다른 악행이지 않은가! (걔네들은 애초에 예수님을 내 구주로 제대로 영접도 안 한 사람일 거라고 개인적으로 생각은 한다)

종북이 예수 믿고 구원받으면 그 구원에 감격하고 지금 처한 여건을 주신 하나님께 감사하고, 국가와 민족에 자부심과 애착이 생기고, 우리에게 있는 자유가 얼마나 소중한지와 지금 진짜 인권 유린을 당하고 있는 곳이 어딘지를 다 자동으로 깨달을 줄 알았는데, 종북이 예수 믿으면 그냥 구원받은 종북이더라!

이게 겨우 일개 인격이나 성품 따위하고 비교가 가능한 문제일까? 성경 말씀이 그런 사람들의 사상 하나조차 제대로 바로 이끌어 주지 못한 걸까?

그러니 난 애초에 혼의 구원과 영원이 걸린 종교 문제는 전적으로 핵심 사상인 교리의 우월성을 보고 선택했지, 주변 사람들의 말단 행실이나 평판 같은 건 “개별적인 case by case”일 뿐, 진지한 고려 대상으로 여기지 않았다.

정말로 비성경적인 수위가 도를 넘어서는 잘못된 목사나 잘못된 교회가 있으면, 당신 혼자 그 교회를 조용히 탈퇴하면 된다. 대한민국은 자유가 있는 좋은 나라이다. 당신이 그런다고 해서 당신을 고문하거나 수용소로 보낼 사람은 아무도 없다.

그리고 도저히 구원받았을 것 같지 않은 막장 성도라면.. 성경에 규정된 대로 징계하고 쫓아내든가, 아니면 그냥 교제 끊고 이교도나 세리 대하듯이 최소한의 예의와 격식만 차려서 대하면 된다. 그 사람이 만에 하나 진짜 구원받은 게 맞다면 나중에 하늘나라에서나 오해가 풀리기를 바라는 수밖에.

영적 전쟁터는 원래 그 어떤 희한한 짓 험한 꼴이 벌어진다 해도 이상할 게 없는 바닥 아닌가?
크리스천이 행동이 병신이었던 게 어디 한두 번이었으며, 그게 그렇게까지 납득이 안 되는 일일까? 난 그렇게 생각하지 않는다.

그런 현실을 지나치게 탓하고 비관하는 건, 예수님이고 성경이고 복음이고, 게다가 그냥 성경도 아니고 신줏단지 같이 떠받드는 KJV 성경조차도 현실에서는 아무짝에도 쓸모가 없다고, 크리스천이라는 사람이 자기 입으로 영적 전투의 패배를 스스로 인정하는 꼴이나 다름없다.

그냥 구원받는 것하고 예수님의 제자의 삶을 사는 건 별개의 문제이다. 예수 믿는 사람이라고 해서 다 자기와 같은 사상을 지니고 자기처럼 성장하고 자기처럼 행동할 거라고는 절대로 기대하지 말지어다.

교회는 영적 전투 수행을 위한 무슨 소수정예 특수부대가 아니다. 이 세상에 아무 결점이 없는 완벽한 성도들로만 이뤄진 교회가 존재한다면, 당신이 그 교회에 가입하는 순간부터 무결성이 깨진다는 심정으로 교회 생활을 하면 된다.

구원과 행실 사이의 관계 문제는 하나도 어려울 것 없다.
보이지 않는 믿음을 보이는 행위로 나타내야 한다는 내 안의 성령님의 압박이 느껴진다면, 남 신경 쓰지 말고 당신이나 잘하면서 남에게 좋은 본을 보이면 된다.

잘못하고 있는 교회 지체를 성경으로 일깨우면서 권면하는 것 이상으로,
누구 다른 사람 때문에 복음이 실추되고 있다는 식으로 피해의식은 어떤 경우에도 가질 필요 없다.

Posted by 사무엘

2013/11/08 08:17 2013/11/08 08:17
,
Response
No Trackback , 3 Comments
RSS :
http://moogi.new21.org/tc/rss/response/896

<날개셋> 한글 입력기 7.11

<날개셋> 한글 입력기 7.1은 다음 7.3이나 7.4가 나올 때까지 오랫동안 안정적으로 쓰일 수 있을 것 같았으나.. 시간이 흐르고 보니 부득이 소규모 7.11 업그레이드가 3주 남짓한 시간 만에 나오게 됐다.

이 버전에서는 외부 모듈이 다음과 같이 대대적으로 개선됐다.

1.
TSF B급 프로그램(메모장 같은)에서 '호환용 한글 자모'를 쓰고도 옛한글이 전혀 입력되지 않던 문제를 해결했다.
이것은 직전 버전인 7.1에만 있던 버그이다. TSF A급 프로그램에서 두벌식 도깨비불 현상에 의해 옛한글이 곧장 시작되었을 때 조합이 끊어지던 문제를 해결했었는데 불행히도 그게 TSF B급 프로그램에서는 심각한 오동작을 유발하고 있었다.
결국 A급일 때와 B급일 때를 인위적으로 나누어 따로 동작하게 하는 방식으로 두 토끼를 모두 잡았다.

한글 IME 개발이라는 게 이렇게 지저분하고 어렵다.
모호한 스펙 때문에 IME를 사용하는 구현체별로 문서화되지 않은 예측 불가능한 동작이 너무 많으며, 이것을 하나하나 다 맞춰 줘야 한다. 한 프로그램에다 동작을 맞춰 주면 다른 프로그램에서 오동작하는 게 왕왕 있다.

물론, TSF B급 프로그램에서는 '호환용 한글 자모'를 써서 첫 조합은 한 글자 길이를 보장시킨 뒤에 제한적으로 옛한글 입력이 가능하지만, 두벌식의 경우 도깨비불 현상으로 옛한글이 곧장 시작되었을 때 조합이 끊어지는 건 어쩔 수 없다.
이런 문제 때문에 MS에서는 논란의 여지를 없애기 위해 자기가 개발한 옛한글 입력기는 오로지 TSF A급 프로그램에서만 동작하게 제약을 걸었다. 지원하는 글자판 자체도 두벌식밖에 없기도 하니 말이다.

2.
Windows 8에는 일명 메트로라고 불리기도 한 Modern UI가 있고 거기서 일부 앱은 권한이 극도로 제한된 상태로 동작한다. 본인은 입력기를 테스트할 때 날씨(Weather) 앱을 실행한 뒤 장소 추가 대화상자를 꺼내 보곤 했다. 그런데 테스트를 하려면 인터넷 연결을 꼭 해야 해서 개인적으로 무척 성가시고 불편함을 느꼈다.

어쨌든, 그런 제한 모드에서는 IME가 자기 입력 설정을 불러올 수조차 없어서 어쩔 수 없이 설치 직후의 기본 입력 설정으로 동작한다. 이때는 입력 설정이 제대로 로딩되어 있는 다른 프로그램--데스크톱 앱 또는 권한 제약이 없는 Modern 앱--을 미리 실행해 놓고, 제약이 걸린 앱도 덩달아 같이 실행해야 입력 설정이 동기화된다. 이것은 기술적으로 더 어쩔 수 없는 귀결이다.

그런데 이번 버전에서는, 제약이 걸린 앱을 먼저 실행하고 제약이 없는 앱(데스크톱 앱 포함)을 “나중에” 뒤늦게 실행한 뒤, 제약이 걸린 앱으로 되돌아오더라도 입력 설정이 동기화되도록 프로그램을 개선했다. Modern 앱에서 <날개셋> 한글 입력기의 사용 편의성이 더 향상될 것으로 기대한다.
당연히.. 제약이 없는 앱을 실행한 뒤엔 그 프로그램에서 <날개셋> 한글 입력기 외부 모듈을 로딩도 하고서 돌아와야 한다.

3.
그리고 드디어, 드디어...
외부 모듈도 현대 한글뿐만 아니라 옛한글까지 bksp 낱자 단위로 지우기 및 달라붙기 같은 기능이 동작 가능해졌다. 물론 TSF A급 프로그램 한정이고, 단순한 한글 자모 나열이 아니라 filler까지 정규화가 잘 된 글자에만 한해서 말이다.

이 기능을 구현하는 기반은 사실 지난 6.5 버전 때 이미 다져져 있었다. 그러나 실제로 제공되지는 못하고 봉인되어 있었는데,
그건 TSF A급 프로그램에서 존재하던 옛한글 조합 관련 버그 때문이었다.
그래서 그 버그를 고치면서 봉인돼 있던 기능까지 다시 살펴보게 되었고, 결국 이것도 덩달아 구현에 성공한 것이다. 만세! 한글 처리 기술의 진보를 이뤘다.

외부 모듈과 관련된 위의 세 이슈가 중요한 것이고 다음은 사소한 변화 사항들이다.

(1) 이 프로그램은 입력 설정이 없는 경우 MS 한글 IME의 설정이 어떻냐에 따라서 세벌식이나 두벌식을 자동으로 가져온다. 그런데 0번 말고 2번에 배당되는 기본 한글 글자판은 언제나 세벌식 옛한글로 고정이었다.
이제부터는 0번이 두벌식으로 맞춰지면 2번의 옛한글 글자판도 두벌식 옛한글로 지정되게 바뀌었다.

(2) 영문 about 대화상자에 layerd 오타-_-를 잡았다. 2004년에 나온 3.0의 영문 GUI 때부터 지금까지 줄곧 존재했던 오타이다. -_-;;; 오타가 그 단어의 실제 발음과 직관적으로 잘 대응하는 편인 관계로, 오타가 있는 줄 9년이 넘게 꿈에도 모르고 있었다.
그리고 편집기의 편집 화면 옵션 대화상자에 있던 '이동줄'이라는 단어도 '스크롤 막대'라고 MS 표준 번역 용어로 바꿨다. 이 역시 해당 옵션이 처음으로 추가되었던 3.0 버전 때부터 썼던 단어인데 드디어 변경.. ㅎㅎ
사실 '이동줄'은 한글 윈도 3.1의 도움말에 있던 용어이다.

(3) 지난 7.1에서는 앞으로 '고급 입력 스키마'를 재개발할 것을 염두에 두고 기존 '동시 입력 스키마'를 제거했다.
그랬는데, 이 입력 스키마를 사용하고 있는 분으로부터 요청을 받고 그걸 다시 원상복귀시켰다.
다만 이제 이 입력 스키마는 이름 뒤에 '임시용'이라는 단서가 붙었으며 영문 명칭에는 아예 legacy라는 단어가 추가되었다.

(4) 화면 키보드 입력 도구가 지금까지 크기가 너무 작은 게 흠이었는데, 작게-중간-크게 3단계로 크기를 조절하는 기능을 추가했다.
물론 글쇠 자체는 여전히 16*16 고정된 크기로만 찍히지만, '중간'으로만 해도 고해상도 화면에서는 글쇠배열이 훨씬 더 시원스럽게 보일 것이다.

(5) 두벌식 종성으로 입력한 종성으로 중성+종성을 한꺼번에 입력하여 도깨비불 현상을 일으킬 때, 내부 처리가 좀 더 정확하게 되도록 입력 엔진을 개선했다. 내부 사연이 좀 복잡하므로 이에 대한 자세한 내역은 별도의 글로 다루도록 하겠다..

(6) <날개셋> 편집기의 찾기 기능을 다소 손질하여, 찾는 문자열 영역이 한글을 이루는 글자의 중간에 걸치는 것은 정상적인 찾기 결과로 인정하지 않게 했다. 그래서 'ㅎㆍ'를 검색하면 정말로 'ㅎㆍ'만 걸리지, 'ㅎㆍㄴ'이 걸리지는 않게 했다. 'ㅎㆍ'로 'ㅎㆍㄴ'을 찾는 건 '한글 낱자 단위로' 옵션을 켰을 때에만 가능하다.

업데이트가 귀찮으시더라도, 부디 최신 버전을 사용하여 프로그램의 원 저자가 의도한 모든 기능을 마음껏 활용하시기 바란다.
아울러, 10월 한 달간 두 분이 후원금을 보내 주셔서 누적 후원자 수는 세 분이 되었다. 태어나서 이런 걸 연달아 받아 보는 경험이 거의 처음이어서 감개무량하고 보람을 느낀다. 감사드리는 바이다.

Posted by 사무엘

2013/11/04 19:23 2013/11/04 19:23
Response
No Trackback , 14 Comments
RSS :
http://moogi.new21.org/tc/rss/response/895

« Previous : 1 : ... 138 : 139 : 140 : 141 : 142 : 143 : 144 : 145 : 146 : ... 221 : Next »

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

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

Site Stats

Total hits:
3075958
Today:
1745
Yesterday:
1671