제프리 리처의 WINDOWS VIA C/C++
https://www.yes24.com/Product/Goods/3205340
페이지 31p ~ 41p
요약
- 첫 번째 챕터로 에러 핸들링에 대한 내용을 담고 있다.
- 윈도우 함수가 호출되면 호출된 함수는 전달된 인자의 유효성을 확인하고 함수의 기능을 수행하려 하는데, 이 과정에서 전달된 인자가 유효하지 않거나 다른 이유로 해당 기능을 수행할 수 없으면 실패를 반환한다.
- 실패를 반환하면 왜 함수가 실패했는지 이유를 알아내야 하며, 마이크로소프트에서는 발생할 가능성이 있는 모든 에러를 32비트 숫자로 정의해 두었다.
- 윈도우 함수가 실패하면 내부적으로 함수를 호출한 스레드의 지역 저장소(thread-local storage)에 적절한 에러 코드를 저장하기 때문에, 멀티 스레드 환경에서도 각각의 스레드는 고유한 에러 코드를 유지할 수 있게 된다.
메모
1. 윈도우 함수의 대표적인 반환 자료형
자료형 |
실패시 반환 값 |
성공시 반환 값 |
VOID |
절대 실패하지 않는 함수. |
- |
BOOL |
실패하면 0을 반환. |
성공하면 0이 아닌 값을 반환. |
HANDLE |
실패하면 대개 NULL을 반환. 몇몇 함수는 INVALID_HANDLE_VALUE(-1)을 반환. |
성공하면 유효한 오브젝트 핸들을 반환. |
PVOID |
실패하면 NULL을 반환. |
성공하면 PVOID가 데이터를 저장하고 있는 메모리 주소를 반환. |
LONG/DWORD |
실패하면 0이나 -1을 반환. 결과값에 대한 정확한 정보는 공식 문서 참고. |
성공하면 각 자료형으로 개수를 반환. |
반환 자료형의 중요성
- 윈도우 함수가 실패하면 왜 실패했는지 이유를 알아내는 과정이 반드시 필요하다. 반환 자료형을 통해 함수의 성공, 실패 여부를 판단할 수 있을 것이다.
- 마이크로소프트는 발생할 가능성이 있는 모든 에러 코드를 32비트 숫자로 정의해 두었다.
- 호출한 함수가 실패했다고 판단되면, 함수를 호출한 직후 반드시 다음과 같은 함수를 사용하여 스레드에 저장된 에러 코드를 확인해야 한다.
// 가장 최근에 호출된 함수의 에러 코드를 스레드 지역 저장소에서 가져옴
DWORD GetLastError();
DWORD
반환 타입은 32비트 크기의 unsigned long
과 같은 타입으로 숫자를 보다 유용한 정보로 변환하기 위해 WinError.h
파일에는 해당 숫자들을 의미 있는 메시지로 치환해 두었다.
서로 다른 이유로 성공하는 케이스
- 특정 함수들은 성공의 이유가 여러 가지가 있다.
- 특정한 이름의 이벤트 커널 오브젝트를 생성하는 함수를 호출하면 실제로 새로운 커널 오브젝트가 생성되는 경우 외에도, 이미 동일한 이름의 커널 오브젝트가 존재하는 경우에도 성공을 반환한다.
- 따라서 어떤 이유로 함수가 성공했는지 구체적으로 알아야 할 필요가 있으며, 에러 코드를 저장하는 방식을 이용하여 이를 구분할 수 있도록 만들어 놓았다.
CreateEvent
문서에서는 동일한 이름의 이벤트 오브젝트가 존재하면 성공을 했어도 GetLastError
의 반환값은 ERROR_ALREADY_EXISTS
가 된다.
스레드 지역 저장소에 저장된 에러 코드 확인하기(Visual Studio 사용)
- 디버깅을 하는 과정에서 스레드 지역 저장소에 기록된 에러 코드를 확인할 수 있다.
- Visual Studio 디버거의 Watch 창을 통해 현재 수행 중인 스레드의 마지막 에러 코드와 메시지를 확인할 수 있다.
- Watch 창의 특정 행을 선택하고
$err,hr
을 입력하면 된다.
Error Lookup 유틸리티
FormatMessage
- FormatMessage를 사용하면 에러 코드를 메시지 텍스트로 변환할 수 있다.
DWORD FormatMessage(
DWORD dwFlags, // 플래그
LPCVOID pSource, // 메시지 정의 위치. 플래그에 따라 달라짐. 해당 사항 없으면 무시
DWORD dwMessageId, // 에러 코드
DWORD dwLanguageId, // 언어 식별자
PTSTR pszBuffer, // null로 끄나는 문자열을 수신하는 버퍼 포인터
DWORD nSize, // 출력 버퍼 크기 지정. 플래그 설정에 따라 달라짐(최대, 최소)
va_list *Arguments // 형식 문자에 사용할 값들
)
// 다이얼로그 핸들러가 가지고 있는 에러 코드를 획득
DWORD dwError = GetDlgItemInt(hwnd, IDC_ERRORCODE, NULL, FALSE);
HLOCAL hlocal = NULL; // 에러 메시지를 저장하기 위한 버퍼 선언
// 에러 메시지를 얻기 위해 시스템 지역 설정
// 아래의 MAKELANGID는 0을 반환함
DWORD systemLocale = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);
// Get the error code's textual description
BOOL fOk = FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL, dwError, systemLocale,
(PTSTR) &hlocal, 0, NULL);
if (!fOk) {
// 네트워크 관련 에러 확인
// DLL을 통해 자신만의 에러 코드와 메시지를 포함할 수 있음
HMODULE hDll = LoadLibraryEx(TEXT("netmsg.dll"), NULL,
DONT_RESOLVE_DLL_REFERENCES);
if (hDll != NULL) {
fOk = FormatMessage(
FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_ALLOCATE_BUFFER,
hDll, 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."));
}
- 첫 번째 인자로 전달되는 플래그는 각각 다음과 같다.
- FORMAT_MESSAGE_FROM_SYSTEM: 운영체제가 정의하고 있는 에러 코드와 대응되는 메시지 텍스트 획득 요청.
- FORMAT_MESSAGE_IGNORE_INSERTS: 에러 메시지 텍스트에 자리 표시자를 실질적으로 변경하지 않을 것을 요청.
- FORMAT_MESSAGE_ALLOCATE_BUFFER: 에러 메시지 텍스트를 저장할 수 있는 메모리 공간 할당을 요청. 할당된 메모리 블록을 가리키는 hlocal 변수에 값이 반환됨
2. 자신만의 에러 코드 정의
- 함수는 여러 이유로 인해 실패할 수 있을 것이며, 원인을 함수의 호출자에게 알려주어야 한다.
- 함수가 실패했음을 나타내기 위해서는 실패의 이유를 스레드의 마지막 에러 코드로 설정하고 FALSE, INVALID_HANDLE_VALUE, NULL과 같이 적절한 값을 반환하도록 함수를 작성한다.
- 스레드의 마지막 에러 코드를 설정하는 함수는 다음과 같다.
VOID SetLastError(DWORD dwErrCode);
WinError.h
에 정의되어 있는 에러 코드 외에 자신만의 에러 코드를 작성하고 싶다면 다음과 같은 규칙에 따라 에러 코드를 만들면 된다.
비트 |
31-30 |
29 |
28 |
27-16 |
15-0 |
내용 |
심각도 |
마이크로소프트/고객 |
예약됨 |
식별 코드 |
예외 코드 |
의미 |
0 = 성공 1 = 정보 2 = 주의 3 = 에러 |
0 = 마이크로소프트가 정의 1 = 고객이 정의 |
항상 0 |
256까지는 마이크로소프트에 의해 예약됨 |
마이크로소프트나 고객이 정의한 코드 |