Unity는 지원하는 모든 플랫폼에 동일한 스크립팅 API와 경험을 제공합니다. 하지만 몇몇 플랫폼에는 각각 고유한 제약이 있습니다. 이러한 제약을 이해하도록 돕기 위해, 각각의 제약이 어떤 플랫폼과 스크립팅 백엔드에 적용되는지가 아래 표에 설명되어 있습니다.
플랫폼(스크립팅 백엔드) | 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) | ✔ | ✔ |
몇몇 플랫폼은 런타임 코드 생성을 지원하지 않습니다. 따라서 타겟 디바이스에서 JIT(just-in-time) 컴파일에 의존하는 일부 관리되는 코드는 작동하지 않습니다. 이 경우 관리되는 코드를 AOT(ahead-of-time) 방식으로 컴파일해야 합니다. 많은 경우 이러한 방식의 차이는 없지만, 몇몇 경우에서는 AOT 플랫폼을 사용할 때 몇 가지 고려해야 할 사항이 있습니다.
AOT 플랫폼은 System.Reflection.Emit
네임스페이스의 메서드를 구현할 수 없습니다. 다만, 컴파일러가 리플렉션을 통해 사용된 코드가 런타임 시 존재해야 한다고 추측할 수 있는 경우에 한해 System.Reflection
의 나머지 부분을 사용할 수 있습니다.
AOT platforms might encounter issues with serialization and deserlization because of the use of reflection. If a type or method is only used via reflection as part of serialization or deserialization, the AOT compiler cannot detect that it needs to generate the code needs for the type or method.
일반 메서드를 사용하는 경우, 컴파일러는 개발자가 작성한 코드를 디바이스에서 실제로 실행되는 코드로 확장시키기 위해 추가적인 작업을 수행해야 합니다. 예를 들어 int
나 double
이 있는 경우 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 컴파일러는 AnyEnum
의 T
와 일반 메서드 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.");
}
컴파일러가 AnyEnum
의 T
와 OnMessage
에 대한 명시적 호출을 받으면, 런타임이 실행할 올바른 코드를 생성합니다. UsedOnlyForAOTCodeGeneration
메서드는 호출되기 위한 목적이 아닌, 컴파일러가 인지하기 위한 목적입니다.
네이티브 코드에서 호출될 수 있도록 C 함수 포인터로 마셜링해야 하는 관리되는 메서드는 AOT 플랫폼에서 몇 가지 제약 사항이 적용됩니다.
[MonoPInvokeCallback]
속성이 있어야 합니다.몇몇 플랫폼은 스레드를 지원하지 않으므로, System.Threading
네임스페이스를 사용하는 관리되는 코드는 런타임 도중 작동하지 않습니다. 또한, .NET 클래스 라이브러리의 일부분은 스레드에 암시적으로 의존합니다. 자주 사용되는 예제로는 System.Timers.Timer
클래스가 있는데, 이 클래스는 스레드 지원에 의존합니다.
IL2CPP는 C# 예외 필터를 지원하지 않습니다. 예외 필터를 사용하는 코드는 올바른 catch
블록으로 수정해야 합니다.
IL2CPP는 System.TypedReference
타입 또는 __makeref
C# 키워드를 지원하지 않습니다.
IL2CPP는 런타임 시 MarhsalAs
및 FieldOffset
속성의 반영을 지원하지 않으며, 이러한 속성을 컴파일 시점에 지원합니다. 적절한 플랫폼 호출 마셜링을 위해 이러한 속성을 사용해야 합니다.
IL2CPP는 C# dynamic
키워드를 지원하지 않습니다. 이 키워드는 IL2CPP의 경우 불가능한 JIT 컴파일을 요구합니다.