typedef — ключевое слово, заимствованное из языков программирования Cи и Cи++. С его помощью описывают новые типы, а точнее — присваивают уже имеющимся типам альтернативные имена, что позволяет использовать удобные псевдонимы. В дальнейшем вы увидите, что с помощью typedef объявляются и некоторые другие конструкции языка IDL.

Конструируемые типы

К конструируемым типам IDL относятся структуры, дискриминируемые объединения и энумераторы.

Структуры

Структуры служат для определения сложных типов, призванных хранить наборы разнородных данных. Типичная структура описывается следующим образом:

struct < Имя структуры > 

{

   < Список членов >

};

Список членов — это разделенный точкой с запятой набор элементов, являющихся комбинацией из имени типа и идентификатора. Имя типа может быть любым базовым типом IDL, а также типом, определяемым самим программистом. Допускается использование массивов с обязательным указанием числа элементов.

Типичный пример описания структуры, состоящей из двух полей, одно из которых пользовательского типа, а второе — массив чисел с плавающей точкой:

struct Our

{

  MyType field;

  float coefficients[10];

};

В более сложных случаях можно описать структуру с помощью ключевого слова typedef:

typedef struct tagOurStruct

{

  MyType field;

  float coefficients[10];

} TheStructure;

При таком описании создается структура типа tagOurStruct и одновременно определяется псевдоним TheStructure.

Дискриминируемые объединения Объединения в IDL несколько сложноваты для программирующих на языках Cи и Cи++. Справедливости ради заметим, что программирующие на Паскале найдут в IDL нечто знакомое. Дело в том, что в IDL объединения «скрещены» с ключевым словом switch и обладают дискриминатором — элементом, определяющим, какой член объединения использовать в том или ином случае. Таким образом, объект, полученный в результате компиляции дискриминируемого объединения, способен в разное время хранить значения разных типов. Главное, чтобы во время описания объединения с помощью IDL были перечислены все возможные варианты хранимых типов. Типичное описание дискриминируемого объединения выглядит следующим образом:
union < Имя объединения > switch
 (< Тип дискриминатора >)

{

   < Список элементов выбора >

};

Здесь <Тип дискриминатора> может быть символьным, целочисленным, булевым или энумератором: char, wchar, short, unsigned short, long, long long, unsigned long, unsigned long long, boolean, enum.

Со списком элементов выбора дело обстоит несколько сложнее. Каждый элемент состоит из ключевого слова case и следующего за ним константного выражения, после которого ставится двоеточие и производится собственно описание хранимого типа. Константное выражение должно возвращать тип, совпадающий с типом дискриминатора. При записи в объединение некоторого значения объединение принимает тип, совпадающий с типом сохраняемого значения. Заодно запоминается дискриминатор. В дальнейшем, если будет произведена попытка считать значение под типом, отличающимся от того, под которым это значение было сохранено, произойдет генерация исключения org.omg.CORBA.BAD_OPERATION. Однако для программиста все-таки предусмотрено некоторое облегчение: объединение обладает значением по умолчанию, которое на языке IDL описывается ключевым словом default. Объединение может содержать только один такой элемент.

Рассмотрим короткий пример описания дискриминируемого объединения:

union MyType switch (short)

{

  case 13: short alpha;

  case 0x0C << 3: long beta;

  default: arr alphabet;

};

В этом случае тип MyType имеет дискриминатор типа short. Числа, стоящие после case, представляют собой значения дискриминатора. Именно от них зависит, какой член объединения будет использован. Если значение дискриминанта равняется 13, то под именем alpha будет храниться значение short. При дискриминанте 0Х0C << 3 (конечное значение этого константного выражения — 96) хранимое значение будет иметь тип long и носить имя beta. И наконец, по умолчанию значение, хранящееся в объединении, будет иметь тип arr и к нему можно обращаться по имени alphabet.

Дискриминируемые объединения могут описываться с помощью ключевого слова typedef:

typedef union tagMyType switch (char)

{

  case ?a?: short alpha;

  case ?b?: long beta;

  default: arr alphabet;

} TheUnion;

В данном примере создается новый тип-объединение tagMyType и его псевдоним TheUnion.

Энумераторы

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

Общая схема описания энумератора такова:

enum < Имя энумератора >

{

   < Список элементов >

};

В качестве списка элементов выступают идентификаторы, разделенные запятыми:

enum Semaphore {red, yellow, green};

Как и прочие конструируемые типы, энумераторы могут описываться ключевым словом typedef, задающим дополнительное имя-псевдоним:

typedef enum tagSemaphore 

{red, yellow, green} TheEnumeration;

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

Шаблонные типы

К шаблонным типам можно отнести строки (как «узкие», так и «широкие»), последовательности и числа с фиксированной точкой.

Строки

Строки в IDL стоят в стороне от базовых типов. Это 8-битовые «узкие» string и «широкие» wstring. Первые могут содержать любые символы типа char за исключением null. Второй тип строки состоит из символов, подпадающих под базовый тип wchar, и заканчивается «широким» символом null. Если простые «узкие» строки ориентированы на локальные кодировки, то «широкие» строки, как правило, употребляются в интернациональных программах.

Длина строки может быть как ограниченной, так и неограниченной. Ограниченные (bounded) строки можно сравнить с символьным массивом заданной длины. Типичное описание подобного типа:


typedef wstring <28> boundedString;

В угловых скобках задается размер строки в символах. Неограниченные же (unbounded) строки содержат в себе символов столько, сколько потребуется:


typedef string unboundedString;

Учтите, что описание строчных типов должно предваряться ключевым словом typedef.

Последовательности

Последовательности во многом схожи со строками. Различие между ними состоит в том, что последовательности помимо символьных данных могут хранить данные и других типов. Точно так же, как и строки, последовательности могут быть ограниченными по размеру либо неограниченными. Правда, в описании последовательности всегда присутствуют угловые скобки, в которых пишется тип хранимых данных (для неограниченных последовательностей) или тип хранимых данных с размером последовательности, разделенные запятой. Первый тип последовательности может выглядеть так:


typedef sequence unboundedSequence;

А вот пример описания типа ограниченной последовательности:


typedef sequence boundedSequence;

Как уже было сказано ранее, только последовательности могут быть рекурсивно вложены друг в друга:


typedef sequence< sequence 
 > recursedSequence;

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

Числа с фиксированной точкой

Тип данных fixed, как мы уже говорили ранее, представляет собой десятичное число с фиксированной точкой, имеющее до 31 значащей цифры. К сожалению, на тот момент, когда эта статья писалась, тип fixed был только описан в спецификации CORBA, но не был реализован. По крайней мере это справедливо для IDL-компиляторов из Inprise VisiBroker 3.3 и JDK 1.2.

Прочие типы

К оставшимся типам относятся массивы и native-типы. Массивы описываются ключевым словом typedef, за которым следуют тип, идентификатор и размерность массива. Допускаются многомерные массивы:


typedef double someArray[100][100];

Еще один любопытный тип, native, имеется в спецификации OMG IDL. Он служит для введения новых типов данных, которые реализованы на языке программирования, отличном от IDL. Короче говоря, следующая строка исходного текста на IDL:


native NonIDLType;

говорит компилятору, что где-то имеется тип NonIDLType, реализованный непонятным для него образом, но тем не менее к нему нужно сделать определенный интерфейс. Программистам на Java ключевое слово native знакомо. Оно описывает объекты, реализованные на языках программирования высокого уровня с соблюдением правил JNI (Java Native Interface).

Маленький пример, показанный ниже, иллюстрирует, как можно использовать native-типы:


module SomeModule

{

  native MyTypeMadeInCPlusPlus;

  typedef MyTypeMadeInCPlusPlusnative
TypeArray[3][3];

};

Ключевое слово native говорит компилятору IDL, что где-то имеется тип MyTypeMadeInCPlusPlus, реализованный на языке Cи++, и мы хотим его использовать. После такого объявления MyTypeMadeInCPlusPlus можно использовать точно так же, как и любой другой тип IDL.

Исключения

В IDL имеется специальная конструкция, описывающая пользовательские исключения, с которыми вы непременно столкнетесь при создании различных типов, модулей и т. д. И здесь IDL окажет вам посильную помощь.

Описание типов-исключений практически полностью совпадает с описанием структур. Минимальное различие состоит в замене ключевого слова struct на exception:


exception < Имя исключения > 

{

   < Список членов >

};

Список членов — это разделенный точкой с запятой набор элементов, являющихся комбинацией из имени типа и идентификатора. Имя типа может быть любым базовым типом IDL, а также типом, определяемым самим программистом. Допускается использование массивов с обязательным указанием числа элементов.

Пример описания исключения:

...

typedef long nativeTypeArray[3][3];

...

exception my

{

  string what;

  nativeTypeArray sa;

};

...

Надо отметить, что многие стандартные исключения CORBA описаны на языке IDL.

Интерфейсы

Интерфейс для современного программиста — одно из важнейших понятий, обозначающих набор операций и атрибутов для обращения к объекту. Интерфейсы могут наследоваться от других интерфейсов, причем наследование может быть множественным. Что касается интерфейсов IDL, то в дополнение к операциям и атрибутам в них могут быть описаны константы и исключения.

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

Описания интерфейсов могут быть полными и опережающими (forward). Допускаются множественные опережающие описания.

Опережающие описания требуются в том случае, если необходимо обратиться к интерфейсу еще до его декларации. Чтобы получить подобное описание, достаточно написать ключевое слово interface и его имя:


interface < Имя интерфейса >; 

При полном описании после имени добавляются двоеточие и имена интерфейсов-предков:


interface < Имя интерфейса > [ :
 < Имя интерфейса-предка 1 > ... [ ,
 < Имя интерфейса-предка n >]...]

{

...

   <Описания типов, констант, исключений,
 атрибутов и операций >

...

};

Внутри описаний интерфейсов описываются прочие элементы IDL, которые допускаются внутри интерфейсов.

Новые типы, константы и исключения нам уже известны. Осталось познакомиться с описаниями атрибутов и операций.

Атрибуты

Как уже было сказано в первом занятии, атрибуты — это члены классов, хранящие какие-то значения. В IDL атрибуты описываются следующим образом:


[readonly] attribute < Тип атрибута >
 < Имя атрибута >;

Необязательный модификатор readonly говорит, что значение описываемого атрибута не может быть изменено, только считано. Если readonly отсутствует, то в любой момент хранимое атрибутом значение может быть заменено на другое, как это происходит с обычными переменными.

Операции

Операции — логическое дополнение к атрибутам. С помощью операций выполняется некий предопределенный участок кода объекта. Описываются операции по схеме, показанной ниже:


{ oneway void | < Возвращаемый тип > }
 < Имя операции > 

   ({ in | out | inout } <Параметр> ...
 [, { in | out | inout } <Параметр>]... ) 

     [ raises ( < Имя исключения > ...
 [, < Имя исключения > ] ... ) ] ;

Данная раскладка требует некоторой расшифровки. Если определяется операция, возвращающая некоторое значение, то слева от ее имени необходимо написать тип возвращаемого значения. Исключением являются операции, определенные как oneway, они должны возвращать тип void, т. е. не возвращать никакого значения. Ключевое слово oneway говорит, что при вызове операции программа не ждет, пока эта операция завершится, а продолжает выполнение. Параметры операции могут быть входными (in), выходными (out) или комбинированными (inout). Если операция может возбудить исключения, то они должны быть перечислены в скобках через запятую после ключевого слова raises.

Пример, показанный ниже, иллюстрирует, как описываются интерфейсы и их начинка:


interface InterfaceDeclaration

{

  typedef long ArrayType[3][3];

  const short SomeError = 0xFF;

  exception SomethingWrong

  {

    string whatHappend;

    ArrayType errors;

  };

  attribute ArrayType matrix;

  unsigned long returnCode(in boolean flag);

};

Описание модуля

Модуль — самая «старшая» единица языка IDL. Он служит для группировки типов, интерфейсов и т. д., логически связанных друг с другом. У модуля есть имя и тело:


module < Имя модуля > 

{

...

   < Тело модуля >

...

};

В принципе можно считать, что модули являются прямым отображением пакетов языка Java и пространств имен в Cи++.