非常教程

Erlang 20参考手册

orber

6. OMG IDL to Erlang Mapping

6.1 OMG IDL到Erlang映射-概述

OMG IDL 接口定义语言映射的目的是作为平台和语言之间的翻译。IDL规范应该描述数据类型,对象类型等。

CORBA独立于用于构造客户端或实现的编程语言。为了使用ORB,程序员必须知道如何从他们的编程语言中访问ORB功能。它将不同的IDL构造转换为特定的编程语言。本章描述OMG IDL构造到Erlang编程语言的映射。

6.2 OMG IDL映射元件

一个完整的语言映射将允许程序员以一种对指定的编程语言很方便的方式访问所有ORB功能。

所有映射都必须定义以下元素:

  • 所有OMG IDL基本类型和构造类型
  • 对OMG IDL中定义的常量的引用
  • 对OMG IDL中定义的对象的引用
  • 调用操作,包括传递参数和接收结果
  • 异常,包括操作引发异常时发生的情况以及异常参数的访问方式。
  • 对属性的访问
  • ORB定义的操作的签名,如动态调用接口、对象适配器等。
  • 范围; OMG IDL有几个级别的范围,它们映射到Erlang的两个范围。

6.3开始

首先,我们应该决定我们需要哪种类型的对象(即服务器),以及如果两个或更多应该导出相同的功能。 让我们假设我们要为不同类型的用户创建一个用于DB(数据库)访问的系统。 例如,任何拥有有效密码的人都可以提取数据,但只有少数人可能会更新数据库。 通常,应用程序是在模块内定义的,所有全局数据类型都是在顶层定义的。 首先我们创建一个模块和我们需要的接口:

// 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    

由于Administrator应该能够像CommonUser一样执行相同的操作,因此前者继承了后者。 Access界面将授予对数据库的访问权限。 现在我们准备好定义我们需要的功能和数据类型。 但是,这需要我们更多地了解OMG IDL。

OMG定义了一组保留的不区分大小写的关键字,它们不能用作标识符(例如模块名称)。有关更多信息,请参阅Reserved Compiler Names and Keywords

6.4 基本OMG IDL类型

OMG IDL映射是强类型的,即使您对CORBA类型有很好的了解,也必须仔细阅读以下映射到Erlang类型。

基本类型的映射是直接的。请注意,OMG IDL double类型映射到不支持完整double值范围的Erlang浮点型。

OMG IDL 类型

Erlang 类型

注意

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

any值被写为包含类型代码表示的字段类型的记录see also the Type Code table,以及值字段本身。

返回类型的函数void将返回原子ok

6.5 模板OMG IDL类型和复杂声明

构造类型都具有本地映射,如下表所示。

类型

IDL 码

映射到

Erlang 码

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 S2; 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数据类型

一个string由除空之外的所有可能的8位量组成。大多数ORB使用包括Orber在内的字符集Latin-1(ISO-8859-1)。该wstring类型表示为整数列表,其中每个整数表示宽字符。在这种情况下,Orber使用UTF-16(ISO-10646-1:1993)字符集作为大多数其他ORB:s。

在定义字符串或wstring时,它们可以是有限长度的,也可以是空终止的:

// Null terminated
typedef string myString;
typedef wstring myWString;
// Maximum length 10 
typedef string<10> myString10;
typedef wstring<10> myWString10;
      

如果我们想定义一个char / string或wchar / wstring常量,我们可以使用八进制(\ OOO - 一个,两个或三个八进制数字),十六进制(\ xHH - 一个或两个十六进制数字)和unicode(\ uHHHH - 一个,两个,三个或四个十六进制数字)。例如:

const string  SwedensBestSoccerTeam = "\101" "\x49" "\u004B";
const wstring SwedensBestHockeyTeam = L"\101\x49\u004B";
const char  aChar  = '\u004B';  
const wchar aWchar = L'\u004C';
      

当然,我们也可以使用“Erlang”,L“Rocks”,“A”和“L'A”。

序列数据类型

序列可以定义为最大长度或无界,并且可以包含基本和模板类型以及作用域名称:

typedef sequence <short, 1> aShortSequence;
typedef sequence <long> aLongSequence;
typedef sequence <aLongSequence> anEvenLongerSequence;
      

数组数据类型

数组是多维的、固定大小的数组.。索引是特定于语言映射的,这就是为什么不应该将它们作为参数传递给另一个ORB。

typedef long myMatrix[2][3];
      

固定数据类型

固定点文字由整数部分(十进制数字),小数点和小数部分(十进制数字)组成,后跟一个 Dd。整数部分或小数部分可能丢失; 小数点可能会丢失,但不是d / D。整数部分必须是小于32的正整数。分数部分必须是小于或等于整数部分的正整数。

const fixed myFixed1 = 3.14D;
const fixed myFixed2 = .14D;
const fixed myFixed3 = 0.14D;
const fixed myFixed4 = 3.D;
const fixed myFixed5 = 3D;
      

也可以使用一元(+ - )和二元(+ - * /)运算符:

const fixed myFixed6 = 3D + 0.14D;
const fixed myFixed7 = -3.14D;
      

上面的固定点例子就是所谓的匿名定义。在后来的CORBA规范中,这些已被弃用为函数参数或返回值。因此,我们强烈建议您不要使用它们。相反,你应该使用:

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
      

有关更多信息,请参阅FixedOrber的参考手册。

现在我们继续处理我们的IDL规范。 首先,我们要限制登录参数的大小(Id和密码)。 由于UserID和Password参数仅在调用Access接口上的操作时使用,因此我们可以选择在接口范围内定义它们。 为了简单起见,我们的数据库将包含员工信息。 因此,作为数据库密钥,我们选择一个整数(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    

但是,例如,该lookup操作应该返回什么?一种选择是使用any数据类型。但是,根据封装的数据类型,这种数据类型的使用可能会相当昂贵。我们可能会在ConstructedIDL类型中找到解决我们问题的方法。

6.6 构造的OMG IDL类型

构造的类型都有本机映射,如下表所示。

类型

IDL 码

映射到

Erlang 码

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可以具有基本,模板,作用域名称和构造类型作为成员。通过使用前向声明,我们可以定义一个递归结构:

struct myStruct; // Forward declaration
typedef sequence<myStruct> myStructSeq;
struct myStruct {
    myStructSeq chain;
};

// Deprecated definition (anonymous) not supported by IC
struct myStruct {
    sequence<myStruct> chain;
};
      

Enum数据类型

在枚举中可以定义的标识符的最大数目是2 3。在枚举规范中命名标识符的顺序定义了标识符的相对顺序。

联合数据类型

一个union可能包括:

  • 标识符
  • 开关-可以是一个整数,字符,布尔,枚举或范围名称。
  • 主体-有或没有default情况;最多一次出现。

案例标签必须与鉴别器的定义类型匹配,并且如果非默认标签中给出的值未涵盖union的鉴别类型的整个范围,则可能只包含默认情况。例如:

// 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; 
};
      

没有必要列出机构中工会歧视者的所有可能值。因此,联合的值是鉴别器的值,并按给定的顺序排列,如下所示之一:

  • 如果判别器匹配CASE语句中显式列出的标签,则该值必须是相同类型的。
  • 如果联合包含默认标签,则该值必须与默认标签的类型匹配。
  • 没有价值。然后Orber插入Erlang原子。undefined从外部ORB接收联合时,在Value字段中。

以上可归纳为:

// 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; 
};
      

以与结构相同的方式,如果使用前向声明(匿名类型已被弃用且不受支持),则联合可以递归:

// Forward declaration
union myUnion;
typedef sequence<myUnion>myUnionSeq;
union myUnion switch (long) {
    case 1 : myUnionSeq chain;
    default: boolean DefaultValue;
};
      

递归类型(union和struct)需要Light IFR。即IC选项{light_ifr,true}被使用,并且Orber被配置为使得轻IFR被激活。目前不支持递归TypeCode,这就是为什么这些不能被封装在任何数据类型中的原因。

警告

例如,结构中的每个字段都必须启动。 否则它将被设置为未定义的原子,当通过IIOP进行通信时,Orber无法编码。 在上例中,使用#'myStruct'{a = 300}调用操作将失败(等于#'myStruct'{a = 300,b = undefined})

现在我们可以继续处理我们的IDL规范。首先,我们应该确定操作的返回值lookup。由于any类型可能相当昂贵,我们可以使用 structunion。如果我们打算每次使用结构时都返回有关员工的相同信息。让我们假设数据库包含名称,地址,员工编号和部门。

// 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    

我们也可以定义每个接口抛出的异常(即不是系统异常)。由于例外在本章中有详细描述System and User Defined Exceptions,我们选择不这样做。因此,我们现在准备通过调用以下代码来编译我们的IDL文件:

$ erlc DB.idl
    

或:

$ erl
Erlang (BEAM) emulator version 5.1.1 [threads:0]

Eshell V5.1.1  (abort with ^G)
1> ic:gen('DB').
ok
2> halt().
    

下一步是实现我们的服务器。但是,要做到这一点,我们需要知道如何访问数据类型定义。例如,由于结构映射到Erlang记录,所以我们必须在回调模块中包含一个hrl文件。

6.7个作用域名称和生成的文件

作用域名称

在范围内,所有标识符必须是唯一的。在OMG IDL中,以下类型的定义构成了作用域:

  • 模块
  • 界面
  • 操作
  • 价值类型
  • 结构
  • 联合
  • 例外

例如,由于枚举不构成作用域,以下IDL代码无效:

module MyModule {
     // 'two' is not unique
     enum MyEnum {one, two};
     enum MyOtherEnum {two, three};
};
      

但是,由于Erlang只有两个级别的作用域,模块函数,所以OMG IDL作用域映射如下:

  • 功能范围-用于常量、操作和属性。
  • Erlang模块范围Erlang模块作用域处理剩余的OMG IDL作用域。

与IDL全局名称相对应的Erlang模块通过将“::”的出现次数转换为下划线并消除前导的“::”来派生。因此,MyEnum从另一个模块访问,一次使用MyModule::MyEnum

例如,在模块M中定义的接口I中定义的foo操作将作为M :: I :: foo和'M_I'写入IDL:Erlang中的foo - foo是函数名称,'M_I' 是Erlang模块的名称。 将这些知识应用于DB.idl的剥离版本,可以:

// 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      

由于上述名称映射,在IDL名称中使用下划线可能会导致含糊不清。建议避免在标识符中使用下划线。例如,以下定义将生成两个名为x_y_z的结构。

module x {

    struct y_z {
    ...
    };

    interface y {

    struct z {
        ...
    };
    };
};
      

生成文件

可以为每个作用域生成几个文件。

  • 为顶级作用域以及Erlang头文件生成一个Erlang源代码文件(.erl)。
  • 将为每个作用域生成一个Erlang头文件(.hrl)。 头文件将包含该范围内所有结构,联合和异常类型的记录定义。
  • 包含至少一个常量定义的模块将生成Erlang源代码文件(.erl)。该Erlang文件将包含该范围的常量函数。不包含常量定义的模块被认为是空的,并且不会为它们生成代码,但仅针对其包含的模块/接口。
  • 接口将产生Erlang源代码文件(.erl),这段代码将包含所有的操作存根代码和实现函数。
  • 除了与范围相关的文件外,还将为每个类型的定义生成一个Erlang源文件structunionexception(这些将在Erlang中作为记录表示)。该文件将包含该记录的特殊访问功能。
  • 顶级作用域将产生两个文件,一个头文件(.hrl)和一个Erlang源文件(.erl)。这些文件被命名为IDL文件,前缀为oe_

编译DB.idl之后,生成了以下文件:

  • oe_DB.hrloe_DB.erl顶级作用域级别。
  • DB.hrl模块DB...
  • DB_Access.hrlDB_Access.erl用于接口DB_Access...
  • DB_CommonUser.hrlDB_CommonUser.erl用于接口DB_CommonUser...
  • DB_Administrator.hrlDB_Administrator.erl用于接口DB_Administrator...
  • DB_employee.erl为结构employee在模块中DB...

由于employee结构是在顶层作用域中定义的,Erlang记录定义可以在中找到DB.hrl。IC还DB_CommonUser.erl为某些数据类型(例如DB_employee.erl)生成存根/骨架(例如)和访问函数。如何使用存根/骨架,Stubs/Skeletons并在中详细描述Module_Interface

6.8类型、标识和名称访问函数

正如前面的部分所述,struct,union和exception类型产生记录的记录定义和访问代码。 对于结构,联合,异常,数组和序列类型,将生成一个特殊文件,该文件包含TypeCode,Identity和Name的访问函数。 这些函数被放在与它们被定义的范围相对应的文件中。 例如,表示员工结构的模块DB_employee.erl会导出以下函数:

  • TC/0-返回结构的类型代码。
  • id / 0 - 返回结构的IFR标识。 在这种情况下,返回值为“IDL:DB / employee:1.0”,但如果该结构在CommonUser的范围内定义,则结果为“IDL:DB / CommonUser / employee:1.0”。 但是,用户通常不需要知道Id,只要哪个Erlang模块包含正确的Id。
  • 名称/ 0 - 返回结构体的作用域名称。该employee结构的名称是"DB_employee"

例如,在Any价值。因此,我们可以封装employee结构中的any按下列类别键入:

%% 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),
....
    

有关更多信息,请参见Type Code listing...

6.9对常量的引用

常量以Erlang函数的形式生成,并通过单个函数调用进行访问。函数被放入与定义它们的作用域相对应的文件中。不需要启动对象来访问常量。

例子:

// m.idl
module m {
    const float pi = 3.14;

    interface i {
    const float pi = 3.1415;
    };
};
    

由于这两个常量是在不同的作用域中定义的,所以上面的IDL代码是有效的,但不一定是一个好方法。编译后m.idl,可以通过调用以下命令提取常量定义:

$ 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().
    

对OMGIDL中定义的对象的6.10引用

对象由对象引用访问。对象引用是ORB创建和维护的不透明Erlang术语。

对象通过提供Object的所有操作和属性的实现来实现see operation implementation

6.11例外

异常作为Erlang捕获和抛出处理。异常被转换为IIOP桥上的消息,但转换回接收端的抛出。调用其他对象上的操作的对象实现必须知道非本地返回的可能性。这包括调用ORB和IFR服务。另见Exceptions部分。

异常参数映射为Erlang记录,并按此方式访问。

引发异常的对象实现将使用corba:raise/1函数,将异常记录作为参数传递。

6.12对属性的访问

属性通过其访问函数进行访问。属性隐式定义_get_set行动。这些操作的处理方式与正常操作相同。大_get操作定义为readonly属性。

readonly attribute long RAttribute;
attribute long RWAttribute;
    

RAttribute要求您实现您的回拨模块中,_get_RAttribute。对于RWAttribute这是必要的实施_get_RWAttribute_set_RWAttribute

6.13 调用操作

标准的Erlang gen_server行为用于对象实现。所述gen_server然后状态被用作对象的内部状态。对象函数的实现是通过实现其方法和属性操作来实现的。这些功能通常将具有内部状态作为第一个参数,随后任何ininout参数。

不要将对象内部状态与其对象引用混淆。对象内部状态是一个Erlang术语,它具有用户定义的格式。

内部状态并非总是第一个参数,因为存根可以使用自己的对象引用作为第一个参数(请参阅IC文档)。

函数调用将调用一个操作。 函数的第一个参数应该是对象引用,然后所有in和inout参数的顺序与IDL规范中指定的顺序相同。 除非函数指定了inout或out参数,否则结果将是返回值; 在这种情况下,将返回一个返回值的元组,随后的参数。

例子:

// 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);
  };
};
    

在Erlang中用作:

%% 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"),
....
    

请注意如何传递并返回inout参数。 在Erlang中没有办法为此使用单个变量。 还要注意的是,Orber的IDL类型void的表示必须由baz和'_set_RWAttribute'返回。 这些操作可以在回调模块中实现为:

'_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}.
    

操作可能需要更多参数(取决于所使用的IC选项)。有关更多信息,请参阅Stubs/SkeletonsModule_Interface

警告

函数也可以定义为oneway,即异步。但是,由于单向操作的行为没有在OMG规范中定义(即,行为可能会因ORB Orber与其他ORB进行通信而有所不同),因此应避免使用它。

6.14 实施数据库应用程序

现在我们准备实施回调模块。我们必须创建三个模块:

  • DB_Access_impl.erl

  • DB_CommonUser_impl.erl

  • DB_Administrator_impl.erl

一个简单的方法就是使用IC后端erl_template,它将生成一个完整的回叫模块。还应该添加相同的编译选项,例如,thisfrom在生成存根/骨架模块时使用:

$> erlc +"{be,erl_template}" DB.idl
    

我们从实施DB_Access_impl.erl模块开始,如果我们使用了模块,它将erl_template如下所示。我们所需要做的就是将逻辑添加到logon操作中。

%%----------------------------------------------------------------------
%% <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) ->
    %% 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}.
    

由于DB_Administrator继承自DB_CommonUser,我们必须在DB_Administrator_impl.erl模块中实现删除,并在DB_Administrator_impl.erlandDB_CommonUser_impl.erl中进行查找。 但是等等,这真的有必要吗? 其实并非如此。 我们简单地使用IC编译选项impl:

$ erlc +'{{impl, "DB::CommonUser"}, "DBUser_impl"}'\ +'{{impl, "DB::Administrator"}, "DBUser_impl"}' DB.idl
$ erlc *.erl
    

我们只需要处理两个回调模块,而不是创建,而不是最不重要的是维护两个回调模块。DBUser_impl.erl如果生成模板,只需重命名DB_Administrator_impl.erlDBUser_impl.erl.亦见Exceptions章节。在下面的示例中,只显示了API函数的实现:

%%======================================================================
%% 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.
    

编译完这两个回调模块并实现缺少的功能(例如lookup_employee / 1)之后,我们可以测试我们的应用程序:

%% 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保留编译器名称和关键字

由于含糊不清,强烈建议不要使用某些名称。但是,使用Erlang映射时,禁止使用某些名称,因为它们严格保留用于IC。

IC保留所有标识符,从OE_oe_供内部使用。

还请注意,IDL中的标识符可以包含字母、数字和下划线字符,但第一个字符是按字母顺序。

OMG定义了一组保留字,如下所示,用作关键字。这些可能不能用作例如标识符。OMG CORBA-3.0规范中引入了非粗体的关键字。

抽象

例外

inout

提供

截去

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

上面列出的关键字必须完全按照所示写入。任何使用与关键字冲突的标识符都是非法的。例如,long是一个有效的关键字; 是非法的关键字和标识符。但是,由于OMG必须能够扩展IDL语法,因此可以使用转义标识符。例如,native在IDL规范中用作标识符的可能性不大。一种选择是将所有事件更改为myNative。通常,有必要改变依赖于IDL的编程语言代码。由于转义标识符只是禁用类型检查(即,如果它是一个保留字),并保持其他所有内容不变,只需要更新IDL规范。为了逃避标识符,只需在_前加上前缀。以下IDL代码是非法的:

typedef string native;
interface i {
   void foo(in native Arg);
   };
};
    

使用转义标识符,代码将如下所示:

typedef string _native;
interface i {
   void foo(in _native Arg);
   };
};
    

6.16型代码表示

类型代码用于any值。为了避免错误,您应该使用数据类型模块(例如struct,union等)或orber_tc模块导出的访问函数。

类型代码

例子

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}}

DefaultNr,{Label,ElemName,ElemTC}}注意:DefaultNr告诉案例列表中的哪些元组是默认值,如果没有默认值,则为-1

{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}}}

Erlang 20

Erlang 是一种通用的面向并发的编程语言,可应付大规模开发活动的程序设计语言和运行环境。

主页 https://www.erlang.org/
源码 https://github.com/erlang/otp
版本 20
发布版本 20.1