Windows Vista와 Windows 7의 Version checking

Windows Vista나 Windows 7에서 어플리케이션을 수행해 보면 프로그램이 정상적으로 수행되지 않고 조용히 실패(silent fail)해 버리거나 “윈도우 XP 이상의 운영체제에서만 수행될 수 있습니다.”와 같은 오류 메시지가 나타나는 경우가 있다. 이러한 문제의 대부분은 어플리케이션 내부에서 운영체제의 버전을 확인하는 루틴에서 문제가 있는 경우이다. 어플리케이션이 이와 같이 버전 정보 확인에 실패하는 이유는 크게 2가지로 나누어 볼 수 있다.

  • 버전 확인 코드의 결함(버그). 주 버전(major version)이 증가했음에도 부 버전(minor version)이 감소하였다는 이유로 실패한다거나(예. 버전이 5.1에서 6.0으로 변경되는 것과 같이) 최신의 운영체제가 설치되어 있음에도 이전 운영체제의 서비스 팩 설치 여부를 확인하는 경우(예. Windows XP SP2에서 Windows Viats SP1으로 버전이 변경된 경우)
  • 개발자가 테스트 해 보지 않은 운영체제에서 어플리케이션이 수행되지 않도록 의도적으로 제한한 경우(미래에 출시될 운영체제를 위해서라도 이처럼 어플리케이션의 수행을 제한해서는 안 된다.)

잘못된 버전 확인 코드로 인해 실제로는 호환되지 않는 운영체제에서 어플리케이션이 수행되면 보통의 경우 오류 메시지가 출력되고 이를 통해 그 원인을 추적할 수 있지만, 간혹 앞서 말한 바와 같이 아무런 동작도 수행되지 않고 프로세스가 조용이 종료 되어버리는 경우도 있다.

다음 절에서 버전 확인 과정에서 야기될 수 있는 호환성 문제를 피할 수 있는 방법과 운영체제의 버전을 확인하는 올바른 방법에 대해서 설명하고 최종적으로 운영체제의 버전을 확인하기 보다는 운영체제의 기능이 사용 가능한지를 확인하는 더 나은 대안을 제안할 것이다.

문제상황을 해결하는 방법

Windows Viata와 Windows 7는 버전 확인 과정에서 발생할 수 있는 문제 상황을 회피할 수 있는 두 가지 메커니즘을 제공하고 있다.

1. 호환성 모드(Compatibility Mode) : 호환성 모드는 일반 사용자가 호환성 문제를 쉽게 해결할 수 있도록 제공되는 방법으로, 이전 버전의 윈도우에서만 수행되도록 작성된 어플리케이션을 Windows Vista나 Windows 7에서 수행될 수 있도록 운영체제의 version을 반환하는 함수들이 이전 운영체제의 version 정보를 반환하도록 버전 속이기(Version lie)를 수행하도록 하는 방법이다. 프로그램의 속성(Properties) 에서 호환성(Compatibility) 탭을 통해서 운영체제 버전 속이기를 수행할 수 있다.

2. 어플리케이션 호환성 툴킷(ACT: Application Compatibility Toolkit) : IT 프로와 개발자들이 어플리케이션 호환성 문제를 확인할 수 있도록 다양한 도구를 포함하고 있는 툴킷이다. 이를 이용하면 다양한 호환성 문제를 확인할 수 있으며, 개발자가 “버전 속이기(Version lie)등과 같은 방법을 통해서 손쉽게 문제 상황을 회피할 수 있는 방법을 제공해 준다.

호환성 모드(Compatibility Mode)

호환성 모드는 다음과 같이 사용할 수 있다.

1. 실행파일이나, 바로가기에서 오른 마우스를 클릭한다.
2. 속성(Properties)를 클릭한다.
3. 호환성(Compatibility) 탭을 클릭한다.
4. 이 프로그램을 실행할 호환 모드(Run this program in compatibility mode for)에서 어플리케이션이 수행 가능할 것으로 예상되는 운영체제 버전을 선택한다. 어플리케이션 여러 개의 실행파일로 구성되어 있는 경우 각각의 실행파일에 대해서 개별적으로 이러한 작업을 수행해 주어야 한다.

image 

5. 확인(OK)를 눌러 대화창을 닫는다.

주의: 호환성 모드는 .net framework 기반의 managed code로 개발된 어플리케이션이 Environment.OSVersion을 사용하여 운영체제의 버전을 얻어오거나 P/Invoke를 사용하여 Win32 함수(예. GetVersionEx)를 호출하는 경우에는 영향을 미치지 못한다. 하지만 어플리케이션이 native code와 managed code가 섞여 있는 경우라면 적용 해 볼만하다. 다행히도 native code에서 버전 확인을 시도하는 경우라면 호환성 모드가 정상적으로 적용될 것이기 때문이다.

어플리케이션 호환성 툴킷(ACT: Application Compatibility Toolkit)

1. ACT를 설치한 후 시작 메뉴에서 Compatibility Administrator를 수행한다.
2. 왼쪽 트리에 “Custom Database” 노드가 나타나지 않는다면 툴바에서 “New”를 선택한다.

image

3. “New Database”를 오른 마우스 클릭한다.
4. “Rename”을 선택하고 호환성 database의 이름을 변경한다.
5. 변경한 database의 이름을 오른 마우스로 클릭한 후 “Create New”를 선택 후, “Applicatio Fix”를 클릭하면, 다음과 같이 “Create new Applicatio Fix” 대화창이 나타난다.

image

6. 세부 내용을 입력한다.
7. Next를 클릭한다.

image

8. Operating System Modes에서 None을 선택한다.
9. Select additional compatibility modes에서 “WinXPSP2VersionLie”를 선택한다.
10. Next를 클릭한다.

image

11. 어플리케이션에 추가적인 수정이 필요한 경우 각각을 선택한다.
12. Next를 클릭한다.

image

13. 윈도우가 버전 속이기(Version Lie)를 수행할 실행 파일을 구분할 수 있도록 여러가지 조건들을 선택한다.
14. Finish를 클릭한다.
15. 툴바에서 Save 버튼을 클릭하여 compatibility database를 저장한다(확장자는 .sdb 이다).
16. 윈도우는 상당히 많은 프로그램에 대한 다양한 수정사항들을 포함하고 있는 compatibility database와 함께 출시된다. Database/Application 노드를 확장해 보면 그러한 정보를 살펴볼 수 있으며, 하부 노드를 클릭하여 어떤 어플리케이션에 어떠한 수정 사항들이 적용되고 있는지도 살펴볼 수 있다.

더 좋은 버전 확인 방법

현재 운영 중인 운영체제가 특정 기능을 제공하는지의 여부를 확인하기 위해서 운영체제의 버전 정보를 확인하는 것은 그다지 좋은 방법은 아니다. 그럼에도 불구하고 운영체제가 제공하는 특정 기능의 가용 여부를 확인하는 방법이 운영체제의 버전 정보를 확인하는 방법 외에는 달리 대안이 없다면 다음에 설명할 내용을 주의 깊게 살펴보기 바란다.

native 어플리케이션의 경우, 항상 새로운 운영체제에서도 정상적으로 어플리케이션이 수행될 수 있도록 작성되어야 한다. 운영체제의 버전이 변경되었다 하더라도 비정상적으로 어플리케이션 동작해서는 안 된다. 아래에 Win32의 GetVersionEx 함수를 호출하는 사용 예를 나타내었다. 이 코드는 주버전(major version)이 5보다 큰지(Windows Viata, Windows Server 2008 R2, Windows 7) 혹은 주버전이 5이고 부버전(minor version)이 1 이상(Windows XP나 Windows Server 2003)인지를 확인한다.

C++

void main()
{
OSVERSIONINFO osvi;
BOOL bIsWindowsXPorLater;

ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

GetVersionEx(&osvi);

bIsWindowsXPorLater =
( (osvi.dwMajorVersion > 5) ||
( (osvi.dwMajorVersion == 5) && (osvi.dwMinorVersion >= 1) ));

if(bIsWindowsXPorLater)
printf("The system meets the requirements.\n");
else printf("The system does not meet the requirements.\n");
}

다음은 VerifyVersionInfo 함수를 사용하여 최소 운영체제의 버전을 확인하는 코드의 예이다.


C++

#include <windows.h>

BOOL Is_WinXP_SP2_or_Later ()
{
OSVERSIONINFOEX osvi;
DWORDLONG dwlConditionMask = 0;
int op=VER_GREATER_EQUAL;

// Initialize the OSVERSIONINFOEX structure.

ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvi.dwMajorVersion = 5;
osvi.dwMinorVersion = 1;
osvi.wServicePackMajor = 2;
osvi.wServicePackMinor = 0;

// Initialize the condition mask.

VER_SET_CONDITION( dwlConditionMask, VER_MAJORVERSION, op );
VER_SET_CONDITION( dwlConditionMask, VER_MINORVERSION, op );
VER_SET_CONDITION( dwlConditionMask, VER_SERVICEPACKMAJOR, op );
VER_SET_CONDITION( dwlConditionMask, VER_SERVICEPACKMINOR, op );

// Perform the test.

return VerifyVersionInfo(
&osvi,
VER_MAJORVERSION | VER_MINORVERSION |
VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR,
dwlConditionMask);
}

.NET Framework 개발자라면 Environment.OSVersion.Version 속성 값을 Version 타입의 객체와 ==, !=, <=, <, >, >= 연산자를 이용하여 비교할 수 있다.


C#

// This code checks if the OS is at least Windows XP
if (Environment.OSVersion.Version < new Version(5, 1))
{
MessageBox.Show("Windows XP or later required.",
"Incompatible Operating System", MessageBoxButtons.OK,
MessageBoxIcon.Error);
return;
}

운영체제의 기능 확인

앞서 언급한 바와 같이 운영체제가 제공하는 기능의 가용여부를 확인하기 위해서 버전정보를 이용하는 것은 그다지 좋은 방법이 아니다. 왜냐하면 운영체제는 배포되는 DLL에 따라서 제공되는 기능이 가감될 수 있기 때문이다. 따라서 운영체제의 플랫폼과 버전 정보를 확인하는 함수인 GetVersionEx 함수를 사용하기 보다는 기능 자체가 지원되는지의 여부를 직접적으로 확인하는 것이 낫다. 예를 들어 Direct2D의 DirectWrite API와 Windows Vista에서 제공되는 Ribbon API를 사용할 계획이라고 하자. 설사 이러한 API를 사용하지 못하는 경우라 하더라도 어플리케이션 자체가 전혀 수행될 수 없다면 곤란하다.


비록 어플리케이션의 기능과 성능에 일부 제약이 있더라도 가능하다면 어플리케이션을 수행할 수 있도록 해주어야 할 것이다. Win32 개발자라면 이를 위해 다음과 같은 기법을 사용할 수 있다.


사용하려는 함수를 구현하고 있는 라이브러리가 어플리케이션의 가상 메모리 주소 공간에 로드되어 있지 않다면 LoadLibrary()를 사용하여 라이브러리를 로드하고, 앞서 로드되어 있는 경우라면(예 Kernel32.dll) GetModuleHandle() 함수를 이용하여 DLL의 핸들 값을 얻는다. 만일 LoadLibrary()나 GetModuleHandle() 함수가 NULL을 반환한다면 오류가 발생한 것이다.


GetProcAddress() 함수를 이용하여 함수 포인터를 획득한다. GetProcAddress()가 NULL을 반환하면 함수가 존재하지 않는 것이다. 만약 이 함수를 통해서 함수 포인터를 받아올 수 있다면 적절한 함수 원형을 가리키는 함수 포인터로 변환을 수행하면 된다. 그런데 몇몇 함수의 경우 함수 자체는 존재하지만, 함수 내부가 구현되지 않은 형태일 수도 있으며, 이 경우 구현되지 않음(not implemented) 오류가 발생할 수도 있으므로 이러한 오류도 반드시 확인해야 한다. 아래에 이러한 기법을 구현한 예를 나타내었다.


C++

// define function pointer type
typedef BOOL (WINAPI *SetWaitableTimerExProc)(
__in HANDLE hTimer,
__in const LARGE_INTEGER *lpDueTime,
__in LONG lPeriod,
__in PTIMERAPCROUTINE pfnCompletionRoutine,
__in LPVOID lpArgToCompletionRoutine,
__in PREASON_CONTEXT WakeContext,
__in ULONG TolerableDelay
);

LARGE_INTEGER liDueTime;
liDueTime.QuadPart = 0;
int period = 1000;
unsigned int tolerance = 1000;
HANDLE hTimer = // Get timer handle

REASON_CONTEXT reasonContext = {0};
reasonContext.Version = 0;
reasonContext.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING;
reasonContext.Reason.SimpleReasonString = L"MyTimer";

// Get module handle to a module which is already loaded
HMODULE hKernel32Module = GetModuleHandle(_T("kernel32.dll"));
if (hKernel32Module == NULL)
return FALSE;

// Get Address of function
SetWaitableTimerExProc pFnSetWaitableTimerEx =
(SetWaitableTimerExProc) ::GetProcAddress(hKernel32Module,
"SetWaitableTimerEx");

// Check if the function exists
if (pFnSetWaitableTimerEx == NULL)
return FALSE;

// Call function
if (!pFnSetWaitableTimerEx(hTimer, &liDueTime, period, NULL, NULL,
&reasonContext, tolerance)
{ /* handle error */ }

이와는 다른 방법으로 DLL 지연 로딩(delayed loading)을 이용한 후, __try…__except 블록 내에서 함수를 호출하는 방법을 사용할 수도 있다.(이에 대해서는 다음 링크를 확인하기 바란다. Linker Support for Delay-Loaded DLLs


만약 COM API를 사용하는 경우라면 CoCreateInstance와 QueryInterface함수의 반환 값에 대하여 오류 처리를 수행할 수 있으며, .NET framework 어플리케이션에서 P/Invoke를 이용하여 WIN32 API를 사용하는 경우라면 EntryPointNotFoundException과 DllNotFoundException에 대하여 예외 처리를 수행하면 된다.


참고



추가 자료


by 김명신 | 2009/09/23 10:02 | 복잡한컴퓨터이야기 | 트랙백 | 덧글(0)
트랙백 주소 : http://himskim.egloos.com/tb/1951478
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]

:         :

:

비공개 덧글



< 이전페이지 다음페이지 >