Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a shared_ptr through a C interface that takes void *

I have a C++ project that uses SDL, in particular SDL Events. I would like to use the event system for incoming network messages just as it is used for UI events. I can define a new event type and attach some arbitrary data (see this example). This is what I would do if I was using ordinary pointers:

Uint32 message_event_type = SDL_RegisterEvents(1);

/* In the main event loop */
while (SDL_Poll(&evt)) {
    if (evt.type == message_event_type) {
         Message *msg = evt.user.data1;
         handle_message(msg);
    }
}

/* Networking code, possibly in another thread */
Message *msg = read_message_from_network();
SDL_Event evt;
evt.type = message_event_type;
evt.user.data1 = msg;
SDL_PostEvent(evt);

Instead, I've been using shared_ptr<Message> up to now. Messages are read-only objects once constructed, and might be used in lots of places while being handled, so I thought to use shared_ptr for them.

I would like to use a shared_ptr to the message in the network side, and also on the event handling side. If I do this:

// in networking code:
shared_ptr<Message> msg = ...
evt.user.data1 = msg.get();

// later, in event handling:
shared_ptr<Message> msg(evt.user.data1);

then there are two independent shared_ptrs and either one could delete the Message object while the either is still using it. I would need to somehow pass the shared_ptr through the SDL_UserEvent struct, which only has a couple of void * and int fields.

Additional. Note that SDL_PostEvent returns immediately; the event itself is put on a queue. The event may be popped from the queue by the handler well after a shared_ptr to the message has gone out of scope in the networking code. So I can't pass the address of a local shared_ptr to copy from. By the time the copying occurs, it's likely to no longer be valid.

Has anyone faced a similar problem and knows of a nice solution?

like image 528
Edmund Avatar asked Jun 26 '15 00:06

Edmund


3 Answers

Allocate a pointer to the shared ptr with new. This calls the constructor (incrementing the reference count), but the corresponding destructor is not called so the shared_ptr will never destruct it's shared memory.

Then in the corresponding handler, just destroy the object after copying the shared_ptr bringing its reference count back to normal.

This is identical to how you'd pass any other non-primitive type through a message queue.

typedef shared_ptr<Message> MessagePtr;

Uint32 message_event_type = SDL_RegisterEvents(1);

/* In the main event loop */
while (SDL_Poll(&evt)) {
    if (evt.type == message_event_type) {
         // Might need to cast data1 to (shared_ptr<Message> *)
         unique_ptr<MessagePtr> data (evt.user.data1);
         MessagePtr msg = *data;
         handle_message(msg);
    }
}

/* Networking code, possibly in another thread */
MessagePtr msg = read_message_from_network();
SDL_Event evt;
evt.type = message_event_type;
evt.user.data1 = new MessagePtr (msg); 
SDL_PostEvent(evt);

Messages are read-only objects once constructed

I just want to point out, this is good and even necessary for the multithreading to be safe. You might want to use shared_ptr<const Message>

like image 98
QuestionC Avatar answered Oct 05 '22 00:10

QuestionC


Seems like ideal place to use std::enable_shared_from_this

struct Message: std::enable_shared_from_this<Message>
{
    …
};

evt.user.data1 = msg.get();

// this msg uses the same refcount as msg above
shared_ptr<Message> msg = evt.user.data1.shared_from_this();
like image 26
StenSoft Avatar answered Oct 05 '22 02:10

StenSoft


I thought of another potential technique: using placement new to store the shared_ptr in the same space as the C struct:

SDL_Event evt;
evt.type = event_type;
// create new shared_ptr, in the same memory as evt.user.code
new (&evt.user.code) shared_ptr<Message>(msg);
SDL_PushEvent(&evt);

SDL then copies the event around as a C object, until later code extracts the message from the event:

shared_ptr<Message> get_message(SDL_Event& evt) {
    // copy shared_ptr out of evt
    shared_ptr<Message> msg = *reinterpret_cast<shared_ptr<Message> *>(&evt.user.code);
    // destroy shared_ptr inside the event struct
    (reinterpret_cast<shared_ptr<Message> *>(&evt.user.code))->~shared_ptr();
    return msg;
}

There are several fields inside the event struct that should be enough space for the shared_ptr (see https://github.com/spurious/SDL-mirror/blob/master/include/SDL_events.h#L485).

I'm aware that this is a bit hacky. I'd appreciate some sanity checking of the technique.

like image 44
Edmund Avatar answered Oct 05 '22 00:10

Edmund