Search code examples
c++boostinterprocessboost-interprocess

Creating a message queue using boost interprocess - memory access violation


I am creating a message queue which is used by two processes. One of them is putting something in it and the other is reading it. The message queue is following struct I created.

  struct MSGQueue {
    Action actions_[256];
    int count;
    MSGQueue() { count = 0; }

    interprocess_mutex mutex;

    Action Pop() {
      --count;
      return actions_[count];
    }

    void Put(Action act) {
      actions_[count] = act;
      ++count;
    }
  };

Action is a custom class I created.

class Action {
 public:
  // Getter functions for the member 

 private:
    std::string name_;
    ActionFn action_fn_; // this is an enum
    void* additional_data_;
 }

I am creating a shared memory like this in the main program

  shm_messages = shared_memory_object(create_only,"MySharedMemory", read_write);
  shm_messages.truncate(sizeof(MSGQueue));
  region = mapped_region(shm_messages_, read_write);

In my other program I am opening it and put an action in the queues array of actions.

  boost::interprocess::shared_memory_object shm_messages_;
  boost::interprocess::mapped_region region_;

  shm_messages_ = shared_memory_object(open_only, "MySharedMemory", read_write);
  shm_messages_.truncate(sizeof(MSGQueue));
  region_ = mapped_region(shm_messages_, read_write);

  //Get the address of the mapped region
  void * addr       = region_.get_address();
  //Construct the shared structure in memory
  MSGQueue * data = static_cast<MSGQueue*>(addr);

  Action open_roof("OpenRoof", ActionFn::AFN_ON, NULL);

  { // Code block for scoped_lock. Mutex will automatically unlock after block.
    // even if an exception occurs
    scoped_lock<interprocess_mutex> lock(data->mutex);

    // Put the action in the shared memory object
    data->Put(open_roof);
  }

The main program is checking if we got some new messages and if there is one it shall read it and put it in a list.

  std::vector<ghpi::Action> actions;

  //Get the address of the mapped region
  void * addr       = region_.get_address();
  //Construct the shared structure in memory
  MSGQueue * data = static_cast<ghpi::Operator::MSGQueue*>(addr);
  if (!data) {
    std::cout << " Error while reading shared memory" << std::endl;
    return actions;
  }

  {
    scoped_lock<interprocess_mutex> lock(data->mutex);

    while (data->count > 0) {
      actions.push_back(data->Pop()); // memory access violation here
      std::cout << " Read action from shm" << std::endl;
    }
  }

The second program which is putting the action works fine. But after it run the main program is seeing the count has increased and is trying to read and throws an memory access violation at me.

I don't know why i am getting this violation error. Is there something special about sharing class objects or structs?


Solution

  • Let's take a look at the objects you're trying to pass between processes:

    class Action {
    
    // ...
        std::string name_;
    }
    

    Well, looky here. What do we have here? We have here a std::string.

    Did you know that sizeof(x), where x is a std::string will always give you the same answer, whether the string is empty, or has their entire contents of "War And Peace"? That's because your std::string does a lot of work that you don't really have to think about. It takes care of allocating the requirement memory for the string, and deallocating when it is no longer used. When a std::string gets copied or moved, the class takes care of handling these details correctly. It does its own memory allocation and deallocation. You can think of your std::string to consist of something like this:

    namespace std {
        class string {
             char *data;
             size_t length;
    
             // More stuff
        };
    }
    

    Usually there's a little bit more to this, in your typical garden-variety std::string, but this gives you the basic idea of what's going on.

    Now try to think of what happens when you put your std::string into shared memory. Where do you think that char pointer still points to? Of course, it still points to somewhere, someplace, in your process's memory where your std::string allocated the memory for whatever string it represents. You have no idea where, because all that information is hidden in the string.

    So, you placed this std::string in your shared memory region. You did place the std::string itself but, of course, not the actual string that it contains. There's no way you can possibly do this, because you have no means of accessing std::string's internal pointers and data. So, you've done that, and you're now trying to access this std::string from some other process.

    This is not going to end well.

    Your only realistic option is to replace the std::string with a plain char array, and then go through the extra work of making sure that it's initialized properly, doesn't overflow, etc...

    Generally, in the context of IPC, shared memory, etc..., using any kind of a non-trivial class is a non-starter.