callbackOnCollectedDelegate MDA
Nota
Este artigo é específico do .NET Framework. Ele não se aplica a implementações mais recentes do .NET, incluindo o .NET 6 e versões posteriores.
O callbackOnCollectedDelegate
assistente de depuração gerenciado (MDA) é ativado se um delegado for empacotado de código gerenciado para não gerenciado como um ponteiro de função e um retorno de chamada for colocado nesse ponteiro de função depois que o delegado tiver sido coletado.
Sintomas
As violações de acesso ocorrem ao tentar chamar o código gerenciado por meio de ponteiros de função que foram obtidos de delegados gerenciados. Essas falhas, embora não sejam bugs do Common Language Runtime (CLR), podem parecer ser assim porque a violação de acesso ocorre no código CLR.
A falha não é consistente; Às vezes, a chamada no ponteiro da função é bem-sucedida e, às vezes, falha. A falha pode ocorrer apenas sob carga pesada ou em um número aleatório de tentativas.
Motivo
O delegado a partir do qual o ponteiro de função foi criado e exposto ao código não gerenciado foi coletado lixo. Quando o componente não gerenciado tenta chamar o ponteiro de função, ele gera uma violação de acesso.
A falha aparece aleatória porque depende de quando ocorre a coleta de lixo. Se um delegado estiver qualificado para coleta, a coleta de lixo poderá ocorrer após o retorno de chamada e a chamada for bem-sucedida. Em outros momentos, a coleta de lixo ocorre antes do retorno de chamada, o retorno de chamada gera uma violação de acesso e o programa é interrompido.
A probabilidade da falha depende do tempo entre a organização do delegado e o retorno de chamada no ponteiro da função, bem como da frequência das coletas de lixo. A falha é esporádica se o tempo entre a organização do delegado e o retorno de chamada subsequente for curto. Este é geralmente o caso se o método não gerenciado que recebe o ponteiro de função não salva o ponteiro de função para uso posterior, mas em vez disso chama de volta o ponteiro de função imediatamente para concluir sua operação antes de retornar. Da mesma forma, mais coletas de lixo ocorrem quando um sistema está sob carga pesada, o que torna mais provável que uma coleta de lixo ocorra antes do retorno de chamada.
Resolução
Depois que um delegado tiver sido organizado como um ponteiro de função não gerenciado, o coletor de lixo não poderá rastrear sua vida útil. Em vez disso, seu código deve manter uma referência ao delegado durante o tempo de vida do ponteiro de função não gerenciado. Mas antes de fazer isso, você primeiro deve identificar qual delegado foi coletado. Quando o MDA é ativado, ele fornece o nome do tipo do delegado. Use esse nome para pesquisar seu código por invocar plataforma ou assinaturas COM que passam esse delegado para código não gerenciado. O delegado infrator é desmaiado através de um desses sites de chamada. Você também pode habilitar o gcUnmanagedToManaged
MDA para forçar uma coleta de lixo antes de cada retorno de chamada para o tempo de execução. Isso removerá a incerteza introduzida pela coleta de lixo, garantindo que uma coleta de lixo sempre ocorra antes do retorno de chamada. Depois de saber qual delegado foi coletado, altere seu código para manter uma referência a esse delegado no lado gerenciado durante o tempo de vida do ponteiro de função não gerenciado empacotado.
Efeito no tempo de execução
Quando os delegados são organizados como ponteiros de função, o tempo de execução aloca um thunk que faz a transição de não gerenciado para gerenciado. Esse thunk é o que o código não gerenciado realmente chama antes que o delegado gerenciado seja finalmente invocado. Sem o MDA callbackOnCollectedDelegate
habilitado, o código de empacotamento não gerenciado é excluído quando o delegado é coletado. Com o MDA callbackOnCollectedDelegate
habilitado, o código de empacotamento não gerenciado não é excluído imediatamente quando o delegado é coletado. Em vez disso, as últimas 1.000 instâncias são mantidas ativas por padrão e alteradas para ativar o MDA quando chamadas. O thunk é eventualmente excluído depois que mais 1.001 delegados marshalled são coletados.
Saída
O MDA relata o nome do tipo do delegado que foi coletado antes de um retorno de chamada ser tentado em seu ponteiro de função não gerenciado.
Configuração
O exemplo a seguir mostra as opções de configuração do aplicativo. Ele define o número de thunks que o MDA mantém vivo para 1.500. O valor padrão listSize
é 1.000, o mínimo é 50 e o máximo é 2.000.
<mdaConfig>
<assistants>
<callbackOnCollectedDelegate listSize="1500" />
</assistants>
</mdaConfig>
Exemplo
O exemplo a seguir demonstra uma situação que pode ativar esse MDA:
// Library.cpp : Defines the unmanaged entry point for the DLL application.
#include "windows.h"
#include "stdio.h"
void (__stdcall *g_pfTarget)();
void __stdcall Initialize(void __stdcall pfTarget())
{
g_pfTarget = pfTarget;
}
void __stdcall Callback()
{
g_pfTarget();
}
// C# Client
using System;
using System.Runtime.InteropServices;
public class Entry
{
public delegate void DCallback();
public static void Main()
{
new Entry();
Initialize(Target);
GC.Collect();
GC.WaitForPendingFinalizers();
Callback();
}
public static void Target()
{
}
[DllImport("Library", CallingConvention = CallingConvention.StdCall)]
public static extern void Initialize(DCallback pfDelegate);
[DllImport ("Library", CallingConvention = CallingConvention.StdCall)]
public static extern void Callback();
~Entry() { Console.Error.WriteLine("Entry Collected"); }
}