Windows Forms의 오류(Exception) 처리 방법에 대한 차이점 설명
다음의 질문에 달린 덧글에,
프로그램 비정상 종료 메시지 창 없애는 방법
; https://www.sysnet.pe.kr/3/0/4830
0으로 나누기 했을때의 메시지 창과 비정상 종료했을 때의 메시지 창 유형이 다르다고 나옵니다.
사실 이것은 0으로 나누기에 대한 문제가 아니라 Windows Forms이 제공하는 환경 위에서 오류가 발생했냐/안 했냐의 문제입니다. 예를 한번 들어 볼까요? ^^
간단하게 Windows Forms 위에 버튼을 하나 두고, 그 버튼 Click 이벤트에 다음의 코드를 넣어 실행해 봅니다.
private void button1_Click(object sender, EventArgs e)
{
int a = 5;
int b = 0;
int c = a / b;
}
(버튼을 누르면, 당연히) 다음과 같은 오류 대화창이 뜨고,
이때 Visual Studio를 이용해 디버거를 붙여 콜 스택을 확인하면 그 이유를 조사할 수 있습니다.
System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason, int pvLoopData) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason, System.Windows.Forms.ApplicationContext context) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Application.RunDialog(System.Windows.Forms.Form form) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Form.ShowDialog(System.Windows.Forms.IWin32Window owner) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Form.ShowDialog() Unknown
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.OnThreadException(System.Exception t) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.WndProcException(System.Exception e) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnThreadException(System.Exception e) Unknown
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Callback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) Unknown
그럼, 대화창을 실제로 만드는 코드를 OnThreadException에서 찾아볼 수 있습니다.
internal void OnThreadException(Exception t)
{
if (!this.GetState(4))
{
this.SetState(4, true);
try
{
...[생략]...
else if (SystemInformation.UserInteractive)
{
ThreadExceptionDialog dialog = new ThreadExceptionDialog(t);
DialogResult oK = DialogResult.OK;
IntSecurity.ModifyFocus.Assert();
try
{
oK = dialog.ShowDialog();
}
...[생략]...
}
return;
Label_0084:
...[생략]...
}
finally
{
this.SetState(4, false);
}
}
}
그리고 OnThreadException은 Windows 응용 프로그램의 필수 요소인 메시지 루프의 Win32 Message 처리 시 예외가 발생한 경우 수행된다는 것을 System.Windows.Forms.NativeWindow.Callback에서 찾아 볼 수 있습니다.
private IntPtr Callback(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam)
{
Message m = Message.Create(hWnd, msg, wparam, lparam);
try
{}
if (this.weakThisPtr.IsAlive && (this.weakThisPtr.Target != null))
{
this.WndProc(ref m);
}
else
{
this.DefWndProc(ref m);
}
}
catch (Exception exception)
{
this.OnThreadException(exception);
}
finally
{
if (msg == 130)
{
this.ReleaseHandle(false);
}
if (msg == NativeMethods.WM_UIUNSUBCLASS)
{
this.ReleaseHandle(true);
}
}
return m.Result;
}
따라서, Win32 Message 처리 도중 예외가 발생한다면 .NET Windows Forms에서 마련한 예외 창이 뜨게 됩니다.
그렇다면, 당연히 다른 곳에서 예외가 발생하면 Windows Forms도 처리를 할 수 없으므로 시스템에 넘어가게 됩니다. 그 결과로 나온 아래와 같은 형식의 창은 (디버거 설치 유무 등의 정보 등을 조사한 다음 적절한 사후 처리를 할 수 있도록) 운영체제가 담당하게 됩니다. 다음은 디버거가 설치된 시스템에서 운영체제가 띄우는 오류 보고 창입니다.
이를 테스트하고 싶다면 Windows Forms에 또 다른 버튼을 만들고 그 이벤트 핸들러에 다음과 같은 식으로 별도의 스레드를 이용해 처리를 맡겨 보면 됩니다.
private void button2_Click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem((arg) =>
{
int a = 5;
int b = 0;
int c = a / b;
}, null);
}
저 스레드에서는 Windows Forms의 Message Loop 처리 코드가 없기 때문에 예외가 발생하면 운영체제로 곧장 넘어가 비정상 종료를 하게 됩니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]