Hello World с GUI на Common Lisp
Aug. 11th, 2009 07:23 amСоздадим простую программу с GUI, которую можно будет запускать как отдельное приложение. Программа не будет делать ничего полезного, и будет просто показывать окно с надписью “Hello, world”.
Для построения GUI воспользуемся библиотекой cl-gtk2.
Опишем процесс по шагам
Запускаем Slime, загружаем cl-gtk2:
(asdf:oos 'asdf:load-op :cl-gtk2-gtk)
Создаем исходный файл (назовем его hello-world.lisp) со следующим текстом:
(defpackage :hello-world
(:use :cl :gobject :gtk)
(:export :main :run))
(in-package :hello-world)
(defun main ()
(within-main-loop
(let ((w (make-instance 'gtk-window :title "Hello, world"))
(l (make-instance 'label :label "Hello, world!")))
(container-add w l)
(connect-signal w "destroy" (lambda (w)
(declare (ignore w))
(gtk-main-quit)))
(widget-show w))))
(defun run ()
(main)
(join-main-thread))
В этом исходнике определяется пакет hello-world, в котором определяются две функции: main и run. Функция main нужна во время разработки – она запускает программу в другой нитке (за это отвечает макрос within-main-loop), а run используется при запуске программы отдельно, не из slime.
Функция join-main-thread ожидает, когда главный цикл обработки сообщений в Gtk+ будет завершен. Этот цикл завершается, когда окно закрывается и получает сигнал “destroy”.
Можно протестировать программу прямо из Slime:
(hello-world:main)
После ввода этой формы в REPL будет запущена фоновая нить (thread), в которой работает Gtk+, и управление сразу вернется в REPL.
Теперь перейдем к созданию программы, которая может запускаться отдельно.
Для начала, следует определить ASDF-систему для нашего hello-world'а.
Создаем файл hello-world.asd в одном каталоге с hello-world.lisp следующего содержания (хотя названия системы и пакета в данном примере совпадают, это совсем не обязательно):
(defsystem :hello-world :name "hello-world" :components ((:file "hello-world")) :depends-on (:cl-gtk2-gtk))
В этом описании системы указывается, как следует собирать программу: программа зависит от системы cl-gtk2-gtk и состоит из одного исходного файла hello-world.lisp (расширение .lisp добавляется автоматически).
Обычно с помощью ASDF определяются системы, содержащие код библиотек и помещаются в общесистемный каталог /usr/share/common-lisp/systems. В данном случае, определяется система для отдельной программы.
С помощью cl-launch можно превратить описание этой системы в запускающий бинарник.
Сперва надо установить cl-launch. В gentoo linux с подключенным lisp-overlay это делается вводом команды
emerge dev-lisp/cl-launch
В других дистрибутивах можно воспользоваться ASDF-INSTALL для установки в общесистемный каталог или каталог пользователя:
(require :asdf-install) (asdf-install:install :cl-launch)
cl-launch содержит шелл-скрипт cl-launch.sh, который используется для приготовления лисповских программ к запуску как отдельные приложения.
Сперва создадим образ лиспа, в котором содержатся cl-gtk2-gtk и наш hello-world. Если не создавать образ, то придется загружать cl-gtk2-gtk из исходников или fasl'ов. Загрузка и компиляция из исходников – очень долгий процесс; загрузка из fasl'ов происходит быстрее, но все равно долго (на моем компьютере загрузка из fasl'ов занимает 30 секунд). Для разработки программ создавать образ не нужно, так как все загружается один раз при запуске.
Для того, чтобы создать образ, введем команду (находясь в одном каталоге с hello-world.lisp и hello-world.asd):
cl-launch.sh -s hello-world -d hello-world-image
cl-launch загружает систему hello-world вместе со всеми зависимости (cl-launch добавляет текущий каталог в список каталогов, в которых ASDF ищет системы, поэтому никаких симлинков на hello-world.asd создавать не нужно) и сохраняет образ в файл hello-world-image.
Если установлено несколько реализаций лиспа, то можно выбрать, какую из них использовать:
cl-launch.sh --lisp sbcl -s hello-world -d hello-world-image
Далее надо с помощью cl-launch создать шелл-скрипт, запускающий программу из созданного образа:
cl-launch.sh -m hello-world-image -i '(hello-world:run)' -o hello-world
(если установлено несколько реализаций лиспа, то также можно добавить ключ --lisp)
В результате этой команды будет создан скрипт hello-world, который запускает программу.
Попробуем запустить его:
./hello-world
Должно практически сразу же появиться окошко:
Время запуска программы из образа можно назвать вполне приемлемым: окошко появляется спустя едва различимый интервал времени.
Но у рассмотренного способа создания приложений есть один недостаток – размер получаемого образа. В моем случае (для SBCL на 64-битной машине) размер составил 64 мегабайта. Основная часть этого образа – сам SBCL (размер основного образа SBCL на моей машине – 42 мегабайта), остальная часть – это cl-gtk2-gtk и совсем небольшая часть – собственно приложение. То есть, при усложнении собственной программы и добавлении к ней кода размер образа не будет сильно расти.
Чтобы исправить эту проблему, можно сжать исполняемый образ с помощью программы gzexe.
gzexe hello-world-image
На моей машине размер образа уменьшается с 64Mb до 12Mb и несколько увеличивается время загрузки программы (с 0.5 с до 1.4 с). 12Mb – это уже более приемлемо даже для загрузки программы из интернета, но все равно много. Разные реализации лиспа создают образы по-разному, и размеры варьируются от реализации к реализации, поэтому из приведенных чисел не следует, что приложения всегда будут получаться большими.
Одним из приемлемых способов распространения приложений на лиспе для дистрибутивов линукса, по-видимому, является сборка образа из исходников на конечной машине. При таком способе общий объем загружаемых из интернета данных получается не настолько большим (при условии, что в составе дистрибутива имеется компилятор лиспа), и время запуска приложения приемлемое. При этом все используемые лисповые библиотеки можно как брать с машины пользователя (для gentoo- или debian-подобных дистрибутивов), так и держать рядом с исходниками (если нет пакетного менеджера, в репозитории которого имеется необходимый софт)
Замечание: сохранение образов с загруженным cl-gtk2 пока работает лишь в SBCL. Работоспособность сохраненных образов с cl-gtk2 в других лиспах - вопрос времени.
no subject
Date: 2009-08-11 08:18 am (UTC)// npoektop
no subject
Date: 2009-08-11 08:43 am (UTC)no subject
Date: 2009-08-11 08:44 am (UTC)no subject
Date: 2009-08-11 09:07 am (UTC)А разве в образ входят замапленные sо? Мне казалось, они выгружаются, делается сборка мусора и сохраняется память. А при загрузке образа библиотеки загружаются.
no subject
Date: 2009-08-11 09:35 am (UTC)load-shared-object interacts with sb-ext:save-lisp-and-die:
1. If dont-save is true (default is NIL), the shared object will be dropped when save-lisp-and-die is called -- otherwise shared objects are reloaded automatically when a saved core starts up. Specifying dont-save can be useful when the location of the shared object on startup is uncertain.
2. On most platforms references in compiled code to foreign symbols in shared objects (such as those generated by DEFINE-ALIEN-ROUTINE) remain valid across save-lisp-and-die. On those platforms where this is not supported, a warning will be signalled when the core is saved -- this is orthogonal from dont-save.
no subject
Date: 2009-08-11 09:28 am (UTC)no subject
Date: 2009-08-17 05:13 pm (UTC)Я сам пока не знаю точного ответа на этот вопрос. Может быть, это NIH-синдром, а может быть, у меня получится лучше. Посмотрим.
А вообще clg выглядит очень приличным (в плане реализации) пакетом. Но он давно не обновлялся, нет документации, туториалов.
А еще что-то мне в нем сперва не понравилось, но уже не помню, что.
no subject
Date: 2009-10-17 08:24 pm (UTC)no subject
Date: 2009-10-17 08:46 pm (UTC)Там расписано, как ставить. Если возникнут проблемыв - обращайтесь.
no subject
Date: 2009-10-17 09:50 pm (UTC)<pre>
(asdf:operate 'asdf:load-op :cl-gtk2-gtk)
; loading system definition from
; /home/user/.sbcl/systems/cl-gtk2-pango.asd into #<PACKAGE "ASDF0">
; registering #<SYSTEM :CL-GTK2-PANGO {AC08B61}> as CL-GTK2-PANGO
; loading system definition from /home/user/.sbcl/systems/iterate.asd into
; #<PACKAGE "ASDF0">
; registering #<SYSTEM :ITERATE {AD4AD29}> as ITERATE
; registering #<SYSTEM :ITERATE-PG {AE72DD9}> as ITERATE-PG
; registering #<SYSTEM :ITERATE-TESTS {B01F311}> as ITERATE-TESTS
; loading system definition from /home/user/.sbcl/systems/cl-gtk2-glib.asd
; into #<PACKAGE "ASDF0">
; registering #<SYSTEM :CL-GTK2-GLIB {B2E27D1}> as CL-GTK2-GLIB
отладчик выдал условие:
; loading system definition from
; /home/user/.sbcl/systems/cl-gtk2-pango.asd into #<PACKAGE "ASDF0">
; registering #<SYSTEM :CL-GTK2-PANGO {AC08B61}> as CL-GTK2-PANGO
; loading system definition from /home/user/.sbcl/systems/iterate.asd into
; #<PACKAGE "ASDF0">
; registering #<SYSTEM :ITERATE {AD4AD29}> as ITERATE
; registering #<SYSTEM :ITERATE-PG {AE72DD9}> as ITERATE-PG
; registering #<SYSTEM :ITERATE-TESTS {B01F311}> as ITERATE-TESTS
; loading system definition from /home/user/.sbcl/systems/cl-gtk2-glib.asd
; into #<PACKAGE "ASDF0">
; registering #<SYSTEM :CL-GTK2-GLIB {B2E27D1}> as CL-GTK2-GLIB
</pre>
no subject
Date: 2009-10-18 07:30 am (UTC)Видимо, забыли скопировать вывод отладчика.
no subject
Date: 2009-10-18 09:02 am (UTC)<pre>
component :CLOSER-MOP not found, required by
#<SYSTEM "cl-gtk2-glib" {B2E0039}>
[Condition of type ASDF:MISSING-DEPENDENCY]
Restarts:
0: [ABORT] Return to SLIME's top level.
1: [TERMINATE-THREAD] Terminate this thread (#<THREAD "repl-thread" RUNNING {A881591}>)
Backtrace:
0: ((LABELS ASDF::DO-ONE-DEP) ASDF:COMPILE-OP :CLOSER-MOP NIL)
1: ((LABELS ASDF::DO-DEP) ASDF:COMPILE-OP (:CLOSER-MOP :BORDEAUX-THREADS :ITERATE :TRIVIAL-GARBAGE :CFFI))
2: ((SB-PCL::FAST-METHOD ASDF::TRAVERSE (ASDF:OPERATION ASDF:COMPONENT)) #(3 NIL) #<unavailable argument> #<ASDF:COMPILE-OP NIL {B306B21}> #<ASDF:SYSTEM "cl-gtk2-glib" {B2E0039}>)
3: ((LABELS ASDF::DO-DEP) ASDF:COMPILE-OP (:ITERATE :CL-GTK2-GLIB))
4: ((SB-PCL::FAST-METHOD ASDF::TRAVERSE (ASDF:OPERATION ASDF:COMPONENT)) #(3 NIL) #<unavailable argument> #<ASDF:COMPILE-OP NIL {AC236F9}> #<ASDF:SYSTEM "cl-gtk2-pango" {AC0A3C9}>)
5: ((LABELS ASDF::DO-DEP) ASDF:COMPILE-OP (:CL-GTK2-PANGO :ITERATE :BORDEAUX-THREADS :CL-GTK2-GDK :CFFI :CL-GTK2-GLIB))
6: ((SB-PCL::FAST-METHOD ASDF::TRAVERSE (ASDF:OPERATION ASDF:COMPONENT)) #(3 NIL) #<unavailable argument> #<ASDF:COMPILE-OP NIL {AAFA4A9}> #<ASDF:SYSTEM "cl-gtk2-gtk" {AABB729}>)
7: ((LABELS ASDF::DO-DEP) ASDF:COMPILE-OP ("cl-gtk2-gtk"))
8: ((SB-PCL::FAST-METHOD ASDF::TRAVERSE (ASDF:OPERATION ASDF:COMPONENT)) #(3 NIL) #<unavailable argument> #<ASDF:LOAD-OP NIL {A8E0201}> #<ASDF:SYSTEM "cl-gtk2-gtk" {AABB729}>)
9: (ASDF:OPERATE ASDF:LOAD-OP :CL-GTK2-GTK)[:EXTERNAL]
10: (SB-INT:SIMPLE-EVAL-IN-LEXENV (ASDF:OOS (QUOTE ASDF:LOAD-OP) :CL-GTK2-GTK) #<NULL-LEXENV>)
11: (SWANK::EVAL-REGION "(asdf:oos 'asdf:load-op :cl-gtk2-gtk)
")
12: ((LAMBDA NIL))
13: (SWANK::TRACK-PACKAGE #<CLOSURE (LAMBDA NIL) {A888755}>)
14: ((LAMBDA (SWANK-BACKEND::FN)) #<CLOSURE (LAMBDA NIL) {A88873D}>)
15: (SWANK::CALL-WITH-BUFFER-SYNTAX #<CLOSURE (LAMBDA NIL) {A88873D}>)
16: (SWANK::REPL-EVAL "(asdf:oos 'asdf:load-op :cl-gtk2-gtk)
")
17: (SB-INT:SIMPLE-EVAL-IN-LEXENV (SWANK:LISTENER-EVAL "(asdf:oos 'asdf:load-op :cl-gtk2-gtk)
") #<NULL-LEXENV>)
18: ((LAMBDA NIL))
19: ((FLET #:FORM-FUN1570))
20: ((FLET #:FORM-FUN1570))
21: ((LAMBDA (SWANK-BACKEND::HOOK SWANK-BACKEND::FUN)) #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL) {A887EED}>)
22: ((LAMBDA NIL))
23: ((FLET #:FORM-FUN1570))
24: ((FLET #:FORM-FUN1570))
25: ((LAMBDA (SWANK-BACKEND::HOOK SWANK-BACKEND::FUN)) #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<FUNCTION (LAMBDA NIL) {B9802A5}>)
26: (SWANK::CALL-WITH-REDIRECTED-IO #<SWANK::CONNECTION {B38DF09}> #<CLOSURE (LAMBDA NIL) {A887E5D}>)
27: (SWANK::CALL-WITH-CONNECTION #<SWANK::CONNECTION {B38DF09}> #<FUNCTION (LAMBDA NIL) {B9802A5}>)
28: (SWANK::HANDLE-REQUEST #<SWANK::CONNECTION {B38DF09}>)
29: (SWANK::REPL-LOOP #<SWANK::CONNECTION {B38DF09}>)
30: (SWANK::REPL-LOOP #<SWANK::CONNECTION {B38DF09}>)[:EXTERNAL]
31: (SWANK::CALL-WITH-BINDINGS NIL #<CLOSURE (LAMBDA NIL) {A88707D}>)
32: ((FLET SB-THREAD::WITH-MUTEX-THUNK))
33: ((FLET #:WITHOUT-INTERRUPTS-BODY-[CALL-WITH-MUTEX]477))
34: (SB-THREAD::CALL-WITH-MUTEX #<CLOSURE (FLET SB-THREAD::WITH-MUTEX-THUNK) {B6435205}> #S(SB-THREAD:MUTEX :NAME "thread result lock" :%OWNER #<SB-THREAD:THREAD "repl-thread" RUNNING {A881591}> :STATE 1) #<SB-THREAD:THREAD "repl-thread" RUNNING {A881591}> T)
35: ((LAMBDA NIL))
36: ("foreign function: #x8064C8C")
37: ("foreign function: #x8052D31")
38: ("foreign function: #x805C16D")
39: ("foreign function: #xB7FB74FF")
</pre>
no subject
Date: 2009-10-18 05:30 pm (UTC)Устанавливается так же, как и cl-gtk2: надо разархивировать и сделать симлинк.
В архиве с http://libcl.com/ есть практически все необходимые библиотеки (можно просто сделать симлинки на все нужные библиотеки), кроме trivial-garbage, ее придется отдельно ставить.
no subject
Date: 2009-10-19 12:05 am (UTC)CL-NUMLIB - (FFA)
FFA - COMPILE-FAILED
Остальные поставились успешно (SBCL)
Слил снапшот trivial-garbage запустил ./release.sh который обновил его до версии 1.0 (как я понимаю только в этой папке) Правильно ли я понимаю, что я должен куда-то положить asd-файл и lisp-файл и что то прописать в конфигурации? Бросьте в меня ссылкой о том как уставливаются библиотеки лиспа, пока я просто следовал инструкциям, но хотелось бы понимания процесса...
Правильный антиквариат в Санкт-Петербурге
Date: 2010-04-06 12:25 pm (UTC)