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

スクリプトの制限

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

.NET 4.x 同等のスクリプティングランタイム

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

事前 (AOT) コンパイル

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

System.Reflection.Emit

事前 (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&lt;AOTProblemExample+AnyEnum&gt;' for which no ahead of time (AOT) code was generated.
  at Manager.SendMessage[T] (IReceiver target, .T value) [0x00000] in &lt;filename unknown&gt;:0 
  at AOTProblemExample.Start () [0x00000] in &lt;filename unknown&gt;: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 ブロックに変更する必要があります。

TypedReference

IL2CPP は、System.TypedReference 型や __makeref C# キーワードには対応していません。

MarshalAs と FieldOffset 属性

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

dynamic キーワード

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

Marshal.Prelink

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

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