Version: 2022.2
言語: 日本語
IL2CPP によるマネージスタックトレース
マネージコードストリッピング

スクリプトの制限

Unity は、サポートしているすべてのプラットフォームで共通のスクリプティング API と体験を提供します。ただし、独自の制限があるプラットフォームもあります。これらの制限を理解するために、以下の表では、各プラットフォームとスクリプティングバックエンドに適用される制限について説明します。

プラットフォーム (スクリプティングバックエンド) 事前 (AOT) コンパイル スレッドに対応
Android (IL2CPP)
Android (Mono) なし
iOS (IL2CPP)
スタンドアロン (IL2CPP)
スタンドアロン (Mono) なし
ユニバーサル Windows プラットフォーム (IL2CPP)
WebGL (IL2CPP) なし

事前コンパイル (AOT)

プラットフォームの中には、ランタイムのコード生成ができないものもあります。そのため、そのようなデバイスで実行時 (Just-In-Time、JIT) コンパイルをおこなうと失敗します。代わりに、すべてのコードを事前 (Ahead-Of-Time、AOT) にコンパイルする必要があります。しばしば、この差異はあまり重要でないこともあります。しかし、ある特定の場合では、AOT コンパイルを必要とするプラットフォームには、追加の配慮が必要なことがあります。

リフレクション

リフレクションは AOT プラットフォームでサポートされています。 しかし、コードがリフレクションによって使用されていることをこのコンパイラーが推測できない場合、ランタイムにコードが存在しない可能性があります。 詳細は マネージコードストリッピング を参照してください。

System.Reflection.Emit

AOT プラットフォームは、System.Reflection.Emit 名前空間のメソッドを実装できません。

シリアライズ

事前 (AOT) コンパイルが必要なプラットフォームでは、リフレクションの使用が原因でシリアライズと非シリアライズで問題が発生することがあります。シリアライズと非シリアライズの一部として、型とメソッドをリフレクション経由でのみ使用できる場合、事前コンパイラーは型とメソッドのためにコードが生成される必要があることを検知することができません。

ジェネリック型とメソッド

ジェネリック型とメソッドの場合、異なるジェネリックインスタンスは異なるコードを必要とするため、コンパイラーはどのジェネリックインスタンスを使うかを判断しなければなりません。例えば、List<int> に使われるコードは、List<double> に使われるコードと異なります。しかし、IL2CPP は参照型のためのコードを共有します。つまり、List<object>List<string> には同じコードが使われます。

以下のような場合、IL2CPP がコンパイル時に見つけられなかったジェネリック型やメソッドを参照することが可能です。

  1. ランタイムに新しいジェネリックインスタンスを作成する場合。Activator.CreateInstance(typeof(SomeGenericType<>).MakeGenericType(someType));
  2. ジェネリックインスタンスで静的メソッドを呼び出す場合。typeof(SomeGenericType<>).MakeGenericType(someType)).GetMethod("AMethod").Invoke(null, null);
  3. 静的ジェネリックメソッドを呼び出す場合。typeof(SomeType).GetMethod("GenericMethod").MakeGenericMethod(someType).Invoke(null, null);
  4. コンパイル時に推論できないジェネリック仮想関数への呼び出しがある場合。
  5. 深いネスト状のジェネリック値の型を持つ呼び出しの場合。例えば、Struct<Struct<Struct<...<Struct<int>>>>

このようなケースをサポートするために、IL2CPP はどんな型のパラメーターでも動作するジェネリックコードを生成します。ただし、このコードは、型のサイズや、参照型か値型かを仮定することができないため、高速ではありません。より速いジェネリックメソッドを確実に生成する必要がある場合は、以下のようにします。 * ジェネリック引数が常に参照型になる場合は、where: class 制約を加えます。 そうすると、IL2CPP は参照型の共有を使ってフォールバックメソッドを生成します。これによりパフォーマンスが低下することはありません。 * ジェネリック引数が常に値型である場合は、where: struct 制約を加えます。 これにより、いくつかの最適化が可能になりますが、値型のサイズが異なる可能性があるため、コードは依然として遅くなります。 * UsedOnlyForAOTCodeGeneration という名前のメソッドを作成し、IL2CPP に生成させたいジェネリック型とメソッドへの参照を加えます。 このメソッドは呼び出される必要はありません (おそらく呼び出されるべきではありません)。 以下の例では、GenericType<MyStruct> の特殊化が生成されることを確認します。

public void UsedOnlyForAOTCodeGeneration()
{
    // Ensure that IL2CPP will create code for MyGenericStruct
    // using MyStruct as an argument.
    new GenericType<MyStruct>();

    // Ensure that IL2CPP will create code for SomeType.GenericMethod
    // using MyStruct as an argument.
    new SomeType().GenericMethod<MyStruct>();

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

    // 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.");
}

“速い (小さな) ビルド” 設定を有効にすると、ジェネリックコードの完全に共有可能な 1 つのバージョンだけがコンパイルされることに注意してください。 これは生成されるメソッドの数を減らし、コンパイル時間とビルドサイズを削減しますが、ランタイムのパフォーマンスを犠牲にします。

ネイティブコードからのマネージメソッドの呼び出し

ネイティブコードから呼び出せるように C 言語の関数ポインターにマーシャリングを行う必要があるマネージメソッドには、AOT プラットフォーム上でいくつかの制限があります。

  • マネージメソッドは静的メソッドでなければなりません。
  • マネージメソッドは、MonoPInvokeCallback 属性を持つ必要があります。
  • マネージメソッドがジェネリックの場合、[MonoPInvokeCallback(Type)] オーバーロードを使用して、サポートする必要があるジェネリックの特殊化を指定する必要があるかもしれません。 その場合、型は正しい数のジェネリック引数を持つジェネリックインスタンスでなければなりません。以下のように、1 つのメソッドに複数の[MonoPInvokeCallback] 属性を指定することができます。
// Generates reverse P/Invoke wrappers for NameOf<long> and NameOf<int>
// Note that the types are only used to indicate the generic arguments.
[MonoPInvokeCallback(typeof(Action<long>))]
[MonoPInvokeCallback(typeof(Action<int>))]
private static string NameOfT<T>(T item) 
{
    return typeof(T).Name;
}

スレッド不可

プラットフォームによっては、スレッドの使用をサポートしていません。そのため、System.Threading 名前空間を使用するマネージコードはすべて、ランタイムに失敗します。また、.NET クラスライブラリの一部は、暗示的にスレッドに依存しています。よく使われる例としては、System.Timers.Timer クラスがあり、スレッドのサポートに依存しています。

例外フィルター

IL2CPP は例外フィルターをサポートしていますが、IL2CPP は C++ 例外を使用してマネージ例外を実装しているため、フィルターステートメントと catch ブロックの実行順序が異なります。 フィルターがフィールドへの書き込みをブロックしない限り、気づかないかもしれません。

MarshalAs と FieldOffset 属性

IL2CPP は、MarhsalAsFieldOffset 属性をランタイムで反映することをサポートしていません。IL2CPP はコンパイル時にはこれらの属性をサポートしています。適切な プラットフォーム呼び出しによるマーシャリング を行うために、これらを使用する必要があります。

dynamic キーワード

IL2CPPは、C# の dynamic キーワードには対応していません。このキーワードには JIT コンパイルが必要ですが、IL2CPP では不可能です。

Marshal.Prelink

IL2CPPは、 Marshal.Prelink または Marshal.PrelinkAll APIメソッドをサポートしていません。

System.Diagnostics.Process API

IL2CPP は、System.Diagnostics.Process API メソッドをサポートしていません。デスクトッププラットフォームでこれが必要な場合は、Mono スクリプティングバックエンドを使用してください。

IL2CPP によるマネージスタックトレース
マネージコードストリッピング