C++11 mapping for IDL interfaces

The proposal is to map IDL interfaces to C++ classes. The user programmer will only see an object reference and not the generated stub or skeleton classes. Also the _ptr/_var types will be not part of this new C++0x mapping.

The nill object reference is not representated anymore by Foo::_nil(), but just with nullptr.

If we have an interface Hello we generate a Hello object reference. This object reference can be checked for nil by comparing it with nullptr (or using a bool conversion operator). The user can call operations on the object reference using the -> operator which can check for a nil pointer and throw an exception if that is the case.

An object reference can be narrowed to a derived type using the type::narrow  method. The user code could than be written as:

  CORBA::ORB orb = CORBA::ORB_init (argc, argv);
  CORBA::Object tmp = orb->string_to_object(ior);

  Demo::Hello hello =
    Demo::Hello::narrow (tmp);

  if (!hello)
    {
      std::cerr << "Nil reference "
<< ior << std::endl;
    }

  std::string the_string = hello->get_string
();

The implementation of the servant for Hello could look like:

  class Hello_servant
    : public virtual Hello::servant_base_type
  {
  public:
    /// Constructor
    Hello_servant (CORBA::ORB orb) : orb_ (orb) {};

    virtual std::string get_string (void);
  private:
    CORBA::ORB orb_;
  };

 

Your rating: None Average: 3 (2 votes)

object reference mapping

We found that there are cases when it is impossible to map the IDL interface Hello to Hello in C++11. We need a special type for the object reference which than support a possible forward declared of Hello. For example

interface Hello;
interface A {
  Hello get_hello();
};

Is a construct that not works when Hello in IDL is mapped to Hello in C++11. We propose to introduce a CORBA::object_reference<T> that now gets used. This template must behave as a std::shared_ptr<>. The operation get_hello() than maps to

CORBA::object_reference<Hello> get_hello();

 

 

 

 

Use traits

While currently CORBA is the only middleware that supports the interface sections of IDL 3.5 (soon 4.0), there is currently an RFP at the OMG for DDS interfaces/RMI.

In order to support this use case, I believe CORBA should not show up directly in user code if at all possible. The object reference types should only be accessed through traits (either internal or external).

For example, for the mapping of the get_hello() method you mentioned, it could be:


// Internal trait
Hello::_ref_type get_hello();

and/or


// External traits
IDL::Object_Traits<Hello>::ref_type get_hello();

This would allow a DDS interface to be used as transparently as possible.

One question is: which is the best to use? For that, I think the external trait should be used in generated infrastructure code, while the internal trait would be easier for developers in their application code.

Trent Nadeau
Northrop Grumman Corp.

Traits

It is true that IDL is used for more than CORBA. In our revised submission we refer to the IDL as part of CORBA v3.2, that is a formal spec. We can imagine that CORBA and DDS map interfaces in different ways, both mapping them to IDL::Object_Traits doesn't seem a good thing at that moment. Maybe have than CORBA::object_traits and DDS::object_traits?

Which trait to use, that is something that depends on preference. With the external trait it is pretty clear which product you are using.

Btw, I can't find a RFP for DDS RMI, seems that is not on the formal DDS roadmap.

Middleware independent IDL2C++11 mapping

I agree that a new IDL2C++11 standard should do everything possible to avoid explicit use of the namespace for any specific middleware technology, and stick to either its own ("IDL") or a baseline C++11 solution for everything.

Aside from the draft DDS RMI presentation a couple of meetings ago at the OMG, the new IDL 3.5 spec was also recently brought into existence by extracting the IDL specification information _from_ the CORBA spec to break that dependency, from the DDS4CCM spec (IDL3+ extensions), and supposedly soon from the DDS XTypes draft spec for the next rewritten IDL 4.0 revision.

This has all been done with the intent of making IDL a more flexible standalone specification without any specific middleware dependencies. It should obviously continue to support the specification of CORBA-based interfaces as it always has, however it should also be forward looking enough to support DDS or some other TBD middleware standard. As such, I would think that any new language mapping would ideally want to evolve using these same middleware independent guidelines.

M. Hayman
Northrop Grumman

Traits

The DDS RMI RFP is still a draft and hasn't been officially issued. The OMG document number is dds/11-09-03.

It seems like you could still use IDL::Object_Traits, since the traits will be typedefs to the appropriate CORBA or DDS types. In addition, I don't think CORBA and DDS versions of the same interface could coexist in the same translation unit due to naming collisions.

Trent Nadeau
Northrop Grumman Corp.

invocations on nil

The specification now also describes that an invocation on a nil object reference should result in a std::logic_error

Use of std exceptions

Shouldn't any exceptions specified by the spec by CORBA exceptions? That way, if server-side code provoking the exception doesn't catch it, it automatically propagates to the client. In this case CORBA::INV_OBJREF would be the exception to use. If the mapping requires non-CORBA exceptions in these cases, everything will end up being converted into CORBA::UNKNOWN, which isn't at all helpful.

corba exceptions

Good point, I will create an internal issue to address this in the revised specification and in our implementation

I've just submitted the code

I've just submitted the code for a working example using object reference arguments to the IDL2C++0x project at http://osportal.remedy.nl.

CORBA::narrow<>

I don't think narrow<>() should be in the CORBA namespace at the user application level. Narrow is an operation that is not CORBA-specific (DDS has it for example). I would prefer if there could just be a narrow<>() (either in the global namespace or in a generic namespace). This way nothing in the mapping itself is tied to any specific middleware.

When using CORBA, the narrow function would/could communicate with the server. When using non-CORBA middleware, narrow would just be a wrapper around dynamic_pointer_cast (or equivalent).

Trent Nadeau
Northrop Grumman Corp.

narrow

Wouldn't it than be better to put narrow again on the object reference as static method, so that you have to use

Demo::Hello hello = Demo::Hello::_narrow (tmp);

narrow

That would work. However, I also like having narrow be a free function since it follows the same pattern as the built-in C++ casts (dynamic_cast<>, etc.).

I also think the "discoverability" of the _narrow static function is much less than one function you can tell people "treat this just like dynamic_cast".

However, given that, if I had to choose between portability and a relatively minor syntactical difference, I will choose portability every time.

Trent Nadeau
Northrop Grumman Corp.

narrow

We have moved the _narrow back to the typed object reference, that seems the best solution as far as we can tell.

narrow

I realize I'm really nitpicking with this, but could the static method just be called "narrow" without the underscore?

I think there are two reasons for this:

  1. Discoverability - In my experience, people remember method/function names as words or phrases, not as literal symbols. Many people remember the word "narrow" not the symbol "_narrow".
  2. Coding standards - While strictly speaking only global symbols beginning with underscores are reserved, for simplicity many coding standards forbid them in all cases.

Trent Nadeau
Northrop Grumman Corp.

narrow

The C++ mapping used _narrow to prevent a possible clash with user defined operations. But I think that is not a problem anymore, if you have Foo, than you can use Foo::narrow() or Foo->narrow(), first the middleware narrow, second is the user defined operation narrow.

Exception mapping

I don't know if this belong here or on a new forum, but I had some ideas for the mapping of IDL exceptions.

At a minimum, I think that all IDL exceptions should:

  • inherit, directly or indirectly, from std::exception
  • with the what() method overloaded to return a string consisting of:
    • the exception name (generated into the class)
    • a description of the error's cause (from parameter in the constructor)

Obviously for exceptions thrown by the middleware (such as CORBA system exceptions), the description of the error's cause would be vendor-specific due to differences in implementation.

Other than that, the mapping itself should be equivalent to the IDL struct mapping (assuming IDL struct is mapped to C++ class).

Trent Nadeau
Northrop Grumman Corp.

Skeleton mapping

How do you propose to implement the skeleton mapping for the server side?

updated example

I updated the example of this post with the most recent ideas

skeleton mapping

Seems I need to update the example code above with the latest ideas. For the skeleton I am looking at something like:

class Hello_servant : public virtual Hello::servant_base_type { };

So, not exposing the POA... class we have with IDL to C++ to the programmer.

New user code proposal

I created some example user code for how I think the user should use object references in the future. A few things I would like to point out:

  1. All _var/_ptr/_duplicate calls are gone, you only see object reference as user
  2. To check if the object reference is nill, just compare it with nullptr
  3. For CORBA a dynamic_cast is not enough, you need to narrow the object reference which can invoke the server. This is now a C++ template instead of a static method
  4. Normally you would call string_to_object on an ORB object reference, but just simplified the code a little bit for today

The user code:

  ::CORBA::Object TestObject = ::CORBA::ORB::string_to_object ("Test");

  if (TestObject == nullptr)
    printf ("Error, got a null pointer\n");
  else
    printf ("Yes, got a valid object reference\n");

  printf ("trying to narrow\n");
  ::Demo::Test t1 = ::CORBA::narrow < ::Demo::Test> (TestObject);

  if (t1 == nullptr)
    printf ("Error, got a null pointer from narrow\n");
  else
    printf ("Yes, got a valid object reference from narrow\n");

  TestObject = ::CORBA::ORB::string_to_object ("Sub");

  if (TestObject == nullptr)
    printf ("Error, got a null pointer\n");
  else
    printf ("Yes, got a valid object reference\n");

  ::Demo::Sub s1 = :: CORBA::narrow < ::Demo::Sub> (TestObject);

  if (s1 == nullptr)
    printf ("Error, got a null pointer from narrow\n");
  else
    printf ("Yes, got a valid object reference from narrow\n");
 
  t1->pass_interface (s1);
 
  ::Demo::Sub obj = t1->return_interface ();

  ::CORBA::Object base = t1->return_base ();

  ::Demo::Sub t2 (::CORBA::narrow < ::Demo::Sub> (base));

  if (t2 == nullptr)
    printf ("error\n");

  t2->foo ();

 

Clean interface

Other than the operator bool() issue that I mentioned earlier, I think this is a very clean interface. I especially like how users would no longer need to know about "special" generated types. If they are using interface Foo::Bar in IDL, then just use the Foo::Bar type in C++.

Trent Nadeau
Northrop Grumman Corp.

operator bool

I think the reference types should have an operator bool() so users can just do "if (myObj)" instead of having to use "if (myObj == nullptr)".

If object references are implemented in terms of std::shared_ptr, then this will come for free.

Trent Nadeau
Northrop Grumman Corp.

operator bool

tanadeau wrote:

I think the reference types should have an operator bool() so users can just do "if (myObj)" instead of having to use "if (myObj == nullptr)".

If object references are implemented in terms of std::shared_ptr, then this will come for free.

Yes, that will also work

Other idea

We could also put the generated stubs in an internal namespace and make for example A a typedef of a std::shared_ptr. That way the user only has pointers he can use and doesn't directly use the stub. Will provide some concrete examples tomorrow.

Nested IDL

A can't be a typedef to std::shared_ptr because interface A can contain nested IDL declarations. It would be odd to have to put them in a different class to A.

If we want class A to have smart pointer semantics (which I think is a good idea), then I think the object reference class will have to be a nested class inside it, otherwise the dependencies will be impossible.

e.g.

interface A {
struct S {
string x;
long y;
};
S giveMeS();
};

would map to something like:

class A {
public:
struct S {
std::string x;
CORBA::Long y;
};

class _objref_T : public _base_objref_type {
S giveMeS();
};

inline A(const A& other) {
other->_ref->_inc_ref();
_ref = other->_ref;
}
// similar operator=

inline ~A() {
_ref->_dec_ref();
}

inline _objref_T* operator->() { return _ref; }

private:
_objref_T* _ref;
};

objref example

dgrisby wrote:

 

interface A {
  struct S {
    string x;
    long y;
  };
  S giveMeS();
};

We would like to map it to something like:

// implementation stub class, hidden for the user
class A_stub {
public:
    S giveMeS();
};

template <T> class A_objref : public virtual CORBA::objref <T> 

{  public:

    struct S {      std::string x;      CORBA::Long y;    };

    inline T* operator-> () { return impl_.get ();}

  private:

      typedef std::shared_ptr  shared_ptr_type;

      shared_ptr_type impl_; }

  typedef A_objref <A_stub> A;

This hides the real implementation details to the user, he only sees the object reference class which has operator-> and any types defined in the interface. It should be pretty easy to plugin some other implementation classes for the stub that are doing something special

Formatting gone!

All my nice formatting has been eaten! Never mind, I expect it's still possible to follow.

shared_ptr

It seems that specializing the shared_ptr isn't as easy as I thought. I think we go for a solution similar like yours, but make it a template so that we can generate less code and also can hide some more implementation details to the user

object reference

Thanks for the info. This is one of the things where we are looking at. Maybe generate a specialization of std::shared_ptr that has the additional types, maybe use your approach, we are experimenting with some ideas.

Only exposing references

The idea to only export the std::shared_ptr to the user is moving forward, experimenting a little bit more before I can post it on ORBzone, be patience ;-)