2005/5/10

     
 

DMI Standard Client Interface to Objects

artefaktur

| DMI Basics | Features Profile | DMI Client Interface | ScriptVar | DMI Server | Using DMI in C++ | DMI with CfgScript | DMI Server Objects | Subclassing | Delegates |


ACDK provides a standard interface to Objects constructors, methods and member variables.


Content of this chapter:

   The standard Client DMI interface
     New
     invoke
     invoke_static
     peek
     peek_static
     poke
     poke_static
   Extended Client DMI interfaces
     Polymorphic functions
     Enumerations
     Default arguments
     Named Parameters
     Any rest Parameter
     Any Named Rest Parameter
     IN, OUT, INOUT
       IN parameter
       OUT parameter
     BYVALIN, BYVALOUT, BYVALINOUT, BYREFIN, BYREFOUT, BYREFINOUT
   Dispatching method over DMI
     Methods overloading
     Multimethods
       Casting
     Dispatching for DMI bridges
     Operator calls



 The standard Client DMI interface

The usage of the DMI interface depends on the language mapping.
There are different language mappings for the different client languages like ACDK C++, Lisp, Perl, Tcl, Python, etc.

Beside the different language mappings there is a common abstract interface with methods like New, invoke, invoke_static, peek, peek_static, poke and poke_static.

 New


Object New(ClassName, Params)
Creates a new instance of a class.
The New maps directly the the public constructor of the ACDK class.

  • Object is the new created object instance.
  • ClassName is the name of the class like "acdk/lang/StringBuffer".
  • Params are optional params given to the constructor.

 invoke


RetVal invoke(Object, MethodName, Params)
Call a public, non-static (virtual or non-virtual) method of the given object.
  • RetVal return value of the call.
    In case the method returns no value, a VOID replacement value will be returned.
  • Object is a instance of a ACDK object.
  • MethodName is the name of the method to call. F.e. "append".
  • Params are optional params given to the method.

 invoke_static


RetVal invoke_static(ClassName, MethodName, Params)
Call a public, static method of the given object.
  • RetVal return value of the call.
    In case the method returns no value, a VOID replacement value will be returned.
  • ClassName is the name of the class like "acdk/lang/Integer".
  • MethodName is the name of the method to call. F.e. "toHexString".
  • Params are optional params given to the method.

 peek


RetVal peek(Object, MemberName)
Reads a public, non-static member of the given object.
  • RetVal the member variable.
  • Object is a instance of a ACDK object.
  • MemberName is the name of the member.

 peek_static


RetVal peek_static(ClassName, MemberName)
Reads a public, static member of the given class.
  • RetVal the member variable.
  • ClassName is the name of the class like "acdk/lang/System".
  • MemberName is the name of the member like "out".

 poke


void poke(Object, MemberName, NewVal)
Writes a public, non-static member of the given object.
  • Object is a instance of a ACDK object.
  • MemberName is the name of the member.
  • New is the new member variable.

 poke_static


void poke_static(Object, MemberName, NewVal)
Writes a public, static member of the given class.
  • ClassName is the name of the class like "acdk/lang/System".
  • MemberName is the name of the member.
  • New is the new member variable.

 Extended Client DMI interfaces

For other DMI specific features, the language mappings differs, resp. some features are not supported in some language mappings.

 Polymorphic functions

ACDK supports polymorphic function via DMI.
The standard DMI uses dynamic overloading.
Not the declared type of an argument will be called but the real type.

class TestClass
: extends acdk::lang::Object
{
  ACDK_WITH_METAINFO(TestClass)
public:
  void foo(int i);
  void foo(IN(RObject) obj);
  void foo(IN(RInteger) obj);
};
RObject testclass = new TestClass();
testclass->invoke("foo", inOf(42)); // invokes void foo(int i);
RObject obj = new String("");
testclass->invoke("foo", inOf(obj)); // invokes void foo(IN(RObject) obj);
obj = new Integer(42);
testclass->invoke("foo", inOf(obj)); // invokes void foo(IN(RInteger) obj);
                                     // and not void foo(IN(RObject) obj);

 Enumerations

C++ enumeration as arguments or return type are mapped to ints.
Although possible in C++ a function with same name and different enum types or an integer is not possible in ACDK DMI.

The argument of an enumeration can be passed as int or as String with valid enumeration value name. The String contains either the enumeration value string, or with leading namespace to avoid ambiguities. The return value or out parameter are always return as integer.


The metacompiler can only handle enumeration, which are declared in the same header of the class. If the enumeration is defined in another header file, the enumeration has to be declared before used as parameter (see OtherEnum below).

Inside the DMI dispatching implementation unknown classes are tried to be loaded via classloader. This is not the case for unknown enumeration and enumeration values.



enum TestEnum
{
  TestEnumVal1 = 42,
  TestEnumVal2 = 43
};
ACDK_DEF_ENUM(TestEnum);

// defined in other header
enum OtherEnum;

class TestClass
: extends acdk::lang::Object
{
  ACDK_WITH_METAINFO(TestClass)
public:
  void foo(TestEnum en);
  foreign foo(int i); // not dmi-able, conflicts with first foo
  void bar(OtherEnum en);
};

RObject testclass = new TestClass();
testclass->invoke("foo", inOf(42));  // OK
RString enval = "TestEnumVal2";
testclass->invoke("foo", inOf(enval)); // also OK

If you define enumeration in shared libraries you have to use instead of the ACDK_DEF_ENUM macro the ACDK_DEF_LIB_ENUM macro.

Please refer also to  Enum Types.

 Default arguments

Default arguments are supported on DMI server side.
Given ACDK class:

class TestClass
: extends ::acdk::lang::Object
{
  ACDK_WITH_METAINFO(TestClass)
public:
  void foo(IN(RObject) obj, int count = -1);
}
two DMI entry points will be generated:

void foo(IN(RObject) obj);
void foo(IN(RObject) obj, int count);

 Named Parameters


The DMI client implementation must support Named Parameters.
  • ACDK DMI C++ client.
  • ACDK CfgScript.


  // C++/CfgScript
  void callIt(IN(RString) str, int ival);

  // CfgScript
  obj->callIt(ival: 42, str: "asdf");
  obj->invoke("callIt", NamedArgs(NamedArg(ival, 42) << NamedArg(str, inOf("asdf"))));


  // C++/CfgScript
  void callIt(IN(RString) str = "", int ival = 42);

  // CfgScript
  obj->callIt(ival: 1);
  // C++
  obj->invoke("callIt", NamedArgs(NamedArg(ival, 42)));

 Any rest Parameter

The Any rest parameter is similar to the eclipse ... in C/C++.
It must be the last parameter in the method declaration.
All passed arguments, which are not consumed by previous parameters are collected by the rest parameter.



  // C++
  void callIt(IN(RString) str, IN(acdk::lang::dmi::RDmiObjectArray) rest);
  // CfgScript
  void callIt(String str, Rest rest);
  
  // C++
  obj->invoke("callIt", "first", inOf("second"), inOf(42));
  
  // CfgScript
  obj.callIt("first", "second", 42);
    

The dispatching routine try to find a method with many as possible non-rest parameters:

// CfgScript
class MyClass {
  static void callRest(Rest rest); // first method
  static void callRest(float r, Rest rest); // second method
}
MyClass.callRest(42, "asdf", 43.3); // this calls the second method, 
                                    // because it can bind the int to the
                                    // float r variable
                                   
MyClass.callRest("asdf", "asdf", 43.3); // this calls the first method
#pragma cast f2i2f false
MyClass.callRest(42, "asdf", 43.3); 

 Any Named Rest Parameter

Named parameter are similar to the Rest Parameter, but collects the named arguments.



  // C++
  void callIt(IN(RString) str, IN(acdk::lang::dmi::RDmiNamedArgArray) rest);
  // CfgScript
  void callIt(String str, NamedRest rest);
  
  // C++
  obj->invoke("callIt", "first", inOf("second"), inOf(42));
  
  // CfgScript
  obj.callIt("first", "second", 42);
    



 IN, OUT, INOUT

IN, OUT and INOUT are argument attributes, which declarares the direction of the argument passing.
  • IN parameter are passed from caller to callee.
    This is also the default, if the argument has no attribute.
  • OUT parameter are passed from callee to caller.
  • INOUT parameter are passed from caller to callee and back.

 IN parameter

As said before parameter without any directional attributes, like IN, OUT or INOUT are handled like IN parameter. This is not the whole story.

  void foo(RStringBuffer sb, int i)
  {
    // this assigns a new instance
    sb = new StringBuffer("asdf");
    // this is legal, but the caller doesn't
    // recognise, that sb had changed
    i = 42; // this is OK, but caller
            // will not be effected
  }
  void foo(IN(RStringBuffer) sb, IN(int) i)
  {
    // although sb is in, you can call function
    // which may modify the internal value of the reference
    sb->append("asdf"); 
    // but this is illegal in C++ and you will receive a compiler error
    // in CfgScript this is legal and works the same way like the paramater
    // without any in/out attribute
    sb = new StringBuffer("asdf");
  }
In the ACDK standard libraries most parameter with an object reference are declared as IN parameter. The main reason is simply performance, because the passed argument has not be copied.

 OUT parameter

To support OUT and INOUT the client language mapping must support references. With OUT parameters it is possible to pass multiple return value back to the caller.


  // C++
  bool parseFileAndLine(IN(RString) line, OUT(RString) fname, OUT(int) lineNo)
  {
    int idx = line->lastIndexOf(':');
    if (idx == -1)
      return false;
    fname = line->substr(0, idx);
    lineNo = Integer::parseInt(line->substr(idx + 1));
    return true;
  }
  
  // CfgScript
  bool parseFileAndLine(IN(RString) line, OUT(RString) fname, OUT(int) lineNo);
  // or
  bool parseFileAndLine(in String line, out String fname, out int lineNo);

 BYVALIN, BYVALOUT, BYVALINOUT, BYREFIN, BYREFOUT, BYREFINOUT

The BYVAL and BYREF attributes are used by DMI remoting interfaces to specify how the parameter should be tranfered. In which case a argument is passed by default is specific to the particular remote DMI implementation.

The BYVAL and BYREF attributes has no effect on lokal invokations.

 Dispatching method over DMI


 Methods overloading

As you seen implicit in the samples, DMI supports class with multiple methods with the same method name but different argument number and/or types.


  // CfgScript / C++
class AClass
{
  void foo(int i);
  void foo(int i, int x);
  void foo(IN(RString) s);
  void foo(IN(RObject) s);
  void foo(double d);
  void foo(float f, bool isFlaot = true);
}
class BClass 
: extends AClass
{
  void foo(short s);
};

RAClass cls = new BClass();
// CfgScript
float f = 42.5;
cls.foo(f);
// same in C++
cls->invoke("foo", inOf(f));

The thrilling question: which foo will be called?
The short answer: It depends on the implementation of the DMI server.
ACDK provides a standard implementation (can be found in  StdDispatch.cpp) which is used in the C++, CfgScript and RDMI ( Remote DMI) implementation.

In the standard implementation void foo(float f, bool isFlaot = true) will be called.

The standard implementation inspect all methods with theire parameter types - the return type will be ignored - and try to find the best matching method signature.

Therefore all arguments types will be compared with the parameter types and a type distance will be calculated (exact match has the type distance 0).
The method with the least type distance for all methods will be selected.

Methods with default parameter will be split into multiple methods:

  // C++/CfgScript
  void foo(float f, bool isFlaot = true);
  
  // DMI view:
  void foo(float f);
  void foo(float f, bool isFlaot);

 Multimethods


In C++ the selection proper version of foo (see above) will be steered by the declared types:

  // C++
 RObject obj = new String("asdf");
 cls->foo(obj);
will call void foo(IN(RObject) s);.

Calling the method via dmi the sitation is different:

  // C++
  RObject obj = new String("asdf");
 cls->invoke("foo", inOf(obj));
 
 // CfgScript
 cls->foo(obj);
will call void foo(IN(RString) s);, because the method will be selected at runtime using the real type of the argument and not the declared. This mechanism is called double dispatching or multimethods because method will not only selected in virtual calls by the real type of the object (cls in our example), but only by the real type of the objects passed as arguments.

 Casting

The central aspect of selecting the proper method is the type distance between argument values and parameter declarations.
Type distance is just a more detailed description of the casting capilities between types. The casting features of a DMI server can be controled by a implementation of the  acdk::lang::dmi::DmiClient interface.

In CfgScript there are  #pragm cast statements to control casts, and type distance between types, so it can be controled if bool type can be cast to int types and vice versa, cast character types to integer types, cast basic types to object types (autoboxing) and vice versa, and even cast String types to basic types try do decode "12.3" to 12.3 (like Perl or Tcl).

 Dispatching for DMI bridges

Some DMI Server objects - CORBA or COM components - doesn't support overloaded methods. In case of native COM or CORBA the question simply doesn't exists.
In the case COM or CORBA is used as a bridge to serve for example ACDK C++ classes as COM objects, a namemangling has to be introduce to address a concrete foo-version.


 Operator calls

ACDK supports also operator methods via DMI:


// C++
class StringBuffer
{
  // ...
  RStringBuffer operator<<(IN(RString) str);
};

// CfgScript
class Average
{
  // ...
  Average operator+(int value);
}

The operator are exported via namemangling.
Calls an operator function via DMI:

  // C++
  Object sb = new StringBuffer();
  sb->invoke("operator_pl", 42);
  
  // CfgScript
  Average average = new Average();
  average.operator_pl(42);