Занятие первое
Появление в программировании того или иного языка обычно связано с возникновением и развитием некоей новой концепции. Так, одновременно с идеей объектно-ориентированной разработки (в ее современном варианте) был создан язык cмалток, сформировавший, ко всему прочему, и условия для превращения обычного Cи в объектно-ориентированный Cи++ и классического виртовского Паскаля в Borland Object Pascal. Все эти «диалекты» живы и по сей день, подтверждая своим существованием, что до заката объектно-ориентированной эпохи еще далеко.
Дальнейшее применение объектов и компонентов вкупе с внедрением последних в распределенные системы вызвало необходимость создания такого языка программирования, который бы позволил описать любой объект или компонент. И, что не менее важно, это описание должно быть одинаковым для любой платформы. Программисты ответили на эти требования, выдав «на-гора» язык описания интерфейсов IDL (Interface Definition Language). Сегодня за развитием IDL следит международный консорциум OMG, в чьем ведении также находятся технология CORBA и язык UML.
Следует заметить, что IDL не только язык, но и инструмент, с помощью которого можно сохранять метаинформацию об объектах, т. е. данные о том, как устроен объект. Известно довольно много случаев, когда язык IDL используется для описания контрактов — технических параметров, позволяющих нескольким независимым группам работать одновременно над различными частями проекта. Хотя в основном исходные тексты на IDL служат своеобразным «сырьем», из которого специальные компиляторы IDL генерируют исходные тексты на одном из языков программирования высокого уровня. Такой процесс правильно называть проецированием (mapping), но для удобства мы наречем его трансляцией. Типичный процесс создания распределенных объектных приложений состоит из описания объектов на IDL, их трансляции на какой-либо язык программирования, добавления бизнес-логики и компиляции полученных исходных текстов в готовые для запуска модули.
Серия статей, публикацию которых мы начинаем с этого номера, знакомит как с самим языком IDL, так и с трансляцией его конструкций. Но для начала следует договориться о терминах, которыми будем оперировать в дальнейшем:
- модуль — блок с заданным именем, объединяющий логически связанные конструкции языка IDL; в целом модуль можно воспринимать как пакет (package) в языке Java или пространство имен (namespace) в языке Cи++;
- интерфейс — набор атрибутов и операций объекта, с помощью которого потребитель может обращаться к объекту;
- атрибут — сущность, описывающая какое-либо свойство объекта; атрибут можно сравнить с внутренней переменной класса;
- операция — сущность, которую вызывают для выполнения действий, связанных с функциональным назначением объекта; операцию можно сравнить с методом класса.
Вооружившись этими важными понятиями, мы можем приступать к описанию элементов и конструкций языка.
Комментарии
Комментарии IDL — точная копия комментариев языка Cи++. Это значит, что для сокрытия фрагмента исходного текста можно применить пары символов /* и */. Для исключения из компиляции одной строки или ее части удобно пользоваться символами //, как это показано на примере:
/* Этот текстовый блок будет пропущен компилятором, потому что он закомментирован */ ... Эти строки будут восприняты компилятором как исходный текст и будут откомпилированы // а эта строка будет пропущена
Идентификаторы
К идентификаторам причисляют имена, с которыми имеет дело программист. Это могут быть имена интерфейсов, модулей, атрибутов, операций, констант и т. д. Для любого компилятора IDL идентификатор — это последовательность букв и цифр, а также знак подчеркивания ?_?. Все идентификаторы в IDL-файлах должны начинаться с буквенного символа. В силу некоторых особенностей разные IDL-компиляторы по-разному реагируют на нарушение этого правила. Если набрать фрагмент, описывающий три новых идентификатора:
attribute Object #x; attribute Object &_x; attribute Object 3X;
то компилятор IDL2JAVA из набора Inprise VisiBroker сообщит о том, что найденные символы не совпадают с ожидаемыми. А вот idltojava из JDK 1.2 скажет, что найдена синтаксическая ошибка, и «вывалится» сразу же на первой ошибочной строке.
Компиляторы не различают регистр букв в идентификаторах IDL, следовательно, совершенно разные имена:
attribute string MirPK; attribute string mIRpk;
будут восприняты компилятором IDL как одно и то же имя, что приведет к появлению на экране сообщения об ошибке, говорящего о попытке переопределить идентификатор MirPK. В этом есть свой резон. Если данный фрагмент IDL будет оттранслирован на язык программирования, не различающий регистр букв, то как соответствующий компилятор этого языка сможет различить имена? Поэтому подобная перестраховка не помешает.
Символ подчеркивания ?_? не может быть первым в имени идентификатора. Однако очень часто при трансляции IDL, скажем, на язык Java, компилятор сам добавляет перед получаемыми идентификаторами символы подчеркивания, чтобы избежать конфликтов имен, и идентификаторы, начинающиеся с подчеркивания, могут породить проблемы. Так, компилятор idl2java из VisiBroker «не видит» разницы между двумя одинаковыми именами, отличающимися лишь наличием символа подчеркивания вначале, и строки исходного текста:
void getSomething(in Object param); void _getSomething(in Object param);
вызовут ошибку переопределения имени getSomething. Реакция других компиляторов может быть иной.
Ключевые слова
Иначе обстоит дело с ключевыми словами — зарезервированным набором IDL. К регистру букв в ключевых словах компиляторы этого языка весьма чувствительны, поэтому даже попытка использовать слово Module вместо module приведет к ошибке времени компиляции.
IDL: ключевые слова
any | double | interface | readonly | unsigned |
attribute | enum | long | sequence | union |
boolean | exception | module | short | void |
case | FALSE | Object | string | wchar |
char | fixed | octet | struct | wstring |
const | float | oneway | switch | |
сontext | In | out | TRUE | |
default | inout | raises | typedef |
Литералы
Литералы языка IDL подразделяются на булевы, «узкие» и «широкие» символьные, целочисленные, с плавающей точкой, с фиксированной точкой и строковые.
Булев литерал самый простой. Он применяется при операциях с типом boolean и может принимать значение TRUE или FALSE:
const boolean flag = TRUE;
Немного сложнее символьные литералы. Они применяются там, где фигурируют типы char и wchar. По традиции, доставшейся от языков Cи и Cи++, символьные литералы заключаются в одинарные кавычки (? ?). Размер простых символьных литералов — 8 бит. Они описывают символы в диапазоне от 0 до 255, т. е. стандартную таблицу символов ISO Latin-1 (8859-1). К этому добавляются служебные символы и символы, описанные числовыми константами.
IDL: специальные неотображаемые символы
символ новой строки | |
горизонтальная табуляция | |
вертикальная табуляция | v |
backspace | |
carriage return | |
form feed | f |
alert | a |
обратная косая черта <> | |
вопросительный знак | ? |
одинарная кавычка | ? |
двойная кавычка | » |
восьмеричное число | ooo |
шестнадцатеричное число | xhh |
Набор специальных не отображаемых на экране символов вполне достаточен для повседневной работы, тем более что формы ooo и xhh позволяют представить любой символ как его номер в таблице символов в восьмеричном и шестнадцатеричном формате. Обычно компилятор IDL во время трансляции конвертирует подобные шестнадцатеричные коды в символ.
Отдельно нужно сказать о «широких» символьных литералах. Когда одного байта для хранения символа не хватает, применяются так называемые «широкие» символы, содержащие более 8 бит. Таблицы этих символов могут быть разными на разных платформах. Поэтому, задавая литералы с «широкими» символами, следует не выходить за рамки таблицы ISO Latin-1 (8859-1).
Целочисленные литералы могут быть десятичными, восьмеричными и шестнадцатеричными. Десятичные литералы состоят из последовательности цифр, но если последовательность цифр начинается с нуля, то компилятор считает, что перед ним литерал в восьмеричной системе счисления. Шестнадцатеричные литералы могут содержать все цифры и латинские символы от A до F. Подобные литералы должны начинаться с пары символов 0x или 0X.
Литералы с плавающей точкой могут состоять из целого числа и дробной части, разделенных символом точки (.). Их также можно записать числом в научном формате, т. е. отделить мантиссу числа от его порядка буквой e или E:
1234E+13 0.987e-150
Для финансовых вычислений в IDL предусмотрены литералы с фиксированной точкой, состоящие из целой и дробной частей, разделенных десятичной точкой и отмеченных буквой d или D. Такого рода литералы будут в дальнейшем применяться вместе с типом fixed. Однако, несмотря на то что эти элементы языка описаны в спецификации CORBA V2.2, найти их реализацию в компиляторах IDL не удалось. По крайней мере ни VisiBroker for Java 3.3, ни утилита idltojava из JDK 1.2 не умеют работать с фиксированной точкой.
И последние литералы — строковые. Они представляют собой строки, состоящие из символов, допустимых в качестве символьных литералов за исключением символа ? ?. И хотя VisiBroker и idltojava из JDK корректно обрабатывают эти символы, тем не менее ясно, что при трансляции IDL на Cи и Cи++ наличие нулевого символа в строке может послужить источником ошибки. Судя по всему, именно это и явилось причиной запрета на использование символа ? ?. Чтобы избежать ошибок компиляции, полученных в результате трансляции исходных текстов, компилятор IDL конвертирует все шестнадцатеричные коды в восьмеричные. Строковые литералы, как и символьные, могут быть основаны на простых и «широких» символах.
Препроцессинг
Для организации условной компиляции и задания некоторых опций компиляторы IDL используют препроцессинг, основанный на директивах, описанных в стандарте ANSI C++, поэтому программисты на языках Cи и Cи++ почувствуют себя здесь как рыба в воде. Если же вы не знакомы с этими языками, то следует обратиться к соответствующей литературе. В препроцессинге IDL присутствуют специальные разновидности директивы #pragma:
#pragma ID #pragma prefix #pragma version #pragma hint
Однако они имеют смысл лишь для знающих CORBA, поэтому мы не станем их касаться.
Область видимости имен
Любое имя IDL видно в том блоке, где оно описано. В качестве подобного блока могут выступать, скажем, описания модулей или интерфейсов. Для ссылки на самый глобальный блок можно использовать его имя с добавлением пары символов :: или же без подобного добавления. Однако хорошим тоном будет, если вы всегда будете добавлять символы :: перед блоками, не вложенными никуда. Таким образом вы подчеркнете, что упоминающееся имя — наиболее старшее в иерархии вложенных блоков. К примеру, чтобы сослаться на имя Inner, описанное следующим IDL-файлом:
interface Upper { typedef string Inner; };
можно написать:::Upper::Inner
илиUpper::Inner
При таком стиле создания сложных полных имен компилятору явно указывается, как найти описание соответствующего имени. Заодно снимается потенциальный конфликт имен, возможный при использовании одинаковых имен внутри разных блоков. К примеру, в одном файле IDL могут быть описаны блоки, внутри которых определены одинаковые имена:
interface One { typedef float nameConflict; }; interface Other { typedef float nameConflict; };
В этом случае потребуется детализация имен при обращении к ним:::One::nameConflict;
или::Other::nameConflict;
Простые типы
Простые типы служат для задания атрибутов объектов и параметров их операций, констант, получения новых, более сложных типов. Вкратце скажем о простых типах, применяемых в IDL. Булев тип. Тип boolean отвечает за хранение логических значений «истина» и «ложь» или, согласно правилам IDL, TRUE и FALSE.
Символьные типы могут описывать обычные 8-битовые символьные данные (тип char) или «широкие» символьные данные (тип wchar), размер которых более 8 бит. В процессе передачи символьных данных по сети они могут быть переконвертированы так, что изменится их представление, но значение символа будет сохранено. Такая ситуация может возникнуть при передаче данных между компьютерами с отличающимися кодировками.
Целочисленные типы — это short, long, long long, а также их беззнаковые разновидности с префиксом unsigned. Как вы уже, наверное, поняли, такого рода типы могут хранить только целые числа.
Обратите внимание, что тип int в IDL отсутствует.
Типы чисел с плавающей точкой подразделяются на float, double и long double. Тип float соответствует IEEE-числу с плавающей точкой одинарной точности. Тип double — IEEE-числу с плавающей точкой двойной точности. И последний тип, long double, используется для описания IEEE-числа с плавающей точкой двойной расширенной точности. Более полные данные о числах с плавающей точкой IEEE можно узнать из документа «IEEE Standard for Binary Floating-Point Arithmetic», ANSI/IEEE Standard 754-1985.
Специальные типы используются в IDL для декларации в особых случаях. Их два: octet и any. Первый служит для передачи по коммуникационным системам 8-битовых чисел так, чтобы они в процессе пересылки не подверглись изменениям. Тип octet — хорошая альтернатива типу char в тех случаях, когда передается маленькое число.
IDL: диапазоны допустимых хранимых значений для целочисленных типов
short | от -2 15 до 2 15 - 1 |
long | от 2 31 до 2 31 - 1 |
long long | от -2 63 до 2 63 - 1 |
unsigned short | от 0 до 2 16 - 1 |
unsigned long | от 0 до 2 32 - 1 |
unsigned long long | от 0 до 2 64 - 1 |
Для того чтобы описать поля, в которых могут храниться значения любого типа, позволенного IDL, any можно сравнить с универсальным типом void* в языках Cи и Cи++ или с классом java.lang.Object в языке Java. Тип any часто применяется в CORBA.
Константы
Описание констант начинается с ключевого слова «const», за которым указываются: тип константы, ее имя, символ присвоения «=» и константное выражение, состоящее из литералов и математических операторов. Константное выражение определяет, какое значение будет присвоено константе. Константы бывают следующих типов: целочисленные, символьные (обычные и «широкие»), с плавающей и фиксированной точкой, строчные (с обычными и «широкими» символами) и булевы.
Литералы, которые могут использоваться в константных выражениях, были рассмотрены ранее. А вот о математических операторах еще не было сказано. Бинарные операторы могут быть следующими:
| — побитовое «ИЛИ»;
^ — побитовое «ИСКЛЮЧАЮЩЕЕ ИЛИ»;
& — побитовое «ИСКЛЮЧАЮЩЕЕ И»;
>> — побитовый сдвиг вправо с заполнением освободившихся битов нулями; разрешается сдвиг в диапазоне от 0 до 63 позиций включительно;
<< — побитовый сдвиг влево с заполнением освободившихся битов нулями; разрешается сдвиг в диапазоне от 0 до 63 позиций включительно;
+ — арифметическое сложение;
- — арифметическое вычитание;
* — арифметическое умножение;
/ — арифметическое деление;
% — взятие остатка от арифметического деления.
Унарные операторы подразделяются на установку отрицательного (-) и положительного (+) знака у числа, а также дополнения этого числа до двух (~).
Унарные операторы (+-), а также бинарные операторы (* / + -) могут быть использованы при создании константных выражений, где участвуют числа с плавающей и фиксированной точкой. А вот унарные операторы (+ - ~) и бинарные операторы (* / % + - << >> & | ^) могут быть применены только к целым числам.
Ниже показаны примеры описания констант:
const short sConst = 1123; const double dConst = 2.f * 3.14 * 1.234E+13; const wstring wsConst = «Широкая строка»; const long long llConst = (1000000 >> 4) % 7;
На следующем занятии познакомимся с более сложными типами и конструкциями IDL.