Virgil

Анонсирую библиотеку Virgil - продвинутый высокоуровневый FFI для CL
http://github.com/Lovesan/virgil
Virgil это ссылка на Данте, да.
Библиотека базируется на cffi, но ориентирована на маршалинг, в противовес манипуляциям сырой памятью.
В зависимостях - cffi, babel(для поддержки строк в разных кодировках) и alexandria
Среди особенностей - поддержка ссылок(references) в стиле C++
Вообще, изначально это был бэкенд в другой моей библиотеке, ldx, но потом я его полностью переписал(и ldx теперь будет основываться на нем)
Банальный пример использования:
(define-external-function ("write" put-string) () (int rv (if (>= rv 0) rv nil)) "Puts a string to the specified file descriptor(stdout by default)" (fd int :optional 1) (string (& string)) (count int :aux (length string)))
(put-string "Hello, world!") вернет 13 и напечатает 'Hello, world!' на stdout'е
(put-string "error" 2) - 5 и 'error' на stderr соответственно, а
(put-string "123" 0) вернет NIL
По пунктам:
Имя внешней функции и имя определяемой лисповской функции:
("write" put-string)
Тут пустой список, но вообще тут два необязательных параметра - конвенции вызова и библиотека, из которой внешнее имя вытаскивается [ по дефолту = (:cdecl :default) ]:
()
Тут список из трех элементов - тип возвращаемого значения, в данном случае - int, имя возвращаемого значения(rv) и "форма возвращаемого значения", которая позволяет определять, что именно возвращается после вызова функции. В данном случае мы определем, чтобы при ошибке write(2) функция нам возвращала NIL:
(int rv (if (>= rv 0) rv nil))
Строка документации:
"Puts a string to the specified file descriptor(stdout by default)"
Дальше идут спецификации аргументов функции в форме (имя тип &optional тип-параметра начальное-значение)
Тип параметра - одно из :primary(дефолтно) :aux :key и :optional,
которые определяют соответствующий тип аргумента в лисповской функции
(fd int :optional 1)
Дальше начинается интересное.
string это встроенный тип для строк, который в полной форме выглядит как
(string :encoding имя-кодировки[по умолчанию - :ascii] :byte-length длина-в-байтах)
(если :byte-length не указана, считается, что строка нуль-терминирована),
А вот (& string) это ссылка на строку.
Фактически, это указатель, но функция принимает в аргументы строку, и внутри, перед вызовом write, выделяет память под нее, маршалит в соответствии с кодировкой, а после вызова - освобождает память. Под определенные типы(с фиксированным размером) память может выделяться на стеке, а под некоторые - совсем не выделяться(конкретно - под массивы определенных типов в некоторых реализациях CL - указатель на данные просто берется из самого массива)
Тип "&", кроме типа на который ссылается, в полной форме имеет еще два аргумента - характер передачи данных(:in :out или :inout)[про это далее] и возможность обнуления
[если последнее не nil, указатель может быть нулевой, а лисповское значение - специальной константой void]
(string (& string))
:aux параметр не появляется в аргументах лисповской функции
(count int :aux (length string))
Среди встроенных типов есть структуры, которые определяются очень похоже на defstruct:
(define-struct (float4 (:constructor float4 (x y z w)) (:type (vector single-float))) (x float) (y float) (z float) (w float))
Теперь самое интересное. Как я сказал, ссылки могут определять характер передачи данных(по дефолту :in)
То есть, с их помощью мы можем передавать лисповские данные в нативные функции,
а нативные функции могут эти данные изменять. И, в коллбэках - наоборот.
Вот пример.
Определяем коллбэк, который у нас будет играть роль сторонней функции:
(define-callback (float4-add :stdcall) void ((out (& float4 :inout)) (v (& float4))) (map-into out #'+ out v))
И вызываем его из лиспа:
(let ((v (float4 1.0 2.0 3.0 4.0))) (external-pointer-call (get-callback 'float4-add) ((:stdcall) (void rv outval) ((& float4 :inout) outval) ((& float4) inval)) v v))
==> #(2.0 4.0 6.0 8.0)
Кстати, поддерживаются функции с переменным числом аргументов:
(define-external-function "printf" () (int) (format (& string)) &rest)
define-external-function с аргументом &rest в конце списка типов создает макрос, а не функцию.
Создаваемый макрос имеет сигнатуру вида
(имя ((&optional новая-форма-возвращаемого-значения) &rest типы-дополнительных-аргументов) &rest аргументы)
Пример использования:
(printf (() (uint)) "%08X" 3405691582)
==> 8
и 'CAFEBABE' на stdout'е
Библиотека только вышла, и пока очень не хватает тестов, и, особенно, документации, но это временно.
|
</> |