Суть проблемы. Есть GObjectIntrospection. Позволяет получить из библиотеки список классов, функций, констант. Далее для класса описание методов, полей.
Проблема в том, что CLOS предполагает головной формой имя метода. Таким образом у меня есть несколько вариантов
1. Забить на CLOS. Тогда интерфейс будет выглядеть где-то так:
(gi-cffi:init "Gtk" :gi-gtk)
(let (win (gi-gtk:call 'window 'new 0))
(gi-gtk:call win 'show)
(gi-gtk:call 'main))
2. Импортировать классы on-demand
(gi-cffi:init "Gtk" :gi-gtk)
(let (win (gi-gtk:call 'window 'new 0)) ;; здесь импортируются методы класса window
(gi-gtk:show win)
(gi-gtk:call 'main))
3. Импортировать всю библиотеку
(gi-cffi:init "Gtk" :gi-gtk) ;; здесь импортируется всё
(let (win (gi-gtk:new 'window 0))
(gi-gtk:show win)
(gi-gtk:main))
Третий вариант наиболее красивый синтаксически, но будут тормоза или при компиляции, или при запуске.
Помогите выбрать. Или посоветуйте что-нибудь ещё
Сколько времени занимает перечисление всех методов всех классов?
Просто перечисление -- мгновенно. Но там (для Gtk) 2550 методов. Соответственно будет 2550 форм defmethod. Сколько они будут компилироваться -- не знаю.
> Нужно на CLOS.
В смысле, генерить всю библиотеку во время компиляции?
Там на самом деле во всех случаях CLOS
1.
(defmethod call ((obj-name (eql 'main)) &rest args) ...)
(defmethod call ((obj-name (eql 'window)) &rest args) ...)
(defmethod send ((object window) (method-name (eql 'show)) &rest args) ...)
(let (win (gi-gtk:call 'window 'new 0))
(gi-gtk:send win 'show)
(gi-gtk:call 'main))
2. То же, но без метода send. Вместо него пачка
(defmethod show ((object window) &rest args) ...)
(defmethod ... ((object window) &rest args)
в момент создания первогообъекта с типом GtkWindow.
3. То же, но без метода call. Вместо него сразу всё генерится.
Методы с сигнатурой
(defmethod ... ((object ...) &rest args)
Функции -- согласно описания из GI.
Если подумать, то можно скрестить 2 и 3.
Сгенерировать конструкторы сразу, но в код сгенерированного конструктора воткнуть генерацию остальных методов.
P.S. При любом раскладе получается, что метод call и/или конструктор придётся делать макросом. Иначе получим пачку warning'ов про то, что "символа нет, функции нет" в момент компиляции.
Не советую №3. Такой подход выбран в cl-gtk2 и в итоге получается очень большой образ.
Зачем нужен CLOS, не очень понятно. Вот, например, в (gi-gtk:show win) зачем тут generic? Тут же вызывается всегда одна и та же функция gtk_widget_show.
Если есть желание переопределять/доопределять методы, то они всё равно не будут видны из GObject если об этом отдельно не позаботиться. Ну а там это получится только с виртуальными методами. Делать все GObject методы дженериками в лиспе ИМХО неразумно. Да и оверхед лишний от них.
> Зачем нужен CLOS, не очень понятно.
(gi-gtk:show win) -> gtk_widget_show. А, например, (gi-gtk:text obj) будет разный в зависимости от того, obj имеет тип entry, label или text-buffer.
Совсем без CLOS можно только в первом варианте. Например, если сделать
(defun gi-cffi:call (obj &rest args)
(if (functionp obj) (apply obj args) (create-obj-closure obj args)))
(let (win (gi-gtk:call 'window 'new 0))
(gi-gtk:call win 'show)
(gi-gtk:call 'main))
Хотя может и вариант. А потом как в CommonQt добавить reader-macro и будет
(let (win (#_new 'window 0))
(#_show win)
(#_main))
> Совсем без CLOS
Только жаль при таком раскладе gtk-cffi хоронить. Потому как подход полностью несовместим.
<offtopic>Эх-х. На эти биндинги убито столько человеко-часов, что можно было бы сделать тулкит с нуля.</offtopic>
В общем, по здравом размышлении, буду делать полностью динамический вариант на замыканиях. Генерировать лишнее на ходу = тормозить первое обращение к классу. Генерировать всё заранее -- тогда уж лучше к gtk-cffi полуавтоматическую дописывалку модулей сделать, там хоть fasl'ы остаются.
Andy128k, я так понимаю, что https://github.com/andy128k/cl-gobject-introspection -- твой проект и ты его уже два года не трогаешь. Ты не будешь против, если я из него сделаю штуку с таким интерфейсом? Больно название у него хорошее и на https://live.gnome.org/GObjectIntrospection/Users уже прописан.
Если не против, то лицензию этому проекту пропиши (BSD, LLGPL или что-то ещё), чтобы неоднозначностей не было.
> Соответственно будет 2550 форм defmethod. Сколько они будут компилироваться -- не знаю.
С функиональным интерфейсом к CLOS можно не компилировать одно и то же тысячи раз. Правда, нужен Closer-MOP.
(defun make-method-body (name)
(lambda (object &rest args)
(list* name object args)))(defun add-gi-method (name class)
(let ((g (ensure-generic-function name :lambda-list '(object &rest args)))
(m (make-instance 'closer-mop:standard-method
:specializers (list (find-class class))
:lambda-list '(object &rest args)
:function (make-method-body name))))
(add-method g m)))(defun add-gi-class (name)
(closer-mop:ensure-class name))
(add-gi-class 'j13)
(add-gi-class 'j14)
(add-gi-method 'a 'j13)
(add-gi-method 'a 'j14)
(a (make-instance 'j13))
(a (make-instance 'j14))
(a t)> С функиональным интерфейсом к CLOS можно не компилировать одно и то же тысячи раз.
Ты предлагаешь пользователю библиотеки в заголовке программы вручную перечислить все классы, которые он будет использовать (включая косвенно)?
Если бы у CLOS был формат вызова (send objeсt 'method arg ...), то проблемы бы не было. Но на практике конструкция (gi-gtk:text obj) требует, чтобы gi-gtk:text был определён и экспортирован до начала компиляции этой строки.
Значить надо иметь всю библиотеку определённой при формирования пакета. И тут динамика почти заставляет перекомпилировать всю библиотеку при каждой компиляции клиентского приложения.
Можно не перекомпилировать, если сделать (defmacro gi-cffi:init (lib-name package-designator) ...) и использовать этот макрос в отдельном файле. Но в этом случае все плюсы динамики уходят: всё равно надо компилировать (и загонять в образ) всю библиотеку, всё равно версия в скомпилированном модуле и в системе в момент запуска могут отличаться (то есть надо не забывать перекомпилировать при обновлении GTK, например).
В таком варианте лучше уж на основании информации из GObjectIntrospection генерировать классический биндинг в полуавтоматическом режиме. Работать будет быстрее, потому что вызовы напрямую к функциям библиотеки, а не косвенно через gi_function_info_invoke с лишним копированием всех входных и выходных данных. Работать будет удобнее, так как некоторые вещи можно будет дописать вручную в более лисповом стиле (не new, new_with_label, new_with_mnemonic, а (make-instance ... &key mnemonic label)).
Динамическая версия всё равно нужна, так как написать и поддерживать биндинги для ....
$ ls /usr/share/gir-1.0/ | wc -l
28
для 28 библиотек уже никакого времени и здоровья не хватит.
> ... cl-gobject-introspection ...
А давай я просто добавлю тебя как collaborator.
> А давай я просто добавлю тебя как collaborator.
Давай. Но лицензию всё-равно укажи. Если тебе всё равно, сделай BSD.
> Ты предлагаешь пользователю библиотеки в заголовке программы вручную перечислить все классы, которые он будет использовать (включая косвенно)?
Нет, я показал, как свести расходы на компиляцию всех методов к нулю (теперь время идёт лишь на создание объектов методов и классов, так что 10000 методов или 10000 классов SBCL создаёт за пару секунд), как уменьшить размер образа (каждый созданный метод или класс съедает по 1кб по информации room после full gc), и как сделать так, чтобы всё это не залёживалось в fasl'ах.
> Можно не перекомпилировать, если сделать (defmacro gi-cffi:init (lib-name package-designator) ...) и использовать этот макрос в отдельном файле. Но в этом случае все плюсы динамики уходят: всё равно надо компилировать (и загонять в образ) всю библиотеку, всё равно версия в скомпилированном модуле и в системе в момент запуска могут отличаться (то есть надо не забывать перекомпилировать при обновлении GTK, например).
В моём варианте достаточно вызвать код инициализации ещё раз, и все новые методы будут доступны.
> генерировать классический биндинг в полуавтоматическом режиме. Работать будет быстрее, потому что вызовы напрямую к функциям библиотеки, а не косвенно через gi_function_info_invoke с лишним копированием всех входных и выходных данных.
Да, но где в GTK нужно вызывать функции в большом цикле, чтобы это имело значение?
Давай свой никнейм на гитхабе.
https://github.com/Kalimehtar
> как свести расходы на компиляцию всех методов к нулю (теперь время идёт лишь на создание объектов методов и классов, так что 10000 методов или 10000 классов SBCL создаёт за пару секунд)
А то, что н каждого метода будет свой make-method-body и его придётся компилировать -- уже неважно? Или как предлагаешь тело метода привязывать? Мне почему-то кажется, что defmethod фактически в то де и разворачивается, что ты предлагаешь. Или ты предлагаешь сделать одно тело функции на ввсе методы и диспатчить уже в нём? Тогда зачем вообще CLOS, если по имени/типу надо руками диспатчить?
> Да, но где в GTK нужно вызывать функции в большом цикле, чтобы это имело значение
Навскидку, GtkTreeView. На каждую клетку приходится вызывать в среднем две-три функции GTK.
Мой пример - замыкания вместо кодогенерации.
> GtkTreeView. На каждую клетку приходится вызывать в среднем две-три функции GTK.
Возможно, в таких случаях стоит вручную писать высокоуровневное "наполнить модель данными", в реализации которого можно вызывать конкретные сишные функции, даже если все остальные биндинги сделаны через CLOS / gi_function_info_invoke.