« Previous : 1 : ... 21 : 22 : 23 : 24 : 25 : Next »

비주얼 C++의 역사

그동안 비주얼 C++에 대한 글을 적지 않게 썼었는데 이렇게 전버전의 특징과 역사를 정리해 본 적은 없는 것 같다.
역시 2003과 2005에 대한 설명이 가장 길다.

※ 1.0

MS C 7.0의 차기 버전으로서, 비주얼 베이직처럼 비주얼이라는 브랜드를 걸고 첫 제품이 출시되었다. 비주얼 C++의 역사는 초기에 Application framework라 불리던 MFC 라이브러리와도 역사를 함께 한다.

※ 1.52

윈도우 3.1 타겟을 지원하는 마지막 버전인지라 굉장히 중요한 위치를 차지하고 있다. AppStudio라는 통합 환경이 있기는 하나 도움말 열람, 리소스 편집 등은 여전히 다른 프로그램에서 따로 해야 했다.
프론트 엔드 GUI는 모두 16비트 프로그램인 반면, 커맨드 라인에서 동작하는 컴파일러, 링커 같은 주요 툴들은 도스 익스텐더 stub을 내장하고 있는 32비트 PE 포맷인 점이 무척 인상적이다.

이 시절에 컴파일러는 도스/윈도우 모두 그것도 메모리 모델별로 다 지원해야 했기 때문에 타겟 지정하는 옵션이 무척 까다로웠던 것 같다. 그리고 kernel32, gdi32, user32처럼 API가 분야별로 나뉘어 있는 게 아니라 윈도우 API import 라이브러리가 한 파일에 다 들어있었다.

군대 갔다 온 분, 특히 육군 쪽으로 갔다 온 분은 ‘상호 존중과 배려’라는 표어에 매우 익숙할 것이다. 실제로 이걸로 검색해 보면 군대 관련 사이트, 블로그 포스트들이 제일 먼저 많이 뜬다. -_-;;
그런데 윈도우 3.1만큼 이 문구가 잘 들어맞는 환경도 별로 없을 것 같다. 프로세스들이 혼자 시스템 자원 CPU 시간을 절대 독점하지 말고 다른 프로세스에 대한 자발적인 ‘상호 존중과 배려’로 멀티태스킹을 서로 만들어 가야 하기 때문이다. ^^;;

※ 2.0

32비트 타겟으로 나온 첫 버전으로 알려져 있으나, 그 시기가 윈도우 95가 나오기도 전이고 NT는 점유율이 미미하던 때였다. 시대를 너무 앞서 가는 바람에 주목을 별로 못 받은 전설 같은 존재이다.
(그나저나 그렇다면 94년 이전에 윈도우 NT 3.1의 각종 바이너리들은 무엇으로 빌드했는지 궁금하다. MS 자체적으로만 쓰는 내부 컴파일러? ㄱ-)

32비트 환경에서는 메모리 모델 구분이 없는 대신, CRT에 링크 방식(static/DLL)과 멀티스레드 지원 여부라는 개념이 새롭게 생겼다.

※ 4.0

DirectX에 4라는 버전이 없는가 하면 비주얼 C++에는 3.x대 버전이 없다. 내부적으로 꾸준히 버전업이 돼 온 MFC의 버전 번호에 맞춰 곧바로 4.0으로 건너뛰게 된다. 참고로 윈도우 95의 워드패드가 웬 듣보잡 MFC 3.0 기반인 점이 흥미롭다. 98 이후부터는 전부 MFC42 기반으로 바뀌었음.

4.0에 와서야 비로소 오늘날의 비주얼 C++과 상당한 골격이 갖춰졌다. 즉 코딩, 리소스 편집, 클래스 마법사, 도움말 열람을 한 프로그램에서 할 수 있는 진정한 통합 환경 Developer Studio가 이 버전부터 생겼다. msdev.exe라는 프로그램 이름은 훗날 6.0까지 이어졌다.
이때는 아직 16비트에서 32비트로 한창 넘어가는 과도기였기 때문에 Win32s 타겟이 지원되기도 했다.

참고로 MS에서 개발한 프로그램 중에서 MFC를 쓴 놈은 고작 워드패드, 그림판, 그리고 비주얼 C++ 6 이하 버전의 IDE와 관련 몇몇 유틸리티(Spy++, Dependency Walker) 정도? -_-;;

※ 4.2

4.1을 거쳐 4.0이 마이너 업그레이드된 버전으로, 제법 인지도를 얻었다. ActiveX 컨트롤, STL 같은 기능이 추가되고 MFC DLL의 이름도 이때부터 MFC42로 이름이 바뀌어 큰 뼈대의 변경 없이 훗날의 6.0까지 이어졌다. MFC42는 윈도우 98 이래로 운영체제가 이제 known DLL로 인정하는 마지막 버전 숫자이기도 하니 더욱 의미가 있다.

4.2에서부터 Win32s의 지원이 중단되었다. 오죽했으면 설치할 때 “이 버전부터 Win32s를 더 지원하지 않으니, 그 환경 개발하고 싶으면 이거 설치하지 말고 옛날 버전 쓰셈!”이란 경고문까지 나온다.

※ 5.0

GUI 껍데기는 거의 다 6.0처럼 바뀐 반면, 내부 구조와 기능은 여전히 4.2와 비슷하다고 보면 되겠다. 도구모음줄과 메뉴가 MS 오피스 97 스타일로 바뀌고(하지만 내부 코드 베이스는 서로 완전히 다름) 대화상자도 리모델링되었으나, 도움말은 여전히 RTF 기반이고 인텔리센스도 아직 없었다. 이듬해에 나온 6.0에 밀려 완전히 존재감을 상실했다. 프로젝트 파일 포맷이 이때 처음으로 DSP, DSW로 바뀌었는데, 이 포맷은 결국 5와 6 두 버전에서만 쓰이고 이후 버전에서는 또 바뀌게 되었다.

이때부터 bool이 built-in type으로 지원되기 시작했다. 그리고 이때부터 빌드하는 EXE 파일에 PE 재배치 정보가 기본적으로 생략되게 되었다.

※ 6.0

나온 지 10년이 지난 지금도 어렵지 않게 찾을 수 있고, 윈도우 XP만큼이나 최장수 개발툴로 이용되고 있는 결정판이다. 이 버전 이후로 거의 4년 가까이 버전업이 없었고 다음 버전인 닷넷이 변화폭이 너무 크기도 했기 때문이다.

6에서는 인텔리센스, edit and continue, delay-loaded DLL 같은 획기적인 기능이 처음으로 추가되고 도움말이 별도의 MSDN 라이브러리로 독립함과 동시에 HTML 기반으로 모두 바뀌었다.
이 버전은 윈도우 9x에서 설치 가능한 마지막 버전임과 동시에 MSVCRT, MFC42 같은 known DLL만 사용하는 바이너리를 생성할 수 있는 마지막 버전이기도 하다. 주요 라이브러리를 DLL 링크하고도 DLL 배포 걱정을 할 필요가 전혀 없다는 장점이 있다.

4.2 때는 ClassView에 있는 각종 클래스, 변수 정보가 소스 파일을 매번 저장할 때에만 업데이트됐다. 이것이 내용이 바뀔 때마다 실시간으로 바뀌기 시작한 것도 5.0은 아니고 6.0부터인 걸로 기억한다.

※ 7.1 (2003)

비주얼 C++의 다음 버전은 처음엔, 버전 번호나 연도가 아닌 닷넷이라는 브랜드로 출시되었다. MFC를 써서 개발된 VC 특유의 전통적인 msdev.exe는 퇴출되고 devenv라는 MS 오피스 내지 비주얼 베이직 스타일의 IDE가 등장했는데, 이는 완전히 새로운 것이라기보다는 예전에 비주얼 스튜디오에 있던 Visual InterDev와 비슷한 형태였다. 단지 거기에다 외형만 MS 오피스 XP 스타일 UI를 그대로 물려받았다.

덕분에 비주얼 C++과는 영 인연이 없고 비주얼 베이직만의 전유물인 것 같던 Property grid가 도입되어 비주얼 C++ 특유의 등록정보 대화상자를 대체했다. 클래스 마법사도 이 형태로 대체됐다. 도움말은 5.0 시절처럼 IDE 내부에 띄울 수도 있고 6.0처럼 외부에 띄울 수도 있는 융통성 있는 형태로 바뀌었다.

이제 주류로 내세운 게 C#, 공용 언어 런타임 같은 닷넷 환경이긴 했지만 네이티브 C/C++ 쪽도 크게 향상되었다. 6이 표준대로 제대로 지원하지 않던 문법 내지 컴파일러의 버그 많이 수정되기도 하고 네이티브 wchar_t 형식이 지원되기 시작했으며, 6에서 첫 도입됐던 인텔리센스도 굉장히 강력해져 특히 #define 심볼에 대해서도 동작하기 시작했다. 링크 시점에서 코드를 생성하는 전역 최적화라든가 crash mini-dump 생성 기능도 이때 첫 도입된 기능이다. 닷넷 정도만 써 보면 이제 6.0은 충분히 열악함을 느끼기에 충분할 것이다.

공용 라이브러리는 이제 MSVCR71, MFC71로 바뀌었는데, 이 DLL은 최신 운영체제라고 해서 더는 기본 내장을 해 주지 않기 때문에 응용 프로그램이 알아서 자기 디렉터리나 윈도우 시스템 디렉터리에다 구비해야 한다. 이 점에 관한 한 6.0이라든가 side-by-side assembly를 사용하는 8.0 이후만도 더 못하고 무책임한 면이 없지는 않다. 또한 64비트 개발은 불가능하지는 않으나 불편하고 디버깅도 되지 않고 IDE 차원의 적극적인 보조는 못 받는 어정쩡한 위상을 차지하고 있다. 이 불완전한 면모들은 후속 버전인 8에서 보완되었다.

사실 7.1은 최초로 나왔던 ‘닷넷’(7.0)의 버그 수정판이다. 엄청난 기능이 도입되었던 만큼 첫 버전은 불안정하고 버그도 많았기 때문이다. 7.1 버전은 아래아한글, VMware, 스타크래프트 같은 다수의 상용 프로그램의 최신 버전 개발에 아직까지 쓰이고 있다.

※ 8.0 (2005)

이 버전부터 닷넷이라는 이름을 떼고 출시는 되었으나 8은 7.1하고 아무 단절도 없이 닷넷과 네이티브를 모두 지원하는 동일한 개발 도구의 연장선이다. 이 버전은 닷넷 초창기 버전인 7의 과도기적 불완전하고 2% 부족하던 면모를 속 시원히 보완한 게 무척 많았다. 요즘 어도비 사에서 나오는 프로그램들이 이 버전으로 개발되고 있다.

첫째, 별도의 플랫폼 SDK를 설치할 필요 없이 IDE 차원에서 64비트 빌드와 디버깅이 정식 지원된다.
둘째, 공용 DLL인 MSVCR80, MFC80의 배포 방식이 side-by-side assembly 방식으로 완전히 바뀌었다. 일각에서의 불만을 감수하고라도 이건 꽤 강한 정책이었다. 이와 덩달아, 리소스 편집기로 수동으로 해야 하던 메니페스트 생성/편집 기능도 상당 부분 자동화됐다.

셋째, for 문의 변수 scope처럼 그동안 표준을 어기고 있던 문법을 교정하고, CRT의 보안을 크게 강화했다. strcpy_s와 gets_s(대상 버퍼 크기), qsort_s(콜백 함수에 데이터 전달 가능), strtok_s(토큰 중복 호출 가능) 같은 함수가 새롭게 등장하고 strchr 같은 경우, C++에서는 const 포인터를 되돌리는 버전과 비const 포인터를 되돌리는 함수가 오버로딩으로 분리해 나갔다. 무척 신선한 변화라 느껴진다. STL 디버깅도 훨씬 더 편해졌다.
넷째, UI 상으로도 이제 MS 오피스 스타일에서 독립한 새로운 외형 비주얼을 갖기 시작했으며, 도구모음줄 아이콘도 256색으로 더 화려해졌다. 전통적으로 ClassView에 클래스와 멤버들이 한 트리에 쫙 나열되던 것이 멤버는 별도의 리스트에 뜨도록 바뀌기도 했다.
이 버전부터는 생성하는 프로젝트의 디폴트 기반 인코딩이 드디어 유니코드로 바뀌었다.

※ 9.0 (2008)

8이 질적으로 많이 바뀌었다면 9는 윈도우 비스타의 등장처럼 8 출시 이후에 생긴 변화를 IDE나 MFC 라이브러리 같은 데에 적극 반영하여 양적인 향상을 꾀했다. 또한 MS 오피스 스타일의 GUI를 손쉽게 만들 수 있는 feature pack을 드디어 도입하기도 했다.

이렇게 시대의 조류 때문에 생긴 변화를 제외하면 9는 네이티브 C/C++의 관점에서 봤을 때, 7에서 8로 넘어갈 때 같던 큰 변화는 느껴지지 않으며 따라서 8은 조만간 별다른 존재감 없이 9로 흡수될 걸로 예상된다. 완전히 새로운 기능으로는 보안 강화를 위해 링커에 추가된 시작 주소 랜덤화 기능 정도? 이 옵션은 MS 오피스 2007과 윈도우 비스타의 모든 바이너리에 기본 적용돼 있으며, 과거 Win32s의 잔재로나 기억되던 EXE 파일의 재배치 정보를 다시 끌어들인 장본인이기도 하다.

9는 8과는 달리 윈도우 2000도 설치되지 않으며, 윈도우 9x 계열은 아예 타겟 플랫폼에서도 완전히 제외되었다. 링커 옵션에서 바이너리의 최소 요구 운영체제 버전이 4에서 5로 껑충 뛰었고, 이보다 낮은 값은 지정 자체가 안 된다.
또한 single threaded CRT 라이브러리 옵션도 없어졌다. 이것만 빼면 그다지 아쉬울 것 없다.

Posted by 사무엘

2010/01/10 23:50 2010/01/10 23:50
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/41

설치 프로그램 같은 부류를 작성하다 보면, 지금은 다른 프로세스에 의해 사용 중이기 때문에 건드릴 수 없는 파일을 다음 재부팅 때 교체(업그레이드 설치) 또는 삭제(제거)되도록 설정해야 할 필요가 있다.
(사용 중이어서 잠긴 파일도 놀랍게도 rename과 같은 드라이브 안의 이동은 가능하다. 단지 드라이브 바깥으로의 이동이나 삭제, 교체, 덮어쓰기가 안 될 뿐이다.)

이 분야에 관한 한 윈도우 API는 무척 자비심이 없었다. 윈도우 NT 계열과 9x 계열이 설정하는 방법이 서로 완전히 달랐기 때문이다.
전자의 경우 MoveFileEx라는 걸출한 API가 있어서 MOVEFILE_DELAY_UNTIL_REBOOT 플래그만 지정해 주면 됐다. 다음 재부팅 때 교체/삭제될 예정인 파일 목록은 레지스트리에 별도로 관리되는데, 이 위치 역시 MSDN에 문서화돼 있다.

이게 제일 깔끔한 방법인 반면 윈도우 9x에서는 이 방법을 쓸 수 없다.
윈도우 9x는 부팅 직전에 업데이트 또는 삭제해야 하는 파일의 리스트를 윈도우 디렉토리에 있는 wininit.ini 파일로부터 읽는다. 거기에 뭔가 의미 있는 값이 있다면, 바로 그 때 “Windows가 일부 구성요소를 업데이트하고 있습니다. 몇 분 정도 시간이 소요될 수 있습니다”란 친근한 메시지를 부팅 중에 텍스트 모드에서 영어로 잠시 보여준다. 아마 9x 유저들은 이 메시지를 기억할 것이다.

이 처리를 다 마치고 나면 wininit.ini 파일은 wininit.bak로 개명된다.
이 일을 하는 프로그램은 wininit.exe라는 프로그램이다. 그런데 얘는 본격적인 32비트 운영체제가 로딩되기 전에 잠깐 실행되는 완벽한 16비트 도스용 프로그램일 뿐이다. 그래서 wininit.ini 리스트에는 긴 파일 이름(LFN)을 쓸 수 없다는 무지하게 불편한 제약이 걸린다. 파일 이름이야 그렇다 치지만 폴더 이름까지 공백 하나 표현 못하니 얼마나 불편하리요.

지울 때야 LONGFI~1.TXT 이렇게 지정하면 되겠지만 옛 파일을 LFN 방식의 새 파일로 깔끔하게 교체하는 건 불가능하다는 뜻이기도 하다. 정 하고 싶으면 rename을 부팅 때 강제로 해 주는 다른 프로그램을 RunOnce 이런 레지스트리에 넣기라도 해야 한다.

또 하나 고려할 만한 점으로..
인스톨러가 프로그램을 설치/제거하다가 일부 파일을 건드리지 못했다면 “다음 프로그램이 이 파일을 사용 중입니다” 이러면서 파일을 사용 중인 프로그램 나열까지 띄워 주면 무척 좋을 것이다. (MSI는 실제로 그렇게 해 주고 있다.)

이 기능을 직접 구현하려면 현재 로딩되어 있는 모듈(EXE/DLL)의 리스트를 얻어 오는 API를 써야 하는데 이 역시 윈도우 9x와 NT가 사용하는 API 계보가 완전히 달랐다. 전자는 ToolHelp라고 불렸고 후자는 Process Status API였는데 다행히 윈도우 2000에는 9x의 ToolHelp API도 추가되어서 API가 일종의 통합을 이뤘다.

한편, 저런 이유 때문에 일부 파일을 결국 교체나 삭제를 못 한 경우 인스톨러는 “작업을 완료하려면 재부팅이 필요합니다. 지금 하시겠습니까?”라고 묻는 게 일반적이다. 하지만 대다수의 사용자는 귀차니즘에 입각하여 ‘아니요’를 고르고 만다.

그런데, 그렇게 재부팅을 미루고 언인스톨러를 종료했는데 그 상태에서 사용자가 그 프로그램을 다시 설치한 경우 꽤 문제가 된다. (변덕 한번 심하기도 하다. -_-)
잘 만들어진 인스톨러라면, 이 경우 교체하거나 삭제하기로 요청해 놓은 파일의 예약을 도로 취소해야 한다. 안 그러면 그렇게 재설치 한 후 다음에 운영체제를 재시작하는 순간 재앙이 벌어질 수도 있다.

따라서 원칙대로라면 건드리지 못한 파일이 없더라도 인스톨러는 wininit.ini 같은 목록을 뒤져 봐야 한다. 그것도 운영체제 계열 별로 완전히 다른 접근 방식으로 말이다. 요즘이야 9x 계열은 사실상 신경 쓸 필요도 없어지긴 했지만. 참고로 MSI가 저것까지 똑똑하게 알아서 처리해 주는지는 테스트 안 해 봤다.

결론은, 인스톨러는 완성도 높게 잘 만들려면 현 프로그램의 설치/제거 상태부터 시작해서, 저런 지저분한 API 차이까지 감안해 가며 귀찮게 따져야 하는 게 무지하게 많다는 것. IME 하나 만들려면 지저분한 잡일이 너무 많으니 MS에서 TSF를 개발한 것처럼, 저런 작업을 표준화하고 자동화하기 위해서 MSI라는 것을 안 만들 수가 없었을 것이다.

MSI는 파일을 복사하고 쓰는 제 본연의 기능 면에서 완성도는 무척 높은 것을 인정하지만, 같은 MS에서 개발한 AppLocale 같은 프로그램하고도 UI가 충돌을 일으킨다는 점은 심히 유감이다. -_-

Posted by 사무엘

2010/01/10 23:46 2010/01/10 23:46
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/40

훅킹 프로그래밍

윈도우 훅킹은 운영체제의 내부 메커니즘(주로 메시지)을 가로채어 시스템 전체의 동작 방식을 바꾸는 매우 강력한 기법입니다.

이런 훅은 우리 프로세스 안의 특정 스레드 안에다가만 설치할 수도 있고 시스템의 모든 스레드에다가 설치할 수도 있는데, 32비트 운영체제로 오면서 후자 같은 global 훅(혹은 시스템 훅)을 설치하기 위해서는 훅 프로시저는 반드시 DLL에 따로 존재해야 하는 약간의 번거로움이 생겼습니다.

global 훅은 동작 방식의 특성상 모든 프로세스들에 나의 코드를 주입하는 가장 간편한 방법입니다. 굳이 윈도우 메시지 훅킹이 아니더라도 다른 종류의 훅킹을 위해서라도 윈도우 훅이 사용됩니다. 한컴사전의 노클릭 단어 인식이라든가 과거의 한스타, 그리고 Dependency Walker의 EXE 프로파일 기능처럼 API 훅킹이 동원되는 프로그램도 살펴보시면 별도의 DLL 파일이 존재하는 것을 알 수 있는데, 이는 API 호출을 변조하는 코드 자체를 삽입하기 위해서 윈도우 훅을 사용한 것입니다.

Global 훅은 완전히 특이한 프로그래밍 패러다임을 제공합니다. 다른 프로세스에서 완전 제각기 따로 실행되는 함수를 만들 수 있습니다.
가령, A라는 프로세스에서 B라는 DLL에 들어있는 훅 프로시저로 global 훅을 설치했습니다. 그러면 A와 B 사이의 통신 방법이 문제가 됩니다. 통상 A는 B의 동작 방식을 결정하는 입력 데이터를 주고, B는 훅킹을 통해 얻은 결과를 A에다 전달해야 할 것입니다.

이를 위해 보통 메시지를 쓰면 무난하죠. B에서 A로 통신할 때야 우리끼리만 쓰는 WM_USER+n이라든가, 심지어 WM_COPYDATA를 바로 보내도 무난하지만, 다른 프로세스 안에 존재하기 때문에 메시지가 겹칠 우려가 있는 B를 “향해서” 메시지를 보낼 때는 RegisterWindowMessage로 값이 안 겹치는 게 보장되는 별도의 메시지를 등록해서 쓰는 게 안전합니다.

또한 프로세스 A가 자신의 주소 공간에 로드되어 있는 DLL B의 변수값을 바꾼다고 해서 다른 프로세스들의 주소 공간에 로딩되어 있는 DLL B의 인스턴스의 변수값이 바뀌지는 않는다는 것도 조심해야 합니다. global 훅 프로시저는 메시지를 받는 그 프로세스의 주소 공간을 기준으로 호출된다는 것!
이걸 헷갈려서는 안 됩니다. 변수 초기화 같은 걸 잘못하면 훅을 설치한 프로세스 A 안의 B만 제대로 동작하게 되고, 다른 프로세스에 침투한 B는 그렇지 못하게 됩니다.

이것 때문에 과거에 굉장히 주의가 필요했던 점이 뭐냐 하면 훅 핸들 값을 공유하는 것이었습니다.
훅 프로시저는 자신에게 걸린 메시지를 처리한 뒤, CallNextHookEx 함수로 그 메시지를 다음 훅에다가 전달도 해 줘야 했습니다. 운영체제에 갈고리질을 하는 놈이 나만 있는 것 아니기 때문에... 그런데 윈도우 9x는 유독 이때 자신이 받은 훅 핸들값도 알아서 전달해 줘야 했습니다.

프로세스 A가 자신의 주소 공간에 있는 B DLL에다가 훅 핸들을 넘겨준다고 해도, 다른 주소 공간에 복제된 다른 B DLL의 인스턴스는 그 값을 알 수 없었습니다.
그래서 이 값은 숫제 메모리 맵드 파일 같은 공유 메모리를 만들어서 넘겨주거나, #pragma data_seg 같은 전처리기로 별도의 공유 섹션을 만들어서 그 전역변수에다 핸들을 공유해야 했지요.

그런데 윈도우 2000 이상, 아니 NT 계열은 그럴 필요가 없습니다. CallNextHookEx 함수를 호출하는 것 자체만으로 이 문맥에서의 훅 핸들은 알아서 감지가 됩니다. 당연히 그렇게 되는 게 이치에 맞죠. 그럼, 윈도우 95보다 NT가 3.1 시절부터 먼저였는데 왜 애시당초 아무 쓸모없던 HHOOK 인자를 받는 게 있었을까? 그건 아마 16비트 시절의 훅킹 API의 프로토타입을 그대로 베끼다 보니 그렇게 된 게 아니었나 싶군요. 32비트 윈도우로 와서 WinMain의 hPrevInstance 인자가 완전 무의미해졌지만 여전히 그대로 남아있는 것처럼.

그럼에도 불구하고 훅킹 API에 관한 한 윈도우 9x는 NT를 100% 닮지 못하고, 그렇다고 해서 아예 3.1 시절처럼 단일 주소 공간도 아니면서(핸들 값 공유도 어려운 환경에서) 꽤 불편한 프로그래밍 관행을 개발자에게 강요하게 되었던 것 같습니다.

윈도우 9x 시절에만 해도 global 훅 프로그래밍은 굉장히 조심스럽게 해야 했습니다. 훅 프로시저에서 뭔가 뻑이 났다간 그건 90% 이상 운영체제 다운으로 연결됐습니다. 디버깅은 더욱 힘들었음. 보호 모드 운영체제란 말이 무색할 정도였어요. 그만큼 운영체제가 안전보다는 열악한 PC 환경에서 효율 내지 도스와의 호환성 위주였으며, 불안정하고 응용 프로그램의 위험한 동작에 대해 취약했습니다.

하지만 XP 정도 되니, 특히 비스타는 이 정도로 안정성이 강화될 줄은 몰랐습니다. 훅 프로시저에 굉장히 어이없는 실수가 들어있었는데 그냥 그 훅만 싹 없어지고 프로그램은 잘 돌아가더군요. 깜짝 놀랐음. 윈도우 9x였으면 당장 blue dead screen이었을 겁니다.
윈도우 2000 때만 해도 IME/TSF 모듈이 해당 응용 프로그램을 뻗게 만들 수 있었는데, XP 이후부터는 안 그렇더군요. 자체적으로 예외 핸들링을 합니다.

global 훅 프로그래밍을 할 때 괴로운 점.
훅을 거둬들이고 모든 프로그램을 종료한 뒤에도 훅 프로시저가 들어있는 DLL의 lock이 즉시 풀리지 않는 경우가 많다는 것입니다. 훅을 해제한 뒤에도 이 DLL이 여전히 몇몇 프로세스의 주소 공간에서 사라지지 않고 상주해 있기 때문입니다. 그런데 3~5분 정도 기다리고 나면 없어져 있어요. 그러니 DLL을 이것저것 고치면서 자주 리빌드를 하기가 힘듭니다. -_-;;

비슷한 예로 글꼴도 있답니다. 프로젝트 파일로부터 최종 TTF 파일을 만들어서 여타 프로그램에서 테스트를 했습니다. 그런데 한번 그러고 나면, 그 프로그램을 종료한 후에도 내가 새로 설치한 TTF 파일이 여전히 in use 상태여서 지워지거나 교체가 안 되는 겁니다. 그러니 글꼴을 빈번히 수정하고 테스트하기가 힘들죠.

이럴 때 VMware가 해결책이 될 수 있습니다. 가상 머신을 만들어서 훅 프로그램을 실행하거나 글꼴을 설치하기 직전 순간의 스냅샷을 만든 후, 테스트를 하고 나서 스냅샷 시점으로 revert 하기. -_-;; 그게 저절로 파일 lock이 풀리길 기다리거나 재부팅을 하는 것보다 더 빠르더군요. ㄱㅅ!

뭐, global 훅 얘기로 길어졌습니다만, 내 응용 프로그램 안에서의 작은 규모의 훅도 충분히 쓰일 일이 있으며 특히 MFC에서는 내부적으로 이런 훅을 사용합니다. 일반적인 방법으로 잡기 힘든 메시지들도 MFC의 단일 프레임워크 하에서 일관성 있게 처리시키기 위한 목적이기도 하고요,
또 모든 대화상자들을 부모 윈도우 기준으로 중앙에다 재배치시키기 위해서도 훅을 사용해 대화상자가 생성되는 시점을 가로챕니다.

그냥 DialogBox 같은 함수만 호출해 보면 잘 알다시피 대화상자가 중앙에 뜨지 않습니다. MFC는 modal 대화상자만 중앙으로 옮겨 주기 때문에, 그냥 Create 함수로 modeless 대화상자를 만들어 보면 당장 그 위치 차이를 감지할 수 있습니다.

Posted by 사무엘

2010/01/10 23:29 2010/01/10 23:29
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/37

※ QuickBasic

아래의 PB와 더불어 제 인생에서 거의 최초로 접한 “EXE 생성기”가 아니었나 싶습니다. 중학교 시절 저의 주된 장난감이었습니다. 특히 가장 대중적인 베이직 컴파일러였던지라 한글 입출력/그래픽/사운드/마우스/메모리 등 갖가지 분야의 공개/상용 라이브러리로 기능을 확장해서 프로그램을 만들던 기억이 납니다.

MS는 오로지 고객 편의 위주로 기존 관행을 확 깨는 새로운 제품 만드는 데는 정말 비상한 재주가 있다는 걸 인정해야겠습니다. 그래서 윈도우 9x 같은 타협안으로 소비자 시장을 우려먹기도 했고, 베이직 개발 환경만 해도 저런 대화형 구조에다가 라이브러리까지 특수한 EXE로 링크해서 ‘퀵라이브러리’(QLB)라는 개념을 발명하여, IDE에서 빌드도 없이 바로 프로그램을 실행시킨 것도 정말 대단하다고밖에 말할 수 없군요.

QB는 최종 버전이 4.5였고 그로부터 몇 년 뒤에 소위 MS Basic이라 불린 Basic PDS (전문 개발 시스템)가 7.0/7.1 (VS .NET 같군)라는 QB 후속 버전이 나오긴 했습니다. 하지만 차이를 전혀 모르겠어요. 오히려 기능 확장의 생명인 라이브러리가 호환이 안 되고 불편해서 널리 사용되지는 않았습니다. PDS 이후 MS는 비주얼 베이직으로 넘어갑니다. 마치 MS C 7 이후로 비주얼 C++이 나온 것처럼.

※ PowerBasic

볼랜드에서 Turbo Basic을 잠시 만들던 전 직원이 볼랜드를 나와서 따로 창조한 브랜드이죠. 베이직만으로 QB보다 월등히 빠르고 거의 C/C++ 수준의 효율적인 네이티브 코드를 만들어 준 덕분에 QB와는 별개 영역에서 사용자를 구축하고 있습니다. 저는 3.1을 써 봤습니다.

단점으로는 QB에 비해 역시 라이브러리 캐부족, 일부 QB와 호환되지 않는 문법, QB보다는 다루기 불편한 IDE. 그리고 PB도 아예 Win32 콘솔 프로그램은 만들어도 32비트 도스 익스텐터와의 연계는 없었던 걸로 압니다.
또한 QBasic도 지원하는 스크린 mode 13H (VGA 320x200 256색) 모드를 지원 안 하는 것도 너무너무 아쉬운 점.

세상에 베이직처럼 Dialect가 많은 언어도 별로 없는 것 같습니다. 요즘 베이직은 그냥 스크립트 언어처럼 쓰거나 아니면 .NET 구도에 맞춰서 문법 다 뜯어고쳐서 쓰는데, PB는 베이직으로 하드코어 네이티브 코드를 지향했다고 볼 수 있습니다. 참고로 베이직을 거의 어셈블리 수준으로 짬뽕시킨 언어 내지 툴로 ASIC이라는 녀석도 있었습니다.

※ Visual Basic

GUI 바로 만들고 거기에다 코드를 즉시 갖다붙이는 형태가 무척 재미있어서 잠시 갖고 놀아 봤습니다. 하지만 런타임 DLL이 필요한 EXE밖에 못 만든다는 걸 알고서 이내 좌절함. 그나마 5.0 이전 버전은 EXE 자체도 자바 내지 닷넷 같은 슈도코드 기반이지 네이티브 코드는 지원도 안 하고 있었고요.

아울러, 닷넷부터는 IDE가 언어 불문하고 한데 통합되었기 때문에 VB 특유의 그 개성 넘치는 IDE를 볼 수 없게 된 것이 좀 아쉽습니다.

※ Borland Pascal

언어 특성상 C/C++과는 비교가 안 될 정도로 빌드 속도가 월등히 빠르고 라이브러리 오버헤드도 훨씬 작아서(Hello, world! 프로그램의 exe 크기) 굉장히 좋은 인상을 받았습니다. 16비트 도스용 네이티브 컴파일러로는 그야말로 최고봉이 아닌가 싶습니다. 파스칼 언어도 제가 베이직에서 C/C++로 전환할 때의 과도기 컨셉 역할을 잘 해 주었습니다. 이걸로 뭔가 걸출한 프로젝트를 진행해 봤으면 하는 아쉬움도 있습니다.

파스칼은 교육용으로 무척 훌륭한 언어이고 간단한 문법 덕분에 빌드하기는 간편하나, C/C++보다 함축성이 굉장히 떨어져서 코딩하기는 번거롭습니다. 가령 정수 나누기 정수도 결과가 무조건 실수가 되고 일일이 형변환을 해야 하는 건 기계 입장에서는 직관적이지 못하죠. 또한 단축연산이란 것도 없고.

※ Turbo C/C++

고등학교 때 정보 올림피아드 준비하던 시절에 잠시 다뤘을 뿐, 제품 자체의 왕년의 영향력에 비해서 얘를 써 본 경험은 거의 없습니다. 본격적인 응용 프로그램 개발을 위해 필요한 프로젝트 세팅, 메모리 모델 설정 같은 것도 전혀 해 본 적 없습니다. 저는 16비트 C/C++과는 도스/윈도우 공히 인연이 없는 셈입니다.

※ Delphi

놀랍게도 제가 얘를 다뤄 본 건 95년에 나온 초창기, 그것도 16비트 버전인 1.0뿐입니다. 오브젝트 파스칼 언어로 비주얼 베이직 같은 프로그래밍을 할 수 있구나 하는 인상만 받고 더 진척된 건 없습니다.

델파이는 런타임 DLL이 필요한 EXE를 만드는 옵션 자체가 없고 기본적으로 EXE 크기가 2~300KB대 이상은 먹고 들어가는 걸로 알고 있습니다.

※ C++ Bulder

비베, 델파이 수준의 RAD 환경을 C++로도 만들어 버렸으니 대단하다는 인상을 받았음. 물론 다른 건 다 델파이를 따라해도 빌드 속도를 따라할 수는 없지요.
버전 3을 써 봤습니다. 골치아프게 Win32 GDI API 쓸 필요 없이 캔바스 객체에 접근하여 프로퍼티를 바꾸는 것만으로 선과 면 색을 바꾸는 것도 흥미로웠습니다.

하지만 RAD 용도로는 어차피 델파이가 한 역할 담당하고 있었고, C++ 빌더는 상대적으로 존재감이 덜했던 것 같습니다.
MFC도 지원은 하지만, 빌드 속도 내지 빌드된 코드의 성능이 MFC의 본고장인 VC로 빌드한 것보다 무척 뒤쳐졌던 걸로 기억합니다. 빌드 속도는 흠, pre-compiled header 설정을 안 해서 그런 걸까?

델파이와는 달리 C++ 빌더의 EXE는 기본적으로 VCL.DLL이던가 컴포넌트 DLL이 필요한 형태입니다. 스테틱 링크가 가능한지는 기억이 안 납니다.

※ Watcom C/C++

MS와 볼랜드 컴파일러의 공통점은 32비트 도스 익스텐더 기반 코드 생성을 지원하지 않았다는 것입니다. 자기네 컴파일러들이나 겨우 몇 MB 정도 추가 메모리 확보를 위해, 286용 16비트 도스 익스텐더 정도를 사용한 게 고작입니다.

그러던 차에, 상용 게임들 덕분에 유명하던 DOS4GW 기반 코드 생성을 지원하던 왓콤 컴파일러는 정말 신선한 충격이었습니다. sizeof(int)도 말로만 듣던 4바이트로 나왔죠.
최적화 성능도 매우 뛰어났던 걸로 기억합니다. 익스텐더는 다르지만 도스용 아래아한글 386 에디션도 왓콤 기반이었던 것으로 유명합니다.

하지만 라이브러리가 부족하고 IDE도 없는 이 컴파일러로 본인이 당장 할 수 있는 일이 별로 없었던 게 아쉽습니다.

※ DJGPP

자유 소프트웨어 진영의 위력을 보여준 불세출의 32비트 도스 컴파일러입니다. 소스까지 공짜, DOS4GW보다 stub 크기가 훨씬 작고 더구나 DPMI가 기제공되는 환경에서는(윈도우 도스박스 같은) 필요하지도 않은 CWSDPMI.
거기에다 rhide 같은 IDE와 알레그로 같은 무지막지한 게임 라이브러리.

빌드 속도가 좀 느리고 exe 크기가 살짝 좀 큰 게 압박인데, 그 정도는 좀 대형 프로그램 작성하다 보면 아무것도 아닙니다. 도스가 살아 있다면 계속해서 쓰고 싶습니다. 물론 윈도우용 GCC도 있다고는 들었지만.

※ Visual C++

여러 개발툴들을 거쳐 이제 최종적으로 정착한 도구는, 그 이름도 유명한 비주얼 C++입니다. 더 설명이 필요없으리라 믿습니다. 닷넷이고 뭐고 많이 나와도 역시 한글 IME를 직접 개발하다 보니 제게는 네이티브 말고 다른 계층은 필요하지가 않네요.

Posted by 사무엘

2010/01/10 23:26 2010/01/10 23:26
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/36

윈도우 운영체제가 NT 초창기 시절 이래로 지금까지 사용해 오고 있는 실행 파일 포맷은 잘 알다시피 portable executable 형식입니다. 헤더도 이니셜인 PE로 시작합니다. 물론 네이티브 EXE이기 때문에 코드 부분은 기계마다 다르겠지만, 헤더 구조체라든가 리소스 같은 공통된 부분은 최대한 일치시켜서 이식성을 고려해서 설계했다는 뜻이지요.

늘 인텔 CPU에서만 돌아가는 EXE만 보다가 MIPS 같은 RISC CPU에서 돌아가는 PE 실행 파일을 헥사 에디터로 들여다보니 진짜로 기계어 코드가 한눈에 보기에도 일정 바이트 간격으로 아주 균일하게 나열돼 있더군요. 그걸 보고 놀랐던 기억이 납니다.

64비트 PE도 일부 구조체만 64비트로 확장되었을 뿐 기본적인 골격은 초창기 32비트 PE와 같습니다. 더구나 윈도우 운영체제가 인식하는 리소스(스트링 테이블, 대화상자, 메뉴 등)의 포맷은 매우 다행스럽게도 32비트 PE와 완전히 일치합니다.

EXE와 DLL은 자신만의 프로세스 공간을 만들어서 단독 실행이 가능하냐의 차이가 존재하는데, 기술적으로는 헤더의 비트 몇 군데만 다르지 똑 같은 PE 바이너리입니다. 이런 바이너리를 ‘모듈’이라고 부릅니다.

c, cpp 같은 소스 코드를 컴파일하면 기계어 코드인 obj 파일이 생깁니다. 이런 obj 파일과 lib를 링크하면 그런 모듈 파일이 결과물로 생성됩니다. lib는 또다른 obj의 묶음일 뿐 obj와 완전히 다른 파일이 아닙니다. 또한 모듈 역시 그런 obj, lib에 들어있는 코드를 PE 규격에 맞게 재배치하고 묶은 파일일 뿐이지 원시 파일과 그렇게 큰 차이가 없습니다.

윈도우 운영체제에서 개발 환경을 만든 사람들의 생각은, 링커가 특별히 할 일이 없게 하는 것이었던 것 같습니다. 물론 요즘은 전역 최적화처럼 링크 타임에도 코드를 생성하는 기술도 도입되어 사정이 그렇게 간단하지만은 않게 됐지요.

PE는 text(실행되는 기계어 코드), rdata(스트링 리터럴처럼 읽기전용 상수나 초기화 값), rsrc(윈도우 리소스 데이터), DLL 심볼 import/export 테이블, reloc(재배치 정보) 등 여러 섹션으로 나뉩니다. 특히 재배치 정보는 Win32s 시절에는 exe에도 필요했지만 지금은 dll에만 넣어 주면 됩니다.

PE의 헤더에는 자신의 기본 어드레스, 자신이 돌아가는 데 필요한 최소한의 운영체제 버전 같은 여러 정보가 들어가고 심지어 자신을 빌드한 링커의 버전을 기입하는 공간도 있습니다. 가령 비주얼 C++로 빌드하면 6.0, 7.1 (닷넷 2003), 8.0 (2005) 같은 번호를 쉽게 식별할 수 있지요.

원래 MS 자체에서 만든 프로그램 바이너리들의 링커 버전은 비주얼 C++의 버전과 거의 일치하지 않았습니다.
가령 윈도우 95는 까마득한 2.5, 그리고 98/ME는 3.1, 윈도우 2000은 5.12, 오피스 XP는 6.2였습니다. 비주얼 C++과는 별도로 자신들만 쓰는 컴파일러/링커가 있었던 것 같습니다.

하지만 이것이 언제부턴가, 한 02~03년부터 버전이 일치하기 시작했습니다. MS에서도 내부적으로 비주얼 스튜디오를 쓰기라도 했는지?
윈도우 XP는 7.0으로 당대의 최신 비주얼 C++이던 닷넷 2002와 일치합니다.
그리고 XP sp2 (sp1은 모르겠음)와 오피스 2003은 비주얼 C++ 닷넷 2003의 버전과 같은 7.1입니다.

그 후 윈도우 비스타와 오피스 2007의 모든 바이너리들은 비주얼 C++ 2005의 버전인 8.0으로 물갈이되어 있습니다. 하지만 CRT 라이브러리는 살짝 다릅니다. 오피스는 msvcr80을 쓰지만 운영체제는 자신만의 msvcrt를 고수하고 있습니다. 하지만 이제는 msvcrt에도 비주얼 C++ 2005에서 새로 추가된 strcpy_s 같은 보안 강화 함수들이 추가되어 있습니다.

msvcrt는 이제 운영체제가 혼자 마음대로 바꿔 쓰는 CRT DLL로 격리시키고 응용 프로그램들은 이제 msvcr??을 알아서 배포해서 쓰든가, 싫으면 스테틱 링크하라는 구도가 된 셈입니다.

Posted by 사무엘

2010/01/10 23:17 2010/01/10 23:17
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/31

C 런타임 라이브러리에는(이하 CRT) 단순히 내가 호출하는 printf, strcpy, malloc 같은 함수에 대한 코드만 있는 게 아니다.
사용자가 작성한 C/C++ 프로그램보다 아래 계층에서 먼저 실행되면서
사용자가 짠 main 함수를 호출해 주고, 표준 C 스펙에 정의돼 있는 각종 전역변수의 값을 설정해 주고
전역변수 C++ 오브젝트들을 미리 초기화해 주는 것도 CRT의 몫이다.

이 오버헤드가 작지는 않다.
그렇기 때문에 hello, world! 한 줄만 찍는 프로그램을 C 언어로 짜 봐도 어지간해서는 크기가 최소한 1만 바이트는 넘어서며, 특히 윈도우 프로그램의 경우 내가 전혀 호출하지 않은 GetStartupInfo, GetVersion 같은 커널 쪽 Win32 API를 호출해 있는 걸 볼 수 있다. (CRT를 스테틱 링크한 경우)

그런 함수는 당연히 CRT가 호출한 것이다.
가령 main 함수에 인자로 전달되는 명령줄 인자는 CRT가 준비해서 넘겨 준 것인데,
CRT 역시 명령줄 인자는 더 아래 계층의 GetCommandLine 같은 API 함수를 통해 얻어 온 후, 파싱해야 하기 때문이다.

이 CRT 초기화 코드를 무시하고 진짜 순수하게 내가 짠 코드만 집어넣게 하는 링크 옵션이 컴파일러에 따라서 물론 존재한다.
이렇게 하면 어셈블리 프로그래밍 하듯이 아주 작은 EXE를 만들 수 있다.
하지만 이 경우, 정상적인 C언어 사용은 포기해야 한다.

CRT 초기화 코드가 실행되지 않으면 printf, malloc 등 I/O라든가 뭔가 초기화 context가 필요한 함수들도 죄다 사용할 수 없게 되기 때문이다.
윈도우 환경의 경우 그런 것들도 Win32 API만으로 내가 직접 다시 짜야 할 것이다.
fopen은 CreateFile로,
malloc/free는 HeapCreate 등으로 힙 관리 직접 다 하고,
sprintf는 wsprintf 등으로. (그나마 윈도우는 운영체제 차원에서 % 문자를 C랑 똑같이 해석해 주는 함수가 있어서 다행이다. 운영체제 자체가 C언어로 개발됐다는 증거가 아닐까 한다)

과거 16비트 도스용 컴파일러 시절에는 이 CRT 라이브러리가
메모리 모델별로 따로 존재해야만 했다. tiny, small, medium, compact, large, huge 기억하시는가? 아주 골치아팠다.

그러다가 32비트 윈도우 환경에 와서는 메모리 모델 구분은 없어지고 CRT에 새로운 속성이 존재하게 됐다.

- 멀티스레드: 옛날에 컨텍스트를 저장하는 데 배째라 전역변수 썼던 것들을 이제 스레드 TLS로 옮겨야 한다. 대표적인 예가 strtok. 이제 비주얼 C++ 2008부터는 싱글스레드 라이브러리는 없어지고 무조건 멀티스레드만 지원한다.
- 디버그: 도스용 컴파일러 중에 디버그 용 CRT가 따로 있었던 건 별로 본 기억이 없다.
- DLL이냐 스테틱이냐: CRT를 DLL로 따로 떼어낼 수도 있게 됐다. 한 프로그램이 같은 CRT를 사용하는 수많은 DLL들을 로딩하는 경우, 그 DLL 모듈들도 CRT DLL 하나만 로딩하도록 개발하면 메모리를 많이 절약할 수 있다.

Posted by 사무엘

2010/01/10 23:01 2010/01/10 23:01
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/25

1. 프로젝트의 링커 옵션 수정

"링커-입력" 카테고리의 "추가 종속성"에다가 같이 링크할 라이브러리 이름을 입력하는 것으로, 가장 일반적인 방법이다. 이 설정은 해당 프로젝트에 대해 전역적으로 적용된다. (당연히)

라이브러리 파일을 찾는 디렉토리 순서는 "링커-일반" 카테고리의 "추가 라이브러리 디렉토리"에다가 설정해도 되고, 아예 "도구-옵션" 명령의 "프로젝트 및 솔루션-VC++ 디렉토리" 카테고리에다가 완전 전역적으로 설정해도 된다.

하지만 내가 관리하는 프로젝트를 어느 컴퓨터의 비주얼 스튜디오에서나 바로 빌드하고 싶다면(외장 하드 같은 데에 담아다가), 디렉토리 순서는 후자의 애플리케이션 단위가 아닌 전자의 프로젝트 단위로 지정해 두는 게 좋을 것이다.
사실, "도구-옵션" 명령의 디렉토리 설정에다가는 C/C++ 기본 라이브러리나 윈도우 플랫폼 SDK 같은 완전 기본적인 디렉토리만 설정해 두고, 내 프로젝트에 특화된 디렉토리는 지정하지 않는 걸 추천한다.

2. 프로젝트의 구성원으로 라이브러리 파일 자체를 추가

한 프로젝트를 이루고 있는 여러 소스 파일들 중, 컴파일러 같은 특정 빌드 도구를 거치지 않은 파일은 자동으로 링커에게 그대로 전달되게 된다.
그렇기 때문에 굳이 링커 옵션을 지정하지 않더라도 프로젝트에 포함되어 있는 lib 파일은 링크 과정에서 자동으로 인식되고 사용된다.

지정하려는 라이브러리가 다른 SDK가 아니라 역시 내가 만들고 수정하고 관리하는 것인 경우에는, 이렇게 해 두는 것도 괜찮다. 특히 링커 옵션이나 "도구-옵션" 설정에 지정되어 있지 않은 임의의 디렉토리에 있는 아무 라이브러리나 자유롭게 참고할 수 있다는 장점이 있다.
단, 이 방법은 debug/release 같은 configuration별로 한 라이브러리의 여러 변종 파일을 지정하기가 좀 까다롭겠다.

옛날에, 거의 비주얼 C++ 6 시절엔, DLL을 만들 때 쓰이는 모듈 정의 파일(def)도 그냥 소스 파일로 프로젝트에다 집어넣어 놓으면 링크할 때 알아서 인식이 됐던 것 같은데 닷넷 이상부터는 안 그런 것 같다. 반드시 "링커-입력" 카테고리의 "모듈 정의 파일"에다 이름을 넣어 줘야 하는 듯.

3. #pragma 지시문

전체 프로젝트 파일 중에서 극소수의 소스 코드만이 특정 희소 API를 사용하기 때문에 그 놈에 대해서만 추가 라이브러리 링크 지정이 필요한 경우엔, 그 소스 파일에다가만
#pragma comment( lib, "rare_lib.lib" )

이렇게 지정해 주면 아주 간단하고 편리하다. 프로젝트 파일 차원에서의 수정이 없으며 한 소스 파일을 여러 프로젝트에서 공유할 때 관리가 용이해진다. 물론 표준이 아니라 MS 컴파일러 확장이긴 하지만.
이 소스를 컴파일하여 생긴 obj 파일의 내부에는, "나중에 나를 읽어들이는 링커님은 내가 가진 심볼들을 링크할 때 요 lib 파일도 추가로 좀 참고해 주셈"이라는 힌트 정보가 포함되게 된다.

이 방법은 static library를 만들 때 유용하다.
A라는 클래스 라이브러리를 빌드하는데, 얘는 B라는 다른 라이브러리의 함수를 참고하는 C라는 메소드를 멤버로 가지고 있다고 치자.
그리고 나는 D라는 애플리케이션을 A 라이브러리를 기반으로 개발하고 있다고 치자.

이 경우, D는 C라는 메소드를 호출하지 않더라도 링크할 때 A뿐만 아니라, 사용하지도 않는 B 라이브러리를 역시 지정을 해 줘야 한다. 그렇지 않으면 링크 에러가 난다.

하지만 라이브러리의 소스 코드 자체에 내가 의존하는 다른 라이브러리에 대한 정보가 이미 포함되어 있으면, 사용자는 그 내부 의존성에 대해서 알 필요가 없어지는 것이다. C 메소드를 사용하든 안 하든 D 프로젝트는 B 라이브러리의 링크 지정을 안 해도 되게 된다.

Posted by 사무엘

2010/01/10 22:55 2010/01/10 22:55
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/23

※ once -- 컴파일러의 동작 방식을 바꿈

비주얼 C++ 거의 6.0 시절부터 지원되었던 것 같습니다.
헤더 파일의 첫 줄에다 삽입하여 이 헤더가 중복 인클루드되지 않도록 합니다.

헤더 파일 전체를 #ifdef, #endif로 싸는 것보다 간편해서 좋습니다.
물론, 이걸 쓰면 특정 헤더 파일이 인클루드됐는지의 "여부"는 알 수 없지만, 그런 거 상관없이 중복 인클루드만을 방지하려는 용도로 아주 적합하지요.

※ comment -- 오브젝트 파일에 뭔가 정보를 기록함

- lib: 프로젝트 전체의 링커 옵션을 일일이 바꾸지 않고도 이 오브젝트 파일을 링크할 때는 이 라이브러리를 추가로 찾아 보도록 지정합니다. 매우 유용합니다.
- linker: 이 소스 코드에만 적용할 링커 옵션을 아예 소스 코드에다 바로 써 넣을 수 있습니다.
비주얼 C++ 2005는 예전까지 번거롭게 리소스로 처리하던 side-by-side 메니페스트 작성을, 링커 옵션으로 지정하여 전용 도구로 손쉽게 할 수 있게 되었습니다. 내 exe가 윈도우 XP 비주얼 스타일을 지원하고 싶으면 그냥 /manifestdependency를 지정하는 링커 옵션 pragma를 한 줄 써 주면 끝이라는 것입니다.

※ pack -- 컴파일러의 코드 생성 방식을 바꿈

위의 pragma 지시들이 그냥 다른 기능이나 옵션 설정만으로도 실현할 수 있는 기능에 대한 '편의'만을 제공하는 거라면, 이건 뭔가 고유한 의미를 갖는 기능입니다. 대체할 수 있는 다른 기능이 없습니다. 바로 구조체 패킹 관련 설정을 제어하는 것이기 때문입니다.

이건 근래에는 32비트 코드를 64비트 코드로 포팅할 때 특히 신경써야 합니다. 평소에는 기계가 처리하기 적당하게 32비트 내지 64비트 크기로 패킹을 하지만, 파일을 한 구조체로 바로 읽어들이거나 네트워크 패킷처럼 크기에 아주 민감하게 구조체를 짜려면 반드시 8 또는 16비트 크기의 정밀한 패킹이 필요합니다.

구조체 패킹 크기 옵션은 컴파일러가 스택 구조로 처리하기 때문에, 나의 새로운 설정을 집어넣었다가(push) 다시 예전 설정으로 돌아가는(pop) 식의 세팅이 가능합니다.

Posted by 사무엘

2010/01/10 22:25 2010/01/10 22:25
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/10

side by side assembly는 윈도우 운영체제의 구조적인 문제 중 하나였던 DLL hell 문제를 해결하기 위해 윈도우 XP에서부터 처음으로 도입한 기술입니다. (그 후 2003, 비스타 등등..)

전통적으로 Win32 EXE가 symbol import table을 통해 읽어들이도록 지정되어 로드 타임 때(런타임이 아닌) 실제로 읽어들이는 외부 dll은
해당 EXE/모듈이 있는 디렉토리, 커런트 디렉토리, 윈도우 및 윈도우 시스템 디렉토리, path 환경변수가 걸린 디렉토리 등등의 순서대로 탐색합니다. 물론 kernel32, gdi32, user32 같은 시스템 dll도 다 여기에 속하고요.

런타임이 아니라 로드(load) 타임이기 때문에
이런 dll 파일이 존재하지 않거나 한 심볼이라도 읽어들이지 못했다면
그 프로그램은 그냥 속수무책으로 전혀 실행되지 못합니다.
그런 상황에서의 처리를 프로그래머가 능동적으로 전혀 할 수 없다는 게 큰 약점입니다.

* * * * * * *

자, 이런 전통적인 로딩 방식은 잠재적인 문제를 품고 있습니다.
먼저, 한 회사에서 만들어서 여러 디렉토리에 상주하는 다수의 프로그램들이 자기네끼리 한데 공유하는 dll의 경로를 지정할 수가 없습니다. 기껏 잘 해 봐야 path 환경변수 지정이 고작인데, 이는 정량적인 방법은 아닙니다. 그 경로 지정 자체가 이미 '런타임'에만 가능한데, dll 로딩 경로는 '로드 타임'에 결정되어야만 하기 때문입니다.

이런 딜레마로 인해 조금이라도 한데 공유하는 dll은 반드시 탐색한다는 게 보장되는 윈도우 시스템 디렉토리에다 집어넣는 게 무조건 장땡이었고, 이 때문에 거기는 운영체제가 기본 제공하는 dll과, 응용 프로그램들이 집어넣은 dll로 인해 몇천 개의 수백 MB에 달하는 dll들로 난장판이 되어 버렸습니다.

둘째, dll을 식별하는 수단이 이름이 유일한지라, 이름이 같지만 다른 위치에 있거나 버전이 다른 엉뚱한 dll이 로딩되었을 때 대처할 방법이 전혀 없습니다. 이는 보안상으로도 매우 위험합니다. 이게 바로 말 그대로 DLL hell의 근본 원인입니다.

msvcrt.dll, mfc42.dll, comctl32.dll 이런 것들은 윈도우 98 이래로 이름은 같지만 그동안 버전별로 프로토콜, 인터페이스가 내부적으로 상당히 바뀐 게 많습니다. 내부적으로 문서화되지 않은 동작 방식이야 두말할 것도 없겠죠. 그런데 본이 아니게 그런 문서화되지 않은 동작 방식에 의존하던 프로그램이 다른 버전의 dll을 읽어들이면 그때부터 프로그램의 안정성은 뻑이 나기 시작한다는 것입니다.

* * * * * * *

side by side assembly 기술은 위의 두 문제를 모두 원천적으로 해결하는 과정에서 나온 해결책입니다.

첫째, 이제 MS에서는 지금까지 내놓았던 시스템 dll 이후로 제 3자 응용 프로그램의 고유 dll이 윈도우 시스템 디렉토리에다 들어가는 것을 비추(discourage) 행위로 규정했습니다.
호환성 때문에 저걸 완전 금지할 수는 없구요. <날개셋>의 경우 윈도우 IME가 반드시 시스템 디렉토리에만 있어야 하기 때문에 시스템 디렉토리를 건드립니다.

그런 특수한 경우가 아니면, 응용 프로그램이 사용하는 모든 dll은 가능하면 응용 프로그램 디렉토리에다가만 두어라! 시스템 디렉토리는 이제 운영체제만 자체적으로 쓰겠다. 이곳의 포화는 이제 그만!
메모리가 부족한 상황에서 자원을 하나라도 더 공유하려고 시스템 디렉토리를 두었지만 이제 그런 정책까지 과감히 포기한 것입니다.

이게 파격이 큰 이유는, 심지어 준시스템 dll이나 다름없는 개발툴의 런타임 dll들까지 이제 시스템 디렉토리에다 넣지 않기로 했기 때문입니다. 비주얼 C++ 기준으로 6.0이 끝입니다. 6.0으로 만든 EXE만이 mfc42.dll이나 msvcrt.dll 정도는 윈도우 98 이래로 어디에나 편재해(ubiquotous) 있다고 간주할 수 있습니다. 즉 'known dll'인 것입니다.

그 이후로, 닷넷으로 만든 EXE는 자신이 사용하는 msvcr71, msvcr80, mfc71 등등을 배포 패키지에다 자체 내장해야 합니다. 윈도우 XP, 비스타의 시스템 디렉토리에 기본으로 들어있지 않습니다. 응용 프로그램이 자기가 쓰는 런타임 dll은 무조건 복사본을 자기 디렉토리에다 심어야 합니다.

둘째. 첫째가 상당히 자비심 없는 정책이 되었는데 그럼 대책이 뭐냐 하면,
내가 정확하게 읽어들이고자 하는 DLL의 고유 식별자, CPU 아키텍쳐, 언어, 정확한 버전 번호를 exe 내부에다 리소스의 형태로 xml 문서로 명시해 놓는 것입니다. 한 컴퓨터의 디렉토리 구조와는 완전히 무관하게 말입니다. 메니페스트 xml이라고들 하죠.

그 메니페스트용으로 등록된 핵심 dll들은 윈도우 시스템 디렉토리에 있는 게 아니라 WinSxS 밑에 각 버전과 아키텍쳐별로 별개의 디렉토리에 저장됩니다.
윈도우 XP라면 gdiplus, comctl32 이런 dll이 존재할 텐데요. 이름이 같은데 윈도우 XP 원본이 갖고 있던 comctl32, SP2가 갖고 있던 comctl32 이런 버전별 '스냅샷'들이 전부 따로 저장되어 있는 걸 알 수 있습니다. 비스타에는 이보다 훨씬 더 많이 있습니다. 내가 만들어서 공유하기를 원하는 핵심 dll도 이런 식으로 등록, 배포할 수 있습니다.

* * * * * * *

윈도우 XP에서 이런 새로운 방식으로 곧바로 사용되기 시작한 대표적인 운영체제 컴포넌트가 바로 comctl32.dll 입니다. advapi, shell32 같은 거 말고 comctl32만 이런 형태가 됐습니다.
얘는 윈도우 9x 시절부터도 인터넷 익스플로러와 함께 곧잘 업데이트되었던 파일이고 특히 윈도우 9x의 안정성을 떨어뜨리는 주범이기도 했습니다.

이 파일이 특히 윈도우 XP에서는 비주얼 스타일 테마의 등장으로 인해 구조가 완전히 뒤바뀌었는데, 정작 새로운 파일은 WinSxS에 있고, system 디렉토리에 있는 파일은 버전이 여전히 5.82이고 옛날 IE 5.x 시절과 별 차이 없습니다. 한 마디로, system 디렉토리에 있는 파일은 옛날 프로그램과의 호환성을 위해 시간이 그 상태 그대로 정지해 버린 것입니다.

그 반면, 메니페스트에다가 새로운 comctl32를 사용하도록 지정을 해 놓은 exe만이 대화상자에서 윈도우 XP 이후의 비주얼 스타일 적용을 받을 수 있는 구조가 됐습니다. comctl32를 이 방법을 통해 최신 버전으로 지정하지 않으면, 옛날 프로그램에서는 알록달록한 테마만 나오지 않는 게 아니라 하이퍼링크 컨트롤 같은 XP에서 새로 추가된 컨트롤을 쓸 수도 없고, 비스타에서 그 유명한 TaskDialog 함수조차도 쓸 수 없습니다.

* * * * * * *

유행 따라서 윈도우 XP 비주얼 스타일을 쓰려고 으레 '리소스' 형태로 따로 작성해 온 메니페스트가 비주얼 스튜디오 2005에서는 개발툴 차원에서 통합적으로 지원되는 요소가 되었습니다. 2005는 이제 배포 대상 플랫폼으로서 윈도우 9x의 지원을 완전히 제껴 버리고 CRT와 MFC DLL 역시 side by side assembly 형식으로"만" 로딩하게끔 정책이 바뀌었습니다.

이게 언뜻 보기에 골때리는 이유가 뭐냐 하면, 이제 2005로 만든 exe는 msvcr80.dll이 시스템 디렉토리나 심지어 그 exe 디렉토리에 같이 있다고 하더라도 winsxs 설정이 안 되어 있으면, '응용 프로그램 구성이 잘못됐다'며 실행 안 되기 때문입니다. (msvcr80이 자체적으로 퇴짜를 때리는 듯.)
그냥 msvcr80, mfc80 같이 슬쩍 복사해 놓는 방식으로는 실행 못합니다. 이들을 winsxs 정식 등록을 해 주든가, 아니면 앗싸리 MS에서 배포하는 VS 2005 redistributable 프로그램을 같이 배포해야 합니다.

그런데 도대체가 VS 2005 redist.도 따로 있고, VS 2005 sp1 resit.도 따로 있습니다.
윈도우 비스타에 기본 내장되어 있는 msvcr80의 버전이 다르고, 오피스 2007이 사용하는 msvcr80의 버전이 다릅니다. 세부 버전 숫자가 하나라도 다르면 로딩 안 되는 게 일단 보안과 안정성 면에서는 나아진 점입니다만, 도대체 같은 기능을 하는 CRT DLL을 왜 자꾸 이렇게 바꿔 대는지, 도대체 앞으로는 또 어느 장단에 맞춰 춤을 추라는 소리인지 헷갈립니다.

MFC도 아니고 CRT가 뭐가 그렇게 자주 바뀌어야 하는지.. 그냥 아무 걱정 없이 윈도우 시스템 디렉토리의 msvcrt만 쓰던 시절이 살짝 그립기도 합니다. strcpy, malloc 이런 고전적인 함수가 도대체 뭐 변할 일이 있나요? (물론 보안 쪽으로 계속 더욱 강력해져 오긴 했죠. strtok_s, strcpy_s 이런 것도 마음에 듦.)

비주얼 스튜디오 2005로 처음으로 만들어 본 <날개셋> 4.8 64비트 바이너리들은 이런 잡음을 원천 봉쇄하기 위해, 메모리 낭비를 감수하고 일단 모든 바이너리에 CRT 라이브러리를 static 링크를 해 버렸습니다. 64비트 에디션을 앞으로 계속 이렇게 만들지는 미지수입니다.

다만, 이도 저도 못한 신세가 되어 버린 게 비주얼 스튜디오 2002/2003의 CRT DLL인 msvcr71입니다. side by side assembly 형식으로 완전히 넘어간 것도 아니면서, 운영체제의 기본 지원도 못 받아서 이 파일의 배포와 로딩은 재래식으로 응응 프로그램이 그냥 알아서 해야 합니다. 이제 VS 2005 쓰라고 64비트 지원도 중단됐고 그냥 과도기 DLL로 역사 속으로 사라지게 되었습니다.
VS 2003도 서비스 팩 1이 나온지라 버전이 살짝 다른 msvcr71.dll 두 버전이 존재하게 되었지만 겉보기 상으로 차이는 없는 듯합니다.

Posted by 사무엘

2010/01/10 22:17 2010/01/10 22:17
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/7

« Previous : 1 : ... 21 : 22 : 23 : 24 : 25 : Next »

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

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

Site Stats

Total hits:
1280873
Today:
60
Yesterday:
501