eclipse-cyclonedds / cyclonedds-cxx

Other
95 stars 74 forks source link

How to replace the idl "interface" keyword? #416

Open keepgoingxxx opened 1 year ago

keepgoingxxx commented 1 year ago

hello. I'm just starting out with DDS.

I want to generate the idl files used in OpenDDS.

I learned that cpp and hpp files cannot be created with cyclone DDS idlc because interface keyword exists in the corresponding idl file.

(Through the link below, I learned that idl interface keyword cannot be used in cyclone dds.) https://github.com/eclipse-cyclonedds/cyclonedds/issues/44

Is there any other way I can use instead of interface keyword? and Could you please share an example of that too?

module HelloWorld {
  interface Msg {
    string message;
    string sayHello();
  };
};

Please tell me the command to generate multiple idl files using idlc.

Thank you in advance.

eboasson commented 1 year ago

DDS really is all about "plain old data", and that's why in DDS you define the type of data you want to publish without defining any functions. The problem here is that you define a function sayHello as part of the interface, and that is what is not really supported by DDS.

What you are looking for is struct:

module HelloWorld {
  struct Msg {
    string message;
  };
};

for defining the data type. The sayHello function you then have to define separately.

You may also want to have a look at https://github.com/eboasson/i11eperf, it has already helped several people trying to do things with Cyclone DDS and OpenDDS. Perhaps you will find it useful, too.

If you have multiple IDL files, you need to run IDLC on all of them. The easiest way is to list them all in the idlcxx_generate command if you use the CMake support functions:

idlcxx_generate(TARGET type_lib_cxx FILES a.idl b.idl c.idl)

(You can also define multiple idlcxx_generate targets, each with its own set of IDL files.)

keepgoingxxx commented 1 year ago

@eboasson, Thank you for your quick reply and additional information As far as I know, the interface keyword in OMG IDL is mapped to a C++ class. Am I right?

If I'm right, if cyclone DDS doesn't support the interface keyword, is there a mechanism to make it behave similarly to the interface keyword?

For reference, my ultimate goal is to implement RPC using DDS. Does the interface keyword have anything to do with implementing RPC? If not, should I implement RPC using something like a Request-Reply communication pattern?

Sorry for asking some stupid questions. I would appreciate your understanding.

eboasson commented 1 year ago

As far as I know, the interface keyword in OMG IDL is mapped to a C++ class. Am I right?

I think so. The interface keyword in IDL is really the old CORBA stuff for calling methods on remote objects. That is really the opposite of the "data centric" model for interaction that DDS is about, and so it never was part of any DDS spec.

That is, until someone decided that it made sense to create the DDS-RPC spec. I believe this to be a mistake: a publish/subscribe system is a rather expensive choice as a basis for something that is fundamentally about point-to-point communication.

If I'm right, if cyclone DDS doesn't support the interface keyword, is there a mechanism to make it behave similarly to the interface keyword?

Not really, because the programming model of DDS is one where processes observe a global data space and publish updates based on their observations, and not one where processes invoke operations on objects. That said, DDS is expressive enough to build any other communication system on it[^1] and so you can run RPC over it.

For reference, my ultimate goal is to implement RPC using DDS. Does the interface keyword have anything to do with implementing RPC?

So, yes. The DDS-RPC spec uses the interface keyword to define the interface of a service for which it can then generate stubs/skeletons and marshalling code for the arguments into topics.

If not, should I implement RPC using something like a Request-Reply communication pattern?

Yes, that's how it works. You take an interface like:

interface Msg {
  bool setLightIntensity(float v);
};

Which leads to a Request topic with one operation (setLightIntensity) with a float argument, and a Reply topic with a boolean result. What you also need are some header fields to keep track of requests and replies so that if you have two processes each takes the result meant for it and discards the other.

But when you look at it, setLightIntensity in the context of DDS makes no sense, really. Instead of sending a command and waiting for a response, it works much better to do two "ordinary" topics:

@topic struct DesiredLightIntensity { float v; };
@topic struct ActualLightIntensity { float v; };

(presumably you'd want some keys in there if you have multiple lights!) Then when you want to change the light intensity you publish the first topic, and you let the light's controller be a process that tries to minimize the error between the desired setting and the current setting. Publishing that current setting means anyone interested can monitor the current status. (If you have multiple places each trying to change the setting, the last one wins.)

It not only works nicer in the programming model of DDS, it also plays nicer with failures. Handling failures in an RPC-based system is absolutely terrifying. Handling failures in a model like the above is actually doable.

[^1]: In the same sense that a Turing machine can compute any computable function: in practice it will often be much less efficient than some alternatives.