• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dapplo / Dapplo.Windows / 22325004088

23 Feb 2026 09:11PM UTC coverage: 32.847% (-0.2%) from 33.028%
22325004088

push

github

Lakritzator
Merge branch 'master' of https://github.com/dapplo/Dapplo.Windows

619 of 2002 branches covered (30.92%)

Branch coverage included in aggregate %.

1702 of 5064 relevant lines covered (33.61%)

29.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.75
/src/Dapplo.Windows.Messages/SharedMessageWindow.cs
1
// Copyright (c) Dapplo and contributors. All rights reserved.
2
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3

4
using Dapplo.Windows.Messages.Enumerations;
5
using Dapplo.Windows.Messages.Native;
6
using Dapplo.Windows.Messages.Structs;
7
using System;
8
using System.Reactive.Disposables;
9
using System.Reactive.Linq;
10
using System.Reactive.Subjects;
11
using System.Runtime.InteropServices;
12
using System.Threading;
13

14
namespace Dapplo.Windows.Messages;
15

16
/// <summary>
17
/// Provides functionality to create an observable stream of Windows messages using a message-only window on a dedicated thread.
18
/// </summary>
19
public static class SharedMessageWindow
20
{
21
    #region PInvokes
22
    [DllImport("user32", SetLastError = true, CharSet = CharSet.Auto)]
23
    private static extern ushort RegisterClassEx(ref WNDCLASSEX lpwc);
24

25
    [DllImport("user32", SetLastError = true, CharSet = CharSet.Auto)]
26
    private static extern bool UnregisterClass(string lpClassName, nint hInstance);
27

28
    [DllImport("user32", SetLastError = true, CharSet = CharSet.Auto)]
29
    private static extern nint CreateWindowEx(uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, int x, int y, int nWidth, int nHeight, nint hWndParent, nint hMenu, nint hInstance, nint lpParam);
30

31
    [DllImport("user32")]
32
    private static extern bool GetMessage(out Msg lpMsg, nint hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
33

34
    [DllImport("user32")]
35
    private static extern bool TranslateMessage(ref Msg lpMsg);
36

37
    [DllImport("user32")]
38
    private static extern nint DispatchMessage(ref Msg lpMsg);
39

40
    [DllImport("user32")]
41
    private static extern nint DefWindowProc(nint hWnd, WindowsMessages msg, nint wParam, nint lParam);
42

43
    [DllImport("user32")]
44
    private static extern void PostQuitMessage(int nExitCode);
45

46
    [DllImport("user32")]
47
    private static extern bool PostMessage(nint hWnd, uint Msg, nint wParam, nint lParam);
48

49
    [DllImport("kernel32")]
50
    private static extern nint GetModuleHandle(string lpModuleName);
51
    #endregion
52

53
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
54
    private struct WNDCLASSEX
55
    {
56
        public uint cbSize; public uint style; public WndProc lpfnWndProc; public int cbClsExtra;
57
        public int cbWndExtra; public nint hInstance; public nint hIcon; public nint hCursor;
58
        public nint hbrBackground; public string lpszMenuName; public string lpszClassName; public nint hIconSm;
59
    }
60

61
    private static IObservable<WindowMessage> _sharedStream;
62
    private static readonly object _lock = new();
1✔
63
    private static readonly BehaviorSubject<nint> _handleSubject = new(0);
1✔
64

65
    /// <summary>
66
    /// Gets the current handle of the message window, or zero if no window is currently active.
67
    /// </summary>
68
    public static nint Handle => _handleSubject.Value;
25✔
69

70
    /// <summary>
71
    /// Gets an observable sequence of all window messages received by the application.
72
    /// </summary>
73
    /// <remarks>The returned observable is shared among all subscribers. Subscribing to this property allows
74
    /// monitoring of window messages as they occur. Unsubscribing from all observers will automatically release
75
    /// resources associated with the stream.</remarks>
76
    public static IObservable<WindowMessage> Messages
77
    {
78
        get
79
        {
80
            lock (_lock)
16✔
81
            {
82
                if (_sharedStream == null)
16✔
83
                {
84
                    _sharedStream = CreateBaseStream()
1✔
85
                        .Publish()
1✔
86
                        .RefCount();
1✔
87
                }
88
                return _sharedStream;
16✔
89
            }
90
        }
16✔
91
    }
92

93
    /// <summary>
94
    /// Subscribes to the shared message loop.
95
    /// </summary>
96
    /// <param name="onSetup">Invoked with the HWND when the window is created (or immediately if it already exists).</param>
97
    /// <param name="onTeardown">Invoked with the HWND when the window is destroyed OR when the subscription is disposed.</param>
98
    public static IObservable<WindowMessage> Listen(Action<nint> onSetup = null, Action<nint> onTeardown = null)
99
    {
100
        if (onSetup == null && onTeardown == null)
16!
101
        {
102
            return Messages;
16✔
103
        }
104

105
        return Observable.Create<WindowMessage>(observer =>
×
106
        {
×
107
            nint activeHwnd = 0;
×
108
            object handleLock = new();
×
109

×
110
            // Manage the Handle Lifecycle
×
111
            var handleSubscription = _handleSubject.Subscribe(hwnd =>
×
112
            {
×
113
                lock (handleLock)
×
114
                {
×
115
                    if (hwnd != 0)
×
116
                    {
×
117
                        activeHwnd = hwnd;
×
118
                        onSetup?.Invoke(activeHwnd);
×
119
                    }
×
120
                    else if (activeHwnd != 0)
×
121
                    {
×
122
                        // Window was destroyed
×
123
                        onTeardown?.Invoke(activeHwnd);
×
124
                        activeHwnd = 0;
×
125
                    }
×
126
                }
×
127
            });
×
128

×
129
            // Pass through the messages
×
130
            var messageSubscription = Messages.Subscribe(observer);
×
131

×
132
            // Ensure teardown runs if the consumer drops the subscription early
×
133
            return Disposable.Create(() =>
×
134
            {
×
135
                lock (handleLock)
×
136
                {
×
137
                    if (activeHwnd != 0)
×
138
                    {
×
139
                        onTeardown?.Invoke(activeHwnd);
×
140
                        activeHwnd = 0;
×
141
                    }
×
142
                }
×
143
                handleSubscription.Dispose();
×
144
                messageSubscription.Dispose();
×
145
            });
×
146
        });
×
147
    }
148

149
    /// <summary>
150
    /// Creates an observable sequence that emits Windows messages for a message-only window with the specified class name.
151
    /// </summary>
152
    /// <remarks>The window is created on a dedicated background thread with a single-threaded apartment
153
    /// state. Disposal of the returned observable triggers destruction of the window and completion of the sequence.
154
    /// This method is useful for scenarios requiring low-level message processing without a visible UI window.</remarks>
155
    /// <returns>An observable sequence of Windows messages received by the created message-only window. The sequence completes when the window is destroyed.</returns>
156
    private static IObservable<WindowMessage> CreateBaseStream()
157
    {
158
        return Observable.Create<WindowMessage>(observer =>
1✔
159
        {
1✔
160
            nint hwnd = 0;
15✔
161
            string className = "MsgLoop_" + Guid.NewGuid().ToString("N");
15✔
162
            WndProc wndProcDelegate = null;
15✔
163

1✔
164
            var thread = new Thread(() =>
15✔
165
            {
15✔
166
                wndProcDelegate = (hWnd, msg, wParam, lParam) =>
15✔
167
                {
15✔
168
                    if (msg == WindowsMessages.WM_DESTROY)
75✔
169
                    {
15✔
170
                        PostQuitMessage(0);
14✔
171
                        return 0;
14✔
172
                    }
15✔
173

15✔
174
                    var windowMessage = new WindowMessage(hWnd, msg, wParam, lParam);
61✔
175
                    observer.OnNext(windowMessage);
61✔
176
                    // Check if any subscriber claimed the message
15✔
177
                    if (windowMessage.Handled)
61!
178
                    {
15✔
179
                        return windowMessage.Result; // Return the custom value defined by the listener
×
180
                    }
15✔
181
                    return DefWindowProc(hWnd, msg, wParam, lParam);
61✔
182
                };
15✔
183

15✔
184
                // Register Class
15✔
185
                var wndClass = new WNDCLASSEX
15✔
186
                {
15✔
187
                    cbSize = (uint)Marshal.SizeOf<WNDCLASSEX>(),
15✔
188
                    lpfnWndProc = wndProcDelegate,
15✔
189
                    hInstance = GetModuleHandle(null),
15✔
190
                    lpszClassName = className
15✔
191
                };
15✔
192

15✔
193
                RegisterClassEx(ref wndClass);
15✔
194

15✔
195
                hwnd = CreateWindowEx(0, className, "NativeMessageWindow", 0, 0, 0, 0, 0, -3, 0, wndClass.hInstance, 0);
15✔
196

15✔
197
                // Trigger External Registrations
15✔
198
                if (hwnd != 0)
15✔
199
                {
15✔
200
                    _handleSubject.OnNext(hwnd);
15✔
201
                }
15✔
202

15✔
203
                while (GetMessage(out var msg, 0, 0, 0))
29✔
204
                {
15✔
205
                    TranslateMessage(ref msg);
14✔
206
                    DispatchMessage(ref msg);
14✔
207
                }
14✔
208

15✔
209
                // Trigger External Cleanup
15✔
210
                if (hwnd != 0)
14✔
211
                {
15✔
212
                    _handleSubject.OnNext(0);
14✔
213
                }
15✔
214
                
15✔
215
                UnregisterClass(className, wndClass.hInstance);
14✔
216
            });
29✔
217

1✔
218
            thread.SetApartmentState(ApartmentState.STA);
15✔
219
            thread.IsBackground = true;
15✔
220
            thread.Start();
15✔
221

1✔
222
            return Disposable.Create(() =>
15✔
223
            {
15✔
224
                if (hwnd != 0)
14✔
225
                {
15✔
226
                    PostMessage(hwnd, (uint)WindowsMessages.WM_DESTROY, 0, 0);
14✔
227
                }
15✔
228
            });
29✔
229
        });
1✔
230
    }
231
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc