Процедура представляет собой логически завершенный программный код, который может содержать в себе все элементы программирования, такие как: объявление переменных, временных таблиц, объекты интерфейса, запросы к базе данных и т.д.
В ABL выделяют два вида процедур: внутренние и внешние.
Внешние процедуры представляют собой отдельные текстовые файлы с расширением .p. Именем внешней процедуры является имя ее файла. Таким образом, создание внешней процедуры сводится к созданию ее файла.
Внутренние процедуры описываются внутри внешней и имеют уникальные имена, указываемые в заголовках их описания.
Внешние и внутренние процедуры могут быть как с параметрами, так и без них.
ПРОЦЕДУРЫ БЕЗ ПАРАМЕТРОВ
Внутренняя процедура без параметров описывается следующим образом:
PROCEDURE proc-name [PRIVATE]: ... (тело процедуры) END [PROCEDURE].
Вызов любой процедуры без параметров осуществляется следующим образом:
RUN proc-name.
где, proc-name - имя вызываемой процедуры, для внешней процедуры это соответственно имя ее файла.
Поиск файла при вызове внешней процедуры осуществляется по каталогам перечисленным в PROPATH. Если процедура, с одним и тем же именем, располагается в нескольких каталогах перечисленных в PROPATH, то вызвана будет процедура из каталога объявленного в PROPATH ранее других.
Если файл находится в каталоге не перечисленном в системной переменной PROPATH, то в операторе вызова процедуры в качестве proc-name необходимо указать полный путь к ее файлу. В том случае, если файл находится в одном из подкаталогов каталога объявленного в PROPATH, то достаточно указать только недостающую часть пути.
Поиск файла процедуры в каталоге осуществляется в следующей последовательности: proc-name.r (файл скомпилированной процедуры proc-name.p), если скомпилированный файл не найден в каталоге, то AVM осуществляет поиск исходного файла proc-name.p.
Создадим две внешние процедуры hello-word.p и test-proc1.p, в которой осуществим вызов hello-word.p.
hello-word.p:
DISPLAY "Hello, Word".
test-proc1.p:
RUN hello-word.p.
Создадим внешнюю процедуру test-proc2.p, в которой опишем и вызовем внутреннюю процедуру hello-word:
test-proc2.p:
RUN hello-word. PROCEDURE hello-word: DISPLAY "Hello, Word". END PROCEDURE.
Если вызываемая внутренняя процедура hello-word не будет описана в test-proc2.p, то AVM попытается найти соответствующую внешнюю процедуру с именем вызываемой внутренней (hello-word.r, hello-word.p).
ПРОЦЕДУРЫ С ПАРАМЕТРАМИ
Параметры процедуры - это значения, записываемые в специальные локальные переменные процедуры при ее вызове, посредством которых осуществляется обмен данными между ней и вызывающей ее процедурой или функцией. Таким образом, параметры могут использоваться в теле процедуры как обычные переменные.
Объявление параметров осуществляется в теле процедуры, как правило в самом ее начале, и очень схоже с объявлением переменных:
temp-table-handle - указатель временной таблицы, тип HANDLE;
dataset - идентификатор ProDataSet;
dataset-handle - указатель ProDataSet, тип HANDLE.
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
Помимо выходных параметров, процедуры дополнительно могут возвращать текстовое значение. Определение данного значения осуществляется с помощью оператора RETURN в теле процедуры.
return-value - возвращаемое значение, тип CHARACTER.
Получение возвращаемого процедурой значения в вызывающем ее блоке осуществляется с помощью функции RETURN-VALUE.
Одним из предназначений данной возможности является возврат ошибки, возникшей в теле процедуры. В этом случае оператор RETURN применяется с опцией ERROR.
Задание значений OUTPUT и INPUT-OUTPUT параметров процедуры должны осуществляться до оператора RETURN ERROR.
ОБЛАСТЬ ВИДИМОСТИ КОНТЕНТА ПРОЦЕДУР
Весь контент внешней процедуры (переменные, временные таблицы и т.д.) доступен всем содержащимся в ней внутренним процедурам и функций.
Внутренние процедуры не могут содержать в себе объявления временных таблиц. Если во внутренней процедуре предполагается работа с временной таблицей, то она должна быть объявлена во внешней процедуре.
Контент внутренней процедуры доступен исключительно ей самой и недоступен вне нее, ни внешней процедуре, ни другим внутренним процедурам и функциям. При этом, например, во внутренней процедуре допускается объявление одноименных переменных с уже объявленными во внешней процедуре.
DEF VAR vText AS CHAR NO-UNDO. vText = "Hello, Word". RUN hello-word. DISPL vText.
PROCEDURE hello-word: DEF VAR vText AS CHAR NO-UNDO. vText = "Привет, Мир". DISPLAY vText. END.
Внешние процедуры могут обращаться к одним и тем же переменным находящимся в памяти. Для этого переменная в процедуре, где она создается должна быть объявлена как NEW [GLOBAL] SHARED, а в процедурах, которым она должна быть доступна как SHARED.
По завершению процедуры весь ее контент удаляется.
ИНКЛЮД-ФАЙЛЫ
Допустим, у нас есть несколько внешних процедур использующих одну и туже часть программного кода. Это могут быть одни и те же функции, внутренние процедуры, запросы к базе данных и т.д. Очень неудобно копировать повторяющуюся часть кода в каждую процедуру, а при необходимости внесения изменений в эту часть, производить их везде, где она используется.
Для этих целей в ABL используются так называемые инклюд-файлы. Инклюд-файлы представляют собой текстовые файлы, содержащие программный код, который можно вставлять в любом месте посредством указания ссылки на данный файл. Ссылка на инклюд-файл представляет собой полное имя инклюд-файла заключенное в фигурные скобки.
Расширение .i является общепринятым для инклюд-файлов, но допускаются указания других расширений с целью группировки инклюд-файлов по типу. Примеры таких расширений представлены в таблице.
Расширение
Назначение
.def
объявление переменных, временных таблиц и т.д.
.pro
описание внутренних процедур
.fun
описание функций
Использование инклюд-файлов подобно «матрешке», т.е. один инклюд-файл может включать в себя другой инклюд-файл и т.д.
Использование инклюд-файла в процедуре при компиляции увеличивает размер r-файла включая в него сам инклюд-файл. При этом r-файл уже не обращается к исходному инклюд-файлу. В таком случае, при внесении изменений в инклюд-файл необходимо перекомпилировать все процедуры использующих его.
АРГУМЕНТЫ
Процедурам и инклюд-файлам могут быть переданы аргументы. Существует два способа передачи аргументов: по имени аргумента и по его порядковому номеру.
RUN my-proc1.p "loan" "open-date".
my-proc1.p:
FOR EACH {1} WHERE {2} >= 01.01.2011 AND {2} < 01.01.2012: DISPLAY loan.cust-corp. END.
При вызове my-proc1.p ей передаются два аргумента 1 - "loan" и 2 - "open-date". В соответствии со своими порядковыми номерами значения аргументов будут подставлены в код процедуры. Таким образом, my-proc.p будет иметь следующий вид при выполнении:
FOR EACH loan WHERE open-date >= 01.01.2011 AND open-date < 01.01.2012: DISPLAY loan.cust-corp. END.
my-proc2.p:
DEF VAR a AS DECIMAL NO-UNDO. DEF VAR b AS DECIMAL NO-UNDO. ASSIGN a = 5.5 b = 3.0. {my-include.i &par1 = a &par2 = b }
my-iclude.i:
DEF VAR c AS DECIMAL NO-UNDO. c = {&par1} * {&par2}. DISPLAY "Результат = " с.
Имя аргумента должно начинаться с символа &. В процедуре my-proc2.p мы подключили инклюд-файл my-include.i с передаваемыми ему аргументами &par1 и &par2, которым присваиваем значения переменных a и b соответственно.
ПРОЦЕДУРНЫЙ УКАЗАТЕЛЬ
Каждой вызываемой процедуре сопоставляется создаваемый автоматически и добавляемый в стек процедур процедурный указатель. Стек представляет собой набор процедурных указателей активных в клиентской сессии процедур. Процедурный указатель в свою очередь представляет собой значение типа HANDLE.
В ABL существует системная переменная THIS-PROCEDURE типа HANDLE, значением которой является указатель процедуры в которой она используется.
Процедурный указатель является ссылкой на процедуру как на объект ООП, обладающий набором атрибутов и методов.
procedure-handle [:attribute | :method]
Атрибуты:
Атрибут
Тип данных
Доступ
Описание
FILE-NAME
CHARACTER
чтение/запись
файл процедуры
PERSISTENT
LOGICAL
чтение
логическое значение TRUE для персистетной процедуры
HANDLE
HANDLE
чтение
процедурный указатель
NAME
CHARACTER
чтение/запись
имя процедуры
TYPE
CHARACTER
чтение
тип указателя, значение "PROCEDURE"
INTERNAL-ENTRIES
CHARACTER
чтение
список внутренних процедур и функций с разделителем - запятая
Методы:
Метод
Описание
ADD-SUPER-PROCEDURE()
GET-SIGNATURE()
возвращает сигнатуру процедуры или функции
REMOVE-SUPER-PROCEDURE()
SET-CALLBACK-PROCEDURE()
GET-SIGNATURE(int-proc-name) - возвращает сигнатуру, указываемого в качестве входного параметра метода, процедуры или функции. Сигнатура представляет собой описание процедуры (функции) и всех ее параметров: идентификатор параметра, тип параметра и тип данных параметра.
Возвращаемая методом сигнатура представляет строку следующего вида:
type, return-type, [mode name p-type [, mode name p-type] ... ]
type - тип процедуры, может принимать следующие значения:
PROCEDURE - внутренняя процедура;
FUNCTION - внутренняя функция;
EXTERNS - внешняя функция;
DLL-ENTRY - точка входа DLL;
MAIN - основная процедура.
return-type - тип данных возвращаемого функцией значения;
mode - тип параметра (INPUT, OUTPUT, INPUT-OUTPUT и т.д.);
name - идентификатор параметра;
p-type - тип данных параметра.
PERSISTENT ПРОЦЕДУРЫ
Рассматриваемый до этого способ вызова внешней процедуры являлся NON-PERSISTENT. Создаваемый при NON-PERSISTENT вызове процедурный указатель существует исключительно до конца выполнения процедуры и по завершению ее удаляется из стека.
Допустим у нас имеется две внешние процедуры test1.p, в которой объявлена внутренняя процедура hello-word и test2.p, в которой нам необходимо осуществить вызов hello-word из test1.p.
Такой вызов внутренней процедуры из сторонней внешней процедуры имеет следующий синтаксис:
RUN intern-proc IN h_extern [(parametrs)].
intern-proc - вызываемая внутренняя процедура;
h_extern - указатель внешней процедуры содержащей intern-proc;
parametrs - параметры вызываемой внутренней процедуры.
Осуществить такой вызов возможно только в том случае, если в момент вызова в test2.p процедуры hello-word в стеке процедур имеется указатель на test1.p. Таким образом, нам необходимо, чтобы test1.p была вызвана ранее чем test2.p и продолжала быть активной, т.е. ожидала завершения test2.p.
Следовательно решение данной задачи будет выглядеть следующим образом:
test1.p:
RUN test2.p (THIS-PROCEDURE). PROCEDURE hello-word: DISPLAY "Hello, Word". END.
test2.p:
DEF INPUT PARAM h_proc AS HANDLE NO-UNDO. RUN hello-word IN h_proc.
Первой вызываемой процедурой является test1.p, в которой уже осуществляется вызов test2.p с передачей ей в качестве входного параметра процедурного указателя test1.p. Процедура test1.p ожидает завершения test2.p и тем самым является активной на протяжении выполнения test2.p.
В отличие от NON-PERSISTENT, процедурный указатель на PERSISTENT процедуру существует в стеке до завершения клиентской сессии, а не самой процедуры, либо до тех пор, пока он не будет принудительно удален из стека.
RUN extern-proc [ PERSISTENT [SET h_extern] [(parametrs)].
extern-proc - имя внешней процедуры;
h_extern - процедурный указатель, тип HANDLE;
parametrs - параметры процедуры.
Таким образом, решение нашей задачи может быть реализовано несколько иначе:
test1.p:
PROCEDURE hello-word: DISPLAY "Hello, Word". END.
test2.p:
DEF VAR h_proc AS HANDLE NO-UNDO.
RUN test1.p PERSISTENT SET h_proc. RUN hello-word IN h_proc.
Теперь первой вызываемой процедурой является test2.p и уже в ней, осуществляется вызов test1.p, указатель которой сохраняется в переменной h_proc и продолжает существовать до конца клиентской сессии.
Внутренняя процедура объявленная как PRIVATE не может быть вызвана из сторонних внешних процедур. Ее вызов возможен только из процедуры в которой она объявлена.
УДАЛЕНИЕ PERSISTENT ПРОЦЕДУР
Персистентная процедура загружается в память и сохраняется в ней до завершения клиентской сессии. За время клиентской сессии может быть осуществлено значительное количество вызовов персистентных процедур, что будет приводить к росту занимаемой клиентским приложением памяти и, как следствие, - снижению производительности.
Для недопущения роста занимаемой памяти, по мере возможности, необходимо удалять ненужные более персистентные процедуры.
Удаление персистентной процедуры осуществляется следующим образом:
DELETE PROCEDURE h_extern [NO-ERROR].
При удалении PERSISTENT процедуры могут возникнуть ошибки, например, когда удаляемый указатель h_extern отсутствует в стеке. Для отключения реакции AVM на возникающую ошибку удаления можно использовать NO-ERROR.
Перед удалением рекомендуется предварительно проверять существование процедурного указателя.
IF VALID-HANDLE(h_extern) THEN DELETE PROCEDURE h_extern.
ШИФРОВАНИЕ ВНЕШНИХ ПРОЦЕДУР (УТИЛИТА XCODE)
Для защиты исходного кода в PROGRESS предусмотрено его шифрование, для осуществление которого применяется утилита XCODE. Найти ее можно в каталоге $DLC/bin.
Необходимо учитывать, что утилиты для дешифрования не предусмотрено. Даже зная ключ шифрования, получить исходный (дешифрованный) файл из зашифрованного не представляется возможным.
xcode [ -k key ] -d directory [files] [ - ]
-k key - ключ шифрования. Максимально допустимая длина ключа составляет 8 символов. Если ключа не задан, то для шифрования применяется так называемый default-key (ключ по умолчанию).
-d directory - директория, в которую будут помещаться зашифрованные файлы. Данная директория должна отличаться от директории с исходными файлами, так как шифрование не изменяет имя файла, а только его содержимое и тем самым исходные файлы будут перезаписаны.
files - относительный путь файлов, которые необходимо зашифровать.
ФУНКЦИИ
Функция, так же как и процедура, является логически завершенным программным кодом, выполняющим обработку данных. В отличии от процедуры, функция не может содержать в себе объектов интерфейса. Результатом действия функции, как правило, является возвращаемое ей значение заданного типа данных, записываемое в ее имя.
Описание функции осуществляется во внешней процедуре.
FUNCTION function-name [RETURNS] return-type [PRIVATE] [(parameter [, parameter] ... )]:
... (тело функции) [RETURN result]. END [FUNCTION].
function-name – имя функции по которому она в дальнейшем будет вызываться;
return-type – тип данных возвращаемого функцией значения;
parametr – параметр функции;
result – возвращаемое функцией значение.
Основным требованием является то, что функция должна быть объявлена до момента обращения к ней. Как правило, объявление функции осуществляется в начале файла внешней процедуры.
Вызов функции осуществляется по ее имени с передачей ей всех параметров:
function-name(param,...).
В отличие от других языков программирования в ABL нет тригонометрических функции. Данные функции (SIN, COS, TG, CTG) можно реализовать самостоятельно, воспользовавшись разложением в ряды Тейлора.
ФУНКЦИЯ SIN(x):
FUNCTION SIN RETURNS DEC (x AS DEC): DEF VAR xRad AS DEC NO-UNDO. /* Радианное значение угла */ DEF VAR e AS DEC NO-UNDO. /* Точность */ DEF VAR fact AS DEC NO-UNDO. /* Значение факториала */ DEF VAR iTel AS DEC NO-UNDO. /* Значение i-го элемента ряда */ DEF VAR iRes AS DEC NO-UNDO. /* i-результат */ DEF VAR n AS INT NO-UNDO.
ASSIGN n = 1 fact = 1 xRad = x * 3.14 / 180 iTel = 1 e = 0.0000001 . DO WHILE ABS(iTel) > e: iTel = (EXP(- 1, (n - 1)) * EXP(xRad, (2 * n - 1))) / fact. n = n + 1. fact = fact * (2 * n - 1) * ( 2 * n - 2). iRes = iRes + iTel. END. RETURN iRes. END FUNCTION.
ФУНКЦИЯ cos(x):
FUNCTION COS RETURNS DEC (x AS DEC): DEF VAR xRad AS DEC NO-UNDO. /* Радианное значение угла */ DEF VAR e AS DEC NO-UNDO. /* Точность */ DEF VAR fact AS DEC NO-UNDO. /* Значение факториала */ DEF VAR iTel AS DEC NO-UNDO. /* Значение i-го элемента ряда */ DEF VAR iRes AS DEC NO-UNDO. /* i-результат */ DEF VAR n AS INT NO-UNDO.
ASSIGN n = 0 fact = 1 xRad = x * 3.14 / 180 iTel = 1 e = 0.0000001 . DO WHILE ABS(iTel) > e: iTel = EXP(- 1, n) * EXP(xRad, (2 * n)) / fact. n = n + 1. fact = fact * (2 * n - 1) * (2 * n). iRes = iRes + iTel. END. RETURN iRes. END FUNCTION.
Разместим данные функции в отдельном инклюд-файле trigonometry.fun. Для того чтобы воспользоваться данными функциями нам необходимо будет подключать данный инклюд-файл в процедуре, в которой мы будем их использовать.
DEF VAR x AS DEC NO-UNDO. {trigonometry.fun} x = sin(30). DISPL x.
Важно, что подключение инклюд-файла осуществляется до использования функций в коде процедуры. Если бы мы подключили инклюд-файл с функциями в конце процедуры, то встретив применение функции SIN() в коде, AVM еще бы ничего не знала о ней, что привело бы к ошибке.
Обойти данное требование можно разместив в начале файла процедуры объявление прототипа функции, тогда ее реализация может быть размещена в конце процедуры.
Объявление прототипа функции осуществляется следующим образом:
FUNCTION function-name [RETURNS] return-type [(parameter [, parameter ... )] { FORWARD | [MAP [TO] actual-name] IN proc-handle | IN SUPER }.
Применим объявление прототипа функции SIN():
DEF VAR x AS DEC NO-UNDO. FUNCTION SIN RETURNS DEC (x AS DEC) FORWARD. x = SIN(30). DISPL x. {trigonometry.fun}
Опция FORWARD указывает на то, что реализация данной функции находится в этом же файле процедуры. Объявление прототипа функции позволило нам осуществить подключение инклюд-файл с ее реализацией в конце процедуры, а не в ее начале.
Объявление прототипа функции дает AVM необходимую информацию о ее сигнатуре. Тем самым, встретив функцию в коде AVM уже знает о ней.
Объявление прототипа функции также позволяет использовать функции из сторонних внешних процедур. Для этого в опции IN необходимо указать handle процедуры, содержащей реализацию функции, которую мы собираемся использовать.
Создадим процедуру pp-lib.p, в которой разместим реализацию наших тригонометрических функций:
pp-lib.p:
{trigonometry.fun}
При объявлении прототипа функций в сторонних процедурах, нам необходимо указать handle процедуры pp-lib.p, следовательно она должна быть активной. Для этого осуществим персистентный вызов процедуры pp-lib.p.
DEF VAR h_lib AS HANDLE NO-UNDO. RUN lib-fun.p PERSISTENT SET h_lib. FUNCTION COS RETURNS DEC (x AS DEC) IN h_lib.
DEF VAR x AS DEC NO-UNDO. x = COS(60). DISPL x.
DELETE PROCEDURE h_lib.
Что если подключаемая персистентная процедура содержит функцию с именем уже использующимся в процедуре, в которой она подключается?
При объявлении прототипа функции мы можем указать альтернативное имя функции, а в опции MAP [TO] актуальное имя функции в персистентной процедуре.
DEF VAR h_lib AS HANDLE NO-UNDO. RUN lib-fun.p PERSISTENT SET h_lib. FUNCTION FCOS RETURNS DEC (x AS DEC) MAP TO COS IN h_lib.
DEF VAR x AS DEC NO-UNDO. x = FCOS(60). DISPL x.
DELETE PROCEDURE h_lib.
Таким образом, мы можем использовать не только внутренние процедуры персистеных процедур, но и содержащиеся в них функции.
В случае если тип параметра не указан явно, то по умолчанию он является INPUT параметром.