6 OMG IDL to Erlang Mapping
6.1 OMG IDL to Erlang Mapping - Overview
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.
6.2 OMG IDL Mapping Elements
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:
- All OMG IDL basic and constructed types
- References to constants defined in OMG IDL
- References to objects defined in OMG IDL
- Invocations of operations, including passing of parameters and receiving of results
- Exceptions, including what happens when an operation raises an exception and how the exception parameters are accessed
- Access to attributes
- Signatures for operations defined by the ORB, such as dynamic invocation interface, the object adapters etc.
- Scopes; OMG IDL has several levels of scopes, which are mapped to Erlang's two scopes.
6.3 Getting Started
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 module, and all global datatypes are defined on the top-level. To begin with we create a module and the interfaces we need:
// 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 Administrator should be able to do the same things as the CommonUser, the previous inherits from the latter. The Access interface will grant access to the DB. Now we are ready to define the functionality and data types we need. But, this requires that we know a little bit more about the OMG IDL.
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 Reserved Compiler Names and Keywords
6.4 Basic OMG IDL Types
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.
OMG IDL type | Erlang type | Note |
float | Erlang float | |
double | Erlang float | value range not supported |
short | Erlang integer | -2^15 .. 2^15-1 |
unsigned short | Erlang integer | 0 .. 2^16-1 |
long | Erlang integer | -2^31 .. 2^31-1 |
unsigned long | Erlang integer | 0 .. 2^32-1 |
long long | Erlang integer | -2^63 .. 2^63-1 |
unsigned long long | Erlang integer | 0 .. 2^64-1 |
char | Erlang integer | ISO-8859-1 |
wchar | Erlang integer | UTF-16 (ISO-10646-1:1993) |
boolean | Erlang atom | true/false |
octet | Erlang integer | |
any | Erlang record | #any{typecode, value} |
long double | Not supported | |
Object | Orber object reference | Internal Representation |
void | Erlang atom | ok |
The any value is written as a record with the field typecode which contains the Type Coderepresentation, see also the Type Code table, and the value field itself.
Functions with return type void will return the atom ok.
6.5 Template OMG IDL Types and Complex Declarators
Constructed types all have native mappings as shown in the table below.
Type | IDL code | Maps to | Erlang code |
string | typedef string S; void op(in S a); |
Erlang string | ok = op(Obj, "Hello World"), |
wstring | typedef wstring S; void op(in S a); |
Erlang list of Integers | ok = op(Obj, "Hello World"), |
sequence | typedef sequence <long, 3> S; void op(in S a); |
Erlang list | ok = op(Obj, [1, 2, 3]), |
array | typedef string S[2]; void op(in S a); |
Erlang tuple | ok = op(Obj, {"one", "two"}), |
fixed | typedef fixed<3,2> myFixed; void op(in myFixed a); |
Erlang tuple | MF = fixed:create(3, 2, 314), ok = op(Obj, MF), |
String/WString Data Types
A string consists of all possible 8-bit quantities except null. Most ORB:s uses, including Orber, the character set Latin-1 (ISO-8859-1). The wstring type is represented as a list of integers, where each integer represents a wide character. In this case Orber uses, as most other ORB:s, the UTF-16 (ISO-10646-1:1993) character set.
When defining a a string or wstring they can be of limited length or null terminated:
// Null terminated typedef string myString; typedef wstring myWString; // Maximum length 10 typedef string<10> 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 "Erlang", L"Rocks", 'A' and L'A' as well.
Sequence Data Type
A sequence can be defined to be of a maximum length or unbounded, and may contain Basic and Template types and scoped names:
typedef sequence <short, 1> aShortSequence; typedef sequence <long> aLongSequence; typedef sequence <aLongSequence> anEvenLongerSequence;
Array Data Type
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];
Fixed Data Type
A Fixed Point literal consists of an integer part (decimal digits), decimal point and a fraction part (decimal digits), followed by a D or d. Either the integer part or the fraction part may be missing; the decimal point may be missing, but not d/D. The integer part must be a positive integer less than 32. The Fraction part must be a positive integer less than or equal to the Integer part.
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:
typedef fixed<5,3> 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 Fixed in Orber's Reference Manual.
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 and Password parameters, only will be used when invoking operations on the Access interface, we may choose to define them within the scope that interface. To keep it simple our DB will contain employee information. Hence, as the DB key we choose an integer (EmployeeNo).
// DB IDL #ifndef _DB_IDL_ #define _DB_IDL_ module DB { typedef unsigned long EmployeeNo; interface CommonUser { any lookup(in EmployeeNo ENo); }; interface Administrator : CommonUser { void delete(in EmployeeNo ENo); }; interface Access { typedef string<10> UserID; typedef string<10> Password; CommonUser logon(in UserID ID, in Password PW); }; }; #endif
But what should, for example, the lookup operation return? One option is to use the any data type. But, depending on what kind of data it encapsulates, this datatype can be rather expensive to use. We might find a solution to our problems among the Constructed IDL types.
6.6 Constructed OMG IDL Types
Constructed types all have native mappings as shown in the table below.
Type | IDL code | Maps to | Erlang code |
struct | struct myStruct { long a; short b; }; void op(in myStruct a); |
Erlang record | ok = op(Obj, #'myStruct'{a=300, b=127}), |
union | union myUnion switch(long) { case 1: long a; }; void op(in myUnion a); |
Erlang record | ok = op(Obj, #'myUnion'{label=1, value=66}), |
enum | enum myEnum {one, two}; void op(in myEnum a); |
Erlang atom | ok = op(Obj, one), |
Struct Data Type
A struct may have Basic, Template, Scoped Names and Constructed types as members.
Enum Data Type
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.
Union Data Type
A union may consist of:
- Identifier
- Switch - may be an integer, char, boolean, enum or scoped name.
- Body - with or without a default case; may appear at most once.
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:
- If the discriminator match a label, explicitly listed in a case statement, the value must be of the same type.
- If the union contains a default label, the value must match the type of the default label.
- No value. Orber then inserts the Erlang atom undefined in the value field when receiving a union from an external ORB.
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; };
Every field in, for example, a struct must be initiated. Otherwise it will be set to the atom undefined, which Orber cannot encode when communicating via IIOP. In the example above, invoking the operation with #'myStruct'{a=300} will fail (equal to #'myStruct'{a=300, b=undefined})
Now we can continue to work on our IDL specification. To begin with, we should determine the return value of the lookup operation. Since the any type can be rather expensive we can use a struct or a union instead. If we intend to return the same information about a employee every time we can use a struct. Let us assume that the DB contains the name, address, employee number and department.
// DB IDL #ifndef _DB_IDL_ #define _DB_IDL_ module DB { typedef unsigned long EmployeeNo; enum Department {Department1, Department2}; struct employee { EmployeeNo No; string Name; string Address; Department Dpt; }; typedef employee EmployeeData; interface CommonUser { EmployeeData lookup(in EmployeeNo ENo); }; interface Administrator : CommonUser { void delete(in EmployeeNo ENo); }; interface Access { typedef string<10> 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 System and User Defined Exceptions, we choose not to. Hence, we are now ready to compile our IDL-file by invoking:
$ 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.
6.7 Scoped Names and Generated Files
Scoped Names
Within a scope all identifiers must be unique. The following kinds of definitions form scopes in the OMG IDL:
- module
- interface
- operation
- valuetype
- struct
- union
- exception
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:
- Function Scope - used for constants, operations and attributes.
- Erlang Module Scope - the Erlang module scope handles the remaining OMG IDL scopes.
An Erlang module, corresponding to an IDL global name, is derived by converting occurrences of "::" to underscore, and eliminating the leading "::". Hence, accessing MyEnum from another module, one use MyModule::MyEnum
For example, an operation foo defined in interface I, which is defined in module M, would be written in IDL as M::I::foo and as 'M_I':foo in Erlang - foo is the function name and 'M_I' is the name of the Erlang module. Applying this knowledge to a stripped version of the DB.idl gives:
// DB IDL #ifndef _DB_IDL_ #define _DB_IDL_ // ++ topmost scope ++ // IC generates oe_XX.erl and oe_XX.hrl. // XX is equal to the name of the IDL-file. // Tips: create one IDL-file for each top module // and give the file the same name (DB.idl). // The oe_XX.erl module is used to register data // in the IFR. module DB { // ++ Module scope ++ // To access 'EmployeeNo' from another scope, use: // DB::EmployeeNo, DB::Access etc. typedef unsigned long EmployeeNo; enum Department {Department1, Department2}; // Definitions of this struct is contained in: // DB.hrl // Access functions exported by: // DB_employee.erl struct employee { ... CUT ... }; typedef employee EmployeeData; ... CUT ... // If this interface should inherit an interface // in another module (e.g. OtherModule) use: // interface Access : OtherModule::OtherInterface interface Access { // ++ interface scope ++ // Types within this scope is accessible via: // DB::Access::UserID // The Stub/Skeleton for this interface is // placed in the module: // DB_Access.erl typedef string<10> 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 x_y_z.
module x { struct y_z { \011... }; interface y { \011struct z { \011 ... \011}; }; };
Generated Files
Several files can be generated for each scope.
- An Erlang source code file (.erl) is generated for top level scope as well as the Erlang header file.
- An Erlang header file (.hrl) will be generated for each scope. The header file will contain record definitions for all struct, union and exception types in that scope.
- Modules that contain at least one constant definition, will produce Erlang source code files (.erl). That Erlang file will contain constant functions for that scope. Modules that contain no constant definitions are considered empty and no code will be produced for them, but only for their included modules/interfaces.
- Interfaces will produce Erlang source code files (.erl), this code will contain all operation stub code and implementation functions.
- In addition to the scope-related files, an Erlang source file will be generated for each definition of the types struct, union and exception (these are the types that will be represented in Erlang as records). This file will contain special access functions for that record.
- The top level scope will produce two files, one header file (.hrl) and one Erlang source file (.erl). These files are named as the IDL file, prefixed with oe_.
After compiling DB.idl, the following files have been generated:
- oe_DB.hrl and oe_DB.erl for the top scope level.
- DB.hrl for the module DB.
- DB_Access.hrl and DB_Access.erl for the interface DB_Access.
- DB_CommonUser.hrl and DB_CommonUser.erl for the interface DB_CommonUser.
- DB_Administrator.hrl and DB_Administrator.erl for the interface DB_Administrator.
- DB_employee.erl for the structure employee in module DB.
Since the employee struct is defined in the top level scope, the Erlang record definition is found in DB.hrl. IC also generates stubs/skeletons (e.g. DB_CommonUser.erl) and access functions for some datatypes (e.g. DB_employee.erl). How the stubs/skeletons are used is thoroughly described in Stubs/Skeletons and Module_Interface.
6.8 Typecode, Identity and Name Access Functions
As mentioned in a previous section, struct, union and exception types yield record definitions and access code for that record. For struct, union, exception, array and sequence types, a special file is generated that holds access functions for TypeCode, Identity and Name. These functions are put in the file corresponding to the scope where they are defined. For example, the module DB_employee.erl, representing the employee struct, exports the following functions:
- tc/0 - returns the type code for the struct.
- id/0 - returns the IFR identity of the struct. In this case the returned value is "IDL:DB/employee:1.0", but if the struct was defined in the scope of CommonUser, the result would be "IDL:DB/CommonUser/employee:1.0". However, the user usually do not need to know the Id, just which Erlang module contains the correct Id.
- name/0 - returns the scoped name of the struct. The employee struct name is "DB_employee".
Type Codesare, for example, used in Any values. Hence, we can encapsulate the employee struct in an any type by:
%% 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 Type Code listing.
6.9 References to Constants
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 { \011const 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 m.idl, the constant definitions can be extracted by invoking:
$ 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().
6.10 References to Objects Defined in OMG IDL
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, see operation implementation.
6.11 Exceptions
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 Exceptions section.
Exception parameters are mapped as an Erlang record and accessed as such.
An object implementation that raises an exception will use the corba:raise/1 function, passing the exception record as parameter.
6.12 Access to Attributes
Attributes are accessed through their access functions. An attribute implicitly defines the _get and _set operations. These operations are handled in the same way as normal operations. The _get operation is defined as a readonly attribute.
readonly attribute long RAttribute; attribute long RWAttribute;
The RAttribute requires that you implement, in your call-back module, _get_RAttribute. For the RWAttribute it is necessary to implement _get_RWAttribute and _set_RWAttribute.
6.13 Invocations of Operations
A standard Erlang gen_server behavior is used for object implementation. The gen_server state is then used as the object internal state. Implementation of the object function is achieved by implementing its methods and attribute operations. These functions will usually have the internal state as their first parameter, followed by any in and inout parameters.
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 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 in and inout parameters follow in the same order as specified in the IDL specification. The result will be a return value unless the function has inout or out parameters specified; in which case, a tuple of the return value, followed by the parameters will be returned.
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 inout parameter is passed and returned. There is no way to use a single occurrence of a variable for this in Erlang. Also note, that ok, Orber's representation of the IDL-type void, must be returned by baz and '_set_RWAttribute'. These operations can be implemented in the call-back module as:
'_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 Stubs/Skeletons and Module_Interface.
A function can also be defined to be oneway, i.e. asynchronous. But, since the behavior of a oneway operation is not defined in the OMG specifications (i.e. the behavior can differ depending on which other ORB Orber is communicating with), one should avoid using it.
6.14 Implementing the DB Application
Now we are ready to implement the call-back modules. There are three modules we must create:
- DB_Access_impl.erl
- DB_CommonUser_impl.erl
- DB_Administrator_impl.erl
An easy way to accomplish that, is to use the IC backend erl_template, which will generate a complete call-back module. One should also add the same compile options, for example this or from, used when generating the stub/skeleton modules:
$> erlc +"{be,erl_template}" DB.idl
We begin with implementing the DB_Access_impl.erl module, which, if we used erl_template, will look like the following. All we need to do is to add the logic to the logon operation.
%%---------------------------------------------------------------------- %% <LICENSE> %% %% $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) -> \011%% Check if the ID/PW is valid and what \011%% type of user it is (Common or Administrator). \011OE_Reply = case check_user(ID, PW) of \011 {ok, administrator} -> \011 'DB_Administrator':oe_create(); \011 {ok, common} -> \011 'DB_CommonUser':oe_create(); \011 error -> \011 %% Here we should throw an exception \011 corba:raise(....) end, \011{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) -> \011{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) -> \011ok. %%---------------------------------------------------------------------- %% 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) -> \011{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) -> \011{noreply, State}.
Since DB_Administrator inherits from DB_CommonUser, we must implement delete in the DB_Administrator_impl.erl module, and lookup in DB_Administrator_impl.erlandDB_CommonUser_impl.erl. But wait, is that really necessary? Actually, it is not. We simple use the IC compile option impl:
$ 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 DBUser_impl.erl. If we generated the templates, we simply rename DB_Administrator_impl.erl to DBUser_impl.erl. See also the Exceptions chapter. In the following example, only the implementation of the API functions are shown:
%%====================================================================== %% 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), ....
6.15 Reserved Compiler Names and Keywords
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 OE_ and oe_ for internal use.
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.
abstract | exception | inout | provides | truncatable |
any | emits | interface | public | typedef |
attribute | enum | local | publishes | typeid |
boolean | eventtype | long | raises | typeprefix |
case | factory | module | readonly | unsigned |
char | FALSE | multiple | setraises | union |
component | finder | native | sequence | uses |
const | fixed | Object | short | ValueBase |
consumes | float | octet | string | valuetype |
context | getraises | oneway | struct | void |
custom | home | out | supports | wchar |
default | import | primarykey | switch | wstring |
double | in | private | TRUE |
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 native have been used in IDL-specifications as identifiers. One option is to change all occurrences to myNative. Usually, it is necessary to change programming language code that depends upon that IDL as well. Since Escaped Identifiers just disable type checking (i.e. if it is a reserved word or not) and leaves everything else unchanged, it is only necessary to update the IDL-specification. To escape an identifier, simply prefix it with _. The following IDL-code is illegal:
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); }; };
6.16 Type Code Representation
Type Codes are used in any values. To avoid mistakes, you should use access functions exported by the Data Types modules (e.g. struct, union etc) or the orber_tc module.
Type Code | Example |
tk_null | |
tk_void | |
tk_short | |
tk_long | |
tk_longlong | |
tk_ushort | |
tk_ulong | |
tk_ulonglong | |
tk_float | |
tk_double | |
tk_boolean | |
tk_char | |
tk_wchar | |
tk_octet | |
tk_any | |
tk_TypeCode | |
tk_Principal | |
{tk_objref, IFRId, Name} | {tk_objref, "IDL:M1\\I1:1.0", "I1"} |
{tk_struct, IFRId, Name, [{ElemName, ElemTC}]} | {tk_struct, "IDL:M1\\S1:1.0", "S1", [{"a", tk_long}, {"b", tk_char}]} |
{tk_union, IFRId, Name, DiscrTC, DefaultNr, [{Label, ElemName, ElemTC}]} Note: DefaultNr tells which of tuples in the case list that is default, or -1 if no default |
{tk_union, "IDL:U1:1.0", "U1", tk_long, 1, [{1, "a", tk_long}, {default, "b", tk_char}]} |
{tk_enum, IFRId, Name, [ElemName]} | {tk_enum, "IDL:E1:1.0", "E1", ["a1", "a2"]} |
{tk_string, Length} | {tk_string, 5} |
{tk_wstring, Length} | {tk_wstring, 7} |
{tk_fixed, Digits, Scale} | {tk_fixed, 3, 2} |
{tk_sequence, ElemTC, Length} | {tk_sequence, tk_long, 4} |
{tk_array, ElemTC, Length} | {tk_array, tk_char, 9} |
{tk_alias, IFRId, Name, TC} | {tk_alias, "IDL:T1:1.0", "T1", tk_short} |
{tk_except, IFRId, Name, [{ElemName, ElemTC}]} | {tk_except, "IDL:Exc1:1.0", "Exc1", [{"a", tk_long}, {"b", {tk_string, 0}}]} |