Unity 6.3
0 онлайн 46 гостей 3 в системе
Вход

Ограничения IL2CPP

Бэкенд IL2CPP имеет ряд существенных ограничений, обусловленных как особенностями опережающей компиляции (AOT), так и ограничениями конкретных платформ.

Нити

Потоки не поддерживаются на веб-платформе, поэтому любой управляемый код, который использует System.Threading пространства имён во время выполнения завершается ошибкой. Часть библиотек классов .NET также неявно зависит от потоков. Типичный пример — System.Timers.Timer класс, работа которого зависит от поддержки потоков.

Отражение

Unity поддерживает рефлексию на платформах AOT. Однако, если компилятор не может выяснить, что код используется через рефлексию, код может не существовать во время выполнения. Дополнительные сведения см. в Управляемое удаление кода.

Платформа AOT не может реализовать ни один из методов в пространстве имен System.Reflection.Emit.

Фильтры исключений

IL2CPP поддерживает фильтры исключений. Однако порядок выполнения фильтров и блоков catch отличается, так как IL2CPP использует исключения C++ для реализации управляемых исключений. Это не заметно, если блок фильтра не записывает в поле.

На Mono запись в поле происходит во время оценки фильтра, перед входом в блок catch, и в определенном порядке относительно других фильтров. На IL2CPP запись в поле может происходить в другое время, потому что фильтр симулируется. Это может привести к наблюдаемым различиям, таким как:

  • Поле обновляется раньше или позже, чем ожидалось, относительно блоков catch или finally.
  • Различные прокладки при наличии нескольких фильтров, потенциально изменяющие, какие обновления видны первыми.
  • Редкие случаи, когда код, опирающийся на побочные эффекты фильтра для поведения ворот в catch или finally, видит устаревшие или иначе упорядоченные значения.

Хорошая практика — избегать побочных эффектов в фильтрах, вынося любые изменения состояния в блок catch (или вспомогательную функцию, вызванную из catch), чтобы обеспечить последовательное поведение между IL2CPP и Mono.

Сериализация

Платформы AOT могут сталкиваться с проблемами с серийизацией и десерийизацией из-за использования отражения. Если тип или метод используется только через отражение как часть серийизации или десерийизации, компилятор 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.");
}

Примечание: Чтобы компилировать только единую, полностью совместимую версию общего кода, установите IL2CPP Генерация кода Настройка игрока до Оптимизация для размера кода и времени сборкиЭто уменьшает количество генерируемых методов, сокращая время компиляции и размер сборки, но в ущерб производительности во время выполнения.

Вызов управляемых методов из основного кода

Управляемые методы, которые должны быть маршированы на указатель функции C, чтобы их можно было вызвать из собственного кода, имеют несколько ограничений на платформах AOT:

  • Управляемый метод должен быть статическим.
  • Управляемый метод должен иметь атрибут [MonoPInvokeCallback].
  • Если управляемый метод является универсальным, вам, возможно, потребуется использовать перегрузку [MonoPInvokeCallback(Type)] для указания универсальных специализаций, которые должны поддерживаться. В этом случае тип должен быть универсальным экземпляром с правильным количеством универсальных аргументов. Можно иметь несколько атрибутов [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;
}

Неподдерживаемые .NET APIs

IL2CPP не поддерживает следующие .NET APIs и функции:

  • Отражение атрибутов ,MarshalAs, и ,FieldOffset,. Поддерживает эти атрибуты во время компиляции. Вы должны использовать их для надлежащего вызова платформы для марширования.
  • Ключевое слово C# dynamic. Это ключевое слово требует компиляции JIT, что невозможно с IL2CPP.
  • Методы Marshal.Prelink или Marshal.PrelinkAll API.
  • System.Diagnostics.Process API методы.

Для случаев, когда это требуется на настольных платформах, используйте Mono скриптовый сервер.

Дополнительные ресурсы