다음을 통해 공유


AOT 경고 소개

애플리케이션을 네이티브 AOT로 게시할 때 빌드 프로세스는 런타임에 애플리케이션을 지원하는 데 필요한 모든 네이티브 코드 및 데이터 구조를 생성합니다. 이것은 애플리케이션을 추상 용어(가상 머신용 프로그램)로 설명하는 형식에서 애플리케이션을 실행하고 런타임에 필요에 따라 네이티브 표현을 만드는 비네이티브 배포와 다릅니다.

프로그램 파트의 추상 표현은 네이티브 표현과 일대일로 매핑되지 않습니다. 예를 들어 제네릭 List<T>.Add 메서드에 대한 추상 설명은 지정된 T(예: List<int>.AddList<double>.Add)에 대해 특수화해야 하는 잠재적으로 무한한 네이티브 메서드 본문에 매핑됩니다.

추상 코드와 네이티브 코드의 관계는 일대일 관계가 아니므로 빌드 프로세스는 빌드 타임에 네이티브 코드 본문 및 데이터 구조의 전체 목록을 만들어야 합니다. 일부 .NET API에 대한 빌드 타임에 이 목록을 만드는 것은 어려울 수 있습니다. 빌드 타임에 예상하지 못한 방식으로 API를 사용하는 경우 런타임에 예외가 throw됩니다.

네이티브 AOT로 배포할 때 동작 변경을 방지하기 위해 .NET SDK는 "AOT 경고"를 통해 AOT 호환성에 대한 정적 분석을 제공합니다. 빌드에서 AOT와 호환되지 않을 수 있는 코드를 발견하면 AOT 경고가 생성됩니다. AOT와 호환되지 않는 코드는 네이티브 AOT로 빌드된 후 애플리케이션에서 동작 변경 또는 충돌을 일으킬 수 있습니다. 이상적으로 네이티브 AOT를 사용하는 모든 애플리케이션에는 AOT 경고가 없어야 합니다. AOT 경고가 있는 경우 네이티브 AOT로 빌드한 후 앱을 철저히 테스트하여 동작 변경이 없는지 확인합니다.

AOT 경고의 예

대부분의 C# 코드에서는 생성해야 하는 네이티브 코드를 간단하게 확인할 수 있습니다. 네이티브 컴파일러는 메서드 본문을 탐색하고 액세스되는 네이티브 코드 및 데이터 구조를 찾을 수 있습니다. 아쉽게도 리플렉션 같은 일부 기능에는 상당한 문제가 있습니다. 다음 코드를 살펴보세요.

Type t = typeof(int);
while (true)
{
    t = typeof(GenericType<>).MakeGenericType(t);
    Console.WriteLine(Activator.CreateInstance(t));
}

struct GenericType<T> { }

위의 프로그램은 그렇게 유용하지는 않지만 애플리케이션을 네이티브 AOT로 빌드할 때 무한 개수의 제네릭 형식을 만들어야 하는 극단적인 경우를 나타냅니다. 네이티브 AOT가 없으면 이 프로그램은 메모리가 부족할 때까지 실행됩니다. 네이티브 AOT를 사용하면 필요한 모든 형식(무한 개수)을 생성하더라도 빌드하지 못할 수 있습니다.

이 경우 네이티브 AOT 빌드는 MakeGenericType 줄에 다음 경고를 발생합니다.

AOT analysis warning IL3050: Program.<Main>$(String[]): Using member 'System.Type.MakeGenericType(Type[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The native code for this instantiation might not be available at runtime.

런타임에 애플리케이션은 실제로 MakeGenericType 호출에서 예외를 throw합니다.

AOT 경고에 대응

AOT 경고는 네이티브 AOT 빌드에 예측 가능성을 제공하기 위한 것입니다. 대부분의 AOT 경고는 시나리오를 지원하기 위해 네이티브 코드가 생성되지 않은 상황에서 발생할 수 있는 런타임 예외에 관한 것입니다. 가장 광범위한 범주는 RequiresDynamicCodeAttribute입니다.

RequiresDynamicCode

RequiresDynamicCodeAttribute는 간단하고 광범위합니다. 이것은 멤버가 AOT와 호환되지 않는다는 주석이 추가되었음을 의미하는 특성입니다. 이 주석은 멤버가 리플렉션 또는 다른 메커니즘을 사용하여 런타임에 새 네이티브 코드를 만들 수 있음을 의미합니다. 이 특성은 코드가 기본적으로 AOT 호환되지 않거나 네이티브 종속성이 너무 복잡해서 빌드 타임에 정적으로 예측할 수 없는 경우에 사용됩니다. Type.MakeGenericType API, 리플렉션 내보내기 또는 기타 런타임 코드 생성 기술을 사용하는 메서드의 경우도 마찬가지입니다. 다음은 예를 보여 주는 코드입니다.

[RequiresDynamicCode("Use 'MethodFriendlyToAot' instead")]
void MethodWithReflectionEmit() { ... }

void TestMethod()
{
    // IL3050: Using method 'MethodWithReflectionEmit' which has 'RequiresDynamicCodeAttribute'
    // can break functionality when AOT compiling. Use 'MethodFriendlyToAot' instead.
    MethodWithReflectionEmit();
}

RequiresDynamicCode 해결 방법은 많지 않습니다. 가장 좋은 해결 방법은 네이티브 AOT로 빌드할 때 메서드를 전혀 호출하지 말고 AOT와 호환되는 다른 항목을 사용하는 것입니다. 라이브러리를 작성하고 메서드 호출 여부는 제어하지 않는 경우 자체 메서드에 RequiresDynamicCode를 추가할 수도 있습니다. 이렇게 하면 메서드가 AOT와 호환되지 않음 주석을 추가할 수 있습니다. RequiresDynamicCode를 추가하면 주석 처리된 메서드의 모든 AOT 경고가 표시되지 않지만 다른 사용자가 호출할 때마다 경고가 생성됩니다. 이러한 이유로 라이브러리 작성자에게는 일반적으로 경고를 공용 API에 ‘버블링’하는 것이 유용합니다.

호출이 안전하고 모든 네이티브 코드를 런타임에 사용할 수 있음을 확인할 수 있는 경우 UnconditionalSuppressMessageAttribute를 사용하여 경고를 표시하지 않을 수도 있습니다. 예:

[RequiresDynamicCode("Use 'MethodFriendlyToAot' instead")]
void MethodWithReflectionEmit() { ... }

[UnconditionalSuppressMessage("Aot", "IL3050:RequiresDynamicCode",
    Justification = "The unfriendly method is not reachable with AOT")]
void TestMethod()
{
    If (RuntimeFeature.IsDynamicCodeSupported)
        MethodWithReflectionEmit(); // warning suppressed
}

UnconditionalSuppressMessageSuppressMessage와 유사하지만 publish 및 다른 빌드 후 도구에서 볼 수 있습니다. SuppressMessage#pragma 지시문은 소스에만 표시되므로, 빌드에서 경고를 표시하는 않도록 하는 데 사용할 수 없습니다.

주의

AOT 경고를 표시하지 않을 때는 주의해야 합니다. 이제 호출이 AOT와 호환될 수 있지만 코드를 업데이트할 때 변경될 수 있으며, 표시되지 않는 모든 항목을 검토하는 것을 잊어버릴 수 있습니다.