Ward What is the purpose of the return value? To know how much bytes are still in the pipe?
No. If positive it is the number of elements read (1 or 0). If negative, it is an error code. That's why you'll have seen in the examples idioms such as:
MyType myobj;
while(pipe.readRt(myObj) > 0)
{
... do stuff with myObj
}
Ward class Msg {
public:
std::string text;
enum icon;
};
This mostly won't work when sending it down a Pipe: the std::string object allocates memory internally and contains a pointer to the allocated memory. This pointer, and not the string's "content" is what is sent down the pipe. When the original object goes out of scope, the memory is freed and the pointer will point to invalid memory. So this is only guarantee to work (and possibly it is implementation-dependent) if the Msg object doesn't get destroyed before the Pipe's content is retrieved at the other end and the memory is accessed. If these conditions are not met, it may still appear to work, but you are accessing invalid memory so it may stop working tragically at any point. In general, you can only send "POD" (plain old data) down a Pipe, not objects that allocate memory. One option would be to use some form of serialisation. For instance, if your maximum string length is relatively small you could have this:
Msg {
char text[kMaxLength];
enum icon;
};
however, if you often send down the pipe strings that are much smaller than kMaxLength then you'll be copying a lot of unused memory around for no reason. An alternative to send messages of variable length is to use a two-step serialisation process, where you first send a message of fixed size containing the size of the payload which is sent in the next message. For instance, see what is done on the with gGuiPipe in core/default_libpd_render.cpp:
// do two separate writes: the pipe is datagram-based
// so it would be impossible to receive partial messages
// at the other end
gGuiPipe.writeNonRt(header);
gGuiPipe.writeNonRt(&array[0], header.size);
// we have successully parsed this message, so the
// default parser shouldn't when we return
// note: in practice there may be times when we'd want
// to have the default parser handle this message
// (e.g.: when an "event" field is also present), but
// for now we ignore them
and at the receiving end you need a simple state machine to keep track of where you are (there is no guarantee that the second block of data will be available immediately after the first one, so you have to account for that so you can read it later if needed):
static struct guiControlMessageHeader header;
static bool waitingForHeader = true;
if(waitingForHeader)
{
int ret = gGuiPipe.readRt(header);
if(1 != ret)
break;
else
waitingForHeader = false;
}
if(!waitingForHeader)
{
char payload[header.size];
int ret = gGuiPipe.readRt(&payload[0], header.size);
if(int(header.size) != ret)
{
break;
}
const char* name = gGuiControlBuffers[header.id].c_str();
if('f' == header.type)
{
if(header.size != sizeof(float))
{
rt_fprintf(stderr, "Unexpected message length for float: %u\n", header.size);
continue;
}
float value = ((float*)payload)[0];
libpd_start_message(1);
libpd_add_float(value);
libpd_finish_message("bela_guiControl", name);
}
if('s' == header.type)
{
libpd_symbol(name, payload);
}
waitingForHeader = true;
}
This has the downside that you need to use two separate writes and two separate reads. The issue with your use case is that using two separate writes is fine without locking only as long as you are writing from a single thread, so you'd need to add locking. It may also be possible to do this in a single write (thus resolving the threading issue), but then you need to change the initialisation of the Pipe ( done in createXenomaiPipe() in include/xenomai_wraps.h) so that it is not datagram oriented but streaming oriented. It is not immediately clear to me how that's done, but looking at the xddp stuff here and here may provide a hint.
But honestly, just use two separate pipes and avoid yourself the headache of going through the Xenomai source or using Xenomai locks.
Ward Do you think I have to worry about non-atomic behavior or locking when writing the pipe?
I think that's handled by Xenomai for you. Only worry about that if you are using two separate write...() calls if sending variable-length messages using two threads or worry about priority inversion as explained above if your objects are "large enough".