Default Marshaling for Delegates

A managed delegate is marshaled as a COM interface or as a function pointer, based on the calling mechanism:

  • For platform invoke, a delegate is marshaled as an unmanaged function pointer by default.
  • For COM interop, a delegate is marshaled as a COM interface of type _Delegate by default. The _Delegate interface is defined in the Mscorlib.tlb type library and contains the Delegate.DynamicInvoke method, which enables you to call the method that the delegate references.

The following table shows the marshaling options for the managed delegate data type. The MarshalAsAttribute attribute provides several UnmanagedType enumeration values to marshal delegates.

Enumeration type Description of unmanaged format
UnmanagedType.FunctionPtr An unmanaged function pointer.
UnmanagedType.Interface An interface of type _Delegate, as defined in Mscorlib.tlb.

Consider the following example code in which the methods of DelegateTestInterface are exported to a COM type library. Notice that only delegates marked with the ref (or ByRef) keyword are passed as In/Out parameters.

using System;
using System.Runtime.InteropServices;

public interface DelegateTest {
void m1(Delegate d);
void m2([MarshalAs(UnmanagedType.Interface)] Delegate d);   
void m3([MarshalAs(UnmanagedType.Interface)] ref Delegate d);  
void m4([MarshalAs(UnmanagedType.FunctionPtr)] Delegate d); 
void m5([MarshalAs(UnmanagedType.FunctionPtr)] ref Delegate d);   
}

Type library representation

importlib("mscorlib.tlb");
interface DelegateTest : IDispatch {
[id(...)] HRESULT m1([in] _Delegate* d);
[id(...)] HRESULT m2([in] _Delegate* d);
[id(...)] HRESULT m3([in, out] _Delegate** d);
[id(...)] HRESULT m4([in] int d);
[id(...)] HRESULT m5([in, out] int *d);
   };

A function pointer can be dereferenced, just as any other unmanaged function pointer can be dereferenced.

Note   A reference to the function pointer to a managed delegate held by unmanaged code does not prevent the common language runtime from performing garbage collection on the managed object.

For example, the following code is incorrect because the reference to the cb object, passed to the SetChangeHandler method, does not keep cb alive beyond the life of the Test method. Once the cb object is garbage collected, the function pointer passed to SetChangeHandler is no longer valid.

public class ExternalAPI {
   [DllImport("External.dll")]
   public static extern void SetChangeHandler(
      [MarshalAs(UnmanagedType.FunctionPtr)]ChangeDelegate d);
}
public delegate bool ChangeDelegate([MarshalAs(UnmanagedType.LPWStr) string S);
public class CallBackClass {
   public bool OnChange(string S){ return true;}
}
internal class DelegateTest {
   public static void Test() {
      CallBackClass cb = new CallBackClass();
      // Caution: The following reference on the cb object does not keep the 
      // object from being garbage collected after the Main method 
      // executes.
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));   
   }
}

To compensate for unexpected garbage collection, the caller must ensure that the cb object is kept alive as long as the unmanaged function pointer is in use. Optionally, you can have the unmanaged code notify the managed code when the function pointer is no longer needed, as the following example shows.

internal class DelegateTest {
   CallBackClass cb;
   // Called before ever using the callback function.
   public static void SetChangeHandler() {
      cb = new CallBackClass();
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
   }
   // Called after using the callback function for the last time.
   public static void RemoveChangeHandler() {
      // The cb object can be collected now. The unmanaged code is 
      // finished with the callback function.
      cb = null;
   }
}

See Also

Default Marshaling Behavior | Blittable and Non-Blittable Types | Directional Attributes | Copying and Pinning