Windows 운영체제에서 GUI의 핵심 구성 요소인 윈도우라는 물건은 자신만의 위치, 스타일, 소속 클래스, 부모 윈도우, ID(또는 메뉴 핸들) 등 여러가지 공통 정보를 갖는다. 이들은 숫자 형태로 표현되며, 대부분 GetWindowLongPtr 함수를 이용해 값을 얻을 수 있다.

그런데 그 공통 정보 중에는 숫자뿐만 아니라 문자열인 것도 있으니, 바로 '텍스트'이다. static 컨트롤이나 버튼들이 출력하고 있는 문장이나 단어들이 바로 자신이 갖고 있는 텍스트이다. 그리고 작업 표시줄에 걸려 있는 응용 프로그램들의 제목(Caption)도 다 윈도우의 내부 텍스트이다.

윈도우에 소속된 텍스트를 읽고 쓰는 함수는 잘 알다시피 Get/SetWindowText이다. 그러나 이것의 실제 구현은 그저 C++ 클래스에서

String getText() const { return m_strText; }
void setText(String t) { m_strText=t; }

요렇게 달랑 코딩하는 것만치 단순하지 않으며, 그보다 추상화 계층이 많고 사연이 훨씬 더 복잡하다.

(1) 먼저, 윈도우는 단순히 자신의 내부 버퍼를 토대로 텍스트를 지정하거나 되돌릴지, 아니면 이런 기본 동작을 무시하고 매번 자신이 능동적으로 반응을 할지를 자기 윈도우 프로시저가 재량껏 결정할 수 있다. 이것은 마치 WM_PAINT 메시지를 이미 저장돼 있는 버퍼를 바로 뿌리는 것으로 처리하는지, 아니면 매번 그래픽 API로 그려서 처리하는지의 차이와 같다.

아까도 얘기했던 static이나 버튼 컨트롤을 포함해 대부분의 윈도우들은 기본 내부 버퍼를 기반으로 동작하는 것만으로도 충분할 것이다. 그러나 에디트 컨트롤은 get의 경우 사용자가 입력한 문자열을 되돌리며 set은 자기가 편집하고 있는 텍스트를 변경한다. 이 텍스트는 당연히 기본 내부 버퍼가 아니라 에디트 컨트롤이 자체적으로 관리하는 자료 구조로부터 얻어진 데이터이다.

그럼 그런 customize를 어떻게 하는가? Get/SetWindowText는 대상 윈도우에다가 WM_GETTEXT, WM_GETTEXTLEN, WM_SETTEXT 메시지를 보내어 그 응답을 받는 방식으로 구현된다. 윈도우가 이 메시지를 처리하지 않고 그냥 DefWindowProc으로 넘기면 내부 버퍼에 대한 문자열 get/set인 기본 기능이 구현된다. 이 메시지는 자기가 에디트 컨트롤을 직접 구현하고 있을 때 정도에나 직접 처리하면 된다.

WM_CREATE에서 넘어온 CREATESTRUCT 구조체를 들여다보면, 이 윈도우를 생성한 CreateWindowEx에서 지정해 준 window name 정보가 있는데, 이것부터 자기 자료 구조에다가 복사를 해 둬야 할 것이다.
만약 내 윈도우가 대용량 텍스트를 다루고 텍스트의 일부 구간이 빈번하게 수정된다면, 저런 원시적인 메시지에만 의존하지 말고 해당 동작만을 위한 custom 메시지를 직접 구현해서 그걸 이용하는 게 효율적이다.

(2) 서로 다른 응용 프로그램끼리는 너무 당연한 말이지만 포인터를 주고받을 수 없다. 서로 메모리 주소 체계가 완전히 다르며, 상대방 프로세스의 메모리 내부를 들여다볼 수 없기 때문이다. 그렇기 때문에 이런 프로그램들끼리 가변 길이의 임의의 데이터를 주고 받으려면 WM_COPYDATA라는 특수한 메시지를 써야 하는데, 이건 의외로 굉장히 유용하고 괜찮은 물건이긴 하다.

그런데 WM_GET/SETTEXT 메시지도 비슷한 구석이 있다. 서로 다른 응용 프로그램간에도 마치 동일 프로그램인 것처럼 텍스트를 얻어 오거나 지정할 수 있다. 이 메시지에 대해서도 운영체제가 예외적인 변환 처리를 해 주기 때문이다.
단, 여기에서는 Get/SetWindowText 함수는 동작에서 약간 차이를 보인다. 같은 프로세스끼리 get/set을 하는 것은 메시지를 날리는 것과 동일하게 동작하는 반면, 다른 프로세스에 있는 윈도우에다가 get/set을 하면.. 그 윈도우의 윈도우 프로시저를 실행하는 게 아니라 그냥 그 윈도우에 있는 내장 버퍼의 문자열만 가져오는 걸로 끝난다. 왜 그렇게 된 걸까?

이것은 보안을 위해서이다. 해당 윈도우를 관할하는 응용 프로그램이 현재 메시지에 반응하지 않고 뻗어 있는데(hang) 이때 또 텍스트를 얻거나 지정하는 메시지를 보내면.. 그 요청을 한 우리 응용 프로그램까지 응답을 기다리느라 뻗어 버릴 수 있기 때문이다. 그래서 이때는 언제나 실행이 정상적으로 즉시 끝나는 게 보장되는 윈도우 내부 버퍼만을 건드리는 걸로 끝난다.

단순 내부 텍스트가 아니라 위험을 감수하고라도 해당 프로그램의 윈도우 프로시저가 실시간으로 실행한 텍스트를 얻고 싶으면, 함수만 달랑 호출하지 말고 직접 WM_GETTEXT 메시지를 보내라는 것이 설계 의도이다. 뻗을 걱정을 최소화하면서 메시지를 보내는 함수로는 SendMessageCallback이라든가 SendMessageTimeout 같은 물건들도 있으니 말이다.

사실, 현재 시스템에 떠 있는 모든 윈도우들을 조회하는 Spy++ 같은 급의 특수한 프로그램을 만드는 게 아닌 이상, 다른 프로그램들에서 제일 겉의 프레임 윈도우도 아니고 내부의 에디트 컨트롤까지 시시콜콜하게 내용을 다 들여다봐야 할 일은 거의 없을 것이다. 그러니 16비트 API에서 최초로 32비트 멀티스레드 환경으로 넘어갈 때, 텍스트를 얻는 동작은 함수 호출에 대해서만 저런 조치를 취하는 것이 적절한 조치라고 마소의 엔지니어들이 판단했다.

(3) 프로세스 장벽에 비해서는 아주 사소한 문제에 불과하지만, 사실은 문자열 인코딩 장벽도 있다.
어떤 윈도우가 있어서 요즘 추세대로 유니코드를 사용하며 WM_GETTEXT에 대해서는 L"abc" 같은 wide 문자열만 되돌린다. 그런 윈도우를 대상으로 GetWindowTextA 함수를 호출했다 하더라도 문자열 변환은 운영체제가 몰래 알아서 해 준다. 해당 프로그램은 ansi 문자열인 "abc"를 받는다.
요청하는 측에서는 GetWindowText나 SendMessage를 A와 W 중 어느 버전으로 호출했는지로 판단을 하고, 받는 윈도우는 RegisterClass(Ex)에서 A와 W 중 어느 버전을 이용해서 등록했는지로 판단한다.

그럼, 텍스트 지정 API와 관계가 있는 다른 분야 얘기를 또 둘 늘어놓고 글을 맺도록 하겠다.

부록 1. 창을 없애는 것과의 관계

윈도우의 텍스트를 얻어 올 때 함수를 쓰느냐 메시지를 보내느냐 하는 건, 마치 윈도우를 닫을 때 직통으로 DestroyWindow를 호출하느냐 아니면 WM_CLOSE 메시지를 보내느냐 하는 것과 비슷한 구석이 있어 보인다.

WM_CLOSE는 마우스로 X 버튼 누르거나 키보드로 Alt+F4를 누른 것과 같다. 이 메시지를 더 처리하지 않고 DefWindowProc으로 넘기면 얘가 DestroyWindow를 호출해 준다. 응용 프로그램의 경우 "이 문서를 저장하시겠습니까?"라고 질문 메시지를 출력하는 게 이 메시지를 받았을 때이다. 물론 '취소'를 누르고 메시지를 씹으면 실제로 닫히지는 않는다.

그 반면, WM_DESTROY는 이미 자기가 닫히고 없어지는 건 막을 수 없는 지경이 됐고, 그 전에 마무리 작업이나 하라는 통지이다. 아직 자기의 자식 윈도우들도 다 남아 있다. 이 메시지는 운영체제로부터 자연스럽게 받게 해야지 사용자가 인위로 생성하지는 말아야 한다.
이게 끝나고 자식 윈도우까지 다 사라진 뒤에 정말 마지막으로 오는 메시지가 바로 WM_NCDESTROY이다. 이때 하는 일은 C++ 클래스와 연결된 윈도우 프로시저에서 delete this를 하는 것 정도가 전부이다.

Get/SetWindowText가 여타 프로세스의 윈도우에 대해서는 다소 방어적으로 동작을 하듯, DestroyWindow에도 약간의 방어적인 제약이 있다. MSDN의 설명에 따르면, 얘는 다른 프로세스 정도가 아니라 아예 다른 스레드에 의해 생성된 윈도우를 파괴하지는 못한다. 그런 창을 닫으려면 그냥 WM_CLOSE라는 간접적인 방법만을 써야 한다.

앞서 말했듯이 WM_CLOSE는 응용 프로그램이 회피· 거부가 가능하기 때문에 응용 프로그램의 입장에서는 다른 스레드/프로세스의 특정 윈도우를 무조건적으로 없애지는 못한다. 굳이 그렇게 해야 할 필요도 없을 테고.
멀티스레드 환경에서 Windows의 창 관리자가 어떤 식으로 API를 설계했는지를 살펴보면 편의와 안정성을 상호 절충하기 위해 여러가지 생각을 했다는 점을 발견할 수 있다.

부록 2. 아이콘과 글꼴은 어떻게?

앞서 살펴본 것처럼 한 윈도우의 속성 중에 텍스트(TEXT)는 운영체제가 DefWindowProc을 통해 내부 텍스트를 관리하기도 하고 한편으로 값을 읽고 쓰는 동작을 customize하는 방법도 제공한다.
그럼, 윈도우와 관련된 부가 정보 중에는 아이콘과 글꼴은 어떻게 관리되는 걸까? 얘들은 함수가 아니라 메시지로만 값을 지정하고 얻어 온다는 공통점도 있다. WM_(GET/SET)(ICON/FONT)라고 말이다.

아이콘은 기본적으로 윈도우가 클래스에 소속돼 있으며, 그 클래스를 기반으로 만들어진 윈도우들이 한 아이콘을 공유하는 형태이다. 그러나 클래스 아이콘과는 별개로 필요하다면 각각의 윈도우도 자기 아이콘을 예외적으로 변경할 수 있다. 이는 특정 윈도우 클래스가 아니라 운영체제 차원에서 기본으로 제공되는 기능이다.
코드로 표현하자면 대충 이런 구조. commonIcon뿐만 아니라 myIcon도 있다는 뜻이다.

class Window {
    static Icon commonIcon;
    Icon myIcon;
public:
    Window() { myIcon=commonIcon; }
    Icon getIcon() { return myIcon; }
    void setIcon(Icon i) { myIcon=i; }
};

그렇기 때문에 대화상자는 윈도우 클래스는 동일하지만 응용 프로그램이 WM_SETICON을 보냄으로써 자신만의 아이콘으로 customize를 할 수가 있다. 그리고 이 기능은 대화상자에만 있는 게 아니라 임의의 top-level 윈도우가 다 갖추고 있다.

이런 아이콘에 비해 글꼴은 운영체제의 윈도우 내부 자료구조에 정보가 자동으로 저장되지 않는다. 내가 custom 컨트롤을 만들고 있고 거기에 대화상자의 기본 글꼴대로 문자를 찍는 기능이 있다면 내부적으로 HFONT 핸들을 멤버로 추가하고 WM_GET/SETFONT 메시지를 직접 구현해 줘야 한다. 즉, 운영체제에는 인터페이스만 정의되어 있을 뿐이다.
요약하자면 text는 내부 자료구조와 custom 동작이 모두 존재하고, icon은 내부 기본 동작만으로 충분하고, font는 기본 동작이 없기 때문에 필요한 경우 자체 구현을 해야 한다는 뜻이다.

Posted by 사무엘

2015/07/23 08:39 2015/07/23 08:39
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1119

사용자의 마우스 클릭에 동작하는 GUI 요소들은 일단은 버튼의 down이 아니라 up 시점 때 반응을 한다. 모든 버튼(push, check, radio 공히)이나 하이퍼링크들이 이렇게 동작하며, 우클릭 메뉴도 오른쪽 버튼을 뗐을 때 튀어나온다. 그리고 이 타이밍 때 그 이름도 유명한 BN_CLICKED라는 notify 메시지가 부모 윈도우에 전달된다.

그에 반해, down 때 바로 반응을 하는 것은 스크롤 바나 슬라이더나 스핀(옆의 숫자를 증가시키거나 감소시키는 up-down 컨트롤)처럼 누르고 있는 동안 '연타'의 여지가 있는 버튼, 아니면 어떤 명령이 즉시 실행되는 게 절대로 아님이 보장되는 첫 단계 메뉴(파일, 편집, 보기) 정도로 국한된다.

그런데 이건 어떨까? 버튼인데, 사용자가 누르고 있는 동안만 풍선 도움말이나 다른 추가적인 정보가 튀어나오고, 버튼을 떼면 그것들이 사라진다. down과 up만 감지하면 되고 그 사이에 연타 개념은 없다.
이건 일단 Windows의 표준 컨트롤에는 없는 기능인 듯하다. 하지만 의외로 요런 UI가 쓰인 경우가 존재한다.

가장 먼저 떠오르는 예는 2007 버전이 나오기 전, 과거의 MS Excel이다. 선에 안티앨리어싱이 없는 건 그렇다 치더라도 색깔이 요즘 것에 비해서 우중충하고 덜 예뻐 보인다.

사용자 삽입 이미지

옛날에 엑셀에는 차트 마법사를 통해서 차트를 만들곤 했다. 1단계는 차트의 종류를 고르는 단계였는데, 아래의 길쭉한 버튼을 살포시 누르고 있으면 지금 사용자의 데이터가 어떤 형태의 차트로 표시되는지 preview를 잠시 볼 수 있었다.
버튼을 한번 눌렀다 뗄 때마다 preview 모드와 선택 모드가 toggle 형태로 바뀌는 게 아니라는 점을 주목하자. 상당히 독특한 UI가 아닐 수 없다. 왜 이렇게 만들었을까?

어지간하면 그냥 대화상자의 왼쪽에 차트의 type과 sub-type을 트리 컨트롤 같은 걸로 모두 때려박고, 오른쪽 전체에 preview 화면을 할당해도 될 듯하지만 그렇게 하기에는 오버헤드가 너무 크고, 또 차트의 타입 자체를 그림으로 표시하기 위한 공간도 많이 필요하니 이런 식으로 preview는 필요할 때 잠깐만 볼 수 있게 UI를 만든 듯하다.

참고로 Office 2007과 그 이후부터는 전통적인 차트 마법사가 없어지긴 했지만, 차트의 종류를 선택하는 대화상자 자체는 남아 있다. 단, 이렇게 버튼을 누르고 있는 동안 preview를 잠깐 보는 기능은 없어졌다.

원래 운영체제의 표준 버튼은 아까도 얘기했듯이 눌렀다 뗀 뒤의 BN_CLICKED 이벤트만 있지, 저렇게 눌러진 것에 대한 이벤트는 제공하지 않는다. 그러니 저런 기능을 구현하려면 일반적으로는 윈도우 프로시저를 서브클래싱하여 마우스 좌클릭과 Space/Enter 누름을 감지해서 좀 불편하게 구현해야 한다.
하지만 MS Office 제품 중에 Word와 Excel은 대화상자 컨트롤들을 운영체제 함수 대신 자체 GUI 엔진으로 구현했기 때문에 편법 없이 저런 기능들이 처음부터 자연스럽게 구현이 가능했을 것이다.

그리고 또 다른 예는 아주 최신 프로그램이다. 바로 Internet Explorer 11.

사용자 삽입 이미지

IE는 최신 버전이자 아마 마지막 버전이 될 것으로 보이는 11이 굉장한 쇄신을 한 것 같다. 예전보다야 가벼워지고 속도가 빨라지고, 텍스트 입력란이 TSF A급으로 바뀌고, 굴림체로 찍히던 기본 글꼴이 맑은 고딕으로 바뀌는 등 변화가 많다.

얘는 웹사이트 내부의 입력란에서 ID와 비번을 입력하기 시작했을 때 오른쪽에 자그마한 버튼이 뜬다. ID 입력란의 오른쪽 끝에는 X 버튼이 생겨서 이걸 누르면 ID가 싹 다 지워진다. 물론 지우는 동작은 눌렀다가 '뗐을 때' 행해진다.
그런데, 비번 입력란의 오른쪽에는 눈알 모양의 버튼이 생기며, 이 버튼을 누르고 있으면 동그라미로 표시되는 암호 문자열의 실제 문자열이 잠시 보인다. 이것 역시 마우스 버튼을 떼는 순간 원상복귀된다!

(1) 엑셀의 차트 미리보기와, (2) IE에서 비번 훔쳐보기가 이 동작에 상당히 적절하게 배치된 경우라고 생각된다. peek라는 동작을 표현한다고나 할까. 이것 말고 요런 동작이 유용하게 쓰일 만한 상황이나 이미 적용된 예가 무엇이 있는지 궁금하다.

마우스 포인터를 갖다대고 있는 동안 뭔가 도움말이나 추가 정보가 나타나는 UI는 그리 새삼스러운 물건이 아니다. 이미 20년 전부터 툴팁이라고 불리는 풍선 도움말이 그 역할을 하고 있으니 말이다.
단, 터치스크린에서는 click과 hover가 구분이 없기 때문에 저렇게 가리키고 있는 것을 표현할 수 없으니 누르고 있는 동안만 뭔가 추가 정보를 표시하는 기능이 더욱 필요할 것이다.

이렇게 글을 바로 맺기는 아까우니 MS Office에서만 볼 수 있는 독특한 UI를 두 가지 좀 늘어놓도록 하겠다.

1. 먼저, 탭 컨트롤의 각각에 Alt 액셀러레이터가 붙은 모양이다. 난 워드나 엑셀을 쓸 때마다 늘 신기하다고 생각해 왔다.

사용자 삽입 이미지

Alt 액셀러레이터는 IsDialogMessage라는 대화상자 전용 메시지 처리 함수가 구현해 주는 것이고, Alt는 윈도우 텍스트를 기반으로 한 윈도우당 한 글자씩만 배당된다. 그러므로 저게 정석적으로 가능하려면 각각의 탭 헤더가 마치 라디오 버튼처럼 독립된 윈도우를 구성하고 있어야 하나, 운영체제의 탭 컨트롤은 그런 구조를 하고 있지 않다.

Alt+단축키를 눌렀을 때 탭 컨트롤의 탭이 어떻게든 전환되게 하는 것 자체는 불가능하지 않다. 하지만 그러려면 윈도우 프로시저 수준이 아니라 해당 응용 프로그램의 메시지 loop 차원에다가 예외적인 처리가 필요하다. MFC로 치면 해당 대화상자의 PreTranslateMessage 함수를 굳이 새로 구현해야 한다.
그래서 본인은 MS Office의 저런 프로퍼티 시트 대화상자를 보면 신기하다는 생각이 든다.

2. 그리고, 라디오 버튼을 더블 클릭하면 대화상자가 OK(확인)으로 종료되는 것도 꽤 독특하다.
요즘 Office 프로그램들이야 워낙 기능이 방대하기 때문에 공간을 아끼기 위해 콤보 박스를 주로 쓰지, 라디오 버튼을 보기는 힘들지만.. 대표적으로 '화면 확대'라든가 탭의 종류를 고르는 화면에서 이를 확인할 수 있다.
아.. 그러고 보니 리스트 박스에서 아이템을 더블 클릭했을 때 대화상자가 재량껏 OK로 종료되는 건 생소하지 않다. 하지만 라디오 버튼을...? 한번 생각해 보시기 바란다.

Posted by 사무엘

2015/07/13 08:45 2015/07/13 08:45
,
Response
No Trackback , 2 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1115

Visual C++ 디버거 관련 생각

코딩으로 먹고 사는 프로그래머 내지 소프트웨어 개발자에게 필요한 것은 단순히 새로운 코드를 스스로 잘 작성하는 능력뿐만이 아니라, 문제가 생겼을 때 디버깅을 잘 하고 남이 만들어 놓은 코드를 신속하게 읽고 분석하는 능력이다. 아니, 업계에서는 어찌 보면 후자가 전자 이상으로 더 중요한지도 모른다. 왜냐하면 오늘날은 뭔가 완전히 새로운 솔루션을 천재 프로그래머 한 명에서 밑바닥부터 새로 만들어 낼 일은 거의 없어졌기 때문이다.

나의 영원한 친구는 비주얼 C++이고, 비주얼 C++ IDE는 예로부터 굉장히 편리한 디버깅 기능을 제공해 왔다. (일례로 Shift+F5는 엉덩국 홍콩행 C언어 병맛 만화에도 나올 정도로 유명한 비주얼 C++ 단축키이다. 디버그 중단 =_=)
특히 IDE가 32비트임에도 불구하고 64비트 프로세스를 아주 seamless하게 디버깅 해 내는 건 아무리 생각해도 대단해 보인다. 물론 이를 구현하기 위해 내부적으로는 64비트 디버그 서버 프로세스를 따로 만들고, 걔가 IDE와 디버기 프로그램 사이를 중재하고 있긴 하다. 그렇게 하는 것 말고는 기술적으로 다른 방법이 없다.

다만, 여러 편리한 기능에도 불구하고 본인이 일말의 아쉬움을 느끼는 점들을 나열하자면 다음과 같다.

1.
소스 코드에서 breakpoint를 여러 곳에 지정해 놓고서
한 breakpoint(A)가 적중한 뒤부터 다른 쪽 breakpoint(B)를 지났을 때 프로그램이 멈추게 하는 게 지원됐으면 좋겠다.

디버깅을 하고자 하는 지점이 평소에도 자주 지나는 곳이긴 하지만, 특정 조건이 만족된 뒤부터 실제로 의미를 갖는다는 뜻이다.
이런 상황에 대비해서 n회 이상 적중했을 때 멈춤, 특정 변수값이 변했을 때 멈춤 같은 여러 breakpoint 옵션이 있긴 하지만..
다른 breakpoint의 hit에 의존하여 그 뒤부터 멈추게 하는 기능은 Visual C++에서 지금까지 못 본 것 같다. 이거 회사일을 할 때와 <날개셋> 개발 중에 자주 필요성을 느꼈다.

IDE 내지 디버거가 이런 기능을 지원 안 해 주면 결국 사람이 해당 기능을 직접 코드에다 써 넣어야 한다.
bool 타입의 전역변수(bkpoint)를 하나 만든 뒤 A에 해당하는 지점에서는 bkpoint=true를 지정하고,
B에 해당하는 지점에서는 extern bkpoint; if(bkpoint) DebugBreak() 를 호출하는 식이다.
이런 긴급/땜빵 코드를 집어넣을 때는 굳이 클래스 따위 생각할 필요 없이 global scope이 존재하는 C/C++이 편리하게 느껴진다.

하지만 조건을 지정하는 코드와 멈추는 코드가 서로 다른 모듈에 있는 경우(static LIB, DLL, EXE 등) 여러 모듈을 고쳐서 재빌드해야 하고 일이 골치아파진다. 그러니 코드를 건드릴 필요 없이 이런 기능 정도는 개발툴이 바로 지원해 주는 게 속 편하다.

사실, 이런 쪽의 기능이 계속 추가되다 보면 디버거도 전처리기나 빌드 시스템처럼 일종의 프로그래밍 가능한 독자적인 시스템이 될지도 모르겠다. 사실은 <날개셋> 한글 입력기의 개발에서는 todo list를 분류하고 체계화하는 것부터가 전략이고 프로그래밍이다.

2.
디버그 로그를 찍는 API 함수는 OutputDebugString이며, 얘는 문자열을 받아들이는 여느 함수들과 마찬가지로 W 버전과 A 버전이 있다. 그러나 얘는 실제로는 오늘날의 NT 계열 운영체제에서도 유니코드를 지원하지 않는다.
다른 함수들은 A 버전이 문자열을 변환한 후 W 버전을 호출하는 형태이지만, 이 함수는 뜻밖에도 W 버전이 문자열을 변환한 후 내부적으로 A 버전을 호출한다.

물론 99%에 가까운 상황에서 프로그래머가 필요로 하는 로그 문자열은 단순히 알파벳과 숫자만으로 이뤄져 있어도 하등 지장이 없으며 충분하다. 그러나 본인처럼 문자 입력기 내지 마이너한 유니코드 문자/글꼴 쪽을 종종 연구하는 입장에서는.. 그런 문자열을 디버거로 곧장 확인할 수가 없어서 불편을 겪은 적이 생각보다 자주 있었다.

디버거 쪽이 여전히 1바이트 문자열 기반 프로토콜이 관행이어서 유니코드를 도입할 수 없다는 말도 변명에 지나지 않는다. 그런 용도로 쓰라고 엄연히 utf8이라는 물건이 있기 때문이다. 소프트웨어 국제화의 혜택이 사용자 인터페이스뿐만이 아니라 이런 데에까지 도달해야 하지 않을지?
직접 확인해 보지는 않았지만 C++말고 C#이나 자바는 디버그 로그가 유니코드를 지원 안 할 리가 없으리라고 생각한다.

3.
최신 201x 버전에서도 가끔은 프로젝트를 빌드하는 데 쓰였던 멀쩡한 소스 파일이 디버거에서 인식이 안 되는 경우가 가끔 있다. F9를 눌러도 해당 라인엔 빈 동그라미○만 생기지 breakpoint가 성공적으로 만들어졌음을 의미하는 ●가 생기지 않는다.
DebugBreak()를 손수 집어넣어서 강제로 세우더라도 그 지점에서 call stack 리스트가 제대로 생성돼 있지 않다. 또한 breakpoint는 만들어지지만 심벌 테이블이 좀 맛이 갔는지 변수값 조회가 동작하지 않을 때도 있다.

본인은 이 현상에 대해 정확한 문제 재연 조건과 원인, 해결 내지 예방 방법을 아직도 정확히 모른다. 프로젝트 전체를 재빌드하고 Visual C++ IDE를 재시작하고 나면 해결되기도 하고 안 그럴 때도 있었던 것 같다. VC++ 6의 고질병이던 허접 인텔리센스 ncb가 깨지는 문제는 오늘날 더 볼 일이 없지만, 디버깅은 여전히 완벽하지 못하다.

그러고 보니 디버그 심벌 데이터베이스는 IDE의 인텔리센스 데이터베이스와는 커버하는 영역이 정확하게 같을 수가 없겠다는 생각이 들었다. 전자는 우리 프로젝트 밖에서 빌드되어 LIB, DLL들에 존재하는 소스 코드와 그쪽 심벌까지 모두 연계해서 동작해야 하기 때문이다. (인텔리센스 정보가 없는 곳)

4.
이 외에도,
함수 안으로 들어가긴 하는데(F11), 그 함수의 인자와 관련된 함수 호출들은 모두 무정차로 건너뛰고서 들어가는 step in이 있었으면 좋겠다. 즉, A(b(), c()) 줄에서 시작한다면 b()나 c()로 들어가는 게 아니라 바로 A()의 몸체로 들어간다는 뜻이다.

그리고 디버깅과 직접적인 관계는 없지만, 텍스트를 검색하는데 주석 내용은 빼고 검색하거나 주석에서만 검색하는 기능도 있으면 좋겠다. #if 0과는 달리 주석 영역을 파악하는 건 단순 텍스트 패턴 매칭이므로 그리 어렵지 않을 것이다.

Posted by 사무엘

2015/07/01 19:31 2015/07/01 19:31
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1111

컴퓨터로 뭔가 input을 받아들여서 output을 내는 나만의 프로그램을 개발한다면, 그 결과물이 단순히 화면으로만 잠깐 나타났다가 사라지는 걸 원하지는 않을 것이다. 꼭 프린터로 출력까지는 아니더라도 파일로 저장하여 사용자의 컴퓨터에 (반)영구적으로 남는 정도는 가능해야 할 것이다.

일반적인 텍스트/그림 파일뿐만이 아니라 내 프로그램만이 인식할 수 있는 고유한 파일 포맷을 제정하고, 그 포맷이 널리 쓰이게 되는 것은 분명 해당 파일 포맷을 만든 사람에게는 기분 좋은 일일 것이다. 새로운 이미지 파일 포맷이라든가 압축 파일 포맷처럼 말이다. 본인의 경우는 <날개셋> 한글 입력기의 글쇠배열/입력 설정 파일이 이런 창조물의 범주에 속하게 됐다.

파일 포맷이라는 건 지금 당장 공간 낭비 없이 읽고 쓰기 빠르게 만드는 효율도 중요하지만, 범용성과 확장성도 대단히 중요하다. 지금 만들고 있는 프로그램이 구조와 기능이 앞으로 어떻게 바뀔지 알 수 없기 때문이다. 마치 프로그래밍 언어가 하드웨어 친화와 사용자 친화라는 양 이념 사이의 tradeoff로 떨어지듯, 파일 포맷도 위의 두 이념 사이의 tradeoff를 고려하여 제정된다.

또한 파일 포맷은 거의 필수적으로 앞부분에 헤더가 들어간다. 이 파일이 요런 파일 포맷으로 된 파일이라는 것을 나타내며, 헤더가 일치하지 않으면 파일을 더 읽지 말고 에러를 출력하라는 일종의 배려이다. 헤더의 앞에는 식별자가 있는데, 요것이 또 파일 포맷마다 아주 개성이 넘쳤다. 도스 실행 파일(EXE)은 MZ, ZIP 압축 파일은 PK 등.

도스에서 파일의 내용을 보여주는 type 명령은 end-of-file을 나타내는 아스키 문자인 0x1A를 만나면 뒷부분에 텍스트가 더 있어도 표시를 멈췄기 때문에, 파일 시그니처의 끝에다가도 저 문자를 넣어 주는 게 일종의 센스쟁이 관행이었다. 딱 HWP Document File v3.0 요까지만 출력하고 멈추게 할 수 있으니까 말이다. 0x1A는 10진수로 26인데, 이것이 바로 지금도 copy con 다음에 종결을 위해 입력하는 Ctrl+Z와 대응한다. Z는 알파벳 26째 마지막 문자이니까 말이다.

PNG 그래픽 파일은 이 시그니처를 상당히 머리를 써서 만든 것으로 잘 알려져 있다. 마냥 텍스트 파일로 오인하지 않게 의도적으로 맨 앞은 0x89라고 128보다 큰 문자를 집어넣고, 그 다음 PNG를 찍고 줄 바꿈 문자를 찍은 뒤 0x1A로 종결시킨다.

옛날에 아래아한글이 도스용으로 1~2.x 버전이던 시절엔 이런 미래 확장 가능성을 꼼꼼히 설계를 안 했는지 파일 포맷이 수시로 바뀌어서 하위 호환성이 깨지곤 했다. 뭐, 2.1 때는 최초로 압축 저장 기능이 생겼고 도중에 암호 체계가 뚫리는 해프닝이 있어서 불가피하게 포맷이 바뀌어야 하기도 했지만 말이다.
그나마 3.0 포맷이 도스와 Windows 공용으로 무려 97 버전까지 변경 없이 잘 쓰이다가 그래도 지금은 무려 워디안 이래로 포맷이 바뀌지 않고 꿋꿋이 잘 나가고 있다. 안정화가 됐다.

그런 최소한의 융통성을 갖춘 파일 포맷을 만들려면, 결국 어떤 용도의 포맷을 만들든지간에 버전 정보를 남기고 섹션, 구획(혹은 chunk)을 설정하는 정도의 추상화는 공통으로 필요하다. 내가 아는 chunk의 정보만 읽어들이고 모르는 건 무시할 수 있게, 하위 호환이 되게 말이다. PE라고 불리는 Windows용 실행 파일에서도 이런 구획이 있고(text, rdata, data, rsrc 등), TTF 폰트 파일에도 내부에 구획이 있다(cmap, glyf, head 등). 미디(mid) 음악 파일도 온갖 구획들이 합쳐진 컨테이너 포맷이다.

그렇게 외부에서 구획을 표현하는 방식은 파일 알멩이 포맷 이전에 껍데기 '컨테이너' 포맷이라는 공통 규격으로 바뀌는 게 요즘 추세이다. 매 프로그램마다 GUI 프로그래밍을 제각각 할 필요가 없듯, 껍데기를 일일이 새로 만들 필요는 없으니 말이다. 무손실 압축 파일 포맷도 컨테이너와 압축 알고리즘을 분리해서 생각하는 건 상식 중의 상식이고, 손실 압축 알고리즘의 각축장인 동영상/소리 파일 포맷도 컨테이너와 내부 컨텐츠 포맷은 계층이 분리돼 있다.

컨테이너는 아예 human-readable한 텍스트 방식과, 그것보다는 성능을 더 중요시한 바이너리 방식 둘로 나뉜다.
텍스트는 xml이 대세를 평정하는가 싶었는데 요즘은 json도 급부상하고 있다. json은 프로그래밍 언어에서 배열이나 튜플 같은 복합 자료형을 표기하는 방식을 그대로 가져왔다는 점이 무척 참신하다. 배열스러운 나열과 key-value 형태의 데이터를 모두 표기할 수 있으며, 그 덕분에 바이너리 덤프 같은 것도 xml보다는 덜 부담스럽게 집어넣을 수 있고 공간 효율도 더 좋다.

바이너리 차원에서의 컨테이너 포맷으로 요즘 굉장히 많이 쓰이는 건 zip 압축 포맷이다. 수많은 압축 알고리즘들이 존재하지만 역시 오픈소스 앞에서는 답이 없다. zip이 세상을 평정했다. 가장 친숙하게는 MS Office 2007 이후의 문서 파일 포맷, 그리고 오픈오피스 문서 파일 포맷이 내부적으로는 zip 압축 파일이다. Java의 jar 라이브러리, 그리고 안드로이드 adb 패키지도 zip이다.

다만, 저런 프로그램들은 zip 안에다가 자기 방식으로 고유한 메타데이터도 집어넣곤 한다. 그렇기 때문에 이들 파일의 압축을 풀었다가 다시 압축을 했다고 해서 그것들이 해당 오피스 문서나 패키지로 인식되지는 않는 경우가 많다.

멀티미디어 파일 포맷 중에는 avi/wav가 동일하게 RIFF(리소스 교환 파일 포맷)라는 컨테이너 기반이다.
한편 Windows 세계에서는 의외로 많이 쓰이는 공용 바이너리 컨테이너 포맷이 있는데.. 그것은 바로 OLE Compound Binary이다. 이름에서 알 수 있듯이 바이너리 규격에서 여러 프로그래밍 규격들의 통일을 시도했던 OLE/COM 기술과 역사를 같이하는 포맷인 것 같다. 난 잘 모르겠지만 아마 이 파일을 읽고 쓰는 I*** 하는 인터페이스 API도 있으리라 여겨진다.

이 방식의 파일은 D0 CF 11 E0 A1 B1 1A E1이라는 8바이트짜리 시그니처로 시작한다. 의도적으로 128 이하의 텍스트나 제어 문자는 제외한 듯하다. 그리고 앞부분엔 0xFF 문자가 수십~수백 개 나온다.
MS Office가 2007 버전이 등장하기 전에 재래식 doc/xls/ppt가 이 컨테이너 하에서 자기 데이터를 저장하곤 했다. 그리고 지금도 일반적으로는 xml+zip 기반의 docx/xlsx/pptx이지만 암호를 걸어서 저장하면 여전히 예전처럼 이 compound binary를 사용한다. 이건 그리 널리 알려져 있지 않을 것이다.

엑셀의 경우 대용량의 데이터를 빠르게 저장하기 위해 예외적으로 xml 대신 바이너리 포맷을 쓰는 xlsb도 지원하긴 하는데, 이때에도 컨테이너는 여전히 zip이다.
하지만 암호를 걸면 xls든 xlsb든 동일하게 컨테이너가 저 OLECB 방식으로 회귀한다.

OLECB는 Office 문서에서만 쓰이는 게 아닌 범용적인 컨테이너 포맷이기 때문에 Windows의 내부에서는 thumbs.db에서도 쓰이고 심지어 msi 패키지도 이 방식으로 만들어져 있다.
국내에서는 아래아한글이 워디안 이후 새로운 hwp 포맷이 이 컨테이너를 사용하는 중이다. 몇 년 전에 hwp 파일의 포맷이 부분적으로나마 공개되면서 요 방식도 같이 주목받은 편이었다. 워디안의 개발 당시에 OLECB를 사용하기로 한 것은 21세기에 아래아한글의 향후 행로를 결정한 매우 중대한 결정이었을 것이다.

파일 포맷이란 건 한번 정해지고 그게 대중화돼 버린 뒤에는 마치 전기 전압이나 교통수단의 통행 방향처럼 다른 방식으로 덥석 고치기가 거의 불가능하다. 프로그램의 구조가 아주 간단하고 기능 구현만 빨랑 해야 할 때는 숫자/문자열 몇 개를 덥석 텍스트 형태로 덤프하거나, 구조체가 차지하는 메모리 형태를 파일로 통째로 써 버렸을지 모른다. 하지만 그 파일을 남과 주고받게 되고 프로그램을 지속적으로 발전시켜야 한다면 본격적으로 파일 포맷을 고민해야 하는 날이 온다.

이걸 처음에 신중하게 생각을 안 하면 파일 포맷은 legacy들이 가득한 누더기가 돼 가고, 참다못해 파일 포맷을 다 갈아엎게 되고 그러면서 사용자들로부터 욕도 먹을 것이다. 컴터쟁이 프로그래머로서 파일 포맷은 참 재미있는 주제인 것 같다. 그 어떤 파일 포맷이라도 결국은 튜링 기계가 인식할 수 있는 형식 언어와 문법에 속하는 방식으로 귀착된다는 점 역시 생각할 점이고 말이다.

Posted by 사무엘

2015/06/26 08:35 2015/06/26 08:35
, ,
Response
No Trackback , 3 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1109

맥 OS는 화면 상단에 붙박이로 메뉴가 있어서 모든 프로그램들이 동일한 메뉴 표시줄을 공유한다. 이게 Windows로 치면 응용 프로그램의 메뉴 겸 작업 표시줄의 시작 메뉴, 그리고 심지어 시스템 트레이의 역할까지 한다.
그러나 Windows는 각각의 창이 자신의 고유한 메뉴 표시줄을 갖는 구조이다. 이건 맥과 Windows가 무려 1.0 시절부터 서로 다르게 설계한 디자인이다.

그래서 Windows에는 응용 프로그램의 기본 메뉴뿐만이 아니라 어떤 창이라면 공통으로 갖는 창 자체에 대한 메뉴도 있는데, 이것을 '시스템 메뉴'라고 한다. 창 자체를 이동하거나 크기를 조절하고, 최소화/최대화를 시키거나 닫는 고정적인 명령들이 있다. 마우스로는 좌측 상단에 있는 해당 프로그램의 아이콘을 클릭하면 되고, 키보드로는 Alt+Space를 누르면 된다.

옛날에 3.x 시절에는 '작업 목록(지금으로 치면 작업 관리자와 비슷)'을 꺼내는 명령도 시스템 메뉴에 있었는데 그건 Windows 95부터 없어졌다. 그땐 시작 메뉴나 작업 표시줄 같은 게 없었기 때문에 Alt+Tab 외에 응용 프로그램간 전환을 하는 방법을 그런 식으로 마련해 놓을 필요가 있었던 것이다.

그런데 창의 시스템 메뉴는 기본 메뉴의 연장선의 성격을 지님과 동시에 한편으로 우클릭 팝업 메뉴의 형태로도 부를 수 있다. Alt나 F10을 눌러서 기본 메뉴를 열어서 좌우 화살표 키를 누르면 기본 메뉴와 더불어 시스템 메뉴로도 순환이 된다.
그러나 창의 제목 표시줄을 우클릭하면 창의 시스템 메뉴만 따로 우클릭 메뉴의 형태로 꺼낼 수 있으며, 이때는 좌우 화살표를 눌러도 기본 메뉴가 표시되지는 않는다. 사실, 시스템 메뉴를 우클릭 메뉴 형태로 꺼내는 건 Windows 95에서부터 추가된 새로운 UI이긴 하다.

그리고 Windows 95에서는 메뉴의 아이템들 중 하나를 default로 지정하여 진하게 강조되어 출력하는 UI가 추가되었다. 당장 시스템 메뉴에서 확인할 수 있는데, 이것은 디자인 컨셉상 우클릭 메뉴에서 쓰라고 도입되었다.
화면에서 어떤 개체를 더블 클릭했을 때 취해지는 default 동작이 해당 개체의 우클릭 메뉴에 포함되어 있다면, 그 동작에 해당하는 메뉴 아이템을 진하게 출력하면 된다.

그래서 파일 열기 대화상자에서 파일이나 디렉터리를 우클릭하면, 탐색기에서 파일/디렉터리를 우클릭했을 때는 없던 '선택/Select'라는 명령이 추가되고 이것이 진하게 표시된 것을 확인할 수 있다.
또한 똑같이 창의 시스템 명령을 꺼냈더라도 아이콘을 클릭했을 때는 '닫기'가 진하게 나오고, 제목 표시줄을 우클릭했다면 '최대화' 명령이 진하게 나오는 것을 알 수 있다. 창의 해당 부위를 더블 클릭하면 실제로 그 동작이 행해지기 때문이다. 우클릭 메뉴의 default 아이템에는 이런 의미가 있는 것이다.

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

그런데 운영체제의 이런 동작에는 버그가 있다.
최대화된 프로그램의 제목 표시줄을 우클릭하면 '이전 크기로'가 진하게 나와야 하고, 최대화되지 않은 프로그램의 제목 표시줄을 우클릭하면 '최대화'가 진하게 나와야 한다.
그러나 탐색기, IE, MS 오피스 프로그램, Media Player 등, 마소에서 개발한 프로그램들은 그렇게 동작하지 않으며, 언제나 '닫기'만이 진하게 나온다. 시스템 메뉴를 직접 꺼냈을 때처럼 말이다.
그 반면, 메모장이나 <날개셋> 편집기 같은 프로그램은 바르게 동작한다. 어찌 된 일일까?

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

여러 프로그램들의 동작을 관찰해 보면, 차이는 단 하나뿐이라는 걸 알 수 있다.
바르게 동작하는 프로그램은 운영체제의 기본 메뉴를 사용하는 프로그램이다. 그 반면, 자체적으로 구현한 메뉴를 사용하느라 기본 메뉴를 사용하지 않는 프로그램은 제목 표시줄을 우클릭했을 때 default 아이템이 제대로 처리되지 않고 언제나 '닫기'만 진하게 나온다. 신기하지 않은가?

Windows라는 운영체제는 창을 생성할 때 또는 아예 창의 클래스를 등록할 때 메뉴 핸들을 같이 전달할 수도 있을 정도로 메뉴 처리를 각별히 신경 써서 만들어졌음에도 불구하고, 이젠 마소에서 자체 기본 메뉴를 구시대 물건으로 치부하고 매우 천대하고 있다. 이것은 어제오늘 트렌드가 아니다. 마소에서 직접 만드는 네임드급 프로그램들 중에 기본 메뉴를 사용하는 프로그램이라고는.. 거의 없다. 자체 메뉴를 쓰는 것도 모자라서 그걸 다 리본 UI로 대체하거나, 아니면 탐색기나 IE의 경우 Alt를 눌렀을 때에만 메뉴가 잠깐 나타난다. 그러니 제목 표시줄을 우클릭했을 때 '최대화'가 진하게 나오는 광경을 보기가 더욱 힘들어지고 있는 것이다.

게다가 본인이 확인한 바로는 이건 거의 Windows 98때부터 동일하게 이러고 있다. 크기 조절과 최소화· 최대화가 가능한 프로그램 윈도우치고 어떤 형태로든 메뉴가 없는 창은 보기가 힘들다. 그러니 이런 버그가 상대적으로 눈에 잘 안 띄었던 것인지도 모르겠다. Windows 95때는 컴의 성능도 열악하고, 그 시절에는 마소에서 만든 프로그램들도 거의 다 기본 보급 메뉴를 썼기 때문에 확인이 쉽지 않다.
아무튼 이거 굉장히 흥미로운 현상이다. 구닥다리 레거시 코드에 존재하고 딱히 심각한 문제도 아니니, 앞으로도 안 고쳐질지도 모르겠다.

Posted by 사무엘

2015/06/15 19:24 2015/06/15 19:24
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1105

요즘은 거의 찾을 수 없는 관행인데, 옛날에 메뉴가 달린 Windows용 프로그램 중에는 파일, 편집 같은 다른 메뉴는 왼쪽에 있는 반면 '도움말' 같은 마지막 메뉴 하나만은 맨 오른쪽에 따로 떨어져서 배치된 것들이 종종 있었다. 그게 유행이었다. 특히 16비트 Windows 3.x 시절에 말이다.

사용자 삽입 이미지

음성학 연구용으로 많이 사용되는 사운드 편집기 프로그램인 프라트(Praat)는 최신 버전까지도 그런 형태라는 게 흥미로웠다. 단어 배치로 치면 단순히 space가 아니라 왼쪽 정렬 탭과 오른쪽 정렬 탭으로 구분된 셈이다.

사족을 덧붙이자면, 얘는 도움말 메뉴뿐만이 아니라 프로그램 외형이 전반적으로 좀 범상치 않아서 혹시 qt나 자바 같은 범용 프레임워크로 GUI를 만들었나 하는 생각이 들었다. 허나 Spy++로 들여다보니 그렇지는 않아 보인다. 다만 컴파일러는 Visual C++이 아니라 gcc 계열을 써서 빌드 됐더라. 그리고 얘 자체가 크로스 플랫폼 프로그램이기도 하고 도움말까지 자체 구현인 걸 보면, 독자 개발한 자체 GUI 라이브러리 자체는 사용한 것으로 보인다. (그리고 이 정도 엄청난 프로그램이 무료 공개라는 게 참 대단하다!)

그나저나, 오른쪽에 따로 떨어진 메뉴 아이템은 어떻게 구현된 걸까?
본인은 아무 근거 없이 정말 막연하게.. 왼쪽의 맨 마지막 아이템과 오른쪽으로 밀려난 첫 아이템 사이에 "아마 separator 아이템이 있는 게 아닐까?"라고 오랫동안 생각했다. 하지만 실제로 이걸 넣어 보니, 아이템과 아이템 사이에 공간만 더 생길 뿐 정렬 방식이 달라지지는 않았다.

이걸 구현한 방식은 허무할 정도로 간단하다. 바로 체크/disabled 등을 나타내는 그 상태 플래그에 MFT_RIGHTJUSTIFY라는 정보도 같이 들어있다. 즉, 플래그에는 자신의 상태도 들어있고 속성도 같이 들어있는 것이다. 그건 뭐 윈도우 스타일도 마찬가지이지만.

여러 메뉴 아이템에 그 스타일이 있으면, 스타일이 등장하는 첫 아이템부터 나머지 메뉴 아이템들은 죄다 오른쪽에 정렬되어 나온다. 가로로 배치된 메뉴 말고 세로로 배치된 메뉴 내지 우클릭 팝업 메뉴 같은 데서는 이 플래그는 아무 기능도 하지 않는 잉여이다. 그걸로 끝이다.
마치 콤보 박스의 extended UI 스타일만큼이나 자주 보기 쉽지 않은 UI이다 보니, 더 특별한 방법이 있는가 싶었는데 다소 실망스럽기까지 했다.

예전에 메뉴에 대해서 한번 글을 쓴 적이 있었는데 그 당시에는 오른쪽 정렬 메뉴 아이템에 대해서는 미처 생각을 못 하고 있었다. 그러니 이번에는 메뉴와 관련해서 non-client 영역 얘기나 좀 더 하고 글을 맺겠다.
메뉴가 표시되는 영역다 응용 프로그램이 자체적으로 뭔가 출력을 하는 예로 옛날에 Freecell 게임이 있었다. 남은 카드의 수가 메뉴의 오른쪽 끝에 나타났기 때문이다.

사용자 삽입 이미지

프리셀은 유사품인 카드놀이(solitaire)와는 달리, 처음부터 32비트 코드 기반으로 개발되어 옛날에 Win32s와 함께 제공되기도 했던 역사적인 유물이다.

그런데 Windows XP로 오면서 살짝 옥에티가 생겼다.
XP의 기본 luna 테마에서는 가로로 배치된 메뉴 표시줄은 회색이다. 하지만 펼쳐진 메뉴 창은 흰색이다. 고전 테마 때는 이런 일이 없었는데 역사상 처음으로 메뉴 표시줄의 배경색과 메뉴 창의 배경색이 서로 달라진 것이다.
허나 프리셀은 "남은 카드 수"를 메뉴 창의 배경색으로 출력하는지 옅은 회색 배경에 흰색 배경으로 글자가 찍혀서 뭔가 이질감이 생겨 있다.

그래서 Vista인가 7부터 프리셀은 화면 하단에 상태 표시줄이 별도로 추가되었고, 남은 카드 수는 거기에다 출력하게 동작이 바뀌었다.

그림을 그리라고 운영체제가 보장을 해 준 클라이언트 영역 말고, 창의 프레임이나 제목 표시줄, 메뉴 표시줄 등은 논클라이언트(non-client) 영역으로 분류된다.
여기는 일단은 운영체제가 알아서 모든 처리를 해 준다. 그릴 일이 있을 때 WM_NCPAINT라는 메시지를 날려 주기는 하지만, 어지간해서는 응용 프로그램이 그걸 건드리지는 않는 게 좋다.

전에도 한번 말했듯이 MS Office는 95 시절에 캡션 바(제목 표시줄)를 독자적으로 그러데이션을 입혀서 그리곤 했다. 이건 1회 유행으로 끝났고 그러데이션은 나중에 Windows 98에서 완전히 전체적으로 적용되었다.
더 옛날 16비트 시절에 시스템 차원의 훅킹이 더 쉽던 시절엔 모든 창의 캡션에 응용 프로그램이 자신의 기능을 수행하는 버튼 같은 것도 막 집어넣기도 했던 것 같다.

허나, 그 동작을 어설프게 가로채면, Windows가 버전업 되어서 논클라이언트 영역의 비주얼이 또 바뀌었는데 응용 프로그램은 동기화가 안 되어서 외형이 이상해지고 프로그램이 오동작 할 수가 있게 된다.
일례로, 과거에 아래아한글 97은 논클라이언트까지 완전히 독자적으로 GUI를 그리던 대표적인 프로그램이다. 그런데 Windows XP 테마에서는 윈도우의 논클라이언트 가장자리에 둥그런 모서리 region이 적용되었다.
하지만 아래아한글 97은 기본 region은 딱히 건드리지 않고 그림만 직사각형 region을 기준으로 곧이곧대로 그렸기 때문에 가장자리가 짤려서 대화상자에 약간 glitch가 있었던 것이다.

하지만 Windows는 맥 OS처럼 GUI가 선택의 여지가 없이 독재 획일화인 운영체제는 아닌지라 당장 MS 자신들부터가 Office나 Visual Studio 같은 중요한 밥줄 프로그램들은 운영체제의 표준 GUI 따위는 전혀 안 쓴다.
특히 리본 UI는 논클라이언트를 완전히 제멋대로 재정의해서 쓴다. 논클라이언트 영역에 적용되는 Aero 투명 효과까지 세밀하게 제어하면서 말이다. 지금은 Aero가 폐기돼서 별 의미는 없어졌지만 말이다.

MS 같은 GUI 잉여짓을 할 여력이 없는 프로그래머라면 WM_NCPAINT를 다룰 일은 별로 없겠지만, custom 컨트롤을 만드는 경우라면 불가피하게 이 메시지를 직접 처리해야 하게 된다.
공용 컨트롤 6 매니페스트가 적용되었더라도 윈도우의 테두리는 그냥 가만히 놔 두면 새끈한 테마 형태가 아니라 옛날의 밋밋한 기본 스타일로 그려지기 때문이다. OpenThemeData와 DrawThemeBackgroundEx 같은 함수로 수동으로 그려야 한다.

이런 부류의 글은 결론이 언제나 동일하다. Windows 프로그래밍은 재미있더라.

Posted by 사무엘

2015/05/16 08:25 2015/05/16 08:25
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1094

※ 컴퓨터 & 프로그래밍

1.
예전에 본인은 시스템 종료 중에라도 사용자가 무슨 동작을 취하면, 컴을 아주 꺼 버리는 시스템 종료가 아니라 그 뒤 '재시작'으로 종료 모드를 바꾸는 기능이 있으면 좋겠다는 제안을 한 적이 있다. 그것과 비슷한 제안인지도 모르겠는데, 또 하나 아이디어를 내자면 이렇다. 사용자가 한동안 컴퓨터를 건드리지 않아서 모니터가 꺼지거나 컴퓨터가 절전· 최대 절전· 종료 등으로 바뀌게 되면, 그 모드로 진입하기 전에 화면에 10초나 5초 정도 카운트다운을 좀 띄웠으면 좋겠다.

프레젠테이션을 할 때처럼 화면을 빤히 보고 있으면서 키보드· 마우스만 안 건드리고 있는데 화면이 갑자기 꺼져 버려서 당황한 적이 여러 번 있었다. 화면 보호기 정도는 카운트다운 없이 바로 진입해도 상관 없겠지만 아예 하드웨어적인 변동이 생기는 저런 모드는 예고가 있으면 좋겠다.

2.
동영상 엔진인 '코덱'과 과거의 컴퓨터 통신 장비인 '모뎀'이 정확히 같은 조어법에 의해 거의 같은 구조의 이니셜을 가진 단어이구나.

3.
식당에서 주문을 한 뒤에야 "아 손님, 죄송하지만 재료가 떨어져서 그 메뉴는 지금 제공이 안 됩니다" 이런 메시지를 받으면 허탈하잖아. 애초에 메뉴판에 그런 메뉴는 disable된 상태로 시각 피드백이 있으면 좋겠다.

4.
공동 작업을 하는 코드의 명칭에 영어 스펠링이 틀린 게 많아서 작업에 지장을 적지 않게 받은 적이 있었다. 검색이 안 되기 때문이다. 이쯤에서 분명 availableItem이런 단어가 있는 걸 봤었는데 나중에 보니 avalible이라고 돼 있는 식.
이건 당장 버그나 성능 같은 동작과 직접적인 관련이 있지는 않지만, 그래도 또 다른 형태의 민폐이다. 도서관으로 치면, 책을 보고 나서는 자기 분류 코드상으로 있어야 할 곳이 아닌 엉뚱한 곳에다 책을 꽂은 것과 같다. "잘못 꽂힌 책은 없는 책과 같습니다. 정리는 사서가 알아서 할 테니까 열람하신 책은 그냥 여기에 놔 두세요" ;;;;

5.
관광 가이드를 매뉴얼과 스케줄 대로 승객들을 안내하는 컴퓨터 프로그램에다가 비유한다면, 이 사람이 수행하는 프로그램의 소스 코드는 정말 그야말로 try ... catch문으로 빽빽이 무장하고 있어야겠구나 하는 생각이 들었다.
누군가가 갑자기 아플 때, 뭔 물건을 놔 두고 왔을 때, 여권을 잃어버렸을 때, 긴급한 사고가 발생했을 때, 일행 중 일부가 없어져서 못 찾을 때 등등.. 그 어떤 예외 상황에서도 패닉과 스케줄 펑크를 최소화하는 방향으로 의연히 대처가 가능해야겠다.

6.
Windows 환경에서 응용 프로그램이 자기 영역으로 사용할 수 있는 메모리 주소는 64KB 이상부터이다. NULL 포인터인 0자체뿐만이 아니라 첫 64KB는 가상 메모리 영역 설계 차원에서 봉인되어 있으며, 이 주소에 메모리를 읽거나 쓰는 건 무조건 에러가 난다. 사실, 0 자체뿐만 아니라 64KB 정도까지는 막혀 있어야 NULL포인터 자체뿐만 아니라 NULL로부터 구조체 멤버를 참조한 포인터도 에러로 처리될 수 있을 것이다. ((POINT *)NULL)->y처럼.

아울러, 과거의 Windows 9x는 이보다 제약이 더 커서 64KB가 아니라 상위 4MB까지가 추가로 막혀 있었다. 64K부터 4M까지의 영역은 16비트 프로그램(도스용 & Windows용 모두)이 사용한다. (☞ 이에 대한 더 자세한 설명)

이런 이유로 인해 전통적으로 32비트 Windows 프로그램들은 시작 주소(preferred base)가 딱 4MB로 맞춰지곤 했다. NT 계열에서는 꼭 4MB가 아니라 64KB 이상 아무 지점이어도 상관이 없지만, 4MB 이상이어야 윈도 9x와 NT계열에서 모두 실행 가능하기 때문이다.

그런데 이건 오늘날까지도 하드디스크가 C로 시작하는 디스크 드라이브 관행과도 정확히 일치하는 것 같다.
플로피 디스크가 완전히 없어졌음에도 불구하고 A, B 드라이브는 사실상 결번으로 남아 있으니 말이다. 요즘은 하다못해 USB 메모리 드라이브를 거기에다 할당해도 될 것 같은데!

※ 알고리즘

7.
longest common subsequence를 구하는 문제와 longest increasing subsequence를 구하는 문제는 서로 관련이 있는 무척 흥미로운 문제인 것 같다.
가만히 생각해 보니, 후자는 임의의 sequence와, 그 입력을 오름차순으로 정렬한 sequence와의 longest common subsequence를 구하는 것과 같다. 그러므로 후자는 전자 문제로 다항 시간 만에 변환 가능한 special case이다.

두 문제는 일단 다이나믹 프로그래밍으로 O(n^2)의 복잡도로 풀 수 있지만, 더 작고 특수한 케이스인 후자는 O(n log n)의 해법도 있다.
전자 문제는 문장의 정확도를 구하는 알고리즘, 소스 코드의 diff 툴 등 활용되는 분야가 굉장히 많다. 지금은 어떤가 모르겠는데 내 때에는 국제 정보 올림피아드의 첫째 날 1번 문제가 해법이 이 형태로 귀착되는 경우도 종종 있었다. 1999년도의 꽃병 문제는 대놓고 저런 타입이었고, 2000년도의 palindrome 문제도 자신과 자신을 역순으로 뒤집은 단어와의 longest common subsequence를 구하는 것과 동일하다.

8.
엑셀에서 파이 모양 차트를 그리면 아이템별로 파랑, 빨강, 주황 등 알록달록한 색깔이 배당되어 차트가 그려진다.
그런데 최초의 색깔인 파랑부터 아이템 N에 이르기까지, 색깔을 선별하는 방식이 과연 무엇일까?
Office 2003까지는 뭔가 보라색 위주의 우중충하고 칙칙한 색깔 위주였는데 2007부터는 그래도 예전보다 훨씬 더 세련되게 바뀌었다.

이건 뭔가 RGB나 hue 같은 색공간에서 최대한 균등하게, 마치 흑에서 백으로 디더링 픽셀을 하나씩 채워 나가듯이 색깔을 뽑아낸 것 같다(관련 링크). 그 구체적인 알고리즘이 궁금하다.
그리고, 이런 픽셀 채우기 문제의 domain을 2차원 평면이 아니라 3차원 공간으로 확장하면 문제의 난이도가 어찌 되는지도 궁금하다.

※ 자동차

9.
자동차 차량 취급 설명서의 각종 선택사양에만 적용되는 설명들은 C/C++ 코드에서 #if #endif 전처리기에 대한 아주 좋은 예시라 여겨진다.

10.
오늘날 "일찍 나는 새가 벌레를 잡는다"보다 훨씬 더 현실적으로 와 닿는 말은 "일찍 움직이는 차가 주차 자리를 차지한다"라고 해도 과언이 아닐 것이다.

※ 기타 미분류

11.
공항 안에 개인 물품 보관함 같은 게 있으면 단독 여행 시에 유용하겠다는 생각이 든다. 이곳과 계절이 크게 다른 지역을 여행 갈 때 지금 입은 옷을 보관해 놓는다거나, 반입 금지 내지 무게 제한에 걸린 물건을 귀국 때까지 임시로 보관할 수 있게 말이다. 물론 후자의 경우는 당사자가 보관함까지 갔다가 돌아오는 게 곤란하니, 추가 비용을 부담해서 보관 대행을 맡길 수 있어야 하겠다.

12.
비행기와 열차의 큰 차이:
열차는 출발 15분 전부터 승강장으로 입장이 가능한 반면, 비행기는 출발 15분 전에 탑승이 종료된다는 것이다.
그리고 여담인데, 내 경험상 인천 공항을 출발한 비행기는 견인차에 끌려 터미널을 떠난 순간부터 활주로에 진입하여 이륙을 시작할 때까지도 거의 정확히 15분이 소요된다.

13.
"바탕체 레귤러"라는 서체 이름을 보고는 바탕체 볼드가 아니라
"바탕체 라지"가 순간적으로 먼저 떠올랐다.
요즘 커피를 너무 많이 마셨나 보다....? =_=;;
하긴, 아메리카노가 생각이 안 나서 순간 "아프리카노요"라고 주문을 했다는 사람 얘기도 있으니..;;

14.
몇 년 전부터 우리나라에서는 우측통행, 도로명 주소 등 일상생활과 직접적인 관계가 있는 여러 규범이 바뀌었으며, 이런 차원에서 단위도 비표준 단위가 통상적으로 쓰이던 곳까지 SI 단위가 강제 추진되었다.
고기의 무게는 오래 전부터 '근'이 거의 전멸하고 100그램 단위로 다 정착을 한 것 같지만 여전히 오락가락하는 곳은 부동산에서 다루는 건물이나 땅의 면적이다.

그런데 내가 보기에도 '1평'을 '3.3제곱미터'로 바꿔서 실생활에서 유리한 게 없다. 부자연스러울 뿐만 아니라 음절수도 너무 많아서 발음하기가 불편하다. 바꿀 거면 사람이 실제로 생각하는 넓이의 덩어리도 1제곱미터나 10제곱미터 단위로 업데이트가 돼야 할 텐데.
참, 그나저나 화면의 크기를 표기할 때 으레 쓰이는 '인치'는 센티미터로 바뀌기라도 했는지 궁금하다. 여기도 평이나 근 만만찮게 좀 이상한 단위가 관습적으로 쓰여 온 곳이니까 말이다.

Posted by 사무엘

2015/04/19 08:36 2015/04/19 08:36
, , , , ,
Response
No Trackback , 7 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1084

C/C++ 같은 지극히 static한 컴파일 지향 언어에서는 상상도 못 할 일이겠지만, 아주 다이나믹하고 인터프리터를 지향하는 프로그래밍 언어들은 문자열에 담긴 자기 언어 코드를 지금의 변수/함수 context를 기준으로 실행해 주는... 엄청난 기능을 제공하기도 한다.

PHP와 자바스크립트 모두 eval이라는 함수가 이 일을 한다. 루비던가 펄이던가 문자열의 동적 처리가 강한 다른 언어에도 응당 동일 기능이 있다.
문자열 변수에 들어있는 값(가령 "abc")을 통해서 해당 이름의 변수에 접근하는 것(abc)도 가능하다. C/C++에서는 지역 변수는 그냥 함수 스택 프레임으로부터 고정된 오프셋 숫자를 나타내겠지만, 저런 언어에서 지역 변수는 힙 기반의 해시나 트리를 참조하는 유동적인 이름-값 key 명칭에 해당할 것이다.

그리고 또 다르게 비유하자면, static type 언어는 테이블의 각 필드별로 타입과 정보량이 매우 엄격하게 정해져야 하는 데이터베이스와 같고(액세스),
dynamic type 언어는 셀에 들어가는 값과 타입이 자유자재로 바뀌어도 되는 스프레드 시트와 같다(엑셀).
어마어마한 대용량의 데이터를 처리하는 성능은 까다롭고 전문적인 DB가 훨씬 더 뛰어나지만, 그래도 엑셀 정도만 돼도 성능이 굉장히 좋아지고 그러면서 일상생활에서는 더 편리하다. 세상엔 그 정도 tradeoff는 어디에나 있는 듯하다.

그나저나, 문자열 코드를 실행하는 기능은 편리한 건 그렇다 치더라도 보안을 매우 취약하게 만들 수 있다는 점을 감안하고 사용해야 한다. C 언어의 %d, %s 같은 포맷 문자열만 해도 이미 위험하기 때문에 입출력 포맷 문자열에다가는 사용자로부터 실시간으로 입력받은 문자열을 절대 넣지 말아야 한다는 것이 불문율이다. 그런데 하물며 저건 문자열에 명시된 대로 아예 프로그램이 실행되니 C의 포맷 문자열과는 차원이 다른 방식으로 위험할 수밖에 없다.

본인이 경험해 본 프로그래밍 언어 중에 코드를 실시간으로 생성하고 자기 자신을 변형할 수 있는 물건의 원조는 역시 GWBASIC이었다.
비록 얘는 문자열을 코드로 그대로 해석하고 실행하는 기능은 없지만, 그래도 RUN, CHAIN, MERGE처럼 파일 차원에서 임의의 코드를 즉석에서 실행하는 기능은 있었기 때문이다.

다음은 스택 기반의 복잡한 파싱 없이 15*(2+5), 256^2, 1/5 등의 수식을 쓱쓱 계산해 내는 계산기 프로그램이다. 내가 이걸 생각한 게 20여 년 전의 일이다. ㅎㅎ

10 INPUT "Expression? ", A$
20 IF A$="" THEN END
30 OPEN "TMP.BAS" FOR OUTPUT AS #1
40 PRINT #1, "10 ON ERROR GOTO 30"
50 PRINT #1, "20 PRINT "+A$+": GOTO 40"
60 PRINT #1, "30 PRINT "+CHR$(34)+"Error"+CHR$(34)
70 PRINT #1, "40 RUN "+CHR$(34)+"CALC.BAS"+CHR$(34)
80 CLOSE #1
90 RUN "TMP.BAS"

프로그램의 동작 원리를 알면 피식 웃음이 나올 것이다. 이 프로그램이 하는 일이라고는 입력받은 수식을 그대로 PRINT하는 프로그램을 생성한 뒤, 그걸 실행하는 것이 전부이기 때문이다. 즉, 실질적인 계산을 하는 부분은 내가 짠 코드가 아니라 GWBASIC 인터프리터 자체인 것이다.
그나마 수식에 오류가 있어도 뻗지 않게 하려고 ON ERROR GOTO를 쓰는 일말의 치밀함(?)을 보였다.

사실, RUN은 지금 내 프로그램을 지우고 실행 제어를 다른 프로그램으로 완전히 옮기는 명령이다. MERGE나 CHAIN을 쓰면 내 프로그램의 컨텍스트를 유지하면서 타 프로그램을 그대로 병합을 할 수가 있으며 이게 내가 의도한 형태에 더 가깝다. 하지만 본인은 저 두 명령은 어떻게 사용하는지 잘 모른다.

MERGE 같은 경우 QuickBasic이나 QBasic으로 넘어가면서 후대의 베이직 언어들도 지원하지 않게 되었으며, 후대의 언어들도 차라리 문자열 코드를 실행하는 eval은 지원해도 저런 무지막지한 명령을 지원하지는 않는다. 먼 옛날에 컴퓨터와 함께 제공되었던 두툼한 MS-DOS 겸 GWBASIC 매뉴얼에서나 그런 명령에 대한 자세한 설명을 볼 수 있었던 걸로 기억한다.

자, 그럼 다시 자바스크립트 얘기로 돌아온다. 얘는 HTML, CSS와 더불어 웹을 구성하는 한 축이며, HTML 문서가 MS 오피스의 Word, Excel 같은 일반 문서라면 자바스크립트는 그런 문서에 첨부된 매크로나 마찬가지이다. 다른 모든 언어들은 사용하기 위해서 해당 언어 구현체들 런타임이나 엔진을 설치해야 하지만 자바스크립트는 웹브라우저만 있는 운영체제라면 바로 돌려볼 수 있다는 차이가 존재한다.

자바스크립트에는 딱히 컴파일· 링크 같은 건 없지만 난독화 겸 간소화(compaction)라는 리팩터링 후처리를 거쳐서 서버에 올라가곤 한다. 굳이 IOCCC 같은 대회를 노려서는 아니다. 핵심 알고리즘의 누출을 막고(분석을 완전히 막지 못하더라도, 어렵게 만들거나 지연시키기 위해) 크기도 줄이기 위해서이다.

난독화 내지 간소화를 자동으로 해 주는 도구가 당연히 존재한다. 모든 주석이나 공란은 제거되며 지역 변수는 전부 a~z, aa ... 처럼 식별만 가능하지 최대한 짤막하고 암호 같은 명칭으로 바뀐다. 상수 명칭의 조합으로 좀 더 알아보기 쉽게 숫자를 표현하던 것도 당연히 다 실제 숫자로 치환된다.

그런데 자바스크립트를 리팩터링해 주는 툴 중에는 그런 명칭 치환 수준을 넘어서 코드를 암호화에 가까운 완전히 다른 형태로 바꿔 버리는 것도 있다. 그런 덩어리를 보면 예전 코드는 다 이상한 문자열로 바뀌고 코드의 가장 바깥은 eval을 호출하는 형태가 돼 있다. 그 문자열을 원래의 자바스크립트 코드로 해독하는 건 다른 치환과 디코딩 함수들이고 말이다.

이건 개념적으로 실행 파일 압축과 동일한 기법이다. 거기도 압축을 푸는 데 필요한 최소한의 코드만 들어있고 나머지는 데이터를 압축을 풀어서 코드를 생성함으로써 실행되니까 말이다. 그게 기계어 코드가 아닌 인터프리터 스크립트형 언어에도 저런 식으로 존재할 수 있다는 게 신기하게 느껴진다. 자바스크립트의 JIT는 저런 것까지 다 감안해서 동작해야 할 테니 런타임이 안 그래도 거의 VM 수준의 스케일로 만들어져야 할 듯하다.

이념적으로 C/C++의 완전 정반대편에 있는 동적 언어를 익혀서 나쁠 건 없는 것 같다. JS는 그렇다 치더라도 파이썬은 슬라이싱 기능이 완전 마음에 든다. 늘 하는 생각이지만, 메모리 관리가 자동으로 되고 마음대로 new를 남발해도 되는 언어로 코딩을 하는 기분은 운전으로 치면 정말 클러치 걱정을 할 필요 없는 자동변속기 차를 모는 것과도 같아 보인다.

그리고 저런 동적 언어들은 아무래도 문자열 처리가 강세이며, 언어의 설계자가 확실히 정규 표현식 덕후라는 생각이 들었다.
메모리 관리를 할 필요가 없는 건 편하지만 C/C++의 포인터처럼 아주 저수준 직관적으로 문자열에 접근을 할 수 없는 건 좀 불편하다. 새로운 언어를 익히면 그 언어의 String 클래스가 제공하는 멤버 함수(메소드)부터 새로 익혀야 하니까.

또한 자바스크립트, 정규 표현식, URL, 그리고 HTML 내부에 제각각으로 돌아가는 탈출문자들 때문에 신물이 났다.
문자열을 검색· 치환하는 함수들이 다 있는 그대로 찾아 바꾸는 게 아니라 기본적으로 정규 표현식 기반이다. 역슬래시나 따옴표 같은 크리티컬한 문자 그 자체를 찾거나 그걸로 바꿔야 할 때 프로그램이 곧이곧대로 동작하지 않아서 그것도 좀 애로사항이었다.

Posted by 사무엘

2015/04/16 19:36 2015/04/16 19:36
,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1085

네트워크나 파일 같은 외부 입출력을 열고 닫는 작업은 실패할 가능성이 워낙 높기 때문에 프로그램 작성에서 에러 처리가 거의 무조건적인 필수이다.

하지만 메모리 할당은 그렇지 않다. 수~수십 MB 정도 공간을 요청하거나 재할당하는 것쯤은 요즘 컴에서는 실패할 가능성이 0에 수렴한다. malloc의 결과값이 NULL인지 일일이 체크하는 코드는 요즘 거의 찾을 수 없다. C++의 new 연산자도 예전에는 실패 시 NULL 리턴이었지만 지금은 예외를 던지는 형태로 디자인이 바뀐 지 오래다. 그거 일일이 NULL 체크를 하는 건 너무 남사스럽고 성가시고 민망하기 때문이다.

메모리 할당을 위해 어지간해서는 C++의 깔끔한 new와 delete를 쓴다지만, C++ 연산자에는 메모리의 재할당 기능이 없기 때문에 이를 위해서는 여전히 malloc/realloc/free 쌍이 쓰인다. 그리고 좀 원시적인 테크닉이긴 하지만 가변 길이 구조체의 메모리 할당을 위해서도 크기 지정이 자유로운 C 스타일의 메모리 함수가 필요하다. 아니면 operator new 함수를 직접 호출하든가 말이다.

그런데 realloc은 실행이 실패했을 때의 상태가 꽤 복잡하다. 보통 ptr=realloc(ptr, newsize) 같은 형태로 활용을 하는데, 재할당이 실패했다고 생각해 보자. 이때는 realloc은 재할당을 할 수 없어서 NULL을 되돌린다. 이는 분명 비정상적인 오류 상황이고 프로그램이 그에 대한 별도의 대비를 하긴 해야 하지만, 그렇더라도 ptr이 원래 가리키던 메모리는 아무 이상이 없다. 그런데 ptr에다가 무턱대고 마치 malloc의 리턴값처럼 NULL을 대입해 버리면 ptr은 소실되고 메모리 leak이 발생하게 된다.

그러니 실행이 실패하더라도 메모리 leak은 발생하지 않게 하려면

ptr_tmp=realloc(ptr, newsize);
if(ptr_tmp) ptr=ptr_tmp; //성공
else { } //실패

번거롭지만 이렇게 임시 포인터 변수를 하나 추가로 둬서 실행이 성공했을 때에만 포인터의 실제값을 반영하게 해야 안전하다. 본인은 이 점을 한 번도 생각을 안 하고 있었는데 비주얼 C++ 2012에서부터 추가된 코드 정적 분석기가 지적을 해 주는 걸 보고서야 “아하!”하고 무릎을 쳤다.

이런 것을 생각하면 realloc의 실패야말로 리턴값보다는 예외 처리로 알려 주는 게 더 편리하겠다는 생각이 든다.
절차형으로 실행되는 컴퓨터 프로그램에서는, 당연한 말이지만 n+1단계 명령은 그 앞의 1~n단계의 모든 명령들이 성공적으로 차곡차곡 잘 실행됐다는 전제하에서만 실행 가능하다. 중간에 뭔가 탈이 났다면 더 진행을 할 수 없으며 어디까지 앞뒤로 되돌아가면 되는지를 컴퓨터가 스스로 판단할 수 없다. 컴퓨터에게는 인간 같은 유도리가 존재하지 않는다. 그렇기 때문에 그런 정보가 없다면 그 프로그램은 전체가 강제 종료되는 것밖에 답이 없다.

자동차 운전을 하는 사람이라면 단순히 핸들과 페달과 변속기를 조작하는 것 말고도 사고가 났을 때의 대처 요령과 보험사 연락처 같은 것도 숙지하고 있어야 하듯, 컴퓨터 프로그램도 마찬가지이다. 중간에 탈이 나도 최대한 부드럽게 수습하고, 피치 못할 상황에서  프로그램이 죽더라도 최소한 지금 작성 중인 문서를 저장이라도 한 뒤에 죽는 그 로직 자체도 프로그래밍이 돼 있어야 한다. 그것이 바로 예외 처리라는 분기 제어에 해당한다.
아울러, 숙달된 프로그래머라면 예외 처리를 구현하는 데 드는 추가 오버헤드와 비용을 숙지해 둘 필요도 있다. 수많은 객체들의 생명 주기를 관리하면서 여러 함수들을 한꺼번에 이탈하는 것도 그냥 될 리는 없으니 말이다.

C/C++은 애초에 운영체제/하드웨어 차원에서의 crash는 있어도 언어 차원에서의 예외 처리라는 게 아예 존재하지 않던 언어이다 보니 이쪽의 지원이 다른 언어들보다 상대적으로 미비하다. C++에 try/catch 키워드는 한참 뒤에 등장했으며 언어 자체는 이 예외 구문을 전혀 사용하지 않는다. 이걸 사용하는 건 라이브러리 계층에서이다. 그리고 예외 처리용 객체를 날려 줄 때조차도 new로 메모리를 할당했다면 해제를 수동으로 해 줘야 하니 불편한 점이 아닐 수 없다.

다시 본론인 realloc 얘기로 돌아온다.
저런 예외 처리도 오버헤드가 크니 싫고 리턴값만으로 모든 책임을 회피하고 싶다면, realloc 함수의 프로토타입을 차라리 이렇게 설계했으면 더 편했을지도 모른다.

bool realloc(void **pptr, size_t newsize);

void **라니 참 COM스러워 보이지만(CoCreateInstance, IUnknown::QueryInterface ㅋㅋ), C++이라면 템플릿 함수로 이걸 감싸서 지저분함을 한결 예방할 수 있을 것이다.

if(realloc((void **)&ptr, newsize)) { /* 성공 */ }
else { /* 실패 */ }
free(ptr);

내가 무엇을 의도하는지는 딱 보면 알 수 있을 것이다. 기존 메모리를 가리키고 있는 포인터의 주소를 받아서, 재할당이 성공하면 그 포인터가 가리키는 값을 그대로 바꿔 버리고 true를 되돌리는 것이다. 어차피 지금 realloc 함수는 ptr=realloc(ptr, newsize)라고 ptr이 함수 인자(input) 겸 리턴값(output) 형태로 동시에 쓰이고 있으며, 재할당이 성공했다면 예전 주소는 보관하고 있을 필요가 전혀 없으니 말이다.

실패했다면 ptr은 *ptr이든 **ptr이든 아무 변화가 없고 리턴값만 false가 된다. free(ptr)을 해 주는 한 어떤 경우든 메모리 leak 걱정은 안 해도 된다. realloc 함수가 이렇게 만들어지는 게 더 낫지 않았나 싶은 생각이 든다.
뭐, realloc이 결코 실패하지 않는다고 가정하고 프로그램이 막무가내로 동작한다면, 차라리 NULL 포인터 일대를 액세스하다가 확실하게 죽는 게 기존 메모리를 범위를 초과하여 건드리다가 죽는 것보다는 더 안전할지도 모르겠지만 말이다.

끝으로 하나 더. fopen에서 접근 모드와(read/write 등) 데이터 처리 모드(바이너리/텍스트) 인자는 들어올 수 있는 조합이 뻔하고 상수 명칭 조합으로 처리해도 하등 이상할 게 없을 텐데, 왜 하필 더 파싱도 어렵게 문자열을 쓰고 있는지도 이유를 모르겠다. 딱히 확장의 여지가 있어 보이지도 않는데 굳이 _open 같은 저수준 함수와 형태를 달리할 이유가 없다. 이런 것들이 C 라이브러리에 대해서 궁금한 점이다.

Posted by 사무엘

2015/04/14 08:25 2015/04/14 08:25
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1083

이건 예전에 썼던 disabled 윈도우 관련 글에서 추가로 다뤘어야 했는데 그때 빠뜨렸던 내용이다.
그 글에서 설명했듯, Windows는 '고전 테마'에서 메뉴, 버튼, static 컨트롤이 disable된 형태는 그냥 회색으로만 표시하는 게 아니라 흰색 윤곽 위에다가 회색 윤곽을 덧씌워서 일종의 '엠보싱' 효과를 줘서 표시한다.

사용자 삽입 이미지

이것들은 은색(밝은 회색, 일명 3D 배경색)이 배경이라는 공통점이 있다. 시스템 컬러 번호는 COLOR_3DFACE. 사용자가 딱히 변경을 할 수 없는 고정된 문자들이다.

그 반면, 리스트 박스나 에디트 컨트롤처럼 흰 배경(COLOR_WINDOW)에 표시되는 문자들은 사용자의 선택이나 입력에 의해 내용이 바뀔 수가 있다. 이런 것은 disable됐을 때 배경이 회색으로 변하고 문자는 딱히 엠보싱 처리가 되지 않는다. 각각의 경우에 비주얼이 나름 원칙이 있게 설계된 셈이다.

그런데 먼 옛날에 Windows 9x 시절에 안전 모드 부팅을 해 본 분들은 뭔가 이상한 낌새를 느끼지 않으셨나 모르겠다.
안전 모드에서는 일체의 외부 디바이스 드라이버를 불러들이지 않는 관계로 그래픽조차도 완전 미개한 VGA 640*480 16색으로로 돌아가 버린다.

회색과 파랑의 기본색 팔레트가 다른 톤으로 바뀌는데, 이건 그렇다 치더라도
안전 모드에서는 disable UI가 엠보싱이 아니라 그냥 짙은 회색 단색으로 간소화되어 출력된다. 그래서 느낌이 더욱 달라진다.

사용자 삽입 이미지

엠보싱을 표현하는 데는 흰색과 회색 단 두 색만 있으면 된다. 색깔 표현의 한계 때문에 엠보싱을 포기한 건 결코 아니다.
결정적으로 NT 커널 기반인 Windows 2000은 똑같이 VGA 16색인 안전 모드에서도 동일하게 엠보싱 처리를 해 준다. 어찌 된 일일까?

사용자 삽입 이미지

the old new thing 블로그에 관련 설명이 있다.
시스템 정보를 얻는 API 함수 중 하나인 GetSystemMetrics를 보면 SM_SLOWMACHINE이라는 아이템이 있는데,
얘의 리턴값이 true일 정도로 열악한 환경에서는 운영체제 셸은 disable UI에 엠보싱을 포기하고 그냥 회색 단색을 출력한다고 한다.

이 플래그는 컴이 486이 안 되고 램이 6MB도 안 되는.. 그야말로 윈도 3.1만 간신히 돌리던 극도의 똥컴에서나 켜지는 정도였다. 윈도 95가 당시 사용자의 컴퓨터 환경을 감안하여 명목상으로는 386+램 4MB 이상을 기준으로 설계되었다는 걸 생각해 보자. 물론 한글판은 한글 입출력 오버헤드 때문에 그런 사양에서는 어림도 없으며, 최하 486에 램 8MB 이상은 기본으로 갖춰져야 했다.

그리고 컴퓨터가 아니면 그래픽 카드가 완전 구릴 때에도 이 플래그가 설정되었다.
이를테면 마우스 포인터 깜빡임 보정조차 안 될 정도로 안습일 때 말이다. 기본 VGA는 하드웨어 가속이고 뭐고 아무것도 없는 느려터진 모드이다. 당장 시스템 종료를 위해 Alt+F4를 눌러 보면, 화면 배경 전체에 검은 도트가 반씩 씌워지는 것조차도 단번에 안 되어 점이 내려오는 게 보일 지경이다.

안전 모드에서 disable UI가 엠보싱 없이 출력되었던 것은 바로 그래픽 모드 때문이었다.
엠보싱은 지금 컴퓨터의 관점에서야 그야말로 껌값인 처리이지만, 나름 더블 버퍼링이라는 오버헤드가 필요한 연산이었으니 말이다.

이런 9x와는 달리, NT 계열은 1993년에 출시된 첫 버전 3.1부터가, 그것도 한글· 한자 오버헤드 따위도 없는 영문 원판이 램이 최하 12MB 이상 필요한 왕창 무거운 물건이었다. 범용성과 안정성, 이식성을 위해서 컴의 성능을 쫙쫙 빼다 쓰는 형태로 설계되었기 때문이다. 기본 문자 집합부터가 1바이트가 아닌 2바이트 크기였으니 그것도 메모리를 추가로 잡아먹었을 테고. (그러니 그 시절에 NT를 돌릴 수 있는 컴을 가진 사람이 도대체 얼마나 됐겠는가.)

블로그 글에 따르면, NT는 그야말로 “All machines are fast.”라고 가정하고 태생적으로 SM_SLOWMACHINE 플래그를 사용하지 않는다고 한다. 즉, 어떤 컴퓨터에서나 언제나 false를 되돌린다.
그러니 Windows 2000에서는 VGA 16색 안전 모드에서도 UI에 엠보싱이 적용되는 이유가 논리적으로 바로 설명이 된다.

단, 신기한 것은 2000은 VGA 16색 안전 모드에서도 그래픽이 그렇게 느리게 느껴지지 않는다는 점이다.
게다가 그 모드에서도 그래픽이 마구 바뀌는 곳에 마우스 포인터를 가져갔을 때 포인터가 깜빡거리지 않는다! 하드웨어 제어를 어떻게 했는지 굉장히 궁금해지는 대목이다. 뭔가 굉장히 탄탄하고 안정적이라는 느낌이 든다.

Windows XP부터는 더 나아가 이제 안전 모드에서도 VBE인지 뭔지 슈퍼 VGA 규격을 사용한다. 비록 하드웨어 가속이 없을지언정 일단 트루컬러는 무조건 보장된다. 그래서 초라한 16색, 256색 따위는 정말로 볼 일이 없어졌으니 참 격세지감이다. 그냥 16비트 컬러냐 32비트 컬러냐의 양자 선택만이 있을 뿐이다.

그리고 고전 테마 말고 새로운 테마에서는 disabled UI에 엠보싱 자체를 하지 않는다. 오히려 SM_SLOWMACHINE 스타일과 같은 맥락인 회색 단색으로 회귀했다. 고전 테마가 아예 없어진 Windows 8부터는 엠보싱은 아련한 과거 추억이 됐다.

그래픽 모드가 아예 단색일 때는 diabled UI는 글자에다가 배경색 도트를 반반씩 뿌려서 흐리게 했었는데 그게 16컬러 시절에 엠보싱으로 바뀌었고, 이것이 궁극적으로는 알파 채널로 변모하는 듯하다. 사실 엠보싱은 트루컬러+알파채널+ClearType에 친화적인 방식이 아니긴 하니까 말이다. (맑은 고딕을 엠보싱해서 흐리게 출력하면 보기가 대략 좋지 않다)

참고로 MS Office 제품 중에 Word와 Excel은 운영체제의 대화상자 API가 아니라 자체 개발한 대화상자와 GUI 라이브러리를 사용한다. 얘들은 고전 테마 기준으로 push 버튼은 엠보싱으로 출력하지만, 라디오나 체크 박스는 단순 회색으로 disabled 상태를 출력한다. 즉, 비주얼이 짬뽕이며, 운영체제 GUI와 외형이 완전히 같지는 않다.

사용자 삽입 이미지

끝으로..
Windows에 사용자의 컴퓨터 성능을 체크하는 듯한 기능 몇 군데를 좀 살펴보고 글을 맺도록 하겠다.
하나는 비스타와 7 시절에 있던 그 유명한 Windows 체험지수인데, 이건 8부터는 없어졌다.
다른 하나는 '내 컴퓨터' 속성의 "고급 - 성능 옵션"에서 각종 시각 효과를 설정하는 곳이다.

이건 Windows XP에서 처음 도입된 걸로 기억하는데, 윈2000이나 돌릴 법한 좀 간당간당한 컴에서 "최적 성능으로 조정"을 켜면 그림자 효과나 창의 애니메이션 등 몇몇 효과가 알아서 제외된다.
하지만 이것은 SM_SLOWMACHINE 플래그와는 별개로 구현된 기능이라고 생각하면 되겠다. 또한, 요즘 컴퓨터에서는 그런 거 성능이 문제되는 경우는 전혀 없다고 봐도 무관하고.

아무튼 흥미로운 사실을 하나 알게 됐다.

Posted by 사무엘

2015/04/11 08:30 2015/04/11 08:30
,
Response
No Trackback , 3 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1082

« Previous : 1 : ... 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : ... 24 : 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:
3940131
Today:
799
Yesterday:
2118