The purpose of OMG IDL, Interface Definition Language, mapping is to act as translator between platforms and languages. An IDL specification is supposed to describe data types, object types etc.
CORBA is independent of the programming language used to construct clients or implementations. In order to use the ORB, it is necessary for programmers to know how to access ORB functionality from their programming languages. It translates different IDL constructs to a specific programming language. This chapter describes the mapping of OMG IDL constructs to the Erlang programming language.
A complete language mapping will allow the programmer to have access to all ORB functionality in a way that is convenient for a specified programming language.
All mapping must define the following elements:
To begin with, we should decide which type of objects (i.e. servers) we
need and if two, or more, should export the same functionality. Let us
assume that we want to create a system for DB (database) access for different
kind of users. For example, anyone with a valid password may extract
data, but only a few may update the DB. Usually, an application
is defined within a
// DB IDL
#ifndef _DB_IDL_
#define _DB_IDL_
// A module is simply a container
module DB {
// An interface maps to a CORBA::Object.
interface CommonUser {
};
// Inherit the Consumer interface
interface Administrator : CommonUser {
};
interface Access {
};
};
#endif
Since the
The OMG defines a set of reserved case insensitive key-words, which may
NOT be used as identifiers (e.g. module name). For more
information, see
The OMG IDL mapping is strongly typed and, even if you have a good knowledge of CORBA types, it is essential to read carefully the following mapping to Erlang types.
The mapping of basic types is straightforward. Note that the OMG IDL double type is mapped to an Erlang float which does not support the full double value range.
The
Functions with return type
Constructed types all have native mappings as shown in the table below.
A
When defining a a string or wstring they can be of limited length or null terminated:
myString10;
typedef wstring<10> myWString10;
]]>
If we want to define a char/string or wchar/wstring constant, we can use octal (\OOO - one, two or three octal digits), hexadecimal (\xHH - one or two hexadecimal digits) and unicode (\uHHHH - one, two, three or four hexadecimal digits.) representation as well. For example:
const string SwedensBestSoccerTeam = "\101" "\x49" "\u004B";
const wstring SwedensBestHockeyTeam = L"\101\x49\u004B";
const char aChar = '\u004B';
const wchar aWchar = L'\u004C';
Naturally, we can use
A sequence can be defined to be of a maximum length or unbounded, and may contain Basic and Template types and scoped names:
aShortSequence;
typedef sequence aLongSequence;
typedef sequence anEvenLongerSequence;
]]>
Arrays are multidimensional, fixed-size arrays. The indices is language mapping specific, which is why one should not pass them as arguments to another ORB.
typedef long myMatrix[2][3];
A Fixed Point literal consists of an integer part (decimal digits),
decimal point and a fraction part (decimal digits),
followed by a
const fixed myFixed1 = 3.14D;
const fixed myFixed2 = .14D;
const fixed myFixed3 = 0.14D;
const fixed myFixed4 = 3.D;
const fixed myFixed5 = 3D;
It is also possible to use unary (+-) and binary (+-*/) operators:
const fixed myFixed6 = 3D + 0.14D;
const fixed myFixed7 = -3.14D;
The Fixed Point examples above are, so called, anonymous definitions. In later CORBA specifications these have been deprecated as function parameters or return values. Hence, we strongly recommend that you do not use them. Instead, you should use:
myFixed53;
const myFixed53 myFixed53constant = 03.140d;
typedef fixed<3,2> myFixed32;
const myFixed32 myFixed32constant = 3.14d;
myFixed53 foo(in myFixed32 MF); // OK
void bar(in fixed<5,3> MF); // Illegal
]]>
For more information, see
Now we continue to work on our IDL specification. To begin with, we want
to limit the size of the logon parameters (Id and password). Since the
UserID;
typedef string<10> Password;
CommonUser logon(in UserID ID, in Password PW);
};
};
#endif ]]>
But what should, for example, the
Constructed types all have native mappings as shown in the table below.
A
myStructSeq;
struct myStruct {
myStructSeq chain;
};
// Deprecated definition (anonymous) not supported by IC
struct myStruct {
sequence chain;
};
]]>
The maximum number of identifiers which may defined in an enumeration is 2³². The order in which the identifiers are named in the specification of an enumeration defines the relative order of the identifiers.
A
A case label must match the defined type of the discriminator, and may only contain a default case if the values given in the non-default labels do not cover the entire range of the union's discriminant type. For example:
// Illegal default; all cases covered by
// non-default cases.
union BooleanUnion switch(boolean) {
case TRUE: long TrueValue;
case FALSE: long FalseValue;
default: long DefaultValue;
};
// OK
union BooleanUnion2 switch(boolean) {
case TRUE: long TrueValue;
default: long DefaultValue;
};
It is not necessary to list all possible values of the union discriminator in the body. Hence, the value of a union is the value of the discriminator and, in given order, one of the following:
The above can be summed up to:
// If the discriminator equals 1 or 2 the value
// is a long. Otherwise, the atom undefined.
union LongUnion switch(long) {
case 1:
case 2: long TrueValue;
};
// If the discriminator equals 1 or 2 the value
// is a long. Otherwise, a boolean.
union LongUnion2 switch(long) {
case 1:
case 2: long TrueValue;
default: boolean DefaultValue;
};
In the same way as structs, unions can be recursive if forward declaration is used (anonymous types is deprecated and not supported):
myUnionSeq;
union myUnion switch (long) {
case 1 : myUnionSeq chain;
default: boolean DefaultValue;
};
]]>
Recursive types (union and struct) require Light IFR. I.e. the IC option {light_ifr, true} is used and that Orber is configured in such a way that Light IFR is activated. Recursive TypeCode is currently not supported, which is why these cannot be encapsulated in an any data type.
Every field in, for example, a struct must be initiated. Otherwise
it will be set to the atom
Now we can continue to work on our IDL specification. To begin with, we should
determine the return value of the
UserID;
typedef string<10> Password;
// Since Administrator inherits from CommonUser
// the returned Object can be of either type.
CommonUser logon(in UserID ID, in Password PW);
};
};
#endif ]]>
We can also define exceptions (i.e. not system exception) thrown by
each interface. Since exceptions are thoroughly described in the chapter
$ erlc DB.idl
or:
$ erl Erlang (BEAM) emulator version 5.1.1 [threads:0] Eshell V5.1.1 (abort with ^G) 1> ic:gen('DB'). ok 2> halt().
The next step is to implement our servers. But, to be able to do that, we need to know how we can access data type definitions. For example, since a struct is mapped to an Erlang record we must include an hrl-file in our callback module.
Within a scope all identifiers must be unique. The following kinds of definitions form scopes in the OMG IDL:
For example, since enumerants do not form a scope, the following IDL code is not valid:
module MyModule {
// 'two' is not unique
enum MyEnum {one, two};
enum MyOtherEnum {two, three};
};
But, since Erlang only has two levels of scope, module and function, the OMG IDL scope is mapped as follows:
An Erlang module, corresponding to an IDL global name, is derived by
converting occurrences of "::" to underscore, and eliminating
the leading "::". Hence, accessing
For example, an operation
UserID;
typedef string<10> Password;
// Since Administrator inherits from CommonUser
// the returned Object can be of either type.
// This operation is exported from:
// DB_Access.erl
CommonUser logon(in UserID ID, in Password PW);
};
};
#endif ]]>
Using underscores in IDL names can lead to ambiguities
due to the name mapping described above. It is advisable to
avoid the use of underscores in identifiers. For example, the following
definition would generate two structures named
module x {
struct y_z {
...
};
interface y {
struct z {
...
};
};
};
Several files can be generated for each scope.
After compiling DB.idl, the following files have been generated:
Since the
As mentioned in a previous section,
%% Erlang code
....
AnEmployee = #'DB_employee'{'No' = 1,
'Name' = "Adam Ivan Kendall",
'Address' = "Rasunda, Solna",
'Dpt' = 'Department1'},
EmployeeTC = 'DB_employee':tc(),
EmployeeAny = any:create(EmployeeTC, AnEmployee),
....
For more information, see the
Constants are generated as Erlang functions, and are accessed by a single function call. The functions are put in the file corresponding to the scope where they are defined. There is no need for an object to be started to access a constant.
Example:
// m.idl
module m {
const float pi = 3.14;
interface i {
const float pi = 3.1415;
};
};
Since the two constants are defined in different scopes, the IDL code
above is valid, but not necessarily a good approach. After compiling
$ erlc m.idl $ erlc m.erl $ erl Erlang (BEAM) emulator version 5.1.1 [threads:0] Eshell V5.1.1 (abort with ^G) 1> m:pi(). 3.14 2> m_i:pi(). 3.1415 3> halt().
Objects are accessed by object references. An object reference is an opaque Erlang term created and maintained by the ORB.
Objects are implemented by providing implementations for all
operations and attributes of the Object,
Exceptions are handled as Erlang catch and throws. Exceptions
are translated to messages over an IIOP bridge but converted
back to a throw on the receiving side. Object implementations
that invoke operations on other objects must be aware of the
possibility of a non-local return. This includes invocation of
ORB and IFR services. See also the
Exception parameters are mapped as an Erlang record and accessed as such.
An object implementation that raises an exception will use the
Attributes are accessed through their access functions. An
attribute implicitly defines the
readonly attribute long RAttribute;
attribute long RWAttribute;
The
A standard Erlang
Do not confuse the object internal state with its object reference. The object internal state is an Erlang term which has a format defined by the user.
It is not always the case that the internal state will be the first parameter, as stubs can use their own object reference as the first parameter (see the IC documentation).
A function call will invoke an operation. The first
parameter of the function should be the object reference and then
all
Example:
// IDL
module m {
interface i {
readonly attribute long RAttribute;
attribute long RWAttribute;
long foo(in short a);
long bar(in char c, inout string s, out long count);
void baz(out long Id);
};
};
Is used in Erlang as :
%% Erlang code
....
Obj = ... %% get object reference
RAttr = m_i:'_get_RAttribute'(Obj),
RWAttr = m_i:'_get_RWAttribute'(Obj),
ok = m_i:'_set_RWAttribute'(Obj, Long),
R1 = m_i:foo(Obj, 55),
{R2, S, Count} = m_i:bar(Obj, $a, "hello"),
....
Note how the
'_set_RWAttribute'(State, Long) ->
{reply, ok, State}.
'_get_RWAttribute'(State) ->
{reply, Long, State}.
'_get_RAttribute'(State) ->
{reply, Long, State}.
foo(State, AShort) ->
{reply, ALong, State}.
bar(State, AShort, AString) ->
{reply, {ALong, "MyString", ALong}, State}.
baz(State) ->
{reply, {ok, AId}, State}.
The operations may require more arguments (depends on IC options used). For
more information, see
A function can also be defined to be
Now we are ready to implement the call-back modules. There are three modules we must create:
An easy way to accomplish that, is to use the IC backend
$> erlc +"{be,erl_template}" DB.idl
We begin with implementing the
%%
%% $Id$
%%
%%----------------------------------------------------------------------
%% Module : DB_Access_impl.erl
%%
%% Source : /home/user/example/DB.idl
%%
%% Description :
%%
%% Creation date: 2005-05-20
%%
%%----------------------------------------------------------------------
-module('DB_Access_impl').
-export([logon/3]).
%%----------------------------------------------------------------------
%% Internal Exports
%%----------------------------------------------------------------------
-export([init/1,
terminate/2,
code_change/3,
handle_info/2]).
%%----------------------------------------------------------------------
%% Include Files
%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Macros
%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Records
%%----------------------------------------------------------------------
-record(state, {}).
%%======================================================================
%% API Functions
%%======================================================================
%%----------------------------------------------------------------------
%% Function : logon/3
%% Arguments : State - term()
%% ID = String()
%% PW = String()
%% Returns : ReturnValue = OE_Reply
%% OE_Reply = Object_Ref()
%% Raises :
%% Description:
%%----------------------------------------------------------------------
logon(State, ID, PW) ->
%% Check if the ID/PW is valid and what
%% type of user it is (Common or Administrator).
OE_Reply
= case check_user(ID, PW) of
{ok, administrator} ->
'DB_Administrator':oe_create();
{ok, common} ->
'DB_CommonUser':oe_create();
error ->
%% Here we should throw an exception
corba:raise(....)
end,
{reply, OE_Reply, State}.
%%======================================================================
%% Internal Functions
%%======================================================================
%%----------------------------------------------------------------------
%% Function : init/1
%% Arguments : Env = term()
%% Returns : {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Raises : -
%% Description: Initiates the server
%%----------------------------------------------------------------------
init(_Env) ->
{ok, #state{}}.
%%----------------------------------------------------------------------
%% Function : terminate/2
%% Arguments : Reason = normal | shutdown | term()
%% State = term()
%% Returns : ok
%% Raises : -
%% Description: Invoked when the object is terminating.
%%----------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%----------------------------------------------------------------------
%% Function : code_change/3
%% Arguments : OldVsn = undefined | term()
%% State = NewState = term()
%% Extra = term()
%% Returns : {ok, NewState}
%% Raises : -
%% Description: Invoked when the object should update its internal state
%% due to code replacement.
%%----------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%----------------------------------------------------------------------
%% Function : handle_info/2
%% Arguments : Info = normal | shutdown | term()
%% State = NewState = term()
%% Returns : {noreply, NewState} |
%% {noreply, NewState, Timeout} |
%% {stop, Reason, NewState}
%% Raises : -
%% Description: Invoked when, for example, the server traps exits.
%%----------------------------------------------------------------------
handle_info(_Info, State) ->
{noreply, State}.
]]>
Since
$ erlc +'{{impl, "DB::CommonUser"}, "DBUser_impl"}'\ +'{{impl, "DB::Administrator"}, "DBUser_impl"}' DB.idl $ erlc *.erl
Instead of creating, and not the least, maintaining two call-back modules,
we only have to deal with
%%======================================================================
%% API Functions
%%======================================================================
%%----------------------------------------------------------------------
%% Function : delete/2
%% Arguments : State - term()
%% ENo = unsigned_Long()
%% Returns : ReturnValue = ok
%% Raises :
%% Description:
%%----------------------------------------------------------------------
delete(State, ENo) ->
%% How we access the DB, for example mnesia, is not shown here.
case delete_employee(No) of
ok ->
{reply, ok, State};
error ->
%% Here we should throw an exception if
%% there is no match.
corba:raise(....)
end.
%%----------------------------------------------------------------------
%% Function : lookup/2
%% Arguments : State - term()
%% ENo = unsigned_Long()
%% Returns : ReturnValue = OE_Reply
%% OE_Reply = #'DB_employee'{No,Name,Address,Dpt}
%% No = unsigned_Long()
%% Name = String()
%% Address = String()
%% Dpt = Department
%% Department = 'Department1' | 'Department2'
%% Raises :
%% Description:
%%----------------------------------------------------------------------
lookup(State, ENo) ->
%% How we access the DB, for example mnesia, is not shown here.
case lookup_employee(ENo) of
%% We assume that we receive a 'DB_employee' struct
{ok, Employee} ->
OE_Reply = Employee,
{reply, OE_Reply, State};
error ->
%% Here we should throw an exception if
%% there is no match.
corba:raise(....)
end.
After you have compiled both call-back modules, and implemented the missing functionality (e.g. lookup_employee/1), we can test our application:
%% Erlang code
....
%% Create an Access object
Acc = 'DB_Access':oe_create(),
%% Login is Common user and Administrator
Adm = 'DB_Access':logon(A, "admin", "pw"),
Com = 'DB_Access':logon(A, "comm", "pw"),
%% Lookup existing employee
Employee = 'DB_Administrator':lookup(Adm, 1),
Employee = 'DB_CommonUser':lookup(Adm, 1),
%% If we try the same using the DB_CommonUser interface
%% it result in an exit since that operation is not exported.
{'EXIT', _} = (catch 'DB_CommonUser':delete(Adm, 1)),
%% Try to delete the employee via the CommonUser Object
{'EXCEPTION', _} = (catch 'DB_Administrator':delete(Com, 1)),
%% Invoke delete operation on the Administrator object
ok = 'DB_Administrator':delete(Adm, 1),
....
The use of some names is strongly discouraged due to ambiguities. However, the use of some names is prohibited when using the Erlang mapping , as they are strictly reserved for IC.
IC reserves all identifiers starting with
Note also, that an identifier in IDL can contain alphabetic, digits and underscore characters, but the first character must be alphabetic.
The OMG defines a set of reserved words, shown below, for use as keywords. These may not be used as, for example, identifiers. The keywords which are not in bold face was introduced in the OMG CORBA-3.0 specification.
The keywords listed above must be written exactly as shown. Any usage
of identifiers that collide with a keyword is illegal. For example,
long is a valid keyword; Long and LONG are
illegal as keywords and identifiers. But, since the OMG must be able
to expand the IDL grammar, it is possible to use Escaped Identifiers. For example, it is not unlikely that
typedef string native;
interface i {
void foo(in native Arg);
};
};
With Escaped Identifiers the code will look like:
typedef string _native;
interface i {
void foo(in _native Arg);
};
};
Type Codes are used in