Unity は、サポートしているすべてのプラットフォームで共通のスクリプティング API と体験を提供します。ただし、独自の制限があるプラットフォームもあります。これらの制限を理解するために、以下の表では、各プラットフォームとスクリプティングバックエンドに適用される制限について説明します。
プラットフォーム (スクリプティングバックエンド) | 事前 (AOT) コンパイル | スレッドなし | .NET Core クラスライブラリのサブセット | |
---|---|---|---|---|
Android (IL2CPP) | ✔ | |||
Android (Mono) | ||||
iOS (IL2CPP) | ✔ | |||
スタンドアロン (IL2CPP) | ✔ | |||
スタンドアロン (Mono) | ||||
ユニバーサル Windows プラットフォーム (IL2CPP) | ✔ | |||
ユニバーサル Windows プラットフォーム (.NET) | ✔ | |||
WebGL (IL2CPP) | ✔ | ✔ |
プラットフォームの中には、ランタイムのコード生成ができないものもあります。そのため、そのようなデバイスで実行時 (Just-In-Time、JIT) コンパイルをおこなうと失敗します。代わりに、すべてのコードを事前 (Ahead-Of-Time、AOT) にコンパイルする必要があります。しばしば、この差異はあまり重要でないこともあります。しかし、ある特定の場合では、AOT コンパイルを必要とするプラットフォームには、追加の配慮が必要なことがあります。
事前 (AOT) コンパイルが必要なプラットフォームには、System.Reflection.Emit
名前空間のメソッドを実装することができません。それ以外の System.Reflection
は使用可能です。ただし、リフレクションを通して使用されるコードがランタイムに存在する必要があることをコンパイラーが推測可能でなくてはなりません。
事前 (AOT) コンパイルが必要なプラットフォームでは、リフレクションの使用が原因でシリアライズと非シリアライズで問題が発生することがあります。シリアライズと非シリアライズの一部として、型とメソッドをリフレクション経由でのみ使用できる場合、事前コンパイラーは型とメソッドのためにコードが生成される必要があることを検知することができません。
ジェネリックメソッドでは、コンパイラーによる追加作業を行い、開発者が書いたコードを拡張してデバイスで実際に実行できるようなコードにすることが必要です。例えば、int
と double
をもつ List
では、異なるコードが必要です。仮想メソッドを使用する場合、動作はコンパイル時よりもはむしろランタイムに決定されます。そのため、ソースコードから明白にされないコードがある場合、コンパイラーによって、簡単にランタイムのコード生成が要求されます。
以下のようなコードがあるとします。JIT プラットフォームでは、意図した通りに動きます (“Message value: Zero” のメッセージがコンソールに 1 回出力されます)。
using UnityEngine;
using System;
public class AOTProblemExample : MonoBehaviour, IReceiver
{
public enum AnyEnum
{
Zero,
One,
}
void Start()
{
//AOT 問題を警告するには 、manager のタイプは
// Manager ではなく、IManager の必要があります。
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はこの行だけ必要です。
OnMessage(AnyEnum.Zero);
// Monoにもこの行が必要です。IManager インターフェース
// ではなくManager 上で直接呼び出します。
new Manager().SendMessage(null, AnyEnum.Zero);
// 例外をもつため、このメソッドが呼び出されたかどうかを確認することができます。
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 はコンパイル時にはこれらの属性をサポートしています。適切な プラットフォーム呼び出しによるマーシャリング を行うために、これらを使用する必要があります。
IL2CPPは、C# の dynamic
キーワードには対応していません。このキーワードには JIT コンパイルが必要ですが、IL2CPP では不可能です。
IL2CPPは、 Marshal.Prelink
または Marshal.PrelinkAll
APIメソッドをサポートしていません。