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