Writing and Compiling IDL¶
Writing IDL for a CORBA application¶
The first step in developing a CORBA application is to define the interfaces to its distributed application objects. You define these interfaces using OMG’s Interface Definition Language (IDL).
Essentially, the IDL specification of an interface lists the names and types of operations that:
Any CORBA object, satisfying that interface, must support.
Any CORBA client application, targeting such an object, may request.
Our bank server application manages three types of CORBA object,
representing accounts, checking accounts, and banks. We declare the
interfaces to all three objects within the same CORBA module,
BankingDemo
:
module BankingDemo {
interface account {
// details follow
};
interface checkingAccount : account {
// details follow
};
interface bank {
// details follow
};
};
The following subsections describe the IDL declarations for each of
the three interfaces. You can find the complete IDL description for
the bank demo in the Open Dylan Examples folder, under
Examples\corba\bank\bank\bank.idl
.
IDL for the account interface¶
This is the IDL definition of the interface to an account
object.
// in module BankingDemo
interface account {
readonly attribute string name;
readonly attribute long balance;
void credit (in unsigned long amount);
exception refusal {string reason;};
void debit (in long amount)
raises (refusal);
};
An account
object’s name
attribute is used to store the name
of the account holder. The state of an account is recorded in the
balance
attribute. To keep things simple, we use CORBA long
values to represent the monetary amounts that we use to store account
balances and to modify them with credits and debits.
To prevent clients from directly altering the account’s name
or
balance
, these attributes are declared as readonly
attributes. The operations credit
and debit
are provided to
allow updates to an account’s balance
attribute.
The operation credit
adds a non-negative amount to the current
account balance.
Next is an exception declaration:
exception refusal {string reason;};
This declares a named exception, refusal
, that the debit
operation uses to signal errors. The refusal
exception is declared
to contain a reason
field that documents the reason for failure
using a string
.
The operation debit
subtracts a given amount from the current
account balance, as long as this does not make the account balance
negative. Qualifying debit
by the phrase raises (refusal)
declares that invoking this operation might raise the exception
refusal
. This phrase is necessary because although a CORBA
operation may raise any CORBA system exception, its declaration must
specify any additional user-defined CORBA exceptions that it might
raise.
This completes the IDL declaration of the account
interface.
IDL for the checkingAccount interface¶
Our application manages a second sort of bank account, called a
checking account . While an ordinary account
must maintain a
positive balance, a checkingAccount
may be overdrawn up to an
agreed limit. We use IDL’s notion of interface inheritance to capture
the intuition that a checking account is just a special form of
account
:
// in module BankingDemo
interface checkingAccount : account {
readonly attribute long limit;
};
The declaration checkingAccount : account
specifies that the
interface checkingAccount
inherits all the operations and
attributes declared in the account
interface. The body of the
definition states that a checkingAccount
also supports the
additional limit
attribute.
The fact that checkingAccount
inherits some operations from
account
does not imply that the methods implementing those
operations need to be inherited too. We will exploit this flexibility
to provide a specialized debit
method for checkingAccount
s.
IDL for the bank interface¶
We can now design the interface of a bank object. The intention is that a bank associates customer names with accounts, with each name identifying at most one account. A client is able to open accounts for new customers and to retrieve both accounts and checking accounts for existing customers from the persistent store. If the client attempts to open a second account under the same name, the bank should refuse the request by raising an exception. Similarly, if the client attempts to retrieve an account for an unknown customer, the bank should reject the request by raising an exception.
The IDL definition of the bank
interface captures some of these
requirements:
// in module BankingDemo
interface bank {
readonly attribute string name;
exception duplicateAccount{};
account openAccount (in string name)
raises (duplicateAccount);
checkingAccount openCheckingAccount(in string name,
in long limit)
raises (duplicateAccount);
exception nonExistentAccount{};
account retrieveAccount(in string name)
raises (nonExistentAccount);
void closeAccount (in account account);
};
The name of a bank
object is recorded in its name
attribute.
The operation openAccount
is declared to take a CORBA string
and return an account
. Because account
is defined as an
interface, and not a type, this means that the operation will return
a reference to an account
object. This illustrates an important
distinction between ordinary values and objects in CORBA: while
members of basic and constructed types are passed by value, objects
are passed by reference.
The qualification raises (duplicateAccount)
specifies that
openAccount
can raise the user-defined exception
duplicateAccount
, instead of returning an account. (The exception
duplicateAccount
has no fields.)
The operation openCheckingAccount
is similar to openAccount
,
but takes an additional argument, limit
, which represents the
account’s overdraft limit.
The operation retrieveAccount
looks up the account (or checking
account), if any, associated with a customer name
, and returns an
object reference of interface account
. The operation can raise the
exception nonExistentAccount
to indicate that there is no account
under the supplied name.
The last operation, closeAccount
, closes an account
by
deleting it from the bank’s records.
Because checkingAccount
inherits from account
, a
checkingAccount
object can be used wherever an account
object
is expected, whether as the actual argument, or the result, of an
operation. For instance, we can use closeAccount
to close
checkingAccount
objects as well as account
objects, and we can
use retrieveAccount
to fetch checkingAccount
objects as well
as account
objects.
Compiling IDL for a CORBA application¶
Open Dylan includes an IDL compiler, Scepter, that it uses to check and compile IDL files into Dylan libraries. These libraries provide essential infrastructure for CORBA-based applications in Dylan.
The libraries are built according to the specification in An IDL Binding for Dylan. That document is a draft specification for a standard mapping from CORBA IDL to the Dylan language. Briefly, the specification states that:
CORBA types are mapped to Dylan types and classes
CORBA interfaces are mapped to Dylan classes
CORBA interface inheritance is mapped to Dylan class inheritance
CORBA attributes are mapped to Dylan getter and setter functions
CORBA operations are mapped to Dylan generic functions
CORBA exceptions are mapped to Dylan conditions
IDL declarations are mapped to Dylan according to these rules. The resulting libraries provide a Dylan protocol equivalent to the IDL.
Libraries created by compiling IDL¶
The IDL compiler can produce skeleton, stub, and protocol libraries from an IDL file. This, again, is as specified in An IDL Binding for Dylan. The purpose of these libraries is to make writing CORBA applications easier, by providing a pre-built interface to CORBA operations.
The skeletons or server skeletons library contains code for use by a CORBA server application, while the stubs or client stubs library contains code for use by a CORBA client application. In both cases, the code hides the details of CORBA communication from the application, allowing you to invoke operations in other CORBA objects without having to worry about where those objects are running. The stubs and skeletons act as proxies for the real, remote operations.
The protocol library is a Dylan representation of the interface described in the IDL file. The Dylan representation is mapped from IDL according to the Dylan IDL binding, with open classes and open generic functions representing IDL interfaces and operations. As we saw in Chapter 2, “Quick Start Tutorial”, the protocol provides the basis for implementing clients and servers. The skeletons and the stubs library both use the protocol library and re-export the names from it.
Note
Typically a server project uses the client stubs library in addition to the skeletons and protocol library. This allows the server to make callbacks to the client.
IDL files in Dylan projects¶
IDL files can be treated as part of Dylan projects, allowing the IDL interface to be compiled at the same time as the project’s Dylan sources. This is in fact the simplest way to manage a Dylan project that uses CORBA facilities.
However, you do not include IDL files directly in the project. Rather, each IDL file that a project depends on is represented in the project by a corresponding Open Dylan Tool Specification (spec) file.
The spec file indicates the path to the IDL file, and states which of the skeletons, stubs, and protocol libraries the project requires. The Open Dylan development environment uses the spec file to invoke the IDL compiler as part of the normal build process for the project.
The Bank-Client and Bank-Server projects each contain a spec file for
the bank.idl
file. The Bank-Client project’s spec file,
idl.spec
, requests that stubs and protocol libraries be
generated. The Bank-Server project’s spec file requests that
skeletons, stubs, and protocol libraries be generated. (This second
spec file is also called idl.spec
but is a different file to the
one in Bank-Client.)
After building both the bank client and the bank server, there will be three new subfolders:
Examples\corba\bank\bank\stubs
Contains the project
bank-stubs.hdp
that defines the Bank-Stubs library. This library is used by the implementation of the bank client. Its project is automatically added tobank-client.hdp
as a subproject.Examples\corba\bank\bank\skeletons
Contains the project
bank-skeletons.hdp
that defines the Bank-Skeletons library. This library is used by the implementation of the bank server. Its project is automatically added tobank-server.hdp
as a subproject.Examples\corba\bank\bank\protocol
Contains the project
bank-protocol.hdp
that defines the Bank-Protocol library. This library is shared by both the Bank-Skeletons and Bank-Stubs libraries, and added automatically to the projects of both.
Compilation steps¶
If you have not built the Bank-Client and Bank-Server projects yet, you should do so now.
Open the Bank-Client and Bank-Server projects from the CORBA section of the Examples dialog.
You can bring up the Examples dialog by choosing
.Choose
in each project window.Notice how the stubs, skeletons, and protocol projects are generated and added automatically to the top-level client and server projects.
Mapping IDL to Dylan¶
To get an impression of the mapping from IDL to Dylan, we can take a
look at the result of applying the mapping to the file
bank.idl
. The following Dylan definitions are taken from
bank-protocol.dylan
, part of the Bank-Protocol project
produced by compiling bank.idl
IDL:
define open abstract class BankingDemo/<account> (<object>)
end class;
define open generic BankingDemo/account/name
(object :: BankingDemo/<account>)
=> (result :: CORBA/<string>);
define open generic BankingDemo/account/balance
(object :: BankingDemo/<account>)
=> (result :: CORBA/<long>);
define open generic BankingDemo/account/credit
(object :: BankingDemo/<account>,
amount :: CORBA/<unsigned-long>)
=> ();
define sealed class BankingDemo/account/<refusal>
(CORBA/<user-exception>)
slot BankingDemo/account/refusal/reason :: CORBA/<string>,
required-init-keyword: reason:;
end class;
define open generic BankingDemo/account/debit
(object :: BankingDemo/<account>, amount :: CORBA/<long>)
=> ();
define open abstract class BankingDemo/<checkingAccount>
(BankingDemo/<account>)
end class;
define open generic BankingDemo/checkingAccount/limit
(object :: BankingDemo/<checkingAccount>)
=> (result :: CORBA/<long>);
define open abstract class BankingDemo/<bank> (<object>)
end class;
define open generic BankingDemo/bank/name
(object :: BankingDemo/<bank>)
=> (result :: CORBA/<string>);
define sealed class BankingDemo/bank/<duplicateAccount>
(CORBA/<user-exception>)
end class;
...
To provide some more intuition for the mapping scheme, the following
subsections examine the Dylan counterparts of some of the more
representative IDL declarations from the file bank.idl
. See
An IDL Binding for Dylan for the full mapping description.
Mapping for interfaces¶
The IDL interfaces account
, checkingAccount
and bank
map
to the Dylan abstract classes BankingDemo/<account>
,
BankingDemo/<checkingAccount>
and BankingDemo/<bank>
.
Dylan does not support nested namespaces, so in Dylan, the IDL scope
identifier BankingDemo
is prefixed to the name of each interface
defined within its scope. This is how we get the Dylan class
identifiers BankingDemo/<account>
,
BankingDemo/<checkingAccount>
and BankingDemo/<bank>
.
Notice how IDL interface inheritance (checkingAccount : account
)
maps naturally onto Dylan class inheritance: the class
BankingDemo/<checkingAccount>
is defined as a subclass of
BankingDemo/<account>
).
Mapping for basic types¶
The IDL types string
, long
, and unsigned long
are mapped
to the Dylan classes CORBA/<string>
, CORBA/<long>
and
CORBA/<unsigned-long>
, which are simply aliases for the Dylan
classes <string>
, <integer>
and a positive subset of
<integer>
.
Mapping for attributes¶
The read-only balance
attribute of an IDL account
gives rise
to the Dylan generic function:
define open generic BankingDemo/account/balance
(object :: BankingDemo/<account>)
=> (result :: CORBA/<long>);
If we had omitted the readonly
keyword from the definition of the
balance
attribute, the mapping would have introduced an
additional generic -setter
function:
define open generic BankingDemo/account/balance-setter
(value :: CORBA/<long>, object :: BankingDemo/<account>)
=> (value :: CORBA/<long>);
Recall that, in the IDL source, the balance
attribute is declared
within the definition, and thus the subordinate namespace, of the
BankingDemo
module and the account
interface. Again, because
Dylan does not support nested namespaces, the IDL scope identifiers
BankingDemo
and account
are simply prefixed to the name of the
attribute’s getter method, resulting in the Dylan function identifier
BankingDemo/account/balance
.
Note
More generally, the Dylan language IDL binding specifies that an
IDL identifier is mapped to a Dylan identifier by appending
together all the enclosing scope identifiers and the scoped
identifier itself, separating the identifiers by forward slashes
(/
).
Mapping for operations¶
The IDL operation credit
is mapped to the Dylan generic function:
define open generic BankingDemo/account/credit
(object :: BankingDemo/<account>,
amount :: CORBA/<unsigned-long>)
=> ();
In IDL, the credit
operation is defined within the account
interface, declaring it to be an operation on account
objects. The
Dylan language IDL binding adopts the convention that an operation’s
target object should be passed as the first argument of the
corresponding Dylan generic function. Thus the first parameter of the
generic function BankingDemo/account/credit
is an object of class
BankingDemo/<account>
.
The operation’s in
and inout
arguments become the remaining
parameters of the corresponding Dylan generic function. In this case,
the credit
operation specifies a single in
parameter, in
unsigned long amount
, that determines the second and only other
parameter, amount :: CORBA/<long>
, of the
BankingDemo/account/credit
generic function.
The operation’s result type and any other parameters declared as
out
or inout
become results of the corresponding Dylan
generic function. In this case, because the result type of credit
is void
, and the operation has no out
or inout
parameters, BankingDemo/account/credit
has an empty result list.
Mapping for exceptions¶
The IDL exception refusal
maps onto the Dylan class
BankingDemo/account/<refusal>
. Its member, string reason;
,
maps onto a slot BankingDemo/account/refusal/reason ::
CORBA/<string>
.
Note that BankingDemo/account/<refusal>
is a subclass of
CORBA/<user-exception>
and, by inheritance, of Dylan
<condition>
. This means that CORBA user exceptions can be raised
on the server, and handled in the client, using the standard Dylan
condition mechanism.