Создание языкового модуля
Как добавить в Plugify поддержку нового языка с помощью языкового модуля.
Языковой модуль — нативная библиотека, которая учит ядро одному языку: загружает написанные на нём плагины, запускает и останавливает их и связывает их методы с методами плагинов на других языках. На этой странице описано, что должен делать модуль и как его начать.
Быстрый старт
Клонируйте шаблон вместе с сабмодулями
Соберите его
Заполните модуль
Реализуйте методы в src/module.cpp и укажите язык в plugify-module-template.pmodule.in.
В шаблоне есть ядро в виде сабмодуля external/plugify, класс модуля с пустыми методами, шаблон манифеста, списки экспорта для компоновщика в sym/, conda-рецепт в conda/ и GitHub-workflow для сборки релизов. Готовые модули (C++, Python, Lua и другие) служат полными примерами.
Манифест модуля
Модуль находится по манифесту *.pmodule:
language связывает плагины с модулем: плагин с "language": "demo" загружает этот модуль, и ядро делает плагин зависимым от модуля. Библиотека модуля по умолчанию — bin/ плюс имя файла манифеста, здесь bin/libdemo_module.so на Linux и bin/demo_module.dll на Windows. Поле runtime задаёт другой путь, а directories добавляет папки, где ищутся зависимости самой библиотеки. Все поля описаны в Архитектуре.
Точка входа
Библиотека должна экспортировать одну C-функцию. Ядро вызывает её один раз после загрузки библиотеки:
PLUGIFY_MODULE_API здесь означает макрос экспорта вашей библиотеки; в шаблоне это LM_EXPORT. Верните указатель на объект, который живёт столько же, сколько библиотека, например глобальный.
Интерфейс ILanguageModule
Все методы, кроме IsDebugBuild, возвращают Result, то есть std::expected<T, std::string>. Чтобы сообщить об ошибке, верните plugify::MakeError("текст {}", args...); ядро пометит расширение как упавшее с этим текстом. Исключения, выброшенные из этих методов, ядро перехватывает и обрабатывает так же.
| Метод | Когда ядро его вызывает |
|---|---|
Initialize | Один раз после загрузки модуля. Сохраните Provider: через него доступны логирование, папки хоста и список расширений |
OnPluginLoad | Для каждого плагина вашего языка в порядке загрузки. Загрузите плагин и верните его методы |
OnMethodExport | Для каждого плагина с методами, после загрузки всех плагинов. Вызывается у каждого модуля, на каком бы языке ни был плагин |
OnPluginStart | Для каждого плагина вашего языка после всех экспортов |
OnUpdate, OnPluginUpdate | Из Update хоста, если в MethodTable установлен соответствующий флаг |
OnPluginEnd | Для каждого плагина вашего языка при завершении, в обратном порядке загрузки |
Shutdown | Один раз при выгрузке модуля |
IsDebugBuild | Должен возвращать true для отладочной сборки. На Windows ядро не загружает модуль, собранный в другой конфигурации, чем оно само |
MethodTable
MethodTable сообщает ядру, какие необязательные вызовы есть, чтобы оно не вызывало пустые:
| Поле | Где возвращается | Смысл |
|---|---|---|
hasUpdate | InitData | Модулю нужен OnUpdate |
hasUpdate | LoadData | Плагину нужен OnPluginUpdate |
hasStart, hasEnd | LoadData | Плагину нужны OnPluginStart, OnPluginEnd |
hasExport | LoadData | У плагина есть методы, поэтому для него вызывается OnMethodExport |
Что возвращает OnPluginLoad
methods: для каждого элементаplugin.GetMethods(), в том же порядке,MethodDataсо ссылкой на этотMethodи нативным адресом его реализации. ПоfuncNameметода модуль находит реализацию. Если список не совпадает с манифестом или адрес нулевой, ядро отклоняет плагин.data: любой указатель, который вы хотите хранить для этого плагина. Ядро возвращает его какplugin.GetUserData().table: флаги, описанные выше.
Файлы плагина лежат в plugin.GetLocation(), а его entry из манифеста — plugin.GetEntry().
Связь между языками
Каждый метод каждого плагина — нативная функция с соглашением о вызовах C данной платформы. Модуль работает в двух направлениях:
- Экспорт. В
OnPluginLoadмодуль отдаёт ядру нативный адрес каждого метода своего плагина. Нативный язык может отдать адрес самой функции. Остальным языкам нужна нативная функция, которая преобразует аргументы и вызывает код плагина; её во время выполнения генерируетJitCallback. - Импорт. В
OnMethodExport(plugin)модуль получает плагин, методы которого лежат вplugin.GetMethodsData(), и делает их доступными своим плагинам. Нативный язык может вызвать адрес напрямую. Остальные вызывают его через функцию, сгенерированнуюJitCall, предварительно преобразовав свои значения в типы Plugify.
Существующие модули импортируют методы так, как принято в их языке:
| Модуль | Как плагин P видит метод M другого плагина |
|---|---|
| C++, Rust, Go | Переменная-указатель на функцию __P_M в библиотеке плагина, её заполняет модуль |
| C# (.NET) | Сгенерированный класс P.P с методом M |
| Python | Модуль plugify.pps.P |
| JavaScript | ES-модуль :P |
| Lua | Таблица P |
Все плагины загружаются и экспортируются до запуска первого из них, поэтому в OnPluginStart все импорты уже заполнены.
Минимальный модуль
Этот модуль обслуживает язык demo. Для каждого метода плагина он генерирует нативную функцию, складывающую два значения int32, а при запуске плагина вызывает каждый метод через JitCall:
С плагином, который объявляет метод Add(int32 a, int32 b) -> int32 и "language": "demo", plug выводит demo_plugin.Add(2, 3) = 5.
Тестирование
- Положите модуль и несколько плагинов в папку
extensionsприложенияplugи проверьте, что они доходят доRunning. - Вызовы между языками проверяйте плагинами
cross_call_masterиcross_call_workerиз репозитория C++-модуля: напишите worker на своём языке с теми же методами, что у C++-версии, загрузите его вместе с master и C++-модулем, и master проверит все виды параметров. См. Инструкции по сборке. - На Windows проверяйте и отладочную, и релизную сборку с ядром той же конфигурации.
Публикация
Модули распространяются conda-пакетами, так же как плагины. Workflow шаблона использует release-please: при релизе он собирает модуль для Linux и Windows, делает conda-пакеты по рецепту из conda/ и прикладывает их к релизу на GitHub. Пакет должен класть манифест в корень окружения, а библиотеку в bin/, то есть в ту же раскладку, которую ядро ожидает в extensions.