« Previous : 1 : ... 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : ... 32 : Next »

1. Windows와 Office의 IME → 독립했다가 도로 운영체제로 합병

오~ 그러고 보니 MS Office 2013부터는 Office IME가 없어졌다는 걸 이제야 확인했다.
한때 웹브라우저인 Internet Explorer가 3~5버전 시절엔 자기가 운영체제의 셸을 뜯어고치고 comctl32, shell32 같은 시스템 DLL을 마구 업데이트 했던 것처럼, 문자 입력 쪽은 아무래도 오피스 제품의 기술 수준이 더 앞서 있었다.

그래서 IME 기반이던 다국어 입력 기능을 TSF라는 인터페이스로 바꾸는 것도 2001년에 MS Office XP가 최초로 도입했다. 그때부터 Office를 설치하면 한국어/일본어판은 자국어 IME도 새 걸로 바뀌기 시작했다. CJK 쪽이 아닌 라틴 알파벳 언어에서는 필기/음성 인식 같은 기능이 이 인터페이스를 기반으로 첫 도입됐다.

TSF 자체는 Windows XP도 운영체제 차원에서 완전히 내장돼 들어갔다. 그러나 이번엔 MS Office 2003 한글판이 제공하는 IME에서 글자가 아닌 단어 단위로 한자 변환을 하는 기능이 추가됐다. 이것은 운영체제의 IME에는 없던 기능이고 비스타에 가서야 추가됐다. 즉, 운영체제가 Office보다 한 박자씩 늦었던 것이다.

그러나 문자 입력 관련 기술들이 다 상향평준화하고 발전이 정체되면서, 굳이 Office가 IME를 또 제공할 필요가 없어졌다. 실제로 윈도 비스타 내지 Office 2007부터는 Office의 한글 IME나 Windows의 한글 IME나 차이는 거의 없어졌다. 괜히 똑같은 프로그램이 중복으로 존재하는 셈이 됐고 그 관행이 2010에까지 이어졌다가 2013부터는 드디어 Office IME는 없어졌다. 2000년대를 풍미했던 관행이 끝났다.

2. Windows와 IE → 합병하려다가 철회

운영체제의 셸의 발전을 주도하던 IE는 2000년대 중반까지 버전 6으로 90%에 달하는 점유율로 리즈 시절을 찍자, 발전이 정체되면서 역설적으로 Office IME와 비슷한 운명을 맞이할 뻔했다.
물론 IE의 버전업을 완전히 중단하는 건 아니고, IE 팀을 해체하고 Windows 팀에다가 합병시켜서 유지보수 비용을 줄이는 것이다. 그래서 IE는 독자적인 프로그램이 아니라 걍 운영체제의 일부로서 Windows와 함께 같이 유지보수를 하겠다는 것이 계획이었다. 웹브라우저는 전면 무료화가 돼 버리는 바람에 오피스나 개발툴과는 달리, 어차피 독자적인 고유 수입도 없으니 말이다.

그렇게 안일하게 생각했는데 파이어폭스의 급부상으로 인해 MS도 생각을 고쳐 먹었고, 급히 IE7을 만들게 됐다. 그리고 IE는 '셸 통합'이라는 예전 트렌드와는 달리 Windows 탐색기와는 다른 길을 가기 시작했다. Windows XP + IE6 시절에만 해도, 탐색기 창이 곧바로 IE 창으로 바뀌고, IE 주소 창에서 내 컴퓨터 디렉터리를 때리면 그 창이 곧바로 탐색기로 바뀌곤 했었는데.. 이것도 참 아련한 추억이다.
물론 지금은 아예 너무 누더기가 된 IE의 개발을 끝내고 마소가 브라우저를 처음부터 다시 만드는 지경까지 갔고 말이다.

3. Windows와 Office의 파일 대화상자 → 독립했다가 도로 운영체제로 합병

지금은 벌써 세월이 많이 지났지만, 2007년경엔 요런 일도 있었다.
원래 MS Office는 운영체제가 제공하는 파일 열기 대화상자 대신 독자 개발한 대화상자를 썼는데, 이젠 Office도 운영체제의 표준 대화상자로 복귀했다. 비슷한 시기에 출시된 Visual Studio 2008도 동일한 조치를 취했다.

운영체제가 보급으로 제공하는 대화상자는 기능이 너무 빈약하다는 이유로, 혹은 별 이유 없이 잉여력이 넘쳐서 Office 팀에서는 같은 기능을 또 만들어서 썼다. 대표적으로 favorite 폴더를 바로 클릭해서 이동하는 기능은 Windows에서는 2000/ME급에서야 도입됐지만 Office에서는 97 때부터 있었다.

하지만 Windows Vista급쯤 되니까 보급 표준 대화상자도 기능이 충분히 강력해졌고, 굳이 둘을 따로 만들 이유가 전혀 없어졌으니 그 시기에 팀간의 코드 통합이 이뤄졌다.

4. Windows와 Visual C++의 CRT → 통합 불가, 영구 독립

Visual C++은 운영체제 자체만큼이나 그야말로 전세계를 석권한 컴파일러이며, 마소 내부에서도 많이 쓴다. 그러나 전부 얘만 쓰는 건 아니었다.
Windows 개발팀이 자체적으로 보유하고 있는 컴파일러와 C 라이브러리 DLL, 그리고 비주얼 C++이 제공하는 컴파일러와 라이브러리가 처음엔 호환되었지만 시간이 갈수록 서로 호환되지 않게 되면서 문제가 심각한 지경이 됐다.

20년 전이나 지금이나 printf, strcpy, qsort 같은 표준 함수의 구현체가 한번 만들어 놓은 뒤에 도대체 바뀔 게 있나 싶지만..
보안 강화 버전이 도입되고 자료형이 32에서 64비트로 확장되는 등, C 라이브러리도 영구 봉인을 하기엔 바뀌는 게 굉장히 많았다.

그래서 Visual C++ 6.0과 Windows 98 시기를 마지막으로 C 라이브러리는 msvcrt(운영체제) 계열과 msvcr???(VC++) 계열로 서로 완전히 이원화가 돼 버렸다. 그 전에는 이름조차도 crtdll(운영체제)과 msvcrt(VC)로 따로 놀았는데 그건 운영체제가 crtdll을 버리고 msvcrt로 간 것이었다. Windows, Visual Studio, Office, IE 등 마소를 구성하는 핵심 부서들간에는 코드의 공유가 생각보다 원활하게 이뤄지지는 않고 있는 듯하다.

마소에서 만든 Windows나 Office의 구버전 바이너리들은 링커 버전 같은 내부 구조를 보면 일반적인 Visual C++ 컴파일러가 생성해 주는 형태가 아니었다. 그러던 것이 Vista 타이밍이 되면서 그 직전에 나온 가장 최신 Visual C++의 버전이 찍히게 되었으며 Office의 경우 Windows가 아닌 VC++의 CRT 라이브러리를 쓰는 형태로 바뀌었다.

그러고 보니 옛날에는 플랫폼 SDK 내지 DDK에 내장돼 있는 무료 컴파일러와 Visual C++ 컴파일러도 서로 달랐다. 그러나 이 역시 지금은 통합이 이뤄졌다. Visual C++이 일부 기능이 빠진 무료 express 에디션이 2003~2005 사이부터 나오기 시작했으며, 2013부터는 아예 MFC와 리소스 컴파일러까지 다 포함된 Community 에디션이 통째로 조건부로나마 무료로 풀렸으니 말이다. 플랫폼 SDK에 내장된 무료 C/C++ 컴파일러는 비주얼 C++ 원판에 비해서는 최적화 성능이 떨어지는 물건이었으나 그것도 비주얼 C++의 오리지널 컴파일러가 대체를 하게 됐다.

마소 내부에서 컴파일러는 비주얼 C++로 이렇게 교통 정리가 돼 가고 있으나, 워낙 다양한 버전들이 난립하고 있으니 요즘은 혼란의 여지를 원천봉쇄하려고 Visual C++ 2015부터는 "CRT 정도는 어지간해서는 각자 걍 static link하세요. 디스크 용량도 많은데.. ㄲㄲㄲ" 이러는 추세이다. 어찌 보면 CRT의 DLL 링크라는 개념이 존재하지 않던 16비트 시절로 회귀하는 것이기도 하다.

Posted by 사무엘

2015/07/20 19:21 2015/07/20 19:21
,
Response
No Trackback , 5 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1118

사용자의 마우스 클릭에 동작하는 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

맥 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

DllMain 이야기

Windows 운영체제에 실행 바이너리로는 잘 알다시피 EXE와 DLL이 있다.
EXE에 시작 지점이 WinMain이 있듯, DLL에는 DllMain이라는 시작 지점이 있어서 간단한 자신의 시작과 종료 같은 이벤트 통지를 받아서 초기화/마무리 작업을 할 수 있다. 이건 static library에는 없는 개념이다.

이 함수는 export table의 이름이나 ordinal로 탐색하는 게 아니라, PE 실행 파일 헤더 차원에서 주어져 있는 entry point의 주소로 진입하는 것이기 때문에 이름이 딱히 export되어 있지 않아도 된다.
물론 요즘 DLL은 코드 셔틀뿐만 아니라 리소스 셔틀의 역할도 많이 하기 때문에(특히 다국어 지원용!) 데이터만 추출한다는 플래그를 주고 LoadLibraryEx를 호출했거나 DLL 자체가 리소스 전용으로만 만들어졌다면 그런 DLL은 DllMain 함수가 없거나, 있더라도  실행되지 않는다.

실행 파일은 자신이 말 그대로 실행 주체이기 때문에 WinMain 함수의 리턴이 곧 해당 프로세스의 종료를 뜻한다. 그러나 DLL은 어떤 함수 호출이 있을 때에만 그때 그때 실행되는 형태이기 때문에 DllMain도 이벤트만 받고는 금세 리턴된다는 차이가 있다. 즉, 이런 점에서 DllMain은 WinMain보다는 윈도우 프로시저와 더 비슷한 형태이다.

DLL이라는 단어 자체가 대문자 이니셜이긴 하지만, 일단 C/C++ 컴파일러가 링크 때 참조하는 명칭은 DLLMain이 아니라 DllMain이다. L은 소문자로 쓰니 스펠링을 혼동하지 않도록 주의하자.
함수의 인자로는 자기 자신의 인스턴스(모듈) 핸들, 그리고 호출 이벤트(DWORD fdwReason)와 부가 인자(PVOID)가 들어오며, 리턴값은 BOOL이다.

이벤트는 4가지 종류가 있다.
일단, DLL이 처음 로딩되어 어떤 프로세스에 붙었을 때(DLL_PROCESS_ATTACH), 그리고 반대로 종료될 때(*_DETACH) 두 가지 경우가 당연히 포함된다. 이것은 LoadLibrary 내지 FreeLibrary 같은 런타임 동작으로 인한 결과일 수도 있고, 아니면 실행 파일의 import 테이블 차원에서 로딩되거나 해당 프로세스가 종료되어 딸린 DLL들이 모두 일괄 종료되는 경우일 수도 있다.

그 다음으로 DLL들은 특별히 그 프로세스 안에서 스레드가 새로 생성되거나 종료되었을 때도 통지를 꼬박꼬박 받는다. DLL_THREAD_ATTACH와 *_DETACH. 이건 응당 16비트 시절에는 없다가 나중에 새로 생긴 정보일 것이다. 그리고 당연한 말이지만 그 생성되거나 소멸되는 스레드의 실행 문맥으로 함수가 호출되므로 GetCurrentThreadId 같은 함수로 지금 스레드 ID 따위를 쉽게 조회해 볼 수 있다.

다만, 이 통지는 이 DLL이 로딩되기 전부터 이미 존재하던 스레드에 대해서는 오지 않으며, 스레드가 하나씩 차례대로 종료되는 게 아니라 메인 스레드가 실행이 끝나서 프로세스의 스레드들이 죄다 한꺼번에 종료될 때는 스레드별로 detach 통지가 오지 않는다. 그러므로 실행 중인 모든 스레드를 한데 조회하는 건 다른 API를 써서 해야 하며, 스레드별로 만들어 뒀던 리소스를 한꺼번에 해제하는 건--가령, 스레드별로 TLS 슬롯들이 가리키는 추가 할당 메모리-- *_PROCESS_DETACH 에다가도 마련해 둬야 한다.

DllMain의 추가 인자는 사실상 *_PROCESS_DETACH일 때에만 쓰인다.
이 DLL이 FreeLibrary로 인해서 레퍼런스 카운트가 0으로 떨어져서 동적으로 해제되는 것이면 추가 인자에는 0이 들어오고, 그렇지 않고 호스트 프로세스가 종료되면서 자명한 이유로 인해 같이 해제되는 것이면 1이 들어온다.
그렇기 때문에 *_PROECSS_DETACH일 때 어떤 값이 들어오느냐에 따라 혹시 모듈의 reference count leak가 있지는 않았는지를 체크할 수 있다.

운영체제가 기본 제공하는 시스템 DLL을 쓰는 게 아닌 이상, 내 EXE에서 쓰는 내 custom DLL은 대부분 동적으로 읽어들이고 해제하는 게 일반적이다. (delay-load 포함) 그런데 한번 읽었던 동일 DLL에 대해 자꾸 LoadLibrary를 반복 호출하면 해당 DLL의 레퍼런스 카운트가 증가하기 때문에 나중에 FreeLibrary를 한 번만 했을 땐 메모리에서 해제가 되지 않게 된다. 그 즉시 DLL_PROCESS_DETACH에 0이 들어오면서 DLL이 해제가 되지 않는다면 어딘가에 버그가 있다는 뜻이다.
결국 프로세스가 종료될 때가 돼서야 궁극적으로 해제가 되긴 하지만(이때는 1이 들어옴), 어쨌든 이런 것도 넓은 의미에서는 memory leak이 되는 셈이다. COM 오브젝트의 레퍼런스 카운트 관리와 완전히 똑같은 문제다.

*_PROCESS_ATTACH일 때에도 추가 인자에는 LoadLibrary에 의한 동적 로드일 때는 0, 그렇지 않고 import 테이블에 의한 정적 로드일 때는 1이나 이에 준하는 nonzero 값이 온다고는 하는데 본인은 그건 확인을 못 해 봤다.
그리고 DLL의 입장에서는 자신이 어떤 방식으로 로드되느냐에 따라 딱히 달리 동작하거나 자신의 로딩 방식을 굳이 알아야 할 일은 거의 없다.
이것 말고 단순히 스레드 생성/소멸 통지 때는 추가 인자는 쓰이지 않고 그냥 0만 온다.

한편, DllMain 함수의 리턴값은 일반적으로는 쓰이지 않고 1을 되돌리든 0을 되돌리든 무시된다.
이게 쓰이는 단 한 가지 상황은 *_PROCESS_ATTACH 때로, 이때는 함수가 TRUE를 되돌려야 DLL의 로딩이 성공한 것으로 간주된다. 안 그러면 이 DLL의 로딩은 거부되며 LoadLibrary의 리턴값은 NULL이 된다.

과거에 Visual C++이 2005와 2008 시절에 CRT와 MFC 라이브러리에 대해서 side-by-side assembly 방식을 강제 적용해서 이 방식으로 DLL이 로딩되지 않으면 로딩을 거부하곤 했는데, 그것 판단을 DllMain 함수에서 했다. 즉, 자신이 단순히 EXE와 같은 디렉터리나 운영체제 시스템 디렉터리에 있어서 로딩이 된 것이라면 DllMain이 고의로 FALSE를 되돌렸던 것이다.

그리고 중요한 점으로는.. DllMain의 *_PROCESS_* 실행 시점은 C++ 프로그램으로 비유하자면 main 함수가 실행되기도 전에 전역 클래스 객체의 생성자 내지 소멸자 함수가 실행된 것과 같다.
범용적인 DLL는 정말 기상천외한 EXE에 붙을 수도 있으며 DllMain의 호출 시점은 주변의 다른 DLL들이 모두 제대로 로딩 됐다고 장담할 수가 없는 때이다. 그렇기 때문에 이때는 다른 복잡한 초기화를 하지 말고 가능한 한 우리의 영원한 친구인 kernel32에 있는 함수만 호출해야 한다.

DllMain에서 할 만한 좋은 작업의 예로는 TLS 슬롯 할당, heap, 뮤텍스, 크리티컬 섹션, memory mapped file 같은 커널 오브젝트의 간단한 초기화 정도이다. 그 외에 user32나 gdi32까지 가는 작업은 권장되지 않으며 하물며 레지스트리나 COM/OLE 같은 시스템을 건드리는 정도만 되면 절대 금지이다.

이 함수 안에서 또 다른 DLL을 연쇄적으로 불러들이거나 해제하는 작업도 금물이다. 그게 가능하다면 참 편할 것 같지만 운영체제의 입장에서는 예측할 수 없는 엔트로피를 키우는 일이며 데드락을 야기할 수 있다. (특히 상호간에 LoadLibrary를 하는 경우는..?? =_=) 완전히 같은 예는 아니지만 생성자 안에서 가상 함수를 호출하는 게 왜 금지되어 있는지를 생각해 보자.

그러니 더 복잡하고 정교한 초기화나 마무리 작업은 DllMain에서 하지 말고, 함수를 따로 만든 뒤에 이 DLL의 사용자로 하여금 그걸 별도로 호출하게 해야 한다.
윈도우 컨트롤을 제공하는 DLL이라면 그냥 LoadLibrary를 하는 순간에 컨트롤의 윈도우 클래스들이 자동으로 등록돼 버리면 좋겠지만, 일단은 윈도우 클래스도 user 계층 관할이기 때문에 초기화를 별도의 함수에서 하는 게 바람직하다.

CWinApp 개체가 존재하는 MFC 확장 DLL의 경우, InitInstance와 ExitInstance가 호출되는 타이밍이 역시 DllMain이다. 그 때밖에 기회가 없으니 어찌 보면 당연한 얘기이다. 그러니 그때에도 사용하는 함수에 동일한 제약을 적용하여 주의해야 한다. 이건 Lyn 님의 블로그에서 발견한 정보임을 밝힌다. ^^

끝으로, 스레드와 관련하여 하나만 더 첨언하고 글을 맺겠다.
*_PROCESS_* 메시지야 어느 DLL에게나 자신의 생명 주기를 알리는 필수불가결한 메시지이겠지만, *_THREAD_*의 경우는 그렇지 않다. 모든 DLL들이 스레드 생성이나 소멸을 일일이 통보 받아야 할 필요는 없다.
그렇기 때문에 스레드 통보를 받을 필요가 없는 DLL은 DisableThreadLibraryCalls라는 함수를 호출함으로써 *_THREAD_*를 받지 않겠다고 운영체제에다 알려 주는 게 조금이나마 성능 향상에 도움이 된다.

얘는 물론 DllMain에서 곧장 호출해도 안전한 kernel32 함수이다. 수시로 켰다 껐다 할 필요가 있는 옵션이 아니어서 그런지, 한번 지정만 하고는 끝이다.
개인적으로는 이런 간단한 정보는 DllMain + DLL_PROCESS_ATTACH의 리턴값 플래그로 접수하는 게 좋지, 굳이 별도의 함수로 만들 필요가 있었나 싶은 생각이 든다. 리턴값이 0이면 로드 거부, 1이면 로드 허용, 2이면 로드 허용하되 앞으로 이 DLL은 스레드 통지는 안 함 정도로. 하지만 함수가 저렇게 만들어져 버렸으니 프로그래머의 입장에서는 그걸 적절히 사용만 하면 되겠다.

다만 C 라이브러리를 static 링크하는 DLL은 저 함수를 사용하지 않는 게 좋다. 자기는 스레드 통지를 사용하지 않더라도 배후의 CRT가 스레드 통지를 내부적으로 활용하기 때문이다. 물론 CRT를 DLL 링크하는 DLL에 대해서는 이런 제약이 적용되지 않는다.
그렇기 때문에 한 소스에 대해 CRT를 static/DLL 여러 방식으로 빌드하는 DLL이라면 #ifdef _DLL에 따라서 DisableThreadLibraryCalls 또는 __noop으로 대응하는 매크로 함수를 만들어 사용하는 게 바람직하다.

Posted by 사무엘

2015/04/28 08:20 2015/04/28 08:20
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1088

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

본인이 인터넷에서 굉장히 고맙게, 유용하게 잘 열람하는 정보 중 하나는 지도이다.
참 대단하지 않은가? 항공 사진, 길거리 사진, 길 찾기, 실시간 대중교통 연계와 도로 상황 안내 등... 정말 혀를 내두르는 수준이다. 이젠 도대체 얼마나 더 똑똑해질 거리가 남아 있는 걸까?

아울러, 지도의 일종인 차량용 내비게이션 소프트웨어도 도대체 어떤 천재가 만들었나 싶은 생각이 든다. 도로 상황을 감안해서 길을 찾는 건 당연한 소리이고, 그걸로도 모자라서 길 가는 중에 실시간으로 “해당 경로에 사고가 발생했습니다. 우회 경로를 재탐색할까요?”까지도 튀어나온다.

2013년엔 구글 회장이 한번 방북을 하고 났더니 구글어스가 평양을 중심으로 북한의 세부 지리 정보(단순 항공 사진은 예전부터 제공했음)를 제공하기 시작했다. 얘를 시작으로 2014년 하반기부터는 국내 지도 사이트들도 북한 정보를 제공하기 시작했다.
고무적인 현상이다. 물론 구글어스도 그 자체는 처음부터 구글이 개발한 게 아니라 타 업체 솔루션을 인수한 것이긴 하지만 말이다.

본인이 예전에 인터넷 지도에 대해 썼던 글은 이 모든 기능이 별도의 응용 프로그램이 아니라 웹에서 웹 표준 기술만으로 바로 구현 가능해진 것이 신기하다는 요지였다.
이번에는 다른 분야에서 대단히 신기하게 느껴지는 것에 대해 이야기를 늘어놓아 보겠다. 바로 이미지 가공 기술이다.

지도 사이트들이 제공하는 평면 항공 사진은 (1) 넓디넓은 영역을 한결같이 위에서 아래를 내려다보는 단일 각도로 본 이미지이다. 그런데 이거 정말 가공을 많이 했겠다는 생각이 들지 않는가?
이미지에서 원근감이라는 걸 완전히 제거하고 건물들이 마치 스타크래프트 맵처럼 보이게 해야 한다. 중심에서 먼 곳의 건물일수록 모양이 왜곡되어 보이는 카메라 렌즈의 오차를 보정해야 한다.

물론 엄청 높은 곳에서 촬영을 하면 건물 자체의 높이로 인해 발생하는 원근감은 상당수 없어지지만 이번엔 반대로 고층 건물도 높이가 전혀 표현되지 않게 되며, 또 사진의 화질이나 해상도, 그리고 구름으로 인한 시야 가려짐 같은 기술적인 문제도 커진다. 게다가 지구 자체도 근본적으로 평면이 아니라 둥근 구이니, 이로 인한 평면의 왜곡은 카메라의 위치가 높아질수록 더욱 부각되어 보일 것이다.

이런 항공 사진은 전세계의 것을 동시에 촬영하기란 불가능할 테니 여러 사진, 혹은 연속적으로 촬영된 사진을 파노라마 사진 만들듯이 연결해야 할 것이고 이 사진들은 촬영 시간대도 최대한 일치해야 할 것이다(광량의 차이). 또한, 주행 중이어서 시시각각 위치가 변하는 자그마한 자동차나 열차의 모습은 어떻게 보정을 하면 좋을까?
이런 것들을 다 극복하고 전국· 전세계의 항공 사진을 최대한 일관성 있는 색조와 각도로 엮는 것은.. 그 어려움과 복잡함이 정말 말도 못 할 것 같다. 비행기에서 아래를 내려다보고 사진만 팡팡 찍는다고 해서 구현 가능한 게 아니다.

사용자 삽입 이미지
(사진으로 나타난 63 빌딩의 높이와, 그림자의 길이를 비교해 보자.;; 각도가 뭔가 자연스러운 것 같지는 않다. 보정을 한 게 아닐까..)

그 보정이 자동화가 가능한지 아니면 일일이 수작업으로 행해지는지가 궁금하다.
마치 요런 영화 촬영 기법을 떠올리게 한다. 피사체는 시간이 정지한 듯 꼼짝 않고 있는데 카메라가 뱅그르르~ 돌아가면서 다른 위치와 각도에서 피사체를 응시하며 촬영하는 것 말이다. 심지어 사람이 하늘에 붕 떠 있는 채로 그런 장면이 나오기도 하니 더욱 신기한 일이다.

그리고 다음으로 생각할 것은 로드뷰이다.
이것은 앞의 항공 평면 사진과는 반대로, (2) 단일 시점에서의 view를 모든 각도로 제공하는 것이다. 이것은 어쨌든 연속으로 촬영할 수는 없기 때문에 로드뷰의 시점은 수~십수 미터 간격으로 띄엄띄엄 제공된다.

사용자 삽입 이미지

이런 시점 view는 지금이야 지도 사이트에서 쉽게 열람할 수 있는 기능이 됐지만, 옛날에 2000년대 초엔 철도청 홈페이지에서 자바 애플릿 형태로 비슷한 기능을 제공한 게 있었다.
바로 새마을· 무궁화· 통일호 내지 전동차의 객실 내부를 저런 로드뷰처럼 상하좌우 둘러보는 기능이었다.

이 기능은 내부적으로 2차원 평면 형태의 파노라마 사진을 한 장 저장하고, 그 그림의 일부에다 원근법을 적용하여 변형한 것을 표시하는 형태로 구현되어 있다. 내가 아는 건 이게 전부이고 구체적으로 어떤 계산을 하는지, 그리고 이런 용도로 사용하는 사진은 어떤 형태이고 어떻게 촬영하는지에 대해서는 잘 모른다. 그야말로 상하좌우 시야각이 다 열려 있는 특수한 카메라를 써야 할 텐데..

내가 10여 년 전에 이미 3차원 그래픽 시연 프로그램이라는 것도 만들어 봤지만, 비트맵 이미지로부터 3차원 시야를 어떻게 구현하는지는 여전히 감이 안 온다.
2차원 이미지에서 원근감을 넣거나 없애고, 평면과 공간 사이를 오고 가게 하는 기술이 참 대단하게 느껴진다. 그 기술이 인터넷 지도, 더 나아가 증강현실 같은 것도 가능하게 한 셈이다.

Posted by 사무엘

2015/04/08 08:32 2015/04/08 08:32
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/1081

« Previous : 1 : ... 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : ... 32 : Next »

블로그 이미지

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

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2026/10   »
일 월 화 수 목 금 토
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

Site Stats

Total hits:
4192438
Today:
674
Yesterday:
1684