UWP 앱이 실행 중인 Windows의 버전 확인하기

ver 명령으로 표시되는 '10.0.16299.309'가 UWP 앱 내부에서 얻을 수 있는 시스템의 Windows 버전 유형입니다.

프로그램 내부에서 현재 실행 중인 운영체제의 버전을 알아내야 하는 경우가 가끔 있기 마련입니다. 앱 실행 로그를 작성한다든지, 크래쉬 리포트를 작성한다든지와 같은 진단 기능에 주로 필요하지요. UWP 애플리케이션에서도 현재 실행 중인 Windows 10의 버전을 확인할 수 있습니다.

Windows.System.Profile.AnalyticsInfo.versionInfo.deviceFamilyVersion 를 사용하여 현재 시스템의 버전을 확인합니다.

현재 Windows 버전이 10.0.16299.309일 경우, deviceFamilyVersion에는 "2814750835278133"이라는 String이 들어 있습니다. Windows에서 UWP에 제공하는 버전명은 사람이 보고 바로 이해할 수 있는 버전명이 아니므로 직접 변환해줘야 합니다. 이 숫자는 무엇일까요?

계산기를 실행하고 프로그래머 모드로 전환합시다. deviceFamilyVersion에서 얻은 값 2814750835278133을 10진수로 다루면, 16진수로 변환했을 때 4개의 단위로 나눌 수 있는(4개의 16비트로 쪼갤 수 있는) 64비트의 숫자를 얻을 수 있습니다. 여기서는 A 0000 3FAB 0135가 나왔습니다. 각각 4자리별로 10진수로 다시 계산해 봅시다. 첫째 자리는 10, 둘째 자리는 0, 셋째 자리는 16299, 넷째 자리는 309가 됩니다. 각 자리 사이에 온점을 넣어 붙이면 10.0.16299.309가 됩니다. 현재 Windows의 버전이군요!

이제 4자리들에 각각 비트 연산자 AND (&)를 통해 해당 자릿수만 추출한 후 오른쪽 쉬프트 연산을 해서 10진수로 변환하면 됩니다. 여기에서 어떻게 하는지 자세하게 확인해볼 수 있습니다.

 

자바스크립트는 좀 다르게 계산해야 된다고요?

C++나 C#, VB를 기반으로 하는 환경에서는 위에 링크된 페이지에서 설명된 대로 비트 연산을 하면 됩니다. JavaScript로도 똑같이 비트 연산을 할 수 있지만 예상된 결과가 나오지 않을 겁니다. 첫째 자리를 (version & 0xFFFF000000000000) >> 48 으로 계산할 때, 10이 나와야 하지만 0이 나옵니다. 나머지 자리는 제대로 계산이 되는데, 왜 첫째 자리는 10 대신 0이 나오는 걸까요?

임의의 버전 숫자 (10.7.16299.309)를 만들어 10진수로 변환(2814780900049205)하고, 이 수를 자바스크립트에서 쉬프트 연산을 해봤습니다. 첫째 자리(48~63비트)와 둘째 자리(32~47비트)는 각각 10과 7이 나와야 했으나 0이 나왔고, 반면 셋째 자리(16~31비트)는 정상적으로 16299가 나온 것을 확인할 수 있습니다.

아시다시피 자바스크립트는 숫자(Number)에 대한 다양한 종류 없이 한 가지 종류만 있습니다. 모든 숫자는 64비트 배정도 부동소수점(64 bit double precision floating point number)으로 메모리에 저장되는데, 이 중 0~51비트에 가수(fraction), 52~62비트에 지수(exponent), 마지막 64비트 자리에는 부호(sign)이 저장됩니다. 따라서 자바스크립트에서는 올바른 계산을 할 수 있는 최대의 정수를 0비트에서 52비트까지를 1로 채운 값인 절댓값 9007199254740991(001F FFFF FFFF FFFF)으로 하고 있습니다. 이 숫자를 넘어서는 계산은 예상치 못한 결과가 나올 수 있습니다.

자바스크립트는 0비트부터 52비트까지를 가수를 담기 위한 공간으로만 사용하므로, 비트 단위 연산을 하게 될 경우 32비트 숫자로 내려서 계산하고 32비트보다 더 큰 수를 표현하는 숫자는 계산되지 않습니다. 위의 이미지에서 이 현상이 드러나고 있습니다. 비트 단위 연산에서 32비트 숫자보다 더 큰 수를 계산해야 할 때에는 다음과 같은 우회 방법을 사용해야 합니다.

JavaScript
var current = Windows.System.Profile.AnalyticsInfo.versionInfo.deviceFamilyVersion;
var currentUlong = parseInt(current).toString(16),
    bit00 = parseInt(currentUlong.slice(currentUlong.length - 4), 16),
    bit16 = parseInt(currentUlong.slice(currentUlong.length - 8, currentUlong.length - 4), 16),
    bit32 = parseInt(currentUlong.slice(currentUlong.length - 12, currentUlong.length - 8), 16),
    bit48 = parseInt(currentUlong.slice(0, currentUlong.length - 12), 16);
return [bit48, bit32, bit16, bit00].join(".");

위와 같이 계산하면 잃는 정보 없이 온전히 변환하여 "10.0.16299.309"와 같은 버전명의 String을 받아낼 수 있습니다. 첫 줄부터 차근차근 짚어 봅시다.

우선 current에 WinRT API로 제공된 윈도우의 버전명을 등록합니다. 이 버전명은 Number가 아닌 String으로 되어 있으므로 먼저 parseInt(current)로 10진수의 숫자 형태로 바꾼 후에 16진수 형태로 표현된 String으로 currentUlong이라는 변수에 저장합니다. 윈도우 버전이 10.0.16299.309일 경우, currentUlong에는 "a00003fab0173"와 같은 String이 들어 있게 됩니다.

그 다음으로 bit00, bit16, bit32, bit48 이라는 네 개의 임의의 변수를 새로 만듭니다.

  • bit00에는 currentUlong의 1번째 문자에서부터 4번째 문자까지의 문자열을 가져온((version & 0xFFFF000000000000) >> 48과 같은 작업) String을 16진수 형태의 Number로 변환한 값을 저장합니다.
  • bit16에는 currentUlong의 5번째 문자에서부터 8번째 문자까지의 문자열을 가져온((version & 0x0000FFFF00000000) >> 32와 같은 작업) String을 16진수 형태의 Number로 변환한 값을 저장합니다.
  • bit32에는 currentUlong의 9번째 문자에서부터 12번째 문자까지의 문자열을 가져온((version & 0x00000000FFFF0000) >> 16과 같은 작업) String을 16진수 형태의 Number로 변환한 값을 저장합니다.
  • bit48에는 currentUlong의 13번째 문자에서부터 16번째 문자까지의 문자열을 가져온(version & 0x00000000FFFF와 같은 작업) String을 16진수 형태의 Number로 변환한 값을 저장합니다.

마지막 줄에서는 bit48, bit32, bit16, bit00의 순서대로 들은 새로운 Array를 만들고 각각 사이에 "."으로 연결된 하나의 String이 반환됩니다. 이제 반환된 버전명으로 필요한 작업에 사용하면 됩니다.