Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일

C# - (JetBrains Omea Reader 대상으로) 런타임 시에 메서드 가로채기

아직도 저는 ^^; .NET 1.1로 만들어진 "JetBrains Omea Reader"를 사용합니다. (독립 실행형 프로그램으로 제공하는 RSS Reader 중에 이만한 것을 찾지 못했습니다.)

그런데, 최근에 겪은 문제점이라면... 종료가 되지 않는 현상이 가끔씩 발생한다는 것입니다. 즉, Close 버튼을 누르면 대기 창이 하나 뜨더니 종료하지 못하고 초 시계만 마냥 돌고 있는 것입니다.

jb_omea_reader_hang_1.png

이 상태에서 WinDbg를 이용해 연결해 보면, 다음의 호출에서 멈춰 있는 것을 볼 수 있습니다.

// 윈도우 응용 프로그램의 경우, 높은 확률로 0번 스레드가 UI 스레드이므로!
0:024> ~0s
eax=00000000 ebx=000d167c ecx=00000000 edx=00000000 esi=00000000 edi=000c1b30
eip=74f511dc esp=0019d354 ebp=0019d398 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
win32u!NtUserWaitMessage+0xc:
74f511dc c3              ret

0:000> !clrstack
OS Thread Id: 0x3688 (0)
Child SP       IP Call Site
0019e0f0 77b15e3c [HelperMethodFrame_1OBJ: 0019e0f0] System.Threading.Thread.JoinInternal(Int32)
0019e174 04c12aff *** WARNING: Unable to verify checksum for OmniaMeaBase.dll
JetBrains.Omea.AsyncProcessing.AsyncProcessor.WaitUntilFinished()
0019e180 04c12a76 JetBrains.Omea.AsyncProcessing.AsyncProcessor.Dispose()
0019e190 04c1217c JetBrains.Omea.MainFrame.h()
0019e43c 73d32516 [DebuggerU2MCatchHandlerFrame: 0019e43c] 
0019e294 73d32516 [HelperMethodFrame_PROTECTOBJ: 0019e294] System.RuntimeMethodHandle.InvokeMethod(System.Object, System.Object[], System.Signature, Boolean)
0019e4e0 714ca58a System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(System.Object, System.Object[], System.Object[])
0019e504 7140d61d System.Delegate.DynamicInvokeImpl(System.Object[])
0019e518 0a024241 JetBrains.Omea.AsyncProcessing.DelegateJob.Execute()
0019e540 0a0241ac JetBrains.Omea.MainFrame.t(System.Object, System.EventArgs)
0019e54c 04c11ee0 JetBrains.Omea.ProgressWindow.b()
0019e558 5a890c30 *** WARNING: Unable to verify checksum for System.Windows.Forms.ni.dll
*** WARNING: Unable to verify checksum for System.ni.dll
System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry)
0019e574 5a890b78 System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(System.Object)
0019e59c 71408e34 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
0019e608 71408d67 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
0019e61c 71408d24 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
0019e634 5a890b0a System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry)
0019e64c 5a8908aa System.Windows.Forms.Control.InvokeMarshaledCallbacks()
0019e68c 5a87fd10 System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
0019e690 5a889581 [InlinedCallFrame: 0019e690] 
0019e6d8 5a889581 System.Windows.Forms.ScrollableControl.WndProc(System.Windows.Forms.Message ByRef)
0019e6e4 5a889247 System.Windows.Forms.Form.WndProc(System.Windows.Forms.Message ByRef)
0019e6f8 5a87fbe3 System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)
0019e700 5a87fbc9 System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
0019e714 5a87f500 System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
0019e950 0218d08e [InlinedCallFrame: 0019e950] 
0019e94c 5a8e4f6d DomainBoundILStubClass.IL_STUB_PInvoke(MSG ByRef)
0019e950 5a89020b [InlinedCallFrame: 0019e950] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
0019e984 5a89020b System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32)
0019e988 5a88fdf9 [InlinedCallFrame: 0019e988] 
0019ea10 5a88fdf9 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
0019ea60 5a88fc4f System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
0019ea8c 5af64033 System.Windows.Forms.Application.RunDialog(System.Windows.Forms.Form)
0019eaa0 5af6d52d System.Windows.Forms.Form.ShowDialog(System.Windows.Forms.IWin32Window)
0019eaa4 04c1185b [InlinedCallFrame: 0019eaa4] 
0019eb48 04c1185b JetBrains.Omea.MainFrame.a(System.String, Boolean, System.Delegate, System.Object[])
0019eb6c 04c1178a JetBrains.Omea.MainFrame.n(System.Object, System.EventArgs)
0019eb90 5af6b253 System.Windows.Forms.Form.OnClosed(System.EventArgs)
0019eba4 5af6e522 System.Windows.Forms.Form.WmClose(System.Windows.Forms.Message ByRef)
0019ebe8 5b32a238 System.Windows.Forms.Form.WndProc(System.Windows.Forms.Message ByRef)
0019ebfc 0c456d5c JetBrains.Omea.MainFrame.WndProc(System.Windows.Forms.Message ByRef)
0019ec20 5a87fbe3 System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)
0019ec28 5a87fbc9 System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
0019ec3c 5a87f500 System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
0019eeb0 0218d08e [InlinedCallFrame: 0019eeb0] 
0019eeac 5a8e2dbd DomainBoundILStubClass.IL_STUB_PInvoke(System.Runtime.InteropServices.HandleRef, Int32, Int32, Int32)
0019eeb0 5a893c5b [InlinedCallFrame: 0019eeb0] System.Windows.Forms.UnsafeNativeMethods.SendMessage(System.Runtime.InteropServices.HandleRef, Int32, Int32, Int32)
0019eeec 5a893c5b System.Windows.Forms.Control.SendMessage(Int32, Int32, Int32)
0019ef04 5af6a416 System.Windows.Forms.Form.Close()
0019ef14 04c1166e JetBrains.Omea.ExitAction.Execute(JetBrains.Omea.OpenAPI.IActionContext)
0019ef1c 04c115f3 JetBrains.Omea.MenuActionManager.a(System.Object, System.EventArgs)
0019ef4c 5b13586e System.Windows.Forms.MenuItem.OnClick(System.EventArgs)
0019ef60 5b20a0ac System.Windows.Forms.MenuItem+MenuItemData.Execute()
0019ef6c 5b0b8519 System.Windows.Forms.Command.Invoke()
0019ef78 5b0b8301 System.Windows.Forms.Command.DispatchID(Int32)
0019ef80 5b32d1db System.Windows.Forms.Control.WmCommand(System.Windows.Forms.Message ByRef)
0019ef90 5a87ff77 System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
0019ef94 5a889581 [InlinedCallFrame: 0019ef94] 
0019efdc 5a889581 System.Windows.Forms.ScrollableControl.WndProc(System.Windows.Forms.Message ByRef)
0019efe8 5a889247 System.Windows.Forms.Form.WndProc(System.Windows.Forms.Message ByRef)
0019effc 0c456d5c JetBrains.Omea.MainFrame.WndProc(System.Windows.Forms.Message ByRef)
0019f020 5a87fbe3 System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)
0019f028 5a87fbc9 System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
0019f03c 5a87f500 System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
0019f278 0218d08e [InlinedCallFrame: 0019f278] 
0019f274 5a8e4f6d DomainBoundILStubClass.IL_STUB_PInvoke(MSG ByRef)
0019f278 5a89020b [InlinedCallFrame: 0019f278] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
0019f2ac 5a89020b System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32)
0019f2b0 5a88fdf9 [InlinedCallFrame: 0019f2b0] 
0019f338 5a88fdf9 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
0019f388 5a88fc4f System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
0019f3b4 0aedeb1c JetBrains.Omea.AsyncProcessing.AsyncProcessor.MsgWaitForMultipleObjects(IntPtr[], Int32, UInt32)
0019f3d4 0eec7f52 JetBrains.Omea.AsyncProcessing.AsyncProcessor.MsgWaitForMultipleObjects(Int32, Int32)
0019f3f0 0aedbd2a JetBrains.Omea.AsyncProcessing.AsyncProcessor.WaitEvents(Int32)
0019f410 0aedb213 JetBrains.Omea.AsyncProcessing.AsyncProcessor.DoJobs()
0019f444 0eec7ea3 JetBrains.Omea.MainFrame+b.h()
0019f464 0aeda90e JetBrains.Omea.AsyncProcessing.AsyncProcessor.ThreadFunction()
0019f498 0eec7e51 JetBrains.Omea.AsyncProcessing.AsyncProcessor.EmployCurrentThread()
0019f4a4 0eec7dbc JetBrains.Omea.MainFrame+b.e()
0019f4d4 02212300 JetBrains.Omea.MainFrame.ad()
0019f67c 73d32516 [GCFrame: 0019f67c] 

호출 스택을 보면, 결국 OmniaMeaBase.dll의 AsyncProcessor.WaitUntilFinished 메서드에서 멈춰 있다는 것인데요, 해당 메서드를 역어셈블하면 다음의 코드가 나옵니다.

// JetBrains.Omea.AsyncProcessing.AsyncProcessor
// Token: 0x060000E7 RID: 231 RVA: 0x00006C64 File Offset: 0x00005C64
public void WaitUntilFinished()
{
    if (this._isThreadStarted != 0)
    {
        if (!Application.MessageLoop)
        {
            this._processorThread.Join();
        }
        else
        {
            while (this._processorThread.IsAlive && !this._processorThread.Join(100))
            {
                Application.DoEvents();
            }
        }
    }
}

자발적인 종료가 안 돼 결국엔 작업 관리자를 이용해 강제 종료를 하게 되는데요, 그렇다면 저 코드는 사실상 (현실적으로 어차피 강제 종료해야 하므로) 필요 없는 단계에 속합니다.

그러니, 단순하게 다음의 정도만을 수행하게 바꿔도 될 것 같은데요,

public void WaitUntilFinished()
{
    Thread.Join(5000);
}

지난번처럼, 이를 위해 dnSpy를 사용하려고 했지만 아쉽게도 이번에는 OmniaMeaBase.dll의 전체적인 코드가 난독화 컴파일돼 있어 dnSpy의 IL 코드 어셈블로는 정상적으로 재컴파일이 안 됩니다.

따라서 이런 경우라면 .NET Profiler를 이용한 IL rewrite 기법을 사용해야 합니다.




한 가지 다행인 점은, Omea Reader의 경우 Plug-in 확장을 지원한다는 것입니다. 그래서 임의로 DLL을 만들어 "Tools" / "Options" 메뉴의 "Plugins"에 등록하면 런타임 시에 우리가 원하는 코드를 실행할 수 있습니다.

따라서 이전에 쓴 내용을 바탕으로,

C# - 실행 시에 메서드 가로채기 (.NET Framework 4.8)
; https://www.sysnet.pe.kr/2/0/13909

.NET Framework 2.0 DLL 프로젝트를 만들고 다음의 코드를 작성해 문제를 우회할 수 있습니다.

using JetBrains.Omea.OpenAPI;
using System;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Windows.Forms;

public class Class1 : IPlugin, IResourceDisplayer, IResourceTextProvider
{
    public IDisplayPane CreateDisplayPane(string resourceType)
    {
        return null;
    }

    public bool ProcessResourceText(IResource res, IResourceTextConsumer consumer)
    {
        return false;
    }

    public void Register()
    {
    }

    public void Shutdown()
    {
    }

    static int _replaced = 0;

    public void Startup()
    {
        if (Interlocked.Exchange(ref _replaced, 1) == 1)
        {
            return;
        }

        MethodInfo targetMethod = this.GetType().GetMethod("WaitUntilFinished");

        foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
        {
            try
            {
                if (Path.GetFileName(asm.Location).Equals("OmniaMeaBase.dll", StringComparison.OrdinalIgnoreCase) == true)
                {
                    Type type = asm.GetType("JetBrains.Omea.AsyncProcessing.AsyncProcessor");
                    if (type == null)
                    {
                        return;
                    }

                    MethodInfo mi = type.GetMethod("WaitUntilFinished", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
                    if (mi == null)
                    {
                        return;
                    }

                    // C# - 실행 시에 메서드 가로채기 (.NET Framework 4.8)
                    // https://www.sysnet.pe.kr/2/0/13909
                    ReplaceMethod.Replace(mi, targetMethod);
                }
            }
            catch { }
        }
    }

    public void WaitUntilFinished()
    {
        // https://stackoverflow.com/questions/14522540/how-to-close-a-messagebox-after-several-seconds
        var owner = new Form { TopMost = true };

        ThreadPool.QueueUserWorkItem((arg) =>
        {
            Thread.Sleep(2000);
            owner.Invoke(
                (WaitCallback)
                (   (cb) =>
                    {
                        if (!owner.IsDisposed)
                        {
                            owner.Close();
                        }
                    }
                )
            );
        });

        var dialogRes = MessageBox.Show(owner, "Waiting...", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }
}

이후로는, 몇 주간 사용해 봤는데 모두 잘 종료했습니다. ^^

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




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







[최초 등록일: ]
[최종 수정일: 5/8/2025]

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

비밀번호

댓글 작성자
 




1  2  [3]  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13875정성태1/24/20253398스크립트: 69. 파이썬 - multiprocessing 패키지의 spawn 모드로 동작하는 uvicorn의 workers
13874정성태1/24/20254082스크립트: 68. 파이썬 - multiprocessing Pool의 기본 프로세스 시작 모드(spawn, fork)
13873정성태1/23/20253263디버깅 기술: 217. WinDbg - PCI 장치 열거파일 다운로드1
13872정성태1/23/20253160오류 유형: 944. WinDbg - 원격 커널 디버깅이 연결은 되지만 Break (Ctrl + Break) 키를 눌러도 멈추지 않는 현상
13871정성태1/22/20253577Windows: 278. Windows - 윈도우를 다른 모니터 화면으로 이동시키는 단축키 (Window + Shift + 화살표)
13870정성태1/18/20254045개발 환경 구성: 741. WinDbg - 네트워크 커널 디버깅이 가능한 NIC 카드 지원 확대
13869정성태1/18/20253714개발 환경 구성: 740. WinDbg - _NT_SYMBOL_PATH 환경 변수에 설정한 경로로 심벌 파일을 다운로드하지 않는 경우
13868정성태1/17/20253401Windows: 277. Hyper-V - Windows 11 VM의 Enhanced Session 모드로 로그인을 할 수 없는 문제
13867정성태1/17/20254372오류 유형: 943. Hyper-V에 Windows 11 설치 시 "This PC doesn't currently meet Windows 11 system requirements" 오류
13866정성태1/16/20254562개발 환경 구성: 739. Windows 10부터 바뀐 device driver 서명 방법
13865정성태1/15/20254217오류 유형: 942. C# - .NET Framework 4.5.2 이하의 버전에서 HttpWebRequest로 https 호출 시 "System.Net.WebException" 예외 발생
13864정성태1/15/20254186Linux: 114. eBPF를 위해 필요한 SELinux 보안 정책
13863정성태1/14/20253625Linux: 113. Linux - 프로세스를 위한 전용 SELinux 보안 문맥 지정
13862정성태1/13/20253926Linux: 112. Linux - 데몬을 위한 SELinux 보안 정책 설정
13861정성태1/11/20254191Windows: 276. 명령행에서 원격 서비스를 동기/비동기로 시작/중지
13860정성태1/10/20253895디버깅 기술: 216. WinDbg - 2가지 유형의 식 평가 방법(MASM, C++)
13859정성태1/9/20254201디버깅 기술: 215. Windbg - syscall 이후 실행되는 KiSystemCall64 함수 및 SSDT 디버깅
13858정성태1/8/20254382개발 환경 구성: 738. PowerShell - 원격 호출 시 "powershell.exe"가 아닌 "pwsh.exe" 환경으로 명령어를 실행하는 방법
13857정성태1/7/20254435C/C++: 187. Golang - 콘솔 응용 프로그램을 Linux 데몬 서비스를 지원하도록 변경파일 다운로드1
13856정성태1/6/20253981디버깅 기술: 214. Windbg - syscall 단계까지의 Win32 API 호출 (예: Sleep)
13855정성태12/28/20244830오류 유형: 941. Golang - os.StartProcess() 사용 시 오류 정리
13854정성태12/27/20244859C/C++: 186. Golang - 콘솔 응용 프로그램을 NT 서비스를 지원하도록 변경파일 다운로드1
13853정성태12/26/20244330디버깅 기술: 213. Windbg - swapgs 명령어와 (Ring 0 커널 모드의) FS, GS Segment 레지스터
13852정성태12/25/20244937디버깅 기술: 212. Windbg - (Ring 3 사용자 모드의) FS, GS Segment 레지스터파일 다운로드1
13851정성태12/23/20244520디버깅 기술: 211. Windbg - 커널 모드 디버깅 상태에서 사용자 프로그램을 디버깅하는 방법
13850정성태12/23/20245185오류 유형: 940. "Application Information" 서비스를 중지한 경우, "This file does not have an app associated with it for performing this action."
1  2  [3]  4  5  6  7  8  9  10  11  12  13  14  15  ...