OrbixCOMet 2000 Programmer's Guide and Reference


10 Introduction to OMG IDL

The CORBA Interface Definition Language (IDL) is used to describe the interfaces to CORBA objects in an enterprise application. An object's interface describes that object to potential clients—its attributes and operations, and their signatures.

Note: OrbixCOMet 2000 does not support all the OMG IDL types described in this chapter. Refer to "CORBA-to-Automation Mapping" and "CORBA-to-COM Mapping" for details of the OMG IDL types that OrbixCOMet 2000 supports.

This chapter contains the following main sections:

An object defined in OMG IDL can be implemented in any language that OMG IDL maps to, such as C++, Java, and COBOL. By encapsulating object interfaces within a common language, OMG IDL facilitates interaction between objects regardless of their actual implementation. Writing object interfaces in OMG IDL is therefore central to achieving the CORBA goal of interoperability between different languages and platforms.

CORBA defines standard mappings from OMG IDL to several programming languages, including C++, Java, and Smalltalk. Each OMG IDL mapping specifies how an OMG IDL interface corresponds to a language-specific implementation. The Orbix IDL compiler uses these mappings to convert OMG IDL definitions to language-specific definitions that conform to the semantics of that language.

This chapter describes OMG IDL semantics and uses. For mapping information, refer to language-specific mappings in the latest CORBA specification from the OMG.

Modules and Name Scoping

You create an application's IDL definitions within one or more IDL modules. Each module provides a naming context for the IDL definitions within it.

Modules and interfaces form naming scopes, so identifiers defined inside an interface need to be unique only within that interface. To resolve a name, the IDL compiler conducts its search among the following scopes, in this order:

  1. The current interface
  2. Base interfaces of the current interface (if any)
  3. The scopes that enclose the current interface

In the following example, two interfaces, Bank and Account, are defined within module BankDemo:

module BankDemo
{
interface Bank
    {
        //...
    };

    interface Account 
    {
        //...
    };
};

Within the same module, interfaces can reference each other by name alone. If an interface is referenced from outside its module, its name must be fully scoped with the following syntax:

module-name::interface-name

For example, the fully scoped names of interfaces Bank and Account are BankDemo::Bank and BankDemo::Account, respectively.

A module cannot be nested inside a module of the same name. Likewise, you cannot directly nest an interface inside a module of the same name. To avoid name ambiguity, you can provide an intervening name scope as follows:

module A
{
    module B
    {
     interface A
     { //... }
     }
}

Interfaces

Interfaces are the fundamental abstraction mechanism of CORBA. An interface defines a type of object, including the operations that the object supports in a distributed enterprise application.

An IDL interface generally describes an object's behavior through operations and attributes:

For example, the Account interface in module BankDemo describes the objects that implement bank accounts:

module BankDemo
{
    typedef float CashAmount;  // Type for representing cash
    typedef string AccountId;  // Type for representing account ids
    //...
    interface Account
    {
    readonly attribute AccountId  account_id;
    readonly attribute CashAmount balance;
    
    void
    withdraw(in CashAmount amount)
    raises (InsufficientFunds);

    void
    deposit(in CashAmount amount);
    }
}

This interface declares two readonly attributes, AccountId and balance, which are defined as typedefs of string and float, respectively. The interface also defines two operations that a client can invoke on this object, withdraw() and deposit().

Because an interface does not expose an object's implementation, all members are public. A client can access variables in an object's implementations only through an interface's operations or attributes.

While every CORBA object has exactly one interface, the same interface can be shared by many CORBA objects in a system. CORBA object references specify CORBA objects—that is, interface instances. Each reference denotes exactly one object, which provides the only means by which that object can be accessed for operation invocations.

Interface Contents

An IDL interface definition typically has the following components:

Of these, operations and attributes must be defined within the scope of an interface; all other components can be defined at a higher scope, and are discussed elsewhere in this chapter.

Operations

IDL operations define the signatures of an object's function, which client invocations on that object must use. The signature of an IDL operation is generally composed of three components:

A operation's return value and parameters can use any data types that IDL supports (click here). For example, the Account interface defines two operations, withdraw() and deposit(); it also defines the exception InsufficientFunds:

module BankDemo
{
    typedef float CashAmount;  // Type for representing cash
    //...
    interface Account
    {
    exception InsufficientFunds {};
    
    void
    withdraw(in CashAmount amount)
    raises (InsufficientFunds);

    void
    deposit(in CashAmount amount);
    }
}

On each invocation, both operations expect the client to supply an argument for parameter amount, and return void. Invocations on withdraw() can also raise the exception InsufficientFunds, if necessary.

Parameter Direction

Each parameter specifies the direction in which its arguments are passed between client and object. Parameter passing modes clarify operation definitions and allow the IDL compiler to map operations accurately to a target programming language. At runtime, Orbix uses parameter passing modes to determine in which direction or directions it must marshal a parameter.

A parameter can take one of three passing mode qualifiers:

In general, you should avoid using inout parameters. Because an inout parameter automatically overwrites its initial value with a new value, its usage assumes that the caller has no use for the parameter's original value. Thus, the caller must make a copy of the parameter in order to retain that value. By using two parameters, in and out, the caller can decide for itself when to discard the parameter.

One-way Operations

By default, IDL operations calls are synchronous—that is, a client invokes an operation on an object and blocks until the invoked operation returns. If an operation definition begins with the keyword oneway, a client that calls the operation remains unblocked while the object processes the call.

Three constraints apply to a one-way operation:

For example, interface Account might contain a one-way operation that sends a notice to an Account object:

module BankDemo {
    //...
        interface Account {
        oneway void notice(in string text);
        //...
    };
};

Orbix cannot guarantee the success of a one-way operation call. Because one-way operations do not support return data to the client, the client cannot ascertain the outcome of its invocation. Orbix only indicates failure of a one-way operation if the call fails before it exits the client's address space; in this case, Orbix raises a system exception.

A client can also issue non-blocking, or asynchronous, invocations. For more information, refer to the Orbix 2000 C++ Edition Programmer's Guide.

Attributes

An interface's attributes correspond to the variables that an object implements. Attributes indicate which variables in an object are accessible to clients.

Unqualified attributes map to a pair of get and set functions in the implementation language, which let client applications read and write attribute values. An attribute that is qualified with the keyword readonly maps only to a get function.

For example, the Account interface defines two readonly attributes, AccountId and balance. These attributes represent information about the account that only the object implementation can set; clients are limited to read-only access.

Exceptions

IDL operations can raise one or more CORBA-defined system exceptions. You can also define your own exceptions and explicitly specify these in an IDL operation. An IDL exception is a data structure that can contain one or more member fields, formatted as follows:

exception exception-name { 
	 	 [member;]...
};

After you define an exception, you can specify it through a raises clause in any operation that is defined within the same scope. A raises clause can contain multiple comma-delimited exceptions:

return-val operation-name( [params-list] )
    raises( exception-name[, exception-name] );

Exceptions that are defined at module scope are accessible to all operations within that module; exceptions that are defined at interface scope are accessible only to operations within that interface.

For example, interface Account defines the exception InsufficientFunds with a single member of data type string. This exception is available to any operation within the interface. In this case, the withdraw() operation raises this exception to indicate that the withdrawal failed:

module BankDemo
{
    typedef float CashAmount;  // Type for representing cash
    //...
    interface Account
    {
    exception InsufficientFunds {};
    
    void
    withdraw(in CashAmount amount)
    raises (InsufficientFunds);
    //...
    }
}

For more information about exception handling, refer to the Orbix 2000 C++ Edition Programmer's Guide.

Empty Interfaces

IDL allows you to define empty interfaces. This can be useful when you wish to model an abstract base interface that ties together a number of concrete derived interfaces. For example, the CORBA PortableServer module defines the abstract ServantManager interface, which serves to join the interfaces for two servant manager types, servant activator and servant locator:

module PortableServer
{
interface ServantManager {};

interface ServantActivator : ServantManager
{
    //...
};

interface ServantLocator : ServantManager
{
    //...
};

Inheritance of IDL Interfaces

An IDL interface can inherit from one or more interfaces. All elements of an inherited, or base interface, are available to the derived interface. An interface specifies the base interfaces from which it inherits as follows:

interface new-interface : base-interface[, base-interface]...
{...}

For example, the following interfaces, CheckingAccount and SavingsAccount, inherit from interface Account and implicitly include all of its elements:

module BankDemo{
    typedef float CashAmount;  // Type for representing cash
    interface Account {
        //...
    };

    interface CheckingAccount : Account {
        readonly attribute CashAmount overdraftLimit;
        boolean orderCheckBook ();
    };

    interface SavingsAccount : Account {
        float calculateInterest ();
    };
};

An object that implements CheckingAccount can accept invocations on any of its own attributes and operations and on any of the elements of interface Account. However, the actual implementation of elements in a CheckingAccount object can differ from the implementation of corresponding elements in an Account object. IDL inheritance only ensures type-compatibility of operations and attributes between base and derived interfaces.

Multiple Inheritance

The following IDL definition expands module BankDemo to include interface PremiumAccount, which inherits from two interfaces, CheckingAccount and SavingsAccount:

module BankDemo {
    interface Account {
        //...
    };

    interface CheckingAccount : Account {
        //...
    };

    interface SavingsAccount : Account {
        //...
    };

    interface PremiumAccount : 
        CheckingAccount, SavingsAccount {
        //...
    };
};

Figure 33 shows the inheritance hierarchy for this interface.

Figure 33: Multiple inheritance of IDL interfaces

Multiple inheritance can lead to name ambiguity among elements in the base interfaces. The following constraints apply:

Inheritance of the Object Interface

All user-defined interfaces implicitly inherit the predefined interface Object. Thus, all Object operations can be invoked on any user-defined interface. You can also use Object as an attribute or parameter type to indicate that any interface type is valid for the attribute or parameter. For example, the following operation getAnyObject() serves as an all-purpose object locator:

interface ObjectLocator {
    void getAnyObject (out Object obj);
};

It is illegal IDL syntax to inherit interface Object explicitly.

Inheritance Redefinition

A derived interface can modify the definitions of constants, types, and exceptions that it inherits from a base interface. All other components that are inherited from a base interface cannot be changed. In the following example, interface CheckingAccount modifies the definition of exception InsufficientFunds, which it inherits from Account:

module BankDemo
{
    typedef float CashAmount;  // Type for representing cash
    //...
    interface Account
    {
    exception InsufficientFunds {};
    //...
    }
    interface CheckingAccount : Account 
    {
    exception InsufficientFunds 
    {
        CashAmount overdraftLimit;
    };
    //...
    };
}

Note: While a derived interface definition cannot override base operations or attributes, operation overloading is permitted in interface implementations for those languages such as C++ that support it.

Forward Declaration of IDL Interfaces

An IDL interface must be declared before another interface can reference it. If two interfaces reference each other, the module must contain a forward declaration for one of them; otherwise, the IDL compiler reports an error. A forward declaration only declares the interface's name; the interface's actual definition is deferred until later in the module.

For example, IDL interface Bank defines two operations that return references to Account objects—create_account() and find_account(). Because interface Bank precedes the definition of interface Account, Account is forward-declared as follows:

module BankDemo
{
    typedef float CashAmount;  // Type for representing cash
    typedef string AccountId;  // Type for representing account ids

    // Forward declaration of Account 
    interface Account;

    // Bank interface...used to create Accounts
    interface Bank
    {
    exception AccountAlreadyExists { AccountId account_id; };
    exception AccountNotFound      { AccountId account_id; };

    Account
    find_account(in AccountId account_id) 
    raises(AccountNotFound);

    Account
    create_account(
        in AccountId account_id,
        in CashAmount initial_balance
    ) raises (AccountAlreadyExists);
    };

    // Account interface...used to deposit, withdraw, and query
    // available funds.
    interface Account
    {
        //...
    };
};

Valuetypes

Valuetypes enable programs to pass objects by value across a distributed system. This type is especially useful for encapsulating lightweight data such as linked lists, graphs, and dates.

Valuetypes can be seen as a cross between data types like longs and strings that can be passed by value over the wire as arguments to remote invocations, and objects, which can only be passed by reference. When a program supplies an object reference, the object remains in its original location; subsequent invocations on that object from other address spaces move across the network, rather than the object moving to the site of each request.

Like an interface, a valuetype supports both operations and inheritance from other valuetypes; it also can have data members. When a valuetype is passed as an argument to a remote operation, the receiving address space creates a copy it of it. The copied valuetype exists independently of the original; operations that are invoked on one have no effect on the other.

Because a valuetype is always passed by value, its operations can only be invoked locally. Unlike invocations on objects, valuetype invocations are never passed over the wire to a remote valuetype.

Valuetype implementations necessarily vary, depending on the languages used on sending and receiving ends of the transmission and their respective abilities to marshal and demarshal the valuetype's operations. A receiving process that is written in C++ must provide a class that implements valuetype operations and a factory to create instances of that class. These classes must be either compiled into the application, or made available through a shared library. Conversely, Java applications can marshal enough information on the sender so the receiver can download the bytecodes for the valuetype operation implementations.

Abstract Interfaces

An application can use abstract interfaces in order to determine at runtime whether an object is passed by reference or by value. For example, the following IDL definitions specify that operation Example::display() accepts any derivation of abstract interface Describable:

abstract interface Describable {
    string get_description();
};

interface Example {
    void display(in Describable someObject);
};

Given these definitions, you can define two derivations of abstract interface Describable, valuetype Currency and interface Account:

interface Account : Describable {
    // body of Account definition not shown
};

valuetype Currency supports Describable {
    // body of Currency definition not shown
};

Because the parameter for display() is defined as a Describable type, invocations on this operation can supply either Account objects or Currency valuetypes.

All abstract interfaces implicitly inherit from native type CORBA::AbstractBase, and map to C++ abstract base classes. Abstract interfaces have several characteristics that differentiate them from interfaces:

IDL Data Types

In addition to IDL module, interface, valutetype, and exception types, IDL data types can be grouped into the following categories:

Built-in Types

Table 1 lists built-in IDL types.


Table 1: Built-in IDL types
Data type Size Range of values
short

16 bits

-215...215-1

unsigned short

16 bits

0...216-1

long

32 bits

-231...231-1

unsigned long

32 bits

0...232-1

float

32 bits

IEEE single-precision floating point numbers

double

64 bits

IEEE double-precision floating point numbers

char

8 bits

ISO Latin-1

string

variable length

ISO Latin-1, except NUL

string<bound>

variable length

ISO Latin-1, except NUL

boolean

unspecified

TRUE or FALSE

octet

8 bits

0x0 to 0xff

any

variable length

Universal container type

Integer Types

IDL supports short and long integer types, both signed and unsigned. IDL guarantees the range of these types. For example, an unsigned short can hold values between 0-65535. Thus, an unsigned short value always maps to a native type that has at least 16 bits. If the platform does not provide a native 16-bit type, the next larger integer type is used.

Floating Point Types

Types float and double follow IEEE specifications for single- and double-precision floating point values, and on most platforms map to native IEEE floating point types.

char

Type char can hold any value from the ISO Latin-1 character set. Code positions 0-127 are identical to ASCII. Code positions 128-255 are reserved for special characters in various European languages, such as accented vowels.

String Types

Type string can hold any character from the ISO Latin-1 character set except NUL. IDL prohibits embedded NUL characters in strings. Unbounded string lengths are generally constrained only by memory limitations. A bounded string, such as string<10>, can hold only the number of characters specified by the bounds, excluding the terminating NUL character. Thus, a string<6> can contain the six-character string cheese.

The declaration statement can optionally specify the string's maximum length, thereby determining whether the string is bounded or unbounded:

string[<length>] name

For example, the following code declares defines a data type ShortString, which is a bounded string whose maximum length is 10 characters:

typedef string<10> ShortString;
attribute ShortString shortName; // max length is 10 chars

octet

Octet types are guaranteed not to undergo any conversions in transit. This lets you safely transmit binary data between different address spaces. Avoid using type char for binary data, inasmuch as characters might be subject to translation during transmission. For example, if a client that uses ASCII sends a string to a server that uses EBCDIC, the sender and receiver are liable to have different binary values for the string's characters.

any

The any type allows specification of values that express any IDL type, which is determined at runtime. An any logically contains a TypeCode and a value that is described by the TypeCode. Refer to the Orbix 2000 C++ Edition Programmer's Guide for more details about the any data type.

Extended Built-in Types

Table 2 lists extended built-in IDL types..


Table 2: Extended built-in IDL types
Data type Size Range of values
long long

64 bits

-263...263-1

unsigned long long

64 bits

0...-264-1

long double

79 bits

IEEE double-extended floating point number, with an exponent of at least 15 bits in length and a signed fraction of at least 64 bits. The long double type is currently not supported on Windows NT.

wchar

Unspecified

Arbitrary codesets

wstring

Variable length

Arbitrary codesets

fixed

Unspecified

31 significant digits

long long

The 64-bit integer types long long and unsigned long long support numbers that are too large for 32-bit integers. Platform support varies. If you compile IDL that contains one of these types on a platform that does not support it, the compiler issues an error.

long double

Like 64-bit integer types, platform support varies for long double, so usage can yield IDL compiler errors.

wchar

The wchar type encodes wide characters from any character set. The size of a wchar is platform-dependent. Because Orbix currently does not support character set negotiation, use this type only for applications that are distributed across the same platform.

wstring

The wstring type is the wide-character equivalent of the string type (click here). Like string types, wstring types can be unbounded or bounded. Wide strings can contain any character except NUL.

fixed

The fixed type provides fixed-point arithmetic values with up to 31 significant digits. You specify a fixed type with the following format:

typedef fixed< digit-size, scale > name

digit-size specifies the number's length in digits. The maximum value for digit-size is 31 and must be greater than scale. A fixed type can hold any value up to the maximum value of a double.

If scale is a positive integer, it specifies where to place the decimal point relative to the rightmost digit. For example the following code declares fixed data type CashAmount to have a digit size 8 and a scale of 2:

typedef fixed<10.2> CashAmount;

Given this typedef, any variable of type CashAmount can contain values of up to (+/-)99999999.99.

If scale is negative, the decimal point moves to the right scale digits, thereby adding trailing zeros to the fixed data type's value. For example, the following code declares fixed data type bigNum to have a digit size of 3 and a scale of -4:

typedef fixed <3,-4> bigNum;
bigNum myBigNum;

If myBigNum has a value of 123, its numeric value resolves to 1230000. Definitions of this sort let you store numbers with trailing zeros efficiently.

Constant fixed types can also be declared in IDL, where digit-size and scale are automatically calculated from the constant value. For example:

module Circle {
    const fixed pi = 3.142857;
};

This yields a fixed type with a digit size of 7, and a scale of 6.

Unlike IEEE floating-point values, the fixed type is not subject to representational errors. IEEE floating point values are liable to inaccurately represent decimal fractions unless the value is a fractional power of 2. For example, the decimal value 0.1 cannot be represented exactly in IEEE format. The cumulative effect of this imprecision can eventually yield inaccurate results over a series of computations with floating-point values.

Type fixed is especially useful in calculations that cannot tolerate any imprecision, such as computations of monetary values.

Complex Data Types

IDL provides the following complex data types:

enum

An enum (enumerated) type lets you assign identifiers to the members of a set of values. For example, you can modify the BankDemo IDL with enum type balanceCurrency:

module BankDemo {
    enum Currency {pound, dollar, yen, franc};

    interface Account {
        readonly attribute CashAmount balance;
        readonly attribute Currency balanceCurrency;
        //...
    };
};

In this example, attribute balanceCurrency in interface Account can take any one of the values pound, dollar, yen, or franc.

The actual ordinal values of an enum type vary according to the actual language implementation. The CORBA specification only guarantees that the ordinal values of enumerated types monotonically increase from left to right. Thus, in the previous example, dollar is greater than pound, yen is greater than dollar, and so on. All enumerators are mapped to a 32-bit type.

struct

A struct data type lets you package a set of named members of various types. In the following example, struct CustomerDetails has several members, including custID, lname, and fname. Operation getCustomerDetails() returns a struct of type CustomerDetails that contains values for the customer's name and age:

module BankDemo{
        struct CustomerDetails {
            string custID;
            string lname;
            string fname;
            short age;
            //...
        };

        interface Bank {
            CustomerDetails getCustomerDetails
                (in string custID);
            //...
        };
};

A struct must include at least one member. Because a struct provides a naming scope, member names must be unique only within the enclosing structure.

union

A union data type lets you define a structure that can contain only one of several alternative members at any given time. A union saves space in memory, as the amount of storage required for a union is the amount necessary to store its largest member.

You declare a union type with the following syntax:

union name switch (discriminator) {
    case label1 : element-spec;
    case label2 : element-spec;
    [...]
    case labeln : element-spec;
    [default : element-spec;]
};

All IDL unions are discriminated. A discriminated union associates a constant expression (label1..labeln) with each member. The discriminator's value determines which of the members is active and stores the union's value.

For example, the following code defines the IDL union Date, which is discriminated by a short value:

enum dateStorage
{ numeric, strMMDDYY, strDDMMYY }

struct DateStructure {
    short Day;
    short Month;
    short Year;
};

union Date switch (short) {
    case numeric: long digitalFormat;
    case strMMDDYY: 
    case strDDMMYY: string stringFormat;
    default: dateStorage structFormat;
};

Given this definition, if the discriminator value for Date is numeric, the digitalFormat member is active; if the discriminator's value is strMMDDYY or strDDMMYY, the stringFormat member is active; otherwise, the default member, structFormat, is active.

The following rules apply to union types:

Arrays

IDL supports multi-dimensional fixed-size arrays of any IDL data type, with the following syntax:

[typedef] element-type array-name [dimension-spec]...

dimension-spec must be a non-zero positive constant integer expression. IDL does not allow open arrays. However, you can achieve equivalent functionality with sequence types (click here).

For example, the following code fragment defines a two-dimensional array of bank accounts within a portfolio:

typedef Account portfolio[MAX_ACCT_TYPES][MAX_ACCTS]

An array must be named by a typedef declaration (click here) in order to be used as a parameter, an attribute, or a return value. You can omit a typedef declaration only for an array that is declared within a structure definition.

Because of differences between implementation languages, IDL does not specify the origin at which arrays are indexed. For example C and C++ array indexes always start at 0, while Pascal uses an origin of 1. Consequently, clients and servers cannot portably exchange array indexes unless they both agree on the origin of array indexes and make adjustments as appropriate for their respective implementation languages. Usually, it is easier to exchange the array element itself instead of its index.

sequence

IDL supports sequences of any IDL data type with the following syntax:

[typedef] sequence < element-type[, max-elements] sequence-name

An IDL sequence is similar to a one-dimensional array of elements; however, its length varies according to its actual number of elements, so that it uses memory more efficiently.

A sequence must be named by a typedef declaration (click here) in order to be used as a parameter, an attribute, or a return value. You can omit a typedef declaration only for a sequence that is declared within a structure definition.

A sequence's element type can be of any type, including another sequence type. This feature is often used to model trees.

The maximum length of a sequence can be fixed (bounded) or unfixed (unbounded):

The following code shows how to declare bounded and unbounded sequences as members of an IDL struct:

struct LimitedAccounts {
    string bankSortCode<10>; 
    sequence<Account, 50> accounts; // max sequence length is 50
}; 

struct UnlimitedAccounts {
    string bankSortCode<10>;
    sequence<Account> accounts; // no max sequence length
};

Pseudo Object Types

CORBA defines a set of pseudo object types that ORB implementations use when mapping IDL to a programming language. These object types have interfaces defined in IDL but do not have to follow the normal IDL mapping for interfaces and are not generally available in your IDL specifications.

You can use only the following pseudo object types as attribute or operation parameter types in an IDL specification:

CORBA::NamedValue
CORBA::Principal
CORBA::TypeCode

To use any of these three types in an IDL specification, include the file orb.idl in the IDL file as follows:

#include <orb.idl>
//...

This statement tells the IDL compiler to allow types NamedValue, Principal, and TypeCode.


WARNING: The orb.idl file should not actually exist in your system. Do not name any IDL file orb.idl.

Defining Data Types

With typedef, you can define more meaningful or simpler names for existing data types, whether IDL-defined or user-defined. The following code defines typedef identifier StandardAccount, so it can act as an alias for type Account in later IDL definitions:

module BankDemo {
    interface Account {
        //...
    };

    typedef Account StandardAccount;
}; 

Constants

IDL lets you define constants of all built-in types except type any. To define a constant's value, you can either use another constant (or constant expression) or a literal. You can use a constant wherever a literal is permitted.

Integer Constants

IDL accepts integer literals in decimal, octal, or hexadecimal:

const short     I1 = -99;
const long      I2 = 0123;  // Octal 123, decimal 83
const long long I3 = 0x123; // Hexadecimal 123, decimal 291
const long long I4 = +0xaB; // Hexadecimal ab, decimal 171

Both unary plus and unary minus are legal.

Floating-Point Constants

Floating-point literals use the same syntax as C++:

const float       f1 = 3.1e-9; // Integer part, fraction part, 
                               // exponent
const double      f2 = -3.14;  // Integer part and fraction part
const long double f3 = .1      // Fraction part only
const double      f4 = 1.      // Integer part only
const double      f5 = .1E12   // Fraction part and exponent
const double      f6 = 2E12    // Integer part and exponent

Character and String Constants

Character constants use the same escape sequences as C++:

const char C1 = 'c';        // the character c
const char C2 = '\007';     // ASCII BEL, octal escape
const char C3 = '\x41';     // ASCII A, hex escape
const char C4 = '\n';       // newline
const char C5 = '\t';       // tab
const char C6 = '\v';       // vertical tab
const char C7 = '\b';       // backspace
const char C8 = '\r';       // carriage return
const char C9 = '\f';       // form feed
const char C10 = '\a';      // alert
const char C11 = '\\';      // backslash
const char C12 = '\?';      // question mark
const char C13 = '\'';      // single quote
// String constants support the same escape sequences as C++
const string S1 = "Quote: \"";      // string with double quote
const string S2 = "hello world";    // simple string
const string S3 = "hello" " world"; // concatenate
const string S4 = "\xA" "B";        // two characters 
                                    // ('\xA' and 'B'), 
                                    // not the single character '\xAB'

Wide Character and String Constants

Wide character and string constants use C++ syntax. Use Universal character codes to represent arbitrary characters. For example:

const wchar     C = L'X';
const wstring   GREETING = L"Hello";
const wchar     OMEGA = L'\u03a9';
const wstring   OMEGA_STR = L"Omega: \u3A9";

Note: IDL files themselves always use the ISO Latin-1 code set, they cannot use Unicode or other extended character sets.

Boolean Constants

Boolean constants use the keywords FALSE and TRUE. Their use is unnecessary, inasmuch as they create needless aliases:

// There is no need to define boolean constants:
const CONTRADICTION = FALSE;    // Pointless and confusing
const TAUTOLOGY = TRUE;         // Pointless and confusing

Octet Constants

Octet constants are positive integers in the range 0-255.

const octet O1 = 23;
const octet O2 = 0xf0;

Note: Octet constants were added with CORBA 2.3, so ORBs that are not compliant with this specification might not support them.

Fixed-Point Constants

For fixed-point constants, you do not explicitly specify the digits and scale. Instead, they are inferred from the initializer. The initializer must end in d or D. For example:

// Fixed point constants take digits and scale from the 
// initialiser:
const fixed val1 = 3D;          // fixed<1,0>
const fixed val2 = 03.14d;      // fixed<3,2>
const fixed val3 = -03000.00D;  // fixed<4,0>
const fixed val4 = 0.03D;       // fixed<3,2>

The type of a fixed-point constant is determined after removing leading and trailing zeros. The remaining digits are counted to determine the digits and scale. The decimal point is optional.


Note: Currently, there is no way to control the scale of a constant if it ends in trailing zeros.

Enumeration Constants

Enumeration constants must be initialized with the scoped or unscoped name of an enumerator that is a member of the type of the enumeration. For example:

enum Size { small, medium, large }

const Size DFL_SIZE = medium;
const Size MAX_SIZE = ::large;

Note: Enumeration constants were added with CORBA 2.3, so ORBs that are not compliant with this specification might not support them.

Constant Expressions

IDL provides a number of arithmetic and bitwise operators.

Arithmetic Operators

The arithmetic operators have the usual meaning and apply to integral, floating-point, and fixed-point types (except for %, which requires integral operands). However, these operators do not support mixed-mode arithmetic; you cannot, for example, add an integral value to a floating-point value. The following code contains several examples:

// You can use arithmetic expressions to define constants.
const long MIN = -10;
const long MAX = 30;
const long DFLT = (MIN + MAX) / 2;

// Can't use 2 here
const double TWICE_PI = 3.1415926 * 2.0;

// 5% discount
const fixed DISCOUNT = 0.05D;
const fixed PRICE = 99.99D;

// Can't use 1 here
const fixed NET_PRICE = PRICE * (1.0D - DISCOUNT);  

Expressions are evaluated using the type promotion rules of C++. The result is coerced back into the target type. The behavior for overflow is undefined, so do not rely on it. Fixed-point expressions are evaluated internally with 62 bits of precision, and results are truncated to 31 digits.

Bitwise Operators

The bitwise operators only apply to integral types. The right-hand operand must be in the range 0-63. Note that the right-shift operator >> is guaranteed to inject zeros on the left, no matter whether the left-hand operand is signed or unsigned.

For example:

// You can use bitwise operators to define constants.
const long ALL_ONES = -1;               // 0xffffffff
const long LHW_MASK = ALL_ONES << 16;   // 0xffff0000
const long RHW_MASK = ALL_ONES >> 16;   // 0x0000ffff

IDL guarantees two's complement binary representation of values.

Precedence

The precedence for operators follows the rules for C++. You can override the default precedence by adding parentheses.



doc-feedback@iona.com
Copyright © 2001, IONA Technologies PLC.