※ 프로세서 정보 얻기
현재 컴퓨터의 CPU 아키텍처 종류를 얻는 대표적인 함수는 GetSystemInfo이다. SYSTEM_INFO 구조체의 wProcessorArchitecture 멤버의 값을 확인하면 된다.
그런데, 64비트 컴퓨터에서 64비트 운영체제를 실행하고 있더라도 32비트 프로그램은 언제나 이 값이 0, 즉 32비트 x86이 돌아온다. 이는 호환성 차원에서 취해진 조치이다. 기존의 32비트 x86용 프로그램은 새로운 API를 쓰지 않으면 자신이 64비트 x64에서 돌아가고 있다는 걸 까맣게 모르며, 전혀 알 수 없다.
자신이 돌아가고 있는 환경이 진짜 x64인지 확인하려면 GetNativeSystemInfo라는 새로운 함수를 써야 한다. 이건 Windows가 최초로 x64 플랫폼을 지원하기 시작한 윈도우 XP에서 추가되었다. 이 함수가 존재하지 않는 운영체제라면 당연히 64비트 환경이 아니다.
64비트 프로그램이라면 그냥 기존의 GetSystemInfo만 써도 x64를 의미하는 9가 돌아온다. GetNativeSystemInfo는 동일한 코드가 32비트와 64비트로 컴파일되더라도 모두 정확한 동작을 보장한다는 차이가 존재할 뿐이다.
또한, 같은 64비트라도 아이테니엄(IA64) 환경에서는 기존 GetSystemInfo도 32비트 x86 프로그램에서 아키텍처를 x86이라고 속이지 않고 정확하게 IA64라고 알려 준다. 왜냐하면 IA64는 x86과는 명백하게 다른 환경이기 때문에 다르다는 걸 알려 줄 필요가 있기 때문이다. 뭐, 지금은 IA64는 완전히 망했기 때문에 일반인이 접할 일이 없겠지만 말이다.
※ 시스템 메모리 정보 얻기
메모리 양을 얻는 전통적인 함수는 GlobalMemoryStatus이다.
그러나 32비트 프로그램이라도 현재 컴이 64비트 운영체제를 사용하여 램이 4GB보다 많이 있는 걸 제대로 감지해서 표시하려면, 윈도우 2000에서 새로 추가된 GlobalMemoryStatusEx 함수를 써야 한다.
그리고 빌드되는 실행 파일의 헤더에 large address aware 플래그가 켜져 있어야 한다. 비주얼 C++ 기준 Linker → System → Enable Large Addresses를 yes로 지정해 주면 된다. 64비트 플랫폼에서는 이 값이 기본적으로 yes이지만, 32비트 플랫폼에서는 기본값이 no이다.
large address aware이 켜져 있지 않으면 32비트에서는 사용 가능한 가상 메모리가 아예 4GB가 아닌 2GB로 반토막이 난 채 표시된다. 포인터의 최상위 1비트를 비워 준다.
그리고 64비트 바이너리에 대해서는 사용 가능한 가상 메모리의 양이야 언제나 있는 그대로 운영체제가 알려 주지만, 해당 바이너리에 이 플래그가 없으면, 운영체제는 아예 상위 32비트를 비워 줘서 DLL 같은 걸 LoadLibrary해도 언제나 32비트 영역 안에서만 주소를 잡는다. 포인터까지 4바이트짜리 int와 구분 없이 작성된 구식 코드들의 64비트 포팅을 수월하게 해 주기 위한 조치이다.
참고로 64비트 전용 프로그램이라면 Ex 대신 기존의 GlobalMemoryStatus만 써도 괜찮다. 받아들이는 구조체의 크기가 int가 아니라 SIZE_T이기 때문에, 32비트 플랫폼에서는 32비트이지만 64비트 플랫폼에서는 자동으로 64비트가 설정되기 때문이다. Ex 함수는 플랫폼의 비트 수에 관계없이 숫자의 크기가 언제나 64비트 크기를 보장해 줄 뿐이다.
※ 32비트 프로그램이 지금 내가 64비트 운영체제에서 동작하고 있는지 감지하기
딱 그 목적을 위해 IsWow64Process라는 함수가 있다. 이것 역시 윈도우 XP 이상에서 추가되었다.
※ 윈도우 시스템 디렉터리에 접근하기
64비트 운영체제는 잘 알다시피 시스템 디렉터리가 64비트용과 32비트용으로 두 개 존재한다.
32비트와 64비트 프로그램에 관계없이 GetSystemDirectory는 언제나 C:\Windows\system32를 되돌린다.
그리고 윈도우 XP에서 추가된 GetSystemWow64Directory라는 함수가 있어서 역시 32비트와 64비트에 관계없이 C:\Windows\SysWow64를 되돌린다. 다만, 운영체제 자체가 64비트가 아닌 32비트 에디션이라면, 후자의 함수는 에러를 리턴한다.
그러니 의외로 이 함수는 플랫폼에 관계없이 절대적으로 같은 결과를 되돌리는 듯한데, 문제는 64비트 운영체제는 32비트 프로그램에 대해 시스템 디렉터리를 기본적으로 redirection한다는 것이다. 즉, 64비트 운영체제는 32비트 프로그램이 C:\Windows\System32를 요청한다고 해도 SysWow64의 내용을 보여주지 진짜 64비트용 시스템 디렉터리의 내용을 보여주지 않는다.
만약 32비트 기반으로 응용 프로그램 설치 관리자나 파일 유틸리티 같은 걸 만들 생각이어서 진짜로 64비트 시스템 디렉터리에 접근을 하고 싶다면, 운영체제에다 별도의 함수를 호출해서 요청을 해야 한다. 그래서 처음에는 Wow64EnableWow64FsRedirection라는 함수가 추가되었다. 이걸로 잠시 예외 요청을 한 뒤, 내가 할 일이 끝난 뒤엔 다시 설정을 원상복귀해야 했다. 왜냐하면 64비트 시스템 디렉터리에 접근 가능하게 해 놓은 예외 동작을 그대로 방치하면, 나중에 다른 32비트 모듈들이 32비트 시스템 디렉터리에 접근하지 못하게 되기 때문이다.
그런데 MS에서는 함수 디자인을 저렇게 한 것을 후회하고, 위의 함수의 기능을 Wow64DisableWow64FsRedirection과 Wow64RevertWow64FsRedirection 쌍으로 대체한다고 밝혔다. MSDN을 읽어 보면 알겠지만, 64비트 접근 여부 설정치를 마치 stack처럼 다단계로 저장했다가 다시 원상복귀를 더 쉽게 할 수 있게 만들려는 의도이다.
※ Program Files 디렉터리에 접근하기
64비트 운영체제는 응용 프로그램 디렉터리도 64비트용과 32비트용이 두 개 존재한다.
운영체제가 사용하는 특수 디렉터리의 위치를 얻어 오는 함수의 원조는 SHGetSpecialFolderPath이며, 이것은 윈도우 운영체제의 셸의 구조가 크게 바뀌었던 인터넷 익스플로러 4 시절에 처음 도입되었다. 그때는 특수 디렉터리들을 CSIDL이라는 그냥 정수 ID로 식별했다.
그랬는데 윈도우 비스타부터는 이 함수의 역할을 대체하는 SHGetKnownFolderPath라는 함수가 추가되었고, 이제는 식별자가 아예 128비트짜리 GUID로 바뀌었다. 문자열 버퍼도 구닥다리 260자짜리 고정 배열 포인터를 받는 게 아니라, 깔끔하게 별도의 동적 할당 형태가 되었다.
64비트 운영체제에서 64비트 프로그램은 64비트와 32비트용 Program Files 위치를 아주 쉽게 얻어 올 수 있다. 32비트를 가리키는 식별자가 따로 할당되어 있기 때문이다. 그러나 32비트 프로그램이 64비트 운영체제의 64비트 위치를 얻는 것은 위의 두 함수로 가능하지 않다. SpecialFolder 함수는 64비트만을 가리키는 식별자 자체가 없으며, KnownFolder함수도 32비트 프로그램에서 FOLDERID_ProgramFilesX64 같은 64비트 식별자를 사용할 경우 에러만 돌아오기 때문이다.
32비트 프로그램이 64비트 Program Files 위치를 얻는 거의 유일한 공식적인 통로는 의외의 곳에 있다. 바로 환경변수이다.
::ExpandEnvironmentStrings(_T("%ProgramW6432%\\"), wt, 256);
위의 환경변수를 사용한 코드는 32비트와 64비트에서 동일하게 64비트용 Program Files 위치를 되돌려 준다.
결론
이렇듯 32비트에서 64비트로 넘어가면서 윈도우 API의 복잡도와 무질서도는 한층 더 높아졌음을 우리는 알 수 있다. 가능한 한 급격한 변화와 단절을 야기하지 않으면서 새로운 기능을 조심스럽게 추가하려다 보니 지저분해지는 건 어쩔 수 없는 귀결이다.
프로그램 배포 패키지를 32비트 exe 하나만 만들어서 64비트와 32비트 플랫폼에서 모두 쓸 수 있게 하면 좋을 것 같다. 32비트 플랫폼에서는 32비트 바이너리만 설치되고, 64비트 플랫폼에서는 비록 32비트 EXE라도 64비트 프로그램 디렉터리들을 모두 건드릴 수 있어야 한다. 그런 프로그램을 만들려면 이 글에서 언급된 테크닉들을 모두 알아야 할 것이다. 설치 프로그램이니 UAC 관리자 권한이 필요하다는 manifest flag도 내부적으로 넣어 주고 말이다.
아, 그러고 보니, 윈도우 9x 시절에는 시스템 디렉터리가 16비트와 32비트로 나뉘어 있지도 않았다. NT 계열로 와서야 system과 구분하기 위해서 system32가 별도로 생기긴 했지만, 16비트용 시스템 디렉터리의 위치를 얻는 별도의 API는 존재하지 않았으며, 사실 필요하지도 않았다. 16비트 프로세스는 이제 NTVDM 밑에서 돌아가는 완전 고립된 별세계로 전락했기 때문이다.
Posted by 사무엘