This section covers the following topics:

Avoid Quick Closure of Readers and Writers

The creation of a Qeo factory, a reader or a writer is an expensive operation. Hence, it is not advisable to create and destroy these entities frequently. Only destroy Qeo readers and writers if you won't be needing them in the near future.

Another reason why one should avoid quickly closing and recreating readers is that events might be missed. Event readers are guaranteed to receive all events of all discovered event writers on the same Topic. However if you close your event reader and immediately create a new one, there is a small time window (the time needed for creating the new reader and re-discovering all writers) in which events may go lost on you.

Threading model

Qeo will spawn its own threads. Callbacks towards the applications MAY run in an application thread (especially when the callback is called at reader creation time) or MAY run in a dedicated Qeo thread. You should not make any assumptions in which thread Qeo callbacks will run. You should also not do any blocking calls in a Qeo callback thread as this will block everything. You should also not close the current reader or close the factory from a Qeo data callback.

When registering signal handlers, you cannot make any assumptions in which thread those signal handlers are executed.

Memory ownership

Ownership of memory is never transferred: pointers to memory passed as arguments in a Qeo callback MUST NOT be free()'d by the application. In addition, if you want to retain the information (i.e. the data sample) passed into the callback after the callback finishes, you need to make your own copy.

static void on_chat_message(const qeo_event_reader_t *reader,
                            const void *data,
                            uintptr_t userdata)
{
 	/* data MUST NOT be free()'d here ! */
} 

Objects passed to Qeo API calls (e.g. qeo_event_writer_write()) are still owned by the application. Hence it's the application's responsibility to free() it (Qeo will make its own copy of the data).

org_qeo_sample_simplechat_ChatMessage_t *chat_msg = calloc(1, sizeof(*chat_msg));
...
qeo_event_writer_write(qeo, chat_msg);
/* chat_msg is still owned by the application: must be free()'d */
free(chat_msg)

Using Sequence Types in C

Sequence members in your QDM are basically variable-length arrays. In the C language binding, these are translated into DDS sequence types (DDS is Qeo's underlying communication technology). This section describes the C-language API for constructing, manipulating and reading from DDS sequences.

Icon

There is one rule of thumb to keep in mind when working with the sequence API as described below: the API calls in ALL_CAPS (which are implemented as preprocessor macros) operate directly on the sequence. The API calls in lowercase (which are implemented as functions) operate on a pointer to the sequence.

Defining a sequence type

You typically don't need to define sequence types manually; the code generator does this for you.

DDS_SEQUENCE(elemtype, seqtypename)

defines a sequence type with elements of type elemtype, called seqtypename.

For example:

DDS_SEQUENCE(int32_t, intsequence_t);
intsequence_t seq;

defines a type called intsequence_t that can hold a variable amount of int32_t elements, and creates one instance of this type, called seq.

Initializing a sequence

There are two ways to initialize an empty sequence:

intsequence_t seq = DDS_SEQ_INITIALIZER(int32_t); /* creates an empty sequence */
intsequence_t seq;
DDS_SEQ_INIT(seq); /* creates an empty sequence */
Sequence properties

A sequence has several properties:

  • Its length (the current element count), accessible through DDS_SEQ_LENGTH(seq);
  • Its capacity (the size of the buffer allocated for this sequence), accessible through DDS_SEQ_MAXIMUM(seq). The following always holds true: DDS_SEQUENCE_LENGTH(seq) <= DDS_SEQUENCE_MAXIMUM(seq).
  • Its element size (the size of a single sequence element), accessible through DDS_SEQ_ELEM_SIZE(seq);
  • The owned flag (1 if the application is responsible for cleanup of the sequence, 0 if Qeo is responsible for cleanup), accessible through DDS_SEQ_OWNED(seq).
Getting and setting sequence elements
Icon

Sequences, like C arrays, are indexed starting from 0.

There are several ways to get and set elements in a sequence:

  • DDS_SEQ_ITEM(seq, i) returns the ith element from the sequence.
int32_t thirdval = DDS_SEQ_ITEM(seq, 2);
  • DDS_SEQ_ITEM_PTR(seq, i) returns a pointer to the ith element in the sequence.
int32_t *thirdvalptr = DDS_SEQ_ITEM_PTR(seq, 2);
  • DDS_SEQ_DATA(seq) returns the sequence's element array. This is the actual internal sequence buffer, not a copy!
int32_t *buffer = DDS_SEQ_DATA(seq);
buffer[2] = 128;
assert(128 == DDS_SEQ_ITEM(seq, 2));
  • DDS_SEQ_ITEM_SET(seq, i, val) sets the ith element in the sequence.
DDS_SEQ_ITEM_SET(seq, 2, 128);
  • dds_seq_replace(seqptr, i, valptr) sets the ith element in the sequence.
int32_t newval = 128;
dds_seq_replace(&seq, 2, &newval);
  • dds_seq_to_array(seqptr, array, arraylen) copies maximum arraylen elements from the sequence to an array.
int32_t *values = malloc(sizeof(int32_t) * DDS_SEQ_LENGTH(seq));
dds_seq_to_array(&seq, values, DDS_SEQ_LENGTH(seq)); /* values now holds a copy of the sequence contents */
  • dds_seq_from_array(seqptr, array, arraylen) sets the sequence contents to a copy of the array.
intsequence_t seq = DDS_SEQ_INITIALIZER(int32_t);
int32_t initialvalues[] = { 1, 2, 3, 4, 5 };
dds_seq_from_array(&seq, initialvalues, sizeof(intitialvalues)/sizeof(initialvalues[0]));
  • dds_seq_copy(destseqptr, srcseqptr) copies the contents of one sequence into another.
intsequence_t destseq = DDS_SEQ_INITIALIZER(int32_t);
dds_seq_copy(&destseq, &srcseq);
assert(DDS_SEQ_LENGTH(destseq) == DDS_SEQ_LENGTH(srcseq);
for (i = 0; i < DDS_SEQ_LENGTH(destseq); ++i)
	assert(DDS_SEQ_ITEM(destseq, i) == DDS_SEQ_ITEM(srcseq, i));

Iterating over sequences

There are several ways to iterate over a sequence's contents:

  • DDS_SEQ_FOREACH(seq,i) is a convenience alias for a simple for loop: for (i = 0; i < DDS_SEQ_LENGTH(seq); i++)
int index;
DDS_SEQ_FOREACH(seq, index) {
	printf("seq[%d] = %d\n", index, DDS_SEQ_ITEM(seq, index));
}
  • DDS_SEQ_FOREACH_ENTRY(seq,i,ptr) augments DDS_SEQ_FOREACH by also giving you a pointer to the sequence element.
int index;
int32_t *valptr;
DDS_SEQ_FOREACH_ENTRY(seq, index, valptr) {
	printf("seq[%d] = %d\n", index, *valptr);
}
  • dds_seq_every(seqptr, iterfunc(void *, void *), cookie) calls iterfunc for every element in the sequence. iterfunc takes two arguments: a pointer to a sequence element, and the cookie you pass as third argument to dds_seq_every.
static void iterator(void *valptr, void *cookie)
{
	int *index = (int *)cookie;
	int32_t val = *(int32_t *)valptr;
	printf("seq[%d] = %d\n", *index, val);
	*index += 1;
}
int ndx = 0;
dds_seq_every(&seq, iterator, &ndx);

Adding to or removing from a sequence

  • dds_seq_prepend(seqptr, valptr) inserts an element at the start of the sequence
int32_t newval = 42;
dds_seq_prepend(&seq, &newval);
  • dds_seq_append(seqptr, valptr) inserts an element at the end of the sequence
int32_t newval = 42;
dds_seq_append(&seq, &newval);
  • dds_seq_insert(seqptr, i, valptr) inserts an element at position i in the sequence
int32_t newval = 42;
dds_seq_insert(&seq, 2, &newval);
  • dds_seq_remove_first(seqptr, valptr) removes the first element from a sequence, optionally returning the removed element via valptr.
int32_t removedval;
dds_seq_remove_first(&seq, &removedval);
  • dds_seq_remove_last(seqptr, valptr) removes the last element from a sequence, optionally returning the removed element via valptr.
int32_t removedval;
dds_seq_remove_last(&seq, &removedval);
  • dds_seq_remove(seqptr, i, valptr) removes the element at index i from a sequence, optionally returning the removed element via valptr.
dds_seq_remove(&seq, 2, NULL);

Testing sequences for equality

 dds_seq_equal(seqptr1, seqptr2) tests two sequences for equality.

/* first copy one sequence to another so we have something to compare */
intsequence_t seq2 = DDS_SEQ_INITIALIZER(int32_t);
dds_seq_copy(&seq2, &seq);
assert(0 != dds_seq_equal(&seq, &seq2));

Emptying and cleaning up sequences

  • dds_seq_reset(seqptr) sets a sequence's length to 0, but does not free the internal buffer.
dds_seq_reset(&seq);
assert(DDS_SEQ_LENGTH(seq) == 0);
  • dds_seq_cleanup(seqptr) sets a sequence's length and capacity to 0, freeing the internal buffer. You need to call this function once you are done with your sequence, to free all associated memory. Note that this function does not call free(seqptr). If your sequence structure itself was dynamically allocated, it is still up to you to free it.
dds_seq_cleanup(&seq);
assert(DDS_SEQ_LENGTH(seq) == 0);
assert(DDS_SEQ_DATA(seq) == NULL);
Icon

Note that the above API is written as a set of generic macros and functions working on void pointers. The DDS sequence API does not know or care about the internal structure of the sequence elements: all copy, add, remove and free operations are shallow. If your sequence elements themselves hold pointers to dynamically allocated data, it is up to you to make sure that those pointers are properly managed.

typedef struct {
	char *string;
} string_t;
DDS_SEQUENCE(string_t, stringseq_t);
string_t val;
val.string = strdup("hello");
stringseq_t seq = DDS_SEQ_INITIALIZER(string_t);
dds_seq_append(&seq, &val);
val.string[0] = 'y';
printf("%s\n", DDS_SEQ_ITEM(seq, 0).string); /* will print "yello" */