LINUX.ORG.RU

Сделать стабильный ABI для библиотеки и не повеситься

 , ,


1

3

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

  1. Шпилить её на чистой сишечке, немного страдать, но получить из коробки

  2. Или написать её на C++, страдая немного по-другому, но пользуясь всеми его благами? Однако реализуя тонкую extern "C" { } обёртку. Много бойлерплейта, но ничего глобально сложного.

Насколько я знаю, в таком случае ещё придётся конечную программу линковать компилятором С++, с этим уже не справится.

Насколько так вообще принято делать?

Ответ на: комментарий от witaway

Пили подпольно пет прожект в рабочее время, а на 1с пару часов в день имитируй деятельность. Если то принудиловка за копейки, то не уволят же всё равно.

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

По сути все проблемы растут из того факта что медленный текстовой интерфейс с ним всё понятно и договорились по крайней мере в рамках юниксов, а вот быстрый бинарный, который дёргает код где-то в кишках софта, с ним не договорились и потому остановились на сишном, т.к. сишка была самой популярной/передовой/распространённой в тот момент когда это всё было актуальной проблемой. Windows костылики в своём мире сделали в виде com, но нигде больше они не прижились, да и майкрософт в этом не заинтересован.

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

Я тебе более интересный вопрос могу подкинуть. Вот смотри, пусть у тебя либа на не важно каком яп работает, она много действий делает, пусть ты ей файлы с диска/сети читаешь и обрабатываешь. Бывает так что ты хочешь логи делать в своей программе, а всё что странного в либе происходит, она тебе не говорит. Функция либо отработала, либо нет и вернула ошибку. Но бывает куча пограничных случаев, когда полезно сказать разрабу что он какую-то фигню делает с вероятностью 95% и 5% что он действительно делает то что хочет. Для этого в классическом софте есть логи. А вот то как логи с внутренней кухни библиотеки в софт протянуть тут тоже целая песня, правда обычно она решается только в рамках какого-то стека/фреймворка, в универсальном случае вообще болт на это положен.

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

А в чём, собственно, глобальная проблема?

Библиотека наружу отдаёт функцию, которая принимает коллбэк. Коллбэк должен быть в cdecl. В документации указано, какие аргументы он принимает. Пишешь обёртку и прячешь эту логику внутри. Но это, вероятно, будет работать только в случае, если программа написана на unmanaged языке и умеет в extern "C"

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

Если это всё же нормальный вариант, тогда можно оформить в сишную библиотеку общего назначения с биндингами на все возможные языки.

witaway
() автор топика
Ответ на: комментарий от peregrine

Да, такую проблему я предположил. Не прокатит. Что насчёт второго варианта? В таком случае ты только с C# и Java дёргаешь функции на сишке, но никогда не наоборот. Звучит даже относительно просто в реализации.

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

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

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

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

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

  1. В цикле вызвать си_получить_лог()
  2. Разобрать полученную структуру. Это может быть сишная структура, тогда надо предоставить её определение. Ещё проще, если логи представляют сериализованный массив байт. Тогда нужно предоставить функцию десериализатор. Если сериализация в JSON или protobuf — ещё проще.
  3. Передать в свой высокоуровневый логгер данные.
  4. Повторить.

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

witaway
() автор топика
Последнее исправление: witaway (всего исправлений: 3)
Ответ на: комментарий от peregrine

Кстати, сейчас быстро почитал, какие есть решения проблемы.

В общем, я только что изобрёл велосипед. Точнее, zeromq. В миниатюре. Если верить документации: либа на плюсах, даёт сишный интерфейс, даёт биндинги для любых языков высокого уровня, предназначена для zero latency IPC, поддерживает внутри себя очередь сообщений, даёт возможность подписываться на каналы.

Именно то, что я описывал. Только не как часть готовой библиотеки, а как отдельная библиотека. 🤔

https://zeromq.org/get-started/

Оно, кстати, устроено сложнее, чем я ожидал. Поддерживает ещё и соединения по TCP… Очень интересная штука.

Из ещё интересных открытий, обнаружил у них в исходниках trie.h. Впервые вижу, чтобы кто-то применял Декартово дерево (оно же дерамида, оно же курево) не на олимпиадках, а на практике. Аж приятно стало.

witaway
() автор топика
Последнее исправление: witaway (всего исправлений: 4)
Ответ на: комментарий от peregrine

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

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

Кроме некоторых довольно интересных исключений (вроде рустовского cxx, который делает мост между растом и плюсами при помощи раскрытия макросов и кодогенерации) люди либо пишут на сях, либо на плюсах с вываливанием наружу сишного интерфейса, и дальше самым дефолтным образом импортируют сишные функции и оборачивают их в языкоспецифичные обёртки

witaway
() автор топика
Ответ на: комментарий от MOPKOBKA

Секунду, а как? Как ты передашь шарповый/жабовый/питонячий коллбэк в сишную библиотеку так, чтобы она ничего не заподозрила?

Наверное, просто плохо гуглил — но решений пока не нашёл.

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

Секунду, а как? Как ты передашь шарповый/жабовый/питонячий коллбэк в сишную библиотеку так, чтобы она ничего не заподозрила?

Что она должна заподозрить? Что ты подразумеваешь под словом заподозрить? Во всех популярных языках есть инструменты что бы передавать свои функции как C функцию. Вот простой пример: https://cffi.readthedocs.io/en/latest/using.html#extern-python-new-style-call...

люди либо пишут на сях

Я про другое, посмотри как устроен API этих библиотек на C, что бы не крутить какие странные апи с логгированием, обработкой ошибок. Curl можно посмотреть.

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

#2, мне кажется это самое лучшее. Хотя, конечно, зависит от того, что библиотека делает. Но если там пара функций со статическими структурами, однозначно #2.

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

Есть вариант 3. Написать на любом языке, желательно без GC, а наружу сделать сишный интерфейс.

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

anonymous
()

раз уж пошло художественное обсуждение, «по мотивам»:

языки можно ещё и миксить.

как пример, упомянутый zmq в tcl : https://github.com/jdc8/tclzmq

помнится что с налёту даже слегка втупил, «а как это собрать и деплоить»

PS/ а замена/альтернатива zmq просится. Они слегка прифигели и ополитячились

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

Знаю только один достаточно популярный контрпример — SFML. У них есть отдельный репозиторий с си-биндингами.

В оффтопике это почти норма, с++ код с обертками на си, начиная с кучи COM объектов, и завершая и некоторыми системными API типа DirectX.

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

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

Глянул, а в питоне уже есть решение. Правда не элегантное. В C# тоже есть, уже приятное и довольно интуитивное.

Боюсь даже представить, как оно может работать внутри. В шарпике ты просто декорируешь делегат как UnmanagedFunctionPointer и оно работает из коробки. Предполагаю, на уровне рантайма происходит следующее:

  1. Каким-то образом, на уровне рантайма, проверяется совместимость входных аргументов и выходного значения. int, double, char, long должен быть по размеру как низкоуровневые платформозависимые аналоги; никаких классов; только статические методы. Что в сишарпе с его статической типизацией задача вполне решаемая, а в питоне с этой всей динамикой надо ещё попотеть.

  2. Все эти типы, естественно, анбоксятся.

  3. Каким-то образом, динамически в адресном пространстве создаётся машинный код, принимающий всё это дело и сразу же передающий обратно на выполнение в CLR на выполнение - при этом надо каким-то образом обеспечить совместимость независимо от платформы.

Рад, что это всё есть и уже работает. Меньше рвать жопу придётся. Если знаешь, как это работает на самом деле - буду рад пояснению или направлению, где искать.

P.s. Посмотрел шарповый биндинг курла - там взаимодействие максимально простое через коллбеки. Да. Очередей не надо никаких. Очереди, потоки, лонг поллинги это всё-таки больше для интенсивного месседжинга.

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

Если не получается найти, просто прочти исходники, вот популярная библиотека для таких целей: https://en.wikipedia.org/wiki/Libffi

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

Ужасссс какой. А для чего такое может вообще быть нужно? В доках говорят, компилирует на лету. Значит, вероятно, скрипт хорошо переносится между платформами - хорошо. Но на лету - значит он, наверное, пожизнено тянет с собой gcc или clang. Странное это всё.

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

Из ещё интересных открытий, обнаружил у них в исходниках trie.h. Впервые вижу, чтобы кто-то применял Декартово дерево (оно же дерамида, оно же курево) не на олимпиадках, а на практике. Аж приятно стало.

Алё, гараж. trie != treap.

Trie — это префиксное дерево (рядом ещё Ахо с Карасиком Корасик пробегали, если слышал).

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

Да, глупо вышло. Зато знания обновил. На хабре есть удивительно хорошая статья.

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

В целом у них и кодовая база, и документация очень интересные. Вижу, есть чему новому научиться.

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

Ужасссс какой. А для чего такое может вообще быть нужно? В доках говорят, компилирует на лету. Значит, вероятно, скрипт хорошо переносится между платформами - хорошо. Но на лету - значит он, наверное, пожизнено тянет с собой gcc или clang. Странное это всё.

у разработчиков как правило есть gcc/clang. на крайний случай есть пакет tcc (tiny-c compiler) - так что «на лету» собрать не проблема. В примере с zmq компилер С понадобиться только при сборке пакета, зато не потребует autoconf/cmake с их обвесами.

миксы и компиляции just-in-time,just-in-place используются 1) для относительно простых интерфейсов, 2) когда алгоритм на другом языке или выражается яснее или работает быстрее (случай с математикой на С/С++ внутри python/tcl) 3) совсем crazy - когда исходный текст также генерируется «на лету»

у миксов глобальный минус, их отлаживать и поддерживать это кошмар. А так штука прикольная

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

Ну да.

Собственно, trie в реальном мире куда более часто встречающееся явление, чем treap — оно и понятно почему. Префиксное дерево улучшает асимптотику с n² до n.

А у Декартова дерева гораздо меньше область применимости, это ведь просто дерево поиска, которое не нужно балансировать (и ещё пара очень специфических операций, типа разрезать дерево на два / склеить обратно за логарифм). А зачем, если чёрно-красные деревья уже давно написаны?

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

Ну нет же, Декартово дерево это всё же большее, чем вы описываете. «Пара специфических операций, типа разразния и склеивания» это и есть та киллер фича, на которой в итоге строят как раз очень много всяких операций, причём все за приятное время.

Там очень много интересного:

  1. Массовые запросы, например: сумма, максимум
  2. Массовые модификации, например: прибавить, отнять, присвоить. Выполняются ещё и лениво.
  3. Можно выполнять операции на множествах: пересечение, объединение, разница - причём эти алгоритмы, вроде как, параллелятся.

Поверх этого есть дерамида по неявному ключу. По сути, получается массив, у которого все операции логарифмические. Что-то очень похожее на Rope:

  1. Вставка, удаление в любое место
  2. Перестановка отрезков массива между собой
  3. Циклические сдвиги
  4. Те же самые массовые операции с прибавлениями, покрасками и суммами.
  5. Реверс отрезка массива

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

P.s. На этом моменте мне стало интересно, где её применяют IRL. Нашёл всего пару примеров со всего поиска на гитхабе:

  1. Применяют в Elliptics. Вроде бы в SLRU-кэше и для всякого рода мониторинга. В множестве нод ищут такое подмножество, на которое приходится наибольший трафик, причём только за последние N секунд. Такие ноды можно отправлять на обработку другому серверу, чтобы разгрузить основной. Плюс получение статистики. Pull Request, Code

  2. Применяют в JDK. Я, честно, не вполне понял, что они там делают. Вроде трекинг памяти (NMT) для дальнейшей оптимизации. Там они построили какую-то абстракцию для добавления новых адресных пространств и мемори маппингов в NMT. Подробности не знаю. Но есть интересная деталь: у них там есть ассерты на то, чтобы максимальная глубина дерева не уходила глубже, чем на log_2(N + 1) * 3. Максимальная глубина RB-дерева log_2(N + 1) * 2. Если до сих пор ассерты не сработали и дерамиду не выкинули - значит на практике теоретических проблем с возможной разбалансировкой нет. Pull Request, Code

  3. Раньше мат. пакет GAP использовал у себя в дерамиду в сборщике мусора. Потом выпилили, потому что нашли вариант оптимальнее. Pull Request

  4. В tinygo используют. Как работает там разбираться и разбираться, но они используют дерамиду и очереди для менеджмента памяти. Вроде у них какие-то оптимизации, при которых они иногда используют именно сишную кучу (видимо, вместо своей). Им это нужно, чтобы их сборщик мусора мог работать с чужим аллокатором Pull Request

  5. В голанге зачем-то используют дерамиду для реализации семефоров. Как работает разбираться не могу и не уже не хочу. У них какая-то своя особая реализация, с какими-то модификациями и нестандарными именами. Code

Почему они решили использовать именно дерамиду именно в тех местах - чёрт его знает. Чтобы понять, надо потратить слишком много времени. Все места, где её применяли, были ОЧЕНЬ нетривиальные. Так что просто оставлю эту заметку.

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

Из ещё интересных открытий, обнаружил у них в исходниках trie.h. Впервые вижу, чтобы кто-то применял Декартово дерево (оно же дерамида, оно же курево) не на олимпиадках, а на практике. Аж приятно стало.

Применяли trie. Отказались. Очень неэффективно по кешу, а в нашем случае это было существенно.

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

Кресты вообще не могут иметь совместимого с чем-либо API потому что часть их кода это шаблоны внутри заголовков.

Шаблоны для инстанцирования должны быть обработаны препроцессором (в 2024 - компилятором), который, в свою очередь, должен знать всякие нюансы типа хитрых дефайнов, которые могут происходить откуда-то из глубин системы сборки. То есть, даже если другой язык будет иметь на борту кусок компилятора C++ всё равно это будет боль и страдания.

За API можно считать всё что что угодно, хоть extern «Pascal», хоть Фортран, но только не C++.

Да, есть ещё вариант создания интерфейса: можно парсить промежуточные файлы после компиляции C++ на предмет закодированных в виде mangled names описаний функций. Но почему-то, в отличие от Windows, именно в *nix такие символы создавать не принято - кодируется только имя функции и аргументы, но не возвращаемое значение

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

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

На самом деле, можно и исключения смело кидать - но надо предупредить об этом вызывающих со стороны C. Из C доступна libunwind, в ней есть всё чтобы ловить исключения

Редко, но таки бывают библиотеки, у которых исключительно хорошо спроектированы исключения. Заворачивать их в обёртку на чистом C значит испортить пользовательский опыт.

ahdenchik
()