InterprocessConnection and reading/writing a MemoryBlock

Hi all,

I’m writing an InterprocessConnection Plug-In/App and am a bit struggling with the MemoryBlock handling. So far I have this code for primitive types like bool, int, float, etc

// sending process: preparing the memory block for sending
int someInt = 46;
MemoryBlock mBlock;
MemoryOutputStream mos(mBlock, false);
mos.writeInt(someInt);
interprocessSender.sendMessage(mBlock);

// receiving process: callback for reading the received memory block
void messageReceived (const MemoryBlock& message) 
{
MemoryInputStream mis(message, false);
int result = mis.readInt();
}

This is working so far. I also read (and understand) the InterprocessConnection example code for sending and receiving Strings. But I wonder how I can transmit other types like custom structs. Will I have to read the memory block byte per byte and writing my own code to reconstruct it? Or is there an easier and safer way to do this?

Thanks
Stefan

Since C++ has no reflection (you can’t query what member variables classes have), you will have to do it manually. If your class is trivial to construct and destroy (including all its member variables), you may be able to get away by doing a byte level copy into and back from the MemoryBlock.

Example :

class Baz
{
public:
	double x = 0.0;
	double y = 1.0;
	int z = 3;
};

MemoryBlock g_block; 

void test_serialization()
{
	{
		Baz ob;
		ob.x = 42.777;
		g_block = MemoryBlock{ (void*)&ob,sizeof(Baz) };
	}
	{
		Baz ob;
		memcpy(&ob, g_block.getData(), sizeof(Baz));
		std::cout << ob.x << " " << ob.y << " " << ob.z << "\n";
	}
}

This isn’t very flexible and safe, though. All the member variables of the class that is serialized this way need to be trivial types like ints and doubles.

1 Like

With a bit of templating, you can use the same function to read/write to a stream, which makes round-tripping a lot more robust (there’s no danger that you edit the ‘write’ function and forget to update the ‘read’ function). This is also a lot safer than just writing the raw bits that make up the object into a stream.

// Wrappers around the juce streams...
struct OutputArchive final {
  OutputArchive(OutputStream& stream) : stream_{stream} {}

  void operator()(bool x) { stream_.writeBool(x); }
  void operator()(int x) { stream_.writeInt(x); }
  void operator()(float x) { stream_.writeFloat(x); }
  void operator()(const std::string& x) { stream_.writeString(x); }

  OutputStream& stream_;
};

struct InputArchive final {
  InputArchive(InputStream& stream) : stream_{stream} {}

  void operator()(bool& x) { x = stream_.readBool(); }
  void operator()(int& x) { x = stream_.readInt(); }
  void operator()(float& x) { x = stream_.readFloat(); }
  void operator()(std::string& x) { x = stream_.readString(); }

  InputStream& stream_;
};

struct MyComplexStruct final {
  bool bool_;
  int int_;
  float float_;
  std::string string_;

  // This function is used to both read and write this object!
  template <typename Archive>
  void serialize(Archive& archive) {
    archive(string_);
    archive(bool_);
    archive(int_);
    archive(float_);
  }
};

int main() {
  MyComplexStruct str{true, 543, 0.12, "hello world"};

  MemoryOutputStream outputStream;
  OutputArchive outputArchive{outputStream};
  // When we call serialize with an OutputArchive, the object writes its
  // contents into an output stream
  str.serialize(outputArchive);

  MemoryInputStream inputStream{outputStream.getData(),
                                outputStream.getDataSize(), false};
  InputArchive inputArchive{inputStream};

  MyComplexStruct copy;
  // When we call serialize with an InputArchive, the object updates its data
  // members using the next items in the input stream
  copy.serialize(inputArchive);

  std::cout << copy.bool_ << '\n';
  std::cout << copy.int_ << '\n';
  std::cout << copy.float_ << '\n';
  std::cout << copy.string_ << '\n';
}
2 Likes

Great! Thank you Xenakios and reuk!

Both solutions are working in my scenario, but I think I will prefer reuks way using the stream wrapper approach. Seems to me a bit more robust.

@reuk I had to replace std::string with String to get the code compiling. Seems that

bool OutputStream::writeString(const String& text)

and

String InputStream::readStream( )

don’t work with std::string.

Thanks again for the quick replies, you saved my day!
Stefan

Oops, sorry about that. I tested with std types on godbolt.org, but I must have forgotten to switch that parameter to the juce eqivalent