callbackOnCollectedDelegate MDA

Note

Este artigo é específico para .NET Framework. Ele não se aplica a implementações mais recentes do .NET, incluindo o .NET 6 e versões posteriores.

O callbackOnCollectedDelegate MDA (assistente de depuração gerenciada) é ativado se um delegado é enviado de código gerenciado para não gerenciado como um ponteiro de função e um retorno de chamada é colocado nesse ponteiro de função depois que o delegado é coletado.

Symptoms

As violações de acesso ocorrem ao tentar chamar o código gerenciado por meio de ponteiros de função obtidos de delegados gerenciados. Essas falhas, embora não sejam bugs clr (common language runtime), 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 de função é bem-sucedida e, às vezes, ela falha. A falha pode ocorrer apenas sob carga pesada ou em um número aleatório de tentativas.

Cause

O delegado do qual o ponteiro de função foi criado e exposto ao código não gerenciado foi coletado. Quando o componente não gerenciado tenta chamar o ponteiro de função, ele gera uma violação de acesso.

A falha parece 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 outras ocasiões, 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 o marshalling do delegado e o retorno de chamada no ponteiro de função, bem como a frequência das coletas de lixo. A falha será esporádica se o tempo entre a delegação e o retorno de chamada subsequente for curto. Geralmente, esse é 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 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 empacotado como um ponteiro de função não gerenciado, o coletor de lixo não poderá acompanhar seu tempo de vida. 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, primeiro você deve identificar qual delegado foi coletado. Quando o MDA é ativado, ele fornece o nome do tipo do delegado. Use esse nome para pesquisar o código em busca de assinaturas COM ou invocação de plataforma que passem esse delegado para código não gerenciado. O delegado infrator é passado por 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 no runtime. 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 em marshalled.

Efeito no Runtime

Quando os delegados são empacotados como ponteiros de função, o runtime 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 callbackOnCollectedDelegate MDA habilitado, o código de marshalling não gerenciado é excluído quando o delegado é coletado. Com o callbackOnCollectedDelegate MDA habilitado, o código de marshalling 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 da tentativa de retorno de chamada em seu ponteiro de função não gerenciado.

Configuração

O exemplo a seguir mostra as opções de configuração do aplicativo. Define o número de thunks que o MDA mantém vivo como 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>

Example

O exemplo a seguir demonstra uma situação que pode ativar este 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"); }
}

Consulte também