메모
이 문서는 .NET Framework와 관련이 있습니다. .NET 6 이상 버전을 포함하여 .NET의 최신 구현에는 적용되지 않습니다.
callbackOnCollectedDelegate 대리자가 관리 코드에서 관리되지 않는 코드로 함수 포인터로 마샬링되고 대리자가 가비지 수집된 후 해당 함수 포인터에 콜백이 배치되면 MDA(관리 디버깅 도우미)가 활성화됩니다.
Symptoms
액세스 위반은 관리되는 대리자에서 얻은 함수 포인터를 통해 관리 코드를 호출하려고 할 때 발생합니다. 이러한 오류는 CLR(공용 언어 런타임) 버그가 아니지만 CLR 코드에서 액세스 위반이 발생하기 때문에 그렇게 나타날 수 있습니다.
오류가 일관되지 않습니다. 때로는 함수 포인터에 대한 호출이 성공하고 때로는 실패합니다. 이 오류는 부하가 많거나 난수의 시도에서만 발생할 수 있습니다.
원인
함수 포인터가 만들어지고 관리되지 않는 코드에 노출된 대리자가 가비지 수집되었습니다. 관리되지 않는 구성 요소가 함수 포인터를 호출하려고 하면 액세스 위반이 생성됩니다.
가비지 수집이 발생하는 시기에 따라 달라지므로 오류가 임의로 표시됩니다. 대리자가 수집에 적합한 경우 콜백 후 가비지 수집이 발생하고 호출이 성공할 수 있습니다. 콜백 전에 가비지 수집이 발생하고, 콜백이 액세스 위반을 생성하고, 프로그램이 중지되는 경우도 있습니다.
실패 확률은 대리자를 마샬링하는 시간과 함수 포인터의 콜백과 가비지 수집 빈도에 따라 달라집니다. 대리자와 후속 콜백을 마샬링하는 시간이 짧은 경우 오류가 산발적으로 발생합니다. 일반적으로 함수 포인터를 수신하는 관리되지 않는 메서드가 나중에 사용할 수 있도록 함수 포인터를 저장하지 않고 대신 함수 포인터를 즉시 다시 호출하여 반환하기 전에 작업을 완료하는 경우입니다. 마찬가지로 시스템이 부하가 많은 경우 더 많은 가비지 수집이 발생하므로 콜백 전에 가비지 수집이 발생할 가능성이 높습니다.
해결 방법
대리자가 관리되지 않는 함수 포인터로 마샬 아웃되면 가비지 수집기는 수명을 추적할 수 없습니다. 대신 코드는 관리되지 않는 함수 포인터의 수명 동안 대리자 참조를 유지해야 합니다. 그러나 이렇게 하려면 먼저 수집된 대리자를 식별해야 합니다. MDA가 활성화되면 대리자의 형식 이름을 제공합니다. 이 이름을 사용하여 비관리 코드에 위임을 전달하는 플랫폼 호출 또는 COM 서명을 검색할 수 있습니다. 위반 대리자는 이러한 호출 사이트 중 하나를 통해 전달됩니다. 또한 MDA가 런타임에 gcUnmanagedToManaged 콜백하기 전에 가비지 수집을 강제로 사용하도록 설정할 수도 있습니다. 이렇게 하면 콜백 전에 가비지 수집이 항상 발생하도록 하여 가비지 수집에서 발생하는 불확실성을 제거합니다. 수집된 대리자를 알고 나면 마샬링된 관리되지 않는 함수 포인터의 수명 동안 관리되는 쪽에서 해당 대리자 참조를 유지하도록 코드를 변경합니다.
런타임에 미치는 영향
대리자가 함수 포인터로 마샬링되는 경우 런타임은 관리되지 않는 것에서 관리되는 것으로의 전환을 수행하는 thunk를 할당합니다. 이 thunk는 관리되는 대리자가 마지막으로 호출되기 전에 관리되지 않는 코드가 실제로 호출하는 것입니다. MDA를 callbackOnCollectedDelegate 사용하지 않으면 대리자를 수집할 때 관리되지 않는 마샬링 코드가 삭제됩니다. MDA를 callbackOnCollectedDelegate 사용하도록 설정하면 대리자를 수집할 때 관리되지 않는 마샬링 코드가 즉시 삭제되지 않습니다. 대신, 마지막 1,000개의 인스턴스는 기본적으로 활성 상태로 유지되고 호출될 때 MDA를 활성화하도록 변경됩니다. 1,001명의 마샬링된 대리자를 더 수집한 후 펑크가 결국 삭제됩니다.
출력
MDA는 관리되지 않는 함수 포인터에서 콜백을 시도하기 전에 수집된 대리자의 형식 이름을 보고합니다.
구성 / 설정
다음 예제에서는 애플리케이션 구성 옵션을 보여줍니다. MDA가 활성 상태로 유지하는 펑크의 수를 1,500개로 설정합니다. 기본값 listSize 은 1,000, 최소값은 50, 최대값은 2,000입니다.
<mdaConfig>
<assistants>
<callbackOnCollectedDelegate listSize="1500" />
</assistants>
</mdaConfig>
Example
다음 예제에서는 이 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"); }
}
참고하십시오
.NET