Object RSL ¾ объектно-ориентированная версия
языка RSL
С макроязыком RSL наши постоянные читатели уже знакомы по предыдущим выпускам «RS-Club». В частности, этой теме были посвящены статьи С. Кубрина «Создание отчетов с переменным числом колонок», «Техника замены процедур» (см. «RS-Club», 1996, Нулевой выпуск, с. 28¾30); «Применение прокрутки по файлам в диалоговых из языка RSL», «Изменения и дополнения в RSL» (см. «RS-Club», 1996, № 1, с. 50¾55); «Способы решения проблем в языке RSL» (см. «RS-Club», 1996, № 2, с. 42¾43), а также другие материалы.
Настоящая статья подготовлена в связи с выходом в свет объектно-ориентированной версии RSL. Характеризуя усовершенствованный язык, автор рассматривает «невизуальные» классы, вспомогательные процедуры для работы с объектами, а также другие особенности новой версии.
Сергей Кубрин
ведущий специалист Отдела исследований
компании «R-Style Software Lab.»
Язык RSL уже давно стал неотъемлемой частью большинства программных продуктов, разработанных компанией «R-Style Software Lab.». Совершенствуясь от версии к версии, RSL из простейшего средства создания нестандартных отчетов превратился в мощный инструмент, существенно расширяющий функциональные возможности таких систем, как RS-Bank, RS-Balance и др. Уходящий год был отмечен очередным этапом эволюции языка RSL: он стал объектно-ориентированным, что отразилось в его новом названии ¾ Object RSL.
Разработка объектно-ориентированного языка ¾ не просто дань моде. Перед программистами «R-Style Software Lab.» стояла задача поддержать новый продукт компании ¾ среду для визуальной разработки форм и отчетов (ее рабочее название Visual RSL). Известно, что при программировании визуальной среды очень удобен объектно-ориентированный подход: такие ее специфические черты, как отчеты, формы, кнопки, поля редактирования, переключатели всегда можно представить в качестве объектов со своими свойствами и методами.
Классы и объекты
Начнем с примера простейшего класса. Зададим класс «Персона», который содержит два свойства ¾ «Имя» и «Фамилия». Для определения класса необходимо воспользоваться новым ключевым словом «Сlass». Подобно большинству конструкций языка, определение класса заканчивается ключевым словом «End»:
Class Персона
Var Имя, Фамилия;
End;
Чтобы использовать заданный класс, следует создать экземпляр класса ¾ объект. Он создается при указании в выражении имени класса:
Obj = Персона
Что произойдет при выполнении этой инструкции? Будет создан объект ¾ экземпляр класса «Персона», и ссылка на него будет присвоена переменной Obj.
Теперь, имея созданный объект, мы можем обращаться к его свойствам. В Object RSL эта процедура аналогична обращению к полям файла (что вполне естественно, поскольку файл в RSL ¾ это объект, а его поля ¾ свойства этого объекта). Приведенный ниже пример показывает, как присваиваются значения свойствам объекта:
Obj. Имя = “Иван”;
Obj. Фамилия = “Петров”
Когда значение переменной, содержащей ссылку на объект, присваивается другой переменной, то новый экземпляр класса не создается ¾ копируется только ссылка. При этом учитывается общее количество ссылок на данный объект, который, в свою очередь, существует в памяти до тех пор, пока на него имеется хотя бы одна ссылка. Как только количество ссылок становится равным нулю, объект удаляется из памяти. Нетрудно предположить, что Object RSL автоматически удаляет все объекты, ссылки на которые сохранились после завершения RSL-программы.
Obj2 = Obj; /* На объект имеется две ссылки */
Obj = null; /* Теперь одна */
Obj2 = null /* А сейчас наш объект удаляется из памяти */
Отлично! Мы создали наш первый класс на Object RSL. Попробуем его усовершенствовать. Чтобы свойства объекта инициализировались при его создании, добавим в класс код для инициализации объекта. Кроме того, надо задать метод, выводящий на печать информацию, которая описывается объектом:
Class Персона (p1,p2)
Var Имя, Фамилия;
Macro Отчет
Println (“Имя: ”, Имя);
Println (“Фамилия”, Фамилия);
End;
Имя = p1;
Фамилия = p2
End;
Кодом инициализации в новом классе являются инструкции, включенные непосредственно в тело определения класса. Следует обратить внимание, что синтаксис определения RSL-класса очень похож на синтаксис определения макропроцедуры. Единственное различие заключается в том, что вместо ключевого слова «Macro» используется слово «Сlass». Все локальные переменные, находящиеся в теле определения класса, становятся свойствами этого класса, а все локальные макропроцедуры ¾ методами. Формальные параметры p1, p2 называются параметрами инициализатора объекта класса.
В результате наших действий при помощи одной инструкции можно и создать, и инициализировать объект:
Obj = Персона (“Иван”,“Петров”)
Вызовем метод «Отчет»:
Obj. Отчет
Как видно из примера, синтаксически вызов метода не отличается от синтаксиса обращения к свойствам объекта: название метода следует через точку после переменной, ссылающейся на объект класса. Если метод имеет параметры, то они ¾ как и параметры макропроцедур RSL ¾ указываются в круглых скобках.
Object RSL позволяет наследовать классы объектов от других классов. Унаследуем класс «Сотрудник» от класса «Персона», добавив к нему свойство «Должность».
Class (Персона) Сотрудник (п_Имя, п_Фамилия, п_Должность)
Var Должность;
Macro Отчет
Отчет;
Println (“Должность: ”,Должность);
End;
InitПерсона (п_Имя, п_Фамилия);
Должность = п_Должность
End;
Базовый класс указывается в круглых скобках после ключевого слова «Class». Для его инициализации применяется особый механизм. Тонкость заключается в том, что если в выражении указать имя класса, то будет создан новый объект (что противоречит нашим целям). Поэтому в каждом классе автоматически создается предопределенный метод для выполнения инициализации, название которого образуется путем добавления к имени класса приставки «Init». Таким образом, для инициализации класса «Персона» нужно вызвать метод «InitПерсона».
Как уже упоминалось, при обращении к свойствам и методам объекта их имена следует указывать через точку после переменной, содержащей ссылку на этот объект. В то же время в коде методов все методы и свойства доступны непосредственно по имени. При этом в каждый метод класса передается скрытый параметр this, представляющий собой ссылку на объект, для которого вызывается метод или свойство. Поэтому в коде метода к каждому методу или свойству можно обратиться двумя способами. Например, из метода «Отчет» к свойству «Должность» можно обратиться либо this. Должность, либо просто ¾ Должность.
Все методы классов Object RSL являются виртуальными. Добавление в дочерний класс метода с именем, которое уже используется для одного из методов базового класса, не будет ошибкой: просто метод дочернего класса заменит метод родительского класса. Чтобы из метода дочернего класса вызвать «замещенный» метод родительского класса, нужно указать то же самое имя метода. Аналогично из метода базового класса можно вызвать метод класса предыдущего уровня иерархии и т. д. Но если в базовом классе не окажется метода с таким же именем, то произойдет рекурсивный вызов! Обратите внимание на приведенный выше пример. Мы сначала вызываем метод «Отчет» класса «Персона», который печатает имя и фамилию сотрудника, а после этого выводим на печать должность этого сотрудника.
Следует отметить, что в классах Object RSL деструкторы, определяемые пользователем, не применяются (объекты и их свойства удаляются автоматически).
При описании процедуры создания объектов я умышленно избегал слова «конструктор», пользуясь словом «инициализатор». Дело в том, что в отличие от других языков, к примеру от С++, в Object RSL конструирование объектов проходит в два этапа. Первый этап ¾ создание объекта. При этом происходит выделение памяти с учетом иерархии наследования, строятся списки замен виртуальных методов, всем свойствам класса присваивается значение V_UNDEF. После этого объект готов к корректному разрушению, потому что не содержит неинициализированных данных. На втором этапе для выполнения инициализации пользователя вызывается метод «Init» созданного объекта. Если инициализацию пользователя необходимо выполнить для базового класса, то его метод «Init» следует вызвать явно при инициализации дочернего класса.
Применение наследования с использованием виртуальных методов позволяет модифицировать функциональность базовых классов. Кроме того, в Obiect RSL можно заменять методы конкретного экземпляра класса. Для этого используется стандартная процедура GenAttach. Заменим метод «Отчет» из класса «Сотрудник» макропроцедурой ПечатьСотрудника.
Macro ПечатьСотрудника (this)
Println (“-------“);
This. Отчет;
Println (“-------“);
End;
Obj = Сотрудник (“Петр”,“Сидоров”,“Директор”);
GenAttach (obj, “Отчет”, @ПечатьСотрудника);
Obj. Отчет;
Что произойдет при выполнении инструкции Obj. Отчет? Будет вызвана процедура ПечатьСотрудника. Так как последняя не является методом класса, ссылка на объект, для которого был вызван метод «Отчет», явно указывается в списке формальных параметров, и доступ к методам и свойствам объекта осуществляется посредством этой ссылки. Наверное, читатели уже догадались, что инструкция this. Отчет в макропроцедуре ПечатьСотрудника вызовет метод «Отчет» класса «Сотрудник». Новый оператор ¾ @ ¾ позволяет получить ссылку на процедуру RSL (основные изменения в языке, которые не связаны с классами, будут рассмотрены ниже).
Для удобства работы со свойствами и методами объектов в Object RSL добавлена новая языковая конструкция ¾ With. Пользуясь ею, процедуру ПечатьСотрудника можно переписать как
Macro ПечатьСотрудника (this)
With (this)
Println (“-------”);
Отчет;
Println (“-------”)
End
End;
Конструкция With заканчивается ключевым словом End. Любая неизвестная переменная внутри блока With ¾ End считается методом или свойством класса, ссылку на который содержит переменная, указанная в круглых скобках после With.
Воспользуясь еще одним новшеством Object RSL ¾ возможностью декларировать тип, ¾ укажем, что объект, на который ссылается переменная this, принадлежит классу «Сотрудник». Декларирование типа позволит компилятору проверить, правильно ли заданы методы и свойства объектов. Тип указывается через двоеточие после имени переменной или формального параметра процедуры. Последний вариант «заменяющей» процедуры ПечатьСотрудника будет выглядеть следующим образом:
Macro ПечатьСотрудника (this:Сотрудник)
With (this)
Println (“-------”);
Отчет;
Println (“-------”)
End
End;
Процедуры работы с объектами
В Object RSL предусмотрено несколько вспомогательных процедур, предназначенных для поддержки работы с объектами. Рассмотрим каждую из них.
GenObject
Эта процедура позволяет создавать объекты, имена классов которых задаются при помощи параметров. Иными словами, на новом языке можно написать программу, которая будет определять имя класса и создавать его объект динамически (т. е. во время выполнения).
Кроме того, используя процедуру GenObject можно создавать внешние объекты Object RSL, к которым относятся формы, отчеты, кнопки и т. д. среды Visual RSL или, например, ActiveX-объекты. Чтобы внешние объекты стали доступны из модуля Object RSL, следует воспользоваться специальными «поставщиками», которые программируются на компилируемых языках С и С++ при помощи комплекта для разработки RSL-расширений DLM SDK[1]. Вот формальное описание процедуры GenObject:
GenObject (className [,parm1,parm2,…])
Первый параметр ¾ строка, задающая имя класса. Далее следуют необязательные параметры, передаваемые инициализатору объекта.
Процедура возвращает ссылку на созданный объект. Если объект заданного класса не может быть создан, произойдет ошибка времени выполнения.
GenClassName
Данная процедура возвращает имя класса для объекта, ссылка на который передается в качестве параметра. Используя процедуры GenObject и GenClassName, можно написать код, при выполнении которого будет создаваться объект, принадлежащий к тому же классу, что и объект, на который указывает ссылка. Например, если переменная Obj1 содержит ссылку на объект некоторого класса, то после выполнения инструкции
Obj2 = GenObject (GenClassName (Obj1))
переменная Obj2 будет ссылаться на новый объект того же класса, что и переменная Obj1.
Новые процедуры GenRun, GenSetProp, GenGetProp тоже используются для организации доступа к свойствам и методам объектов ¾ они позволяют определять имена свойств или методов во время выполнения программы.
GenRun
Процедура GenRun вызывает для объекта Obj метод с именем methodName и передает ему параметры par1, par2 и т. д. Возвращаемое процедурой значение ¾ это значение, которое возвращает метод. Формальное описание GenRun:
GenRun (Obj, methodName [,par1,par2,…])
В следующем примере для объекта Obj вызывается метод Отчет, и возвращаемое им значение присваивается переменной Val:
Val = GenRun (Obj, “Отчет”)
GenSetProp
Эта процедура устанавливает новое значение Val для свойства с именем propName объекта Obj. Ее формальное описание:
GenSetProp (Obj, propName, Val)
GenGetProp
Процедура GenGetProp возвращает значение свойства с именем propName объекта Obj:
Val = GenGetProp (Obj, propName)
Приведем пример, в котором мы устанавливаем, а потом выводим на печать значение свойства «Имя» объекта Obj.
GenSetProp (Obj,”Имя”,”Козлов”);
Println ( GenGetProp (Obj,”Имя”))
Полезные новшества
Безусловно, способность поддержать объектно-ориентированное программирование ¾ наиболее характерная черта Object RSL. Однако новая версия RSL имеет и другие особенности. Перечислим их.
1. Появилась новая операция получения ссылки на процедуру @. Следующая инструкция присваивает переменной Addr ссылку на процедуру MyProc:
Addr = @MyProc
Вызвать процедуру по ссылке можно при помощи старых процедур RSL ExecMacro или ExecMacro2. Эти процедуры модифицированы и теперь могут принимать в качестве параметра либо строку с именем макропроцедуры, которую необходимо вызвать, либо ссылку на нее. Для иллюстрации сказанного приведем небольшой пример:
Macro MyMacro (parm)
Println (“Вызвана процедура MyMacro с параметром ”,parm)
End;
ExecMacro (@MyMacro,”Это параметр для MyMacro”)
Кроме того, старая процедура RunDialog и новая RunMenu (о ней говорится ниже) вместо имени макропроцедуры могут принимать в качестве параметра ссылку на нее.
2. Инструкция присваивания заменена правоассоциативной операцией присваивания, и появилась новая инструкция ¾ выражение (заимствование из С и С++). Это означает, что теперь одной инструкцией можно присвоить некоторое значение сразу нескольким переменным. Например, в результате выполнения команды
A = B = C = D = 10
каждая из четырех переменных получит значение, равное 10.
Выражение с операцией присваивания может использоваться для записи условия в инструкциях If или While. Например:
Wile ((stat = MyProc (n)) > 10)
Println (stat)
End
3. Добавлены недостающие процедуры для работы с базой данных ¾ GetGT и GetLT. Они аналогичны процедурам GetGE и GetLE, только выполняют поиск по строгим условиям: GetGT ищет запись, значение ключа которой строго больше, а GetLT ¾ строго меньше заданного.
4. Появился новый спецификатор :f., который форматирует строки в соответствии с установленным в системе RS-Bank шаблоном для полей типа SNR (они используются для хранения и редактирования номеров лицевых счетов). Пример:
File ff (account);
………………..
Println (“Номер счета: ”, ff. Account:f)
5. Процедура Menu теперь принимает дополнительный параметр ¾ номер текущего активного пункта.
Array mn;
/* Формируем пункты меню */
mn = “Первый пункт”;
mn = “Второй пункт”;
mn = “Третий пункт”;
Menu (mn,”Подсказка”,”Заголовок”,null, null, 2);
В данном случае, активным станет третий пункт экранного меню (нумерация пунктов начинается с нуля).
6. Появилась возможность декларировать типы переменных, параметры макропроцедур и методов, а также их возвращаемые значения.
Декларируемый тип переменной или формального параметра указывается через двоеточие после объявления. Ниже приведен пример определения переменной «Имя» с декларированным типом «string»:
Var Имя:string
Теперь значение, которое присваивается переменной «Имя», будет автоматически преобразовываться к строковому типу.
Тип возвращаемого значения макропроцедуры или метода указывается после двоеточия, которое следует за списком формальных параметров, а в случае их отсутствия ¾ непосредственно после имени макропроцедуры или метода класса. Например:
Macro MyStrLen (str:string) : integer
Return strlen (str)
End;
Типы данных Object RSL приведены в Таблице 1.
Таблица 1. Типы данных Object RSL
Тип | Описание |
Variant | переменная этого типа может принимать значения любых типов данных Object RSL; используется по умолчанию для всех переменных |
Integer | четырехбайтовое целое число |
Money | восьмибайтовое число для денежных величин |
Double | восьмибайтовое число с плавающей точкой |
MoneyL | десятибайтовое число для денежных величин |
DoubleL | десятибайтовое число с плавающей точкой |
String | строка символов |
Bool | логическая величина; может принимать значения «true» или «false» |
Date | для хранения даты |
Time | для хранения времени |
Object | ссылка на объект |
ProcRef | ссылка на макропроцедуру |
Декларируя тип данных, можно использовать и имена классов Object RSL.
Пусть имеется класс «Mycalss»:
Class MyClass
…
End;
тогда мы можем определить переменную для хранения ссылки на его объект:
Var obj:MyClass
Следует иметь в виду, что декларирование типов не приводит к увеличению производительности RSL-программы.
8. И последнее в этом списке ¾ использование в Object RSL горизонтального меню. Новый исполняемый модуль RSL. EXE может редактировать и сохранять в специальной библиотеке (раньше она называлась библиотекой диалогов) не только диалоговые панели, но и описание горизонтального меню. Для создания, редактирования и тестирования горизонтальных меню в RSL. EXE предусмотрены специальные команды в режиме редактирования ресурсов (ранее ¾ редактирование диалогов). Меню, созданные таким образом, ничем не отличаются от тех, которые имеются в программах RS-Bank или RS-Balance.
Для редактирования горизонтальных меню используется текстовый процессор. Описание меню имеет достаточно простой формат. Оно начинается с ключевого слова MENU, после которого следуют имя меню и комментарий. Далее идут блоки POPUP ¾ END, описывающие ниспадающие меню. Ключевое слово END заканчивает описание.
Имя и комментарий для ниспадающего меню указываются после ключевого слова POPUP. Внутри блока POPUP ¾ END содержится описание пунктов меню ITEM и разделителей пунктов DELIM. Для каждого пункта задается имя, целочисленный идентификатор и комментарий. Идентификатор не должен быть равен 256, так как этот код используется для обработки нажатия клавиши ESC. Комментарии представляют собой текст, появляющийся в нижней части экрана при выборе пунктов меню. Ниже приводится пример описания горизонтального меню с именем Demo:
MENU Demo, “Демонстрационное меню”
POPUP “~К~оманды”, “Команды демонстрационного меню”
ITEM “~П~ечать”, 102, “Печать отчета”
DELIM
ITEM “~В~ыход”, 101, “Окончание работы”
END
END
Выполнение меню активизируется с помощью процедуры RunMenu, которая принимает два параметра:
· строку с библиотечным именем меню;
· строку с именем процедуры обработки команд меню либо ссылку на эту процедуру.
Процедура обработки вызывается автоматически, когда пользователь выбирает пункт меню. В качестве параметра данная процедура получает целочисленный идентификатор выбранного пункта. Если в ответ она возвращает значение true, то это же значение возвращает процедура RunMenu, и обработка меню завершается.
Рассмотрим теперь случай, когда пользователь нажимает клавишу ESC. RSL вызывает процедуру обработки команд меню с идентификатором, равным 256. Если она вернет null (или не вернет никакого значения), то обработка меню закончится, и RunMenu возвратит значение false. Если же завершение обработки меню по клавише ESC нежелательно, то тогда для идентификатора, равного 256 (или для всех необрабатываемых идентификаторов, как это будет сделано в последующем примере), нужно возвращать значение false.
Для иллюстрации сказанного приведем пример, предположив, что в библиотеке создано меню Demo, имеющее два пункта:
· выход, с идентификатором 101;
· печать отчета, с идентификатором 102.
Код обработки такого меню на Object RSL будет выглядеть следующим образом:
Macro HandleMenu (id)
If (id == 101) /* Завершить работу с меню */
Return true;
Elif (id == 102) /* Напечатать отчет */
RunReport
End;
Return false;
End;
RunMenu (“Demo”, @HandleMenu);
Заканчивая обзор объектно-ориентированной версии языка RSL, пожелаем всем читателям успешного использования Object RSL.
[1] Описанию средств DLM SDK планируется посвятить отдельную статью, которая будет опубликована в одном из следующих выпусков «RS-Club» (Ред.).


