들어가는 말

  • 프로젝트 단위: 말 그대로 한 개의 결과물을 생성하는 것을 목표로 하는 한 비주얼 C++ 프로젝트당 하나씩만 생성되는 파일이다. 리소스는 특수한 경우가 아니면 보통 프로젝트마다 하나만 있기 때문에, per-프로젝트인 것으로 간주된다.
  • configuration 단위: 한 프로젝트 내에서 debug나 release 별로 따로 생성되고, x86이나 x64 같은 플랫폼별로 다 따로 생성되는 파일이다.
  • 소스 단위: 번역 단위(translation unit)별로 다 제각각 생성되는 파일이다. configuration에도 물론 종속적이며, 다 따로 생성된다.

※ 프로젝트를 열면 생성되는 것

APS (프로젝트 단위)

전통적인 윈도우용 실행 파일(EXE/DLL)을 빌드하기 위해서는 잘 알다시피 컴파일된 코드뿐만 아니라 리소스도 같이 들어가는데, 그 리소스를 명시해 주는 '리소스의 소스', 일명 리소스 스크립트는 바로 *.rc 파일이다. 그리고 *.rc와 일반 소스 코드 *.cpp는 resource.h에 정의된 심벌들을 통해 동일 리소스를 식별하게 된다.

그런데 매번 일반 텍스트 형태로 된 rc 파일을 resource.h와 엮어서 파싱하자니 불편하다. 리소스 스크립트는 텍스트 에디터를 써서 사람이 손으로 편집한 뒤 컴파일하기에는 적합하지만, IDE 같은 소프트웨어가 자동으로 다뤄 주기에는 비효율적인 구조인 것이다.

그래서 비주얼 C++은 리소스 ID까지 포함하여 리소스 스크립트의 바이너리 representation을 따로 만들어 두고 지낸다. APS 파일이 존재하고 이게 RC나 H 같은 텍스트 소스에 비해 outdate되지 않았다면, 프로그램은 매번 텍스트를 파싱하는 게 아니라 APS 파일을 곧장 읽는다.

비주얼 C++에서 프로젝트를 처음으로 열어서 리소스 뷰로 리소스들을 처음 열람하면, 프로그램이 리소스 컴파일러를 가동해서 뭘 파싱하면서 시간이 오래 걸린다. 하지만 다음에 열 때부터는 리소스가 곧바로 빨리 열리는데, 이것이 바로 APS 파일 덕분이다.

CLW (프로젝트 단위) deprecated

이것은 비주얼 C++ 4~6 사이에, 그 이름도 유명한 MFC Class Wizard (클래스 마법사) 때문에 도입되었던 부가정보 파일이다.
MFC 클래스에서 파생된 윈도우 클래스 같은 데서 메시지 핸들러(마법사의 용도가 굳이 메시지 핸들러뿐인 건 아니지만)를 추가하려면 일단 헤더 파일에 afx_msg void OnXXXX가 추가되어야 하고, 메시지 맵 BEGIN_MESSAGE_MAP() 밑에 ON_MESSAGE_***가 추가되어야 하고, 끝으로 소스 파일에 해당 멤버 함수의 몸체가 추가되어야 한다.

그런데 이 일을 모든 소스 코드를 일일이 파싱하면서 추가 지점을 찾아서 하기란 여간 어려운 일이 아닐 수 없다.
C++은 선언 따로, 정의 따로이고(C#, 자바는 그렇지 않다) 정의부가 반드시 어느 번역 단위에 존재해야 한다는 제약이 전혀 없다. FM대로 하는 건 15년 전의 펜티엄 컴으로는 무리였다.

그래서 편의상 클래스의 선언부와 메시지 맵의 주변엔 클래스 마법사만이 식별하는 문자열이 들어간 주석이 있고, 클래스 마법사는 그 구간을 대상으로만 작업을 신속하게 했다. 그리고 그걸로도 부족해서 클래스 마법사의 파싱 결과가 CLW 파일에 들어갔다. 식별자 주석을 건드리면 클래스 마법사가 제대로 동작하지 못했다.

21세기에 나온 비주얼 C++ .NET과 그 이후 버전은 CLW 파일을 만들거나 사용하지 않으며, 클래스 마법사 주석 없이도 멤버 함수나 핸들러의 추가를 그럭저럭 정확하게 해낸다. 사실 클래스 마법사 자체가 비주얼 C++ 200x에서는 사라졌다가 2010에서부터 부활했다.

※ 빌드하면 생성되는 것

OBJ (소스 파일 단위)

비주얼뿐만이 아니라 전세계의 어느 C/C++ 컴파일러를 돌리더라도, 소스 코드를 컴파일하면 이것이 매 소스, 즉 번역 단위별로 생성된다. 소스 코드를 번역한 기계어 코드가 obj 파일 포맷에 맞게 들어있는데, 때로는 기계어 코드뿐만 아니라 각종 디버깅 정보와 링크 때 링커가 참고할 만한 메타데이터도 잔뜩 가미된다.

static library라고 불리는 LIB는 별개의 포맷이 아니라, 그냥 여러 번역 단위들을 컴파일한 obj들의 컬렉션일 뿐이다. obj를 단순히 lib로 합치기만 할 때는 링크 에러가 나지 않는다(즉, 선언된 심벌들이 반드시 정의되어야 할 필요가 없다.)

RES (configuration 단위)

리소스 스크립트를 컴파일하여 생성되는 결과물이다. 리소스 스크립트의 바이너리 최적화 형태인 APS와 무엇이 다르냐고 물으신다면, 차이가 적지 않다.
APS는 리소스 스크립트 파일의 표현 형태만 메모리 친화적으로 바꾼 것이기 때문에 ID_RADIO1 같은 상수 명칭의 문자열 원형과 심지어 조건부 컴파일을 위한 스펙까지 다 보존되어 있으며, 참조하는 비트맵 같은 데이터 파일도 파일명 형태로 존재한다. APS 파일로부터 RC 파일과 resource.h 파일을 복원해 낼 수 있다.

그러나 RES는 상수는 다 숫자로 박히고 참조하는 데이터 파일도 모두 내부에 embed되었으며, 이 상태 그대로 실행 파일에다 링크되어 들어가기만 하면 되는 상태인 것이다.

PCH (configuration 단위)

pre-compiled header인 stdafx.h와, 이에 대응하는 번역 단위인 stdafx.cpp를 컴파일하여 얻은 각종 컴파일 context들, 즉 함수와 클래스 선언, #define 명칭 등등을 바이너리 형태로 보관하고 있는 파일들이다. 이게 있으면 stdafx.h를 인클루드하라는 명령은 실제 헤더 파일을 파싱하는 게 아니라 그냥 pch 파일을 참조하는 것으로 대체된다.

컴파일러의 버전이 올라가고 각종 플랫폼 SDK의 크기가 커질수록 이 파일의 크기도 야금야금 커져 왔다. 이거 없이는 C++은 살인적인 인클루드질 때문에, 느린 빌드 속도를 도저히 감당할 수 없다.

PDB (configuration 단위)

빌드 결과 만들어진 EXE/DLL에서 기계어 코드의 어느 부분이 어느 소스의 몇째 줄에 대응하는지(소스 코드 자체는 없고 소스의 경로만), 이 함수에서 이 지역변수의 이름이 무엇인지 등을 담고 있는 디버그 정보 데이터베이스이다.

디버그 모드가 아니라 릴리스 모드로 빌드한 최적화된 실행 파일이라도, PDB 파일을 참조하게 하는 최소한의 정보만이라도 남겨 두면, 나중에 프로그램이 뻗는다거나 할 때 소스상으로 최소한 어느 지점에서 뻗었는지를 개발자의 컴에서 확인해 볼 수 있다. 개발자의 컴엔 직전에 이 바이너리를 빌드하면서 같이 생성된 PDB 파일이 존재하기 때문이다.

ILK (configuration 단위. 대개 디버그 빌드에서만)

증분 링크(incremental link)를 위한 context 정보가 들어있다.
이것은 프로그램의 빌드 속도를 올리기 위한 테크닉이다. 매번 링크를 처음부터 일일이 새로 하는 게 아니라, 처음에 빌드할 때 바이너리를 좀 여분을 둬서 듬성듬성 큼직하게 만들어 두고, 다음부터는 바뀐 obj 파일 내용만 기존 바이너리의 자기 지점에다 대체하는 방식으로 빌드를 신속하게 끝낸다. 혹은 뒷부분에다가 새로운 빌드 내용을 계속 추가해 넣기만 하고, 예전 빌드 내용을 무효화시키는 방법도 쓴다.

요즘 디버그 빌드가 단순히 최적화를 안 한 것 이상으로 릴리스 빌드보다 빌드된 바이너리의 크기가 유난히 큰 이유가 여기에 있다. 게다가 Edit and continue 기능을 위해서도 여분 공간이 필요하기 때문에 크기가 커질 수밖에 없다. 디버그 빌드 바이너리를 바이너리 에디터로 들여다보면, 온통 0xCC (no op)으로 도배가 되고 내부가 헐렁함을 알 수 있다.

MS 오피스도 2007 이전 버전을 보면 방대한 워드/엑셀 문서를 편집할 때 바뀐 내용만 짤막하게 저장하는 옵션이 있었다. 그게 일종의 증분 저장 기능이다. 지금은 그게 보안상으로 문제가 되기도 하고 문서 파일 포맷이 크게 바뀌었으며, 굳이 증분 저장을 안 써도 될 정도로 PC 성능이 좋아졌다고 여겨져서 그런 기능이 없어졌지만 말이다.
증분 링크는 보통은 디버그 모드 빌드에서만 쓰인다.

VC???.idb (configuration 단위. 대개 디버그 빌드에서만)

ILK 파일과 마찬가지로 빌드 시간의 단축을 위해 존재하는 파일이다.
디버그 모드로 빌드를 해 보면, 헤더 파일이 바뀌었더라도 해당 헤더를 인클루드하는 cpp 파일들이 전부 리빌드되는 게 아니라 가끔 'Skipping.. (no relevant changes detected)'이러면서 넘어가는 파일도 있다. 그리고 대체로 이런 컴파일러의 판단이 맞다. 헤더 파일을 고쳤더라도 클래스의 선언부 같은 크리티컬한 부분이 아니라 그냥 주석 같은 trivial한 부분만 바뀌었기 때문에 굳이 리빌드가 필요하지 않다는 걸 어떻게 판단할까?

컴파일러가 제공하는 Enable Minimal Rebuild (/Gm) 옵션 때문에 가능하다. 이게 지정되면 빌드 과정에서 프로젝트명이 아니라 고정된 이름의 의존성 판단용 부가정보 파일이 생긴다. ???는 해당 비주얼 C++의 버전이다. 2008의 경우 90, 2010의 경우 100.

정리하자면, 빌드와 함께 생성되는 파일들 중, 실제로 링커에 의해 EXE/DLL 따위를 만드는 데 동원되는 파일은 OBJ, RES이다.
빌드 시간을 단축시키는 데 쓰이는 파일은 PCH, IDB, ILK이다.
PDB는 프로그램의 문제 추적을 위해 추후에 쓰이는 파일이다.

※ 편의 기능 + 빌드

SBR (소스 파일 단위), BSC (configuration 단위)

자, 이 파일은 빌드를 하면 생성되지만, 프로그램의 빌드나 디버깅을 위해서 반드시 생성해야만 하는 파일은 아니다.
방대한 양의 소스 코드를 컴파일하고 나면 컴파일러는 그 소스 코드의 모든 내부 구조에 대해서 알게 된다. 그걸 알아야만 기계어 코드를 생성할 수 있을 테니까.

컴파일이 끝났다고 그 정보를 그냥 버리는 건 아깝기 때문에, 일정한 파일 포맷을 제정하여 이것을 소스 코드에 대한 browsing에 활용할 수 있다. 가령, 이 클래스 멤버 함수의 정의는 어디에 있고, 이 함수가 호출하는 함수와, 이 함수를 호출하는 함수와의 그래프 관계는 어떻고 하는 것 말이다. 소스 코드가 텍스트라면, browse 정보는 정교하게 짜여진 색인인 셈이다.

이 개념과 파일 포맷은 비주얼 C++의 아주 초창기 시절부터 존재했다.
그리고 비주얼 C++은 버전 6까지는, 프로젝트를 빌드할 때 browse 정보도 같이 이렇게 덤으로 빌드되게 해서 browse 정보를 조회하는 기능을 갖추고 있었다. SBR과 BSC의 관계는 C/C++ 소스 코드에서 OBJ와 EXE의 관계와 정확히 같다. 한 번역 단위를 컴파일하면 한 SBR이 생겼고, SBR들을 뭉쳐서 BSC 파일이 생성되었다.

물론 이렇게 하면 빌드 시간이 더욱 길어졌고, 굳이 browse 기능을 쓰지 않는 사람도 있었기 때문에 이 기능은 철저히 선택사항이었다. 그리고 닷넷부터는 이 정보를 만들지 않더라도, 뒤에서 설명할 인텔리센스 정보만으로 IDE 차원에서 browse 대체 기능을 갖추기 시작했다.

※ 인텔리센스

NCB (프로젝트 단위) deprecated

sbr/bsc보다는 나중에, 시기적으로는 clw와 비슷한 타이밍(비주얼 C++ 4)에 만들어진 파일 포맷이다.
바야흐로 비주얼 C++ 4에서는 최초로 Class View라는 게 생겨서 프로젝트에 존재하는 모든 클래스와 멤버, 전역 변수/함수들을 표시하는 기능이 추가되었다. ncb는 browse 정보를 만드는 것만치 소스를 심도 있게 일일이 다 까 보지는 않고, 그보다는 단순하게 코드를 파싱하여 해당 기능을 빠르게 구현하는 데 필요한 부가 정보를 저장했다.

Class View가 도입되었던 초창기에는 소스 코드를 매번 빌드는 아니어도 저장을 해야만 컨텐츠가 업데이트되었다. 그나마 저장하지 않고도 실시간으로 업데이트가 되기 시작한 건 VC 6부터이다.
그리고 VC 6에서는 잘 알다시피 초보적인 수준의 인텔리센스 및 멤버 표시/자동 완성 기능이 구현되었고, 그 정보 역시 ncb 파일에다 저장되었다. 당연히 같은 프로젝트를 만들어도 ncb 파일의 크기는 더욱 커지게 됐다.

비주얼 C++이 버전업되면서 인텔리센스는 성능이 더욱 강력해졌다. 바로 닷넷에서부터는 #define 심벌이 추가로 인텔리센스의 혜택을 입기 시작했으며 템플릿도 제대로 지원되기 시작했다. 오동작 빈도도 더욱 줄었다.

그러나 이 모든 것은 여전히 10년 전의 ncb 파일을 기반으로, 진품이 아닌 가짜 parser를 임기응변 식으로 확장하면서 구현된 것이기 때문에, 어느 수준 이상의 정확도를 낼 수는 없었으며 복잡한 C++ 문법의 모든 것을 수용하는 데에도 근본적인 한계가 있었다.

가령, 클래스 멤버 함수의 선언이 복잡한 #define 매크로 안에 숨어 있으면 Class View에 이것이 제대로 나타나지 않았다. 갑자기 빌드 configuration이나 플랫폼을 확 바꿔 버리면 인텔리센스가 멘붕을 일으켰으며, 복잡한 조건부 컴파일 구간에 숨어 있는 코드도 인텔리센스가 상황 파악을 제대로 못 하는 경우가 많았다. 멘붕의 정도가 심하면 인텔리센스가 아예 동작을 멎어 버리기도 했기 때문에, 수시로 ncb 파일을 지우고 다시 만들어 주는 건 필수 작업이었다.

SDF (프로젝트 단위), IPCH (configuration 단위)

위와 같은 기존 ncb 기반 인텔리센스의 문제를 극복하고자 비주얼 C++ 2010은 안 그래도 C++11 때문에 문법도 대폭 확장해야 하는데 이 기회에 인텔리센스 엔진을 완전히 갈아 엎었다. SQL server compact edition이라는 전문 DB 엔진을 쓰기 시작했다.

2010부터는 가짜 parser가 아니라 진짜 컴파일러와 똑같은 수준의 parser가 background에서 모든 소스와 헤더 파일들을 일일이 파싱하여 실시간으로 심벌 정보를 고친다. 정확한 문맥을 파악하고 있기 때문에 100% 정확한 인텔리센스가 제공되며, 예전처럼 좀 오동작한다 싶어도 잠시 기다려서 파싱 정보가 갱신되고 나면 곧장 똑바로 동작하기 시작한다.

다만, 이런 첨단 기술이 공짜로 된 건 아니기 때문에, 어지간한 C++ 프로젝트는 이제 인텔리센스 파일만 수십~100수십 MB씩 디스크를 쳐묵쳐묵 하는 대가를 감수해야 한다. 어느 프로젝트를 열든지 동일하게 공유되는 MFC나 플랫폼 SDK의 인텔리센스 정보는 여러 프로젝트들이 한데 공유만 할 수 있어도 인텔리센스의 용량이 크게 줄어들 텐데, 무척 아쉽다.

그래도 비주얼 C++ 제작진에서 일말의 배려를 했다 싶은 대목은, 인텔리센스 DB 파일이 생성되는 곳만 한 곳에 따로 대체 지정이 가능하다는 것이다. 프로젝트-옵션이 아니라 도구-옵션에서 “텍스트 편집기-C/C++/고급”으로 가면 fallback location을 지정하는 옵션이 있으며, 이것만 해 주면 비주얼 C++로 만드는 모든 프로젝트들의 인텔리센스 DB는 거기 아래로 한데 모이게 된다.

이렇듯, 비주얼 C++ IDE나 컴파일러가 생성하는 보조 파일들의 용도와 배경에 대해서 공부하면 C/C++ 언어의 특성을 알 수 있고, 프로그래밍 언어에 대한 비판적인 안목, 그리고 언어의 비효율을 극복하고 조금이라도 개발 도구의 생산성을 올리기 위해 해당 제작진이 어떤 꼼수를 동원했는지에 대해서도 알 수 있다.

Posted by 사무엘

2012/10/16 08:30 2012/10/16 08:30
,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/744

요즘 코딩 잡설

1.

<날개셋> 한글 입력기의 개발 작업은 단순히 새로운 기능을 구현하거나 알려진 버그를 수정하는 것 말고도, 멀쩡히 동작하는 기능의 내부 구현 형태를 바꾸는 리팩터링도 무시 못 할 비중을 차지하고 있다.

이미 지금도 문제가 없긴 하지만, 열기-닫기 내지 할당-해제 같은 패턴은 어지간하면 클래스의 생성자와 소멸자가 알아서 하게 바꿔서 리소스 누수(leak)를 컴파일러 차원에서 원천적으로 차단하고 있으며,
최근에는 비주얼 C++ 2010으로 갈아탄 덕분에 지저분한 임시 #define 함수들을 지역 변수 형태의 람다 함수로 교체하는 재미가 쏠쏠하다. 예를 들어 이런 것 말이다.

BEFORE
#define PickNumber(e) ((e)[1] ? wcstol((e), &f, 16): *(e))

AFTER
auto PickNumber = [&f](PCWSTR e) -> int { return e[1] ? wcstol(e, &f, 16): *e; };

별도의 함수로 추가하기에는 너무 지엽적이고 한 함수 안에서만 잠깐 쓰고 마는 반복적인 루틴들은 람다로 싸 주는 게 딱이다. type-safety가 보장되고, scope도 엄격하게 정해지고, 이 루틴을 매번 인라인으로 expand할지 아니면 그냥 함수 호출로 처리해서 코드 크기를 줄일지를 컴파일러가 좀 더 유연하게 판단할 수 있기 때문에 아주 좋다.

예전에는 C++에 대해서 C with classes라고 배웠겠지만, 이제는 C++은 C with classes라고만 정의하기에는 설명에 누락된 요소가 너무 많아졌다.
람다 함수를 전역 변수로 선언하는 건 문법적으로 불가능하지는 않으나, 그럴 바에야 그냥 재래식 형태의 함수를 하나 선언하고 말지 아무런 특별한 의미가 없을 것이다.

2.

그런데, 이렇게 리소스 누수를 막기 위해서 노력하고 있지만 구조체에다 함께 넘어온 핸들이나 메모리 포인터는 그것만 따로 클래스의 소멸자가 자동으로 소멸하게 할 수 없으니 구조적으로 여전히 누수 위험이 존재한다.

예를 들어 CreateProcess 함수는 실행 후 해당 프로세스에 대한 핸들을 PROCESS_INFORMATION 구조체에다가 되돌려 준다. 이 값을 이용해서 프로그램은 자신이 새로 실행한 프로그램이 실행이 끝날 때까지 기다린다거나 할 수 있다. 하지만 실행된 프로세스가 종료되더라도 그 프로세스를 가리키던 핸들은 해제되지 않는다. 호스트 프로그램이 핸들을 닫아 줘야만 완전히 해제된다.

CreateProcess 함수를 자주 쓴다면 핸들 해제까지 모든 작업을 자동화해 주는 클래스를 만들어서 쓰는 게 효과적이다. 사실, 이 함수는 받아들이는 인자가 많고 기능 한번 쓰는 게 번거로운 편이기 때문에 클래스를 쓸 법도 하지만, 어쩌다 한 번 쓰고 마는 특수한 함수를 전부 클래스로 감싸는 건 좀 낭비처럼 보이는 게 사실이다.

<날개셋> 편집기에는 있으나마나한 잉여이긴 하지만 명색이 텍스트 에디터이다 보니 인쇄 기능이 있다.
그런데 한때는 인쇄를 한 뒤에 자신이 사용한 프린터 DC를 해제하지 않아서 GDI 개체 누수가 생기는 버그가 있었다.
물론 이건, 리소스 제한이 있는 윈도우 9x에서 이 프로그램을 한 번 실행한 후, 프린터 드라이버를 이용한 인쇄(화면 인쇄 말고) 명령을 연달아 몇백, 몇천 번쯤 내려야(한 번에 몇백, 몇천 페이지를 인쇄하는 것과는 무관) 여파가 나타날 버그이니, 현실적으로는 아무런 위험이 아닌 것이나 마찬가지이다.

이 문제의 원인은 PrintDlg 함수가 PRINTDLG 구조체에다가 넘겨준 hDC 멤버(프린터 DC)를 해제하지 않아서였다.
그런데 이런 실수가 들어갈 법도 했던 게, MSDN에서 해당 함수나 해당 구조체에 대한 설명 어디에도, 사용이 끝난 DC를 처분하는 것에 대해서는 언급이 없었다.
이거 혹시 해제해야 하는 게 아닌지 미심쩍어서 내가 우연히 잉여질 차원에서 다른 예제 코드를 뒤져 본 뒤에야 DeleteDC로 해제를 해야 한다는 걸 알게 되었고, 예전 코드에 리소스 누수 버그가 있음을 깨달았다.

하긴, 내 기억이 맞다면, COM 오브젝트도 프로그래머가 Release를 제대로 안 해서 개체 누수가 하도 많이 생기다 보니 MS에서도 골머리를 썩을 정도였다고 하더라. 현실은 이상대로 되질 않는가 보다.

3.

윈도우 운영체제의 device context에 대해서 보충 설명을 좀 할 필요를 느낀다.
DC라는 건 그림을 그리는 매체가 (1) 화면, (2) 메모리(대부분은 화면에다 내보낼 비트맵을 보관하는 용도), 아니면 (3) 프린터 이렇게 셋으로 나뉜다. 화면용 DC는 GetDC나 GetWindowDC를 통해 HWND 오브젝트로부터 얻어 오고 해제는 ReleaseDC로 한다.

그러나 나머지 두 DC는 화면 DC와는 달리, DeleteDC로 해제한다는 차이가 있다. 화면용 DC는 운영체제가 통합적으로 관리하는 성격이 강한 반면, 나머지는 전적으로 사용자 프로그램의 재량에 달린 비중이 커서 그런 것 같다.

메모리 DC는 화면 같은 다른 물리적인 매체 DC와 연계를 할 목적으로 만들어지는 가상의 DC이므로, 보통 CreateCompatibleDC를 통해 이미 만들어진 DC를 레퍼런스로 삼아서 생성된다. 레퍼런스 DC가 무엇이냐에 따라서 하다못해 pixel format 같은 거라도 차이가 날 수 있기 때문이다.

그 반면 프린터 DC는 대개 가장 수준이 낮은 CreateDC를 통해 만들어지는데, 응용 프로그램이 특정 디바이스를 지목해서 DC를 하드코딩으로 생성하는 경우는 거의 없고 보통은 사용자에게 인쇄 대화상자를 출력한 뒤에 운영체제의 GUI가 생성해 주는 DC를 그대로 사용하면 된다.

사실, 프린터야 인쇄 전과 인쇄 후에 프린터에다 초기화 명령을 내리고 종이를 빼내는 등 여러 전처리· 후처리 작업이 필요하고 그런 저수준 명령은 프린터 하드웨어의 종류에 따라 다 다르다.
메모리는 프린터만치 하드웨어를 많이 가리지는 않겠지만, 그래도 그래픽을 보관하기 위해 메모리를 할당하고 나중에 해제하는 작업이 필요하다.

그에 반해 단순히 화면에다가 그림을 찍는 건 각 context별로 좌표를 전환하고 클리핑 영역 설정을 바꾸는 것 외에는 별다른 오버헤드가 존재하지 않는다. 도스 시절의 그래픽 라이브러리는 그런 DC 같은 추상화 계층 자체가 아예 존재하지도 않았으니 말이다.
그런 오버헤드의 위상이 ReleaseDC와 DeleteDC의 차이를 만든 것 같다.

Posted by 사무엘

2012/09/19 19:32 2012/09/19 19:32
,
Response
No Trackback , 8 Comments
RSS :
http://moogi.new21.org/tc/rss/response/734

1. 심벌 검색 기능의 퇴화(?)

예전에도 글에서 언급한 적이 있지만, 비주얼 C++에는 Alt+F12를 누르면 심벌 검색을 할 수 있다. 주어진 프로젝트의 소스 코드에 등장하는 모든 명칭들(클래스, 함수, 전역 변수 등등)의 선언과 정의가 있는 곳을 곧바로 찾아갈 수 있으니 이건 매우 편리한 기능이 아닐 수 없다.

이 기능이 특히 강력한 이유는 내가 해당 프로젝트의 내부에서 선언한 명칭뿐만 아니라, 인클루드 파일에 있는 명칭들도 전부 조회할 수 있기 때문이다. 따라서 C/C++ 라이브러리에 있는 함수나 윈도우 플랫폼 SDK 내지 MFC 라이브러리에 있는 방대한 명칭들도 다 조회가 되어서 해당 명칭의 출처를 쉽게 알아낼 수 있다.

어차피 소스 코드를 빌드하여 precompiled header나 인텔리센스 정보를 만들 때 이런 정보들을 다 한 번씩 파싱을 하기 때문에, 심벌 검색은 최적화된 자체 데이터베이스를 대상으로 신속하게 행해진다. 무식하게 수백, 수천 개의 헤더와 소스 파일들을 텍스트 형태로 찾는 find in files 형태가 아니다.

그런데, 비주얼 C++ 2010을 보니 심벌 검색은 해당 프로젝트에서 직접 선언한 명칭만 가능하고, 그 프로젝트가 stdafx.h에다가 인클루드하여 사용하는 플랫폼 SDK, MFC 같은 것들의 명칭은 조회되지 않는다.
200x 시절과 동일하게 '참조에서 찾기' 옵션을 켜고, 검색 범위를 'All components'로 바뀌었는데도 여전하다. 이 기능에 무슨 문제가 생겼는지 궁금하다.

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

(WM_CREATE 위치가 뜨는 2003 좌, 하지만 뜨지 않는 2010 우)

물론, 소스 코드에서 MFC나 플랫폼 SDK의 명칭을 참조하는 부분에서 F12를 눌러 보면 여전히 해당 명칭의 선언부로 가긴 간다. 하지만 명칭을 직접 입력해서 찾는 심벌 검색 기능은 왜 그게 불가능해진 걸까?

보아하니 그저 닷넷 프레임워크 라이브러리의 명칭을 조회하는 기능에만 신경 쓰느라, C++ 네이티브 개발 쪽은 지원이 간과되기라도 한 건지? 2010은 그렇잖아도 인텔리센스에다 빌드 보조 파일들이(*.sdf, *.ipch) 예전에 비하면 기겁을 할 정도로 방대해졌는데 편의 기능은 도리어 없어지면 어떡하냐 말이다.

2. 메뉴 편집기의 우클릭

C++ 프로젝트를 새로 만들거나 열어서 리소스에서 메뉴 편집기를 연다. 아, 프로젝트를 만들 필요 없이 그냥 리소스 템플릿만 하나 만들어서 메뉴를 생성해도 되겠다.

열었으면 클라이언트 화면의 빈 공간을 아무 데나 우클릭하여 메뉴 편집기에 대한 컨텍스트 메뉴를 연다. 그 후 마우스로 다른 곳을 클릭하거나, 명령을 선택하거나, ESC를 눌러서 컨텍스트 메뉴를 없앤다.
그러면 컨텍스트 메뉴가 화면 좌측 상단에 한 번 또 나타나서 사용자를 성가시게 할 것이다.

이는 명백한 버그이다. 대화상자 같은 다른 리소스 편집기에서는 우클릭을 해도 이런 현상이 생기지 않는다.
2010뿐만이 아니라 무려 2003에서도 동일한 현상이 발견된다. 거의 10년 묵은 버그라는 뜻인데 아무도 신경을 안 쓰는지 지금까지 고쳐지지 않았다.
설마 6.0에서까지 이랬을 것 같지는 않은데 잘 모르겠다. 아직도 6.0 쓰시는 분이 계시면 확인 요망.

여담이지만 마우스가 아니라 Shift+F10 같은 키보드로 컨텍스트 메뉴를 열면 이런 현상이 생기지 않는다.
그리고 화면 빈 공간이 아니라 편집 중인 메뉴 항목의 경우 우클릭하더라도 역시 그 현상이 생기지 않는다.
이건 아주 사소한 코딩 실수로 보이고, 몇 라인만 고치면 바로 제거할 수 있는 버그이다만, 10년에 가까운 시간 동안 발견하고 지적한 사람이 없었나 보다.

C#이나 VB, C++/CLI 같은 닷넷 환경의 경우, 폼(네이티브 개발 환경으로 치면 대화상자)에다가 메뉴 컴포넌트를 집어넣으면 그 자리에서 바로 메뉴를 편집할 수 있게 되어 있으니 네이티브 개발과는 환경이 꽤 다르다.
닷넷 프로그램도 기본 메뉴는 일반 윈도우 운영체제가 제공하는 표준 네이티브 메뉴 형태로 나오지 않겠나 하고 생각해 왔는데, 놀랍게도 그렇지 않다. 비주얼 스튜디오 200x와 비슷한 형태인 싸제 메뉴이다.

3. 툴바 편집기의 화면 잔상

이뿐만이 아니다.
리소스 중에서 툴바 편집기를 보면, 툴바 아이템들을 순서대로 하나씩 찍어 보기만 해도 예전 selection 흔적이 지저분한 잔상으로 잔뜩 남는다. 저건 절대로 multiple selection을 나타내는  게 아니며, WM_PAINT 메시지만 다시 받아도 잔상은 싹 없어진다.

사용자 삽입 이미지
열기, 저장, 모두 저장, 인쇄 아이콘의 테두리에 생긴 잔상들을 보라.
그리고 믿어지지 않겠지만 이건 비주얼 C++ 2003 시절부터 변함없던 버그이다!
전세계에서 압도적인 인지도와 점유율을 자랑하는 개발툴에 이런 초보적인 버그가 있다는 게 믿어지는가? 6.0은 그렇지 않았던 걸로 난 기억한다.

아이콘의 배치 순서를 조정하거나 중간에 여백을 넣기 위해서 드래그 드롭만 해도 잔상이 잔뜩 쌓인다. 구체적으로 재연 조건과 증상을 일일이 기술하기에는 구차하나, 잔상 현상은 2010에서 조금 더 심해졌다.

4. 속성 대화상자

비주얼 C++ 6.0까지는 전통적으로 가로로 길쭉한 자신만의 context-sensitive한(문맥 민감. 사용자가 키보드 포커스를 두거나 선택한 개체나 문서에 따라서 대화상자 내부 내용이 수시로 동적으로 바뀌는) 속성 대화상자가 있어서 Alt+Enter를 누르면 언제든지 그게 떴었다. old timer라면 추억의 옛날 스타일 대화상자를 기억하실 것이다.

사용자 삽입 이미지
그게 닷넷부터는 비주얼 베이직 스타일의 프로퍼티 그리드로 다 바뀌었다.
특히 프로젝트 설정 대화상자(VC6 표준 단축키 기준 Alt+F7)도 이 형태로 리모델링된 것 여러분들 다 아실 것이다.

그러나 프로퍼티 그리드가 커버하지 못하는 UI가 있었으니 그것은 바로 preview 기능이다.
비트맵, 대화상자, 메뉴 등 리소스들을 일일이 열 필요 없이 찍어 보기만 해도 이놈이 대략 어떻게 생겼는지 간략히 표시해서 보여주는 기능인데,
이건 2차원적인 공간에다 뭔가를 그려야 하기 때문에 기존 프로퍼티 그리드로 커버할 수가 없다.

그래서 별도의 버튼을 누르면 결국 과거 6.0 시절의 속성 대화상자와 비스무리하게 생긴 대화상자가 떠서 미리보기를 보여주는 기능이 들어갔다. 뭐, 여기까지는 뭐 나쁘지 않다. 메뉴나 대화상자가 좀 더 깔끔하게 그려졌으면 좋겠는데 10년 전이나 지금이나 하나도 바뀐 게 없이 똑같이 엉성하다는 건 아쉽지만 말이다.

그런데 과거의 200x 시절에는 미리보기를 보는 중에도 키보드 포커스는 각종 리소스들을 고르는 화면에서 계속 유지가 되어서 위· 아래 화살표를 누르며 리소스들을 조회할 수 있었는데,
2010부터는 뭔가를 선택하고 나면, 키보드 포커스가 미리보기 대화상자로 바뀌어 버린다. 그래서 마우스로 해당 아이템들을 일일이 찍어야 한다.

역사적으로 비주얼 C++은 4.0 때 Developer Studio (MSDEV)라는 첫 UI가 갖춰진 이래로 닷넷으로 넘어갈 때 대대적인 리모델링을 거쳤고, 2010 때는 WPF 기반으로 또 IDE의 구현체가 크게 바뀌었다.

요즘 다시 C++11 지원처럼 C++ 지원이 강화되고는 있다지만, 기존 코드들이 리팩터링되는 과정에서 예전에는 없던 사소한 버그들이 끼어 들어가는 게, MS에서 닷넷에 비해 네이티브 환경 개발에 점점 소심해지고 있다는 생각이 들어서 아쉽다. 닷넷과 관련된 개발 환경이라면 저런 버그가 들어갔을 리가 없을 텐데 말이다.

다음은 버그까지는 아니고, 비주얼 C++과 관란하여 추가로 떠오르는 생각들이다.

1. 비주얼 C++은 32비트 시절 이래로(무려 4.x부터) 80비트 초정밀 부동소숫점인 long double을 무시하고, 이것도 일반 double과 완전히 동일한 64비트 부동소숫점으로만 제공하는 것으로 잘 알려져 있다.
난 32비트 CPU에서는 10바이트 단위로 정보를 처리하는 게 불편해저서 long double이 도태한 게 아니겠나 정도로만 생각해 왔다.
그런데 나중에 알고 보니 인텔 CPU엔 80비트 부동소숫점을 연산하는 명령 자체는 존재한다고 한다. 단지, MS 컴파일러가 이를 활용하지 않는다고.

이것까지 지원해야 하면 %타입 문자부터 시작해서 언어 라이브러리에도 그야말로 대대적인 칼질이 가해져야 하는 건 사실일 것이다. 그런데 그렇다고 해서 있는 CPU의 기능을 컴파일러가 활용하지 않는 건 좀 문제가 있어 보이는데?
인텔 컴파일러 같은 다른 벤더 제품 중에는 long double을 쓸 수 있는 놈이 있는지 궁금하다.

2. 오늘날 거의 모든 IDE와 에디터들은 탭을 customize할 수 있다.
화면에 표시되는 탭 길이를 조절하고(보통 거의 다 4를 쓰지만), 코딩용 자동 들여쓰기를 할 때 공백을 삽입할지 탭을 삽입할지를 지정할 수 있다. 그리고 언어별로 어떤 탭 설정을 사용할지도 지정 가능하다.

그런데 여기서 한 발 더 나아가서, 읽어들이는 소스 코드의 형태를 보고 탭 컨벤션을 자동 감지하게 할 수는 없나?
space로 맞춰져 있는 소스 코드에다가 눈치 없게 탭으로 들여쓰기를 삽입한다거나 혹은 그 반대로 하는 것. 불편하다.

자동 들여쓰기를 구현했을 정도라면 앞뒤의 중괄호가 어떻게 돼 있고 whitespace들이 space인지 tab인지 주변 context들은 다 파악했다는 뜻이다.
따라서 조금만 더 센스 있게 동작하게 만드는 것은 마치 코드의 줄바꿈 문자의 종류를 자동 감지하는 것만큼이나 그렇게 어려운 일이 아니리라 여겨진다.

Posted by 사무엘

2012/07/29 08:33 2012/07/29 08:33
, ,
Response
No Trackback , 8 Comments
RSS :
http://moogi.new21.org/tc/rss/response/713

※ 프로세서 정보 얻기

현재 컴퓨터의 CPU 아키텍처 종류를 얻는 대표적인 함수는 GetSystemInfo이다. SYSTEM_INFO 구조체의 wProcessorArchitecture 멤버의 값을 확인하면 된다.
그런데, 64비트 컴퓨터에서 64비트 운영체제를 실행하고 있더라도 32비트 프로그램은 언제나 이 값이 0, 즉 32비트 x86이 돌아온다. 이는 호환성 차원에서 취해진 조치이다. 기존의 32비트 x86용 프로그램은 새로운 API를 쓰지 않으면 자신이 64비트 x64에서 돌아가고 있다는 걸 까맣게 모르며, 전혀 알 수 없다.

자신이 돌아가고 있는 환경이 진짜 x64인지 확인하려면 GetNativeSystemInfo라는 새로운 함수를 써야 한다. 이건 Windows가 최초로 x64 플랫폼을 지원하기 시작한 윈도우 XP에서 추가되었다. 이 함수가 존재하지 않는 운영체제라면 당연히 64비트 환경이 아니다.

64비트 프로그램이라면 그냥 기존의 GetSystemInfo만 써도 x64를 의미하는 9가 돌아온다. GetNativeSystemInfo는 동일한 코드가 32비트와 64비트로 컴파일되더라도 모두 정확한 동작을 보장한다는 차이가 존재할 뿐이다.

또한, 같은 64비트라도 아이테니엄(IA64) 환경에서는 기존 GetSystemInfo도 32비트 x86 프로그램에서 아키텍처를 x86이라고 속이지 않고 정확하게 IA64라고 알려 준다. 왜냐하면 IA64는 x86과는 명백하게 다른 환경이기 때문에 다르다는 걸 알려 줄 필요가 있기 때문이다. 뭐, 지금은 IA64는 완전히 망했기 때문에 일반인이 접할 일이 없겠지만 말이다.

※ 시스템 메모리 정보 얻기

메모리 양을 얻는 전통적인 함수는 GlobalMemoryStatus이다.
그러나 32비트 프로그램이라도 현재 컴이 64비트 운영체제를 사용하여 램이 4GB보다 많이 있는 걸 제대로 감지해서 표시하려면, 윈도우 2000에서 새로 추가된 GlobalMemoryStatusEx 함수를 써야 한다.

그리고 빌드되는 실행 파일의 헤더에 large address aware 플래그가 켜져 있어야 한다. 비주얼 C++ 기준 Linker → System → Enable Large Addresses를 yes로 지정해 주면 된다. 64비트 플랫폼에서는 이 값이 기본적으로 yes이지만, 32비트 플랫폼에서는 기본값이 no이다.
large address aware이 켜져 있지 않으면 32비트에서는 사용 가능한 가상 메모리가 아예 4GB가 아닌 2GB로 반토막이 난 채 표시된다. 포인터의 최상위 1비트를 비워 준다.

그리고 64비트 바이너리에 대해서는 사용 가능한 가상 메모리의 양이야 언제나 있는 그대로 운영체제가 알려 주지만, 해당 바이너리에 이 플래그가 없으면, 운영체제는 아예 상위 32비트를 비워 줘서 DLL 같은 걸 LoadLibrary해도 언제나 32비트 영역 안에서만 주소를 잡는다. 포인터까지 4바이트짜리 int와 구분 없이 작성된 구식 코드들의 64비트 포팅을 수월하게 해 주기 위한 조치이다.

참고로 64비트 전용 프로그램이라면 Ex 대신 기존의 GlobalMemoryStatus만 써도 괜찮다. 받아들이는 구조체의 크기가 int가 아니라 SIZE_T이기 때문에, 32비트 플랫폼에서는 32비트이지만 64비트 플랫폼에서는 자동으로 64비트가 설정되기 때문이다. Ex 함수는 플랫폼의 비트 수에 관계없이 숫자의 크기가 언제나 64비트 크기를 보장해 줄 뿐이다.

※ 32비트 프로그램이 지금 내가 64비트 운영체제에서 동작하고 있는지 감지하기

딱 그 목적을 위해 IsWow64Process라는 함수가 있다. 이것 역시 윈도우 XP 이상에서 추가되었다.

※ 윈도우 시스템 디렉터리에 접근하기

64비트 운영체제는 잘 알다시피 시스템 디렉터리가 64비트용과 32비트용으로 두 개 존재한다.
32비트와 64비트 프로그램에 관계없이 GetSystemDirectory는 언제나 C:\Windows\system32를 되돌린다.
그리고 윈도우 XP에서 추가된 GetSystemWow64Directory라는 함수가 있어서 역시 32비트와 64비트에 관계없이 C:\Windows\SysWow64를 되돌린다. 다만, 운영체제 자체가 64비트가 아닌 32비트 에디션이라면, 후자의 함수는 에러를 리턴한다.

그러니 의외로 이 함수는 플랫폼에 관계없이 절대적으로 같은 결과를 되돌리는 듯한데, 문제는 64비트 운영체제는 32비트 프로그램에 대해 시스템 디렉터리를 기본적으로 redirection한다는 것이다. 즉, 64비트 운영체제는 32비트 프로그램이 C:\Windows\System32를 요청한다고 해도 SysWow64의 내용을 보여주지 진짜 64비트용 시스템 디렉터리의 내용을 보여주지 않는다.

만약 32비트 기반으로 응용 프로그램 설치 관리자나 파일 유틸리티 같은 걸 만들 생각이어서 진짜로 64비트 시스템 디렉터리에 접근을 하고 싶다면, 운영체제에다 별도의 함수를 호출해서 요청을 해야 한다. 그래서 처음에는 Wow64EnableWow64FsRedirection라는 함수가 추가되었다. 이걸로 잠시 예외 요청을 한 뒤, 내가 할 일이 끝난 뒤엔 다시 설정을 원상복귀해야 했다. 왜냐하면 64비트 시스템 디렉터리에 접근 가능하게 해 놓은 예외 동작을 그대로 방치하면, 나중에 다른 32비트 모듈들이 32비트 시스템 디렉터리에 접근하지 못하게 되기 때문이다.

그런데 MS에서는 함수 디자인을 저렇게 한 것을 후회하고, 위의 함수의 기능을 Wow64DisableWow64FsRedirection과 Wow64RevertWow64FsRedirection 쌍으로 대체한다고 밝혔다. MSDN을 읽어 보면 알겠지만, 64비트 접근 여부 설정치를 마치 stack처럼 다단계로 저장했다가 다시 원상복귀를 더 쉽게 할 수 있게 만들려는 의도이다.

※ Program Files 디렉터리에 접근하기

64비트 운영체제는 응용 프로그램 디렉터리도 64비트용과 32비트용이 두 개 존재한다.
운영체제가 사용하는 특수 디렉터리의 위치를 얻어 오는 함수의 원조는 SHGetSpecialFolderPath이며, 이것은 윈도우 운영체제의 셸의 구조가 크게 바뀌었던 인터넷 익스플로러 4 시절에 처음 도입되었다. 그때는 특수 디렉터리들을 CSIDL이라는 그냥 정수 ID로 식별했다.

그랬는데 윈도우 비스타부터는 이 함수의 역할을 대체하는 SHGetKnownFolderPath라는 함수가 추가되었고, 이제는 식별자가 아예 128비트짜리 GUID로 바뀌었다. 문자열 버퍼도 구닥다리 260자짜리 고정 배열 포인터를 받는 게 아니라, 깔끔하게 별도의 동적 할당 형태가 되었다.

64비트 운영체제에서 64비트 프로그램은 64비트와 32비트용 Program Files 위치를 아주 쉽게 얻어 올 수 있다. 32비트를 가리키는 식별자가 따로 할당되어 있기 때문이다. 그러나 32비트 프로그램이 64비트 운영체제의 64비트 위치를 얻는 것은 위의 두 함수로 가능하지 않다. SpecialFolder 함수는 64비트만을 가리키는 식별자 자체가 없으며, KnownFolder함수도 32비트 프로그램에서 FOLDERID_ProgramFilesX64 같은 64비트 식별자를 사용할 경우 에러만 돌아오기 때문이다.

32비트 프로그램이 64비트 Program Files 위치를 얻는 거의 유일한 공식적인 통로는 의외의 곳에 있다. 바로 환경변수이다.

::ExpandEnvironmentStrings(_T("%ProgramW6432%\\"), wt, 256);

위의 환경변수를 사용한 코드는 32비트와 64비트에서 동일하게 64비트용 Program Files 위치를 되돌려 준다.


결론

이렇듯 32비트에서 64비트로 넘어가면서 윈도우 API의 복잡도와 무질서도는 한층 더 높아졌음을 우리는 알 수 있다. 가능한 한 급격한 변화와 단절을 야기하지 않으면서 새로운 기능을 조심스럽게 추가하려다 보니 지저분해지는 건 어쩔 수 없는 귀결이다.

프로그램 배포 패키지를 32비트 exe 하나만 만들어서 64비트와 32비트 플랫폼에서 모두 쓸 수 있게 하면 좋을 것 같다. 32비트 플랫폼에서는 32비트 바이너리만 설치되고, 64비트 플랫폼에서는 비록 32비트 EXE라도 64비트 프로그램 디렉터리들을 모두 건드릴 수 있어야 한다. 그런 프로그램을 만들려면 이 글에서 언급된 테크닉들을 모두 알아야 할 것이다. 설치 프로그램이니 UAC 관리자 권한이 필요하다는 manifest flag도 내부적으로 넣어 주고 말이다.

아, 그러고 보니, 윈도우 9x 시절에는 시스템 디렉터리가 16비트와 32비트로 나뉘어 있지도 않았다. NT 계열로 와서야 system과 구분하기 위해서 system32가 별도로 생기긴 했지만, 16비트용 시스템 디렉터리의 위치를 얻는 별도의 API는 존재하지 않았으며, 사실 필요하지도 않았다. 16비트 프로세스는 이제 NTVDM 밑에서 돌아가는 완전 고립된 별세계로 전락했기 때문이다.

Posted by 사무엘

2012/06/14 08:22 2012/06/14 08:22
, ,
Response
No Trackback , 5 Comments
RSS :
http://moogi.new21.org/tc/rss/response/695

GDI+에 대하여

GDI+는 잘 알다시피 전통적인 윈도우 API가 제공하는 GDI에서 더 나아가, 더 향상된 그래픽과 더 깔끔해진 프로그래밍 패러다임을 제공하는 그래픽 API이다. 사실, 닷넷에서는 애초에 기본 그래픽 API가 GDI+이다. (System.Drawing 네임스페이스)

GDI+는 벌써 10년 전, 닷넷 프레임워크가 첫 등장하고 윈도우와 오피스에 XP라는 브랜드가 붙던 시절에 도입되었다. MS가 제공하는 API로는 흔치 않게 C언어 함수도 아니고 그렇다고 DirectX 같은 COM도 아닌, C++ 언어 형태로 되어 있다. 물론, 그래도 실제로 링크를 해 보면 symbol들은 다 C언어 함수 호출 형태로 된다. C++ 클래스는 단순히 C 함수를 호출하는 wrapper인 것이다.

GDI+는 여러 편리한 기능을 많이 제공하지만, 무엇보다도 벡터 그래픽에 안티앨리어싱을 넣기 위해서라도 쓰지 않을 수 없다. 이런 간단한 기능은 그냥 기존 GDI 함수에다가도 옵션을 확장해서 좀 넣어 주지 하는 아쉬움이 있다. 재래식 GDI로도 안티앨리어싱된 텍스트는 얼마든지 찍을 수 있는 것처럼 말이다. (LOGFONT 구조체에 글꼴의 품질을 지정하는 추상화 계층이 있기 때문에, 나중에 추가된 안티앨리어싱 기능도 얼마든지 지정 가능)

재래식 GDI는 열악하던 컴퓨터 환경에서 최대한 장치 독립적인 추상적인 그래픽 계층을 구현하는 게 목표였다. 그래서 래스터 그래픽보다는 벡터 그래픽에, 애니메이션보다는 정적인 그래픽에 초점이 가 있었다. 그 추상화 계층이 확장성 측면에서 편리한 점은 분명 있었지만, 색깔을 하나 바꾸려고 해도 펜이나 브러시를 다시 만들고, 도스 시절의 그래픽 프로그래밍 때는 할 필요가 없었던 GDI 객체 관리를 해야 하니 상당히 불편했다.

그래서 GDI는 게임 그래픽용으로는 적합하지 않은 구석이 있었다. 물론, 지금과 같은 틀을 유지하면서도 JPG/PNG 이미지를 지원하고, 알파 채널 비트맵 Blit이나 별도의 gradient fill 함수를 추가하고, 윈도우 2000처럼 펜이나 브러시의 색깔을 손쉽게 바꿀 수 있는 DC pen/DC brush 같은 기능을 stock object로 넣는 등, 기능 개선이 꾸준히 진행돼 왔다. 하지만 MS 측에서는 이에 만족하지 못하고 이 참에 API를 근본적으로 갈아엎고 싶다는 욕망을 느꼈던 모양이다.

GDI+는 모든 API가 자신만의 별도의 namespace 안에 선언되어 있으며, POINT 같은 간단한 자료형도 자신만의 것을 재정의하여 쓸 정도로 기존 윈도우 API와는 거리를 두고 만들어졌다. 그리고 C++답게 같은 함수도 다양한 overload 버전이 존재하며, 좌표는 정수뿐만이 아니라 실수로도 받기 때문에 편리하다.

사소한 것이다만, 글자를 찍을 때 null-terminated string에 대해서 글자 길이 지정을 생략해도 되는 것 역시 마음에 든다.
전통적으로 윈도우의 GDI 함수들은 글자를 찍는 함수들은 문자열 길이를 반드시 지정해 주게 되어 있다. 왜냐하면 한 null-terminated string을 부분적으로 여러 줄에 걸쳐 찍어야 할 일도 있기 때문이다.

그러니 그런 API 디자인이 수긍은 가지만, 어차피 한 줄밖에 찍을 일이 없는 문자열을 매번 _wcslen 해 주는 것도 귀찮지 않은가. 예전에는 gdi가 아니라 user 계층에 있는 DrawText 같은 고수준 함수나 문자열 길이 지정을 -1로 생략이 가능했던 반면, GDI+는 이 정책이 좀 더 확대되었다.

GDI+는 GDI에 비해서 state machine으로서의 의미가 크게 퇴색했다. 그래서 그리기에 필요한 모든 정보들을 함수 호출 때 매개변수로 일일이 전달해 줘야 하는 경우가 많다. 가령, current position이라는 개념이 없기 때문에 MoveTo와 LineTo 따로가 아니며, SelectOjbect라는 개념도 없어져서 그리기 함수 때 매번 펜이나 브러시에 해당하는 개체를 따로 공급해 줘야 한다.

이런 디자인은 편리한 점도 있지만, 당장 화면에 뭔가를 찍는 드로잉 말고 벡터 path를 기록한다거나 메타파일 같은 걸 만들 때는, 내가 보기에 좀 불편하게 작용하는 점도 있는 것 같다. 가령, GDI에서는 똑같이 HDC이고 여기에다가 BeginPath를 해 주면 그때부터 path 그리기 모드로 GDI가 상태 관리를 하면서 동작한다. 그러던 것이 GDI+에서는 Graphics와 GraphicsPath라고 클래스가 아예 갈라졌다. 두 개체를 상태별로 분리한 건 분명 잘한 디자인이라는 거 인정한다.

하지만 Graphics 말고 GraphicsPath는 어차피 예전 위치에서 계속해서 이어서 그래픽을 기술하는 게 많은 만큼, 재래식 GDI처럼 current position이 있는 게 편리하지 않을까 싶다. 지금 API 체계에서는 직전 위치에 대한 정보를 응용 프로그램이 계속 공급해 줘야 한다.

또한, 복잡한 path를 화면에다 그릴 때, 예전 GDI는 지금 DC가 가지고 있는 펜과 브러시로 윤곽선을 그리고 내부를 채우는 것을 함수 호출 한 번으로 동시에 할 수 있었다. 그러나 GDI+는 선을 그리는 것과 내부를 채우는 것을 따로 해야 한다. path의 경계를 추출하여 래스터라이즈하는 것은 상당히 복잡한 계산이 필요한 작업인데, 동일한 작업이 비효율적으로 중복 적용되는 건 아닌지 우려된다.

즉, 본인은 GDI+에 대해서 참신한 기능은 분명 마음에 든다. 이 글에서 언급된 것 말고도 여러 고급 기능들이 있다. 윈도우 비스타 Aero와 연동하는 일부 드로잉 기능(가령, 클라이언트 영역에도 반투명 Aero 효과를 추가하고, 거기에다 글자를 찍는 것)은 오로지 GDI+로만 접근해야 하는 것도 있다.

하지만 (1) 그냥 재래식 GDI API에다가 옵션을 추가하는 형태로 구현했어도 충분해 보이는 것, (2) GDI+가 바꿔 놓은 API 디자인이 오히려 좀 불편하고 비효율적일 수도 있겠다 싶은 것에 대해 비판적인 안목을 갖고 있다. 속도가 재래식 GDI보다 꽤 느린 건 차치하고라도 말이다.

Posted by 사무엘

2012/04/28 08:39 2012/04/28 08:39
, ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/675

Windows라는 PC용 운영체제는 1985년에 처음 나온 이래로 많은 변화를 겪었다.

1.0 시절에 윈도우는 잘 알다시피 독자적인 실행 파일 포맷을 갖고 있긴 했지만, 완전한 운영체제가 아니라 16비트 도스 위에서 추가로 구동되는 액세서리 멀티태스킹 환경에 불과했다. 또 개발 언어가 의외로 C가 아닌 파스칼이었기 때문에, 실행 파일 내부의 각종 export/import 심볼을 보면 대소문자 구분이 없이 다 대문자였고, 문자열도 null-terminated 형태가 아니라 글자수가 앞에 찍힌 형태로 저장되어 있었다.

상업적으로 최초로 대성공을 거둔 윈도우 3.0때부터(혹은 2.x때?) C언어 형태 기반으로 API가 재정비되었으나, 이런 파스칼의 흔적은 실행 파일 포맷이라든가 함수 호출 규약 같은 데에 여전히 일부 남아 있었다. API에 하위 호환성도 잘 지켜진 편이기 때문에 1.x~2.x용 실행 파일도 내부의 리소스 데이터의 구조만 살짝 고쳐 주면 3.x에서 바로 실행 가능할 정도였다.

그랬는데 1993년에 윈도우 NT가 개발되면서 프로그램의 내부 구조가 크게 바뀌었다. 16비트에서 32비트 환경으로 갈아탔으며, 멀티스레딩+선점형 멀티태스킹이라는 게 도입되었다. 이때 실행 파일의 포맷도 NE에서 PE 방식으로 바뀌었고, 이 전통이 오늘날까지 그대로 이어져 내려오고 있다.

마이크로소프트는 동일 코드를 거의 고치지 않아고도 재컴파일만으로 16비트 바이너리와 32비트 바이너리를 동시에 만들 수 있게 많은 배려를 했다. 특히 운영체제의 API 함수는 int 크기가 4바이트가 된 것 같은 불가피한 변화를 빼면 프로토타입이 거의 바뀌지 않았다.
그럼에도 불구하고 불가피하게 프로토타입이 크게 바뀐 함수가 의외로 GDI 계층에 많이 있다. MoveToEx 함수가 그 예이다.

16비트 윈도우 시절에 이 함수는

long MoveTo(HDC, int x, int y);

처럼 정의되어 있었다. 주어진 DC가 내부적으로 기억하고 있는 그리기 기준 위치를 x, y로 옮기고, 예전의 기준 위치를 리턴값으로 돌려줬다. 그때는 좌표계의 범위가 16비트이기 때문에, 두 개의 16비트 수치를 32비트 long 정수로 합산해서 표현하는 게 괜찮은 방법이었다.

그러나 이 디자인은 32비트 환경에서는 바뀌는 게 불가피해졌다. int 개개의 값이 32비트로 커졌고 32비트 윈도우는 32비트 좌표계를 지원하기 때문이다. 16비트 숫자야 범위가 너무 좁기 때문에 16비트 컴퓨터 시절에도 느리게나마 32비트 정수를 다루는 long 같은 타입이 있었지만, 32비트 둘을 합친 64비트 정수는, 언어 차원에서 표준으로 지정된 타입이 그 당시에 없었다.

그래서 32비트 환경에서는 예전의 기준 위치를 POINT라는 별도의 구조체의 포인터에다가 되돌리는 형태로 동작 방식이 바뀌어야 했고, MoveToEx라는 함수가 추가되었다.

BOOL MoveToEx(HDC, int x, int y, POINT *pPoint);

윈도우 API에 어떤 함수의 Ex 버전이 추가되더라도 MS는 어지간하면 옛날 버전 함수도 남겨 두는 편인데, MoveTo만큼은 그렇게 하지 않았다. 원래 있던 함수는 삭제되고 새로운 함수로 대체되었기 때문에, 16비트 코드를 포팅하는 사람은 이 함수의 호출 부분을 수동으로 리팩터링을 하지 않을 수 없게 되었다. 좌표계가 어차피 16비트 범위를 넘을 일이 절대 없다는 보장이 있고 기존 16비트 코드를 빠르게 포팅해야 하는 사람이라면, 그냥 이런 wrapper 함수를 자체적으로 만들 필요가 있을 것이다.

long MoveTo(HDC hDC, int x, int y)
{
    POINT pt;
    MoveToEx(hDC, x, y, &pt);
    return MAKELONG(pt.x, pt.y);
}

오리지널 버전을 왜 살려 두지 않았냐 하면, 저런 식으로 확장해야 하는 함수가 한두 개가 아니기 때문에, 오리지널 버전을 다 살려 뒀다간 윈도우 API가 심하게 너무 지저분해지기 때문이다.

GetViewportExtEx, GetWindowExtEx, GetViewportOrgEx, GetWindowOrgEx와 이들의 Set 버전들. 오늘날의 윈도우 API에 Ex 버전만 존재하고 오리지널은 남아 있지 않은 이유가 동일하다. 16비트 시절에는 간단하게 x, y좌표를 32비트 long으로 합쳐서 되돌리던 함수였는데 그것이 32비트 윈도우에서부터는 POINT나 SIZE 구조체를 통해서 결과값을 받도록 바뀌었다.

사실, GDI라는 게 화면 픽셀만을 취급한다면 좌표계가 16비트 범위만으로도 아주 충분할 것이다. 오늘날도 화면 해상도는 끽해야 1000~2000대를 벗어나지 않기 때문이다. 그러나 GDI는 화면뿐만 아니라 프린터도 다루고, 픽셀뿐만 아니라 장치 독립적인 더욱 정밀한 단위도 취급하기 때문에 궁극적으로는 좌표계의 크기를 32비트로 확장할 필요가 있었다.

다만, 과거의 윈도우 9x는 GDI와 USER 계층의 상당수가 16비트 코드를 그대로 답습하고 있었기 때문에, API는 저렇게 32비트 형태여도 내부적으로 여전히 16비트 좌표계의 한계를 지니고 있긴 했다. 그러니 실수로 32767을 넘어가는 40000쯤 되는 좌표로 선을 그으라고 하면, 숫자가 음수로 바뀌어 인식되어 선이 오른쪽 끝이 아닌 왼쪽 끝으로 가게 되었다. 이런 보정은 응용 프로그램이 알아서 해 줘야 했다. 암울했던 시절이다.

이런 점에서 윈도우 API를 커버하는 계층인 MFC가 편한 구석이 있다. 16비트 시절이나 32비트 시절이나 CDC 클래스의 멤버 함수의 프로토타입은 CPoint MoveTo(int x, int y)로 동일하다. POINT 자료구조를 생으로 함수값으로 되돌리게 한 것은 오버헤드가 따르지만, 그냥 이식성과 개발 편의에다 더 비중을 두고 클래스를 설계한 셈이다.

그럼, 세월이 흘러 32비트에서 64비트로 넘어가는 과정에서 생긴 큰 변화는 무엇일까?
뭐니뭐니해도 GetWindowLong 함수를 예로 들 수 있다. Set 버전도 포함.
얘는 원래 주어진 윈도우에 대해서 스타일, ID, 윈도우 프로시저 주소 등 다양한 수치 정보를 얻어 오는 일종의 다형적인(polymorphic) 함수이다. 리턴값이 일반 숫자일 수도 있고 포인터나 핸들일 수도 있다.

32비트 시절에는 컴퓨터가 표현하는 숫자의 크기는 32비트로 사실상 획일화되어 있었기 때문에, 문제될 게 없었다. int나 long을 바로 포인터로 typecast하거나 그 반대로 해도 정보가 손실될 일이 없었다.
그러나 64비트에서는 이것이 큰 문제로 작용하게 되었다. 윈도우 운영체제는 int와 long은 호환성 차원에서 32비트로 그대로 유지하고,포인터와 핸들만 64비트로 키우는 정책을 선택했기 때문이다.

그래서 개발자의 편의를 위해 비주얼 닷넷쯤의 플랫폼 SDK에서는 잘 알다시피 INT_PTR처럼 _PTR이라는 자료형 typedef가 추가되었다. 포인터의 크기와 같은 정수형이라는 보장이 있는 정수형을 따로 구분해서 표현하기 위해서이다. 윈도우 API도 원래는 GetWindowLong 하나만 있었는데 GetWindowLongPtr이라는 명칭이 추가되었다. 이것이 32비트 환경에서는 그냥 GetWindowLong로 도로 치환되는 매크로에 불과하지만, 64비트에서는 Ptr 버전만이 운영체제의 user32.dll에 실제로 존재하는 함수이다.

다시 말해, 32비트에서는 기존 Long과 새로운 LongPtr 버전을 둘 다 쓸 수 있고 LongPtr이 내부적으로는 Long으로 도로 바뀌어 처리되는 반면, 64비트에서는 LongPtr만 써야 하고 Long을 쓰면 에러가 난다.

이 함수가 받는 매개변수도 32비트 범위로 충분한 GWL_STYLE, GWL_ID 같은 상수는 바뀐 게 없는데, 포인터와 크기가 같은 윈도우 프로시저나 인스턴스 핸들 같은 걸 지정할 때는 GWL_*말고 GWLP_*라는 명칭이 새로 추가되었다. 둘은 의미하는 값도 차이가 없는데 왜 이런 조치를 취한 것일까?

이는 단순히 프로그래머의 편의를 위해서이다.

int n = (int)GetWindowLong(hWnd, GWL_WNDPROC);

64비트에 환경에서는 윈도우 프로시저의 크기 (8바이트)가 int의 크기(4바이트)보다 더 크기 때문에, 이런 식으로 32비트 관행을 전제를 하고 작성된 코드는 64비트 환경에서 아예 컴파일이 되지 않게 하기 위해서이다.

INT_PTR n = (INT_PTR)GetWindowLongPtr(hWnd, GWLP_WNDPROC);

이렇게 짜 주면 32비트와 64비트에서 모두 안전하게 잘 동작하는 코드가 된다.

memory mapped file을 만드는 CreateFileMapping이나 MapViewOfFile 함수는 메모리의 크기를 64비트 범위로 잡을 수 있어서 그 값을 32비트 기계에서 처리하기 편하게끔 두 개의 32비트 숫자로 쪼개서 받아들인다. 64비트 윈도우에서는 굳이 그렇게 할 필요가 없지만 함수의 프로토타입이 바뀌지 않았다. 어차피 64비트 윈도우라고 해서 당장 4GB를 능가하는 어마어마한 양의 메모리를 한 번에 잡는 일은 실제로 거의 없기 때문이다.

GlobalAlloc, VirtualAlloc, HeapAlloc 같은 메모리 할당 함수들은 메모리의 양을 잡는 숫자의 자료형이 SIZE_T이다. 즉, 32비트 환경에서는 32비트, 64비트 환경에서는 64비트로 결정된다는 뜻. SIZE_T는 UINT_PTR과 의미상 사실상 동급인 셈이다.
하지만 파일을 읽고 쓰는 ReadFile와 WriteFile은 정보를 전송하는 단위가 SIZE_T도 아니고 그냥 DWORD(32비트)로 고정되어 있다.

다만, 32비트 환경에서라도 32비트 크기의 범위를 능가하는 방대한 파일을 취급해야 할 일이 있기 때문에 파일의 크기를 얻거나(GetFileSize), 파일의 특정 지점을 탐색하는(SetFilePointer) 함수는 역시 32비트 필드를 두 개 받아서 64비트 숫자를 전달할 수 있게 되어 있다. 윈도우 2000부터는 숫자를 32비트 단위로 쪼갤 필요 없이 64비트 숫자를 한 번에 전달받는 Ex 함수가 운영체제 차원에서 추가되었다.

MFC는 운영체제에 그런 Ex 함수가 추가되기 전부터 CFile::Seek나 CFile::GetLength는 언제나 64비트 정수를 다뤄 왔으니 속 편한 경우라 하겠다.

GlobalMemoryStatus 함수는 현재 컴퓨터의 전체 메모리 양과 남은 메모리 양을 되돌리는 함수인데, 램 용량이 4GB를 넘어서는 날이 올 거라고 과거에 상상이 가능했을까. 구조체의 각 멤버가 32비트 크기로 고정되어 있다가 이것이 64비트로 확장된 Ex 함수가 역시 윈도우 2000 때부터 추가되었다. 64비트 운영체제에서는 오리지널 함수를 없애 버려도 될 법도 해 보이는데 이건 오리지널과 Ex가 여전히 남아 있다.

16비트 시절에는 윈도우 메시지와 함께 전달된 두 개의 부가 정보 중 WPARAM은 16비트이고 LPARAM은 32비트 크기였다. 그러던 것이 32비트 환경에서는 둘 다 32비트가 되었다. 16비트와 같은 사고방식이라면 64비트 환경에서는 WPARAM은 32비트이고 LPARAM만 64비트로 승격해도 될 것 같으나 그렇지 않다. 둘 다 64비트이다.

machine word보다 더 작은 크기로 정보를 제한해서 담을 필요가 전혀 없을 뿐더러, 이미 32비트 시절에 WPARAM과 LPARAM을 구분하지 않고 포인터와 핸들을 담는 관행이 10년 넘게 지속되었을 텐데 다시 그 구분을 넣는다는 건 불가능한 지경이 되었기 때문이다.

한 플랫폼에서만 10년 넘게 프로그래밍을 하니까 이제는 그 API를 처음에 설계한 사람의 마음을 읽고 시대에 따른 변천사를 이해하는 경지에 도달하는 걸 느낀다. ^^

Posted by 사무엘

2012/04/21 19:29 2012/04/21 19:29
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/672

비주얼 C++ MFC의 역사 -- 下

현재 윈도우 운영체제의 GUI는 잘 알다시피 윈 2000/ME까지 유지되던 고전 테마, 그리고 XP 테마와 비스타/7 테마가 서로 따로 논다.
MS 오피스도 XP/2003/2007이 따로 놀고,
거기에다가 비주얼 개발툴의 디자인도 2005/2008/2010이 다 달라진다.

어디 그뿐인가. 현재 운영체제의 configuration에 따라 테마가 나타나는 모양도 달라져야 한다. 가령, 오피스 2003은 전반적으로 파란 색상이지만 운영체제가 회색 고전 테마일 때는 은색이 깔림. 그 반면 오피스 2007은 독자적인 배색 컨셉이기 때문에 운영체제의 상태에 관계 없이 여전히 은은한 하늘색 색상이 되는 게 맞다.

이런 세세한 알고리즘은 reverse engineering이라도 해서 알아내야 할 텐데, GUI 툴킷 만드는 일은 정말 장난 아니게 어려울 것 같다. 아래아한글의 스킨을 개발하는 팀도 고생 무지하게 했을 거라는 생각이 든다. 차라리 운영체제 GUI를 완전히 생까던 9x  시절이나, 반대로 잠시 100% 운영체제 표준 GUI만 따르던 워디안 시절이 나았겠다.

자, 이렇게 GUI 비주얼들이 난립하고 기존 MFC는 이런 수요를 충족하지 못한 채 시간만 자꾸 흐르고 있었다. 그러던 차에 MS는 결단을 내렸다. 바로 비주얼 C++ 2008 플러스 팩. MFC42 시절 이래로 MFC에 대대적으로 기능을 추가하여 역대 오피스와 비주얼 툴들의 UI, 그리고 심지어 리본 UI까지 MFC에다 넣어 줬다.

MFC의 새단장 소식이 전해지던 당시, 많은 개발자들은 MS가 자기네 오피스 팀이 개발한 오리지널 GUI 소스를 리팩터링해서 전해 줄지 무척 기대했었다. 하지만 아쉽게도 그건 아니고, 이미 BCG라는 러시아 회사에서 개발하여 판매 중이던 제3자 라이브러리를 MS라는 브랜드만 넣어서 제공하는 형태가 되었다. MS의 GUI는 MFC를 써서 개발된 것도 아니고, 모듈 구조를 보아하니 외부에 선뜻 떼어 줄 만한 상태가 아닌 모양이다.

이 작업의 결과로 MFC DLL은 덩치가 정말 크고 아름다워져서, 1MB 초반이던 게 4MB를 훌쩍 넘어갔다. CWinApp, CFrameWnd는 새로운 GUI 기능과의 연동을 위해, Ex라는 접미사가 붙은 CWinAppEx, 그리고 CFrameWndEx라는 파생 클래스가 추가되었다.

그런데, MS가 제3자 중에서도 왜 하필 BCG 라이브러리를 선택했는지에 대해서 불만을 제기하는 사람이 있다. 김 민장 님이 굉장히 옛날에 쓰신 개념글을 하나 읽어보자.

일단, 한눈에 BCG 제품의 메뉴 폰트가 한 픽셀 작음을 알 수 있다. 메뉴뿐만 아니라 툴바 아이콘도 가로 폭이 1픽셀 작은 것 같고, "Solution Explorer"의 글씨도 작다. (중략) 데모 프로그램 다운 받고 단 3분 동안 들여다 봤는데도 벌써 이상한 점 서너 개가 나왔다. 그러니 BCG 제품을 믿지 못하겠고 사용하기가 꺼려지는 것이다.


특히 메뉴의 글씨의 크기가 작은 것은 비주얼 C++ 2008/2010에서까지 그대로 이어져 오고 있는 걸 본인 역시 확인했다. 이건 꽤 심각한 문제인데? (왼쪽이 good, 오른쪽이 bad)

사용자 삽입 이미지사용자 삽입 이미지
도대체 왜 이렇게 동작하는 걸까? 궁금해서 MFC의 소스를 들여다봤다. 내가 발견한 문제점은 두 가지이다.

afxglobals.cpp를 보면 MFC의 GUI 구성요소들이 한데 공유하는 GDI 오브젝트들이 한데 모여 있으며, 그 중에는 글꼴 오브젝트도 응당 있다. 그런데 여기 생성자를 보면,

m_bUseSystemFont = FALSE;

로만 되어 있고, SystemFont라고 MFC 소스 전체를 검색해 봐도 이 값을 바꾸는 멤버 함수 같은 건 어디에도 나오지 않는다. 메뉴의 글씨체는 운영체제의 시스템 글꼴로 나오는 게 일반인데(한글 윈도우의 고전 테마에서는 그냥 ‘굴림’처럼), 저게 TRUE가 아닌 FALSE인 한은 운영체제 한글 윈도우 고전 테마 + 오피스 구버전 테마 모드에서도 메뉴의 글꼴은 Segoe 내지 Tahoma로 자기의 독자적인 글꼴로 출력된다.

그리고 더 큰 문제는 UpdateFonts 함수에 있다. Adjust font size라는 주석 하에

if (nFontHeight <= 12)
    nFontHeight = 11;

라는 글꼴 보정 코드가 있다. 그런데 이 잘못된 보정 때문에 BCG 툴킷이 정상보다 글씨가 작게 찍힌다. 도대체 이 코드가 왜 들어갔는지 모르겠다. 12픽셀이던 걸 11픽셀로 줄이면, 한글· 한자가 가장 잘 찍히는 9포인트보다 글자 크기가 한 단계 더 작아져 버린다. 아마 영문 오피스 2007의 글꼴이 여타 비주얼에도 획일적으로 적용되는 듯하다.

한눈에 봐도 MS 원본 프로그램과는 UI가 다르게 찍히는 GUI 툴킷을 최소한의 보정도 없이 MS가 자기 이름으로 어떻게 MFC에다 내장시켜서 내놓은 걸까? 물론 이런 시도조차 없는 것보다는 낫지만, 역시 네이티브 C++ 개발자로서 꽤 안타까운 일이다. 차라리 제3자 라이브러리 시절에는 소스를 고쳐서 라이브러리를 새로 빌드할 수라도 있지만, 그 거대한 MFC의 내부에 내장이 되어 버린 코드는 고칠 수도 없는 노릇이고.

김 민장 님의 블로그에 나와 있듯, BCG보다는 Codejock이라는 미국 회사에서 개발한 Extreme Toolkit이라는 GUI 라이브러리가 원본과의 고증(?)이 더 잘 돼 있고 품질이 좋다고 그런다.

이렇듯, MS는 자기네가 처음으로 구현한 기능을(뭐, 오피스 팀이니, VS 팀과는 다른 부서이지만) 외부 회사로부터 구현체를 사 오는 기묘한 방법으로 집어넣어서 MFC를 확장했다. 일단 MS 내부에서 MFC를 자기네 프로그램의 개발에 거의 안 쓴다는 점을 염두에 둘 필요가 있겠다. -_-;; 진짜로 워드패드와 그림판을 빼고는 거의 없다.

본인 역시 일단 중요 제품인 <날개셋> 한글 입력기를 MFC 없이 순수 API + 간단한 싸제 라이브러리만으로 개발하기 시작했으니, MFC와는 직접적으로 결별했다. 하지만 C++과 윈도우 플랫폼이 살아 있는 한, MFC의 중요성과 의의는 그리 금방 격하되지 않을 것이다.

다만, 덩치가 커져도 너무 커져 버린 건 진짜로 아쉬운 점이다. 그렇게 살이 뒤룩뒤룩 찐 네이티브 환경을 쓰느니, 차라리 훨씬 더 생산성 좋은 닷넷이 그 역할을 조금씩 대체하고 있는 게 아닐까 싶다.
그리고 여담이지만, 윈도우, 오피스, VS는 앞으로도 또 비주얼이 완전히 확 바뀌는 일이 있을지 궁금하고 기대-_-되기도 한다. ^^

Posted by 사무엘

2012/03/29 09:10 2012/03/29 09:10
, ,
Response
No Trackback , 6 Comments
RSS :
http://moogi.new21.org/tc/rss/response/661

비주얼 C++ MFC의 역사 -- 上

옛날에 비주얼 C++의 역사에 대해서는 거창한 글을 쓴 적이 있는데 MFC 자체에 대해서는 의외로 블로그 개설 이래로 글을 쓴 적이 없었던 것 같다. 그래서 오늘은 이 주제로 한번 칼럼을 써 보겠다.

MFC는 잘 알다시피 C 언어 기반인 윈도우 API를 C++로 얇게 포장하는 한편으로, C++의 특성을 살려 더욱 생산성 있고 편리한 윈도우 응용 프로그램 개발을 목표로 만들어진 C++ 라이브러리이다. C 언어로 API만 쓸 때에 비해 네이티브 코드 프로그램 개발이 넘사벽급으로 훨씬 더 편리해진다는 건 부인할 수 없는 사실이다(특히 message map 덕분에! ㅋㅋ).

이것의 역사는 윈도우 3.x 시절이던 1992년 무렵으로 거슬러 올라간다. 이때는 MS의 주력 운영체제가 도스에서 윈도우로 바뀌고, 주 개발 언어가 C에서 C++로 이제 막 바뀌던 매우 중요한 과도기였다. 당시 볼랜드의 터보 C의 인지도에 압도적으로 밀리고 있긴 했지만, 마이크로소프트도 MS C라는 컴파일러를 개발해서 팔고 있었고, 이게 7.0 버전부터는 C++ 언어도 지원하기 시작했다.

MFC는 바로 MS C/C++ 7.0부터 도입되었으며, 그 다음부터는 제품명이 그 이름도 유명한 비주얼 C++ 1.0으로 바뀌게 되었다. 당시에는 라이브러리에 Microsoft Foundation Classes라는 거창한 정식 명칭이 없었기 때문에, 그냥 Application Frameworks라고 불렸다. 이것이 바로 AFX의 어원이다.

그래서 오늘날까지도 MFC의 핵심 인클루드 파일 이름은 mfc.h가 아니라 afx.h / afxwin.h이다. 또한 AfxGetApp(), AfxMessageBox() 같은 함수명과, AfxWnd##, AfxFrameOrView##처럼 MFC가 자체적으로 생성하는 윈도우 클래스 이름에도 AFX라는 약어를 찾을 수 있다.

비록 16비트 시절부터 존재하긴 했지만 MFC가 본격적으로 볼랜드 사의 컴파일러와 걔네들 라이브러리를 누르고 인기를 누리기 시작한 건 32비트로 넘어오면서이다. MFC가 첫 도입되었을 때는 C++ 개념을 구현하는 데 드는 특유의 오버헤드가 성능 면에서 문제가 되지 않을까 하는 회의적인 시각이 많았었다. 가령, code bloat으로 인한 용량 증가를 비롯해, 가상 함수 호출이라든가, 윈도우 핸들을 C++ 개체로 연결하기 위한 비용 같은 것 말이다.

워낙 옛날에, C++이 지금과 같은 규격으로 확장되기 전에 개발되던 것이다 보니 MFC는 RTTI를 자체적인 메커니즘으로 구현했으며, 컨테이너 클래스를 템플릿 없이 자체적으로 여러 구현체로 만든 게 있다. CPtrList, CObList 같은 것 말이다. MFC가 처음 개발되었을 때는 C++에 아직 템플릿이란 게 없었기 때문이다. 그리고 그 흔한 namespace조차 쓰지 않았다. 당연히 그때는 namespace가 없었기 때문.

MFC가 드디어 어느 정도 안정화를 이뤄 낸 것은 비주얼  C++ 4.2에서 도입된 MFC 4.2이다(MFC42.DLL). 비록 후속 버전인 비주얼 C++ 5와 6에서 개선과 기능 추가가 있었지만, 이것은 하위 호환성이 완벽하게 유지되는 변화이기 때문에 파일 이름이 동일했고, 이것이 MFC42.DLL이 윈도우 98 이래로 모든 윈도우 운영체제가 기본 내장하고 있는 표준 MFC DLL이 되었다.

오늘날 운영체제가 제공하는 MFC42.DLL은 이제 비주얼 C++ 6.0이 제공하던 클래식 MFC42.DLL과 하위 호환성만 유지되는 superset으로, 비주얼 C++과는 별개로 운영체제가 관리하는 DLL이다. 가령, 윈도우 7의 워드패드와 그림판은 명목상 MFC42.DLL을 사용하지만, 원래 VC6에는 없던 리본 인터페이스를 자기네 MFC42.DLL로부터 가져와서 사용하고 있다.

16비트에서 32비트로 넘어가면서 MFC는 일부 내부 자료구조가 바뀌었다. 특히 handle map은 스레드마다 서로 따로 돌아가기 때문에, 서로 다른 스레드끼리 핸들을 주고받을 때는 MFC 개체가 아닌 핸들을 직통으로 주고받아야 안전하다.

뭐, 이뿐만이 아니라 자체 구현으로 표시하던 toolbar와 status bar가 윈도우 95/NT 3.5부터는 common control이라고 운영체제에 정식으로 편입되었기 때문에, 그걸 그냥 끌어다 쓰는 걸로 내부 구현이 바뀌었다는 것도 첨언하겠다.

비주얼 C++ 6에서 닷넷으로 넘어가면서는 CString 같은 기초 자료형이 MFC가 아닌 ATL이라는 다른 라이브러리로 내부 관할이 바뀌고, 완전히 템플릿 기반으로 바뀌어서 한 코드로 ansi string과 wide string을 모두 다룰 수 있게 되었다. 그리고 DLL의 배포 방식이 변화를 겪기도 했다.

그 뒤 MFC는 2000년대에 들어와서는 이렇다 할 큰 변화가 없었고, 하루가 다르게 발전하는 C# 언어와 닷넷에 비해 MS가 네이티브 개발을 너무 홀대하는 게 아니냐는 우려의 목소리가 나올 정도였다. 특히 MS는 오피스 제품을 주축으로 독자적인 화려한 GUI(메뉴, 툴바 등)를 선보였고 오피스 2007부터는 리본 UI라는 완전히 새로운 물건까지 선보여 왔는데, 이를 모방하여 구현해 주는 MFC 기반 싸제 GUI 라이브러리가 제3자에 의해 미들웨어로 전문적으로 개발되어 판매될 정도가 되었다. 이름하여 GUI 툴킷.

과거 오피스 97/2000까지만 해도 프로그램 비주얼이 운영체제의 그것과 그렇게 큰 차이는 없었다. 그러나 오피스 XP부터 운영체제 기본 프로그램의 비주얼과 그런 오피스(?)급 프로그램의 비주얼은 이질감이 확 생겨 버렸다. 사실은 오피스 XP의 그 하얗고 깔끔한 2D같은 GUI가 윈도우 XP의 전반적인 GUI 디자인이 될 예정이었는데, 그 계획이 수틀리는 바람이 그 오피스 계열하고 운영체제의 알록달록 둥글둥글(?)한 디자인 계열은 따로 놀게 된 거라고 한다.

다음 하편에서는 이 GUI 툴킷과 관련된 MFC의 변천사 이야기를 계속하겠다.

Posted by 사무엘

2012/03/27 08:35 2012/03/27 08:35
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/660

철도 인명 사고에 대한 생각

지난 3월 12일에 본인은 출근길에서부터 충격적인 소식을 들으며 월요일 하루를 시작했다.
딱 정확하게 본인이 타려는 지하철이 앞 역에서 웬 인명 사고가 발생해서 열차 운행이 한동안 중단됐기 때문이다.
이런 일은 처음이었다. 게다가 요즘은 스크린도어까지 버젓이 있는데 웬 인명 사고가 난단 말인가? 이 때문에 난생 처음으로 지하철 지연 증명서(난 뭐 지연이라기보다는 열차 탑승을 애초에 포기한 경우이지만)라는 걸 구경했다.

그런데 사고의 사망자는 놀랍게도 일반 승객이 아니라 지하철 회사의 직원이고, 역무원도 아닌 기관사인 것으로 밝혀졌다. 선로로 접근이 가능한 내부 직원이 마음먹고 자살을 하려 든다면, 제아무리 스크린도어가 갖춰져 있어도 애시당초 소용이 없을 것이다.

철도 공기업에서 승무직으로 일할 정도이면 연봉 빵빵한 건 말할 것도 없고 정년도 보장되기 때문에 겉으로 보기엔 아주 부러운 지위에 속하는 사람이다. 군대로 치면 제일 중요한 전투 병과요, 게임 개발로 치면 딱 프로그래머에 해당하지 않는가. 최소한 경제적인 문제로 인해 자살할 이유는 없다.

하긴 2004년, 본인이 아직 대학 재학 중이던 시절엔 대구 과학고의 1회 졸업생이고 카이스트 기계공학과를 졸업한 어떤 사람이 대구 지하철 기관사로 취직한 게 신문에 보도된 적도 있었다. (☞ 관련 기사 클릭 )

그땐 카이스트라는 스펙에 비해 저학벌(?) 직업을 선택한 이례적인 사례로 그 사람이 소개되었지만, 저게 과연 그렇게 만만한 직업일까? 요즘은 SKY급 대학 나오고도 공기업, 공무원엔 말단으로라도 들어가려는 사람들이 줄을 서 있을 텐데. 이런 트렌드와는 무관하게 나도 2007년에 서울 도시철도 공사에서 공채를 하던 시절에 원서를 넣긴 하고 싶은 마음이 있었다. 철도 차량 운전 면허가 없으니 승무직은 못 하더라도 다른 부서로라도 말이다. 그때 난 병특 중이었기 때문에 애당초 지원을 할 수가 없어서 못 했을 뿐이다.

다만, 지하철 기관사의 근무 여건은 그리 좋다고 보기 어렵다. 우선, 근무 시간이 불규칙적이고 교대를 돌면서 주기적으로 주말을 반납할 각오를 해야 한다. (일요일에 교회에 가야 하는 사람에겐 큰 마이너스) 아니, 승무직은 입사 지원할 때부터 주말 교대 근무에 동의한다는 각서를 제출한다. 비록 버스 기사처럼 교통 체증과 복잡한 도로, 매연, 차멀미로 인한 데미지는 없어서 좋지만, 몇 시간째 햇빛을 못 보면서 어두컴컴한 터널만 돌아다니는 것도 정서에 좋을 리가 없다. 자세한 건 예전 글을 참고할 것.

그래서 본인은 기관사가 그런 극단적인 방법으로 스스로 생을 마감할 정도이면, 근무 환경에 적응을 못 해 극도의 우울증에 시달렸거나, 아니면 정신적으로 문제가 있어서 사내에서 왕따이거나 대인 관계에 심각하게 문제가 있어서 그럴 거라고 생각했다. 이 사고에 대한 후속 뉴스 보도를 보니 내 추측이 얼추 맞는 것 같다.

특히 도철은 1인 승무를 국내 최초로 도입한 지하철 회사이기 때문에 그 점이 노조로부터 두고두고 까여 왔으며, 이번 기관사 자살 사건을 계기로 그게 또 부각되었다. 하지만 차량 상주 승무원 수의 최소화는 철도 기술의 엄연한 트렌드이기 때문에 그 자체가 근본적인 까임거리가 될 수는 없을 듯. 도철은 1990년대 중반에 1인 승무로도 모자라서 아예 무인 운전까지 시도한 적이 있는 과감한 회사이긴 하다만 말이다. (운전실이 없는 완전 무인 운전인 신분당선 전동차에도 승객들과 부대끼는 진짜 승무원이 객실에 한 명 있긴 함.)

난 우리나라 지하철의 스크린도어를 보면 무척 놀라움을 느낀다. 오죽했으면 국가에서 천문학적인 돈을 들여서 2009~2010년을 전후하여 서울 지하철의 거의 모든 역들에다 스크린도어를 도배해 버렸을까? 수백 개에 달하는 그 많은 지하철역들에다 스크린도어를 이렇게 단기간에다 모두 설치한 나라는 세계에서도 유례를 찾기 어려울 것이다. 거의 기네스북 감이 아닐까?

그만치 지하철 자살 러시가 심각한 사회 문제로 대두되고 있었기 때문에 정부가 그런 무리수를 둔 것이다. 뉴스 기사들을 검색해 보면, 당장 본인이 이용하는 집 근처의 지하철역에서만 해도 최소한 3명이 각각 2004년, 2007년, 2008년에 선로 투신으로 목숨을 끊은 적이 있다는 기록이 나온다. 그나마 스크린도어가 설치되면서 2008년 기록이 마지막이 된 것이다.

지난 2010년 8월 23일, 분당선 태평 역에서 발생한 인명 사고에도 본인은 직접적인 영향을 받았다. 이건 사람이 죽지는 않아서 큰 사고로 보도되지는 않았지만, 대학원 입학을 앞두고 사람 만날 일이 있어서 분당에서 학교로 가는 길이었는데 열차 지연으로 인한 불편을 겪었다. 분당선에서 태평과 야탑 역은 2012년 3월 현재까지도 아직 스크린도어가 없다. 다만, 공사 중이긴 하다.

직접 겪어 보지도 않고 남이 처한 상황을 폄하하고 싶지는 않다만..
저렇게 작정하고 자살하려는 사람을 치는 사고를 낸 기관사는 하나도 잘못이 없는데 왜 그 정도까지 충격과 정신 공황을 겪는지 잘 모르겠다.

사람이 끔살 당하는 장면을 라이브로 본 것에 대한 정신적 데미지는 있겠지만, 자기가 살인을 저질렀다는 식의 생각엔 제발 안 빠졌으면 좋겠다. 이건 불의의 사고로 사람이 죽은 것도 아니고, 작정하고 자살하려는 사람이 죽은 것이다. 사형 집행관만큼이나 하나도, 전혀 죄책감 가질 필요가 없다. 일본처럼 죽은 사람 유족에게 민폐에 대한 책임으로 벌금을 때리지 않은 걸 고맙게 여겨야 할 판.

그나저나 작년 12월엔 공항 철도에서 정말 어이없는 사고가 난 적이 있다. 막차 운행이 아직 안 끝났는데도 끝난 줄 알고, 선로 보수 인부 여러 명이 선로로 들어갔다가 전동차에 치여 끔살 당한 것이다. 한밤중인 데다 요즘은 전동차가 기술이 좋아서 워낙 조용하게 달리기 때문에, 저런 상황에서는 답이 없다..;; 기관사도 갑툭튀한 사람들 보고 간이 떨어질 정도로 얼마나 놀랐을까? 이건 승강장 자살도 아니고..! 텅 빈 막차를 몰고 저기까지만 가면 오늘 근무 끝이고 들어가서 잘 일만 남았을 텐데!

원래 그런 건 다 규정이 있다. 지하철의 경우, 심야에 업무를 위해 선로 내부로 들어가는 직원은 열차 운행 영업이 종료된 정도가 아니라 아예 전차선(전깃줄)이 완전히 단전된 걸 확인하고 들어가야 한다. 사탄의 인형 처키는 건전지 없이도 움직였지만-_-, 현실의 전동차는 그렇지 않으니까. -_-;; 선로 보수· 청소 차량들은 애시당초 전기가 아닌 기름으로 움직인다.
그러니 내가 보기엔 저 사고는 일차적으로는 일종의 근무 기강 문제이다. 거기에다 또 선로 보수 인력이 외주 용역이다 보니 앞뒤 손발이 안 맞은 것일 수도 있겠고.

그랬는데, 고의적인 자살이 아니면서 사람이 여럿 죽는 사고가 나 버리는 바람에, 내 기억이 맞다면 애꿎은 전동차 기관사도 일단은 잡혀 들어갔다. 경적을 안 울리고 완벽한 수준의 안전 조치를 안 취했다고... 구속인가? 한국은 법을 적용하는데 동기나 과정보다 결과를 더 우선시하는 풍토가 있어서..;; 안타까운 일이다. 기관사의 입장에서는 저건 정말 운이 없어서 이런 신세로 전락한 것이나 마찬가지이다.

물론, 하루에 전철이 수송하는 승객 수가 얼마나 엄청나고 방대한지를 감안하면, 그에 비해 저 정도로 예외적인 급으로 발생하는 사고는 정말 극소수이고 새 발의 피도 안 된다는 것이 분명한 사실이다. 철도는 정말 수송 효율이 좋고 안전한 교통수단이 맞다. 오늘도 수도권 시민들의 운송을 책임지는 지하철/전철 기관사님들 힘내시길.

Posted by 사무엘

2012/03/25 08:26 2012/03/25 08:26
, ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/659

1.
겨울방학도 이렇게 끝이 보인다.
본인이 이번 방학 때 이뤄낸 가장 뜻 깊은 성과를 둘 꼽자면 하나는 <날개셋> 한글 입력기 6.5이고, 다른 하나는 HFT(아래아한글 전용 글꼴) 해킹 성공이다.

사용자 삽입 이미지

저 글꼴들을 MS 오피스 제품에서도 쓰고 PDF도 자유롭게 만들고, 무엇보다도 화면에 anti-alias가 된 부드러운 모습으로 보니 너무 좋다.. 근성의 reverse engineering 오덕질을 통해서 이뤄 낸 성과. ㅋㅋ 새로운 글꼴로 아무 글이나 마구마구 써 보고 싶다.

또한, 날개셋 6.5 버전은 아직까지도 프로그램을 크게 고친 부분이 없을 정도로 역대 최고로 손꼽히는 안정성과 완성도로 잘 만들어졌다. 대단히 만족스럽다. 그래서 당분간 안심하고 완전히 새로운 기능 연구와 논문 준비 등에 전념할 수 있을 것 같다.

2.
윈도우 7은 콘솔(명령창)에서 세벌식을 쓰면 '다다.' 이렇게 한글이 덧나는 버그가 있다. 왜 SP1에서도 안 고쳐진 걸까? 예전에도 언급한 적이 있지만, 이런 건 두고두고 디스를 좀 해 줘야 된다.
윈도우 운영체제는 NT 계열도 꼭 이런 이상한 버그가 역사적으로 하나씩 있었다.

과거 2000은 256색으로 들어갔다가 돌아오면 군청색 제목 표시줄의 색깔이 옅어지는 버그를 유일하게 갖고 있어서 SP4에서까지 안 고쳐졌고,
XP는 테마를 저장했다가 불러오면 Luna 모드에서도 메뉴가 클래식 모드처럼 표시되어 색깔이 이상해지는 버그가 있는데 역시 SP3에서까지 안 고쳐지고 저렇게 끝났다.

이런 이유로 인해 본인은 한글 IME 개발과 관련하여 까탈스럽고 안 좋은 추억 때문에 남들이 7 좋아하는 것만치 7을 좋아하지는 않으며, 남들이 비스타 싫어하는 것만치 비스타를 싫어하지도 않는다. -_-;; 뭐, 비스타도 희한한 버그 의심 증상이 하나 있긴 했으나, SP1에서 곧바로 고쳐짐.

3.
<날개셋> 편집기는 txt 확장자를 자기 프로그램으로 연결한다거나 하지는 않는다.
그렇기 때문에 이렇게만 놔 둬서는 윈도우 7의 jump list를 활용하지 못한다. (윈7 사용자 중에 jump list가 뭔지 모르는 분은 없으리라 생각되지만, 어쨌든 모른다면 검색 요망)

탐색기에서 txt 파일을 우클릭하여 '연결 프로그램 선택 → 찾아보기' 등을 골라서 <날개셋> 편집기를 한 번이라도 지정한 뒤(꼭, 기본 연결을 시켜 놓을 필요는 없고 이렇게만이라도), 나중에 <날개셋> 편집기의 열기 대화상자로 txt 파일을 열고 나면 그건 앞으로 jump list 에 등재되게 된다.

jump list를 좀 더 창의적으로 활용하면, 편집기는 당장 저런 기본 용도만으로도 충분할 것이고(최근 파일 열기),
변환기는 클립보드 변환 같은 자주 사용할 만한 task를 등록해 놓을 수 있겠으며,
입력 패드는 자주 쓰는 보조 입력 도구를 실행과 동시에 바로 꺼내 놓게 할 수 있겠는데 더 자세한 활용법에 대해서는 좀 더 시간을 두고 고민해 봐야겠다.

4.
얼마 전엔 드디어 <날개셋> 한글 입력기 프로젝트를 비주얼 C++ 2010용으로 정식으로 포팅해서 빌드해 봤다. 특이사항으로는,

- 사소한 컴파일 에러가 있었으나, 더 깔끔하고 분명한 코드를 만드는 데 도움이 되는 에러였으며 쉽게 잡았다.
- VS 2010의 빌드 시스템은 $(TargetPath) 변수의 값을 예전과는 다른 방식으로 부여하는 듯하다. 그래서 이걸 조정하는 노가다를 매 프로젝트들마다 좀 해야 했다.

이것 외엔 딱히 트러블이 없었으니 무리 없이 잘 갈아탔다.

동일한 옵션으로 빌드했지만 x86 바이너리(32비트)들은 VS 2008과 비교했을 때 크기가 살짝 더 커졌고, x64 바이너리(64비트)들은 살짝 더 줄어들었다.
같은 코드를 빌드했을 때 바이너리 크기가 조금씩 더 커지는 건, VC6 이래로 개발툴이 업데이트되면서 비교적 일관되게 관찰되는 추세였다. 최적화가 인라이닝 등 코드 크기를 더 키우는 쪽으로 행해져서 그런 것 같다.

비주얼 C++ 2010은 C/C++ 언어도 빌드 중에 'Linking...'이라는 말이 안 뜨고 generating code...에 링킹이 포함되는 듯하다.
똑똑한 인텔리센스는 부러운 점이긴 하다만, 너무 커진 덩치, 이질감이 생긴 GUI와 도움말 시스템 때문에 2010은 개인적으로 언제쯤 도입하게 될지 모르겠다. 다만, 리소스 에디터가 드디어 윈도우 비스타의 PNG 내장 아이콘(256*256)까지 제대로 표시해 주는 점은 마음에 든다.

5.
내 경험상 윈7은 USB 메모리(스틱 or 외장 하드) 매체의 제거를 예전 OS에 비해 더 관대하게, 더 빨리 허용해 주는 것 같다. 해당 드라이브를 사용하는 프로그램들을 다 껐는데도 '사용 중이기 때문에 제거 못 함' 꼬장 때문에 할 수 없이 아예 OS를 로그오프하거나 아니면 그냥 강제로 매체를 제거해 버린 일이 거의 발생하지 않았다.

그리고 다른 얘기이다만, 윈도우 7은 Taskbar에 있는 각종 프로그램들 아이콘과 시스템 트레이의 아이콘을 드래그하여 마음대로 순서를 바꿀 수 있는 게 무척 인상적이다.
7에서 새로 바뀐 작업 표시줄은, 실행 중인 프로그램과(동일 프로그램이 중복 실행된 것 포함) 그렇지 않은 프로그램의 구분이 잘 되아 보이는 걸 개인적으론 안 좋게 생각했었다. 그러나 한동안 써 보니까 이게 그럭저럭 나쁘지 않은 디자인이라는 생각이 든다.

무엇보다도, 창을 몇 개씩 띄워 놔도 많이 띄웠다는 느낌이 심리적으로 안 드는 게 인상적임.
옛날에 윈9x의 전통적인 시작 메뉴 시절엔, 컴을 몇 년 쓰면서 수십 종류의 프로그램들을 설치하고 나니까 '프로그램' 메뉴 옆으로 프로그램 리스트가 두 칼럼 이상씩 차지하는 크고 아름다운 면적으로 주렁주렁 달렸던 거 기억하는가?

또한 인터넷 서핑하다가 브라우저 창이 열몇 개씩 넘어가니까 작업 표시줄 모습이 가히 가관으로 바뀌던 것도 기억하시는지? 윈도우 7은 복잡도를 제어하는 디자인에 무척 신경을 썼다..

6. 윈도우 비스타와 7 모두, 로그인 화면에서 암호를 입력하고 나면, 암호가 맞든 틀리든 일단 Welcome부터 출력하면서 설레발을 치다가 그 뒤에 암호가 틀렸으면 사용자 진입을 거부한다. “안 돼! / 돼!”도 아니고 츤데레도 아니고 이건 도대체 무슨 디자인일까?? -_-;;
암호가 맞을 때만 Welcome을 출력하게 개선되면 좋겠다.

7.
윈도우 9x는 프로그램을 조심해서 짜는 데에 도움을 준다.
NT 계열에서는 해제했던 메모리를 중복 해제하거나, 자원을 반납· 해제하는 걸 깜빡하는 식으로 대충 대충 짜도 당장 티가 안 나며 그냥 넘어간다. 그러나 9x에서 그랬다간 얼마 못 가 시스템 자원이 바닥난다거나 바로 뻗어 버리기 때문에, 이런 차이 덕분에 프로그램을 윈도우 9x에서 테스트하다가 버그를 발견하여 구조적인 문제를 해결한 경우가 지금까지 종종 있다.

아마 <날개셋> 한글 입력기도 한 2~3년 동안 윈9x에서 테스트를 안 한 채 개발을 계속하다가 버전이 1.0쯤 올라간 뒤에 다시 윈9x용으로 테스트하면, 여기저기서 원인을 알 수 없는 버그가 자꾸 튀어나와서 단순히 유니코드 API layer를 쓰는 것만으로는 윈9x를 결코 다시 지원할 수 없는 수준에 이를 것이다.
지금 <날개셋> 한글 입력기 소스가, 비록 같은 C++언어이지만 비주얼 C++ 6.0으로는 도저히 개발을 계속할 수 없는 이질적인 단계에 도달한 것처럼 말이다(각종 문법 차이 때문).

Posted by 사무엘

2012/02/23 08:33 2012/02/23 08:33
, , , ,
Response
No Trackback , 8 Comments
RSS :
http://moogi.new21.org/tc/rss/response/645

« Previous : 1 : ... 5 : 6 : 7 : 8 : 9 : 10 : 11 : 12 : 13 : Next »

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2026/10   »
일 월 화 수 목 금 토
        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:
4199048
Today:
2690
Yesterday:
2133