반응형

 

전통적으로(?) Visual C++에서 윈도우의 버전 번호를 읽는 함수는 GetVersion() / GetVersionEx() 였다.

그런데, MS는 윈도우 8.1을 출시하면서 이 함수의 기능을 OS의 버전 대신 manifest 정보를 리턴하도록 변경했다.

 

MS의 입장[각주:1]은 알겠지만, 많은 개발자들에게 이 수정은 커다란 혼란을 가져왔고, 여전히 이해가 어려운 게 사실이다.

이에 다양한 버전 번호를 읽는 방식들과 동작 결과 등을 간략하게 정리한다.

 

아래의 실행 환경은 모두 윈도우 10 Pro (x64)임을 미리 얘기해둔다.

 

Windows 10 Pro, x64 기반 프로세서

 

1. GetVersionEx()

 

OS의 버전을 읽는 방식은 앞에서 기술한 GetVersionEx()가 기본적이었다.

하지만, 이제 deprecated 되었기 때문에 사용하려면 아래와 같이 손을 좀 대야 한다.

 

#pragma warning(push)
#pragma warning(disable : 4996)
	GetVersionEx(&osver);
#pragma warning(pop)

 

 

2. GetFileVersionInfoExW()

 

이렇게 OS에서 직접 읽어오는 방식엔 한계가 있어 다음으로 대두된 방식은 시스템 파일의 파일 버전을 읽는 것이다.

kernel32.dll의 파일 버전을 읽는 코드는 다음과 같다.

 

WCHAR path[_MAX_PATH];
GetSystemDirectoryW(path, _MAX_PATH);
wcscat_s(path, L"\\kernel32.dll");

DWORD handle;
DWORD len = GetFileVersionInfoSizeExW(FILE_VER_GET_NEUTRAL, path, &handle);

BYTE* buff = new BYTE[len];
GetFileVersionInfoExW(FILE_VER_GET_NEUTRAL, path, 0, len, (LPVOID)buff);

VS_FIXEDFILEINFO* vInfo = nullptr;
UINT infoSize;

if (!VerQueryValueW(buff, L"\\", reinterpret_cast<LPVOID*>(&vInfo), &infoSize) || !infoSize) {
	SetDlgItemText(IDC_EDIT2, _T("error!"));
}
else {
	CString cs;
	cs.Format(_T("%u.%u.%u.%u"),
		HIWORD(vInfo->dwFileVersionMS),
		LOWORD(vInfo->dwFileVersionMS),
		HIWORD(vInfo->dwFileVersionLS),
		LOWORD(vInfo->dwFileVersionLS));

	SetDlgItemText(IDC_EDIT2, (LPCTSTR)cs);
}
delete[]buff;

 

 

3. 레지스트리

 

레지스트리에는 윈도우 버전을 포함한 시스템의 정보들이 저장되어 있다.

아래 코드를 사용하면 레지스트리에서 간단히 시스템 정보들을 읽어올 수 있다.

 

HKEY hkey;
if (RegOpenKey(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), &hkey) == ERROR_SUCCESS) {
	static TCHAR result[MAX_PATH];
	DWORD dwType;

	DWORD dwLen = MAX_PATH;
	if ((RegQueryValueEx(hkey, _T("CurrentVersion"), NULL, &dwType, reinterpret_cast<LPBYTE>(result), &dwLen) == ERROR_SUCCESS) &&
		(dwType == REG_SZ)) {

		CString csVer(result);

		dwLen = MAX_PATH;
		if ((RegQueryValueEx(hkey, _T("CurrentBuild"), NULL, &dwType, reinterpret_cast<LPBYTE>(result), &dwLen) == ERROR_SUCCESS) &&
			(dwType == REG_SZ)) {
			csVer.AppendFormat(_T(".%s"), result);
		}
        
		SetDlgItemText(IDC_EDIT3, csVer);
	}

	dwLen = MAX_PATH;
	if ((RegQueryValueEx(hkey, _T("ProductName"), NULL, &dwType, reinterpret_cast<LPBYTE>(result), &dwLen) == ERROR_SUCCESS) &&
		(dwType == REG_SZ)) {
		SetDlgItemText(IDC_EDIT4, result);
	}

	RegCloseKey(hkey);
}

 

 

4. RtlGetVersion()

 

아래 결과에서 다시 얘기하겠지만, 위의 방식들만으로는 OS의 버전을 손쉽게 읽어오는 것이 불가능하다.

별도로 manifest를 생성하여 지정해야만 올바른 결과를 얻을 수 있다.

 

ntdll.dll에 포함된 RtlGetVersion() 함수는 커널 모드의 GetVersionEx() 정도로 보면 된다.

이 함수를 이용하면 manifest 생성 없이도 정확한 OS의 버전을 읽어올 수 있다.

대략 아래와 같이 하면 된다.

 

typedef NTSTATUS(WINAPI* LPFN_RTLGETVER) (LPOSVERSIONINFOEXW);
LPFN_RTLGETVER fnRtlGetVer;

HMODULE h = GetModuleHandle(_T("ntdll"));
fnRtlGetVer = (LPFN_RTLGETVER)GetProcAddress(h, "RtlGetVersion");
if (fnRtlGetVer) {
	OSVERSIONINFOEXW osInfo;

	osInfo.dwOSVersionInfoSize = sizeof(osInfo);
	fnRtlGetVer(&osInfo);

	CString cs;
	cs.Format(_T("%lu.%lu.%lu"), osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber);

	SetDlgItemText(IDC_EDIT18, cs);
}

 

 

5. IsWindows*()

 

전술했듯이, 윈도우 8.1 출시와 함께 버전 번호를 읽고 비교하는 방식이 완전히 바뀌었다.

MS는 버전 번호를 좀 더 명확하게 확인할 수 있도록 IsWindows*() 함수들을 만들었다.

 

이를 통해서는 버전 번호을 읽어오는 대신, 원하는 OS 환경인지를 확인할 수 있다.

 


 

앞에서 기술한 방식들을 모두 구현한 결과는 아래와 같다.

Visual Studio 2019로 만들었으며, manifest를 추가로 만들지 않고 기본 설정만으로 돌린 결과이다.

 

 

충격과 공포다! 그지 깽깽이들아!

 

일단 GetVersionEx()의 결과는 6.2 즉, Windows 8이라고 나온다.

빌드 넘버 역시 옛날에 보던 그 빌드 넘버이다.

이 점은 MS에서 명확하게 기술한 내용에 부합한다.

 

재미있는 것은, kernel32.dll 파일의 정보를 읽어도 6.2로 나온다는 점이다.

실제로 이 파일의 정보를 들여다보면 아래와 같이 10.0.19041.292로 나온다.

이걸 윈도우는 꼼꼼하게도 6.2.19041.292[각주:2]로 알려주는 것이다.

 

 

레지스트리에는 버전 번호가 6.3으로 저장되어 있다.

윈도우 8.1의 버전 번호와 윈도우 10의 빌드 넘버/제품명이 동시에 기록되어 있다는 것이 좀 웃프다...

 

IsWindow*() 함수들의 결과도 앞의 결과들과 동일하다.

 

결국, 윈도우의 버전 번호를 있는 그대로 보여주는 함수는 RtlGetVersion() 하나밖에 없다는 뜻이다.

 


 

같은 소스 코드를 32비트 환경에서도 돌려봤다.

 

Windows 10 Enterprise라굽쇼?

 

64비트 환경과 대동소이 하지만, 윈도우 제품명은 좀 충격이다.

Windows 10 Enterprise...

아니, 이거 오류 아닌가... 아니면, 내가 모르는 다른 이유가 있는 건가...

 


 

MS가 설계한 방식 정확히 따르려면 다음과 같은 내용의 manifest 파일을 생성하여 이를 추가하면 된다.

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
	<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
		<application>
			<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> <!-- Vista -->
			<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> <!-- 7 -->
			<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> <!-- 8 -->
			<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> <!-- 8.1 -->
			<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> <!-- 10 -->
		</application>
	</compatibility>
</assembly>

 

이를 적용한 결과는 아래와 같다.

 

돌아온 10.0.19041.292

 

 

  1. 호환성 문제 해결을 위해 상황이 복잡해졌으며, 이를 정리하기 위한 수단임 [본문으로]
  2. 근데, 왜 6.2.9200이 아닌 거냐... [본문으로]
반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band