From 0af6b4d6b01a0503147fc0a83feee118f774d958 Mon Sep 17 00:00:00 2001 From: Dan Moseley Date: Tue, 3 Mar 2026 19:09:32 -0700 Subject: [PATCH] Add GC.KeepAlive example to Marshal.GetFunctionPointerForDelegate (#12365) * Add GC.KeepAlive example to GetFunctionPointerForDelegate docs The existing remarks say to keep the delegate from being collected but don't show how. Add a concrete code example using GC.KeepAlive, and explain when a static field is needed for persistent callbacks. Applies to both the non-generic and generic overloads. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update xml/System.Runtime.InteropServices/Marshal.xml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update xml/System.Runtime.InteropServices/Marshal.xml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> --- .../Marshal.xml | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/xml/System.Runtime.InteropServices/Marshal.xml b/xml/System.Runtime.InteropServices/Marshal.xml index beb0c9868ed..23c6401c254 100644 --- a/xml/System.Runtime.InteropServices/Marshal.xml +++ b/xml/System.Runtime.InteropServices/Marshal.xml @@ -4079,7 +4079,16 @@ The code retrieves a reference to an instance of Microsoft Word successfully. Ho ## Remarks The delegate `d` is converted to a function pointer that can be passed to unmanaged code using [the default platform calling convention](/dotnet/standard/native-interop/calling-conventions#platform-default-calling-convention). You can set the calling convention by applying the to the delegate. - You must manually keep the delegate from being collected by the garbage collector from managed code. The garbage collector does not track references to unmanaged code. + You must manually keep the delegate from being collected by the garbage collector from managed code. The garbage collector does not track references to unmanaged code. Use to prevent the delegate from being collected before the native call completes: + + ```csharp + var callback = new MyNativeCallback(MyManagedMethod); + IntPtr fnPtr = Marshal.GetFunctionPointerForDelegate(callback); + NativeMethod(fnPtr); + GC.KeepAlive(callback); // Prevent collection — fnPtr does not root the delegate. + ``` + + If native code stores the function pointer beyond the duration of the call, root the delegate for its entire lifetime — for example, by storing it in a `static` field. This API is unsupported in environments that don't support dynamic entry-point allocation, such as `ProcessDynamicCodePolicy` on Windows, `execmem off` in SELinux, and WebAssembly. @@ -4155,7 +4164,16 @@ The code retrieves a reference to an instance of Microsoft Word successfully. Ho ## Remarks The delegate `d` is converted to a function pointer that can be passed to unmanaged code by using [the default platform calling convention](/dotnet/standard/native-interop/calling-conventions#platform-default-calling-convention). You can set the calling convention by applying the to the delegate. - You must manually keep the delegate from being collected by the garbage collector from managed code. The garbage collector does not track references to unmanaged code. + You must manually keep the delegate from being collected by the garbage collector from managed code. The garbage collector does not track references to unmanaged code. Use to prevent the delegate from being collected before the native call completes: + + ```csharp + var callback = new MyNativeCallback(MyManagedMethod); + IntPtr fnPtr = Marshal.GetFunctionPointerForDelegate(callback); + NativeMethod(fnPtr); + GC.KeepAlive(callback); // Prevent collection — fnPtr does not root the delegate. + ``` + + If native code stores the function pointer beyond the duration of the call, root the delegate for its entire lifetime — for example, by storing it in a `static` field. This API is unsupported in environments that don't support dynamic entry-point allocation, such as `ProcessDynamicCodePolicy` on Windows, `execmem off` in SELinux, and WebAssembly.