나의 브을로오그으

#1. 에러 핸들링 본문

Windows

#1. 에러 핸들링

__jhp_+ 2024. 3. 9. 14:49

1) 윈도우즈 함수의 대표적인 반환 자료형

자료형 실패했을 때의 값
VOID 이 함수는 절대 실패 x, 아주 적은 수의 윈도우즈 함수만이 VOID 반환 자료형을 가짐.
BOOL 함수 실패 시 FALSE(0), 성공 시 FALSE(0)이 아닌 값. 그렇기 때문에 TRUE와 비교 하지 말 것.
HANDLE 함수가 실패하면 반환 값은 대개 NULL 이다.
성공 시에는 유효한 오브젝트 핸들을 반환한다. 몇몇 함수들은 INVALID_HANDLE_VALUE(-1)를
반환하는 경우가 있기 때문에 주의가 필요하다. 문서를 꼼꼼히 볼것.
PVOID 함수가 실패하면 NULL을 반환. 성공 시 데이터를 저장하고 있는 메모리 주소를 가리킴.
LONG/DWORD 이러한 종류의 함수는 대개 개수 같은 것을 반환한다.
만약 개수 반환에 실패하면 대개 0이나 -1을 반환한다. 문서를 꼼꼼히 볼것.

 

 

윈도우 함수가 실패하면 왜 함수가 실패했는지의 여부를 알아내는 과정이 반드시 필요하다. 마이크로 소프트는 발생할 가능성이 있는 모든 에러 코드를 32비트 숫자로 정의해 두었다.

 

윈도우 함수가 실패하게 되면 내부적으로 함수를 호출한 스레드의 스레드의 지역 저장소(thread-local storage)에 적절한 에러 코드를 저장해 둔다. 이러한 매커니즘을 통해 여러 개의 스레드가 동시에 수행될 경우라도 상호간에 영향을 미치지 않고 각 스레드별로 에러 코드를 유지할 수 있게 된다. 호출한 함수가 실패한 것으로 판단되면 어떤 에러가 발생했는지 확인하기 위해 GetLastError 함수를 사용할 수 있다.

 

DWORD GetLastError();

이 함수는 단순히 가장 최근에 호출된 함수의 에러 코드를 스레드 지역 저장소(thread-local storage)로부터 가져온다.

 

 

2) 에러코드의 구조

- 메시지 ID(GetLastError 함수의 반환 값과 비교할 수 있도록 정의된 매크로)

- 메시지 텍스트(영어로 된 에러 설명)

- 에러 코드(메시지 ID 대신 이 값을 직접 사용해서는 안 된다.)

 

에러 발생 시 에러 코드를 획득하기 위해 바로 GetLastError() 함수를 호출해야 한다. 이 함수를 호출하기 전에 다른 함수를 호출하게 되면 다른 함수의 수행 결과가 겹쳐 써지게 된다. (함수 호출이 성공하면 ERROR_SUCCESS를 에러 코드로 기록한다.)

 

설령 함수 호출에 성공하였더라도 성공 사유가 다를 수 있기 때문에 마이크로소프트에서는 비슷한 매커니즘으로 지정해 놓았다. 몇몇 함수의 경우 함수 호출이 성공해도 부가적인 성공의 이유를 확인하기 위해 GetLastError()를 이용할 것을 명확하게 기술하고 있다.

 

3) 디버깅 수행

디버깅 수행 동안에 스레드 지역 저장소(thread-local storage)에 기록된 에러 코드를 지속적으로 확인할 수 있으면 편리한데, 마이크로소프트의 Visual Studio 내에 포함된 디버거는 Watch 창을 통해 현재 수행중인 스레드의 마지막 에러 코드와 메시지 텍스트를 확인할 수 있는 기능을 제공하고 있다. 이를 위해 Watch 창에 $err, hr을 입력하면 된다.

 

 

4) FormatMessage

DWORD FormatMessage(
  [in]           DWORD   dwFlags,
  [in, optional] LPCVOID lpSource,
  [in]           DWORD   dwMessageId,
  [in]           DWORD   dwLanguageId,
  [out]          LPTSTR  lpBuffer,
  [in]           DWORD   nSize,
  [in, optional] va_list *Arguments
);

 

: 사용자에게 보여줄 메시지 텍스트를 구성하는 더 나은 방법들을 제공해 준다. 이 함수의 유용성 중 하나는 이 함수가 다양한 언어로 문자열을 구성할 수 있다는 것이다. 이 함수가 언어 식별자를 인자로 받기 때문에 언어 식별자에 준하는 언어로 메시지 텍스트를 구성할 수 있다.

물론 최초에는 애플리케이션 개발자가 메시지를 번역하고 번역된 메시지를 스트링 테이블 리소스 형태로 .exe나 DLL 모듈에 포함시켜야 하지만, 이 함수를 통해서 그중 적절한 것을 선택할 수 있다.

 

 

1. 자신만의 에러 코드를 정의하는 방법

VOID SetLastError(DWORD dwErrCode);

 

: 가능한 "WinError.h"에 정의되어 있는 에러 코드를 사용하는 편이 낫다.

 

1) 에러 코드의 의미

비트 31-30 29 28 27-16 15-0
내용 심각도 마이크로소프트/고객 예약됨 식별 코드 예외 코드
의미 0 = 성공
1 = 정보
2 = 주의
3 = 에러
0 = 마이크로소프트
가 정의한 코드
1 = 고객이 정의한
코드
항상 0 256까지는
마이크로소프트에
의해 예약 됨.
마이크로소프트
나 고객이 정의
한 코드

 

마이크로소프트가 정의한 모든 에러 코드는 29번째 비트를 0으로 설정할 것을 규정하기 때문에 에러 코드를 직접 만드는 경우에는 겹치지 않는 것을 보장받으려면 1로 설정해야 한다.

 

2. ErrorShow 예제 애플리케이션

[UI]

1. EditControl: Sample이라고 작성된 EditBox의 경우 ID는 IDC_ERRORCODE이며, 해당 BOX에는 ErrorCode를 작성.

2. Button: 해당 버튼 클릭 시 에러 코드에 맞는 메시지를 찾아서 하단의 EditControl에 표시한다.

3. EditControl: 에러 코드에 맞는 메시지를 표시한다.

 

 

[코드 분석]

// 에러코드를 획득
DWORD dwError = GetDlgItemInt(hWnd, IDC_ERRORCODE, NULL, FALSE);

// 에러 메시지를 저장하기 위한 버퍼
HLOCAL hLocal = NULL;

// 윈도우 메시지 문자열을 얻기 위해 기본 시스템 지역을 설정한다.
DWORD systemLocal = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);

// 에러 코드의 메시지를 가져온다.
BOOL fOk = FormatMessage(
	         	FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
         		FORMAT_MESSAGE_ALLOCATE_BUFFER, 
         		NULL, dwError, systemLocale, 
         		(PTSTR) &hlocal, 0, NULL
           );
if (fOk == FALSE) {
	// 에러 코드의 메시지 로드 실패 시 네트워크 문제인지 확인
	HMOUDLE hDll = LocalLibraryEx(TEXT("netmsg.dll"), 
    	NULL, DONT_RESOLVE_DLL_REFERENCES);
    
    if (hDll != NULL) { 
    	fOk = FormatMessage(
	         	FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
         		FORMAT_MESSAGE_ALLOCATE_BUFFER, 
         		NULL, dwError, systemLocale, 
         		(PTSTR) &hlocal, 0, NULL
           	  );
         FreeLibrary(hDll);
    }
    
    if (fOk && (hLocal != NULL)) { 
        SetDlgItemText(hwnd, IDC_ERRORTEXT, (PCTSTR) LocalLock(hlocal));
        LocalFree(hlocal);
     } else {
        SetDlgItemText(hwnd, IDC_ERRORTEXT, 
           TEXT("No text found for this error number."));
     }
}