1.
Windows API에서 DrawText는 gdi도 아니고 user 계층에 있는 고급 함수인 주제에 여러 줄(DT_SINGLELINE 플래그 없는 기본 모드)을 찍을 때에도 세로 정렬(DT_VCENTER, DT_BOTTOM)을 좀 지원해 주면 어디 덧나나 싶다. 오래 전부터 개인적으로 매우 대단히 아쉽다고 생각해 온 점이다.
얘는 gdi 계층에 있는 다른 글자 출력 함수들과는 달리, 글자수를 -1 (null-terminate string을 가정하고 알아서 길이를 계산하게)로 줄 수가 있으며, 긴 파일/디렉터리 이름의 중간을 생략하여 찍거나 액셀러레이터 &를 다음 글자의 밑줄로 바꿔 출력하는 기능, 심지어 밑줄만 출력하는 기능도 있다.
& 전처리의 경우, 하는 게 아니라 “끄는 게” 별도의 플래그로 주어져 있을 정도로 기본 기능이다.
그러니 이건 천상 운영체제 내부에서 자기네 GUI 출력용으로 쓰는 함수인데 제3자도 사용할 수 있게 공용 API로 열어 놨다는 뜻이다.
안 그래도 텍스트를 처음부터 끝까지 쭉 읽어 봐야만 할 수 있는 처리들이 즐비한데, 그에 비해 멀티라인 텍스트의 세로 정렬은 텍스트 전체에서 \n 개수를 세어서 줄 수만 파악하고 나면 아주 손쉽게 구현 가능한 처리이다.
그러니 왜 지원을 안 하는지가 몹시 의문이다.
공교롭게도 운영체제의 컨트롤들 중에 static text는 DrawText의 기능을 사용해서 그런지 multiline 상태에서 세로로 중앙이나 아래 정렬을 하는 옵션이 없다.
그러나 버튼(push, radio, check 모두)들은 그런 옵션이 있다.
2.
아마 이건 예전에 의견을 한번 피력한 적이 있는데 다시 적자면..
본인은 선에 안티앨리어싱을 해서 그리는 기능 정도는 그냥 Pen 관련 GDI 함수/구조체에다가도 스타일로 추가해서 지원을 좀 해 줬으면 하는 생각을 한다. PS_SMOOTH 정도로..;;
마치 Cleartype이 적용된 글자를 찍기 위해 생소한 API를 굳이 사용할 필요가 없는 것처럼 말이다. 그냥기존 LOGFONT 구조체의 lfQuality에 새로운 값이 추가되는 걸로 훌륭하게 잘 구현되지 않았던가.
21세기 초에 야심차게 도입됐던 GDI+는 하드웨어 가속 버프도 없이 거의 버림받은 신세가 됐고, Direct2D는 COM을 사용하는 등 API 패러다임이 너무 다르다.
하지만 GDI는 유구한 역사를 자랑하는 Windows의 창립 멤버 API이고 이제 와서 도저히 버릴 수가 없는 압도적인 짬밥을 보유하고 있으니.. 그냥 유지보수 차원에서만 지원되는 legacy가 돼 버렸고 GDI API에 근본적인 확장은 없을 것으로 생각된다.
3.
유니코드 UTF16 문자열과 여타 8바이트 기반 인코딩(UTF8 포함) 사이를 변환하는 API 함수는 잘 알다시피 WideCharToMultiByte와 MultiByteToWideChar이다.
얘는 Windows NT가 유니코드+2바이트 wide char 기반으로 통 크게 설계되었을 때부터 역사를 함께 해 왔다. 옛날에 Windows 3.x에다가 Win32s를 설치하면 단순히 32비트 커널+썽킹 코드뿐만 아니라 코드 페이지 변환 테이블도 잔뜩 설치되었다. 32비트 EXE/DLL은 리소스의 내부 포맷부터가 유니코드인 관계로, 이들을 당장 변환할 수 있어야 하기 때문이다.
유니코드에서 여타 인코딩으로 변환하는 것은 마치 double에서 short로의 형변환처럼 큰 집합에서 작은 집합으로 이동하는 변환이다. 그러니 인코딩에 존재하지 않는 문자는 ? 같은 default 문자로 치환된다.
그런데, 별도의 플래그가 없다면 WideChar... 함수는 약간의 '유도리'를 발휘하여 동작한다. 여러 유니코드 문자가 한 여타 인코딩으로 변환될 수 있다는 뜻이다.
예를 들어, 유니코드를 KS X 1001로 변환한다고 치면, 원래 거기에 있던 호환용 한글 자모 ㄱ(U+3131)만 0xA4, 0xA1로 바꾸는 게 아니라 표준 한글 자모 영역에 있는 U+1100(초성 ㄱ)과 U+11A8(종성 ㄱ)까지 다 호환용 한글 자모 ㄱ으로 바꾼다는 뜻이다. ?로 바꾸지 않는다.
이런 예가 호환용 한글 자모나 일부 유럽 문자에 대해서 더 존재한다. 유럽 문자라 함은, 대문자 버전이 존재하지 않을 경우 그냥 소문자 버전으로 바꾸는 식이다.
이런 동작을 원하지 않고 엄밀하게 변환을 하고 싶다면 WC_NO_BEST_FIT_CHARS라는 플래그를 반드시 줘야 한다. 얘는 변환된 타 인코딩을 유니코드로 역변환했을 때 원래의 유니코드로 정보가 유지되지 않는다면 무조건 ?로 바꾼다. 즉, U+11??대의 표준 한글 자모는 호환용 한글 자모로 바뀌지 않는다. 이 옵션은 Windows NT4에도 존재하지 않으며, 98/2000부터 새로 추가된 얼마 안 되는 기능이다.
어느 방식을 사용할지는 그야말로 상황에 따라 다르다. 문자열을 복사하는 함수만 해도 버퍼 크기가 초과되었을 때 그냥 뒷부분을 융통성 있게 잘라 버려도 괜찮은 경우가 있는가 하면, 반드시 정확도가 보장되어야 해서 차라리 예외가 발생해야 하는 경우도 있을 수 있으니 말이다.
한편, 여타 인코딩에서 유니코드로 바꾸는 경우는 작은 집합에서 큰 집합으로 가는 것이니 일단은 유니코드에 대응하지 못하는 문자 걱정은 없다.
하지만 아무래도 여러 바이트가 한 글자를 구성하다 보니 정규화가 잘못되어서 해당 인코딩에 해당하지 않고 유니코드로 변환 자체가 될 수 없는 바이트 나열이 들어있을 수 있다. 이 경우는 유니코드로 변환했다가 다시 그 인코딩으로 역변환을 했을 때 바이트 나열이 원래대로 돌아올 수가 없게 된다.
이런 일이 발생했는지를 엄격하게 체크하려면 Multi... 함수에다 MB_ERR_INVALID_CHARS 플래그를 주면 된다.
<날개셋> 편집기는 이 두 경우를 모두 체크하여 불러오기가 제대로 되지 않았을 때, 혹은 저장과 함께 정보가 소실될 우려가 있을 때 경고 메시지가 나온다.
저장이야 UTF8 내지 UTF16 같은 유니코드 계열 인코딩만 골라 주면 문제가 없지만, 불러오기 자체가 문제가 있었다면 그 어떤 인코딩을 쓰더라도 다시 저장하는 순간 정보 소실이 생기기 때문이다.
4.
다음으로, 우클릭 메뉴를 구현할 때 즐겨 쓰이는 TrackPopupMenu(Ex) 함수에 대해서도 좀 한 마디 하겠다.
사실 얘는 굳이 임의의 지점을 우클릭했을 때 외에도, 어떤 버튼을 눌렀을 때 메뉴가 튀어나오게 하는 용도로도 많이 쓰인다. 그래서 Ex 버전에서는, 메뉴가 상하좌우 좀 치우친 곳에서 튀어나와서 위치 보정이 필요하더라도, 그 버튼 영역은 메뉴에 의해 가려지지 않게 하는 유용한 옵션이 추가되었다.
윈도 Vista 이상에서부터는 버튼의 오른쪽 끝에 ▼라는 split 버튼을 넣는 옵션이 추가된 관계로, 팝업 메뉴는 이 UI와 연동되어 즐겨 사용된다. 본인이 개발하는 <날개셋> 한글 입력기의 제어판 UI에도 물론 적극 활용되었다.
그런데 그건 그렇고.. 본인이 이 함수에 대해서 좀 이해가 안 되는 면모는 크게 두 가지이다.
얘는 HWND를 하나 인자로 받는다. 사용자가 메뉴를 ESC로 취소하지 않고 뭔가 항목을 선택하면 그 명령 ID가 부모 윈도우에다가 WM_COMMAND의 형태로 전달된다. 이것은 일단은 팝업 메뉴 말고도 단축키 내지 프로그램 창에 기본으로 딸린 메뉴를 선택했을 때와 동작의 일관성을 맞추기 위한 조치이다.
그러나 그렇게 하지 말고 사용자가 선택한 명령 ID가 그냥 함수의 리턴값으로 바로 오게 할 수도 있다. DLL 같은 걸 만들기 때문에 응용 프로그램의 기본 메뉴 연계 따위를 생각 안 하는 환경에서는 이런 디자인이 훨씬 더 유용하다. 그래서 이때는 flag에다가 TPM_RETURNCMD를 주면 된다.
사소해 보이는 팝업 메뉴의 디자인도 이렇게 두 양상으로 나누어 생각할 수가 있는 것이다.
마우스의 드래그 드롭 동작을 각 WM_LBUTTONDOWN, WM_MOUSE, WM_LBUTTONUP 핸들러 함수에다 제각기 따로 처리할지, 아니면 WM_LBUTTONDOWN 안에다가 또 message loop을 만들어서 한 함수 안에다가 다 집어넣을지의 차이와 비슷한 맥락이다.
아무튼, 메뉴에서 TPM_RETURNCMD에 대해, MSDN에는 "determine the user selection without having to set up a parent window for the menu."라는 문장까지 버젓이 있는데..
그럼에도 불구하고 TPM_RETURNCMD가 있더라도 HWND hParent의 값은 어떤 경우에도 NULL이어서는 안 된다. 심지어 자신이 만들지 않은 다른 윈도우(데스크톱 전체 윈도우 같은)를 줘도 안 되고 동작이 실패한다.
WM_COMMAND를 안 받으면 이 윈도우는 정말 레알 천하에 필요하지 않은데도 말이다. 애초에 메뉴가 튀어나오는 좌표도 언제나 화면 좌표이지 부모 윈도우 같은 걸 받지도 않는다. 그래도 이 윈도우는 없으면 안 된다.
그래서 <날개셋> 한글 입력기는 부득이하게 화면에 표시도 안 되는 message-only 윈도우를 간단히 만들어서 이걸 셔틀로 삼아 메뉴를 띄운 뒤, 메뉴가 사라지자마자 그 윈도우를 메시지 펌핑 하나 안 하고 파괴해 버리는 꼼수를 불가피하게 쓴 부분도 있다. 순전히 삽질이다.
이게 한 가지이고, 다른 하나는.. TPM_NONOTIFY라는 플래그는 왜 있느냐는 것이다. TPM_RETURNCMD 플래그가 있으면 명령 ID는 리턴값으로 오고 WM_COMMAND가 가지 않아서 이미 no notify의 효과가 나는데 저 플래그가 또 하는 일이 무엇인지 MSDN만 봐서는, 또 내 직관과 경험만으로는 모르겠다. 알 수 없는 노릇이다.
5.
인터넷에서 갓 다운로드한 파일은 운영체제가 뭔가 좀 다르게 취급한다는 걸 컴퓨터(일단은 Windows 기준으로) 사용자라면 경험적으로 다들 아실 것이다.
Word나 Excel 같은 프로그램에서 문서를 열면 "이 문서는 인터넷에서 가져온 것이기 때문에 위험할 수 있다. 매크로를 기본적으로 꺼 놨다" 이런 꼬리표가 붙는다. msi나 exe는 잠재적인 범죄자로 취급되며, 특히 디지털 서명 같은 게 없으면 다루기가 정말 까다로워져 있다.
먼 옛날 2000년대 중반엔 Windows XP에 보안 업데이트가 행해져서 이렇게 '인터넷 다운로드'로 분류돼 있는 CHM(컴파일된 HTML)은 아예 화면에 표시가 되지 않게 됐다. 파일 속성을 들어가서 '차단 해제'를 해 줘야만 이들 파일도 일반 파일들과 동등하게 다룰 수 있게 된다.
(XP도 초창기엔 읽기 전용 매체인 CD도 아니고 USB 메모리가 autorun.inf 실행이 됐을 정도로 UI 차원에서의 보안이 굉장히 막장이긴 했다. 이것도 다 훗날 보안 업데이트를 통해 막혔음.)
그나저나 저런 '다운로드 파일' 보안 속성은 운영체제 내부에서는 어떻게 구현되어 있을까?
가장 간단하게 생각할 수 있는 방법은 도스 시설부터 존재했던 파일 속성이다. 일명 ARHS(기록, 읽기 전용, 숨김, 시스템)의 형태로 존재하던 것 말이다. 실제로 Windows에는 이것 말고도 압축/암호화 등 내부적으로 쓰이는 속성이 더 있다.
하지만 다운로드 속성은 그런 비트 형태의 속성으로 구현되어 있지는 않다.
바로 파일 시스템 차원에서 제공되는 대체 데이터 스트림이 해당 파일에 꼬리표처럼 붙는데 거기에 있는 zone identifier가 이 파일이 인터넷에서 왔음을 나타낸다.
대체 데이터 스트림은 당장 내 컴퓨터의 다운로드 디렉터리에서 DIR /r을 하면 정체를 확인할 수 있다. 내 기억이 맞다면 CreateFile 함수로 저 대체 스트림의 내용을 바로 확인할 수도 있으며, IZoneIdentifier 인터페이스 등을 얻어서 이것을 조작할 수도 있다. 물론 저 꼬리표를 제거하는 것도 포함해서 말이다. 자세한 방법 소개는 The Old New Thing 블로그 내용을 링크하는 것으로 대체하겠다.
이런 기능이 과거의 FAT 계열 파일 시스템에서 가능했을 것 같지는 않고.. 언제 도입되었는지는 잘 모르겠다. 최소한 Windows 9x 시절의 IE 6 미만에는 없었던 것 같다.
Posted by 사무엘