Microsoft MVP성태의 닷넷 이야기
.NET Framework: 1187. RDP 접속 시 WPF UserControl의 Unloaded 이벤트 발생 [링크 복사], [링크+제목 복사],
조회: 14106
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)

RDP 접속 시 WPF UserControl의 Unloaded 이벤트 발생

재미있는 질문이 있군요. ^^

Remote Desktop으로 접속시 WPF UI 가 다시 그려지는 이벤트를 막을 수 없을까요?
; https://www.sysnet.pe.kr/3/0/5632

질문하신 분이 부연 설명을 댓글로 하고 있는데요, 간단하게 재현을 해볼까요? ^^

아래와 같이 WPF UserControl을 추가하고 Unloaded를 걸어둔 후,

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();

            this.Unloaded += UserControl1_Unloaded;
        }

        private void UserControl1_Unloaded(object sender, RoutedEventArgs e)
        {
            MessageBox.Show($"TEST: {Environment.StackTrace}");
        }
    }
}

RDP로 연결한 환경에서 실행해 둡니다. 그런 다음 RDP 연결을 끊고, 다시 접속을 하면... ^^ 화면에는 다음과 같은 식의 호출 스택이 뜨는 것을 볼 수 있습니다.

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()

그러니까, RDP 재접속만으로 UserControl이 내려갔다 다시 올라오는 것입니다. 오~~~ 신기하군요. ^^




일단, 저걸 막는 방법은 없습니다. 보는 바와 같이 마이크로소프트의 내부 구현으로 발생하는 것이기 때문에 딱히 저걸 손볼 여지가 없습니다.

단지, 약간의 테스트는 할 수 있는데요, 호출 스택을 보면 MS.Win32.HwndWrapper.WndProc 메서드가 보이는 걸로 봐서, 분명히 이것은 Win32 메시지와 관련된 것임을 짐작게 합니다.

따라서, 아마도 특정 메시지를 무시하면 될 것도 같은데요, 이를 위해 ThreadFilterMessage를 이용해,

WPF - 윈도우 이벤트 가로채기
; https://www.sysnet.pe.kr/2/0/772

이런 식으로 걸어두면 어떨까요?

public UserControl1()
{
    InitializeComponent();
    timer.Change(0, 1000);

    this.Unloaded += UserControl1_Unloaded;

    ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcher_ThreadFilterMessage);
}

private void UserControl1_Unloaded(object sender, RoutedEventArgs e)
{
    ComponentDispatcher.ThreadFilterMessage -= new ThreadMessageEventHandler(ComponentDispatcher_ThreadFilterMessage);
    MessageBox.Show($"TEST: {Environment.StackTrace}");
}

RDP 재접속 시 발생하는 이벤트를 기록해 보면,

void ComponentDispatcher_ThreadFilterMessage(ref System.Windows.Interop.MSG msg, ref bool handled)
{
    string txt = $"[{this.GetHashCode()}] msg == {Win32MessageMap.GetText(msg.message)}, {msg.wParam}, {msg.lParam}, {msg.time}, {msg.ToString()}";
    using (var w = File.AppendText("C:\\temp\\test.txt"))
    {
        w.WriteLine(txt);
    }
}

몇몇 메시지들이 보일 텐데요, 해당 메시지들의 실행을 다음과 같은 식으로 막으면서 테스트할 수 있습니다.

void ComponentDispatcher_ThreadFilterMessage(ref System.Windows.Interop.MSG msg, ref bool handled)
{
    uint code = (uint)msg.message;

    if (code == (uint)Win32Message.WM_WTSSESSION_CHANGE)
    {
        handled = true;
    }
}

다소 무식하지만... ^^ 저런 방법으로 범인을 찾다 보면 정규 Win32 메시지가 아닌, 0xc0eb 값의 특별한 메시지를 막는 경우 UserControl의 Unloaded 이벤트가 발생하지 않는다는 것을 알 수 있습니다.

void ComponentDispatcher_ThreadFilterMessage(ref System.Windows.Interop.MSG msg, ref bool handled)
{
    if (msg.message == 0xc0eb)
    {
        // 이후, Unloaded가 발생하지 않음.
        handled = true;
    }
}

문제는, (RDP 환경에서) 이후로는 응용 프로그램의 컨트롤들이 동작하지 않습니다. 게다가 응용 프로그램을 종료해도 윈도우만 닫히고 프로세스는 종료가 안 됩니다.

어떤 메시지가 원인인지는 추적했지만, 결국 WPF 프레임워크상 필요해서 만들었을 테니... 저걸 외부 개발자가 막기에는 정보가 너무 없습니다. ^^

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/21/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 136  137  138  139  [140]  141  142  143  144  145  146  147  148  149  150  ...
NoWriterDateCnt.TitleFile(s)
1554정성태12/26/201335230Windows: 78. 마음에 드는 윈도우 8.1 태블릿 - 델 베뉴 8 프로 5830 [4]
1553정성태12/26/201322226개발 환경 구성: 206. JNBridgePro와 한글 인코딩 문제파일 다운로드1
1552정성태12/25/201327410개발 환경 구성: 205. JNBridgePro를 이용해 C#에서 Java메서드 호출 테스트파일 다운로드1
1551정성태12/24/201322557.NET Framework: 398. tech-days 미니 토요세미나 - 3회 C#편 PPT 자료파일 다운로드1
1550정성태12/13/201324920Windows: 77. Windows 8 - 잠시 사용을 안하는 경우 화면 잠김 상태로 빠지는 문제
1549정성태12/13/201328524VC++: 73. IIS - ISAPI 필터 제작하는 방법 [2]
1548정성태12/10/201321218오류 유형: 198. C# - 제네릭 covariance/contravariance 사용할 때 컴파일 오류가 발생한다면?
1547정성태12/10/201330799.NET Framework: 397. C# - OCX 컨트롤에 구현된 메서드에 배열을 in, out으로 전달하는 방법파일 다운로드2
1546정성태11/28/201324665.NET Framework: 396. C# - 프로퍼티로 정의하면 필드보다 느릴까요? - windbg / ollydbg [3]
1545정성태11/28/201328547.NET Framework: 395. C# - 프로퍼티로 정의하면 필드보다 느릴까요? [3]
1544정성태11/27/201325042개발 환경 구성: 204. Visual Studio Online "Monaco" 서비스와 github 연동
1543정성태11/27/201329778오류 유형: 197. error MSB8008: Specified platform toolset (v120) is not installed or invalid. [1]
1542정성태11/27/201335338오류 유형: 196. The procedure entry point InitializeCriticalSectionEx could not be located in the dynamic link library KERNEL32.dll
1541정성태11/22/201336525.NET Framework: 394. async/await 사용 시 hang 문제가 발생하는 경우 [7]파일 다운로드1
1540정성태11/20/201325016개발 환경 구성: 203. Azure - WEB SITES 서비스 소개 [4]
1539정성태11/19/201329028VS.NET IDE: 83. 형상 관리 서버 운영을 대신해 주는 Visual Studio 온라인 서비스
1538정성태11/19/201329870오류 유형: 195. 웹 사이트의 모든 정적 컨텐츠 요청에 대해 "Internal Server Error" 응답
1537정성태11/19/201321538오류 유형: 194. 윈도우 서버 백업으로 인해 Hyper-V VM들의 상태가 모두 "Backing up..." 상태로 오래 지속되는 문제
1536정성태11/19/201326311오류 유형: 193. 윈도우 서버 백업 - Hyper-V 가상 머신이 백업되지 않는 경우
1535정성태11/18/201326442.NET Framework: 393. Internet Explorer 11에서 ASP.NET 컨트롤의 크기가 달라지는 문제 [1]
1534정성태11/13/201326454.NET Framework: 392. .NET 스레드 콜 스택 덤프 (6) - MDbg를 이용한 방법 [2]파일 다운로드1
1533정성태11/12/201333661기타: 39. Internet Explorer 11에서 유튜브 동영상의 1080p 옵션이 보이지 않는 경우 [5]
1532정성태11/5/201334583Phone: 8. 안드로이드용 Xamarin 개발 시 겪을 만한 시행 착오 정리 [6]
1531정성태11/5/201325981VS.NET IDE: 82. Visual Studio에서 Attach 메서드를 이용해 디버깅을 시작한 경우 Breakpoint가 안 잡힌다면?
1530정성태11/5/201327344기타: 38. 오픈소스로 풀린 하드 디스크 관리 도구 - WindowSMART
1529정성태11/5/201323254오류 유형: 192. SQL 서버 - The transaction log for database '...' is full due to 'LOG_BACKUP'.
... 136  137  138  139  [140]  141  142  143  144  145  146  147  148  149  150  ...