Compartir a través de


MDA de callbackOnCollectedDelegate

El asistente para la depuración administrada (MDA) callbackOnCollectedDelegate se activa si se calculan las referencias de un delegado desde código administrado a no administrado como puntero de función y se coloca una devolución de llamada en dicho puntero una vez que se hayan recolectado los elementos no utilizados del delegado.

Síntomas

Cuando se intenta llamar a código administrado mediante punteros de función obtenidos de delegados administrados se producen infracciones de acceso. Aunque ese no es el caso, estos errores pueden parecer errores de Common Language Runtime (CLR), porque la infracción de acceso se produce en el código de CLR.

No es un error que se produzca siempre; a veces se puede llamar al puntero de función sin ningún problema y otras veces se produce un error. Es posible que sólo se produzca el error cuando la carga es muy fuerte o cuando el número de intentos es aleatorio.

Motivo

Se habían recolectado los elementos no utilizados del delegado a partir del que se creó el puntero de función y se expuso al código no administrado. Cuando el componente no administrado intenta llamar al puntero de función, se genera una infracción de acceso.

El error parece aleatorio porque depende de cuando se produce la recolección de elementos no utilizados. Si un delegado es candidato a la recolección de elementos no utilizados, esta puede producirse en cuanto la llamada y la devolución de llamada se hayan realizado con éxito. En caso contrario, si la recolección de elementos no utilizados tiene lugar antes de la devolución de llamada, ésta genera una infracción de acceso y el programa se detiene.

La probabilidad de error depende del tiempo que pase entre el cálculo de referencias del delegado y la devolución de llamada en el puntero de función, así como de la frecuencia con que se recolecten los elementos no utilizados. El error será esporádico si pasa poco tiempo entre el cálculo de referencias del delegado y la devolución de llamada subsiguiente. Esto es lo más habitual cuando el método no administrado que recibe el puntero de función no reserva dicho puntero para su uso posterior, sino que devuelve inmediatamente la llamada al puntero de función para que finalice la operación antes de volver. Igualmente, cuando el sistema se encuentra bajo una fuerte carga se producen más recolecciones de elementos no utilizados, lo que implica más probabilidades de que se produzcan recolecciones antes de la devolución de llamada.

Resolución

Una vez calculadas las referencias del delegado y convertido éste en puntero de función no administrada, el recolector de elementos no utilizados no puede controlar su duración. En su lugar, se debe conservar la referencia del delegado en el código mientras dure el puntero de función no administrada. Pero antes de ello, primero debe identificar el delegado del que se han recolectado elementos no utilizados. Cuando se activa el asistente para la depuración administrada, proporciona el nombre de tipo del delegado. Utilice este nombre para buscar en el código la invocación de plataforma o los prototipos COM que pasan el delegado a código no administrado. El delegado que produce el error pasa a través de uno de estos emplazamientos de llamada. También puede permitirle al asistente para la depuración administrada gcUnmanagedToManaged que fuerce una recolección de elementos no utilizados antes de cada devolución de llamada en Common Language Runtime. Esto quitará la incertidumbre provocada por la recolección de elementos no utilizados, garantizando que dicha recolección se produzca siempre antes de la devolución de llamada. En cuanto sepa en qué delegado se han recolectado elementos no utilizados, modifique su código para que conserve una referencia a dicho delegado en la parte administrada mientras dure el nuevo puntero de función no administrada del que se han calculado las referencias.

Efecto en Common Language Runtime

Cuando se calculan las referencias de un delegado como puntero de función, Common Language Runtime asigna un código thunk que hace la transición de no administrado a administrado. Este código thunk es lo que en realidad llama el código no administrado antes de invocar finalmente el delegado administrado. Si el asistente para la depuración administrada callbackOnCollectedDelegate no está habilitado, el código no administrado del que se calculan las referencias se elimina al recolectar los elementos no utilizados del delegado. Si el asistente para la depuración administrada callbackOnCollectedDelegate está habilitado, el código no administrado del que se calculan las referencias no es eliminado inmediatamente al recolectar los elementos no utilizados del delegado. En su lugar, las últimas 1.000 instancias se mantienen activas de manera predeterminada y se modifican para activar el asistente para la depuración administrada en el momento en que se le llame. El código thunk se acaba eliminando una vez recolectados los elementos no utilizados de otros 1.001 delegados de los que se hayan calculado referencias.

Output

El asistente para la depuración administrada informa del nombre de tipo del delegado cuyos elementos no utilizados fueron recolectados antes del intento de devolución de llamada en su puntero de función no administrada.

Configuración

En el ejemplo siguiente se muestran las opciones de configuración de la aplicación. El número de códigos thunk que el asistente para la depuración administrada mantiene activos es de 1.500. El valor listSize predeterminado es 1.000, el mínimo 50 y el máximo 2.000.

<mdaConfig>
  <assistants>
    <callbackOnCollectedDelegate listSize="1500" />
  </assistants>
</mdaConfig>

Ejemplo

En el ejemplo siguiente se muestra una situación que puede activar este asistente para la depuración administrada:

// 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"); }
}

Vea también

Referencia

MarshalAsAttribute

MDA de gcUnmanagedToManaged

Conceptos

Diagnóstico de errores con ayudantes de depuraciones administradas

Cálculo de referencias de interoperabilidad

Otros recursos

Interoperabilidad