Создание EXE-файлов.

HomeLisp (по крайней мере в предлагаемой версии) не способен компилировать Лисп-код в команды микропроцессора. Поэтому для создания автономных исполняемых файлов применяется технология, которую можно условно назвать псевдо-EXE.

Суть этой технологии состоит в следующем.

В поставку HomeLisp входит исполняемый файл-заглушка, в составе которого содержится ядро HomeLisp. Объем этого файла составляет около двухсот килобайт. При создании EXE-файла сначала делается копия файла-заглушки с именем, которое задает разработчик. Все дальнейшие манипуляции выполняются с созданной копией, которую далее будем называть целевым файлом.

Предположим, что разработчик написал и загрузил ряд функций, которые в совокупности составляют приложение. При создании EXE-файла, все выбранные разработчиком функции приписываются в "хвост" целевого файла. Кроме этих функций, в "хвост" целевого файла записывается т.н. стартовое S-выражение, а также задаваемые пользователем настройки среды HomeLisp (размеры внутренних стеков, количество динамических объектов и т.д.)

При запуске целевого файла управление получает сначала программа инициализации ядра HomeLisp, которая затем считывает из файла все сохраненные пользователем функции и загружает их в среду Лиспа. После этого управление получает блок стартового S-выражения, и предусмотренная разработчиком программа начинает исполняться.

Рассмотрим процесс создания EXE-файла на простом практическом примере: создадим диалоговую программу, которая будет вычислять наибольший общий делитель двух целых чисел с помощью алгоритма Евклида. Пример этот не так уж бесполезен: поскольку HomeLisp работает с целыми неограниченной разрядности, написание аналогичного примера в любой расхожей среде программирования (VB,C++,Delphi) потребовала бы заметных усилий...

Функция *GCD имеет следующий вид:


(defun *gcd (x y) (cond ((eq x y)                x)
                        ((greaterp y x) (*gcd y x))
                        ((eq (remainder x y) 0)  y)
                        (T              (*gcd y (remainder x y)))))

==> *gcd

(*gcd 655 72) 

==> 1

(*gcd 655 75) 

==> 5

(*gcd 65536 4096) 

==> 4096

Примеры вызова показывают работоспособность функции. Далее необходимо создать диалоговую оболочку для вызова этой функции. В простейшем случае эта оболочка может быть организована следующим образом:

Запрос (посредством функции ASK) первого операнда;

Запрос второго операнда;

Вычисление результата;

Отображение результата с помощью функции SAY;

Напишем определяющее выражение для функции GCD-1, в котором будет реализован приведенный выше алгоритм:


(defun GCD-1 nil

    (prog (a1 a2)

          (setq a1 (str2fix (Ask "Введите первый операнд")))

          (setq a2 (str2fix (Ask "Введите второй операнд")))

          (TRY

              (say (fix2str (*gcd a1 a2)))

           EXCEPT

              (say "Ошибка!")

          )

     )

)

Следует обратить внимание на то, что результат вызова ASK имеет тип STRING, а функция *GCD требует операндов типа FIXED. Поэтому, прежде, чем присваивать введенные пользователем значения локальным переменным, a1 и a2, эти значения следует преобразовать в тип FIXED (для чего служат вызовы функции STR2FIX).

Далее, поскольку готовится диалоговая программа, предназначенная для конечного пользователя, следует предусмотреть возможные ошибки. Именно поэтому вычисление наибольшего общего делителя сделано критическим участком кода с помощью вызова функции TRY Если в процессе вычисления произойдет сбой, программа не "упадет", а будет выдано сообщение "Ошибка!".

Если выполнить функцию GCD-1 из среды разработки, то сначала будет выведено окно запроса первого операнда:


Пользователь вводит первый операнд и нажимает кнопку OK; появляется окно запроса второго операнда:


Пользователь вводит второй операнд, нажимает кнопку OK и получает результат:


Теперь построим на основе отлаженной функции GCD-1 исполняемый файл. Для этого, находясь в среде разработки выберем пункт главного меню EXE-файл или нажмем кнопку на панели инструментов со следующей иконкой:


В обоих случаях будет открыто окно построения EXE-файла:


Чтобы создать EXE-файл, необходимо выполнить следующие действия:

Задать имя EXE-файла. Имя (и путь) будущего EXE-файла задаются в комбинированном поле ввода в левом верхнем углу формы создания EXE-файла. Имя и путь можно задать вручную, а можно нажать кнопку "..." и выбрать директорию и имя с помощью стандартного диалога Сохранить как. По умолчанию создается файл noname.exe в текущей директории.

В области ввода "Стартовое S-выражение" необходимо задать код, с которого начнется выполнение будущего приложения. Стартовое S-выражение может быть вызовом функции. Если требуется задать стартовое S-выражение, состоящее из нескольких вызовов, то эти вызовы следует "заключить" в PROG-конструкцию.

Перечисленные действия являются минимально-необходимыми. Кроме того, дополнительно пользователь может выполнить следующее:

В списке, озаглавленном "Функции, константы, переменные", сбросить флажки, стоящие против объектов, наличие которых для выполнения приложения не является необходимым. Это мероприятие несколько повысит скорость выполнения приложения;

Изменить значения настроечных параметров в списке, расположенном в правом верхнем углу. Последовательность действий при замене значений параметров будет описана ниже;

Сбросить флажок "Отображать диспетчерскую форму при старте". Если этот флажок оставить поднятым, то при запуске приложения будет отображаться специальная диспетчерская форма. Этой форме можно задать заголовок (в соответствующем поле ввода). Использование диспетчерской формы удобно при отладке.

Установить или сбросить флажок "Сообщать о завершении". Если этот флажок установлен, то перед завершением приложения будет выдано сообщение.

Окно создания EXE-файла с минимально-необходимым набором параметров может иметь вид:


Далее необходимо нажать кнопку с изображением зеленой галочки. Если заказанный EXE-файл уже существует, HomeLisp предолжит подтвердить перезапись файла или задать новое имя. После чего, при успешном завершении, выдается сообщение:


Можно убедиться, что в текущей директории пявился EXE-файл gcd-1.exe, при запуске которого происходит описанный выше диалог с пользователем.

Если же при создании EXE-файла поднять флажок "Отображать диспетчерскую форму при старте" и задать заголовок формы:

то при исполнении полученного EXE-файла, в правом верхнем углу экрана отображается диспетчерская форма следующего вида:

При выборе пункта меню "Пуск" (или нажатии соответствующей кнопки, расположенной ниже), начинает выполняться приложение. После завершения приложения, диспетчерская форма остается на экране, что позволяет запускать приложение многократно. Для окончательного завершения приложения нужно выбрать пункт меню "Выход" (или нажать кнопку, расположенную ниже).

Если в процессе выполнения приложения с диспетчерской формой возникает ошибка, то информация об ошибке выводится в область вывода, которая в нормальном состоянии скрыта. Например, если сделать стартовым S-выражением вызов несуществующей функции (unknown) , то при выполнении возникнет картинка, приведенная ниже:

Использование диспетчерской формы позволяет принудительно остановить выполнение приложения. Для этого служит пункт меню "Стоп" и кнопка, расположенная ниже (и пункт меню, и кнопка активны только в процессе выполнения приложения). Если в качестве стартового выражения задать, например, вычисление факториала достаточно большого числа, то можно успеть увидеть пункт меню и кнопку останова:

Если в процессе выполнения нажать "Стоп", то выполнение программы прерывается:

Пункт главного меню "Окна" диспетчерской формы (и соответствующая командная кнопка) будут активны в случае, когда в процессе выполнения приложения создано хотя бы одно графическое окно. При выборе пункта "Окна" (или нажатии на кнопку, расположенную ниже), вызывается менеджер графических окон.

Режим построения EXE-файлов с диспетчерской формой предназначен для облегчения отладки приложений. Если не использовать диспетчерскую форму, то управлять графическими окнами должен будет сам разработчик, а в случае "зависания" приложения для его снятия придется использовать диспетчер задач ОС.

При необходимости изменить значение какого-либо настроечного параметра (размер стека, число лексем и т.д.) необходимо щелкнуть мышью по нужно строке списка "Параметры". Числовое значение соответствующего параметра будет перенесено в область ввода, расположенную под списком:

Далее следует исправить значение параметра и нажать кнопку с изображением стрелки, направленной вверх. Значение параметра будет исправлено:

В качестве второго примера создания EXE-файла, рассмотрим прежнюю задачу (вычисление НОД), но с оконным графическим интерфесом. Для этого создадим диалог (как описано здесь), на форме которого разместим три метки, три поля ввода и две кнопки. Назначим одной из кнопок процедуру-обработчик, в котором вычислим НОД чисел, введенных в первое и второе поля ввода и занесение вычисленного НОД в поле результата. Обработчик нажатия второй кнопки будет закрывать и уничтожать диалог. Форма диалога с элементами управления может иметь вид, приведенный ниже:

Программный код, сгенерированный дизайнером, может иметь такой вид:


   (prog nil

      (dlgCreate '_Dlg_ 419 233 "Наибольший общий делитель:")

      (dlgAddControl '_Dlg_ '_LBL_1 _LABEL 14 15 100 21 
                     '("Tahoma" 14,25 1 0 0) "Первое:" 0 &H80000012 &H8000000F)

      (dlgAddControl '_Dlg_ '_TXT_1 _TEXT 155 13 248 31 
                     '("Tahoma" 14 1 0 0) "" 0 &H80000008 &H80000005)

      (dlgAddControl '_Dlg_ '_LBL_2 _LABEL 12 59 133 26 
                     '("Tahoma" 14,25 1 0 0) "Второе:" 0 &H80000012 &H8000000F)

      (dlgAddControl '_Dlg_ '_TXT_2 _TEXT 156 56 247 31 
                     '("Tahoma" 14 1 0 0) "" 0 &H80000008 &H80000005)

      (dlgAddControl '_Dlg_ '_LBL_3 _LABEL 13 101 127 27 
                     '("Tahoma" 14,25 1 0 0) "Н.О.Д." 0 &HFF &H8000000F)

      (dlgAddControl '_Dlg_ '_TXT_3 _TEXT 157 98 247 31 
                     '("Tahoma" 14 1 0 0) "" 0 &HFF &H80000005)

      (dlgAddControl '_Dlg_ '_BUT_1 _BUTTON 24 148 180 50 
                     '("Tahoma" 8,25 1 0 0) "Вычислить")
      (dlgPutPicture '_BUT_1 7)

      (dlgAddControl '_Dlg_ '_BUT_2 _BUTTON 213 149 180 50 
                     '("Tahoma" 8,25 1 0 0) "Закрыть")
      (dlgPutPicture '_BUT_2 36)

      //
      // Обработчик события CLICK для кнопки _BUT_2
      //

      (defun _BUT_2_Click  Nil 

          (prog Nil

               (dlgHide '_DLG_) 
  
               (dlgDestroy '_DLG_)
    
               (gc)

          )

      )

      //
      //   Назначение процедуры-события _BUT_2_Click  контролу _BUT_2
      //

      (dlgSetEvent '_BUT_2 '_BUT_2_Click )
      //
      // Обработчик события CLICK для кнопки _BUT_1
      //

      (defun _BUT_1_Click  Nil 

            (prog (a1 a2)
                 
               (setq a1 (str2fix  (dlgGetText '_TXT_1)))
                  
               (setq a2 (str2fix  (dlgGetText '_TXT_2)))

               (TRY
               
                    (dlgPutText '_TXT_3 (fix2str (*gcd a1 a2)))
                    
                EXCEPT

                    (dlgPutText '_TXT_3 "Ошибка!!!")      

               )
            )

      )

      //
      //   Назначение процедуры-события _BUT_1_Click  контролу _BUT_1
      //

      (dlgSetEvent '_BUT_1 '_BUT_1_Click )

      //
      //   Отображение диалога _Dlg_
      //

      (dlgShow '_Dlg_)
   )

Для того, чтобы построить EXE-файл, нужно загрузть в среду Лиспа функцию *GCD и написать стартовое выражение для отображения диалога. Проще всего "погрузить" приведенную выше PROG-конструкцию в функцию MAIN без параметров. Тогда стартовым S-выражением для запуска EXE-файла будет вызов (main). Итак, если загрузить в среду Лиспа следующие функции:


//
//  Поиск наибольшего общего делителя
//

(defun *gcd (x y) (cond ((eq x y)                x)
                        ((greaterp y x) (*gcd y x))
                        ((eq (remainder x y) 0)  y)
                        (T              (*gcd y (remainder x y)))))

//
//  Программа отображения диалога _Dlg_
//


(defun main nil

   (prog nil

      (dlgCreate '_Dlg_ 419 233 "Наибольший общий делитель:")

      (dlgAddControl '_Dlg_ '_LBL_1 _LABEL 14 15 100 21 
                     '("Tahoma" 14,25 1 0 0) "Первое:" 0 &H80000012 &H8000000F)

      (dlgAddControl '_Dlg_ '_TXT_1 _TEXT 155 13 248 31 
                     '("Tahoma" 14 1 0 0) "" 0 &H80000008 &H80000005)

      (dlgAddControl '_Dlg_ '_LBL_2 _LABEL 12 59 133 26 
                     '("Tahoma" 14,25 1 0 0) "Второе:" 0 &H80000012 &H8000000F)

      (dlgAddControl '_Dlg_ '_TXT_2 _TEXT 156 56 247 31 
                     '("Tahoma" 14 1 0 0) "" 0 &H80000008 &H80000005)

      (dlgAddControl '_Dlg_ '_LBL_3 _LABEL 13 101 127 27 
                     '("Tahoma" 14,25 1 0 0) "Н.О.Д." 0 &HFF &H8000000F)

      (dlgAddControl '_Dlg_ '_TXT_3 _TEXT 157 98 247 31 
                     '("Tahoma" 14 1 0 0) "" 0 &HFF &H80000005)

      (dlgAddControl '_Dlg_ '_BUT_1 _BUTTON 24 148 180 50 
                     '("Tahoma" 8,25 1 0 0) "Вычислить")
      (dlgPutPicture '_BUT_1 7)

      (dlgAddControl '_Dlg_ '_BUT_2 _BUTTON 213 149 180 50 
                     '("Tahoma" 8,25 1 0 0) "Закрыть")
      (dlgPutPicture '_BUT_2 36)

      //
      // Обработчик события CLICK для кнопки _BUT_2
      //

      (defun _BUT_2_Click  Nil 

          (prog Nil

               (dlgHide '_DLG_) 
  
               (dlgDestroy '_DLG_)
    
               (gc)

          )

      )

      //
      //   Назначение процедуры-события _BUT_2_Click  контролу _BUT_2
      //

      (dlgSetEvent '_BUT_2 '_BUT_2_Click )
      //
      // Обработчик события CLICK для кнопки _BUT_1
      //

      (defun _BUT_1_Click  Nil 

            (prog (a1 a2)
                 
               (setq a1 (str2fix  (dlgGetText '_TXT_1)))
                  
               (setq a2 (str2fix  (dlgGetText '_TXT_2)))

               (TRY
               
                    (dlgPutText '_TXT_3 (fix2str (*gcd a1 a2)))
                    
                EXCEPT

                    (dlgPutText '_TXT_3 "Ошибка!!!")      

               )
            )

      )

      //
      //   Назначение процедуры-события _BUT_1_Click  контролу _BUT_1
      //

      (dlgSetEvent '_BUT_1 '_BUT_1_Click )

      //
      //   Отображение диалога _Dlg_
      //

      (dlgShow '_Dlg_)
   )

)

а затем построить EXE-файл gcd-2.exe, задав следующие параметры:

то получится вполне работоспособный EXE-файл. Его можно запустить, и убедиться, что НОД считается верно:

Сайт создан в системе uCoz
Сайт создан в системе uCoz