Version: 2021.2
언어: 한국어
IL2CPP로 관리되는 스택 추적
관리되는 코드 스트리핑

스크립팅 제약

Unity는 지원하는 모든 플랫폼에 동일한 스크립팅 API와 경험을 제공합니다. 하지만 몇몇 플랫폼에는 각각 고유한 제약이 있습니다. 이러한 제약을 이해하도록 돕기 위해, 각각의 제약이 어떤 플랫폼과 스크립팅 백엔드에 적용되는지가 아래 표에 설명되어 있습니다.

.NET 4.x 동급 스크립팅 런타임

플랫폼(스크립팅 백엔드) Ahead-of-time compile No threads .NET Core class libraries subset
Android(IL2CPP)
Android(Mono)
iOS(IL2CPP)
Standalone(IL2CPP)
Standalone(Mono)
유니버설 Windows 플랫폼(IL2CPP)
유니버설 Windows 플랫폼(.NET)
WebGL(IL2CPP)

AOT 컴파일

몇몇 플랫폼은 런타임 코드 생성을 지원하지 않습니다. 따라서 타겟 디바이스에서 JIT(just-in-time) 컴파일에 의존하는 일부 관리되는 코드는 작동하지 않습니다. 이 경우 관리되는 코드를 AOT(ahead-of-time) 방식으로 컴파일해야 합니다. 많은 경우 이러한 방식의 차이는 없지만, 몇몇 경우에서는 AOT 플랫폼을 사용할 때 몇 가지 고려해야 할 사항이 있습니다.

System.Reflection.Emit

AOT 플랫폼은 System.Reflection.Emit 네임스페이스의 메서드를 구현할 수 없습니다. 다만, 컴파일러가 리플렉션을 통해 사용된 코드가 런타임 시 존재해야 한다고 추측할 수 있는 경우에 한해 System.Reflection의 나머지 부분을 사용할 수 있습니다.

직렬화

AOT 플랫폼은 반사를 사용하므로 직렬화와 역직렬화 문제가 생길 수 있습니다. 직렬화 또는 역직렬화의 일부인 반사를 통해서만 타입 또는 메서드가 사용되는 경우, AOT 컴파일러는 해당 타입이나 메서드가 필요로 하는 코드를 감지할 수 없습니다.

일반 가상 메서드

일반 메서드를 사용하는 경우, 컴파일러는 개발자가 작성한 코드를 디바이스에서 실제로 실행되는 코드로 확장시키기 위해 추가적인 작업을 수행해야 합니다. 예를 들어 intdouble이 있는 경우 List에 대한 다른 코드를 필요로 합니다. 동작이 컴파일 시점이 아닌 런타임 시점에 결정되는 가상 메서드가 존재하는 경우, 컴파일러는 소스 코드에서는 명백하지 않더라도 런타임 코드 생성을 쉽게 요구할 수 있게 됩니다.

다음 예제 코드는 JIT 플랫폼에서는 의도한 대로 작동하여, 콘솔에 “Message value: Zero”를 한 번 출력합니다.

using UnityEngine;
using System;

public class AOTProblemExample : MonoBehaviour, IReceiver
{
    public enum AnyEnum 
    {
        Zero,
        One,
    }

    void Start() 
    {
        // Subtle trigger: The type of manager *must* be
        // IManager, not Manager, to trigger the AOT problem.
        IManager manager = new Manager();
        manager.SendMessage(this, AnyEnum.Zero);
    }

    public void OnMessage<T>(T value) 
    {
        Debug.LogFormat("Message value: {0}", value);
    }
}

public class Manager : IManager 
{
    public void SendMessage<T>(IReceiver target, T value) {
        target.OnMessage(value);
    }
}

public interface IReceiver
{
    void OnMessage<T>(T value);
}

public interface IManager 
{
    void SendMessage<T>(IReceiver target, T value);
}

하지만 IL2CPP 스크립팅 백엔드가 있는 AOT 플랫폼에서 이 코드를 실행하면 아래의 예외 오류가 발생합니다.

ExecutionEngineException: Attempting to call method 'AOTProblemExample::OnMessage<AOTProblemExample+AnyEnum>' for which no ahead of time (AOT) code was generated.
  at Manager.SendMessage[T] (IReceiver target, .T value) [0x00000] in <filename unknown>:0 
  at AOTProblemExample.Start () [0x00000] in <filename unknown>:0

유사하게도, Mono 스크립팅 백엔드에서는 아래의 예외 오류가 발생합니다.

  ExecutionEngineException: Attempting to JIT compile method 'Manager:SendMessage<AOTProblemExample/AnyEnum> (IReceiver,AOTProblemExample/AnyEnum)' while running with --aot-only.
    at AOTProblemExample.Start () [0x00000] in <filename unknown>:0

AOT 컴파일러는 AnyEnumT와 일반 메서드 OnMessage에 대한 코드를 생성해야 함을 인지하지 못합니다. 따라서 컴파일러는 이 메서드를 무시하게 됩니다. 메서드가 호출되어도 런타임이 호출할 올바른 코드를 찾지 못하므로 이 오류 메시지를 반환합니다.

이러한 AOT 문제를 해결하려면 강제로 컴파일러가 올바른 코드를 생성하도록 만들 수 있습니다. 이를 위해서는 AOTProblemExample 클래스에 아래의 예시 메서드를 추가하십시오.

public void UsedOnlyForAOTCodeGeneration() 
{
    // IL2CPP needs only this line.
    OnMessage(AnyEnum.Zero);

    // Mono also needs this line. Note that we are
    // calling directly on the Manager, not the IManager interface.
    new Manager().SendMessage(null, AnyEnum.Zero);

    // Include an exception so we can be sure to know if this method is ever called.
    throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime.");
}

컴파일러가 AnyEnumTOnMessage에 대한 명시적 호출을 받으면, 런타임이 실행할 올바른 코드를 생성합니다. UsedOnlyForAOTCodeGeneration 메서드는 호출되기 위한 목적이 아닌, 컴파일러가 인지하기 위한 목적입니다.

네이티브 코드에서 관리되는 메서드 호출

네이티브 코드에서 호출될 수 있도록 C 함수 포인터로 마셜링해야 하는 관리되는 메서드는 AOT 플랫폼에서 몇 가지 제약 사항이 적용됩니다.

  • 관리되는 메서드는 정적 메서드여야 합니다.
  • 관리되는 메서드에는 [MonoPInvokeCallback] 속성이 있어야 합니다.

스레드 없음

몇몇 플랫폼은 스레드를 지원하지 않으므로, System.Threading 네임스페이스를 사용하는 관리되는 코드는 런타임 도중 작동하지 않습니다. 또한, .NET 클래스 라이브러리의 일부분은 스레드에 암시적으로 의존합니다. 자주 사용되는 예제로는 System.Timers.Timer 클래스가 있는데, 이 클래스는 스레드 지원에 의존합니다.

예외 필터

IL2CPP는 C# 예외 필터를 지원하지 않습니다. 예외 필터를 사용하는 코드는 올바른 catch 블록으로 수정해야 합니다.

TypedReference

IL2CPP는 System.TypedReference 타입 또는 __makeref C# 키워드를 지원하지 않습니다.

MarshalAs 및 FieldOffset 속성

IL2CPP는 런타임 시 MarhsalAsFieldOffset 속성의 반영을 지원하지 않으며, 이러한 속성을 컴파일 시점에 지원합니다. 적절한 플랫폼 호출 마셜링을 위해 이러한 속성을 사용해야 합니다.

동적 키워드

IL2CPP는 C# dynamic 키워드를 지원하지 않습니다. 이 키워드는 IL2CPP의 경우 불가능한 JIT 컴파일을 요구합니다.

Marshal.Prelink

IL2CPP는 Marshal.Prelink 또는 Marshal.PrelinkAll API 메서드를 지원하지 않습니다.

IL2CPP로 관리되는 스택 추적
관리되는 코드 스트리핑