A component I’m working on needs to be able to serialize its data via JSON. That’s fine for scalars, arrays and maps, but we also needed to be able to serialize COM objects (there’s a large legacy codebase underneath, much of which is deployed via COM). It turns out ATL provides API functions for Base 64 encoding (which was a surprise to me and a number of my colleagues). Here’s some code to make calling it easier (I’ve replaced the production code error handling with simple strings):
std::string to_base_64(int byte_length, const void* bytes ) { if (bytes == 0) throw "ToBase64 called with zero bytes"; ATL::CStringA base64; int base64_length = ATL::Base64EncodeGetRequiredLength(byte_length); if(!ATL::Base64Encode(static_cast<const BYTE*>(bytes), byte_length, base64.GetBufferSetLength(base64_length), &base64_length)) throw "ATL::Base64Encode failed"; base64.ReleaseBufferSetLength(base64_length); return static_cast<const char*>(CT2A(base64)); } std::pair<size_t,std::unique_ptr<BYTE>> from_base_64( const std::string& encoded ) { // Predict length needed for decoding int decoded_length = Base64DecodeGetRequiredLength( encoded.size() ); std::unique_ptr<BYTE> buffer( new BYTE[decoded_length] ); // Actual decoded-length may be different to that predicted - method overwrites the value if (!Base64Decode(encoded.c_str(), encoded.size(), buffer.get(), &decoded_length)) { throw "ATL::Base64Decode failed"; } return std::make_pair( decoded_length, std::move(buffer) ); }
That leaves the requirement to turn a serializable COM object into a byte array. The following code does the job provided the COM object implements IPersistStream:
std::pair<size_t, std::unique_ptr<BYTE>> object_to_bytes( IUnknown* pUnk ) { // Create an in-memory stream, HGlobalMem will be allocated internally HRESULT hr; CComPtr<IStream> pIStream; if (FAILED(hr = CreateStreamOnHGlobal(0, TRUE, &pIStream))) throw "Could not CreateStreamOnHGlobal"; CComPtr<IPersistStream> pPersistStream; if (FAILED(hr = pUnk->QueryInterface(IID_IPersistStream, &pPersistStream ) ) throw "Failed to QI to IPersistStream"; CLSID clsid; if (FAILED(hr = pPersistStream->GetClassID(&clsid) ) throw "Failed to get clsid"; // Serialise CLSID into stream if (FAILED(hr = WriteClassStm( pIStream, clsid ))) throw "WriteClassStm failed"; // Serialise object into stream if (FAILED(hr = pPersistStream->Save( pIStream, TRUE ) ) throw "Save object into IPersistStream failed"; // Find out how big the stream is LARGE_INTEGER liStart = {0}; if (FAILED(hr = pIStream->Seek( liStart, STREAM_SEEK_SET, NULL ) ) throw "IPersistStream::Seek failed"; STATSTG statstg; memset( &statstg, 0, sizeof(statstg) ); if (FAILED(hr = pIStream->Stat( &statstg, STATFLAG_NONAME ) ) throw "IPersistStream::Stat failed"; // Populate a byte array from the stream std::unique_ptr<BYTE> buffer( new BYTE[statstg.cbSize.LowPart] ); unsigned long size_read; if (FAILED(hr = pIStream->Read( (void*)buffer.get(), statstg.cbSize.LowPart, &size_read ) ) throw "IPersistStream::Read failed"; ASSERT( size_read == statstg.cbSize.LowPart ); return std::make_pair( size_read, std::move(buffer) ); } CComPtr<IUnknown> bytes_to_object( size_t length, void* bytes ) { HRESULT hr; CComPtr<IStream> pIStream; HGLOBAL handle = GlobalAlloc( GMEM_MOVEABLE | GMEM_NODISCARD, length ); if (FAILED(hr = CreateStreamOnHGlobal(handle, TRUE, &pIStream))) throw "Could not CreateStreamOnHGlobal"; unsigned long size_written; if (FAILED(hr = pIStream->Write( bytes, length, &size_written ) ) throw "Failed to write to stream"; // Reset the stream LARGE_INTEGER liStart = {0}; if (FAILED(hr = pIStream->Seek( liStart, STREAM_SEEK_SET, NULL ) ) throw "IPersistStream::Seek failed"; // Serialise CLSID out of stream CLSID clsid; if (FAILED(hr = ReadClassStm( pIStream, &clsid ))) throw "ReadClassStm failed"; // Create object from the clsid and stream from IStream into the object, using IPersistStream CComPtr<IUnknown> pUnk; if (FAILED(hr = ::CoCreateInstance(clsid, NULL, CLSCTX_ALL, &pUnk ) ) throw "Failed to CoCreate clsid"; CComPtr<IPersistStream> pStream; if (FAILED(hr = pUnk->QueryInterface(IID_IPersistStream, &pStream ) ) throw "Failed to QI to IPersistStream"; if (FAILED(hr = pStream->Load( pIStream ) ) throw "IPersistStream::Load failed"; return pUnk; }
Then it’s a matter of putting the methods together to achieve a simple API:
std::string to_base64_encoded_byte_array( IUnknown* pUnk ) { auto bytes = object_to_bytes( pUnk ); return to_base_64( bytes.first, bytes.second.get() ); } CComPtr<IUnknown> from_base64_encoded_byte_array( const std::string& encoded) { auto bytes = from_base_64( encoded ); return bytes_to_object( bytes.first, bytes.second.get() ); }