애플리케이션을 네이티브 AOT로 게시할 때 빌드 프로세스는 런타임에 애플리케이션을 지원하는 데 필요한 모든 네이티브 코드 및 데이터 구조를 생성합니다. 이는 애플리케이션을 추상 용어(가상 머신용 프로그램)로 설명하고 런타임에 필요에 따라 네이티브 표현을 만드는 형식에서 애플리케이션을 실행하는 비 네이티브 배포와 다릅니다.
프로그램 파트의 추상 표현에는 네이티브 표현에 대한 일대일 매핑이 없습니다. 예를 들어, 제네릭 List<T>.Add
메서드의 추상적인 설명은 지정된 T
를 위해 특수화해야 하는, 무한한 잠재적 네이티브 메서드 본문과 매핑됩니다 (예: List<int>.Add
, List<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
호출에서 예외를 발생합니다.
AOT 경고에 대응
AOT 경고는 네이티브 AOT 빌드에 예측 가능성을 가져오기 위한 것입니다. 대부분의 AOT 경고는 시나리오를 지원하기 위해 네이티브 코드가 생성되지 않은 상황에서 가능한 런타임 예외에 관한 것입니다. 가장 광범위한 범주는 .입니다 RequiresDynamicCodeAttribute
.
작업에 동적 코드가 필요합니다 (RequiresDynamicCode)
RequiresDynamicCodeAttribute 은 간단하고 광범위합니다. 멤버가 AOT와 호환되지 않는 것으로 주석이 추가되었음을 의미하는 특성입니다. 이 주석은 멤버가 리플렉션 또는 다른 메커니즘을 사용하여 런타임에 새 네이티브 코드를 만들 수 있음을 의미합니다. 이 특성은 코드가 기본적으로 AOT 호환되지 않거나 네이티브 종속성이 너무 복잡하여 빌드 시 정적으로 예측할 수 없는 경우에 사용됩니다. API, 리플렉션 내보내기 또는 기타 런타임 코드 생성 기술을 사용하는 Type.MakeGenericType
메서드의 경우도 마찬가지입니다. 다음은 예를 보여 주는 코드입니다.
[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
}
UnconditionalSuppressMessage
는 SuppressMessage
과 비슷하지만 publish
및 기타 빌드 후 도구에서 볼 수 있습니다.
SuppressMessage
및 #pragma
지시문은 원본에만 있으므로 빌드에서 경고를 침묵하는 데 사용할 수 없습니다.
주의
AOT 경고를 억제할 때는 조심해야 합니다. 이제 호출이 AOT와 호환될 수 있지만 코드를 업데이트할 때 변경될 수 있으며 모든 제거를 검토하는 것을 잊어버릴 수 있습니다.
.NET