callbackOnCollectedDelegate MDAcallbackOnCollectedDelegate MDA

대리자가 함수 포인터로 관리 코드에서 비관리 코드로 마샬링되고 대리자가 가비지 수집된 후 콜백이 해당 함수 포인터에 배치된 경우 callbackOnCollectedDelegate MDA(관리 디버깅 도우미)가 활성화됩니다.The callbackOnCollectedDelegate managed debugging assistant (MDA) is activated if a delegate is marshaled from managed to unmanaged code as a function pointer and a callback is placed on that function pointer after the delegate has been garbage collected.

증상Symptoms

관리되는 대리자에서 가져온 함수 포인터를 통해 관리 코드를 호출하려고 하면 액세스 위반이 발생합니다.Access violations occur when attempting to call into managed code through function pointers that were obtained from managed delegates. 이러한 오류는 CLR(공용 언어 런타임) 버그가 아니지만 CLR 코드에서 액세스 위반이 발생하기 때문에 버그처럼 보일 수도 있습니다.These failures, while not common language runtime (CLR) bugs, may appear to be so because the access violation occurs in the CLR code.

오류는 일관성이 없습니다. 함수 포인터에 대한 호출이 성공하는 경우도 있고 실패하는 경우도 있습니다.The failure is not consistent; sometimes the call on the function pointer succeeds and sometimes it fails. 부하가 높은 상태에서 또는 임의의 시도 횟수에서만 오류가 발생할 수도 있습니다.The failure might occur only under heavy load or on a random number of attempts.

원인Cause

함수 포인터가 생성되고 비관리 코드에 노출된 대리자가 가비지 수집되었습니다.The delegate from which the function pointer was created and exposed to unmanaged code was garbage collected. 관리되지 않는 구성 요소가 함수 포인터를 호출하려고 하면 액세스 위반이 생성됩니다.When the unmanaged component tries to call on the function pointer, it generates an access violation.

가비지 수집이 발생하는 시기에 따라 달라지므로 오류는 무작위인 것처럼 보입니다.The failure appears random because it depends on when garbage collection occurs. 대리자가 컬렉션에 적합한 경우 콜백 후에 가비지 수집이 발생할 수 있으며 호출이 성공합니다.If a delegate is eligible for collection, the garbage collection can occur after the callback and the call succeeds. 그러지 않은 경우에는 콜백 전에 가비지 컬렉션이 발생하며, 콜백에서 액세스 위반을 생성하고 프로그램이 중지됩니다.At other times, the garbage collection occurs before the callback, the callback generates an access violation, and the program stops.

오류 가능성은 대리자 마샬링과 함수 포인터에 대한 콜백 사이의 시간 및 가비지 수집 빈도에 따라 달라집니다.The probability of the failure depends on the time between marshaling the delegate and the callback on the function pointer as well as the frequency of garbage collections. 대리자 마샬링과 이후 콜백 사이의 시간이 짧으면 오류가 드물게 발생합니다.The failure is sporadic if the time between marshaling the delegate and the ensuing callback is short. 이는 일반적으로 함수 포인터를 받는 관리되지 않는 메서드가 나중에 사용하기 위해 함수 포인터를 저장하지 않고 함수 포인터에 대해 즉시 콜백하여 반환하기 전에 해당 작업을 완료하는 경우에 나타납니다.This is usually the case if the unmanaged method receiving the function pointer does not save the function pointer for later use but instead calls back on the function pointer immediately to complete its operation before returning. 마찬가지로, 시스템의 부하가 높은 상태에서는 가비지 수집이 더 많이 발생하므로 콜백 전에 가비지 수집이 발생할 가능성이 커집니다.Similarly, more garbage collections occur when a system is under heavy load, which makes it more likely that a garbage collection will occur before the callback.

해결 방법Resolution

대리자가 관리되지 않는 함수 포인터로 마샬링된 경우 가비지 수집기가 수명을 추적할 수 없습니다.Once a delegate has been marshaled out as an unmanaged function pointer, the garbage collector cannot track its lifetime. 대신, 코드에서 관리되지 않는 함수 포인터의 수명 동안 대리자에 대한 참조를 유지해야 합니다.Instead, your code must keep a reference to the delegate for the lifetime of the unmanaged function pointer. 그러나 이렇게 하려면 먼저 수집된 대리자를 식별해야 합니다.But before you can do that, you first must identify which delegate was collected. MDA가 활성화되면 대리자의 형식 이름을 제공합니다.When the MDA is activated, it provides the type name of the delegate. 이 이름을 사용하여 해당 대리자를 비관리 코드에 전달하는 COM 서명이나 플랫폼 호출을 코드에서 검색할 수 있습니다.Use this name to search your code for platform invoke or COM signatures that pass that delegate out to unmanaged code. 위반 대리자는 이러한 호출 사이트 중 하나를 통해 전달됩니다.The offending delegate is passed out through one of these call sites. gcUnmanagedToManaged MDA에서 각 런타임 콜백 전에 가비지 컬렉션을 강제할 수 있게 할 수도 있습니다.You can also enable the gcUnmanagedToManaged MDA to force a garbage collection before every callback into the runtime. 그러면 항상 콜백 전에 가비지 수집이 발생하므로 가비지 수집에 의한 불확실성이 제거됩니다.This will remove the uncertainty introduced by the garbage collection by ensuring that a garbage collection always occurs before the callback. 수집된 대리자를 확인했으면 마샬링된 관리되지 않는 함수 포인터의 수명 동안 관리되는 쪽에서 해당 대리자에 대한 참조를 유지하도록 코드를 변경합니다.Once you know what delegate was collected, change your code to keep a reference to that delegate on the managed side for the lifetime of the marshaled unmanaged function pointer.

런타임에 대한 영향Effect on the Runtime

대리자가 함수 포인터로 마샬링되는 경우 런타임은 비관리에서 관리로 전환을 수행하는 썽크를 할당합니다.When delegates are marshaled as function pointers, the runtime allocates a thunk that does the transition from unmanaged to managed. 이 썽크는 관리되는 대리자가 최종 호출되기 전에 비관리 코드에서 실제로 호출하는 대상입니다.This thunk is what the unmanaged code actually calls before the managed delegate is finally invoked. callbackOnCollectedDelegate MDA를 사용할 수 없는 경우 대리자가 수집되면 관리되지 않는 마샬링 코드가 삭제됩니다.Without the callbackOnCollectedDelegate MDA enabled, the unmanaged marshaling code is deleted when the delegate is collected. callbackOnCollectedDelegate MDA를 사용할 수 있는 경우 대리자가 수집되어도 관리되지 않는 마샬링 코드가 즉시 삭제되지 않습니다.With the callbackOnCollectedDelegate MDA enabled, the unmanaged marshaling code is not immediately deleted when the delegate is collected. 대신, 마지막 1,000개 인스턴스가 기본적으로 활성 상태로 유지되고 호출 시 MDA를 활성화하도록 변경됩니다.Instead, the last 1,000 instances are kept alive by default and changed to activate the MDA when called. 썽크는 1,001개 이상의 마샬링된 대리자가 수집된 후에 삭제됩니다.The thunk is eventually deleted after 1,001 more marshaled delegates are collected.

출력Output

MDA는 관리되지 않는 함수 포인터에서 콜백이 시도되기 전에 수집된 대리자의 형식 이름을 보고합니다.The MDA reports the type name of the delegate that was collected before a callback was attempted on its unmanaged function pointer.

구성Configuration

다음 예제에서는 애플리케이션 구성 옵션을 보여 줍니다.The following example shows the application configuration options. MDA에서 활성 상태로 유지하는 썽크 수를 1,500개로 설정합니다.It sets the number of thunks the MDA keeps alive to 1,500. 기본 listSize 값은 1,000이고, 최소값은 50이고, 최대값은 2,000입니다.The default listSize value is 1,000, the minimum is 50, and the maximum is 2,000.

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

예제Example

다음 예제에서는 이 MDA를 활성화할 수 있는 상황을 보여 줍니다.The following example demonstrates a situation that can activate this 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"); }  
}  

추가 정보See also