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)
13725정성태9/4/20246414디버깅 기술: 201. WinDbg - sos threads 명령어 실행 시 "Failed to request ThreadStore"
13724정성태9/3/20248489닷넷: 2296. Win32/C# - 자식 프로세스로 HANDLE 상속파일 다운로드1
13723정성태9/2/20248559C/C++: 170. Windows - STARTUPINFO의 cbReserved2, lpReserved2 멤버 사용자 정의파일 다운로드2
13722정성태9/2/20246289C/C++: 169. C/C++ - CRT(C Runtime) 함수에 의존성이 없는 프로젝트 생성
13721정성태8/30/20246312C/C++: 168. Visual C++ CRT(C Runtime DLL: msvcr...dll)에 대한 의존성 제거 - 두 번째 이야기
13720정성태8/29/20246477VS.NET IDE: 193. C# - Visual Studio의 자식 프로세스 디버깅
13719정성태8/28/20246623Linux: 79. C++ - pthread_mutexattr_destroy가 없다면 메모리 누수가 발생할까요?
13718정성태8/27/20247819오류 유형: 921. Visual C++ - error C1083: Cannot open include file: 'float.h': No such file or directory [2]
13717정성태8/26/20247444VS.NET IDE: 192. Visual Studio 2022 - Windows XP / 2003용 C/C++ 프로젝트 빌드
13716정성태8/21/20247050C/C++: 167. Visual C++ - 윈도우 환경에서 _execv 동작 [1]
13715정성태8/19/20247812Linux: 78. 리눅스 C/C++ - 특정 버전의 glibc 빌드 (docker-glibc-builder)
13714정성태8/19/20247010닷넷: 2295. C# 12 - 기본 생성자(Primary constructors) (책 오타 수정) [3]
13713정성태8/16/20247888개발 환경 구성: 721. WSL 2에서의 Hyper-V Socket 연동
13712정성태8/14/20247524개발 환경 구성: 720. Synology NAS - docker 원격 제어를 위한 TCP 바인딩 추가
13711정성태8/13/20248305Linux: 77. C# / Linux - zombie process (defunct process) [1]파일 다운로드1
13710정성태8/8/20248332닷넷: 2294. C# 13 - (6) iterator 또는 비동기 메서드에서 ref와 unsafe 사용을 부분적으로 허용파일 다운로드1
13709정성태8/7/20248052닷넷: 2293. C# - safe/unsafe 문맥에 대한 C# 13의 (하위 호환을 깨는) 변화파일 다운로드1
13708정성태8/7/20247957개발 환경 구성: 719. ffmpeg / YoutubeExplode - mp4 동영상 파일로부터 Audio 파일 추출
13707정성태8/6/20248170닷넷: 2292. C# - 자식 프로세스의 출력이 4,096보다 많은 경우 Process.WaitForExit 호출 시 hang 현상파일 다운로드1
13706정성태8/5/20248198개발 환경 구성: 718. Hyper-V - 리눅스 VM에 새로운 디스크 추가
13705정성태8/4/20248430닷넷: 2291. C# 13 - (5) params 인자 타입으로 컬렉션 허용 [2]파일 다운로드1
13704정성태8/2/20248488닷넷: 2290. C# - 간이 dotnet-dump 프로그램 만들기파일 다운로드1
13703정성태8/1/20247674닷넷: 2289. "dotnet-dump ps" 명령어가 닷넷 프로세스를 찾는 방법
13702정성태7/31/20248288닷넷: 2288. Collection 식을 지원하는 사용자 정의 타입을 CollectionBuilder 특성으로 성능 보완파일 다운로드1
13701정성태7/30/20248626닷넷: 2287. C# 13 - (4) Indexer를 이용한 개체 초기화 구문에서 System.Index 연산자 허용파일 다운로드1
13700정성태7/29/20248435디버깅 기술: 200. DLL Export/Import의 Hint 의미
1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...