Skip to content

Commit 839b134

Browse files
authored
Convert more interop to use function pointers (#43514)
1 parent 80194e6 commit 839b134

File tree

4 files changed

+68
-117
lines changed

4 files changed

+68
-117
lines changed

src/libraries/Common/src/Interop/Windows/IpHlpApi/Interop.NetworkInformation.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -508,8 +508,6 @@ internal unsafe struct MibUdp6RowOwnerPid
508508
internal ReadOnlySpan<byte> localAddrAsSpan => MemoryMarshal.CreateSpan(ref localAddr[0], 16);
509509
}
510510

511-
internal delegate void StableUnicastIpAddressTableDelegate(IntPtr context, IntPtr table);
512-
513511
[DllImport(Interop.Libraries.IpHlpApi)]
514512
internal static extern uint GetAdaptersAddresses(
515513
AddressFamily family,
@@ -563,10 +561,10 @@ internal static extern uint GetExtendedUdpTable(IntPtr pUdpTable, ref uint dwOut
563561
internal static extern uint CancelMibChangeNotify2(IntPtr notificationHandle);
564562

565563
[DllImport(Interop.Libraries.IpHlpApi)]
566-
internal static extern uint NotifyStableUnicastIpAddressTable(
564+
internal static extern unsafe uint NotifyStableUnicastIpAddressTable(
567565
AddressFamily addressFamily,
568566
out SafeFreeMibTable table,
569-
StableUnicastIpAddressTableDelegate callback,
567+
delegate* unmanaged<IntPtr, IntPtr, void> callback,
570568
IntPtr context,
571569
out SafeCancelMibChangeNotify notificationHandle);
572570
}

src/libraries/Common/src/Interop/Windows/User32/Interop.EnumWindows.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ internal partial class Interop
88
{
99
internal partial class User32
1010
{
11-
internal delegate bool EnumThreadWindowsCallback(IntPtr hWnd, IntPtr lParam);
12-
1311
[DllImport(Libraries.User32)]
14-
public static extern bool EnumWindows(EnumThreadWindowsCallback callback, IntPtr extraData);
12+
public static extern unsafe bool EnumWindows(delegate* unmanaged<IntPtr, IntPtr, Interop.BOOL> callback, IntPtr extraData);
1513
}
1614
}

src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Win32.cs

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,48 +18,47 @@ internal static partial class ProcessManager
1818
{
1919
public static IntPtr GetMainWindowHandle(int processId)
2020
{
21-
return new MainWindowFinder().FindMainWindow(processId);
21+
return MainWindowFinder.FindMainWindow(processId);
2222
}
2323
}
2424

25-
internal sealed class MainWindowFinder
25+
internal struct MainWindowFinder
2626
{
2727
private const int GW_OWNER = 4;
2828
private IntPtr _bestHandle;
2929
private int _processId;
3030

31-
public IntPtr FindMainWindow(int processId)
31+
public static unsafe IntPtr FindMainWindow(int processId)
3232
{
33-
_bestHandle = IntPtr.Zero;
34-
_processId = processId;
33+
MainWindowFinder instance;
3534

36-
Interop.User32.EnumWindows(EnumWindowsCallback, IntPtr.Zero);
35+
instance._bestHandle = IntPtr.Zero;
36+
instance._processId = processId;
3737

38-
return _bestHandle;
38+
Interop.User32.EnumWindows(&EnumWindowsCallback, (IntPtr)(void*)&instance);
39+
40+
return instance._bestHandle;
3941
}
4042

41-
private bool IsMainWindow(IntPtr handle)
43+
private static bool IsMainWindow(IntPtr handle)
4244
{
43-
if (Interop.User32.GetWindow(handle, GW_OWNER) != IntPtr.Zero || !Interop.User32.IsWindowVisible(handle))
44-
return false;
45-
46-
return true;
45+
return (Interop.User32.GetWindow(handle, GW_OWNER) == IntPtr.Zero) && Interop.User32.IsWindowVisible(handle);
4746
}
4847

49-
private bool EnumWindowsCallback(IntPtr handle, IntPtr extraParameter)
48+
[UnmanagedCallersOnly]
49+
private static unsafe Interop.BOOL EnumWindowsCallback(IntPtr handle, IntPtr extraParameter)
5050
{
51-
int processId;
51+
MainWindowFinder* instance = (MainWindowFinder*)extraParameter;
52+
53+
int processId = 0; // Avoid uninitialized variable if the window got closed in the meantime
5254
Interop.User32.GetWindowThreadProcessId(handle, out processId);
5355

54-
if (processId == _processId)
56+
if ((processId == instance->_processId) && IsMainWindow(handle))
5557
{
56-
if (IsMainWindow(handle))
57-
{
58-
_bestHandle = handle;
59-
return false;
60-
}
58+
instance->_bestHandle = handle;
59+
return Interop.BOOL.FALSE;
6160
}
62-
return true;
61+
return Interop.BOOL.TRUE;
6362
}
6463
}
6564

src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/TeredoHelper.cs

Lines changed: 45 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.ComponentModel;
66
using System.Diagnostics;
7+
using System.Runtime.InteropServices;
78
using System.Net.Sockets;
89
using System.Threading;
910

@@ -22,22 +23,15 @@ namespace System.Net.NetworkInformation
2223
// CancelMibChangeNotify2 guarantees that, by the time it returns, all calls to the callback will be complete
2324
// and that no new calls to the callback will be issued.
2425
//
25-
// The major concerns of the class are: 1) making sure none of the managed objects needed to handle a native
26-
// callback are GC'd before the callback, and 2) making sure no native callbacks will try to call into an unloaded
27-
// AppDomain.
2826

2927
internal class TeredoHelper
3028
{
31-
// Holds a list of all pending calls to NotifyStableUnicastIpAddressTable. Also used as a lock to protect its
32-
// contents.
33-
private static readonly List<TeredoHelper> s_pendingNotifications = new List<TeredoHelper>();
3429
private readonly Action<object> _callback;
3530
private readonly object _state;
3631

3732
private bool _runCallbackCalled;
3833

39-
// We explicitly keep a copy of this to prevent it from getting GC'd.
40-
private readonly Interop.IpHlpApi.StableUnicastIpAddressTableDelegate _onStabilizedDelegate;
34+
private GCHandle _gcHandle;
4135

4236
// Used to cancel notification after receiving the first callback, or when the AppDomain is going down.
4337
private SafeCancelMibChangeNotify? _cancelHandle;
@@ -46,123 +40,85 @@ private TeredoHelper(Action<object> callback, object state)
4640
{
4741
_callback = callback;
4842
_state = state;
49-
_onStabilizedDelegate = new Interop.IpHlpApi.StableUnicastIpAddressTableDelegate(OnStabilized);
50-
_runCallbackCalled = false;
43+
44+
_gcHandle = GCHandle.Alloc(this);
5145
}
5246

5347
// Returns true if the address table is already stable. Otherwise, calls callback when it becomes stable.
5448
// 'Unsafe' because it does not flow ExecutionContext to the callback.
55-
public static bool UnsafeNotifyStableUnicastIpAddressTable(Action<object> callback, object state)
49+
public static unsafe bool UnsafeNotifyStableUnicastIpAddressTable(Action<object> callback, object state)
5650
{
57-
if (callback == null)
58-
{
59-
NetEventSource.Fail(null, "UnsafeNotifyStableUnicastIpAddressTable called without specifying callback!");
60-
}
51+
Debug.Assert(callback != null);
6152

62-
TeredoHelper helper = new TeredoHelper(callback, state);
53+
TeredoHelper? helper = new TeredoHelper(callback, state);
54+
try
55+
{
56+
uint err = Interop.IpHlpApi.NotifyStableUnicastIpAddressTable(AddressFamily.Unspecified,
57+
out SafeFreeMibTable table, &OnStabilized, GCHandle.ToIntPtr(helper._gcHandle), out helper._cancelHandle);
6358

64-
uint err = Interop.IpHlpApi.ERROR_SUCCESS;
65-
SafeFreeMibTable table;
59+
table.Dispose();
6660

67-
lock (s_pendingNotifications)
68-
{
69-
// If OnAppDomainUnload gets the lock first, tell our caller that we'll finish async. Their AppDomain
70-
// is about to go down anyways. If we do, hold the lock until we've added helper to the
71-
// s_pendingNotifications list (if we're going to complete asynchronously).
72-
if (Environment.HasShutdownStarted)
61+
if (err == Interop.IpHlpApi.ERROR_IO_PENDING)
7362
{
63+
Debug.Assert(helper._cancelHandle != null && !helper._cancelHandle.IsInvalid);
64+
65+
// Suppress synchronous Dispose. Dispose will be called asynchronously by the callback.
66+
helper = null;
7467
return false;
7568
}
7669

77-
err = Interop.IpHlpApi.NotifyStableUnicastIpAddressTable(AddressFamily.Unspecified,
78-
out table, helper._onStabilizedDelegate, IntPtr.Zero, out helper._cancelHandle);
79-
80-
if (table != null)
70+
if (err != Interop.IpHlpApi.ERROR_SUCCESS)
8171
{
82-
table.Dispose();
72+
throw new Win32Exception((int)err);
8373
}
8474

85-
if (err == Interop.IpHlpApi.ERROR_IO_PENDING)
86-
{
87-
if (helper._cancelHandle.IsInvalid)
88-
{
89-
NetEventSource.Fail(null, "CancelHandle invalid despite returning ERROR_IO_PENDING");
90-
}
91-
92-
// Async completion: add us to the s_pendingNotifications list so we'll be canceled in the
93-
// event of an AppDomain unload.
94-
s_pendingNotifications.Add(helper);
95-
return false;
96-
}
75+
return true;
9776
}
98-
99-
if (err != Interop.IpHlpApi.ERROR_SUCCESS)
77+
finally
10078
{
101-
throw new Win32Exception((int)err);
79+
helper?.Dispose();
10280
}
103-
104-
return true;
10581
}
10682

107-
private void RunCallback(object? o)
83+
private void Dispose()
10884
{
109-
if (!_runCallbackCalled)
110-
{
111-
NetEventSource.Fail(null, "RunCallback called without setting runCallbackCalled!");
112-
}
113-
114-
// If OnAppDomainUnload beats us to the lock, do nothing: the AppDomain is going down soon anyways.
115-
// Otherwise, wait until the call to CancelMibChangeNotify2 is done before giving it up.
116-
lock (s_pendingNotifications)
117-
{
118-
if (Environment.HasShutdownStarted)
119-
{
120-
return;
121-
}
85+
_cancelHandle?.Dispose();
12286

123-
#if DEBUG
124-
bool successfullyRemoved = s_pendingNotifications.Remove(this);
125-
if (!successfullyRemoved)
126-
{
127-
NetEventSource.Fail(null, "RunCallback for a TeredoHelper which is not in s_pendingNotifications!");
128-
}
129-
#else
130-
s_pendingNotifications.Remove(this);
131-
#endif
132-
133-
if ((_cancelHandle == null || _cancelHandle.IsInvalid))
134-
{
135-
NetEventSource.Fail(null, "Invalid cancelHandle in RunCallback");
136-
}
137-
138-
_cancelHandle.Dispose();
139-
}
140-
141-
_callback.Invoke(_state);
87+
if (_gcHandle.IsAllocated)
88+
_gcHandle.Free();
14289
}
14390

14491
// This callback gets run on a native worker thread, which we don't want to allow arbitrary user code to
14592
// execute on (it will block AppDomain unload, for one). Free the MibTable and delegate (exactly once)
14693
// to the managed ThreadPool for the rest of the processing.
147-
//
148-
// We can't use SafeHandle here for table because the marshaller doesn't support them in reverse p/invokes.
149-
// We won't get an AppDomain unload here anyways, because OnAppDomainUnloaded will block until all of these
150-
// callbacks are done.
151-
private void OnStabilized(IntPtr context, IntPtr table)
94+
[UnmanagedCallersOnly]
95+
private static void OnStabilized(IntPtr context, IntPtr table)
15296
{
15397
Interop.IpHlpApi.FreeMibTable(table);
15498

99+
TeredoHelper helper = (TeredoHelper)GCHandle.FromIntPtr(context).Target!;
100+
155101
// Lock the TeredoHelper instance to ensure that only the first call to OnStabilized will get to call
156102
// RunCallback. This is the only place that TeredoHelpers get locked, as individual instances are not
157103
// exposed to higher layers, so there's no chance for deadlock.
158-
if (!_runCallbackCalled)
104+
if (!helper._runCallbackCalled)
159105
{
160-
lock (this)
106+
lock (helper)
161107
{
162-
if (!_runCallbackCalled)
108+
if (!helper._runCallbackCalled)
163109
{
164-
_runCallbackCalled = true;
165-
ThreadPool.QueueUserWorkItem(RunCallback, null);
110+
helper._runCallbackCalled = true;
111+
112+
ThreadPool.QueueUserWorkItem(o =>
113+
{
114+
TeredoHelper helper = (TeredoHelper)o!;
115+
116+
// We are intentionally not calling Dispose synchronously inside the OnStabilized callback.
117+
// According to MSDN, calling CancelMibChangeNotify2 inside the callback results into deadlock.
118+
helper.Dispose();
119+
120+
helper._callback.Invoke(helper._state);
121+
}, helper);
166122
}
167123
}
168124
}

0 commit comments

Comments
 (0)