[personal profile] dmitry_vk

Создадим простую программу с 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 в других лиспах - вопрос времени.

Date: 2009-08-11 08:18 am (UTC)
From: [identity profile] banan.livejournal.com
шестидесятичетырехмегабайтный hello-world — это уже почти .net

// npoektop

Date: 2009-08-11 08:43 am (UTC)
From: [identity profile] dmitry-vk.livejournal.com
Не совсем. Дистрибутив .net framework версии 3.5 весит около 200Мб, если мне не изменяет память.

Date: 2009-08-11 08:44 am (UTC)
From: [identity profile] 13-49.livejournal.com
64 мегабайтный hello-world - это hello-world с мощным рантаймом, компилятором, CLOS, MOP, кучей библиотек, замапленными гткшными so'шками. В принципе, ничто не мешает такому hello-world'у саморазвить свой AI до охрененного уровня и захватить мир.

Date: 2009-08-11 09:07 am (UTC)
From: [identity profile] dmitry-vk.livejournal.com
В принципе, статически слинкованный hello-world на mono тоже имеет мощный рантайм, компилятор, объектную систему, немало встроенных библиотек (я не утверждаю, что оно на уровне common lisp'а, но все равно неплохо).
А разве в образ входят замапленные sо? Мне казалось, они выгружаются, делается сборка мусора и сохраняется память. А при загрузке образа библиотеки загружаются.

Date: 2009-08-11 09:35 am (UTC)
From: [identity profile] swizard.livejournal.com
Ну так ты прав:

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.

Date: 2009-08-11 09:28 am (UTC)
From: [identity profile] swizard.livejournal.com
Если этот пост на правах статьи, то надо бы поправить опечатку: s#12 мегабайтов#12 мегабайт#
(deleted comment)

Date: 2009-08-17 05:13 pm (UTC)
From: [identity profile] dmitry-vk.livejournal.com
Можно и его использовать. Но меня как автора cl-gtk2 больше интересует именно cl-gtk2:)
Я сам пока не знаю точного ответа на этот вопрос. Может быть, это NIH-синдром, а может быть, у меня получится лучше. Посмотрим.
А вообще clg выглядит очень приличным (в плане реализации) пакетом. Но он давно не обновлялся, нет документации, туториалов.
А еще что-то мне в нем сперва не понравилось, но уже не помню, что.

Date: 2009-10-17 08:24 pm (UTC)
From: [identity profile] axiger.livejournal.com
где взять и как установить cl-gtk2-gtk? пардон за ламерство, но очень хочется..

Date: 2009-10-17 08:46 pm (UTC)
From: [identity profile] dmitry-vk.livejournal.com
http://common-lisp.net/project/cl-gtk2/tutorial.html
Там расписано, как ставить. Если возникнут проблемыв - обращайтесь.

Date: 2009-10-17 09:50 pm (UTC)
From: [identity profile] axiger.livejournal.com
Распаковал в /usr/src/cl-gtk2, создал симлинки, запустил:
(asdf:operate 'asdf:load-op :cl-gtk2-gtk)

; loading system definition from
; /home/user/.sbcl/systems/cl-gtk2-pango.asd into #
[Error: Irreparable invalid markup ('<package "asdf0">') in entry. Owner must fix manually. Raw contents below.]

Распаковал в /usr/src/cl-gtk2, создал симлинки, запустил:
<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>

Date: 2009-10-18 07:30 am (UTC)
From: [identity profile] dmitry-vk.livejournal.com
текст после "отладчик выдал условие:" совпадает с текстом после "Распаковал в /usr/src/cl-gtk2, создал симлинки, запустил:"

Видимо, забыли скопировать вывод отладчика.

Date: 2009-10-18 09:02 am (UTC)
From: [identity profile] axiger.livejournal.com
и действительно..
component :CLOSER-MOP not found, required by
#
[Error: Irreparable invalid markup ('<system [...] {b2e0039}>') in entry. Owner must fix manually. Raw contents below.]

и действительно..
<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>

Date: 2009-10-18 05:30 pm (UTC)
From: [identity profile] dmitry-vk.livejournal.com
Надо closer-mop установить.
Устанавливается так же, как и cl-gtk2: надо разархивировать и сделать симлинк.
В архиве с http://libcl.com/ есть практически все необходимые библиотеки (можно просто сделать симлинки на все нужные библиотеки), кроме trivial-garbage, ее придется отдельно ставить.

Date: 2009-10-19 12:05 am (UTC)
From: [identity profile] axiger.livejournal.com
Not loaded:
CL-NUMLIB - (FFA)
FFA - COMPILE-FAILED
Остальные поставились успешно (SBCL)

Слил снапшот trivial-garbage запустил ./release.sh который обновил его до версии 1.0 (как я понимаю только в этой папке) Правильно ли я понимаю, что я должен куда-то положить asd-файл и lisp-файл и что то прописать в конфигурации? Бросьте в меня ссылкой о том как уставливаются библиотеки лиспа, пока я просто следовал инструкциям, но хотелось бы понимания процесса...
From: (Anonymous)
Антикварный магазин «Янус» (http://yanusantik.ru/)предлагает, покупает антиквариат, производит независимую оценку предметов старины и антиквариата в Санкт-Петербурге.

Profile

dmitry_vk

April 2023

S M T W T F S
      1
234567 8
9101112131415
16171819202122
23242526272829
30      

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Apr. 6th, 2026 12:29 am
Powered by Dreamwidth Studios