Anatoly Vorobey (avva) wrote,
Anatoly Vorobey
avva

Categories:

о библиотеках (программистское)

Нижеследующее будет интересно только программистам.

Время от времени, когда мне приходится сталкиваться с тем, как в Перле или других похожих языках устроен интерфейс с библиотеками (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 итд.

Ненавижу бессмысленный клей.

Subscribe
  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 33 comments