From: senderista@... (Tobin Baker) Date: 2001-08-01T07:16:24+09:00 Subject: [ruby-talk:18919] README for Dynamic-ORBit If anyone wants to comment on my IDL mapping for Ruby, here's the README: Author/maintainer: Tobin Baker Email: senderista@hotmail.com This library is licensed under the GNU Library General Public License. See COPYING for details. This is an alpha release of ORBit bindings for Ruby. Most of the code here has been stolen directly from Owen Taylor's excellent CORBA::ORBit module for Perl. Some other features (such as automatic loading of IDL files) have been borrowed from Jason Tackaberry's ORBit-Python module. A feature that this library shares with these two modules is dynamic loading of IDL files. No intermediate IDL compilation step is necessary! You either specify the location of an IDL file directly with CORBA._load_idl or simply require the top-level module from that file, and the library will search all IDL files in the current directory, several hard-coded paths, or the IDLPATH environment variable for an IDL file containing that module. The library parses the IDL file and dynamically creates all Ruby objects corresponding to the IDL definitions in the file. In this release, only client-side bindings are available. Most of the server-side code has been written and is currently being debugged. I hope to release it in just a few weeks. The IDL-Ruby mapping in this release is highly experimental. Some features are rather "baroque", and may well be withdrawn in the future, depending on public feedback. I should also say that I have gotten a few of the ideas for the IDL-Ruby mapping from Dai, who began work on ORBit bindings for Ruby long before I did. Our approaches are rather different, but I hope that by discussions on the newsgroups and mailing lists, we can eventually arrive at a standardized CORBA mapping for Ruby that will be as portable as Ruby itself. Please email me with any bug reports or suggestions. I hope CORBA bindings bring us one step closer to world domination! (Sorry, must have been hanging around Linux zealots too long :-) The IDL-Ruby Mapping 1. Modules IDL modules map directly to Ruby modules. The fact that Ruby has true nested modules makes for an especially natural mapping from IDL to Ruby. E.g., //IDL module Foo { module Bar { }; }; #Ruby module Foo module Bar end end It should be noted that since Ruby module names must begin with capital letters, using IDL files with lowercase module names could be a problem. I especially have in mind IDL written for use with Java, where package names (which map to IDL modules) are conventionally in lowercase. I have done nothing to address this problem in this release, but some kind of name mangling may be necessary for compatibility with existing IDL. To me, this illustrates the kind of unintended consequences that can arise from a well-meaning attempt to turn conventions into rules. 2. Interfaces IDL interfaces are also mapped to Ruby modules. Each Ruby module corresponding to an IDL interface includes the base module CORBA::Object, which declares all the methods found in the IDL CORBA::Object interface, prefixed with an underscore, along with an additional method, repo_id, which returns the interface's Repository ID. When an IDL interface definition is encountered by the parser, a Ruby module corresponding to the interface is created and each attribute and operation defined in the interface is defined as an instance method in that module. The Ruby modules corresponding to base interfaces in the IDL definition are included in the new module. At runtime, an object reference entering the ORB is demarshaled into an instance of CORBA::ORBit::Stub, which wraps ORBit's native object reference type, and extended with the module corresponding to the object reference's interface. While interfaces are never directly instantiated by the client, what happens looks something like this: //IDL module Foo { module Bar { interface Baz { void do_it(in long dummy); }; }; }; #Ruby module Foo module Bar module Baz def do_it(dummy) end end end end myBaz = CORBA::ORBit::Stub.new() myBaz.extend(Foo::Bar::Baz) In the server-side mapping, a Ruby module with the suffix "_POA" is created for each interface definition. Any implementation class for that interface must include this module. E.g., assuming the same IDL as the last example: #Ruby module Foo module Bar module Baz_POA def do_it(dummy) end end end end class Baz include Foo::Bar::Baz_POA def do_it(dummy) #method implementation end end 3. Operations IDL operations are mapped to Ruby method definitions. As a rule, in arguments are passed as ordinary parameters, return values and out arguments are returned as a single array, and inout arguments are passed in like in parameters and returned like out parameters, with no "pass-by-reference" semantics involved. E.g., //IDL interface Foo { long do_it(in long in_arg, out long out_arg, inout long inout_arg); }; #Ruby #somehow get reference myFoo to Foo object ret_val, out_arg, inout_arg_ret = myFoo.do_it(in_arg, inout_arg_par) This treatment of parameters coincides with the Python Mapping Specification. This version, however, contains certain experimental features which attempt to emulate the "call-by-reference" semantics implicit in the IDL concepts of out and inout arguments. Specifically, if a block is supplied with a CORBA operation invocation, the block will be executed with all block parameters set to the values of corresponding out arguments returned by the invocation. Since Ruby binds block parameters to any local variables with the same name defined in the enclosing scope of the block, this can be used to simulate "call-by-reference". E.g., //IDL interface Foo { void do_it(out long arg1, out long arg2, out long arg3); }; //implementation sets arg1 = 1, arg2 = 2, arg3 = 3 #Ruby #somehow get reference myFoo to Foo object arg1, arg2, arg3 = nil do_it() {|arg1, arg2, arg3|} p arg1, arg2, arg3 #prints 1, 2, 3 Another feature emulates "call-by-reference" for inout parameters. If a parameter to an operation invocation is a Ruby symbol, it is interpreted as an inout argument, provided the word "inout" is supplied as the last parameter to the invocation: //IDL interface Foo { void do_it(inout inout_arg); }; //implementation adds 1 to inout_arg #Ruby #somehow get reference myFoo to Foo object inout_arg = 10 do_it(:inout_arg, inout) p inout_arg #prints 11 It could certainly be argued that these features are more confusing than useful. I wanted to include them in this first release, however, to see if anyone preferred operation semantics that more closely emulate those implicit in IDL. 4. Attributes IDL attributes are mapped to Ruby methods of the same name. For ordinary attributes, a "getter/setter" pair of methods is created; for readonly methods, only a "getter" method is created. These follow the ordinary Ruby naming conventions for attributes, e.g., //IDL interface Foo { attribute bar; readonly attribute baz; }; #Ruby module Foo def bar; end def bar=(val); end def baz; end end In the server-side mapping, attr_accessor or attr_reader methods will be defined in the skeleton module corresponding to an interface: #Ruby module Foo_POA attr_accessor :bar attr_reader :baz end 5. Constants For at least two reasons, IDL constants are *not* mapped to Ruby constants. In the first place, as we all know, Ruby "constants" are not really constant. This is at variance with the "immutable" semantics implicit in IDL. In the second place, Ruby constants must begin with a capital letter. This is clearly an unreasonable requirement to place on CORBA implementors who write their IDL with other languages in mind. Therefore I implement constants as methods returning the value of the constant. E.g., //IDL module Foo { const float pi = 3.14159265; }; #Ruby module Foo def pi 3.14159265 end end 6. Exceptions Exceptions derive from CORBA::UserException (which derives from CORBA::Exception, which derives from StandardError). For each attribute in the IDL definition, a matching parameter is added to the initialize method for the corresponding Ruby class and an attr_reader is defined with the same name. E.g., //IDL interface Foo { exception SystemFailure { short err_code; string desc; }; }; #Ruby module Foo class SystemFailure < CORBA::UserException attr_reader :err_code, :desc def initialize(err_code, desc) @err_code = err_code @desc = desc end end end 7. Structs Structs derive, naturally enough, from the standard Ruby Struct class. For each IDL struct definition, an anonymous Ruby struct class is created and then installed as a constant in the appropriate module. E.g., //IDL module Foo { struct Person { short id; string first_name; string last_name; string address; }; }; #Ruby module Foo end struct_class = Struct.new(nil, :id, :first_name, :last_name, :address) Foo.const_set(:Person, struct_class) 8. Unions Unions are a bit more complicated. They derive from a special CORBA::Union class, whose definition is: module CORBA class Union def initialize(discriminator=nil, value=nil) @discriminator = discriminator @value = value end def _d @discriminator end def _v @value end end end Each subclass of Union has methods corresponding to each attribute in the IDL definition, which either fetch the value provided the discriminator is set to the appropriate value, or set the value to the argument supplied and set the discriminator to the appropriate value for the attribute. The discriminator cannot be set at all except at initialization. E.g., //IDL union Foo (switch long) { case 0: short a; case 1: float b; case 2: string c; case 3: boolean d; }; #Ruby class Foo < CORBA::Union def a @value if @discriminator = 0 end def a=(value) @value = value @discriminator = 0 end def b @value if @discriminator = 1 end def b=(value) @value = value @discriminator = 1 end def c @value if @discriminator = 2 end def c=(value) @value = value @discriminator = 2 end def d @value if @discriminator = 3 end def d=(value) @value = value @discriminator = 3 end end No type checking is done when attributes are assigned to; type checking is currently reserved for marshaling. This may change in the future, but seems in accord with Ruby's weakly typed nature. 9. Sequences Sequences are mapped to Ruby arrays, except in the case of octet or character sequences, which are mapped to strings for efficiency. Bounded sequences are currently not checked for overflow at assignment, but this could change. Bounds checking currently only occurs at marshaling. 10. Arrays Arrays are mapped to Ruby arrays, except in the case of octet or character arrays, which are mapped to strings for efficiency. They are currently not checked for overflow at assignment, but this could change. Bounds checking currently only occurs at marshaling. 11. Enumerations IDL enumerations are mapped to two entities in Ruby; first each constant in the enumeration is mapped just as an ordinary constant would be if it were defined in the same IDL module, with consecutively increasing integral values starting from 0; then an array is created in the Ruby module corresponding to the IDL module, with indices matching the constant values and string values matching the name of the constant. E.g., //IDL module Foo { enum Color { red, green, blue }; }; #Ruby module Foo def red; 0; end def green; 1; end def blue; 2; end Color = ["red", "green", "blue"] end It will be noticed again that the name of an enumeration must begin with a capital letter, since it is defined as a constant in the enclosing module. This remains to be resolved by some kind of name mangling mechanism. 12. Typedefs Typedefs are "unwound" to a definition composed of basic types or non-typedef user-defined types. They never should appear in Ruby code. 13. Any IDL Any values are mapped to objects of class CORBA::Any. They have two members, a TypeCode and a value, which are available as read-only attributes. Any objects delegate all method calls to the enclosed value, which will raise a TypeError if it does not respond to the method invoked. Any objects may be generated by the client by instantiating a TypeCode appropriate for the value to be inserted, and then calling CORBA::Any.new with the TypeCode and the value as arguments. Type checking for Any currently occurs only at marshaling. 14. TypeCodes IDL TypeCodes are mapped to a simple Ruby "wrapper" class for the ORBit native type. They may be instantiated from Ruby by calling CORBA::TypeCode.new with the Repository ID of the TypeCode as an argument. Currently none of the operations in the OMG CORBA::TypeCode interface are available in Ruby, but this may change if there seems to be reason to do so (for instance querying the TypeCode member of an Any value for various attributes). TypeCode constants corresponding to all the TypeCodes for basic types are defined in the class CORBA::TypeCode, with the same names as in the C mapping. 15. The Client-Side Mapping for the ORB Pseudo-Object The ORB pseudo-object is instantiated by calling CORBA::ORB.init with any legal arguments defined in the CORBA specification or by ORBit itself. All the operations defined in the OMG CORBA::ORB interface are available from Ruby, except for the various TypeCode creation operations. Examples Examples are available in the files "test-suite.idl", "test-server.py", and "test-client.rb". Since server-side bindings are not yet complete, you will have to install ORBit-Python to run this test suite (which is stolen directly from that project, due to lack of time and imagination on my part). If there is popular demand, I may also include a server-side script for Perl (CORBA::ORBit) as well.