프로그래밍 언어가 제공하는 기본 라이브러리에는 단순히 자주 쓰이는 자료 구조나 알고리즘 외에도, 운영체제에다 요청을 해야 지원받을 수 있는 기능이 일부 있다. 메모리를 할당하거나 파일을 읽고 쓰는 작업이 대표적인 예이다. C/C++ 라이브러리라 해도 그런 기능은 궁극적으로 Windows API 같은 저수준 API를 호출함으로써 제공하는 셈이다.

그러니 프로그래머로서는 굳이 이식성을 염두에 두고 작성하는 코드가 아니라면, 언어가 제공하는 API보다 운영체제가 제공하는 API를 직통으로 쓰는 게 성능면에서 낫지 않나 하는 생각을 하게 된다.
이게 완전히 잘못된 생각은 아니다. 그러나 그렇지 않은 경우도 있으므로 주의해야 한다.

예를 들어, 윈도우 API에 있는 ReadFile/WriteFile과, C 라이브러리에 있는 fread와 fwrite를 생각해 보자.
C 라이브러리의 소스를 보신 분은 있겠지만, 일례로 fwrite는 내부적으로 _write 함수를 호출하는 형태이고, 두 함수만 해도 소스 코드가 수백 줄에 달한다. 뭔가 추상화 계층을 거치는 게 있고 복잡하다. 그러면서 _write 함수의 한쪽 구석에 결국은 WriteFile 함수를 호출하는 부분이 있다. fwrie가 WriteFile 직통보다 빠를래야 빠를 수가 없어 보인다.

그런데 윈도우 환경에서 프로그래밍을 오래 해 본 분은 경험적으로 아시겠지만, 몇 바이트짜리 소량의 I/O를 수백, 수천 번씩 반복해서 시켜 보면 fread/fwrite가 ReadFile/WriteFile보다 훨씬 더 빠르게 수행된다.
그렇다. C 함수는 내부적으로 버퍼링? 캐싱?을 해서 소량의 I/O는 뭉쳤다가 몰아서 한꺼번에 하는 반면, 운영체제 API는 곧이곧대로 매번 오버헤드를 감수하면서 I/O를 직통으로 하기 때문이다.

물론, 요즘은 운영체제가 자체적으로 디스크 캐싱을 다 하는 게 대세이지만, C 함수는 더 상위 계층에서도 캐싱을 하는 걸로 보인다. 이게 성능 차이가 굉장히 많이 난다.
<날개셋> 한글 입력기에서 1년 전쯤에 공개된 지난 6.2 버전의 README를 보면, 편집기의 파일 저장 및 변환기의 변환 속도가 훨씬 더 빨라졌다고 적혀 있다. 이것의 비결이 바로 저 특성을 이용해서 파일 I/O 속도를 향상시킨 것이었다.

메모리 할당도 마찬가지이다.
운영체제는 프로세스마다 heap이라는 가상 메모리를 둬서 프로그램이 다수의 작은 메모리 덩어리를 동적으로 요청할 때 빨리 빨리 반응할 수 있게 하고 있다. 연결 리스트나 트리 같은 자료구조는 메모리 할당이 잽싸게 안 되면 성능이 크게 떨어질 테니 말이다.
(이때 heap은 자료 구조 heap하고는 전혀 관계 없는 개념이므로 혼동하지 말 것.) 그래서 윈도우 운영체제에서 C 라이브러리의 malloc 계열 함수는 HeapAlloc이라는 API 함수를 호출하는 상위 계층이다.

내 경험상으로는 요즘의 NT 커널 윈도우는 HeapAlloc와 malloc, 그리고 HeapFree와 free가 성능 차이가 거의 느껴지지 않는다. 그러나 과거의 윈도우 9x 시절에는 그렇지 않았다.
“윈도우 9x에서는 이 함수는 진짜로 작은 메모리 블록에만 최적화되어 있기 때문에, 이걸로 수 MB에 달하는 메모리를 한꺼번에 여러 번 할당하면 성능이 크게 떨어지고 프로그램이 느려짐. 그 경우엔 다른 메모리 할당 함수를 쓰기 바람.”이라는 경고문이 MSDN에 명시되어 있었다.

내부적으로 그 함수가 어떻게 구현되어 있는지는 잘 모르겠지만, 내가 테스트 해 보니 진짜 그랬다. 9x에서는 프로그램이 뻗은 게 아닌가 싶을 정도로 도저히 견딜 수 없이 느려졌다.
이때에도 윈도우 API가 아닌 C 라이브러리의 malloc 함수는 랙 없이 잘 동작했다. 대용량 메모리 할당 요청이 왔을 때 가상 메모리 주소를 다시 잡는 등 대비가 되어 있어서 그런 것 같다.

원론적으로야 추상화 계층이 있는 언어 API보다는 운영체제 API 직통이 더 빠를 수밖에 없는 게 맞다. 사실, Windows API로도 모자라서 NTDLL처럼 아예 문서화되어 있지도 않은 곳에 있는 native API를 사용하는 프로그램이 있기도 하고 말이다.

그러나 프로그램의 이식성까지 희생하면서 굳이 직통 API를 쓰고자 한다면, 위에서 예를 들었듯이, 그 API의 특성을 잘 알고 쓰는 게 무엇보다도 중요하다고 하겠다. C++ 라이브러리야 객체지향 구현을 위해서 bloat되는 게 불가피하다고 쳐도, 그보다는 더 단순한 C 라이브러리의 추상화 계층은 그저 불필요한 잉여밖에 없는 건 아닐 것이기 때문이다.

Posted by 사무엘

2012/08/20 08:25 2012/08/20 08:25
, ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/722

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

오늘날 PC에서 명맥을 유지하며 살아있는 데스크톱용 운영체제는 역시나 윈도우, 맥, 리눅스 3관왕이다. 다만 이들이 대등한 점유율이 절대 아니며, 셋의 점유율은 공비가 무려 10에 육박하는 등비수열을 이룬다.

맥이야 x86 계열 CPU로 갈아타고 기계의 가격도 내리면서, 옛날에 비해서야 정말 많이 대중화가 되었다. 또한 아이폰/아이패드가 모바일에서 워낙 큰 성공을 거둔 덕분에 맥북/아이맥까지 반사 이익을 보고 있기도 하다. 아이폰/아이패드에서 돌아가는 소프트웨어를 만들려면 결국 그 계열의 PC가 필요하니까 말이다.

그래도 윈도우에 비하면야 맥 사용자는 정말 10% 이내의 소수이다. 맥OS를 작정하고 써 볼 의향이 있는 게 아니라면, 맥 계열 기계는 비슷한 사양의 일반 컴퓨터보다 여전히 비싸며 구입 후에 서비스도 구리고 키보드· 마우스의 일부 동작 방식이 이질적이기 때문에 덥석 권할 게 못 된다. 솔직히 나도 지금의 맥북 살 돈으로 일반 노트북을 샀다면 아마 화면이 두 배 정도 더 큰 걸 살 수 있었지 싶다. 그러나 ‘스잡빠’, ‘앱등이’로 대표되는 굳건한 추종자도 있는 마당에, 이쪽 진영은 결코 없어지지는 않을 것이다.

맥은 소수이지만 인지도라도 있지 리눅스는 그조차도 없다. 리눅스를 서버도 아닌 데스크톱 로컬 환경의 주 운영체제로 쓰는 사람은 가히 소수 중의 소수이다. 작정하고 MS 진영을 반대하고 철저한 copyleft 정신으로 무장한 컴덕후 해커이거나, 잡스를 숭배하는 것처럼 리처드 스톨먼을 숭배하는(최소한 그의 인격이 아니면 그의 이념을) 사람 정도만이 리눅스를 쓰지 않을까 싶다.

물론, 맥이 예전보다 접근성이 개선된 것만큼이나 리눅스도 옛날에 비해서는 초보자가 쓰기 정말 편해지긴 했다. 하지만 그래도 초보자가 쓰기엔 리눅스는 인지도 있는 응용 프로그램이 부족하고, 뭘 세팅하고 바꾸려면 유닉스 명령줄을 다뤄야 하는 등 생소한 면모가 적지 않다.

사용자 삽입 이미지
(연구 목적으로 2010년 무렵에 VM을 만들어서 돌려 본 우분투 리눅스 9.x의 화면이다. 한때 리눅스의 그래픽 셸은 GNOME이냐 KDE냐로 갈라져 혼란스러운 편이었으나, 요즘은 결국 둘 다 지원하는 쪽으로 가는 추세라 한다.)

20년이 넘게 도스와 윈도우에만 길들여지고 10년이 넘게 윈도우 프로그래밍만 해 본 본인의 입장에서 맥 OS에 존재하는 주목할 만한 특징을 간추려 보면 다음과 같다.

가장 먼저, 운영체제의 시스템 메뉴와 응용 프로그램의 메뉴가 한데 완전히 통합되어 있다는 점이 매우 인상적이다.
Windows는 운영체제의 시스템 메뉴에 해당하는 시작 메뉴가 task bar에 있다. 이것은 응용 프로그램의 창에 소속된 메뉴하고는 당연히 완전히 별개이다. CreateWindowEx 함수는 창을 생성할 때 메뉴 핸들도 별개로 받는다.

그러나 맥은 화면 상단에 항상 고정되어 있는 시스템 메뉴에 응용 프로그램의 메뉴가 얹힌 형태로 나타난다. 이런 건 윈도우에서는 OLE 개체 embedding 상태에서나 어렴풋이 볼 수 있는 모습이다.

사용자 삽입 이미지
(제목은 워드패드인데 도움말에 나와 있는 건 그림판. 어?)

응용 프로그램 메뉴는 파일이나 도움말 같은 기본적인 것만 남고, 그 사이엔 개체를 제공하는 프로그램의 메뉴가 뜨는 것 말이다. 요즘은 이런 디자인도 과거 유물로 치부되어 별도의 프로그램 창이 따로 뜨는 형태로 바뀌고 있지만. (MS부터가 자기네들이 만든 표준 메뉴 인터페이스를 구닥다리로 치부하고 안 쓰려 하니 말이다)

맥에서는 시스템 전체를 통틀어 pull-down 메뉴는 하나만 있으며, 한 순간에 현재 활성화되어 있는 프로그램의 메뉴 하나만 볼 수 있다. 문서 창에 메뉴가 따로 달려 있지 않다. 그리고 맥에서 돌아가는 GUI 응용 프로그램이라면 반드시 이런 디자인을 따라야만 한다.

윈도우에서는 대화상자 하나만 달랑 띄우고 따로 메뉴를 만들기는 곤란한 프로그램의 경우, 대화상자의 시스템 메뉴를 customize해서 보통 ‘이동 / 닫기’만 있는 그 메뉴에다가 About이라든가 Always on top 같은 추가 명령을 넣는 경우가 있다. 그러나 맥은 어떤 프로그램에게라도 무조건 기본 메뉴가 주어지니 그런 식의 테크닉이 존재하지 않는다.

맥은 그런 기본적인 인터페이스가 모든 응용 프로그램에서 무조건 동일하기 때문에 윈도우처럼 무슨 오피스 200x 스타일 메뉴나 도구모음줄을 만들어 주는 GUI 툴킷이라는 게 존재하지 않는다. 윈도우에서야 보급 메뉴 대신에 그 자리에다 싸제 메뉴 창을 얹어서 보급 메뉴처럼 동작하게만 만들면 custom UI를 손쉽게 만들 수 있지만, 맥은 그렇게 할 수 없으니 말이다.

비주얼 C++의 MFC 프로젝트 마법사를 보면, GUI 응용 프로그램을 전통적으로 MDI, SDI, 대화상자라는 세 형태로 분류한다. 그런데 맥에서는 어떤 형태의 프로그램도 일단은 가장 범용적인 MDI에서 시작한다고 볼 수 있다. 실제로 문서 창은 하나밖에 생성하지 않는 프로그램이라 할지라도 말이다. ‘<날개셋> 타자연습’을 맥용으로 만든다면, 프로퍼티 페이지의 밖에 존재하는 ‘사용자 로그인’이나 ‘종합 환경설정’ 같은 명령은 응당 메뉴에서 내리는 명령으로 바뀌어야 할 것이다.

또한 맥은 동일 프로그램의 중복 실행에 대한 개념이 Windows와는 다르다. 같은 프로그램은 한 번만 실행되고 그 동일한 프로그램이 여러 문서를 담당한다는 MDI 사고방식이 맥은 더욱 엄격하다. 그래서 맥 OS의 task bar에 해당하는 dock은 프로그램이 실행됐냐 안 됐냐의 여부만 표시되어 있지만, 이 아이디어를 차용한 윈도우 7의 task bar는 프로그램의 중복 실행 개수도 살짝 표시하고 있는 것이다.

이런 디자인 때문에, 맥에서 프로젝트 단위로 문서를 다루는 프로그램은 내부 구조가 많이 복잡해진다. 개발툴이 그런 예 중 하나이다. 비주얼 C++이야 여러 개를 실행해서 제각기 프로젝트를 열면 되지만, xcode는 프로젝트별로 각각의 문서창을 차지하고 있으면서 그 내부에서 또 파일을 편집하는 창을 관리해야 한다.

맥 응용 프로그램은 마지막 문서 창이 닫혀서 문서가 하나도 없는 상태에서 키보드 포커스가 다른 프로그램으로 넘어갈 때 자동으로 종료되는 편이다. 이런 동작 방식은 Windows에서는 볼 수 없던 모습이다.
물론 모든 프로그램이 그러는 건 아니다. 대표적으로 Finder도 파일 표시(윈도우로 치면 탐색기 창 같은) 창이 하나도 없이 포커스가 바뀌더라도 종료되지 않고 언제나 실행되어 있는다. 내장 웹브라우저인 사파리도 마찬가지이다.

키보드로는 Alt+F4에 해당하는 Cmd+Q를 누르면 언제나 프로그램이 종료된다. 단, 윈도우의 Alt+F4는 그냥 창을 닫는다는 보편적인 용도도 포함하는 단축키인 반면, Cmd+Q는 언제 어디서나 해당 응용 프로그램을 완전히 종료시킨다는 의미 차이가 있다.

윈도우 프로그래머라면, 맥에서는 저런 것뿐만이 아니라 응용 프로그램의 파일 구성까지 상상도 못 할 정도로 다르다는 사실에 더욱 놀라게 될 것이다. 단순 실행 파일 말고 GUI 응용 프로그램은 아이콘, 리소스, 구성 파일이 한데 담긴 패키지 형태로 배포된다. 파일 시스템상으로는 디렉터리 구조이지만 운영체제 내부에서는 가상적인 단일 패키지 파일로 취급된다.

맥에서는 그럼 레지스트리가 없으면 응용 프로그램의 설정 저장을 위해 어떤 기법을 쓰는지? 프로그램 추가/제거는 어떻게 하는지? 동일 개발자가 만든 여러 프로그램이 동일 코드를 공유하려면 DLL 같은 건 어느 디렉터리에다 집어넣으면 되는지? 맥은 윈도우 같은 32/64비트 코드 혼용 문제는 없는지?

알고 싶은 게 한두 가지가 아닌데 그런 걸 윈도우 프로그래머의 입장에서 잘 설명해 놓은 인터넷 사이트나 책은 아직까지는 난 못 봤다. 아무래도 둘은 서로 달라도 너무 달라서 두 플랫폼의 사고방식에 모두 완전히 통달한 개발자는 거의 없으리라 예상된다.;;

이렇듯, 그토록 쉽게 다가설 수 없는 이질감에도 불구하고, 맥 OS는 좀 써 보면 윈도우에서는 느낄 수 없는 참을 수 없는 고급스러움, 미적 감각이 느껴지는 건 부인할 수 없다. 맥 같은 녀석이 존재함으로써 IT 세계가 좀 더 다양해지고 MS의 독점을 약간이나마 견제하는 효과가 난 건 인류 전체의 관점에서는 그래도 이익이긴 한 것 같다.

Posted by 사무엘

2012/07/10 08:11 2012/07/10 08:11
, , ,
Response
No Trackback , 10 Comments
RSS :
http://moogi.new21.org/tc/rss/response/705

C/C++, 자바, C# 비교

전산학의 초창기이던 1950년대 후반엔 프로그래밍 언어의 조상이라 할 수 있는 코볼, 포트란 같은 언어가 고안되었다. 그리고 이때 범용적인 계산 로직의 기술에 비중을 둔 알골(1958)이라는 프로그래밍 언어가 유럽에서 만들어졌는데, 이걸 토대로 훗날 파스칼, C, Ada 등 다양한 언어들이 파생되어 나왔다.

이때가 얼마나 옛날이냐 하면, 셸 정렬(1959), 퀵 정렬(1960) 알고리즘이 학술지를 통해 갓 소개되던 시절이다. 구현체는 당연히 어셈블리어.;; 그리고 알골이 도입한 재귀호출이라는 게 함수형 언어가 아닌 절차형 언어에서는 상당히 참신한 개념으로 간주되고 있었다. 전산학의 역사를 아는 사람이라면, 컴퓨터를 돌리기 위해 프로그래밍 언어가 따로 만들어진 게 아니라, 프로그래밍 언어를 구현하기 위해 컴퓨터가 발명되었다는 걸 알 것이다.

알골 자체는 시대에 비해 언어 스펙이 너무 복잡하고 막연하기까지 하며, 구현체를 만들기가 어려워서 IT 업계에서 실용적으로 쓰이지 못했다. 그러나 후대의 프로그래밍 언어들은 알골의 영향을 상당히 많이 받았으니 알골은 가히 프로그래밍 언어계의 라틴어 같은 존재로 등극했다.

물론, 그로부터 더 시간이 흐른 오늘날은 알골의 후예에 속하는 언어인 C만 해도 이미 라틴어 같은 전설적인 경지이다. 중괄호 블록이라든가 C 스타일의 연산자 표기 같은 관행은 굳이 C++, 자바, C# 급의 언어 말고도, 자바스크립트나 PHP처럼 타입이 엄격하지 않고 로컬이 아닌 웹 지향 언어에도 그런 관행이 존재하니 말이다.

C가 먼저 나온 뒤에 거기에 OOP 속성이 가미되어 C++이라는 명작/괴작 언어가 탄생했다. C가 구조화 프로그래밍을 지원하는 고급 언어에다가 어셈블리어 같은 저급 요소를 잘 절충했다면, C++은 순수 OOP 개념의 구현보다는 역시 OOP 이념을 C 특유의 성능 지향 특성에다가 적당히 절충을 잘 했다. 그래서 C++이 크게 성공할 수 있었다.

잘 알다시피 C/C++은 모듈이나 빌드 구조가 컴파일 지향적이며, 거기에다 링크라는 추가적인 작업을 거쳐서 네이티브(기계어) 실행 파일을 만드는 것에 아주 특화되어 있다.

번역 단위(translation unit)라고 불리는 개개의 소스들은 프로그래밍에 필요한 모든 명칭들을 텍스트 형태의 다른 헤더 파일로부터 매번 include하여 참조한 뒤, 컴파일되어 obj 파일로 바뀐다.
한 번역 단위에서 참조하는 외부 함수의 실제 몸체는 어느 번역 단위에 있을지 알 수 없다. 어차피 링크 때 링커가 모든 obj 파일들을 일일이 뒤지면서 말 그대로 연결을 하게 된다.

이 링크를 통해 드디어 실행 파일이나 라이브러리 파일이 최종적으로 만들어진다. 실행 파일은 대상 운영체제가 인식하는 실행 파일 포맷을 따라 만들어지지만 static 라이브러리는 그저 obj들의 모음집일 뿐이기 때문에 lib 파일과 obj 파일은 완전히 같지는 않아도 내부 구조가 크게 차이가 나지 않는다.

이런 일련의 컴파일-링크 계층이 C/C++을 로컬 환경에서의 매우 강력한 고성능 언어로 만들어 주는 면모가 분명 있다. 또한 197, 80년대에는 컴퓨터 자원의 한계 때문에 원천적으로 언어를 그런 식으로 설계해야 하기도 했다.
그러나 오늘날은 대형 프로젝트를 진행할 때 C/C++의 그런 디자인은 심각한 비효율을 초래하기도 한다. 내가 늘 지적하듯이 C보다도 특히 C++은 안습할 정도로 빌드가 너무 느리고 생산성이 떨어진다.

그에 반해 자바는 문법만 살짝 비슷할 뿐 디자인 철학은 C++과는 완전히 다른 언어이다. 잘 알다시피 자바는 의도하는 동작 환경 자체가 native 기계어가 아니라 플랫폼 독립적인 자바 가상 기계이다. 컴퓨터 환경이 발달하고 웹 프로그래밍이 차지하는 비중이 커진 덕분에 이런 발상이 나올 수가 있었던 셈이다.

모든 자바 프로그램은 무조건 1코드, 1클래스이며(단, 클래스 내부에 또 다른 클래스들이 여럿 있을 수는 물론 있음), 심지어 소스 파일 이름과 클래스 이름이 반드시 일치해야 한다. 클래스가 곧 C/C++의 ‘번역 단위’와 강제로 대응한다. 그리고 컴파일된 자바 소스는 곧장 컴파일된 바이트코드로 바뀌며, 이것이 자바 VM이 있는 곳이라면 어디서나 돌아가는 실행 파일(EXE)도 되고 라이브러리(DLL, OBJ)도 된다. 물론, 여러 라이브러리들의 집합체인 JAR이라는 포맷도 따로 있기도 하고 말이다.

클래스 내부에 public static void main 메소드(멤버 함수)만 구현되어 있으면 곧장 실행 가능하다. C++은 C와의 호환을 위해 시작 함수가 클래스 없는 일반 main으로 동일하게 지정돼 있는 반면, 자바는 global scope이 존재하지 않고 모든 명칭이 클래스에 반드시 소속돼 있어야 하기 때문에 그렇다. javac 명령으로 소스 코드(*.java)를 컴파일한 뒤, java 명령으로 컴파일된 바이트코드(*.class)를 실행하면 된다.

다른 모듈을 끌어다 쓸 때도 import로 바이너리 파일을 곧장 지정하면 되니, 텍스트 파싱이 필요한 C++의 #include보다 효율적이다. 번거롭게 *.h와 *.lib (그리고 심지어 *.dll까지)를 일일이 따로 구비할 필요 없다.

요컨대 자바는 C++에 비해 굉장히 많은 자유도와 성능을 제약한 대신, C++보다 훨씬 더 손이 덜 가도 되고 빌드도 훨씬 빨리 되고 프로젝트 세팅도 월등히 더 간편하게 되게 만들어졌다. 함수 호출 규약, 인라이닝 방식, C++ symbol decoration, 링크 에러, CRT의 링크 방식, link-time 코드 생성 최적화 같은 온갖 골치 아프고 복잡한 개념들이 자바에는 전혀 존재하지 않는다.
C++이 벙커에 시즈 탱크에 터렛과 마인 등, 손이 많이 가는 테란이라면, 자바는 프로토스 정도는 되는 것 같다.

자바는 하위 호환성을 고려하지 않은 새로운 언어를 만든 덕분에 디자인상 깔끔한 것도 있지만, 상상도 못 할 편리함을 실현하기 위해 성능도 C++ 사고방식으로는 상상도 못 할 정도로 많이 희생한 것 역시 사실이다. 이는 단순히 메모리 garbage collector가 존재하는 오버헤드 이상이다.

그래서 요즘은 자바 바이트코드를 언어 VM이 그때 그때 실시간으로 네이티브 코드로 재컴파일하여, 자바로도 조금이라도 더 빠른 속도를 내게 하는 JIT(just in time)기술이 개발되어 있다. 비록 이 역시 한계가 있을 수밖에 없겠지만 한편으로는 구조적으로 유리한 점도 있다.

컴파일 때 모든 것이 결정되어 버리는 C++ 기반 EXE/DLL은 사용자의 다양한 실행 환경을 예측할 수 없으니 보수적인 기준으로 빌드되어야 한다. 그러나 자바 프로그램의 경우, VM만 그때 그때 최신으로 업데이트하여 최신 CPU의 명령이나 병렬화 테크닉을 쓰게 하면 그 혜택을 모든 자바 프로그램이 자동으로 보게 된다. 물론 C++로 치면 cout이 C의 printf보다 코드 크기가 작아지는 경지에 다다를 정도로 컴파일러가 똑똑해져야겠지만 말이다.

자바 얘기가 길어졌는데, 다음으로 C#에 대해서 좀 살펴보기로 하자.
C# 역시 네이티브 코드 지향이 아니라 닷넷 프레임워크에서 돌아가는 바이트코드 기반인 점, 복잡한 링크 메커니즘을 생략하고 C++의 지나치게 복잡한 문법과 모듈 구조를 간소화시켰다는 점에서는 자바와 문제 접근 방식이 같다.

단적인 예로, 클래스를 선언하면서 멤버 함수까지 클래스 내부에다 정의를 반드시 집어넣게 한 것, 그리고 생성자 함수의 호출이 수반되는 개체의 생성은 반드시 new를 통해서만 가능하게 한 것은 컴파일러와 링커가 동작하기 상당히 편하게 만든 조치이다. 이는 자바와 C#에 공통적으로 적용된다.

다만 C#은 자바처럼 엄격한 1소스 1클래스 체계는 아니며, 빌드 결과물로 엄연히 일반적인(=윈도우 운영체제가 사용하는 PE 포맷 기반인) EXE와 DLL이 생성된다. 물론 내부엔 기계어 코드가 아닌 바이트코드가 들어있지만 말이다.

C# 역시 클래스 내부에 존재하는 static void Main가 EXE의 진입점(entry point)이 된다. 그러나 C#은 자바 같은 1소스, 1클래스, 1모듈 구조가 아니기 때문에 여러 클래스에 동일한 static void Main이 존재하면 컴파일러가 어느 것을 진입점으로 지정해야 할지 판단할 수 없어서 컴파일 에러를 일으킨다. 링크나 런타임 에러가 아님. 진입점을 별도의 컴파일러 옵션으로 따로 지정해 주거나, Main 함수를 하나만 남겨야 한다.

여담이지만, C#의 진입점 함수는 자바와는 달리 첫 글자 M이 대문자이다. 전통적으로 자바는 첫 글자를 소문자로 써서 setValue 같은 식으로 메소드 이름을 지어 온 반면, 윈도우 세계는 그렇지 않기 때문이다(SetValue).
그리고 C#의 Main은 굳이 public 속성이 아니어도 된다. 어차피 진입점인데 접근 권한이 무엇이면 어떻냐는 식의 발상인 것 같다.

닷넷 실행 파일이 사용하는 바이트코드는 자바와 마찬가지로 기계 독립적인 구조이다. 그러나 그것의 컨테이너라 할 수 있는 윈도우 운영체제의 실행 파일 포맷(PE)은 여전히 CPU의 종류를 명시하는 필드가 존재한다. 그리고 32비트와 64비트에서 필드의 크기가 달라지는 것도 있다. 이것은 기계 독립성을 추구하는 닷넷의 이념과는 어울리지 않는 구조이다. 그렇다면 닷넷은 이런 상황을 어떻게 대처하고 있을까?

내가 테스트를 해 본 바로는 플랫폼을 ‘Any CPU’라고 지정하면, 해당 C# 프로그램은 명목상 그냥 가장 무난하고 만만한 x86 껍데기로 빌드되는 듯하다.
작정하고 x64 플랫폼을 지정하고 빌드하면 헤더에 x64 CPU가 명시된다. 뒤에 이어지는 바이트코드는 어느 CPU에서나 동일하게 생성됨에도 불구하고, 그 프로그램은 x86에서는 실행이 거부되고 돌아가지 않게 된다.

그러니, 64비트 네이티브 DLL의 코드와 연동해서 개발되는 프로그램이기라도 하지 않은 이상, C# 프로그램을 굳이 x64용으로 제한해서 개발할 필요는 없을 것이다. 다만, x86용 닷넷 바이너리는 관례적으로 닷넷 런타임인 mscoree.dll에 대한 의존도가 추가되는 반면 x64용 닷넷 바이너리는 그런 게 붙어 있지 않다. 내 짧은 생각으론, 64비트 바이너리는 32비트에서 호환성 차원에서 넣어 줘야 했던 잉여 사항을 생략한 게 아닌가 싶다.

DLL에 기계 종류와 무관한 리소스나 데이터가 들어가는 일은 옛날부터 있어 왔지만, 닷넷은 코드조차도 기계 종류와 무관한 독립된 녀석이 들어가는 걸 가능하게 했으니 이건 참 큰 변화가 아닐 수 없다. 네이티브 쪽과는 달리 골치 아프게 32비트와 64비트를 일일이 신경 쓸 필요가 없고, 한 코드만으로 x86(-64) 계열과 ARM까지 다 커버가 가능하다면, 정말 어지간히 하드코어한 분야가 아니라면, 월등한 생산성까지 갖추고 있는 C#/자바 같은 개발 환경이 뜨지 않을 수 없을 것 같다. C++과 자바, C#을 차례로 비교해 보니 그런 생각이 들었다.

Posted by 사무엘

2012/06/16 19:37 2012/06/16 19:37
, , ,
Response
No Trackback , 7 Comments
RSS :
http://moogi.new21.org/tc/rss/response/696

※ 프로세서 정보 얻기

현재 컴퓨터의 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

1.

옛날에 폰 노이만(폰 노이만 구조라는 컴퓨터 근간을 닦은 사람)이라는 사람은 기계어로 직접 컴퓨터에다 코딩을 하는 기계어 매니아였다. 기계어가 너무 불편하다고 어느 제자가 어셈블리 비슷한 상위 계층 언어를 만들려 하자 “귀한 컴퓨터 자원으로 쓸데없는 짓이나 한다”고 그를 나무랐다.;;
이거 마치 희대의 저격수인 시모 하이하가 조준경 그딴 걸 왜 쓰냐고 나무란 것과 비슷한 맥락 같다.;;
 
그 반면, 데이크스트라(다익스트라. 그래프 탐색 알고리즘을 고안한 그 사람)는 어셈블리/기계어 같은 언어를 비생산적이고 삽질스럽다고 아주 강하게 디스한 것으로 잘 알려져 있다. 그도 그럴 것이, 구조화 프로그래밍을 주장하면서 GOTO문을 배격한 사람이 기계스러운 BRANCH 따위가 난무하는 저급 언어를 좋아할 리가 없겠다.
 
둘 다 우주괴수급의 천재 수학자 및 전산학자이다만, 이런 식의 관점의 차이가 존재하는가 보다. 재미있는 일이다.
 
2.

밸브 코퍼레이션의 창립자 게이브 뉴웰 (카운터 스트라이크, 하프 라이프, 포탈 등의 게임 개발사)
페이스북의 창립자인 마크 주커버그 (나보다 더 어림..)
마이크로소프트의 창립자인 빌 게이츠 (설명 불필요)
 
억만장자 IT 기업인인 이들은 모두 하버드 대학 중퇴자라는 공통점이 있다. 다 미국인이기도 하고.

3.

개발자가 소프트웨어를 팔아서 먹고 살려면

(1) 관공서나 기업에서 구입하지 않을 수 없는 핵심적인 프로그램을 개발 (교육이나 업무 분야)
(2) 하드웨어에 같이 들어가는 프로그램을 개발해서 하드웨어와 함께 판매
(3) 온라인 게임 개발 (늘 서버 접속을 하기 때문에 이용료 징수 가능)
(4) 아니면 개인을 대상으로도 유료 판매가 가능한 유통 경로(앱스토어, 스마트폰 등)를 거치는 프로그램 개발

중 하나로는 가야 할 것 같다. 저 네 가지 말고 혹시 다른 방법이 있을까?

4.

내가 맥 OS에 매력을 느끼는 큰 이유 중 하나는.. 폰트 래스터라이저가 정말 짱이라는 점.
똑같은 글꼴을 화면에 찍어 내는 퀄리티가 서로 게임이 안 되는 수준이니...

사용자 삽입 이미지
위는 Windows, 아래는 맥이다.
Windows는 ClearType을 시키면 맑은 고딕처럼 전용 힌팅이 들어간 글꼴이 아니면 그냥 안티알리어싱이 없는 것보다 약간 나은 정도로만 찍히는 반면.
Mac은 힌팅이 없다시피한 글꼴도 Adobe Reader 이상의 퀄리티로 찍어 준다!

5.

그나저나 맥 OS는 Finder (윈도우로 치면 탐색기)에서 파일이나 디렉터리의 이름을 바꾸는 게 엔터이고, 실행하거나 여는 게 Cmd+아래라니 참 희한하다. 윈도우라면 이름 바꾸는 건 F2이고, 여는 게 응당 엔터인데 말이다.

6.

과거에 MS 오피스가 2003에서 2007로 버전업되었을 때 비주얼이 화려해지고 좋아진 기능이 분명 적지 않았지만, 내게는 굉장히 마음에 안 드는 변화도 있었다. 그것 중 하나는 파워포인트에서 '컬러 타자기' 애니메이션 효과가 굉장히 느려져서 랙이 심해지고 프레임 수가 감소한 것이었다. 글자가 말 그대로 타자기로 찍듯이 한 글자씩 천천히 나타나는 것 말이다. 그렇게 현란하거나 CPU의 부하가 심한 효과도 아니다.

그랬는데 2010을 나중에 써 보니, 마치 2003처럼 애니메이션이 다시 매끄러워져 있었다.
혹시 컴퓨터가 빨라지고 화면 해상도가 낮아져서 그런 게 아닌가 싶어서 컴퓨터를 바꿔서 확인해 보았다.
그랬더니 같은 1280*1024 화면이라도 역시 2010에서는 Core2 duo급 컴에서도 매끄럽게 나오는 반면, 2007에서는 쿼드코어 i5급 컴에서도 버벅거렸다.

그래서 이것은 소프트웨어적인 알고리즘 개선 덕분이라는 결론을 내리게 됐다. 2007과 2010 사이엔 이런 차이도 존재하는가 보다.

7.

근래엔 <날개셋> 한글 입력기의 구성 파일들에 대해서 바이러스 및 악성 코드 진단 문의가 부쩍 늘었다. 그래서 그에 대한 개발자의 공식 입장을 내 홈페이지에다가도 게시할 필요를 느끼게 됐다.

결론부터 말하자면 당연히 “바이러스 아님”이다. 모든 프로그램들은 바이러스도, 안티바이러스(일명 백신)도 알지 못하는 100% 청정 컴퓨터에서 개발되며, 개발 환경에서 갓 빌드된 직후의 실행 파일들이 곧바로 설치 패키지로 포장된다. 바이러스 같은 게 들어갈 일이란 없다. 이 일 때문에 본인에게 문의하면 언제나 동일한 대답밖에 돌아올 게 없으며, 그 외에 더 할 말이 없음을 이 자리에서 밝히는 바이다.

더 근본적으로는 실행 파일과 MSI 패키지가 디지털 서명을 받지 못한 관계로, 웹브라우저부터가 빨간 경고와 함께 <날개셋> 프로그램의 다운로드를 저지(discourage)하는 것도 좀 아쉬운 점이다. 이건 훗날 프로그램이 더 나은 수익원과 배포 통로를 확보했을 때에나 해결 가능할 것 같다.

그래서 이 참에 아예 프로그램 다운로드 페이지에다가 설명을 써 놨다. “10년이 넘게 인생을 걸며 이 프로그램을 개발하고 개선해 온 저를 믿으신다면, 그런 보안 경고들은 모두 무시하고 안심하고 사용하시기 바랍니다.

문득 생각해 볼 문제: 비주얼 C++이나 그에 상응하는 개발툴이 설치된 컴퓨터를 자동으로 감지하여 프로그램이 링크될 때 쓰이는 C 라이브러리 같은 lib, obj 파일을 감염시키는 컴퓨터 바이러스 프로그램이 존재할까? 처음부터 바이러스에 감염된 프로그램이 생성되도록? -_-;;

Posted by 사무엘

2012/05/19 08:22 2012/05/19 08:22
, ,
Response
No Trackback , 9 Comments
RSS :
http://moogi.new21.org/tc/rss/response/684

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

잘 알다시피 <날개셋> 한글 입력기는 Windows용 한글 IME이다(IME이기만 한 건 아니지만). 이 분야는 경쟁 프로그램이 거의 없다시피하기 때문에, MS가 직접 공급하는 IME를 제외하면 3rd party 한글 IME 중에서는 <날개셋> 한글 입력기가 가히 독주를 하는 중이다. 그 이유로는,

첫째, 모바일용도 아니고 PC용으로는 한글 입력 방식이 딱히 더 만들 게 없다고 여겨지고 있어서인 것 같다. 그리고 딱히 돈이 되는 것도 아니니까 말이다. 싸제 IME가 활발히 쓰이고 있는 중국어· 일본어 IME의 개발 환경과 비교했을 때 이것이 크게 다른 점이다.

그리고 둘째로는, 윈도우용 IME라는 게 여타 운영체제의 IME와 비교해 보더라도 그 아키텍처와 스펙이 미치도록 폐쇄적이기 때문이다. 비록 프로토콜이 공개돼 있는 건 있지만, 그것만 참고해서는 쌩쌩 잘 돌아가는 한글 IME를 절대로 만들 수 없다. 문서화되지 않은 무수히 많은 상황에 대한 대비를 해야 되는데 이걸 이제 와서 혼자 처음부터 만든다는 건 불가능에 가깝다.

그럼에도 불구하고 <날개셋> 한글 입력기 말고 ‘싸제’ 한글 IME가 전혀 없는 건 아니다. 본인은 MS가 개발하지 않은 한글 IME를 최소한 두 종류를 더 알고 있다.

※ 새나루

윈도우 DDK에 등재되어 있는 FakeIME라는 일본어 예제 IME를 고쳐서 만들어진 한글 IME이다. 오픈소스 진영에서 만들어진 프로그램답게 소스 공개이다. 개발자들은 본인처럼 아예 대놓고 국어 정보학 쪽으로만 발을 들인 것도 아닌데 이쪽으로 조예가 굉장히 깊은 고수 프로그래머이다.

싸제 IME답게 여러 실험적인 기능이 많아서 실속이 있으며, 그러면서도 <날개셋>보다 덩치 작고 가볍다는 이점이 있다. 특히 <날개셋>이 개발 방향의 특성상 의도적으로 더 지원하지 않는 다음 기능들 때문에 새나루를 선호하는 사람도 있다.

키보드 드라이버 차원에서 드보락 글자판과의 연동: 쉽게 말해, 단축키까지 드보락 식으로 나오면서 그 상태에서 한글 입력까지 지원.

글자가 아니라 단어 전체를 조합으로 잡아서 단어 단위로 한자 치환: 일부 한자 혼용론자가 무척 좋아하는 기능이라 한다. MS IME로는 이 기능은 TSF A급 프로그램에서만 가능하며, <날개셋> 한글 입력기 역시 훗날 이 기능을 추가한다 하더라도 MS IME처럼 TSF A급에서만 지원할 것이다.

이 외에도 잘은 모르겠지만, 안 마태 키보드 드라이버도 입력 스키마를 살짝 변조한 수준에 머물러 있는 <날개셋>보다 새나루가 좀 더 지원을 잘 하는 게 있는 듯하다.

다만, 새나루의 개발자는 <날개셋>의 개발자처럼 한글 입력기 하나에만 완전 목숨을 건 타입은 아니다 보니, 프로그램의 유지· 보수와 버전업이 <날개셋>만치 애착을 갖고 꼬박꼬박 되고 있는 건 아니어 보인다. 하긴, 무료 소프트웨어가 이 정도라도 개발되어 온 게 감지덕지지.

※ Unicode CJK IME

이건 아는 분이 얼마 없지 싶다. 이건 무려 남북 합작으로 개발된 프로그램이다. 주 개발은 북한의 평양 정보 센터(PIC)에서 했으며, 남한의 한국 과학 기술 정보 연구원과 고려 대학교 민족 문화 연구원은 프로그램을 설계하고 각종 한자 데이터베이스를 구축했다. PIC는 서체도 만들고 ‘단군’이라는 워드 프로세서도 개발한 적이 있을 정도로 문자 처리 쪽에 기술이 상당한 수준이다. 그러니 IME도 만들었다.

세벌식은 전혀 지원하지 않지만, 남북 합작 IME 답게 북한 두벌식을 지원한다. 그리고 한양 PUA 방식의 옛한글을 지원하며, 문자표, 부수로 한자 입력, 자체 한자 사전 등의 기능을 내장하고 있다.

제목에서 알 수 있듯, 이 제품은 한글 IME뿐만이 아니라, 동일한 UI 엔진 기반으로 개발된 중국어· 일본어 IME와 한 세트를 구성하고 있다. 북한에서 그런 것까지 만들었다. 하지만 이들 IME의 성능(사전 크기 및 어절 분할 정확도)은 본인이 판단하기에 운영체제가 기본 제공하는 중국· 일본어 MS IME보다 못하다.

이런 프로그램들과는 달리, <날개셋> 한글 입력기는 처음에는 전용 에디터로만 개발되고 있었다. 2.x 시절까지만 해도 본인은 내가 스스로 한글 IME를 만들 수 있을 거라고 생각도 못 하던 처지였다. 그랬는데 2003년은 참으로 드라마틱하게도 한글 IME 개발의 원년으로 등극하게 되었다.

새나루는 2003년 말에 첫 버전이 나왔다. 그리고 본인이 접한 Unicode CJK IME 역시 2003년 6월자 버전이었다(다만, 그 후로 유지 보수는 중단된 듯). 그리고 그 해 가을에 출시된 MS 오피스 2003은 한자 변환 기능이 크게 강화되어 단어 단위 한자 변환이 처음으로 도입된 버전이었다. 이게 다 우연인 걸까?

이런 일련의 사건을 계기로 본인은 운영체제의 IME 스펙을 처음으로 공부하기 시작했으며, <날개셋> 한글 입력기를 운영체제의 IME로 거듭나게 하려는 연구를 난생 처음으로 시작했다. 마침 2003년 하반기이면 <날개셋> 한글 입력기 역시 3.0이 개발 중이었고, 입력기의 내부 구조를 싹 뒤집어 엎고 있었다. 나의 대학 3학년 시절, 이때가 <날개셋> 한글 입력기의 미래를 결정하는 개발이 이뤄지던 시절이었으니, 흥미롭지 않을 수 없다.

그래서 <날개셋> 한글 입력기에 좀 이렇다 할 외부 모듈이 난생 처음으로 탑재된 건, 2004년 9월에 나온 3.02 버전이다. 한글 입력기를 표방하면서 정작 윈도우용 IME가 나온 것은 새나루나 남북 합작 IME보다 시기적으로 늦다.

첫 버전은 당연히 정말 불안정했고 볼품없는 퀄리티였다. 아직 운영체제의 IME 시스템의 내부 구조를 제대로 이해 못 한 상태에서 최소한의 글자 찍기만 가능하던 상태였다. 이 때문에 직후 버전인 3.1에서 당장 무더기 버그 패치가 이뤄졌으며, 그 후로 외부 모듈이 큰 안정화 단계를 마치기까지는 1년이 넘는 시간이 더 필요했다.

그러나 첫 진입 단계에서 이런 시행착오를 충분히 겪은 뒤엔, 워낙 탄탄한 자체 한글 입력 시스템을 갖추고 있던 <날개셋> 한글 입력기가 완성도 높은 윈도우용 IME로 완전히 자리잡게 되었다. TSF 인터페이스를 이용해 bksp 달라붙기 같은 <날개셋> 고유 기능까지 그럭저럭 재연해 냈고, 심지어 윈도우 95부터 오늘날의 7까지 모든 운영체제를 지원하는 최적화까지 덤으로 구현했기 때문이다.

<날개셋> 한글 입력기는 이런 내력을 거쳐 지금과 같은 모듈들이 잘 개발되었다. 하지만 IME(외부 모듈)이 첫 개발되던 그 시절을 본인은 지금도 잊을 수 없으며, IME 모듈의 개발에 영향을 끼친 위의 두 프로그램들에도 나름 애착을 갖고 있다.

Posted by 사무엘

2012/04/09 08:23 2012/04/09 08:23
, , ,
Response
No Trackback , 7 Comments
RSS :
http://moogi.new21.org/tc/rss/response/666

비주얼 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

« Previous : 1 : ... 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : ... 21 : Next »

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2026/07   »
      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:
3942890
Today:
184
Yesterday:
1689