OrbixCOMet 2000 Programmer's Guide and Reference |
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.
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:
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 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.
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.
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.
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
: The parameter is initialized only by the client and is passed to the object.
out
: The parameter is initialized only by the object and returned to the client.
inout
: The parameter is initialized by the client
and passed to the server; the server can modify the value before
returning it to the client.
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.
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:
void
.
in
.
raises
clause is allowed.
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.
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.
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:
exceptionexception-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.
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 { //... };
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:
interfacenew-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.
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:
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.
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.
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 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.
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:
TRUE
) or a value (FALSE
). The demarshalling code can thus determine whether the argument passed to it is an object reference or a value.
CORBA::Object
,
in order to allow support for valuetypes. If the runtime argument
supplied to an abstract interface type can be narrowed to an object
reference type, then CORBA::Object
operations can be invoked on it.
In addition to IDL module, interface, valutetype, and exception types, IDL data types can be grouped into the following categories:
short
, long
, and float
long long
and wstring
enum
and struct
, and string
Table 1 lists built-in IDL types.
Data type | Size | Range of values |
---|---|---|
|
||
|
||
|
||
|
||
|
||
|
||
|
||
string |
||
string<bound> |
||
|
||
|
||
|
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.
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.
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.
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
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.
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.
Table 2 lists extended built-in IDL types..
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.
Like 64-bit integer types, platform support varies for long double
, so usage can yield IDL compiler errors.
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.
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.
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.
IDL provides the following complex data types:
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.
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.
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:
unionname
switch (discriminator
) { caselabel1
:element-spec
; caselabel2
:element-spec
;[
...]
caselabeln
: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:
integer
, char
, boolean
or enum
, or an alias of one of these types; all case
label expressions must be compatible with this type.
union
provides a naming scope, member names must be unique only within the enclosing union.
union
contains a pair of values: the discriminator value and the active member.
stringFormat
is active when the discriminator is either strMMDDYY or strDDMMYY
.
default
case label. The corresponding member is active if the discriminator value does not correspond to any other label.
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.
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 };
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: Theorb.idl
file should not actually exist in your system. Do not name any IDL fileorb.idl
.
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; };
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.
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 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 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 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 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 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.
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 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.
IDL provides a number of arithmetic and bitwise 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.
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.
// 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.
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. |