Маршалинг во время выполнения
Эффективное взаимодействие между языками.
В Plugify маршалинг во время выполнения — это процесс преобразования типов данных между управляемым и неуправляемым кодом, обеспечивающий бесшовное взаимодействие между плагинами, написанными на разных языках программирования. Это руководство содержит советы и рекомендации для разработчиков языковых модулей о том, как эффективно обрабатывать маршалинг и как использовать утилиты Plugify для упрощения этого процесса.
Базовое сопоставление типов
В следующей таблице перечислены способы представления типов в C++ API:
Тип C++ | Псевдоним Plugify | Поддержка ссылок ? |
---|---|---|
void | void | ❌ |
bool | bool | ✅ |
char | char8 | ✅ |
char16_t | char16 | ✅ |
int8_t | int8 | ✅ |
int16_t | int16 | ✅ |
int32_t | int32 | ✅ |
int64_t | int64 | ✅ |
uint8_t | uint8 | ✅ |
uint16_t | uint16 | ✅ |
uint32_t | uint32 | ✅ |
uint64_t | uint64 | ✅ |
uintptr_t | ptr64 | ✅ |
uintptr_t | ptr32 | ✅ |
float | float | ✅ |
double | double | ✅ |
void* | function | ❌ |
plg::string | string | ✅ |
plg::any | any | ✅ |
plg::vector<bool> | bool | ✅ |
plg::vector<char> | char8 | ✅ |
plg::vector<char16_t> | char16 | ✅ |
plg::vector<int8_t> | int8 | ✅ |
plg::vector<int16_t> | int16 | ✅ |
plg::vector<int32_t> | int32 | ✅ |
plg::vector<int64_t> | int64 | ✅ |
plg::vector<uint8_t> | uint8 | ✅ |
plg::vector<uint16_t> | uint16 | ✅ |
plg::vector<uint32_t> | uint32 | ✅ |
plg::vector<uint64_t> | uint64 | ✅ |
plg::vector<uintptr_t> | ptr64 | ✅ |
plg::vector<uintptr_t> | ptr32 | ✅ |
plg::vector<float> | float | ✅ |
plg::vector<double> | double | ✅ |
plg::vector<plg::string> | string | ✅ |
plg::vector<plg::any> | any | ✅ |
plg::vector<plg::vec2> | vec2 | ✅ |
plg::vector<plg::vec3> | vec3 | ✅ |
plg::vector<plg::vec4> | vec4 | ✅ |
plg::vector<plg::mat4x4> | mat4x4 | ✅ |
plg::vec2 | vec2 | ✅ |
plg::vec3 | vec3 | ✅ |
plg::vec4 | vec4 | ✅ |
plg::mat4x4 | mat4x4 | ✅ |
Обработка типов Plugify
Plugify требует два типа маршалинга:
- Из языка в C++: Маршалинг данных из языка (например, Go, Python) в типы C++.
- Из C++ в язык: Генерация и экспорт функций C++, чтобы их можно было вызывать из других языков.
Маршалинг из языка в C++
Это процесс преобразования данных из нативных типов языка в типы C++ от Plugify (например, plg::string
, plg::vector
). Обычно это делается, когда плагин вызывает функцию, предоставленную другим плагином.
Пример: Работа с plg::string
в C
Вот как можно работать с plg::string
из другого языка несколькими способами. Простой подход — использовать нативную систему языка, однако в этом примере мы рассмотрим альтернативный подход для языка, у которого нет такой функции.
На машинном уровне объекты C++ от Plugify по сути являются структурами C. Единственное отличие заключается в том, что они требуют вызова конструкторов и деструкторов. Рассматривая эти объекты как простые структуры C, вы можете избежать генерации оберток во время выполнения и вместо этого использовать маршалинг во время компиляции для взаимодействия с ними.
Код C++ (Языковой модуль)
Код C (CGo)
Использование в Go
Преимущества этого подхода
- Отсутствие накладных расходов во время выполнения: Избегает необходимости в генерации функций во время выполнения, снижая накладные расходы на производительность.
- Совместимость с языками: Работает с языками, которыми нельзя управлять из неуправляемого кода во время выполнения.
- Явное управление памятью: Обеспечивает полный контроль над жизненным циклом объектов и выделением памяти.
Важные моменты
- Убедитесь, что расположение в памяти и выравнивание типов Plugify совпадают со структурами C, используемыми в целевом языке.
- Управляйте созданием и уничтожением объектов вручную, чтобы избежать утечек памяти или неопределенного поведения.
Экспорт функций из языка в C++
Plugify требует, чтобы плагины экспортировали функции, написанные на их родном языке (например, Python, Go), чтобы их можно было вызывать из других плагинов. Этот процесс включает создание оберток, которые преобразуют типы C++ от Plugify в типы целевого языка и наоборот. Эти обертки генерируются во время загрузки плагина и сохраняются в LoadResult
для последующего экспорта в другие языковые модули.
Как это работает
- Загрузка плагина: При загрузке плагина языковой модуль инициализирует скрипт плагина и извлекает его экспортированные функции.
- Создание обертки: Для каждой экспортированной функции языковой модуль создает обертку с помощью
JitCallback
или аналогичного механизма. Эта обертка обрабатывает преобразование типов между C++ типами Plugify и типами целевого языка. - Экспорт в другие модули: Обертки сохраняются в
LoadResult
и экспортируются в другие языковые модули на этапеOnMethodExport
.
Пример: Языковой модуль Python
Вот упрощенный пример того, как языковой модуль Python экспортирует функцию (add_numbers
) в C++:
Код плагина на Python
Код C++ (Языковой модуль Python)
Языковой модуль Python создает обертку для функции add_numbers
во время загрузки плагина. Эта обертка преобразует типы C++ в типы Python, вызывает функцию Python и преобразует результат обратно в тип C++.
Для плагинов, написанных на одном и том же языке, хорошим подходом является полное обход маршалинга. Эта оптимизация особенно полезна для приложений, критичных к производительности.
Ключевые шаги в процессе
- Извлечение функции: Языковой модуль извлекает экспортированные функции плагина (например,
add_numbers
в Python). - Создание обертки: Для каждой функции создается обертка с помощью
JitCallback
. Эта обертка обрабатывает преобразование типов и вызов функции. - Обработка ошибок: Если функция не может быть найдена или обертка не может быть создана, языковой модуль регистрирует ошибку и пропускает функцию.
- Экспорт в другие модули: Обернутые функции сохраняются в
LoadResult
и экспортируются в другие языковые модули на этапеOnMethodExport
.
Преимущества этого подхода
- Независимость от языка: Функции, написанные на любом языке, могут быть экспортированы и вызваны другими плагинами.
- Безопасность типов: Обертки обеспечивают правильное преобразование типов между языками.
- Производительность: За счет генерации оберток во время загрузки накладные расходы во время выполнения минимизируются.
Важные моменты
- Управление памятью: Обеспечьте правильную очистку ресурсов (например, объектов Python), чтобы избежать утечек памяти.
- Обработка ошибок: Корректно обрабатывайте ошибки, особенно когда функции отсутствуют или преобразование типов завершается неудачно.
- Производительность: Оптимизируйте обертки для приложений, критичных к производительности.
Использование библиотеки Jit
Библиотека plugify-jit
— это мощный инструмент для генерации функций во время выполнения и динамических вызовов функций. Она предоставляет два ключевых класса, JitCallback
и JitCall
, которые необходимы для маршалинга функций между управляемым и неуправляемым кодом.
JitCallback
Класс JitCallback
позволяет создавать объекты обратного вызова, которые можно передавать функциям как указатели на функции обратного вызова. Эти объекты обеспечивают динамическую итерацию по аргументам при вызове колбэка.
Ключевые методы
GetJitFunc
: Генерирует динамически созданную функцию на основе ссылки на метод.CallbackHandler
: Тип функции, который обрабатывает логику обратного вызова.
Пошаговое руководство
Инициализируйте объект:
Создайте экземпляр класса JitCallback
. Ему требуется объект времени выполнения asmjit
для выделения исполняемой памяти.
Сгенерированная функция будет освобождена, когда объект JitCallback
выйдет из области видимости. Убедитесь, что объект остается в области видимости до тех пор, пока функция необходима, или используйте умные указатели для управления его жизненным циклом.
Сгенерируйте функцию:
Используйте метод GetJitFunc
для генерации указателя на функцию.
Убедитесь, что объект method
действителен и правильно представляет метод, для которого вы хотите сгенерировать функцию обратного вызова.
Реализуйте функцию обратного вызова:
Определите функцию обратного вызова для обработки преобразования типов и вызова исходной функции.
Пример
JitCall
Класс JitCall
инкапсулирует семантику вызова функций, позволяя динамически передавать параметры функций и выполнять вызовы. Это особенно полезно для динамического вызова функций C.
Ключевые методы
GetJitFunc
: Генерирует динамически созданную функцию на основе ссылки на метод.
Пошаговое руководство
Инициализируйте объект:
Создайте экземпляр класса JitCall
.
Сгенерированная функция будет освобождена, когда объект JitCall
выйдет из области видимости. Убедитесь, что объект остается в области видимости до тех пор, пока функция необходима.
Сгенерируйте функцию:
Используйте метод GetJitFunc
для генерации указателя на функцию.
Пример
Преимущества использования библиотеки Jit
- Динамическая генерация функций: Создавайте и вызывайте функции во время выполнения, обеспечивая гибкость и адаптируемость.
- Интероперабельность: Облегчает взаимодействие между различными языками программирования в рамках Plugify.
- Упрощенная интеграция: Предоставляет простой API для генерации и использования динамических функций, снижая сложность маршалинга.
Линковка библиотеки
Чтобы слинковать библиотеку plugify-jit
с вашим языковым модулем, добавьте plugify::plugify-jit
в вашу цель CMake:
Это гарантирует, что библиотека plugify-jit
будет слинкована во время процесса сборки, делая ее функциональность доступной для вашего кода.
Устранение неполадок
Распространенные проблемы
- Утечки памяти:
- Убедитесь, что динамически выделенная память (например, для объектов
JitCallback
илиJitCall
) управляется должным образом. - Используйте умные указатели или идиому RAII, чтобы избежать утечек.
- Убедитесь, что динамически выделенная память (например, для объектов
- Неверные ссылки на методы:
- Убедитесь, что объект
method
, переданный вGetJitFunc
, действителен и правильно представляет целевой метод.
- Убедитесь, что объект
- Советы по отладке:
- Используйте подробное логирование для отслеживания вызовов функций и значений параметров.
- Включите отладочные символы в вашей конфигурации сборки, чтобы упростить отладку.
Советы по производительности
- Минимизируйте выделение памяти:
- По возможности повторно используйте объекты
JitCallback
иJitCall
, чтобы избежать частых выделений памяти. - Используйте буферы, выделенные на стеке, для небольших структур данных.
- По возможности повторно используйте объекты
- Избегайте ненужных преобразований типов:
- По возможности используйте нативные типы, чтобы уменьшить накладные расходы.
- Кэшируйте преобразованные значения, если они часто используются повторно.
- Оптимизируйте функции обратного вызова:
- Делайте функции обратного вызова легковесными и избегайте блокирующих операций.
- Используйте асинхронную обработку для трудоемких задач.