Message Loop

한 달에 한번은 글을 써야지 하는 마음으로…
뭘 쓸지 고민하다 얼마 전 개발한 내용을 공유 드릴까 합니다.

이번 주제는 닷넷인듯 닷넷아닌 닷넷같은 주제인데 약간 교양 수준으로 봐주셔도 되고
마지막에 이걸 알면 이런걸 좀 더 잘 이해할수 있다 하는 내용도 있으니 참고해 주시기 바랍니다.

프로그램을 만들 때 컨트롤을 만들고 개발하면 당연하다 생각하는 부분들이 있습니다.
그려지고, 클릭되고, 이동하고 그런것들이죠.

이 모두가 당연하게 되는 것들이지만 실제론 좀 더 복잡한 구조가 숨어 있습니다.

우선 WPF가 사용하는 OS(Operating system) 즉 Windows에 대해서 조금 알고 갈 필요가 있습니다.
일반적으로 OS는 SDK(Software development kit)를 제공해 줍니다.
Windows 의 경우 SDK를 C언어로 제공 하고 있고 그 안에는 여러 라이브러리와 헤더파일이 있습니다.

그리고 그 헤더파일에 선언 되어 있는 함수들을 API(Application Programming Interface)라고 합니다.
그것들을 통해서 우리는 프로그램을 개발할수 있습니다.

이런 WinAPI 를 통해서 만드는 간단한 샘플은 VS에서 제공해 줍니다.
image

안에 있는 여러 소스 중에서 우리가 봐야 할 것은 단 7줄입니다.

    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

Windows 에서 모든 프로그램은 각자의 메세지 큐를 가지고 있고
OS는 해당 프로그램에 맞는 메세지를 큐에 넣어 줍니다.
프로그램은 자신의 메세지 큐에 메세지가 들어 오면
그것을 가져다가 메세지에 맞는 동작을 합니다.

WPF는 그런 복잡한 내용들을 객체지향적으로 잘 분리하고
랩핑하여 쓰는사람에게 편의성을 제공하는 프레임워크입니다.

즉, API를 안쓰는게 아니라 내부에 숨겨 놓은 겁니다.

실제 코드로 보면 Dispatcher 에서 동작하는 걸로 보이네요.

말이 길어 졌는데 간단히 정리하면
Windows에서는 모든 이벤트 처리는 메세지큐에 있는 메세지를
펌핑해서 처리 한다. 이를 메세지 루프라고 부른다

입니다.

그럼 마지막으로 이걸 왜 알아야 되냐? 입니다.
WPF가 모든 API를 랩핑해 놔서 WPF 기본 기능으로
Windows의 모든 기능을 쓸수 있다면 위 내용은 몰라도 상관이 없습니다.

하지만 몇몇 기능에 대해서는 아직 지원 되지 않는 내용이 있습니다.

가장 대표적인 예로 Clipboard Change 입니다.

자신이 만들 프로그램에서 Windows 의 클립보드를 쓰는 경우가 있을수 있습니다.
.net core WPF에서 클립보드를 쓸때는 System.Windows.Clipboard 클래스를 통해서
클립보드에 데이터를 쓰고 읽을 수 있습니다.

하지만 클립보드가 변경되었을때 알림을 받을수 있는 기능은 아직 구현 되어 있지 않습니다.
메모장에서 글씨가 복사 되었을때 내쪽에서는 붙여 넣는 데이터가 있다는걸 알아야 할수 있습니다.

해당 기능을 구현한 예를 보여 드리겠습니다.

    [DllImport("User32.dll")]
    public static extern IntPtr SetClipboardViewer(IntPtr hWnd);
//........
    WindowInteropHelper helper = new(this);
    HwndSource.FromHwnd(hWnd).AddHook(new HwndSourceHook(WndProc));
    next = SetClipboardViewer(hWnd);
// .......
private nint WndProc(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled)
{
    switch ((User32.WindowMessage)msg)
    {
        case User32.WindowMessage.WM_CHANGECBCHAIN:
            if (wParam == next)
                next = lParam;
            else if (next != 0)
                User32.SendMessage(next, (User32.WindowMessage)msg, wParam, lParam);
            break;
        case User32.WindowMessage.WM_DRAWCLIPBOARD:
            ClipboardChanged();
            User32.SendMessage(next, (User32.WindowMessage)msg, wParam, lParam);
            break;
    }
    return IntPtr.Zero;
}

위에서 말씀 드린 메세지루프나 너무 길어서 뺀 WndProc 같은걸 어느 정도
이해만 하고 계시면 이런 문제가 생겼을때
검색 된 소스를 보고 이해 하는데 도움이 되실거 같네요.

9개의 좋아요

윈도우 프로그래밍을 경험하지 못했고, C나 C++ 에 일자무식에 가까운 저도 이해하기 매우 쉬운 것 같습니다.좋은 글 감사합니다.

그런데, 아래의 케이스는 선뜻 이해하기 쉽지 않은 것 같습니다.

내 앱은 그냥 키보드 이벤트(ctrl+v)를 받을 때만 클립보드를 열람하면 되지 않나요?
시스템에서 일어나는 모든 클립보드 이벤트를 처리한다는 게 선뜻 이해되지 않습니다.

내 앱이 클립보드 관리 프로그램이 아닌데도, 클립보드 이벤트를 받아야 하는 경우라면 어떤 게 있을까요? 실무적으로 경험한 것이 있다면 공유부탁드립니다.

6개의 좋아요

[틀린 그림 찾기]
image
image

요렇게 오피스의 경우에도 붙여넣기 가능 할때와 불가능 할때 아이콘이 다릅니다.

저희 제품도 마찬가지 인데요


Ctrl+V 이전에 클립보드 체인지를 감지 하여
CanExecute 를 처리해 주면 좀 더 안정적이고 불필요한 동작을 예방할수 있습니다.

추가로 ClipBoard에는 여러 타입의 데이터가 들어 올 수 있습니다.

이것에 대해서도 미리 판단 하여 우리가 받아 들일 타입인지,
아닌지도 알수 있습니다.

8개의 좋아요

호오!! :heart_eyes:

4개의 좋아요

역시 캐드 종류 프로그램은 WPF를 베이스로 하는게 '와따’같네요…
엄청납니다…