Введение в нативные плагины в Unity
A нативный плагин предоставляет интерфейс на C, к которому можно обращаться из управляемых скриптов (C#).
Поддержка собственных плагинов Unity использует стандартную функцию .NET Вызов платформенного кода. Эта функция позволяет коду C# вызывать неуправляемые функции, экспортированные библиотеками собственного кода. Для передачи данных между управляемым и неуправляемым кодом используется процесс под названием marshalling.
Для примера нативного плагина, ознакомьтесь с Простейший пример плагина на Github.
Импорт собственных плагинов
Вы можете импортировать нативные плагины в проект Unity в двух основных формах:
- Двоичные файлы, предварительно скомпилированные для конкретных платформ и архитектур CPU.
- Файлы исходного кода, которые компилируются как часть процесса сборки вашего проекта Unity. (Чтобы использовать файлы исходного кода в качестве собственного плагина, ваш проект должен использовать IL2CPP scripting backend.)
Чтобы работать в Unity, нативный плагин должен экспортировать свои внешние функции с помощью связывания на C. Этот тип связывания позволяет избежать манипуляции именами C++ и других проблем с Application Binary Interface (ABI).
Чтобы получить доступ к неуправляемой функции из управляемого кода C#, объявляете функцию C# static extern с тем же именем и совместимыми типами возврата и параметров, а затем вызываете ее как любой другой метод. Полные правила объявления и примеры см. в Вызов функций.
Unity поддерживает предварительно скомпилированные статические библиотеки на мобильных платформах Apple (iOS, tvOS и visionOS) и на Android. Плагины, импортированные в проект Unity в виде файлов исходного кода, тоже связываются статически. Такие плагины поддерживаются в проектах с бэкендом скриптинга IL2CPP. Для скриптов редактора и для кода, выполняющегося в режиме Play, плагины из исходного кода не поддерживаются.
После импорта файлов плагина в проект Unity необходимо установить свойства, определяющие, когда библиотека должна загружаться. Для этого выберите импортированные файлы в панели Unity Project и установите соответствующие свойства в панели Inspector. Подробнее см. в Изменение параметров плагина.
Примечание: Когда вы загружаете основной плагин во время сессии Unity Editor – либо через скрипт Редактора, либо в режиме воспроизведения, Редактор не может выгрузить его. Чтобы обновить основной плагин после того, как он был загружен процессом Редактора – либо в качестве предкомпилированного двоичного файла, либо в качестве файла исходного кода, вы должны перезапустить приложение Редактора. В противном случае Редактор продолжает использовать загруженную версию плагина вместо обновленной версии.
Взаимодействие с собственным плагином
Код C#, который вы пишете в Unity, управляется временем выполнения скрипта. Время выполнения скрипта управляет тем, где данные хранятся в памяти, и регулярно перемещает данные во избежание фрагментации памяти. Неуправляемый код в собственном плагине, с другой стороны, управляет своей собственной памятью напрямую и может использовать указатели для адресации конкретных мест в памяти. Когда вы пишете управляемый код, который взаимодействует с неуправляемым кодом, вы должны внимательно относиться к тому, где хранятся данные, когда данные доступны, и какая сторона отвечает за высвобождение памяти, которая больше не требуется.
Некоторые типы данных могут быть представлены иначе в управляемом коде по сравнению с собственным кодом. .NET предоставляет марширующий интероп, который обеспечивает представление и преобразование по умолчанию для большинства типов. В ситуациях, когда поведение по умолчанию является неправильным или неоптимальным, .NET Interop Services API предоставляет следующие атрибуты, которые можно использовать для явного определения желаемого представления и преобразования для конкретных типов данных:
- StructLayoutAttribute
- MarshallAsAttribute
- FieldOffsetAttribute
Дополнительные сведения см. в разделе Передача данных между управляемым и неуправляемым кодом.
Пример базового плагина
Простая библиотека на языке Си с одной функцией может иметь код, который выглядит следующим образом:
float ExamplePluginFunction ()
{
return 5.0F;
}
Примечание: В этом минимальном примере аннотация экспорта для краткости опущена. Предкомпилированная динамическая библиотека (например, та, которая импортирована ниже с помощью [DllImport("PluginName")]) должна экспортировать свои функции. См. Вызов неуправляемых функций из управляемого кода для аннотации EXPORT_API, которая экспортирует функцию на каждой платформе.
Если вы скомпилировали этот код отдельно и импортировали скомпилированную библиотеку в ваш проект Unity, вы можете использовать следующий скрипт C# для вызова ExamplePluginFunction():
using UnityEngine;
using System.Runtime.InteropServices;
class ExampleScript : MonoBehaviour
{
// Use the library file name for dynamically linked libraries.
[DllImport ("PluginName")]
private static extern float ExamplePluginFunction ();
void Awake ()
{
// Calls the ExamplePluginFunction inside the plugin
// And prints 5 to the console
Debug.Log (ExamplePluginFunction ());
}
}
Если вы импортируете собственный код в качестве исходного кода, то в атрибуте DllImport вместо имени библиотеки вы должны использовать __Internal:
using UnityEngine;
using System.Runtime.InteropServices;
class ExampleScript : MonoBehaviour
{
// Use __Internal instead of the library name for source-code plug-ins.
[DllImport ("__Internal")]
private static extern float ExamplePluginFunction ();
void Awake ()
{
// Calls the ExamplePluginFunction inside the plugin
// And prints 5 to the console
Debug.Log (ExamplePluginFunction ());
}
}
См. DllImport атрибут для получения дополнительных сведений о том, как определить библиотеку основного плагина для загрузки.
Дополнительные ресурсы
Взаимодействие между управляемым и неуправляемым кодом является сложной темой. Данная документация предназначена только для краткого ознакомления с темой. Для более глубокого понимания см. следующую документацию .NET:
- Маршаллинг интероп
- Поведение Маршаллирования по Умолчанию
- Маршрутизация данных с вызовами платформы
Примечание: В данной документации обсуждается модель взаимодействия Platform Invoke. Однако можно также использовать модель взаимодействия COM, обсуждаемую в документации Microsoft .NET.