Маршалинг данных между управляемым и неуправляемым кодом - Обратный вызов P/Invoke и время существования делегата
ОГЛАВЛЕНИЕ
Обратный вызов P/Invoke и время существования делегата
Среда CLR позволяет передать делегат в неуправляемый код, чтобы тот мог обращаться к нему как к неуправляемому указателю функции. В сущности, среда CLR создает преобразователь адресов, который направляет вызовы из машинного кода к фактическому делегату, а затем к самой функции (см. рис. 10).

Обычно о времени существования делегата задумываться не приходится. При передаче делегата в неуправляемый код среда CLR гарантирует его существование на протяжении всего вызова.
Сложности возникают тогда, когда машинный код сохраняет копию указателя даже после завершения вызова и пытается впоследствии осуществить обратный вызов по этому указателю: в таком случае иногда приходится использовать GCHandle явным образом, чтобы сборщик мусора не подобрал делегат. Нужно иметь в виду, что при фиксации GCHandle может значительно снизиться производительность программы. К счастью, в нашем случае выделять фиксированный дескриптор GC не приходится, поскольку преобразователь адресов выделяется в неуправляемой куче и ссылается на делегат через ссылку, известную GC, поэтому преобразователь адресов перемещаться не может, и машинный код в любой момент способен вызвать делегат через неуправляемый указатель (если сам делегат еще существует).
Функция Marshal.GetFunctionPointerForDelegate преобразовывает делегат в указатель функции, но она никаким образом не гарантирует существование делегата в течение нужного срока. Обратите внимание на следующее объявление функции.
public delegate void PrintInteger(int n);
[DllImport(@"MarshalLib.dll", EntryPoint="CallDelegate")]
public static extern void CallDelegateDirectly(
IntPtr printIntegerProc);
Если вызвать Marshal.GetFunctionPointerForDelegate для этой функции и сохранить возвращенное значение IntPtr, то затем вы передадите его в функцию, которую вы собираетесь вызывать, следующим образом.
IntPtr printIntegerCallback = Marshal.GetFunctionPointerForDelegate(
new Lib.PrintInteger(MyPrintInteger));
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
CallDelegateDirectly(printIntegerCallback);
Существует возможность того, что делегат будет удален сборщиком мусора прежде, чем вы вызовете функцию CallDelegateDirectly. Тогда вы получите сообщение MDA о том, что обнаружено событие CallbackOnCollectedDelegate. Чтобы исправить эту ошибку, нужно либо сохранить в памяти ссылку на делегат, либо выделить дескриптор GC.
Если машинный код возвращает среде CLR неуправляемый указатель функции, то за сохранение кода функции отвечает машинный код. Здесь обычно проблем не возникает — если, конечно, код не находится в динамически загружаемой библиотеке DLL и не создается оперативно.