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

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

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

윈도우 운영체제에는 잘 알다시피 DLL이라는 게 있어서 한 프로세스를 구성하는 코드들을 여러 모듈로 분할할 수 있고, 반대로 동일한 코드를 여러 프로세스가 동시에 효율적으로 공유할 수 있다. 유닉스 계열의 운영체제에는 이와 비슷한 개념으로 shared library (*.so)라는 게 있다.

윈도우 DLL은 주소 공간이 해당 DLL을 로딩한 프로세스(EXE)에 완전히 종속된다. 그렇기 때문에 DLL이 사용하는 코드는 공유될지언정, 그 DLL이 선언하는 전역변수 같은 건 EXE마다 제각각이며, 심지어 동일 EXE를 여러 번 실행하더라도 제각각이다. 이것은 유닉스의 shared library와 다른 점이다. 윈도우에서 진짜 여러 프로세스들이 공유할 수 있는 메모리를 만들려면 내부적으로 공유되는 메모리 섹션을 별도로 생성해 놔야 한다.

이런 차이로 인해 특별히 조심해야 할 점이 있다. 한 EXE 안에서 3개의 DLL이 돌아가고 있고, 이들은 모두 동일한 버전의 비주얼 C++로 개발되어 동일한 CRT 라이브러리를 사용한다고 치자. 만약 이들이 모두 CRT를 DLL로 링크하여 동일한 CRT 라이브러리의 코드와 데이터를 공유한다면, 어느 모듈에서 malloc/new한 메모리를 다른 모듈에서 곧바로 free/delete해도 된다.

그러나 이들이 제각각 CRT를 static link했다면 그럴 수 없다. 한 모듈에서 할당한 메모리는 반드시 동일한 모듈에서만 해제해야 한다. 비록 메모리를 할당하고 해제하는 코드는 실질적으로 동일하지만 해당 코드가 동작하는 context는 모듈들이 모두 서로 다르기 때문이다. 모듈간에 메모리 할당과 해제를 자유롭게 하려면 각 모듈들은 자신만의 메모리 할당/해제 함수를 외부에다 별도로 제공해 줘야 한다.

DLL을 만드는 일은 64비트 운영체제가 등장하면서 다소 까다로워졌다. 32비트와 64비트 사이에는 DLL의 교차 로딩이 되지 않기 때문이다. 즉, 32비트 모듈(EXE/DLL)은 64비트 DLL을 읽을 수 없으며, 반대로 64비트 모듈은 32비트 DLL을 읽을 수 없다. 다시 말해, x64 운영체제에서도 x86 EXE를 아무 차이 없이 실행은 할 수 있지만, 그래도 64비트 EXE가 32비트 DLL을 내부적으로 읽을 수는 없으므로 착오 없기 바란다.

한 주소 공간은 전부 32비트 아니면 전부 64비트뿐이지, 두 종류의 코드가 섞여 있을 수가 없어졌다. 옛날과는 달리 지저분한 썽킹 계층 같은 게 없다. 동일 코드를 32비트와 64비트용으로 제각기 빌드해서 다같이 내놓아야 한다.
그러니 태생적으로 반드시 네이티브 코드 DLL을 만들어야 하는 컴포넌트/미들웨어/드라이버 분야라든가, 훅킹 내지 운영체제의 shell extension을 만드는 쪽은 다른 어떤 분야보다도 진작부터 64비트 프로그래밍이 필요했다. 사실은 IME 쪽도 예외가 아니다.

그런데 문제가 있다. 지금까지 DLL은 코드를 담는 용도로만 쓰여 온 게 아니라는 점이다. 잘 알다시피 윈도우 운영체제는 실행 파일의 포맷 차원에서 리소스라 하여 나름 계층을 갖춘 custom 데이터를 간편하게 불러오는 기능이 있다. 일일이 메모리 할당을 할 필요가 없이, 운영체제가 알아서 메모리 주소를 잡아서 로딩해 주고, API 함수 호출 한 번에 데이터를 곧장 찾아 주기까지 하니, 상당히 편리하다. 그래서 데이터 리소스만 갖춘 DLL도 엄청 많이 쓰였다.

특히 다국어 지원을 위한 외국어 리소스를 집어넣는 데 DLL보다 더 좋은 수단은 지금까지 별로 없었다. 운영체제의 대화상자 내지 string table 같은 주요 리소스 파일의 포맷은 32비트나 64비트나 바뀐 게 없다. 호환성 차원에서 일부러 동일하게 유지시킨 듯하다. 비주얼 스튜디오 200x의 MSDN(Document Explorer)이 사용하는 *.hxs 파일도 리소스 전용 DLL이다.

그런데 DLL은 태생적으로 컴퓨터 기계 종류에 지극히 종속적인 코드를 담는 게 주 목적인 파일 포맷이다. 그렇다면 기계 종류에 관계없이 동일한 데이터를 담는 DLL도 기계별로 일일이 다 따로 만들어야 할까?
그렇지는 않다. 데이터밖에 들어있지 않은 리소스 전용 DLL을 위해서 운영체제는 다음과 같은 배려를 하고 있다.

먼저, DLL을 읽을 때 흔히 사용하는 LoadLibrary 대신, LoadLibraryEx 함수는 LOAD_LIBRARY_AS_IMAGE_RESOURCE 같은 플래그를 제공하여 DLL의 코드 부분을 전혀 감안하지 않고 데이터 부분만 읽게 할 수 있다. 이렇게 하면 해당 DLL은 DllMain 함수 자체가 실행되지 않으며, 자신과 호환되지 않는 기계를 타겟으로 만들어진 DLL도 읽을 수 있다. 물론, FindResource 같은 함수로 리소스 추출만 가능할 뿐, GetProcAddress 같은 기능을 사용할 수는 없다.

다음으로, DLL 자신이 리소스 전용 DLL일 뿐이라고 명시해 줄 수 있다. 비주얼 C++의 DLL 프로젝트에서 링커 옵션을 보면 /NOENTRY가 있다. 이 옵션이 지정되면 그 DLL에는 아무 코드도 삽입되지 않으며, 진입점인 DllMain 함수 자체가 없는 리소스 전용 DLL임이 인증된다. 이런 DLL은 외부에서 그냥 대놓고 LoadLibrary로만 호출해도 기계 종류에 관계 없이 읽을 수 있다.

윈도우는 공유 라이브러리를 다루는 개념이 유닉스 계열과 차이가 있을 뿐만 아니라 실행 파일을 로딩하는 방식에도 개념적인 차이가 있다. 윈도우는 position-dependent, 즉, 주소 종속적인 방식이다. 윈도우의 EXE와 DLL에는 preferred address라는 게 있어서, 이 메모리 주소에 딱 로딩이 되면 아주 성능이 좋지만, 그 주소에 로딩이 못 되면 재배치 페널티가 따르는 방식을 사용한다. 재배치 작업을 어떻게 하면 되는지에 대한 재배치 정보가 reloc이라는 섹션에 있다.

EXE야 Win32s 같은 과거의 열악한 환경이 아닌 이상, 자신만의 고유한 주소 공간이 있기 때문에 언제나 preferred address에 로딩된다는 보장이 있다. 그에 반해 남의 주소 공간에 달라붙는 형태인 DLL은 그렇지 못하기 때문에 일반적으로 반드시 reloc 섹션이 필요하다.
다만, 이런 재배치 작업도 코드가 없는 리소스 전용 DLL에는 전혀 해당사항이 없다. 리소스 내부에 DLL의 메모리 주소에 종속적인 숫자 데이터 같은 게 있을 리가 없기 때문이다.

그러니 이론적으로 리소스 전용 DLL은 reloc 정보를 생략해 버려도 괜찮지만, 그냥 대놓고 생까-_- 버리는 것도 곤란하다. 윈도우 2000 미만의 옛날 운영체제의 경우 DLL을 preferred base에다 로딩을 못 하는데 재배치 정보마저 없다면, 원래 재배치를 할 게 없는 리소스 전용 DLL임에도 불구하고 그냥 일방적으로 로딩을 거부해 버리게 된다.

재배치를 안 해도 괜찮은 DLL이라는 별도의 플래그는 윈도우 2000 이상에서부터 도입되었다. 물론, 애당초 기계어 코드가 들어있지 않은 DLL의 reloc 섹션에 의미 있는 재배치 정보 자체가 있을 리가 없다. 그냥 껍데기뿐인 잉여인 것이다.

이렇듯, DLL은 원래 코드를 공유하려고 만들어졌지만 기계와는 무관한 리소스나 데이터를 공유하는 container 역할도 결코 무시할 수 없는 위치에 있다. 심지어 트루타입(TTF) 글꼴이 도입되기 전에 윈도우에서 쓰이던 비트맵/벡터 글꼴인 FON 파일은 16비트 형태의 리소스 전용 DLL로, 오늘날의 64비트 윈도우에서도 그 잔재가 남아 있다!

그래서 운영체제가 리소스 전용 DLL에 대해서는 예외적으로 기계를 가리지 않고 읽을 수 있고, 또 그런 DLL에는 불필요한 정보를 합법적으로 생략도 할 수 있게 별도의 배려를 해 왔음을 우리는 윈도우 API의 변천사를 통해 알 수 있다.
그러고 보니, 32/64비트 응용 프로그램이 각각 32/64비트에 해당하는 윈도우 시스템 디렉터리나 각종 프로그램 디렉터리를 얻어 오고 접근하는 법도 꽤 복잡해져 있는데, 이건 다음 기회에 다루도록 하겠다.

Posted by 사무엘

2012/02/19 08:29 2012/02/19 08:29
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/643

윈도우 운영체제가 제공하는 파일 목록 탐색 API로는 FindFirstFile, FindNextFile가 있다.
사실, 도스 시절에도 C언어에는 내부적으로 도스 API를 사용하는 _findfirst, _findnext 같은 함수가 있었는데, 윈도우 API 역시 그 인터페이스를 거의 그대로 차용했다.

파일을 탐색하는 동작은 state가 존재하는 costly한 작업이기 때문에, 파일을 여닫는 것처럼 핸들을 주고받는 과정이 수반되며, 탐색이 끝나고 나면 그 핸들을 반드시 닫아 줘야 한다.
state가 존재하는 덕분에, 파일 탐색을 하는 도중에 다른 디렉터리에 대해 다른 파일 탐색 작업을 시작할 수도 있다. 이게 가능해야 재귀적으로 하위 디렉터리 다단계 탐색을 할 수 있을 것이다. 참고로 C 표준 함수 중 strtok 함수는, state가 존재함에도 불구하고 state 핸들값을 별도로 받지 않아서 디자인상 문제가 있는 함수라고 까였음..

본인은 운영체제가 제공하는 파일 탐색 함수의 인터페이스에 대해 다음과 같은 불만이 있다.
먼저, 파일 탐색 동작을 식별하는 핸들값 HANDLE과, 파일이 계속 존재하는지를 판단하는 BOOL값을 따로 관리해야 한다는 것이다. FindFirstFile은 HANDLE을 되돌리고, FindNextFile은 BOOL을 되돌린다. 그래서 이들을 가지고 for문이라도 만들려면 두 변수를 모두 갖고 있어야 한다. (말만으로는 실감이 잘 안 갈 테니, 관심 있으신 분은 파일 탐색 루틴을 직접 짜 보기 바란다.)

MFC의 CFileFind는 기존 API 함수를 거의 그대로 캡슐화했지만 다행히 FindFirstFile에 해당하는 FindFile 함수도 동일하게 FindNextFile과 마찬가지로 BOOL을 되돌려서 그나마 낫다.
또한 소멸자는 자동으로 FindClose를 호출해 주며, 지금 찾은 파일에 대한 정보를 별도의 GetFilePath 같은 멤버 함수를 통해 얻어 올 수 있다. 그래서 아래와 같은 형태로 loop을 작성하면 된다.

CFileFind fnd; BOOL b;
for(b=fnd.FindFile(L"*.txt"); b; b=fnd.FindNextFile())
  Use(fnd.GetFilePath());

본인은 한술 더 떠서 이렇게 독자적으로 만든 클래스를 즐겨 사용한다. 생성자와 소멸자를 빼면 다들 연산자 오버로딩이다.

class CMyFileFind {
public:
  CMyFileFind(PCTSTR pszFile);
  ~CMyFileFind();
  const WIN32_FIND_DATA *operator ->() const;
  operator bool() const;
  void operator++(int);
};

for(CMyFileFind fnd(L"*.txt"); fnd; fnd++)
  Use(fnd->cFileName);

짠~
파일 탐색을 생성자에서 바로 시작할 수 있고, WIN32_FIND_DATA에 파일 정보가 존재하는지의 여부를 bool 형변환 연산자가 바로 알려준다. 그리고 ++ 연산자가 다음 파일 탐색을 의미하며, -> 연산자를 통해 찾은 파일 정보를 곧바로 얻을 수 있다. 깔끔하지 않은가? ㄲㄲ

개인적으로, FindNextFile 함수는 더 발견된 파일이 없는 경우 주어진 찾기 핸들을 자동으로 close해 버리는 기능도 있으면 좋겠다.
파일 탐색 기능에 앞으로 되돌아가는 기능이 있는 것도 아닌데(=PrevFile 같은 거라도..;;), 더 찾을 파일이 없으면 이 핸들은 닫아 버리는 것 말고 도대체 다른 용도가 있는가? 놔 둘 이유가 전혀 없다.
이렇게 되면 파일을 찾다가 중간에 멈추는 게 아닌 이상, FindClose를 번거롭게 또 호출해야 할 필요가 없어져서 좋을 것이다.

이 찾기 핸들의 자료형은 HANDLE이다. 하지만 파일이나 스레드 같은 커널 오브젝트가 아니어서 그런지, CloseHandle이 아니라 반드시 FindClose 함수로 닫아야 한다. 그리고 실패를 의미하는 값이 NULL이 아니라, 마치 CreateFile의 실패값처럼 INVALID_HANDLE_VALUE (-1)이다. 이런 인터페이스가 뒤죽박죽인 건 윈도우 API의 디자인 결함인 것 같다. memory-mapped file을 만드는 CreateFileMapping의 실패값은 또 NULL임.. -_-;;

또한, 파일과 디렉터리를 구분 없이 찾는 것도 개인적으로 무척 불만이다.
그래서 이 탐색 결과를 담고 있는 구조체에 대해서 dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY 체크부터 꼭 해 줘야 한다.
또한, 이런 디자인으로 인해, 어떤 디렉터리 내부에서 파일은 *.txt 같은 와일드카드로 찾고 디렉터리는 와일드카드 없이 다 찾으려면 검색을 두 번 수행해야 한다. 디렉터리 이름은 언제나 전체 검색이지 이걸 와일드카드로 찾는 일은 오늘날 전혀에 가깝게 없기 때문이다. DIR *.txt /S 같은 걸 구현하는 걸 생각해 보면 쉽게 이해가 될 것이다.

와일드카드를 해석하는 작업은 보통 운영체제가 알아서 해 준다. 하지만 도스와 윈도우는 전통적으로 이 알고리즘이 굉장히 단순하기 그지없어서 * 같은 경우 문자열의 뒤에만 붙일 수 있다. A*T.*P 같은 식의 패턴을 쓸 수는 없다는 뜻.
하지만 프로그래밍 언어나 런타임의 제작사에 따라서는 파일 탐색 기능을 제공하면서 와일드카드 해석은 독자적으로 하는 경우도 있다. 가령, 파이썬은 운영체제의 와일드카드 해석 루틴을 사용하지 않으며, 도스에서 구동되던 DJGPP도 디렉터리 아예 구분자로 \ 대신 유닉스처럼 /를 쓰는 등, 파일 경로 해석 자체를 독자적으로 한다.

이상 파일 탐색 관련 잡설이었다.
파일에서 뭔가 검색, 탐색을 한다고 하면 파일 내부에 있는 특정 문자열을 검색하는 것과, 파일 목록을 추출하는 것, 그리고 열어 놓은 파일 내부에서 읽거나 쓰는 지점을 이동하는 seek가 모두 가능하다.
그리고 특정 파일에 대해서 크기나 날짜 같은 부가 정보를 얻는 기능은, 열어 놓은 파일 핸들을 상대로 수행하는 것과 파일을 열지 않고 수행하는 것이라는 두 양상으로 나뉜다는 특징이 있다.

Posted by 사무엘

2011/07/29 08:32 2011/07/29 08:32
, ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/547

어지간한 중급 이상 수준의 기능을 갖춘 텍스트 에디터나 워드 프로세서들은 일명 ‘칼럼 블록’ 기능을 제공한다. 아래아한글은 도스 시절부터 ‘구역’이라고 하여 동일 기능을 제공했으며, 단축키는 F4였다. 일반 블록의 단축키는 F3이고 말이다. 마우스로는 그냥 드래그는 일반 블록이고 Alt+드래그가 칼럼 블록으로 통용되고 있다. 칼럼 블록을 만드는 키보드 단축키가 통일되어 있는지는 잘 모르겠다.

칼럼 블록이 일반 블록의 복붙 동작과는 어떤 차이가 있으며 얼마나 유용한지 일일이 구차하게 설명하지는 않겠다. 칼럼 블록은 불연속적인 여러 줄들의 일부를 통째로 선택할 수 있을 뿐만 아니라, 붙이는 동작도 여러 줄에다가 내용을 끼워 넣는 식으로 달라진다. <날개셋> 편집기는 전문적인 에디터를 표방하면서 개발되고 있지는 않기 때문에, 현재 (아쉽게도) 칼럼 블록을 지원하지는 않는다.

그런데 칼럼 블록을 구현할 때 현실적으로 부딪히는 문제가 있다. ‘붙이기’를 할 때 클립보드의 내용이 일반 블록인지 아니면 칼럼 블록인지를 어떻게 판별할 거냐는 것이다.
제일 간단한 방법은 응용 프로그램이 별도의 플래그를 갖고 있는 것이다. 클립보드에다가는 일반 블록처럼 텍스트만 복사해 놓으나, 이 블록이 칼럼 블록이라면 플래그를 켠다. 그래서 붙이기를 할 때 플래그가 켜져 있으면 칼럼 형태로 붙인다.

윈도우 탐색기가 파일을 클립보드에다 복사(Copy)한 것인지 오린(Cut) 것인지 판별할 때도 내부적으로 이런 자체 플래그를 쓴다. 파일은 오려 놓는다고 해서 실제로 파일을 지워 버릴 수는 없으므로, 자체적인 표식밖에는 구분할 방법이 없으니 말이다. 파일의 오리기는 텍스트의 오리기와 다르다. 더 나아가면, 엑셀 같은 스프레드 시트의 오리기도 마찬가지임.

하지만 이 방법을 쓸 경우, 칼럼 블록을 복사해 놓고는 다른 응용 프로그램에서 텍스트를 또 복사했을 때, 그 텍스트도 칼럼 형태로 붙여진다는 문제가 있다. 국산 에디터인 AcroEdit, 그리고 유명한 개발 IDE인 Source Insight가 칼럼 블록을 이런 식으로 구현했고 저런 동작을 보이는 것을 확인했다.
내부 플래그 방식으로 칼럼 블록을 구현했다면, 클립보드 내용이 외부에서 바뀌었을 때 내부 칼럼 플래그를 끄는 기능도 구현해야 할 것이다.

이런 방식 말고, 클립보드 차원에서 아예 자신만이 인식 가능한 별도의 포맷을 등록하는 방법도 있다. 칼럼 블록은 그 포맷으로 복사한 후, 붙이기를 할 때 그 지정 포맷이 존재하면 일반 형식이 아닌 칼럼 형식으로 붙여 넣는 것이다.
물론, 칼럼 블록을 복사하더라도, 다른 프로그램이 내용을 일반 블록 형태로 붙여넣을 수도 있게 일반 텍스트 형식으로도 복사는 해 놓는다.

국산 에디터인 EditPlus, 그리고 MS 비주얼 스튜디오 IDE는 이렇게 칼럼 블록은 별도의 클립보드 포맷을 써서 복사해 놓는 것을 확인했다. 이렇게 하면 칼럼 블록을 오로지 자기 프로그램에서 생성한 클립보드 데이터를 통해서만 인식할 수 있기 때문에 앞서 언급했던 오동작이 발생할 여지가 없다.

EditPlus의 식별자는 “EditPlus Column Selection”이요,
비주얼 스튜디오의 식별자는 “MSDEVColumnSelect”이다. 다른 프로그램들은 어떨지 모르겠다.
워드 프로세서들은 어차피 자기네 고유 포맷을 쓰는 게 관행이기 때문에 칼럼 블록만을 위한 고유 포맷을 만들지는 않는 듯하다. (아래아한글과 MS 워드의 경우)

개인적은 생각은, CF_TEXT 같은 것처럼 칼럼 블록을 위한 텍스트도 운영체제 차원에서 표준 클립보드 포맷을 도입하면 좋지 않을까 싶다. 내부적으로 전세계의 수많은 텍스트 에디터들이 자신만의 고유 포맷으로 칼럼 블록을 표현하고 있을지 알 수 없는 노릇이기 때문이다. 그게 제정되면 칼럼 블록도 에디터들마다 공유가 가능할 것이다.

Posted by 사무엘

2011/07/10 08:08 2011/07/10 08:08
, ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/538

도움말의 역사

오늘날 PC의 GUI 환경에서 돌아가는 거의 모든 프로그램들은 F1을 누르면 도움말이 나온다.

윈도우는 운영체제 차원에서 표준 도움말 규격이 있는 것으로 예로부터 유명했다. 아예 운영체제의 API에 WinHelp 같은 함수가 정식으로 등재되어 있다. -_-
맥 OS는 모르겠고, 리눅스는 그런 시스템이 없다고 들었다(쉘부터가 GNOME이 뭔지 KDE가 뭔지 사실 아직도 알쏭달쏭... ㄲㄲ).

그 원조는 바로 WinHelp와 그 기반의 HLP 도움말 파일이다. 20년 전의 윈도우 3.0때 처음으로 도입된 이 기능은 나름 굉장히 유용했다. 다양한 서식을 적용한 텍스트 + 이미지 + 하이퍼링크 + 팝업창은 일종의 인터넷 WWW와도 비슷한 수준의 인터페이스였다. WinHelp는 의외로 기능이 다양해서 도움말에다 색인도 넣고, 도움말 창의 버튼도 customize 가능했다.

당시 WinHelp를 설계한 엔지니어가 누군지는 모르겠지만, 도움말이라는 간단한 주제 하나만으로 굉장한 대작을 만들어 냈다. 윈도우 3.1 때 도움말 시스템이 소폭 업그레이드되긴 했으나, 아직까지는 대동소이하고 하위 호환성 정도는 유지되었던 걸로 본인은 기억한다.

도움말은 기본적으로 오늘날 워드패드가 사용하는 RTF(서식 있는 텍스트) 기반이었다. 문서 파일에다가 각종 도움말 메타정보를 WinHelp 스펙대로 넣어 준 후, 이들 파일과 그림들을 Help Compiler로 컴파일하고 압축하면 HLP 파일이 생성되었다. 하지만 이건 대단히 번거롭고 까다로운 일이었기 때문에, 그 절차를 간소화해 주는 위지윅 도움말 저작 도구도 응당 개발되어 나오곤 했다.
16비트 윈도우용 SDK를 보면 도움말 컴파일러가 HC30 (윈 3.0 공용), HC31 (윈 3.1 전용)이 따로 있었다.

이 WinHelp는 윈도우 95에서는 4.0으로 버전이 올라가고 기능이 훨씬 더 강화되었다. 계층 구조의 목차가 따로 추가되어서(*.CNT) 도움말의 첫 화면에다 번거롭게 목차를 본문 형태로 넣을 필요가 없어졌으며, What's this?라는 풍선 도움말이 추가되었다. 창도 더욱 아담해지고 응용 프로그램과 통신만 잘 주고받으면 거의 CBT 수준의 인터렉티브한 도움말을 만들 수도 있게 됐다.

윈도우 3.x 시절에는 매 대화상자마다 한쪽 끝에 ‘도움말’ 버튼이 있는 게 유행이었는데 그게 95부터는 다 사라졌다. 그 대신 X 버튼 왼쪽에 [?] 버튼이 생겼다.

그리고 또 여기서 주목할 점은, 도움말 시스템이 16비트(winhelp.exe)와 32비트(winhlp32.exe)로 완전히 분리되었다는 것이다. 왜 그럴까?
윈도우 운영체제의 도움말은 단순히 하이퍼링크가 달린 RTF 문서 뷰어 수준을 훨씬 더 능가하는 방대한 시스템이었기 때문이다.

HLP 파일은 윈도우 API를 호출하는 건 물론이고 WinHelp 규격대로 만들어진 플러그 인 DLL들을 붙여서 도움말 화면을 사실상 마음대로 제어할 수 있었다. 나만의 버튼을 추가하고, 확장 기능을 넣고... DLL이 들어간 이상 16비트와 32비트의 분리는 불가피해진 것이다. 지금 같으면 32비트와 64비트의 분리가 필요하겠지.

얼마나 customize가 가능하냐 하면, HLP 파일에다가 마치 오늘날의 HTML 도움말(CHM)처럼 목차 탭을 도움말 내부에다 보조 윈도우로 집어넣을 수 있었다. 도움말이 WinHelp에다가 아예 없던 기능을 추가할 수 있을 정도였던 것이다.
과거 본인이 즐겨 이용하던 Paint Shop Pro 7은 Robo HelpOffice라는 저작 도구로 만들어진 HLP 도움말을 제공했는데, 정말 기능이 상상을 초월하게 화려했다.

이게 웹으로 치면 ActiveX이다. 도움말 세계의 ActiveX인 셈인 것이다. -_-;;

그랬는데, 윈도우 98 + 인터넷 익스플로러 4가 되면서 새로운 도움말 시스템이 또 등장했다. 서식 있는 텍스트+하이퍼텍스트의 진수는 바로 웹이 아니던가. RTF가 아니라 아예 IE의 엔진을 쓰는 도움말이 생긴 것이다. 이것이 오늘날까지 전해져 오는 HTML 도움말이며, 파일의 확장자는 CHM이다. Compiled HTML.

HTML 도움말은 내부적으로 IE를 쓰는 관계로 과거의 WinHelp보다 훨씬~ 더 덩치 크고 무거웠다.
IE 4를 얹지 않은 옛날 윈도우 95 시절의 탐색기 vs 오늘날 탐색기의 덩치 및 구동 시간은
과거 WinHelp 도움말 vs 오늘날 HTML 도움말의 덩치 및 구동 시간

이건 비슷한 구도이다. -_-;;;
하지만 웹에서 쓰이는 각종 자바스크립트+다이나믹 비주얼 효과를 도움말에서도 그대로 재활용 가능하다는 점을 감안하면, 웹 기술을 도움말에다 활용하겠다는 발상 자체는 훌륭하다고 볼 수 있다. WinHelp 기술은 윈도우 밖에서는 아무 쓸모도 없는 테크닉이지 않은가.

개발자의 입장에서야 RTF보다야 HTML이 훨씬 더 친근하니 예전보다 도움말 만들기가 쉬워진 것도 아주 좋다. 본인도 HLP 도움말은 만들 엄두를 못 냈었는데 CHM 도움말은 나모나 프런트페이지만으로도 비교적 손쉽게 만들 수 있었다.
홈페이지 만드는 데 쓰이는 파일을 그대로 모아서 컴파일만 하면 끝. 그러니 CHM 파일은 웹 문서 아카이브를 만드는 데도 아주 유용했다.

일반 웹에는 존재하지 않지만 도움말에는 필요한 기능이 있다. 가령, 팝업 메뉴를 띄운다거나 외부 프로그램을 실행하는 기능은 소스를 보면 MS가 자체적으로 ActiveX처럼 비표준 확장 태그를 써서 구현해 놓은 걸 볼 수 있다.

CHM 도움말은 장기적으로 기존 HLP 도움말 시스템을 대체할 목적으로 만들어졌기 때문에, HLP로 할 수 있는 일은 CHM으로도 다 할 수 있게 돼 있다. 가령, CHM으로도 풍선 도움말을 구현 자체는 할 수 있다. 하지만 그 쬐그만 풍선 도움말이 웹 페이지 내용이라는 건 영 안 어울린다. 실제로, 비스타부터 풍선 도움말은 윈도우 운영체제 내부에서는 완전히 사라졌다.

윈도우 98부터 XP까지 운영체제의 도움말은 WinHelp와 HTML 도움말이라는 양분된 구도를 유지하고 있었다. 그러다 윈도우 비스타는 과감하게 WinHelp를 없애 버렸다. HLP 도움말을 열면 ‘이 도움말은 옛날 버전으로 만들어져서 이제는 더 지원되지 않습니다’만 뜬다. (단, 16비트용 WinHelp는 남아있음)
원래 마소는 과거 호환성을 극도로 존중해 주는 집단이다. 그런데 왜 이런 조치를 취한 것일까?

오늘날 과거 호환성보다 더 중요한 건 보안이기 때문이다.

HLP와 CHM 모두 단순히 read-only 하이퍼텍스트 문서만 취급하는 게 아니다. 사용자의 컴퓨터에 있는 응용 프로그램을 실행할 수 있고, DLL의 코드를 불러와서 실행할 수 있다. 따라서 잠재적 보안 위험성도 충분하다.

마소는 21세기부터 자사 소프트웨어에 있는 이스터 에그를 모두 없앴으며, 이미 짜 놓은 수많은 코드에 대해서도 대대적인 보안 강화 리팩터링을 시작했다. 비주얼 C++ 2005부터는 잘 알다시피 비표준 오명을 감수하고라도 C 라이브러리까지 뜯어고쳐서 *_s 함수를 도입할 정도였다.

그랬는데 그렇잖아도 구닥다리 WinHelp의 코드를 보니까, 이건 기능도 카오스 그 자체이지, 앞으로 지원도 안 할 건데 리팩터링을 할 필요를 못 느낀 것이다. 그러니 철도 당국이 수익 안 나는 간이역을 폐역하듯이 지원 중단 결정을 내렸다. WinHelp 함수 지못미.

이런 보안 강화 정책으로 인해, 윈도우 비스타부터는 탐색기에 16비트 윈도우 실행 파일들이 아이콘이 그려져 나오지 않는다. 웹페이지의 파비콘을 추출하고 그리는 코드의 허점을 이용해서도 악성 코드를 주입하고 실행할 수 있을 정도이니 말 다 했다. -_-;; 비슷한 이유로, 워드패드에서 과거 wri 파일 포맷을 읽는 기능도 삭제되었다. 구닥다리 코드는 이제 와서 보안을 강화하는 작업을 하기 귀찮으니까 그냥 무시하기. ㄲㄲ

사실은, CHM 파일마저도 이제 MS가 더 적극적으로 개발을 안 하기는 마찬가지이다. 요즘 MS가 만드는 프로그램들은 CHM 안 쓰고 또 자기만의 다른 도움말 시스템을 쓴다. 비록 내용 렌더링이 HTML 기반인 건 동일하지만, CHM은 아니라는 뜻. 그 때문인지는 모르겠지만 HTML 도움말을 보면, 도구상자의 아이콘은 10년 전이나 지금이나 아직까지도 16컬러 그대로이다. ㄲㄲㄲ

아울러, CHM 역시 보안 위협을 많이 받는 관계로, 웹에서 받아서 바로 실행한 녀석은 내용이 표시되어 나오지 않는다. 반드시 로컬 환경에다 저장해서 ‘속성 -> 제한 해제’를 해 줘야 내용을 볼 수 있다.

앞으로 윈도우 운영체제의 도움말 시스템이 어떻게 변모할지는 알 수 없는 노릇이다. 하지만 설마 CHM이 HLP처럼 그렇게 갑작스럽게 호락호락 없어지지는 않을 듯하다.

하긴, 예전엔 아래아한글도 언어(한국어든 영어든)와 플랫폼(3.1/95/NT)을 불문하고 동일하게 표시되는 GUI 엔진을 표방하고서, 도움말조차 자체 포맷을 만들었던 적이 있다. 97까지만 해도 윈도우 3.1 도움말 스타일의 자체 도움말을 썼었는데 워디안/2002 이후부터는 싹 잊혀지고 그냥 CHM을 쓰기 시작했다.
도스용 프로그램 개발할 때 도움말 기능을 구현하던 추억이 아직도 새록새록하다. ^^

※ 잡설: 응용 프로그램의 보안 문제

유명한 국산 압축 프로그램인 빵집의 구버전에서, 악의적으로 일부 내용이 조작된 zip 파일을 열자 엉뚱하게도 내가 지정해 준 프로그램이 실행된다. 프로그램의 보안 취약점을 시연하는 동영상을 보니 신기하기 그지없었다.

프로그램의 소스로는 제각각의 이름으로 구분되는 수많은 변수와 함수의 명칭들이, 빌드 후에는 그저 아무 의미 없는 오프셋 내지 메모리 주소로 바뀐다. 그러니 이런 정교한 숫자가 조금이라도 바뀌면 프로그램은 전혀 다른 동작을 하게 된다. 그런 조작은 입력 파일의 조건 검사를 허술하게 하는 프로그램의 허점을 이용해서 가능하다. 더구나 zip은 프로그램 실행과는 전혀 관계없는 데이터 파일일 뿐인데, 하물며 대놓고 프로그램 실행 기능이 있는 파일은 얼마나 위험하겠는가?

사용자의 입장에서는 각종 보안 업데이트가 귀찮기 그지없다. 하지만 개발자의 입장에서는 보안 업데이트를 안 하면 어떻게 되는지를 너무 자세하게 설명해 줄 수도 없다. 그러니 서로 답답한 노릇이다.

“모방 범죄 예방을 위하여 더욱 정확한 후레쉬 조작법... 이 아니고 더욱 정확한 악성 코드 삽입 방법은 알려드리지 못하는 점 양해 바랍니다.” ㅋㅋㅋㅋ

Posted by 사무엘

2011/05/03 08:14 2011/05/03 08:14
, , , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/505

※ 비주얼 스튜디오 2003

지금은 바야흐로 2011년. 비주얼 C++ 2010이 나온 지 이미 1년이 지났고 C++0x 규격까지 등장한 마당에, 그 옛날 버전인 비주얼 C++ 닷넷 2003 관련 블로그 포스트를 아직도 적지 않게 찾을 수 있어서 본인은 놀랐고 한편으로 동질감을 느꼈다.
본인도 <날개셋> 타자연습을 비롯해 여러 legacy 프로젝트들을 여전히 VS 2003으로 관리하고 있다.

특히 2005와 그 후대 버전들은 MFC 라이브러리가 너무 심하게 bloat되어서 본인은 업그레이드할 의욕을 대략 상실했다.
static link를 할 엄두를 못 내겠는데, dynamic link 정책도 영 괴상한 방식으로 바뀌니..
MFC 라이브러리보다도 덩치 더 큰 포토샵, MS 오피스급의 초대형 상업용 프로그램을 개발할 때라면 모를까, 이건 소규모 프로그램 개인 개발자에겐 영 아니라는 생각이 든다.

그래도 듣자하니 2010은 msvcr100과 mfc100을 굳이 winsxs 매니페스트 방식을 안 쓰더라도 자기 local 디렉터리에서 로딩 가능하게 바뀌었다고 하던데, 그건 그나마 다행인 점이다. 전세계에서 터져 나온 개발자들의 불만을 비주얼 C++ 팀이 받아들인 모양이다. Class wizard도 부활하는 등, 2010이 꽤 참신한 변화를 많이 했다. 2005->2008은 연도 차이가 3임에도 불구하고, 2일 뿐인 2008->2010보다 변화량이 훨씬 더 적었다.

잡설이 길었는데..
운영체제인 윈도우 비스타는 시스템 계층에서 바뀐 게 많다 보니, 예전의 2000/XP와는 달리 비주얼 스튜디오, 혹은 비주얼 C++에게 그리 자비롭지 않았다. 비스타뿐만이 아니라 7도 마찬가지.

구닥다리 6.0은 그런 최신 OS에서 동작 자체는 한다만, 설치하는 과정에서 무슨 문제가 있을 수 있다고 하고(OLEView 같은 일부 컴포넌트),
2003은 비스타에서 대놓고 좀 잡다한 문제를 일으킨다. 하지만 MS는 VS 2003 지원은 2006년 중반의 SP1을 끝으로 이제 완전히 끊었다. 비스타 이후부터는 버려진 자식. -_-;;

그 잡다한 문제 중 유명한 게 뭐냐 하면, Find in files 기능.
윈도우 비스타/7에서 VS 2003으로 이 명령을 내리고 있으면 IDE가 응답 없이 그대로 멎어 버렸다. 파일 검색 기능을 쓸 수 없다는 소리. 그런데 이건 개발자가 밥먹듯이 써야 하는 기능인데, 이거 없으면 불편해서 프로그램 개발 어떻게 하라고? -_-;;;

이 문제를 해결하는 방법은 의외로 간단하다.
탐색기로 DEVENV.EXE를 찾아가서 Alt+Enter로 속성을 꺼낸 후, 호환성 탭에서 시각 테마를 꺼 버리면 된다. 이 팁을 올린 각종 블로그 포스트에는 한국어와 영어를 불문하고 "너무 좋은 정보군요.", "알려 주셔서 대단히 고맙습니다", "퍼 갑니다" 댓글들이 즐비하다. 아직도 VS 2003 쓰는 개발자가 많다는 뜻 되겠다. X86 아키텍처와 PE 방식 실행 파일이 존재하는 한, C++, Win32 API, 네이티브 코드 자체가 근본적으로 유행을 그렇게 타는 분야가 아니니 말이다.

비주얼 C++ 2003은 MS 오피스 XP와 동일한 GUI 기반이며, 어차피 comctl32 6.0 기반의 시각 테마를 쓰지도 않는 프로그램이다. 그러니 프로그램 외형이 딱히 달라지지도 않는다.
단, 그 비주얼 C++로 실행한(디버거를 붙인 F5든, 붙이지 않은 Ctrl+F5든) 나의 프로그램도 시각 테마가 무시된 채 실행되므로 그건 주의해야겠다. 물론, 탐색기 같은 걸로 따로 실행하면 문제 없음.

덧붙이자면 VS 2003은 도킹 윈도우 같은 걸 드래그하여 이동할 때 점선으로 윤곽이 나타나는데, 이 역시 알다시피 Aero 하에서는 동작이 굉장히 굼뜬다.
이것이 VS 2005부터는 개선되어 파란 도킹 다이아몬드도 생기고 비주얼이 산뜻해졌으나, 어차피 2005조차도 비스타에서 제대로 돌아가지 않긴 마찬가지이다.

SP1에다가 비스타 패치까지 다 복잡하게 설치해야 한다. 문제는 SP1과 비스타 패치 각각이 VS 2005를 처음 설치하는 것만치 시간이 욕 나오도록 오래 걸린다는 것. ㅆㅂ
윈도우 비스타보다 늦게 나온 2008부터가 드디어 비스타에서 별 트러블 없이 잘 돌아가며, 내장하고 있는 플랫폼 SDK도 윈도우 비스타 기준 최신이다.

※ 32비트 프로그램이 64비트 프로그램 디렉터리에 접근하기

잘 알다시피 64비트 윈도우에서도 시스템 디렉터리의 경로는 32비트와 마찬가지로 windows\system32이다. 64가 아니다.
그럼 64비트 윈도우 내부에서 32비트 시스템 파일들이 들어가는 경로는 windows\SysWOW64이다.

그런데 본인은 며칠 전 굉장히 놀랐다.
GetSystemDirectory를 호출하는 코드를 32비트로 빌드하나, 64비트로 빌드하나, 실행 결과는 windows\system32로 동일했기 때문이다. 왜 그럴까?

이는 일종의 가상화 내지 redirection 메카니즘 때문이다.
64비트 윈도우에서 동작하는 32비트 프로그램은 애초에 64비트 윈도우 시스템 디렉터리로 접근을 아예 할 수 없다. system32와 syswow64가 모두 보이긴 하지만, system32 디렉터리를 들여다보면 운영체제가 보내 주는 것은 어차피 syswow64의 정보뿐이다. 그 안에서만 놀아야 한다.

Program Files 디렉터리는 그렇지 않아서 32비트 프로그램이 32비트용 경로와 64비트 경로로 모두 따로 접근이 가능하다. 하지만 어차피 known path를 운영체제 차원에서 얻는 방법이 없다. 64비트 프로그램은 64비트용 경로와 32비트용 경로에 모두 접근 가능하지만 32비트 프로그램은 64비트용 경로를 얻을 수 없다.

이렇게 가상화를 너무 잘 해 주기 때문에, 심지어 64비트 OS에서도 32비트 프로그램은 GetSystemInfo 함수를 호출하더라도 멀쩡한 64비트 x64 컴퓨터를 32비트 x86으로만 인식한다. 이 OS가 진짜 64비트인지 32비트 프로그램도 알려면, GetNativeSystemInfo라는 새로운 함수를 써야 한다.

32비트 윈도우에서 <날개셋> 한글 입력기 64비트 에디션은 당연히 설치가 되지 않지만, 64비트 윈도우에서 32비트 에디션은 설치가 가능하다. 이 경우, 편집기나 변환기 같은 프로그램이야 별 차이 없이 쓰겠지만, 외부 모듈이나 입력 패드는 당연히 64비트 프로그램에서 제대로 쓸 수 없다.

그래서, 64비트 윈도우에서 <날개셋> 32비트 에디션이 설치되었을 때, "에디션을 잘못 고르신 것 같은데, 가능하면 64비트 쓰시져?" 하는 경고 메시지를 띄워 주고 싶다. 그런데, 32비트 프로그램이 자기 주변이 64비트인지 파악하기가 의외로 쉽지 않아서 고민이다. 운영체제가 64비트인 경우, 자신이 64비트 모듈과 병행 설치도 되어 있는지 체크를 해야 하는데 파일 시스템이 워낙 저렇게 샌드박스화해 있으니 말이다. =_=;;

※ TlsGetValue와 에러 코드

파일을 읽어서 주어진 일을 처리하는 함수를 만들었다. 이 함수는 파일을 찾지 못하면 false를 즉시 리턴하며, 그 후 이 함수가 호출한 CreateFile 함수가 남긴 GetLastError 값을 바탕으로, 응용 프로그램은 에러 메시지를 찍는다는 게 본인의 계획이었다.

그런데 이 함수가 선언한 각종 객체의 소멸자 함수가 뭔가 처리를 하면서, 또 GetLastError 값을 바꿔 버리기 때문에 정작 파일 조작이 실패한 이유를 밖에서 알 수가 없게 되는 경우가 있었다.
도대체 어디서 에러 코드가 바뀌지? MSDN을 뒤져보다 본인은 깜짝 놀랄 수밖에 없었다.

일반적으로 윈도우 API 함수들은 동작이 실패했을 때만 에러 코드를 설정한다. 그러나 TLS 값을 되돌리는 TlsGetValue 함수는 성공일 때도 에러 코드를 에러 없음을 의미하는 0으로 설정함으로써, 예전 함수의 에러 코드를 덮어써 버린다. 왜냐하면 리턴값 0만으로는 진짜 TLS 슬롯 값이 0인지, 아니면 실패를 의미하는 0인지 알 수 없기 때문이다.

TlsGetValue 함수는 C/C++에 언어적으로 존재하지 않는 새로운 scope를 만드는 것이나 마찬가지인 함수이기 때문에 밥먹듯이 자주 호출된다. 성능이 매우 중요함에도 불구하고 이 함수는 예외적으로 언제나 에러 코드를 건드리도록 설계되어 있다.

이게 에러 코드인지, 정상적인 리턴값인지 알 수 없는 예로 GetExitCodeThread/Process 함수가 있다.
STILL_ACTIVE라는 값이 리턴되었는데, 이게 해당 스레드나 프로세스가 종료하면서 진짜로 리턴한 값인지, 아니면 그게 아직 종료되지 않은 상태인지.. 알 게 뭐야..;;
개인적으로 함수를 왜 저렇게 설계했는지 모르겠다. 어지간하면 성공/실패 여부를 별도의 인자에다가 따로 되돌리게 하는 게 훨씬 안전할 텐데.

Posted by 사무엘

2011/04/08 17:29 2011/04/08 17:29
, ,
Response
No Trackback , 7 Comments
RSS :
http://moogi.new21.org/tc/rss/response/493

윈도우 프로그래머라면 이미 다 아시겠지만, 비스타에서부터 task dialog라는 아주 참신한 UI 기능이 추가되었다.
구닥다리 MessageBox를 쓰자니 뭐가 많이 부족하고,
그렇다고 해서 겨우 에러 메시지 하나 찍자고 별도의 대화상자를 또 만들자니 너무 번거로운데
task dialog는 가히 사막에 있는 오아시스 같은 존재가 아닐 수 없다.

사용자 삽입 이미지
위의 그림은 바로 task dialog의 뼈대. (출처: MSDN)

이제 당장 운영체제부터가 상당수의 UI를 task dialog으로 구현하고 있고,
메모장부터 워드패드까지 모든 기본 프로그램들의 “문서를 저장하시겠습니까?” 대화상자도 죄다 task dialog로 바뀌었다.
덕분에 Yes / No 일색이던 버튼이 Save / Don't save로 바뀐 걸 알 수 있다. task dialog는 각 버튼들에 들어가는 텍스트를 사용자가 자유롭게 지정 가능하기 때문이다.

Y/N이라고만 하면 이게 무슨 질문에 대한 “예/아니요”인지, 응답에 대한 결과를 사용자가 한 단계 더 추론을 해야 한다.
그러나 대놓고 “저장함/저장 안 함”이라고 표시를 해 주면, 이 선택으로 인해 야기되는 결과를 사용자가 더 직관적으로 알 수 있다. MS는 저런 UI 용어 하나하나까지 세심하게 검토를 해 온 것이다.

이것뿐만이 아니라 또 개인적으로 본인은 task dialog가 유용하다고 가장 먼저 느낀 면모가 뭐냐 하면,

“다음부터 이 확인 질문 안 하기” 부류의 체크 상자를 간단하게 추가할 수 있다는 점이었다. 과거의 MessageBox에서 진짜로 2% 부족한 면모였다.
그래픽 모드나 해상도를 바꾼 뒤에 타이머를 걸어서 “화면이 잘 나타나 보입니까? n초 이내에 응답이 없으면 원래 모드로 되돌립니다”를 구현하는 것도 이 task dialog로는 드디어 가능하다. 예전에는 그런 걸 구현하려면 전용 대화상자를 따로 만들어야 했다.

task dialog에는 인터넷 URL 링크를 넣을 수 있고, 라디오 버튼을 넣어서 사용자의 간단한 선택을 받을 수도 있다. 제목-본문 형태로 텍스트를 깔끔하게 배치할 수 있다는 것도 아주 좋은 점이다.
물론, 워낙 기능이 많기 때문에 사용하기가 다소 까다롭다는 건 어쩔 수 없다. 그래서 이를 간소화하기 위해, 비주얼 C++ 2008의 확장팩 내지 2010부터는 MFC에도 CTaskDialog라는 클래스가 추가되었다. 자료구조 관리는 이 클래스가 다 알아서 해 주기 때문에 사용자는 코드 한 줄로 간단하게 원하는 버튼, 원하는 컴포넌트들을 대화상자에다 추가할 수 있다.

그런데 task dialog로 할 수 있는 일은 단순히 메시지를 찍고 사용자로부터 간단한 피드백을 받는 일에 국한되지 않는다.
progress bar를 넣는 기능이 있고 bar의 상태를 일정 주기로 업데이트까지 가능하기 때문에, 이를 이용하면 진행 상황 표시 대화상자도 간단하게 구현 가능하다.

본인은 task dialog를 제어하는 코드와 스레드 작업 관련 코드를 한데 합쳐서 별도의 클래스를 만들어 이를 개인적으로 매우 즐겨 사용한다. task dialog를 사용하는 형태는 딱 정해져 있으니까 별로 customize를 하지 않고, 작업 상황 표시와 작업 스레드의 customization이 이 클래스의 존재 목표가 되는 셈이다.

task dialog 콜백과 스레드 콜백 함수는 내부의 private static 함수로 숨겨 놓는다. 스레드 콜백 함수는 this 포인터에 대해서 아래의 순수 가상 함수를 호출한다.

virtual UINT Work() = 0; //오버라이드 할 것
volatile int m_nCurPos, m_nPosMax; //현재/전체 진행 상황
volatile bool m_bCancel;

그리고 task dialog 콜백은 당연히.. 주기적으로 m_nCurPos 값을 체크하여 progress bar를 업데이트한다.
사용자가 도중에 취소 버튼을 눌러 버렸다면, m_bCancel 플래그가 설정된다. 작업 스레드는 이 값을 수시로 체크해서 사용자가 중단을 요청했다면 신속히 작업을 중단해야 할 것이다.

일이 언제 끝날지 모르는 작업에 대해서는 게이지가 marquee 형태로 뱅글뱅글 돌기만 하게 만들 수도 있다. 윈도우 부팅할 때처럼 말이다.

다만 한 가지 아쉬운 것은, task dialog는 진행 상황 표시만 전문으로 하는 녀석이 아니다 보니, progress bar를 두 개 표시해 주는 기능은 없다는 점이다.
설치 프로그램이라든가 압축/FTP 유틸리티처럼 파일을 다루는 프로그램들은 현재 처리하고 있는 파일의 진행률과 그리고 전체 작업의 진행률을 한데 표시하고 있으며, 이건 매우 흔한 관행이다. 이건 여전히 내가 직접 대화상자를 만들어야 할 것 같다.

.
.

그나저나 드디어 윈도우 7도 SP1이 정식 출시된 지 한 달쯤 됐다.
콘솔에서 세벌식으로 한글 입력할 때 한글+기호 입력이 제대로 안 되던 버그도 고쳐졌으려나? (난 7 안 써서 잘 모르겠다) 했는데
어느 지인의 얘기에 따르면 여전하다고 하네... -_- 어쩌라고.

Posted by 사무엘

2011/03/17 08:32 2011/03/17 08:32
, , ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/481

큼직한 그래픽 화면에서 마우스로 조작하는 컴퓨터-사용자 인터페이스를 일명 GUI라고 부른다.
매킨토시가 원조라고 하는 이런 환경에서는 대화상자가 라벨, 입력란, 리스트박스, 버튼 같은 몇몇 기초 UI 요소들로 구성되며, 이 구성요소들을 윈도우 프로그래밍에서는 ‘컨트롤’이라고 부른다. GUI에서 일종의 부품과도 같다.

이런 GUI와는 달리, 그냥 전통적인 선택 막대만으로 각종 기능을 선택하고 옵션을 설정하는 단순한 인터페이스도 있는데, 과거의 도스용 아래아한글이 대표적인 예였다.
단순한 인터페이스는 말 그대로 너무 단조로워 보이기는 하지만, 다른 건 몰라도 화면 차지 면적이 작다는 장점 하나는 독보적이기 때문에 요즘은 스마트폰의 UI에서 제 위치를 찾은 것 같다.

이 글의 주제는 윈도우 GUI 컨트롤이므로 다시 GUI로 화제를 바꾸기로 한다.
아까 말했던 입력란(edit box), 라벨, 리스트박스, 버튼(체크, 라디오 등도 포함) 같은 건 기본 중의 기본 필수 요소이며, 까놓고 말해 윈도우 1.0 시절부터 존재했던 녀석이다. 해당 컨트롤의 기능은 운영체제와 완전히 일심동체가 되어 깊숙이 박혀 있다.
비단 운영체제의 GUI뿐만이 아니라 어느 GUI 툴킷을 보더라도 저런 컨트롤이 빠진 물건은 없다.

그런데 세월이 흐르다 보니 좀 더 새끈하고 산뜻한 컨트롤이 필요해졌다.
그래서 1990년대 후반, 윈도우 NT 3.51, 그리고 윈도우 95에서부터 운영체제 차원에서 새로운 컨트롤들이 여럿 도입되었다.
이름하여 공용 컨트롤(common control). 윈도우 1.0 때부터 있었던 innate한 선배들 system control과 구분하기 위해 붙은 이름이다.

무엇이 추가되었냐 하면,
트리 컨트롤: 계층 구조, 목차 따위를 표시할 수 있다.
리스트 컨트롤: 수많은 개체를 단순히 리스트 형태뿐만이 아니라 아이콘 모양으로도 표시할 수 있고, 개체의 특성을 여러 칼럼으로 분할해서 표현할 수 있어서 유용함.
도구모음줄(toolbar)과 상태표시줄(status bar)
탭 컨트롤
진행 상황 게이지 컨트롤(progress bar)

나름 쓸모 있는 것들이 많다. 특히 윈도우 95의 탐색기는 죄다 이 새로운 컨트롤을 잔뜩 우려먹어서 만든 것이다.
리스트 컨트롤과 기존 리스트박스는 하는 역할이 일면 비슷하다 할 수 있으나, 아이템을 추가하거나 정보를 얻어 오는 프로그래밍 인터페이스는 서로 완전히 다르다. 새로운 녀석이 기능이 워낙 많다 보니 훨씬 더 복잡하다. 게다가 둘은 사용법도 차이가 있다. 가령, 아이템을 복수 선택할 때 기존 리스트박스는 Shift+F8과 Space를 사용하지만, 리스트 컨트롤은 Ctrl+화살표와 Ctrl+Space를 사용한다.

이들 공용 컨트롤들은 어차피 MS가 오피스 같은 선구자적(?) 제품에서 자체 구현해 놓고 쓰던 컨트롤들을 운영체제 차원에서 정형화해 놓은 게 많았다.
예를 들어 도구모음줄과 상태표시줄은 어느 응용 프로그램들이나 자체적으로 구비해 놓던 GUI 요소였는데 그걸 다루기 쉬운 정식 컨트롤로 만들어 놨다. MFC도 16비트 시절에는 CToolBarCtrl이 도구 아이콘을 그리고 관리하는 자체 구현이었지만, 32비트부터는 공용 컨트롤에다 요청만 하는 형태로 바뀌었다.

윈도우 3.x의 매체 재생기는 자체 구현한 slider로 재생 위치를 표시했지만, 윈도우 95의 매체 재생기(지금 있는 Media Player의 전신)는 공용 컨트롤에 있는 slider를 썼다.
윈도우 3.x 시절의 설치 프로그램들은 자체 구현한 게이지로 설치 진행 상황을 표시했지만, 윈도우 95의 설치 프로그램은 공용 컨트롤에 있는 게이지를 쓴다. 컨트롤들의 재사용성이 향상된 셈.
비주얼 C++ 4.x의 예제 프로그램 중에는 이런 공용 컨트롤들을 다루는 모습만 시연해 놓은 놈도 있을 정도였다. 아래 그림을 참고하라.

사용자 삽입 이미지

공용 컨트롤들은 시기적으로 나중에 추가된 만큼, 기존 시스템 컨트롤만치 운영체제와 뗄레야 뗄 수 없는 일심동체 형태는 아니었다. 시스템 컨트롤들의 코드가 운영체제의 3대 요소 중 하나인 user(32)에 통합되어 있다면, 공용 컨트롤은 comctl32라는 고유한 라이브러리에 따로 들어있었다. 그리고 이들 컨트롤을 쓰려면 응용 프로그램이 comctl32.dll을 로딩하고 InitCommonControls 같은 함수도 호출해 줘야 했다.

실제로, ListBox, ComboBox, Edit 같은 시스템 컨트롤들은 언제라도 GetClassInfo 함수로 컨트롤의 정체성 정보를 확인할 수 있는 반면 SysListView32, msctls_statusbar32 같은 공용 컨트롤은 comctl32.dll을 별도로 읽어들이고 나야 정보를 얻을 수 있다. 운영체제와 완전한 일심동체가 아니라는 말이 이런 의미인 것이다.

다만, 굳이 InitCommonControls 초기화를 할 필요는 없이 그 DLL을 LoadLibrary만 해 줘도 되는 듯하다.
또한, 이들 컨트롤을 운영체제의 대화상자에서 쓴다면, 어차피 DialogBox 같은 함수가 comctl32.dll의 로딩과 공용 컨트롤의 초기화 정도는 알아서 해 주는 것 같다. 따라서 현실적으로는 응용 프로그램 개발자가 일일이 InitCommonControls를 호출은 안 해도 된다.

공용 컨트롤 라이브러리는 저렇게 새로운 컨트롤만 부품 차원에서 제공하는 게 아니라, 이들 컨트롤을 이용한 자체 UI 기능도 함수 형태로 제공한다. 탭 컨트롤을 이용한 Property Sheet와, ‘이전, 다음’ 형태인 Wizard가 대표적인 예이다. 이것도 윈도우 3.x 시절부터 자체 구현이 하도 유행으로 뜨다 보니까 운영체제 차원에서 자동화 기능을 넣어 준 셈이다.

윈도우 95 이후로 공용 컨트롤 라이브러리는 윈도우 XP에서 또 큰 변화를 겪었다. 이는 물론 테마라는 기능이 추가되었기 때문이다.
컨트롤을 그리는 방식이 예전과는 근본적으로 완전히 다르게 바뀌었기 때문에, 예전 라이브러리를 고칠 수는 없어서 그건 호환성 차원에서 그대로 두고 새 라이브러리를 덧씌우는 방식이 채택되었다. 바로 윈도우 XP에서 추가된 DLL side-by-side assembly 매니페스트 방식으로 말이다.

윈도우 시스템 디렉터리에 있는 comctl32.dll은 이제 구형이다. 윈도우 비스타나 7에서도 이놈의 제품 버전은 6.x이지만 파일 버전은 5.8x에서 멈춰 있음을 알 수 있다. 그 반면, 새로운 comctl32.dll은 Windows\winsxs 같은 완전히 다른 곳에 숨어 있다.
새로운 comctl32는 원래 user32에 있던 기존 컨트롤들을 테마가 적용된 자기 걸로 다 대체해 버린다. 그래서 Spy++ 같은 프로그램으로 확인해 보면, Edit· Button 같은 시스템 컨트롤들도 클래스 스타일에 CS_GLOBALCLASS 같은 플래그가 있다.

또한, URL 링크 같은 새로운 공용 컨트롤이 XP에서 추가되었는데, 이런 것들은 응용 프로그램이 6.0 버전 이상의 새로운 공용 컨트롤 라이브러리를 사용하겠다는 표식을 명시적으로 해야만 사용 가능하다. 나중에 새롭게 추가된 기능은 호환성을 지키기 위해 옛날 프로그램들에게는 바로 노출되지 않으며, 접근 내지 사용하가 이런 식으로 더욱 까다로워진다는 걸 알 수 있다.

이거 지정을 위해 예전에는 개발자가 매니페스트 xml 문서를 직접 써 줘야 했지만 비주얼 C++ 2005부터는 #pragma comment(linker, ...) 한 줄로 손쉽게 할 수 있다. 사실, 2005부터는 MFC와 C 라이브러리 DLL 지정도 이런 방식으로 바뀌었고, 2008부터는 윈도우 비스타에서 추가된 응용 프로그램 권한 등급 지정도 이렇게 할 수가 있다는 것도 잘 알려진 사실이다.

윈도우 비스타에서부터 추가된 UI 기능인 Task dialog 역시 comctl32.dll에 기능이 구현되어 있으며, 공용 컨트롤 6.0 이상이 지정된 응용 프로그램에서만 사용 가능하다. 쉘(shell32)이나 공용 대화상자(comdlg32) 계층에 구현되어 있지 않나 생각했는데 그렇지는 않다.

닷넷 프레임워크에서는 저런 운영체제의 지저분한 버전 별 디테일을 전혀 신경 쓸 필요가 없다는 게 좋은 점 중 하나이겠다.

Posted by 사무엘

2011/02/26 08:12 2011/02/26 08:12
, , ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/471

아래아한글이 윈도우 비스타부터 키매크로를 지원 안(못) 하는 이유는?
(관련 글: 아래아한글의 키매크로 )
이것과 관련하여 갑자기 떠오른 생각이 있어서 글을 남긴다. 본인은 한컴에 입사한 개발자도 아니고 아래아한글의 소스 코드를 본 적도 없지만, 본인이 보기에 이것 때문이 거의 확실하다.

윈도우 훅 중 WH_JOURNALRECORD와 WH_JOURNALPLAYBACK 훅이 비스타에서부터는 보안 강화를 이유로 차단되었기 때문이다. MSDN을 보면 알 수 있지만 저건 완전 키매크로를 구현하라고 만든 훅이다.
(관련 글: 훅킹 프로그래밍 )
실패 사유를 나타내는 에러 코드는 5(access denied)가 들어온다.
심지어는 프로그램을 관리자 권한으로 실행해도 차단은 풀리지 않는다. 이건 좀 너무 심하지 않았나?

물론, 키매크로를 구현하는 방법이 저 훅만 있는 건 아니기 때문에 다른 키보드/마우스 훅을 사용하여 동일 기능을 우회 구현할 수도 있다. 하지만 이래저래 개발자에게는 귀찮고 짜증나는 일이 하나 더 생긴 게 틀림없다.

참고로 이렇게 차단을 하는 주체는 사용자 계정 컨트롤(UAC)이다. 그렇기 때문에 이걸 끄면 비스타도 XP와 완전히 동일하게 동작은 한다. 하지만 보안상으로는 위험하기 때문에 개발자가 사용자에게 UAC를 끌 것을 강요해서는 안 된다.
UAC는 안전을 위해 프로세스 간 의사소통을 하는 메커니즘에도 상당한 제약을 부과했다. 단적인 예로, 권한이 낮은 프로그램이 권한이 높은 프로그램에게 임의의 메시지를 보낼 수 없다.

이미 아시는 분도 있겠지만 <날개셋> 한글 입력기는 5.3부터 입력 패드라는 프로그램을 제공하고 있다. 윈도우 IME 훅킹을 통해, 정식 외부 모듈이 아니면서도 외부 모듈의 동작을 흉내 내어 주는 프로그램인데, 이 프로그램을 제대로 사용하려면 관리자 모드로 실행해 줘야 한다. 그렇지 않으면 입력 패드보다 권한이 높은 프로그램(관리자 권한으로 실행된)에다가는 글자 입력을 할 수 없게 된다.

그런데, UAC 하에서도 예외적으로 실행 중인 모든 프로세스의 윈도우에다가 메시지를 보낼 수도 있고 심지어 봉인된 WH_JOURNAL* 훅까지 구사할 수 있는 만능 권한 등급이 없는 건 아니다. MS에서는 대표적인 예 중 하나로 장애인의 UI 접근성 개선을 위해 쓰이는 프로그램에게나 그런 만능 권한을 주고 있다.
예를 들어 화면 키보드 같은 프로그램이야 권한을 초월하여 아무 프로그램에게나 문자 입력 메시지를 전달할 수 있어야 하고 심지어 운영체제 로그인 UI에도 존재해야 하기 때문이다. (ID/패스워드 입력할 때)

단지 그 권한을 얻기가 더럽게 까다로워서 문제이다. 만능 권한을 얻을 수 있는 프로그램은 사용자의 컴퓨터에 반드시 관리자 권한으로 정식으로 설치되어 EXE 파일이 Program Files 같은 특정 경로에만 존재해야 한다. 잘 알다시피 UAC 하에서는 평소에는 Program Files 디렉터리 밑에다가 파일을 만들지도 못한다.

또한, 결정적으로 EXE 내부에 디지털 서명이 되어 있어야 한다. 과거에 마치 ActiveX를 배포할 때 안전한 코드 인증을 받는 것처럼 말이다. 이거 서명을 받으려면 $이 필요하고, 무엇보다도 사업자 등록 번호가 있어야 한다. 즉, 듣보잡 개인 개발자는 서명을 받지도 못한다는 뜻.
따지고 보면 <날개셋> 입력 패드도 일종의 화면 키보드처럼 UI 접근성 개선 프로그램의 성격이 강하기 때문에 저런 권한이 있어야 할 텐데... 그러지는 못하고 있고 그저 사용자가 알아서 충분히 높은 권한을 줘서 프로그램을 실행해 주길 바랄 뿐이다.

이래저래 보안이 안전을 빌미로 세상을 많이 복잡하게 만들고 있다.
(관련 글: 프로그램의 권한 )

Posted by 사무엘

2010/09/13 18:34 2010/09/13 18:34
, , ,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/372

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

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

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

Site Stats

Total hits:
2675211
Today:
1903
Yesterday:
1540