성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>RDP 접속 시 WPF UserControl의 Unloaded 이벤트 발생</h1> <p> 재미있는 질문이 있군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Remote Desktop으로 접속시 WPF UI 가 다시 그려지는 이벤트를 막을 수 없을까요? ; <a target='tab' href='https://www.sysnet.pe.kr/3/0/5632'>https://www.sysnet.pe.kr/3/0/5632</a> </pre> <br /> 질문하신 분이 부연 설명을 댓글로 하고 있는데요, 간단하게 재현을 해볼까요? ^^<br /> <br /> 아래와 같이 WPF UserControl을 추가하고 Unloaded를 걸어둔 후,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using CustomMessageLoop; using System; using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Interop; namespace WpfControlLibrary1 { public partial class UserControl1 : UserControl { public UserControl1() { InitializeComponent(); <span style='color: blue; font-weight: bold'>this.Unloaded += UserControl1_Unloaded;</span> } private void UserControl1_Unloaded(object sender, RoutedEventArgs e) { <span style='color: blue; font-weight: bold'>MessageBox.Show($"TEST: {Environment.StackTrace}");</span> } } } </pre> <br /> RDP로 연결한 환경에서 실행해 둡니다. 그런 다음 RDP 연결을 끊고, 다시 접속을 하면... ^^ 화면에는 다음과 같은 식의 호출 스택이 뜨는 것을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > at System.Environment.get_StackTrace() at WpfControlLibrary1.UserControl1.UserControl1_Unloaded(Object sender, RoutedEventArgs e) in C:\...\WpfControlLibrary1\UserControl1.xaml.cs:line 33 at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised) at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args) at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e) at System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject root, RoutedEvent routedEvent) at System.Windows.BroadcastEventHelper.BroadcastUnloadedEvent(Object root) at System.Windows.Media.MediaContext.FireLoadedPendingCallbacks() at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks() at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget) at System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) at System.Windows.Threading.DispatcherOperation.InvokeImpl() at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state) at System.Windows.Threading.DispatcherOperation.Invoke() at System.Windows.Threading.Dispatcher.ProcessQueue() at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs) at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam) at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg) at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg) at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame) at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame) at System.Windows.Threading.Dispatcher.Run() at System.Windows.Application.RunDispatcher(Object ignore) at System.Windows.Application.RunInternal(Window window) at System.Windows.Application.Run() at WpfApp1.App.Main() </pre> <br /> 그러니까, RDP 재접속만으로 UserControl이 내려갔다 다시 올라오는 것입니다. 오~~~ 신기하군요. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 일단, 저걸 막는 방법은 없습니다. 보는 바와 같이 마이크로소프트의 내부 구현으로 발생하는 것이기 때문에 딱히 저걸 손볼 여지가 없습니다.<br /> <br /> 단지, 약간의 테스트는 할 수 있는데요, 호출 스택을 보면 MS.Win32.HwndWrapper.WndProc 메서드가 보이는 걸로 봐서, 분명히 이것은 Win32 메시지와 관련된 것임을 짐작게 합니다.<br /> <br /> 따라서, 아마도 특정 메시지를 무시하면 될 것도 같은데요, 이를 위해 ThreadFilterMessage를 이용해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > WPF - 윈도우 이벤트 가로채기 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/772'>https://www.sysnet.pe.kr/2/0/772</a> </pre> <br /> 이런 식으로 걸어두면 어떨까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public UserControl1() { InitializeComponent(); timer.Change(0, 1000); this.Unloaded += UserControl1_Unloaded; <span style='color: blue; font-weight: bold'>ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcher_ThreadFilterMessage);</span> } private void UserControl1_Unloaded(object sender, RoutedEventArgs e) { ComponentDispatcher.ThreadFilterMessage -= new ThreadMessageEventHandler(ComponentDispatcher_ThreadFilterMessage); MessageBox.Show($"TEST: {Environment.StackTrace}"); } </pre> <br /> RDP 재접속 시 발생하는 이벤트를 기록해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > void ComponentDispatcher_ThreadFilterMessage(ref System.Windows.Interop.MSG msg, ref bool handled) { string txt = $"[{this.GetHashCode()}] msg == {<a target='tab' href='https://www.sysnet.pe.kr/2/0/13019'>Win32MessageMap</a>.GetText(msg.message)}, {msg.wParam}, {msg.lParam}, {msg.time}, {msg.ToString()}"; using (var w = File.AppendText("C:\\temp\\test.txt")) { w.WriteLine(txt); } } </pre> <br /> 몇몇 메시지들이 보일 텐데요, 해당 메시지들의 실행을 다음과 같은 식으로 막으면서 테스트할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > void ComponentDispatcher_ThreadFilterMessage(ref System.Windows.Interop.MSG msg, ref bool handled) { uint code = (uint)msg.message; if (code == (uint)Win32Message.WM_WTSSESSION_CHANGE) { <span style='color: blue; font-weight: bold'>handled = true;</span> } } </pre> <br /> 다소 무식하지만... ^^ 저런 방법으로 범인을 찾다 보면 정규 Win32 메시지가 아닌, <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerwindowmessagea#return-value'>0xc0eb</a> 값의 특별한 메시지를 막는 경우 UserControl의 Unloaded 이벤트가 발생하지 않는다는 것을 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > void ComponentDispatcher_ThreadFilterMessage(ref System.Windows.Interop.MSG msg, ref bool handled) { if (msg.message == 0xc0eb) { // 이후, Unloaded가 발생하지 않음. <span style='color: blue; font-weight: bold'>handled = true;</span> } } </pre> <br /> 문제는, (RDP 환경에서) 이후로는 응용 프로그램의 컨트롤들이 동작하지 않습니다. 게다가 응용 프로그램을 종료해도 윈도우만 닫히고 프로세스는 종료가 안 됩니다.<br /> <br /> 어떤 메시지가 원인인지는 추적했지만, 결국 WPF 프레임워크상 필요해서 만들었을 테니... 저걸 외부 개발자가 막기에는 정보가 너무 없습니다. ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/2/0/13020'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
2756
(왼쪽의 숫자를 입력해야 합니다.)