Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use function pointers for native TaskDialog and Enum*Windows callbacks #4003

Merged
merged 1 commit into from
Nov 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public unsafe partial struct TASKDIALOGCONFIG
public char* pszCollapsedControlText;
public IconUnion footerIcon;
public char* pszFooter;
public IntPtr pfCallback;
public delegate* unmanaged<IntPtr, TDN, IntPtr, IntPtr, IntPtr, HRESULT> pfCallback;
public IntPtr lpCallbackData;
/// <summary>
/// "width of the Task Dialog's client area in DLU's. If 0, Task Dialog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,18 @@ internal static partial class User32
{
public delegate BOOL EnumChildWindowsCallback(IntPtr hWnd);

private delegate BOOL EnumChildWindowsNativeCallback(IntPtr hWnd, IntPtr lParam);

private static readonly EnumChildWindowsNativeCallback s_enumChildWindowsNativeCallback = HandleEnumChildWindowsNativeCallback;

[DllImport(Libraries.User32, ExactSpelling = true)]
private static extern BOOL EnumChildWindows(IntPtr hwndParent, EnumChildWindowsNativeCallback lpEnumFunc, IntPtr lParam);
private static extern unsafe BOOL EnumChildWindows(IntPtr hwndParent, delegate* unmanaged<IntPtr, IntPtr, BOOL> lpEnumFunc, IntPtr lParam);

public static BOOL EnumChildWindows(IntPtr hwndParent, EnumChildWindowsCallback lpEnumFunc)
public static unsafe BOOL EnumChildWindows(IntPtr hwndParent, EnumChildWindowsCallback lpEnumFunc)
{
// We pass a static delegate to the native function and supply the callback as
// We pass a function pointer to the native function and supply the callback as
// reference data, so that the CLR doesn't need to generate a native code block for
// each callback delegate instance (for storing the closure pointer).
var gcHandle = GCHandle.Alloc(lpEnumFunc);
try
{
return EnumChildWindows(hwndParent, s_enumChildWindowsNativeCallback, GCHandle.ToIntPtr(gcHandle));
return EnumChildWindows(hwndParent, &HandleEnumChildWindowsNativeCallback, (IntPtr)gcHandle);
}
finally
{
Expand All @@ -48,9 +44,10 @@ public static BOOL EnumChildWindows(HandleRef hwndParent, EnumChildWindowsCallba
return result;
}

[UnmanagedCallersOnly]
private static BOOL HandleEnumChildWindowsNativeCallback(IntPtr hWnd, IntPtr lParam)
{
return ((EnumChildWindowsCallback)GCHandle.FromIntPtr(lParam).Target!)(hWnd);
return ((EnumChildWindowsCallback)((GCHandle)lParam).Target!)(hWnd);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,29 @@ internal static partial class User32
{
public delegate BOOL EnumThreadWindowsCallback(IntPtr hWnd);

private delegate BOOL EnumThreadWindowsNativeCallback(IntPtr hWnd, IntPtr lParam);

private static readonly EnumThreadWindowsNativeCallback s_enumThreadWindowsNativeCallback = HandleEnumThreadWindowsNativeCallback;

[DllImport(Libraries.User32, ExactSpelling = true)]
private static extern BOOL EnumThreadWindows(uint dwThreadId, EnumThreadWindowsNativeCallback lpfn, IntPtr lParam);
private static extern unsafe BOOL EnumThreadWindows(uint dwThreadId, delegate* unmanaged<IntPtr, IntPtr, BOOL> lpfn, IntPtr lParam);

public static BOOL EnumThreadWindows(uint dwThreadId, EnumThreadWindowsCallback lpfn)
public static unsafe BOOL EnumThreadWindows(uint dwThreadId, EnumThreadWindowsCallback lpfn)
{
// We pass a static delegate to the native function and supply the callback as
// We pass a function pointer to the native function and supply the callback as
// reference data, so that the CLR doesn't need to generate a native code block for
// each callback delegate instance (for storing the closure pointer).
var gcHandle = GCHandle.Alloc(lpfn);
try
{
return EnumThreadWindows(dwThreadId, s_enumThreadWindowsNativeCallback, GCHandle.ToIntPtr(gcHandle));
return EnumThreadWindows(dwThreadId, &HandleEnumThreadWindowsNativeCallback, (IntPtr)gcHandle);
}
finally
{
gcHandle.Free();
}
}

[UnmanagedCallersOnly]
private static BOOL HandleEnumThreadWindowsNativeCallback(IntPtr hWnd, IntPtr lParam)
{
return ((EnumThreadWindowsCallback)GCHandle.FromIntPtr(lParam).Target!)(hWnd);
return ((EnumThreadWindowsCallback)((GCHandle)lParam).Target!)(hWnd);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,29 @@ internal static partial class User32
{
public delegate BOOL EnumWindowsCallback(IntPtr hWnd);

private delegate BOOL EnumWindowsNativeCallback(IntPtr hWnd, IntPtr lParam);

private static readonly EnumWindowsNativeCallback s_enumWindowsNativeCallback = HandleEnumWindowsNativeCallback;

[DllImport(Libraries.User32, ExactSpelling = true, SetLastError = true)]
private static extern BOOL EnumWindows(EnumWindowsNativeCallback lpEnumFunc, IntPtr lParam);
private static extern unsafe BOOL EnumWindows(delegate* unmanaged<IntPtr, IntPtr, BOOL> lpEnumFunc, IntPtr lParam);

public static BOOL EnumWindows(EnumWindowsCallback lpEnumFunc)
public static unsafe BOOL EnumWindows(EnumWindowsCallback lpEnumFunc)
{
// We pass a static delegate to the native function and supply the callback as
// We pass a function pointer to the native function and supply the callback as
// reference data, so that the CLR doesn't need to generate a native code block for
// each callback delegate instance (for storing the closure pointer).
var gcHandle = GCHandle.Alloc(lpEnumFunc);
try
{
return EnumWindows(s_enumWindowsNativeCallback, GCHandle.ToIntPtr(gcHandle));
return EnumWindows(&HandleEnumWindowsNativeCallback, (IntPtr)gcHandle);
}
finally
{
gcHandle.Free();
}
}

[UnmanagedCallersOnly]
private static BOOL HandleEnumWindowsNativeCallback(IntPtr hWnd, IntPtr lParam)
{
return ((EnumWindowsCallback)GCHandle.FromIntPtr(lParam).Target!)(hWnd);
return ((EnumWindowsCallback)((GCHandle)lParam).Target!)(hWnd);
}
}
}
36 changes: 2 additions & 34 deletions src/System.Windows.Forms/src/System/Windows/Forms/TaskDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,24 +68,6 @@ public partial class TaskDialog : IWin32Window
/// </remarks>
private const User32.WM ContinueButtonClickHandlingMessage = User32.WM.APP + 0x3FFF;

/// <summary>
/// The delegate for <see cref="HandleTaskDialogNativeCallback"/> which is
/// marshaled as native callback.
/// </summary>
/// <remarks>
/// <para>
/// This delegate must be kept alive (protecting it from garbage collection)
/// to ensure the native function pointer doesn't become invalid.
/// </para>
/// </remarks>
// Because marshaling a delegate this will allocate some memory required
// to store the native code for the function pointer, we only do this
// once by using a static function, and then identify the actual TaskDialog
// instance by using a GCHandle in the reference data field (like an
// object pointer).
private static readonly ComCtl32.PFTASKDIALOGCALLBACK s_callbackProcDelegate =
HandleTaskDialogNativeCallback;

private TaskDialogPage? _boundPage;

/// <summary>
Expand Down Expand Up @@ -282,6 +264,7 @@ public IntPtr Handle

private static void FreeConfig(IntPtr ptrToFree) => Marshal.FreeHGlobal(ptrToFree);

[UnmanagedCallersOnly]
private static HRESULT HandleTaskDialogNativeCallback(
IntPtr hwnd,
ComCtl32.TDN msg,
Expand Down Expand Up @@ -578,21 +561,6 @@ private unsafe TaskDialogButton ShowDialogInternal(IntPtr hwndOwner,
}

_waitingNavigationPages.Clear();

// We need to ensure the callback delegate is not garbage-collected
// as long as TaskDialogIndirect doesn't return, by calling
// GC.KeepAlive().
//
// This is not an exaggeration, as the comment for GC.KeepAlive()
// says the following:
// The JIT is very aggressive about keeping an
// object's lifetime to as small a window as possible, to the point
// where a 'this' pointer isn't considered live in an instance method
// unless you read a value from the instance.
//
// Note: As this is a static field, the call to GC.KeepAlive() might be
// superfluous here, but we still do it to be safe.
GC.KeepAlive(s_callbackProcDelegate);
}
}
finally
Expand Down Expand Up @@ -1387,7 +1355,7 @@ private unsafe void BindPageAndAllocateConfig(
pszFooter = MarshalString(page.Footnote?.Text),
nDefaultButton = defaultButtonID,
nDefaultRadioButton = defaultRadioButtonID,
pfCallback = Marshal.GetFunctionPointerForDelegate(s_callbackProcDelegate),
pfCallback = &HandleTaskDialogNativeCallback,
lpCallbackData = _instanceHandlePtr
};

Expand Down