Comparing generated C++ classes

6 posts / 0 new
Last post
Offline
Last seen: 2 years 9 months ago
Joined: 02/15/2013
Posts: 20
Comparing generated C++ classes

Hello there,

for some reasons I want to compare the C++ classes which are generated from the IDL.

Example: (IDL)

struct TSpeed
{
  double value;
};

Generated C++ class:

class TSpeed                                       
{
public:           
#ifdef __cplusplus
    typedef struct TSpeedSeq Seq;

#ifndef NDDS_STANDALONE_TYPE
    typedef TSpeedTypeSupport TypeSupport;
    typedef TSpeedDataWriter DataWriter;
    typedef TSpeedDataReader DataReader;
#endif

#endif
    
    DDS_Double  value;          
};                        

When I try to compare two variables of type TSpeed the compiler complains that there is no comparison defined. I could of course define a global operator==() but this would then be dependent on the IDL and would need to be updated every time the IDL is changed.

Did someone else have this problem before and has a suggestion for me?

And no, memcmp() is no solution as it generates wrong results even on simple structures, e.g. with padding present.

Thank you for your help

Regard

Josef

Organization:
Offline
Last seen: 2 years 9 months ago
Joined: 02/15/2013
Posts: 20

Hello again,

never mind. There is no sensible solution besides generating the comparison code in the nddsgen. I modified the XML files of nddsgen and added the operator==() to the class (in C++ only) and this works fine. Drawback is that I have to migrate the changes in nddsgen for every new version. But I already had some changes in there anyway.

During my changes I found out that DDS_Wchar is 4 bytes contrary to the HTML documentation.which states 'An 16 bit quantity...' and therefore the wcscmp cannot be used here. In this first version I left out the tricky thing of comparing sequences but I will add those. Bitfields will be left out as I never used them.

Regards

Josef

Gerardo Pardo's picture
Offline
Last seen: 12 hours 20 min ago
Joined: 06/02/2010
Posts: 601

Hi Josef,

Actually I think there is another sensible solution although modifying rtiddsgen may still be the more efficient one.

The idea is to use the serialization code that rtiddsgen generates to serialize the two objects and then do a memcmp() on the serilized bytes. This is quite simple but it requires using some APIs that are public, but not publicly documented. 

Assuming you have a type called MyType. When you run rtiddgen you will get generated code that implements the function MyTypePlugin_serialize().  You can use this function with the aid of some temporary buffers to serialize an object of type MyType into an array of bytes.

The approch can be seen in the C++ code below.  I have also created a ZIP file with a working example. You can find it under File Exchange > Code Snippets > C++ > compare_data.zip.

Gerardo

 

int get_cdrstream_bufsize_for_MyType()
{
    int maxSerializedSize;
    maxSerializedSize =
            MyTypePlugin_get_serialized_sample_max_size(
                NULL, /* This can be left to NULL for stand-alone serialization */
                RTI_FALSE,  /* No need to serialize an encapsulation Id */
                0, /* Ignored since we indicated no encapsulation */
                0  /* assume zero alignment */ )

            + RTI_CDR_STREAM_ALIGNMENT; /* Add extra to compensate for any alignment */

    return maxSerializedSize;
}

DDS_Boolean init_cdrstream_for_MyType(
        RTICdrStream *stream, char *buffer, int bufferLen)
{
    if ( bufferLen < get_cdrstream_bufsize_for_MyType() ) {
        printf("init_cdrstream_for_MyType: error buffer size is %d need at least %d\n",
               bufferLen, get_cdrstream_bufsize_for_MyType());
        return DDS_BOOLEAN_FALSE;
    }

    RTICdrStream_init(stream);
    RTICdrStream_set(stream, buffer, bufferLen);

    return DDS_BOOLEAN_TRUE;
}

int compare_MyType(
        MyType *data1, MyType *data2,
        char *buffer1, char *buffer2, int bufferLen)
{
    RTICdrStream stream1;
    RTICdrStream stream2;

    if ( !init_cdrstream_for_MyType(&stream1, buffer1, bufferLen) ) {
        return -2;
    }
    if ( !init_cdrstream_for_MyType(&stream2, buffer2, bufferLen) ) {
        return -2;
    }

    if ( !MyTypePlugin_serialize(
                NULL, /* This can be left to NULL for stand-alone serialization */
                data1,
                &stream1, /* Must be initialized to have a buffer of sufficient size */
                RTI_FALSE,  /* No need to serialize an encapsulation Id */
                0, /* Ignored since we indicated no encapsulation */
                RTI_TRUE, /* Do serialize the data */
                NULL /* Can be left to NULL */ ) ) {

        printf("compare_MyType: error serializing stream1\n");
        return -2;
    }

    if ( !MyTypePlugin_serialize(
                NULL, /* This can be left to NULL for stand-alone serialization */
                data2,
                &stream2, /* Must be initialized to have a buffer of sufficient size */
                RTI_FALSE,  /* No need to serialize an encapsulation Id */
                0, /* Ignored since we indicated no encapsulation */
                RTI_TRUE, /* Do serialize the data */
                NULL /* Can be left to NULL */ ) ) {

        printf("compare_MyType: error serializing stream2\n");
        return -2;
    }

    int serializedLength1 = RTICdrStream_getCurrentPositionOffset(&stream1);
    int serializedLength2 = RTICdrStream_getCurrentPositionOffset(&stream2);

    if ( serializedLength1 == serializedLength2 ) {
        return memcmp(
                RTICdrStream_getBuffer(&stream1),
                RTICdrStream_getBuffer(&stream2),
                serializedLength1);
    }
    else if ( serializedLength1 > serializedLength2 ) {
        return 1;
    }
    else {
        return -1;
    }
}

Offline
Last seen: 2 years 9 months ago
Joined: 02/15/2013
Posts: 20

Hi Gerardo,

thank you for the answer. This solution is interesting on its own and I might use this later in our project.

I was looking into comparing the serialized data but rejected that solution based on the following criteria:

- I need to compare about 50 different types and would need to generate the code for every type. Adding more types to be compared would mean adding more code manually 

- The serialize stuff is in the type plugin and not easly accessible on a generic (aka template) base. The type pluging functions are accessible on a type name independent base only thru XXXPlugin_New() but this seemd overkill for me.

- Serializing the topics for every compare looks like a performance bottleneck. I would need to cache the serialized version which also increases complexity and is a chance for errors.

But maybe my way of tackling the problem is not optimal and you have a better idea. What I really want is a 'send on change' data writer. This would mean that the same data content is not published/sent again if it is written again, The QoS for this writer would be RELIABLE with KEEP_LAST 1.

This looks like a content filtered topic which is able to compare to the last written value. Filtering would have to be at the sender side. Is this possible?

Thank you again and best regards

Josef

.

Gerardo Pardo's picture
Offline
Last seen: 12 hours 20 min ago
Joined: 06/02/2010
Posts: 601

Hello Josef,

I see... I agree that serializing in order to compare is not the best solution. I share your concern regarding the performance impact of two serializartions versus a field-by-field compare which would not only use more efficient equality operations but also short-cut the comparison as soon as a difference is foung.   However the objection that the 'serialization' approach forces you to generate code for every type would also apply to the changes you are making to rtiddsgen or to some future changes we make if we decided to generate code for the "==" operator, would that not the be case?

You bring up a good point about being able to access the type plugin generically using templates. This is definitely something that should be improved.

On the subject of "send on change" DataWriter. This is something that we already had a few requests for. However, at least in the situations I encountered, the need was not as simple as sending if the new sample was different. Rather the desire was to do it only if the difference was bigger than a certain threshold. For example if the data represents reading of a continous signal, e.g. the value of a temperature sensor, each reading could be different but the difference not be significant to the receiver (e.g. a small fraction of a degree). In fact even if there is no chanche such signals would produce different reading just due to noise.  Therefore what I had heard as a requirement is something that would compare two samples and determine if there are bigger than a certain threshold.

Internally we have discussed several approaches to this problem, such as:

  • Define whether two values are different more than a threshold by navigating the data values to their primitive members (ints, floats, strings) and computing the difference member by member. If any two members differ more than the specified threshold then the two data values would be considered "significantly" different.  Doing this would require definition of the threshold for each primitive member that appears in the type.
  • Define a more generic generalized "distance" function between two data values which would take into consideration the "distance" between each pair of members as defined for each primitive type and doing the geomeric average of those.

You are right in that this looks very much like a writer-side content-filter. 

With out latest Connext DDS 5.0.0 release writer-side content filter is now very scalable and works well even for large number of subscribers. The missing piece is the definition of the "distance" function I mentioned above. Once that is done we could use the "writer-side" filter approach to decide what goes to a particular DataReader. 

Also in our latest Connext DDS 5.0.0 there are some new writer-side content filter plugin APIs that could perhaps be used to immplement what you mentioned in the filter itself. That is, it allows plugging-in a writer side filter that keeps track per data-reader of the last value that passed the filter, compares it with the new value being published, and makes the decision on whether the new data shoud pass the filter and be sent to that DataReader.  However I think these API's are not publicly documented at the moment...  If this is of interest I would check with RTI support or services to see if they have something that would help you further.

Gerardo 

 

 

 

Offline
Last seen: 2 years 9 months ago
Joined: 02/15/2013
Posts: 20

Hi Gerardo,

yes, I have to check/adapt my changes to rtiddsgen for every update of DDS. But this has to be done only for updates of the library and not every change of the data model / IDLs. Updates of libraries during development have to be evaluated carefully because of side effects. As you might now, for developers updates mean replacing known errors by new, unknown errors :-)

Btw. generating the equality operator is not as easy as it seems at first, especially code for wide strings and sequences. It would be really nice if sequences would be templates and contain iterators in the C++ implementation...

Generating the operator< (less) as well as your distance function automatically is impossible as the result is dependent on application logic and therefore it has to be written manually. E.g. the distance of a sequence of strings cannot be defined automatically, even defining an order for strings can be very difficult in the context of internationalization. This looks like a good candidate for the writer side content filter which has to be written manually anyway.

 So for now I stay with my generated equality operator which solves my problem and does not increase the workload of developers and/or maintainers.

Thank you again for your effort and ideas.

Josef