이번 시간에는 에러 발생 시 이를 제대로 처리해줄 수 있도록 C++ exception을 활용하여 기능을 추가할 것이다.
커스텀 Exception 클래스를 정의하자.
여기서의 핵심이 되는 내용은 whatBuffer와 what()인데, stringstream을 활용해 타입과 로그 string을 whatBuffer에 저장하고 whatBuffer.c_str()으로 char*를 반환한다.
이때 그냥 한번에 return oss.str().c_str()을 하지 않는 이유는 stream의 특성 상 함수 종료시 삭제되기 때문에 (정확히는 stream은 copy가 불가능하므로 함수의 반환값으로 lvalue stream을 사용할 수 없는 것) 함수 외부의 클래스 멤버로 저장된 whatBuffer에 데이터를 저장해둔 후 이를 반환하는 것이다.
사실 엄청나게 중요한 내용은 아니고, 그냥 stream의 특성을 인지하는 정도면 된다.
이렇게 만든 exception 클래스를 윈도우 프레임워크 클래스 내부로 가져와 winapi 연관 exception은 별도로 정의하자.
윈도우 exception들의 에로코드들(HRESULT)에 대한 정보가 추가되었음을 알 수 있다.
이 에러코드를 활용해 winapi의 FormatMessage를 이용해 해당 에러코드에 대응되는 에러 string을 반환하도록 다음과 같은 형태로 작성해준다. FormatMessage에 대한 약간의 이해가 있어야 코드 이해가 원활하다. 레퍼런스를 참조하자.
간단한 매크로 형태로 작성함으로 편의를 위해 hr만 전달해줘도 사용가능하게 만든다. (winapi의 LINE, FILE을 일일히 입력해야되는 수고를 덜 수 있다. 주의: LINE은 실제 에러가 발생한 줄이 아니라 exception이 throw된 줄을 반환한다.)
추가로 CHWND_LAST_EXCEPT()라는 가장 마지막 에러를 반환하는 매크로도 만든다. 사진에는 짤려있지만 마지막 아규먼트 hr에는 GetLastError()를 전달한다.
이렇게 window exception을 정의했으니, 이를 실제로 catch()해주어야 한다.
#include "Window.h"
int CALLBACK WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow )
{
try
{
Window wnd( 800,300,"Donkey Fart Box" );
MSG msg;
BOOL gResult;
while( (gResult = GetMessage( &msg,nullptr,0,0 )) > 0 )
{
// TranslateMessage will post auxilliary WM_CHAR messages from key msgs
TranslateMessage( &msg );
DispatchMessage( &msg );
}
// check if GetMessage call itself borked
if( gResult == -1 )
{
throw CHWND_LAST_EXCEPT();
}
// wParam here is the value passed to PostQuitMessage
return msg.wParam;
}
catch( const ChiliException& e )
{
MessageBox( nullptr,e.what(),e.GetType(),MB_OK | MB_ICONEXCLAMATION );
}
catch( const std::exception& e )
{
MessageBox( nullptr,e.what(),"Standard Exception",MB_OK | MB_ICONEXCLAMATION );
}
catch( ... )
{
MessageBox( nullptr,"No details available","Unknown Exception",MB_OK | MB_ICONEXCLAMATION );
}
return -1;
}
에러 발생 시 윈도우 창를 띄우도록 만들었다.
throw CHWND_EXCEPT( 에러코드 ); // winapi 에러 유발
throw std::runtime_error("에러 메세지"); // 런타임 에러 유발
throw 123456789; // unknown (define되지 않은) 에러 유발
잘 작동한다!
이제 이걸 실제로 프레임워크에 적용하자. winapi의 FAILED 매크로를 사용해도 되고, null check 등을 할 때 사용해도 된다.
한 가지 주의해야할 사항은, HandleMsg()와 같은 메세지 핸들링 함수들 내에서는 exception을 사용해야 하지 말아야 한다는 것인데, 이유는 winapi측에서 해당 함수들을 호출하는 것이기 때문에 만약 중간에 exception을 던져버리면 winapi 자체가 흐름이 끊겨 프로그램 자체가 크래시가 나게 된다.
마무리하기 전에 간단하게 프로그램 아이콘을 추가해주자.
비주얼 스튜디오에 Add Resource를 사용해 .ico파일 리소스를 추가해준 후, resource.h로 가서 확인해보면 우리가 등록한 아이콘 리소스에 해당하는 파일이 매크로(정수 value)로 추가된 것을 볼 수 있다.
이 리소스는 이제 우리가 프로그램을 컴파일하면 컴파일된 executable에 baked된다.
(주석: 매크로명을 코드상에서 직접 수정해서는 안되는 듯 하다. 수정하는 방법이 있을 듯 한데, 시간나면 찾아보자)
이걸 실제로 코드로 불러와 사용하기 위해 코드에서 간단한 수정을 해주면 된다.
#include "resource.h"
// Window Class Stuff
Window::WindowClass Window::WindowClass::wndClass;
Window::WindowClass::WindowClass() noexcept
:
hInst( GetModuleHandle( nullptr ) )
{
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof( wc );
wc.style = CS_OWNDC;
wc.lpfnWndProc = HandleMsgSetup;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = GetInstance();
wc.hIcon = static_cast<HICON>(LoadImage(
GetInstance(),MAKEINTRESOURCE( IDI_ICON1 ),
IMAGE_ICON,32,32,0
)); // winapi의 LoadImage 함수는 이미지를 불러오고, MAKEINTRESOURCE를 이용해 저장한 리소스를 가져온다.
wc.hCursor = nullptr;
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
wc.lpszClassName = GetName();
wc.hIconSm = static_cast<HICON>(LoadImage(
GetInstance(),MAKEINTRESOURCE( IDI_ICON1 ),
IMAGE_ICON,16,16,0
)); // 작은 아이콘
RegisterClassEx( &wc );
}
잘 작동한다! (큰 아이콘은 taskbar에 표시된다)
'computer graphics > DirectX' 카테고리의 다른 글
[D3D Engine] 7. Window framework (0) | 2023.03.19 |
---|---|
[D3D Engine] 6. WM_CHAR, Mouse (0) | 2023.03.19 |
[D3D Engine] 5. Window Messages (1) | 2023.03.19 |
[D3D Engine] 4. Message loop, WinProc (0) | 2023.03.19 |
[D3D Engine] 3. Window Creation (0) | 2023.03.19 |