о библиотеках (программистское)
Jan. 31st, 2007 11:38 pmНижеследующее будет интересно только программистам.
Время от времени, когда мне приходится сталкиваться с тем, как в Перле или других похожих языках устроен интерфейс с библиотеками (shared libraries - .so на юниксах и DLL на Windows), их безумие меня раздражает. В Перле, например, вся система XS для написания расширений на C - одно сплошное упражнение в прикладном мазохизме.
Как правило, приходится писать на C с использованием кучи заголовков и макросов данного языка; потом все это компилируется в shared library, которую может загрузить программа на данном языке во время выполнения, динамически. Зачастую перед компиляцией нужно еще исходники пропустить через специальный препроцессор для данного языка или данной интерфейсной библиотеки (см. SWIG), у этого препроцессора есть свой синтаксис, который нужно помнить, итд. Иногда все это выглядит очень просто - например, модуль Inline в Перле позволяет вызывать код на C прямо так, без казалось бы всякого клея, оно берет и работает - но на самом деле там за кулисами происходит, во время исполнения, создание XS-модуля, компиляция его, и кэширование результата.
Главное, что остается непонятным - зачем вообще нужно что-то компилировать. Мы хотим загрузить файл foobar.dll, в котором есть функция foo(), и после этого вызвать ее из нашего относительно-динамического языка. Мы знаем ее прототип. Мы знаем соглашение вызова (calling convention), которое она использует. У нас есть вся информация, чтобы прямо во время исполнения сконструировать правильный вызов к этой функции. Нам не нужен компилятор C, чтобы сделать эту работу для нас. Да, конечно, с компилятором в целом проще и выглядит надежнее, но какой же это, с другой стороны, overkill; и необходимость правильной трансляции между типами нашего языка и типами C приводит к необходимости использовать ужасные препроцессоры, макросы, и прочую дрянь.
Большинство функций, которые мы хотим вызвать таким образом - простые. Они получают в качестве аргументов такие простые вещи, как int или char* и возвращают то же. Иногда они что-то пишут в один из аргументов по указателю. Для всего этого сделать правильный перевод аргументов туда-обратно прямо во время исполнения - тривиально и не занимает много времени. Для более сложных случаев, когда в фунцкию надо передать, скажем, указатель на структуру, можно придумывать более сложные решения. Скажем, компактное описание структур; или плюнуть и вернуться к компилятору. Но для самых простых случаев, которых очень много - зачем? Мне кажется идиотским тот факт, что вот у меня лежит какая-нибудь libgtk-1.2.so.0 или kernel32.dll, в ней лежит какая-то функция, которая мне нужна, и принимает примитивную строку, и возвращает примитивное число, и я все знаю для того, чтобы ее вызвать, но нет, мне надо идти и писать какую-то хрень на промежуточном клейном языке, чтобы это скомпилировалось, вызвалось, и уже в свою очередь вызвало эту мою функцию.
Пару дней назад опять об этом подумал, на этот раз в применении к языку Lua. Он уж, казалось бы, просто создан для соединения с кодом на C, все для этого заточено, но все равно - вызывать можно только функцию, объявленную определенным образом, с определенными аргументами. Хочешь вызвать какую-то хорошо известную функцию из хорошо известной системной библиотеки - будь добр написать и скомпилировать абсолютно бессмысленный промежуточный клей. Глупо.
Сегодня рассказал об этом своем раздражении Галю, и решил заодно еще раз поискать, есть ли где нормальное решение - и оказалось, что есть в Python: библиотека ctypes, начиная с последней версии языка стандартная. Она делает все именно как я описываю: во время исполнения, без компиляции. Я не знаю Python (пока что - собираюсь вскоре изучить), и не хочу прямо сейчас им начинать пользоваться, но почему там есть, а в том же Перле или Lua - нет?
В общем, решил, что спасение утопающих - дело рук самих утопающих, и начал писать соответствующий модуль (для Perl) вместе с
gaal'ем сегодня. Версия 0.01 будет вызывать только по соглашению cdecl, работать только на x86 linux, и предполагать, что аргументы бывают только 32-битные. Зато этот примитивный прототип уже почти закончен; затем добавим stdcall и соответственно поддержку Windows, а также 64-битные машины и 64-битные аргументы. В более отдаленных планах - parsing прототипа функции в рантайме, правильная обработка передачи буферов и возврата в них результатов, обработка структур с помощью pack/unpack итд.
Ненавижу бессмысленный клей.
no subject
Date: 2007-01-31 10:00 pm (UTC)Препроцессор недоступен. Многие вещи по части совместимости разных версий решаются на уровне препоцессора. Вот пример:
- if (rpmReadPackageHeader(Fd, &h, &isSource, Null(int *), Null(int *))) {
+#if RPM_VERSION >= 410
+ if (rpmReadHeader(NULL, Fd, &h, NULL))
+#else
+ if (rpmReadPackageHeader(Fd, &h, &isSource, Null(int *), Null(int *)))
+#endif
+ {
К тому же so библиотека имеет soname, открывать ее просто так через симлинк /usr/lib/lib$name.so это.... Нужно открывать через /usr/lib/$soname, причем $soname гарантирует минимальную бинарную совместимость...
Что до lua то никакой принципиальной разницы с перлом не вижу. Механизм lightuserdata это по сути тот же T_PTROBJ.
no subject
Date: 2007-01-31 10:09 pm (UTC)Это верно, не спорю. Но в случае, скажем, стандартизованных системных APIев это редко важно. В случае простой функции, которую просто хочешь вызвать, это часто совсем не нужно.
А если делать просто dlopen/dlsym и дальше вызывать функцию по адресу, то весь calling convention держится на честном слове.
Она и так держится на честном слове, просто это честное слово заголовка, который вы включаете.
При этом хорошо если функция и сама библиотека действительно stateless, а ведь в некоторых случаях нужно не забыть вызвать какой-нибудь SSL_library_init() где-нибудь в BOOT.
Препроцессор недоступен. Многие вещи по части совместимости разных версий решаются на уровне препоцессора. Вот пример:
Вот для таких случаев пусть XS и живет, не спорю.
открывать ее просто так через симлинк /usr/lib/lib$name.so это....
Да я все делаю через DynaLoader, зачем же мне руками вызывать dl_open.
no subject
Date: 2007-01-31 10:26 pm (UTC)Не совсем. Заголовок, как правило, включается в компилируемый код библиотеки на стадии сборки самой библиотеки. Этим отслеживается несовпадение прототипов. При рассогласовании прототипов библиотека просто не соберется. Т.е. при правильном подходе (включать заголовок перед реализацией, ср. -Wmissing-prototypes) прототипы из заголовка это значительно больше, чем просто честное слово.
Анатолий, Ваша идея мне понятна и мила. Это когда-то работало, может быть в начале семидесятых годов, на заре юникса. При случае взгляните в unix-v6/iolib/printf.c, это ещё до-stdio'шный код. Факт наверное в том, что сейчас такой подход не работает. Если не перестраховаться, то SEGV поджидает за ближайшим поворотом.
Как Вы загружаете библиотеку через DynaLoader без указания soname?
no subject
Date: 2007-01-31 11:08 pm (UTC)no subject
Date: 2007-01-31 11:29 pm (UTC)no subject
Date: 2007-01-31 11:39 pm (UTC)no subject
Date: 2007-01-31 10:12 pm (UTC)термин, который вам с Галем, видимо, незнаком: "foreign function interface", или коротко ffi.
попробуй прогуглить "ffi Perl", и откроется тебе ненужность трудов твоих.
no subject
Date: 2007-01-31 10:20 pm (UTC)Спасибо!
no subject
Date: 2007-01-31 10:28 pm (UTC)no subject
Date: 2007-02-01 06:35 am (UTC)получить возможность с комфортом пользоваться динамическими библиотеками? (она у тебя есть, просто не в Перле).
улучшить Перл на годик-пару (до очередного bitrot-inducing изменения микроклимата)?
поиграть с FFI на уровне ABI?
no subject
Date: 2007-02-01 06:38 am (UTC)no subject
Date: 2007-01-31 11:12 pm (UTC)(Если еще к вашей бибилиотеке будет какой-то формат "опеисания" вызова -- чтобы его попытаться сделать стандартным -- то всем наступит счастье ;)
PS Основная кстати причина -- почему я интеграционным языком выбрал питон -- который я знаю слабо, а не перл который я знаю достаточно хорошо --- это то что к питону проще вязать С/С++ билиотеки.
no subject
Date: 2007-01-31 11:15 pm (UTC)И еще немножко его.
Вопрос в "стандартном описании reflections для C/C++"
no subject
Date: 2007-01-31 11:21 pm (UTC)no subject
Date: 2007-01-31 11:35 pm (UTC)И/или libffcall там по моему немножко по другому но тоже самое ;)
no subject
Date: 2007-02-01 04:29 am (UTC)no subject
Date: 2007-02-01 06:20 am (UTC)no subject
Date: 2007-02-02 06:31 am (UTC)Но уйти от зависимости от реализации (где-то куча общая, где-то частные) можно аккуратным построением API по принципу "освобождает тот, кто занял".
no subject
Date: 2007-02-02 07:03 am (UTC)no subject
Date: 2007-02-02 07:26 am (UTC)Нет.
Если Вы попытаетесь в случае DLL'ок у которых свои malloc'и вызвать free() в основной программе что получило malloc() в основной - в лучшем случае ничего не будет, в худшем программа завершится. free() должна делать та же DLL. Поэтому, увы, не иллюзия.
> ведь у всех подпрограмм одно адресное пространство.
Это не мешает вышеописанному эффекту.
> Если говорить про windows, то там еще и двухэтапное выделение памяти (сначала зарезервировать непрерывный регион, потом по частям его начинать использовать) влияет, когда 30 пустых куч резервируют для себя 300 МБ адресного пространства.
Да, но это уже другой вопрос.
no subject
Date: 2007-02-02 12:58 pm (UTC)Я имел в виду как раз фрагментацию куч, и как результат - рост объема занятой части адресного пространства пропорционально количеству реально используемой памяти не в первой степени. Частично эффект возникает потому, что это все-таки куча, но множитель количества куч тоже возвожится в не-первую степень. Таким образом, лучше пусть будет одна куча.
Теперь пусть исполняющая машина предоставляет кучу, и я пишу нативную рутину, которая ей пользуется. Нативная рутина явно пишется не с нуля, она пользуется какими-нибудь контейнерами из stl, для которых все реально ленятся писать аллокаторы, и как результат - каждая нативная рутина создаёт свою кучу. (Правда Вы говорите, в юниксах так не принято.) А возможно вспомогательная библиотека просто не умеет пользоваться чужой кучей.
То есть вопрос не в том, что какая-то память не выделится. Это проблема совсем другого класса. Вопрос в том, что адресное пространство превратится в помойку, и при занятых 40 процентах размер максимального свободного региона не будет превышать 20.
no subject
Date: 2007-02-01 07:56 am (UTC)require 'dl/import'
module LIBC
extend DL::Importable
dlload "libc.so"
typealias ("size_t", "unsigned int")
extern "void *qsort(size_t, size_t, void*)"
end
(это был чистый Руби, после этого описания можно вызывать LIBC::qsort)
Так вот, по опыту использования того и другого - программы с Ruby/DL достаточно неудобны в отладке - проще иметь свой написанный на C модуль, в котором можно наставить брекпойнтов и прочего. Но вот уже отлаженные библиотеки народ достаточно успешно переводит на Ruby-only versions (как правило, сопровождаемые предупреждениями "но эта версия намного медленнее" ;)
no subject
Date: 2007-02-01 11:15 am (UTC)Не говоря уже про opendir/readdir(3)
no subject
Date: 2007-02-01 02:40 pm (UTC)(и, while you are at it, какая между ними связь? и, раз уж на то пошло, интерпретируемость и байткодность являются свойствами реализации, а не языка).
no subject
Date: 2007-02-01 02:46 pm (UTC)Можно, конечно, представить себе систему, автоматически генерирующую клей по сишным декларациям, но будет это как-то не очень весело, как мне кажется.
no subject
Date: 2007-02-01 02:56 pm (UTC)это да: массаж данных туда и обратно является местами интересной проблемой, и автоматизировать его в общем случае невозможно.
no subject
Date: 2007-02-01 12:10 pm (UTC).Net это экстаз
Date: 2007-02-01 02:48 pm (UTC)Re: .Net это экстаз
Date: 2007-02-01 03:08 pm (UTC)Причём, всё могло бы быть почти столь же лёгким и в случае взаимодействия С++ с Perl, LUA, питонами и прочего, если бы разработчики С++ лет эдак десять назад сделали бы стандартный ABI для С++ (ну то есть стандарт на бинарное представление объектов), ну и может быть расширили бы стандарт объектных файлов. Это решило бы и кучу проблем в самом языке, и под этот стандарт прогнулись бы все прочие языки попроще, поперлее и попитонистее:) Тогда на то были все возможности: С++ был единственным серъёзным языком. Потом уже всякие джавы с дотнетами полезли, а теперь уже и возможность упущена.
Re: .Net это экстаз
Date: 2007-02-01 07:28 pm (UTC)Re: .Net это экстаз
Date: 2007-02-01 07:30 pm (UTC)Re: .Net это экстаз
Date: 2007-02-01 07:37 pm (UTC)no subject
Date: 2007-02-01 06:54 pm (UTC)Предвзятость? или питонопоклонники тебе платят? :)