LINUX.ORG.RU

Расширение функционала программы на Си через другие языки

 , , , ,


1

3

Доброго времени суток.
Стоит задача запрограммировать модульную систему с ядром на си, которую можно было бы расширять с помощью других языков.

Расширения играют роль «органа» общения с внешними системами: одно принимает данные, другое обрабатывает, третье отдает пользователю, итд, короче, что-то вроде VM, но без такого лютого оверхеда и сложностей.
Для расширений уже придуман C API и, соответственно будут биндинги в другие языки для упрощения написания конфигурации

Собственно, я вижу два варианта устройства расширений:
1. Динамические библиотеки - подгрузка через dlopen, короче канон расширений плагинами.
2. Что-то вроде подхода CGI - единый интерфейс обмена через переменные окружения, stdin/stdout

Лично мне больше нравится первый вариант из-за отсутствия оверхеда при каждом вызове - один раз подгрузил плагины при старте и вызывай код из них сколько хочешь. Чем меньше оверхеда и затрат - тем лучше. (пишу системный демон с довольно высокой частотой пробуждения) Но, увы, не любой язык можно скомпилировать в динамическую библиотеку, а потенциальные пользователи, читай, одмены, вряд ли оценят написание конфигов на си.

Второй дает большую в этом плане гибкость - хоть на баше пиши или асме, но накладывает стоимость вызова шелла.

Реализовывать оба интерфейса возможности нет.

Вопрос номер один: можно ли как-то таки скомпоновать хотя бы питоньий код в динамическую библиотеку?
Вопрос номер два: можно ли при использовании второго варианта как-то уменьшить расходы на запуск сторонних исполняемых файлов?

★★★★★

Ничто не мешает реализовать первый вариант, а потом написать плагин, который будет по сути прослойкой между API и пайпами. Соответственно, плагин на питоне будет выглядеть как плагин на питоне, использующий stdin/stdout и шаблонная so-библиотека (абсолютно одинаковая, меняется только имя файла, который нужно запустить и чей стандартный ввод-вывод перехватывать), которая только и делает, что транслирует вызовы API в пайп, предварительно запустив питонью программу.

Вообще зависит от задачи. Если у тебя такие объёмы данных, что становится заметна задержка от IPC, то лучше библиотеку. А если у тебя программа к плагинам обращается раз в минуту, то пайпы будут куда лучше. По идее плагины загружаются только один раз, так что накладные расходы на старт нового процесса можно не учитывать.

KivApple ★★★★★
()

neovim выбрали msgpack-rpc для общения с дополнениями. Я пока склоняюсь к mqtt. С последним, благодаря mosquitto-client, «дополнения» можно писать наколенке (на баше, по крону вызывается mosquitto-client с нужными опциями, плюет данными в брокер, а брокер уже сам рассылает эти данные всем подписавшимся. Соответственно, задача упращается в разы)

makoven ★★★★★
()
Последнее исправление: makoven (всего исправлений: 1)

Но, увы, не любой язык можно скомпилировать в динамическую библиотеку

Не совсем понятен ход твоих мыслей. Если ты пишешь плагин на сишке или любом другом языке, умеющем в C ABI, то получаешь so-шку, которую потом грузишь через so = dlopen() и общаешься примерно так:

struct API {
    int (*func1)(void);
    double (*func2)(const char *s);
} api = { ... };

struct PluginAPI {
    void *instance_data;
    struct API *api;
    void (*close)(struct PluginAPI *plugin);
    void (*process_event)(struct PluginAPI *plugin, const char *event);
};

void *so = dlopen("/path/to/plugin.so", 0);
struct PluginAPI *plugin1 = dlsym(so, "plugin_new")(&api);
plugin1->process_event(plugin1, "event_name");
// ...
plugin1->close(plugin1);
dlclose(so);

Для некомпилируемых языков просто будет одна so-шка для всего языка, которая будет бриджить API в язык и обратно, экспортируя его символы в соответствии с как там принято. Точно также PluginAPI будет замаплен на функции в скрипте. Единственное внешнее отличие — придется передавать имя скрипта в plugin_new:

dlsym(so, "plugin_new")(&api, script_path);


можно ли как-то таки скомпоновать хотя бы питоньий код в динамическую библиотеку

Можно, но зачем? Хочешь обязать «одминов» статически компилировать питон вместе со скриптом на каждый скрипт? Пусть пишут свои settings.lua, proces1.pl и obrabotka_dannyx.py где-нибудь в /usr/local/share/<daemon_name>/scripts и все.


Реализовывать оба интерфейса возможности нет

Не вижу причин. Если ты сделаешь бридж в скриптовую среду, то кто-нибудь за вечер склепает на ней модуль с posix.popen и начнет юзать его как типо-cgi-прокси. Вот необходимости такой и правда нет.

arturpub ★★
()
Последнее исправление: arturpub (всего исправлений: 1)

можно ли как-то таки скомпоновать хотя бы питоньий код в динамическую библиотеку?

И да и нет. Питон, по крайней мере мейнстрим-питон, не может компилироваться в машинный код. Но ничто не мешает тебе засунуть питоновский байт-код в подходящую секцию динамической библиотеки и невозбранно использовать из главной программы. Но да, в таком случае большинство вопросов линковки (как статической линковки SO, так и динамической линковки плагина) лягут на твои плечи.

Macil ★★★★★
()
Ответ на: комментарий от KivApple

Тоже думал об этом, вариант неплохой.

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от makoven

Спасибо за либу, сейчас почитаю, возможно, и не придется велосипедить)

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от arturpub

Для некомпилируемых языков просто будет одна so-шка для всего языка, которая будет бриджить API в язык и обратно, экспортируя его символы в соответствии с как там принято.

А можно поподнобнее об этом? Не для задачи, просто интересно.

mersinvald ★★★★★
() автор топика

на мой взгляд у вас идея шиворот-навыворот.

логичнее взять скриптовый язык в качестве ядра и расширять его модулями С, чем наоборот.

удобнее тем, что:

  • 100% что существуют языки более высокого уровня подходящие для вашей задачи
  • ядро проще модифицировать если оно на дин.языке высокого уровня
  • API расширений/модулей стабилен и поддерживается не вами
  • (вытекает из 1) можно найти готовые/годные модули и не писать самим
MKuznetsov ★★★★★
()

Чем меньше оверхеда и затрат - тем лучше. (пишу системный демон с довольно высокой частотой пробуждения)

Инфа 99% что питона для всего хватит полностью. Но деталей, как всегда, автор рассказать не пожелает.

A1
()

Собственно, я вижу два варианта устройства расширений:
1. Динамические библиотеки - подгрузка через dlopen, короче канон расширений плагинами.
2. Что-то вроде подхода CGI - единый интерфейс обмена через переменные окружения, stdin/stdout

Через POSIX Shared Memory еще можно

SZT ★★★★★
()

Либо сразу js интерпретатор с API, либо плагины, а плагин уже может реализовать все что тебе нравится и сам встроить js.

зы lua фтопку.

invy ★★★★★
()
Ответ на: комментарий от makoven

lua - говно. точнее не сам язык говно, а интерпретаторы. Насколько я понял выбирать особо не из чего. lua, luajit, llvm-lua. Вроде бы единственный «юзабельный» - luajit, но он убог из-за дурацкого ограничения «на 64х битной системе память для lua интерпретатора должна выделяться только в первых двух гигабайтах адресного пространства».

А потом имеем вот такие костыли:
http://hacksoflife.blogspot.de/2012/12/integrating-luajit-with-x-plane-64-bit...

зы и да, два года спустя воз и ныне там.

invy ★★★★★
()
Последнее исправление: invy (всего исправлений: 1)
Ответ на: комментарий от invy

lua - говно. точнее не сам язык говно, а интерпретаторы.

тыибанись. нахер тебе джит? луа 5.3 вполне себе компилячится чем угодно. а luajit замер на версии 5.2, а разница там ощутима. Просто бери исходники lua и собирай со своим проектом. Не будет костылей.

зыж llvm-lua вообще недоразумение

anonymous
()
Ответ на: комментарий от invy

точнее не сам язык говно, а интерпретаторы

Ситуация с точностью наоборот. У луа есть референсный интерпретатор на ANSI C с красивым стандартным C API, для которого уже написана куча биндингов.

А js-интерпретаторов масса, причем популярные явно избыточны для встраивания и написаны на плюсах. А такого, чтобы имел C API и готовые биндинги - нету. v7 хорош, но вложено в него явно меньше человеко-часов, чем в луа

makoven ★★★★★
()
Последнее исправление: makoven (всего исправлений: 1)
Ответ на: комментарий от invy

Чтобы оно не тормозило?

Ага. А в бенчмарках всякие алгоритмы. Смысл писать сортировку и трассировку лучей на луа? Он не для того в проект встраивается

makoven ★★★★★
()
Ответ на: комментарий от invy

Там 5.1 это значит раз. 5.1 это вообще другой язык по сравнению с 5.3 хотя бы потому что в 5.3 есть честные целочисленные и битовые операции, а не через double как в 5.1.

А в целом lua не про это. луа про клей между нативщиной.

anonymous
()
Ответ на: комментарий от makoven

причем популярные явно избыточны для встраивания и написаны на плюсах.

Т.е. тебя плюсы смущают? :)

Ага. А в бенчмарках всякие алгоритмы. Смысл писать сортировку и трассировку лучей на луа? Он не для того в проект встраивается

А для чего его встраивают? Ты можешь гарантировать, что lua не станет узким местом в проекте?

invy ★★★★★
()
Ответ на: комментарий от mersinvald

Подробнее разве что реализация бриджа будет... Ну вот частичный пример для мерзкого ненужного Lua:

struct PluginAPI *
plugin_new(struct API *api, const char *path)
{
    struct PluginAPI *me = malloc(sizeof(*me));
    me->L = luaL_newstate(); // создаем стейт
    luaL_openlibs(me->L); // стандартные либы

    // registry.api = api
    lua_pushlightuserdata(me->L, api);
    lua_setfield(me->L, LUA_REGISTRYINDEX, "api");

    // globals.func2 = api.func2
    lua_pushcfunction(me->L, l_func2);
    lua_setglobal(me->L, "func2");

    if (luaL_dofile(me->L, path)) {
        // report error
        lua_pop(L, 1);
    }

    return me;
}

static struct API *
l_api(lua_State *L)
{
    lua_getfield(L, LUA_REGISTRYINDEX, "api")
    struct API *api = lua_touserdata(L, -1);
    return lua_pop(L, 1), api;
}

static int
l_func2(lua_State *L)
{
    const char *s = luaL_checkstring(L, 1);
    lua_pushnumber(L, l_api(L)->func2(s));
    return 1;
}

Теперь можно в скрипте говорить print(func2("qwe")). По аналогии можно обрабатывать события-вызовы из главного модуля, только нужно обертки написать C-to-Lua по типу l_func2 и засунуть их в PluginAPI. Как видишь, этот модуль универсальный для всего языка, только путь к скрипту разный (или один даже и тот же, можно несколько инстансов создавать). То же самое будет для питона, для перла (с мультиплисити), для лиспа, для жс.

arturpub ★★
()
Ответ на: комментарий от invy

Т.е. тебя плюсы смущают? :)

Ну попробуй интегрируй V8 в rust. Или напиши сишную либу для nodejs

А для чего его встраивают?

Для скриптиков в десятки строчек, например позволить пользователю написать реакцию на какое-то событие. Там обычно многого не нужно: несколько if-ов и вызовов библиотечных функций

makoven ★★★★★
()
Последнее исправление: makoven (всего исправлений: 1)

Бери первый вариант. Можно будет потом, если что, доставить таким образом поддержку питоньих скриптов. (или любых других, лишь бы сишное ABI умело)

a1batross ★★★★★
()
Ответ на: комментарий от makoven

А потом скриптиков внезапно становится несколько десятков и выполняться они должны десятки раз в секунду. А сволочи-пользователи начинают писать тяжеловесные скрипты. И тут вдруг начинаются тормоза.

invy ★★★★★
()
Последнее исправление: invy (всего исправлений: 1)
Ответ на: комментарий от invy

Правильно, не стоит пользователям давать свободу - путь расширенияна си пишут, не такие резвые будут.

mersinvald ★★★★★
() автор топика
Ответ на: комментарий от invy

Количество скриптиков значения не имеет (все-равно большинство будет лежать мертвым грузом почти всегда), а вот частота - да. Если она измеряется в секундах - то референсный lua. А если чаще - то, наверное luajit. А лучше си)

makoven ★★★★★
()
Ответ на: комментарий от mersinvald

путь расширенияна си пишут, не такие резвые будут.

тогда их нужно в отдельный процесс пихать, а иначе очень легко получишь segfault

dimon555 ★★★★★
()

питон хорошо встраивается в си

anonymous
()

Специально для таких целей изобретен Луа. Встраиваемый и быстрый. Питон тут сильно проиграет по производительности.

Aswed ★★★★★
()
Ответ на: комментарий от dimon555

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

mersinvald ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.