Как избежать ошибок FLOATING-POINT-INVALID-OPERATION в CL-CFFI-GTK?
Я сталкиваюсь с ошибками FLOATING-POINT-INVALID-OPERATION при использовании CL-CFFI-GTK с Common Lisp. Ошибка возникает при выполнении базовых операций GTK, таких как:
(gtk-widget-show-all (make-instance 'gtk-window
:type :toplevel
:title "hello"))
Это приводит к:
; Оценка прервана из-за #<FLOATING-POINT-INVALID-OPERATION {10021F69B3}>.
Я пробовал:
- Использовать различные реализации Lisp (SBCL и ECL)
- Тестировать в разных средах (автономный REPL, SLIME+Emacs)
- Исследовать как CL-CFFI-GTK, так и CL-GTK4
- Попытаться использовать
sb-int:with-float-traps-masked, но столкнулся с ошибками вроде “unknown float trap kind: GTK:GTK-WIDGET-SHOW-ALL”
Детали моей системы:
- Debian 13 Trixie
- SBCL 2.5.2.debian
- ECL 24.5.10
Проблема, кажется, связана с CFFI-мостом между Common Lisp и базовыми C-функциями GTK. Я ищу:
- Правильный способ обработки или предотвращения этих ошибок с плавающей запятой
- Руководство по пониманию взаимодействия между GTK и CFFI-мостом
- Лучшие практики для избежания подобных проблем при работе с CL-CFFI-GTK
Как можно решить ошибки FLOATING-POINT-INVALID-OPERATION и обеспечить стабильную работу GTK-приложений в Common Lisp?
Ошибки FLOATING-POINT-INVALID-OPERATION, с которыми вы сталкиваетесь при работе с CL-CFFI-GTK, возникают из-за того, что реализации Common Lisp перехватывают исключения с плавающей точкой при вызовах внешних функций в код GTK на C. Это происходит потому, что SBCL и другие реализации Lisp имеют более строгую обработку ошибок с плавающей точкой, чем код на C, что вызывает конфликты при вызове функций GTK через мост CFFI.
Содержание
- Понимание основной причины
- Специфические решения для SBCL
- Портативная обработка ловушек с плавающей точкой
- Решения для ECL и других реализаций
- Лучшие практики для стабильных приложений GTK
- Отладка и устранение неполадок
Понимание основной причины
Основная проблема заключается в разных стратегиях обработки ошибок с плавающей точкой между Common Lisp и C. Когда вы вызываете функции GTK через CFFI, базовый код C может выполнять операции, которые вызывают исключения с плавающей точкой, которые SBCL считает ошибками, но код на C обычно игнорирует.
Как объясняется в ошибке SBCL #1519630: “При вызовах внешних функций (с помощью sb-alien или обертки, такой как cffi), sbcl будет перехватывать ошибки с плавающей точкой, происходящие в коде C, и по умолчанию sbcl перехватывает ошибки, которые обычно не включены в коде C.”
Это проявляется в виде ошибок FLOATING-POINT-INVALID-OPERATION при выполнении, казалось бы, безобидных операций GTK, таких как gtk-widget-show-all, даже если те же операции работают нормально в нативных приложениях на C.
Специфические решения для SBCL
Использование sb-int:with-float-traps-masked
SBCL предоставляет макрос sb-int:with-float-traps-masked для временного отключения определенных ловушек с плавающей точкой во время вызовов внешних функций. На основе результатов исследования, вот рекомендуемый подход:
(sb-int:with-float-traps-masked (:invalid :inexact :overflow :underflow :divide-by-zero)
(gtk-widget-show-all (make-instance 'gtk-window
:type :toplevel
:title "hello")))
Этот подход упоминался в вопросе #28 как потенциальное решение для подобных проблем.
Глобальная конфигурация ловушек с плавающей точкой
Для более комплексных решений вы можете настроить режимы плавающей точки SBCL глобально. Как показано в cl-cffi-gtk gtk.package.lisp:
#+sbcl
(when (and (find-package "SB-EXT")
(find-symbol "SET-FLOATING-POINT-MODES" (find-package "SB-EXT")))
(funcall (find-symbol "SET-FLOATING-POINT-MODES" "SB-EXT")
:traps '()))
Это отключает все ловушки с плавающей точкой глобально, что может быть необходимо для полной совместимости с GTK.
Портативная обработка ловушек с плавающей точкой
Для более портативного решения на разных реализациях Common Lisp рассмотрите использование библиотеки float-features. Эта библиотека предоставляет единый интерфейс для обработки ловушек с плавающей точкой на различных реализациях.
(float-features:with-float-traps-masked (:invalid :inexact :overflow :underflow :divide-by-zero)
;; Ваш код GTK здесь
(gtk-widget-show-all (make-instance 'gtk-window
:type :toplevel
:title "hello")))
В документации float-features показана поддержка:
- ABCL (:overflow :underflow)
- CCL (:overflow :underflow :inexact :invalid :divide-by-zero)
- CLISP (:underflow)
- CMUCL T
- ECL (:underflow :overflow :inexact :invalid :divide-by-zero)
- MEZZANO T
- SBCL T
Решения для ECL и других реализаций
Обработка специфичная для ECL
Из исследования также упоминалось, что ECL 16.1.3 имеет похожие проблемы. ECL поддерживает маскировку ловушек с плавающей точкой, но с другим синтаксисом:
;; Специфическая для ECL обработка ловушек с плавающей точкой
(ext:with-float-traps-masked (:invalid :inexact :overflow :underflow :divide-by-zero)
;; Ваш код GTK здесь
)
Поддержка Clozure CL (CCL)
CCL также предоставляет возможности обработки ловушек с плавающей точкой:
(ccl:with-float-traps-masked (:invalid :inexact :overflow :underflow :divide-by-zero)
;; Ваш код GTK здесь
)
Лучшие практики для стабильных приложений GTK
1. Оборачивайте все операции GTK
Создайте макрос-обертку, которая автоматически обрабатывает ловушки с плавающей точкой для всех операций GTK:
(defmacro with-safe-gtk (&body body)
"Выполняет BODY с маскированными ловушками с плавающей точкой для безопасных операций GTK."
(float-features:with-float-traps-masked
(:invalid :inexact :overflow :underflow :divide-by-zero)
(progn ,@body)))
2. Инициализируйте GTK с правильной конфигурацией плавающей точки
Настраивайте режимы плавающей точки во время инициализации GTK, как показано в пакете cl-cffi-gtk:
(glib:at-init ()
(eval-when (:compile-toplevel :load-toplevel :execute)
;; Настраиваем режимы плавающей точки для совместимости с GTK
#+sbcl
(when (and (find-package "SB-EXT")
(find-symbol "SET-FLOATING-POINT-MODES" (find-package "SB-EXT")))
(funcall (find-symbol "SET-FLOATING-POINT-MODES" "SB-EXT")
:traps '()))
;; Добавьте здесь другие специфичные для реализации конфигурации
))
3. Стратегии обработки ошибок
Реализуйте надежную обработку ошибок вокруг операций GTK:
(defun safe-gtk-widget-show-all (widget)
"Отображает WIDGET с правильной обработкой ошибок для исключений с плавающей точкой."
(handler-bind
((floating-point-invalid-operation
(lambda (c)
(warn "Ошибка с плавающей точкой в операции GTK: ~a" c)
(continue c))))
(gtk-widget-show-all widget)))
Отладка и устранение неполадок
Определение проблемных функций GTK
При столкновении с ошибками с плавающей точкой определите, какие именно операции GTK их вызывают:
;; Обертка для отладки для определения проблемных операций
(defun debug-gtk-operation (operation &rest args)
"Оборачивает OPERATION GTK с отладкой для определения проблем с плавающей точкой."
(handler-bind
((floating-point-invalid-operation
(lambda (c)
(format t "Ошибка с плавающей точкой в ~a с аргументами: ~a~%" operation args)
(continue c))))
(apply operation args)))
Логирование и мониторинг
Добавьте логирование для отслеживания возникновения ошибок с плавающей точкой:
(setf *debug-io* *standard-output*)
(setf *error-output* *standard-output*)
(defparameter *gtk-debug* nil)
(defun log-gtk-error (condition)
"Логирует ошибки GTK для отладки."
(when *gtk-debug*
(format t "Ошибка GTK: ~a~%" condition))
(continue condition))
;; Используйте обработчик ошибок глобально
(handler-case
(ваш-код-gtk)
(floating-point-invalid-operation (c)
(log-gtk-error c)))
Тестирование разных версий GTK
Исследования показывают, что разные версии GTK могут иметь разное поведение с плавающей точкой. Рассмотрите возможность тестирования с разными версиями GTK для выявления проблем совместимости.
Заключение
Для решения ошибок FLOATING-POINT-INVALID-OPERATION в CL-CFFI-GTK:
- Используйте правильную маскировку ловушек с плавающей точкой с реализациями-специфичными или портативными решениями, такими как
float-features - Настраивайте режимы плавающей точки во время инициализации GTK, особенно для SBCL
- Реализуйте надежную обработку ошибок вокруг всех операций GTK
- Создавайте макросы-обертки для обеспечения последовательной обработки ловушек с плавающей точкой во всем приложении
- Рассмотрите альтернативные реализации, если проблемы сохраняются с вашей текущей настройкой
Ключевое понимание заключается в том, что это известная проблема в интерфейсе Common Lisp ↔ GTK C, и правильная конфигурация ловушек с плавающей точкой необходима для стабильной работы. Большинство пользователей успешно решают проблему либо с глобальной конфигурацией ловушек с плавающей точкой, либо с маскировкой на каждую операцию с помощью with-float-traps-masked.
Для дальнейшей разработки следите за репозиторием cl-cffi-gtk на GitHub для обновлений по обработке плавающей точки, так как это активная область улучшения для библиотеки.
Источники
- Ошибка SBCL #1519630 - Ловушки с плавающей точкой вызываются внешним кодом
- Вопрос cl-cffi-gtk #28 - Ошибка деления на ноль в GTK-MAIN
- cl-cffi-gtk gtk.package.lisp - Конфигурация плавающей точки
- Документация библиотеки Float-Features
- Quickdocs cl-liballegro - Обработка ошибок с плавающей точкой
- Вопрос Quicklisp #743 - Ошибки с плавающей точкой с cl-cffi-gtk
- CLiki: cl-cffi-gtk - Информация о библиотеке
- Учебник GTK 3 для Lisp - Установка и использование