Hi Hui,
Sorry for the slow reply, I was on leave.
messageToFlatArray() is a convenience function for when you don't mind allocation and copying. If you want more control over serialization, you can use MessageBuilder::getSegmentsForOutput() to get direct pointers to the message's underlying buffer space, without copying anything. You can then serialize these segments as you see fit. Note that you are responsible for tracking the segment boundaries in this case. On the receiving end, pass the same array-of-arrays into SegmentArrayMessageReader to parse the message. Again, this doesn't perform any allocation or copies; that's up to you to manage.
Another option is to implement your own kj::OutputStream, and then use capnp::writeMessage() to write a message to the stream. This will always write the entire message using one single call to write() on the underlying stream, passing an array-of-arrays that point into the original message buffer -- again, no copies. You could then copy into your scratch buffer or do whatever else you want. An advantage of this approach (using OutputStream instead of getSegmentsForOutput()) is that writeMessage() will also build a segment table for you and include that in the output, so on the receiving end you can use FlatArrayMessageReader instead of SegmentArrayMessageReader.
-Kenton