Skip to content

Commit 79eed8a

Browse files
authored
Swap WinForm for native window proc (#5530)
## Issue The recent addition of a hidden WinForm pulled in a number of binaries that in total were over 30 MB. This is overkill for what we needed. ## Change Replace that hidden WinForm with a native window proc handler.
1 parent f67d56c commit 79eed8a

File tree

4 files changed

+185
-27
lines changed

4 files changed

+185
-27
lines changed

.github/actions/spelling/expect.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ winreg
599599
winrtact
600600
winstring
601601
WMI
602-
Wnd
602+
wnd
603603
WNDCLASS
604604
WNDCLASSEX
605605
workaround

src/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
55
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
6-
<UseWindowsForms>true</UseWindowsForms>
76
<ImplicitUsings>enable</ImplicitUsings>
87
<Nullable>enable</Nullable>
98
<SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Runtime.InteropServices;
5+
using System.Threading;
6+
7+
namespace ConfigurationRemotingServer
8+
{
9+
internal class EnvironmentChangeListener
10+
{
11+
// Window message constants
12+
private const int WM_SETTINGCHANGE = 0x001A;
13+
private const int WM_QUIT = 0x0012;
14+
15+
// User32.dll imports
16+
[DllImport("user32.dll")]
17+
private static extern IntPtr CreateWindowEx(
18+
uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle,
19+
int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu,
20+
IntPtr hInstance, IntPtr lpParam);
21+
22+
[DllImport("user32.dll")]
23+
private static extern bool DestroyWindow(IntPtr hWnd);
24+
25+
[DllImport("user32.dll")]
26+
private static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
27+
28+
[DllImport("user32.dll")]
29+
private static extern bool GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
30+
31+
[DllImport("user32.dll")]
32+
private static extern bool PostMessageW(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
33+
34+
[DllImport("user32.dll")]
35+
private static extern bool TranslateMessage(ref MSG lpMsg);
36+
37+
[DllImport("user32.dll")]
38+
private static extern IntPtr DispatchMessage(ref MSG lpMsg);
39+
40+
[DllImport("user32.dll")]
41+
private static extern ushort RegisterClass(ref WNDCLASS lpWndClass);
42+
43+
[DllImport("kernel32.dll")]
44+
private static extern IntPtr GetModuleHandle(string? lpModuleName);
45+
46+
// Windows message structure
47+
[StructLayout(LayoutKind.Sequential)]
48+
public struct MSG
49+
{
50+
public IntPtr hwnd;
51+
public uint message;
52+
public IntPtr wParam;
53+
public IntPtr lParam;
54+
public uint time;
55+
public POINT pt;
56+
public uint lPrivate;
57+
}
58+
59+
[StructLayout(LayoutKind.Sequential)]
60+
public struct POINT
61+
{
62+
public int x;
63+
public int y;
64+
}
65+
66+
// Window class structure
67+
[StructLayout(LayoutKind.Sequential)]
68+
public struct WNDCLASS
69+
{
70+
public uint style;
71+
public IntPtr lpfnWndProc;
72+
public int cbClsExtra;
73+
public int cbWndExtra;
74+
public IntPtr hInstance;
75+
public IntPtr hIcon;
76+
public IntPtr hCursor;
77+
public IntPtr hBackground;
78+
[MarshalAs(UnmanagedType.LPStr)]
79+
public string? lpszMenuName;
80+
[MarshalAs(UnmanagedType.LPStr)]
81+
public string lpszClassName;
82+
}
83+
84+
// Window procedure delegate
85+
private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
86+
// Keep a reference to the delegate to prevent it from being garbage collected
87+
private static WndProcDelegate? _wndProcDelegate;
88+
89+
private IntPtr _hwnd;
90+
private Thread _thread;
91+
92+
public delegate void EnvironmentChangedEventHandler();
93+
public static event EnvironmentChangedEventHandler? EnvironmentChanged;
94+
95+
public EnvironmentChangeListener()
96+
{
97+
_thread = new Thread(MessageLoop);
98+
_thread.Start();
99+
}
100+
101+
public void Stop()
102+
{
103+
if (PostMessageW(_hwnd, WM_QUIT, IntPtr.Zero, IntPtr.Zero))
104+
{
105+
_thread.Join();
106+
}
107+
}
108+
109+
private void MessageLoop()
110+
{
111+
// Register window class
112+
string className = "EnvironmentMonitorClass";
113+
_wndProcDelegate = WndProc;
114+
115+
WNDCLASS wndClass = new WNDCLASS
116+
{
117+
style = 0,
118+
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
119+
cbClsExtra = 0,
120+
cbWndExtra = 0,
121+
hInstance = GetModuleHandle(null),
122+
hIcon = IntPtr.Zero,
123+
hCursor = IntPtr.Zero,
124+
hBackground = IntPtr.Zero,
125+
lpszMenuName = "",
126+
lpszClassName = className
127+
};
128+
129+
RegisterClass(ref wndClass);
130+
131+
// Create hidden window
132+
const uint WS_OVERLAPPED = 0x00000000;
133+
const uint WS_EX_TOOL_WINDOW = 0x00000080;
134+
135+
_hwnd = CreateWindowEx(
136+
WS_EX_TOOL_WINDOW,
137+
className,
138+
"Environment Monitor",
139+
WS_OVERLAPPED,
140+
0, 0, 0, 0,
141+
IntPtr.Zero, IntPtr.Zero,
142+
GetModuleHandle(null), IntPtr.Zero);
143+
144+
if (_hwnd == IntPtr.Zero)
145+
{
146+
return;
147+
}
148+
149+
MSG msg;
150+
while (GetMessage(out msg, IntPtr.Zero, 0, 0))
151+
{
152+
TranslateMessage(ref msg);
153+
DispatchMessage(ref msg);
154+
}
155+
156+
// Clean up
157+
DestroyWindow(_hwnd);
158+
}
159+
160+
private static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
161+
{
162+
switch (msg)
163+
{
164+
case WM_SETTINGCHANGE:
165+
// Check if this is an environment variable change notification
166+
string? msgInfo = Marshal.PtrToStringAnsi(lParam);
167+
if (msgInfo == "Environment")
168+
{
169+
if (EnvironmentChanged != null)
170+
{
171+
EnvironmentChanged.Invoke();
172+
}
173+
}
174+
break;
175+
}
176+
177+
return DefWindowProc(hWnd, msg, wParam, lParam);
178+
}
179+
}
180+
}

src/ConfigurationRemotingServer/Program.cs

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using Microsoft.Management.Configuration;
1010
using Microsoft.Management.Configuration.Processor;
1111
using Microsoft.Management.Configuration.Processor.Helpers;
12-
using Microsoft.Win32;
1312
using WinRT;
1413
using IConfigurationSetProcessorFactory = global::Microsoft.Management.Configuration.IConfigurationSetProcessorFactory;
1514

@@ -82,16 +81,8 @@ static int Main(string[] args)
8281
string staticsCallback = args[1];
8382

8483
// Listen for setting change message and update PATH if needed.
85-
HiddenForm hiddenForm = new HiddenForm();
86-
var settingChangedListenerThread = new Thread(
87-
() =>
88-
{
89-
SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
90-
Application.Run(hiddenForm);
91-
SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged;
92-
});
93-
94-
settingChangedListenerThread.Start();
84+
EnvironmentChangeListener.EnvironmentChanged += OnEnvironmentChanged;
85+
EnvironmentChangeListener environmentChangeListener = new EnvironmentChangeListener();
9586

9687
try
9788
{
@@ -168,23 +159,11 @@ static int Main(string[] args)
168159
}
169160
finally
170161
{
171-
hiddenForm.BeginInvoke(new Action(() => { hiddenForm.Close(); }));
172-
settingChangedListenerThread.Join();
173-
}
174-
}
175-
176-
private class HiddenForm : Form
177-
{
178-
public HiddenForm()
179-
{
180-
this.Width = 0;
181-
this.Height = 0;
182-
this.Visible = false;
183-
this.Load += (sender, e) => this.Hide();
162+
environmentChangeListener.Stop();
184163
}
185164
}
186165

187-
private static void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
166+
private static void OnEnvironmentChanged()
188167
{
189168
PathEnvironmentVariableHandler.UpdatePath();
190169
}

0 commit comments

Comments
 (0)