Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET GZipStream decompress producing empty stream

I'm trying to serialize and compress a WPF FlowDocument, and then do the reverse - decompress the byte array and deserialize to recreate the FlowDocument - using the .NET GZipStream class. I'm following the example described on MSDN and I have the following test program:

var flowDocumentIn = new FlowDocument();
flowDocumentIn.Blocks.Add(new Paragraph(new Run("Hello")));
Debug.WriteLine("Compress");
byte[] compressedData;
using (var uncompressed = new MemoryStream())
{
    XamlWriter.Save(flowDocumentIn, uncompressed);
    uncompressed.Position = 0;
    using (var compressed = new MemoryStream())
    using (var compressor = new GZipStream(compressed, CompressionMode.Compress))
    {
        Debug.WriteLine(" uncompressed.Length: " + uncompressed.Length);
        uncompressed.CopyTo(compressor);
        Debug.WriteLine(" compressed.Length: " + compressed.Length);
        compressedData = compressed.ToArray();
    }
}

Debug.WriteLine("Decompress");
FlowDocument flowDocumentOut;
using (var compressed = new MemoryStream(compressedData))
using (var uncompressed = new MemoryStream())
using (var decompressor = new GZipStream(compressed, CompressionMode.Decompress))
{
    Debug.WriteLine(" compressed.Length: " + compressed.Length);
    decompressor.CopyTo(uncompressed);
    Debug.WriteLine(" uncompressed.Length: " + uncompressed.Length);
    flowDocumentOut = (FlowDocument) XamlReader.Load(uncompressed);
}

Assert.AreEqual(flowDocumentIn, flowDocumentOut);

However I get an exception at XamlReader.Load line which is normal since the debug output tells that the uncompressed stream has a zero length.

Compress
 uncompressed.Length: 123
 compressed.Length: 202
Decompress
 compressed.Length: 202
 uncompressed.Length: 0

Why doesn't the final uncompressed stream contain the original 123 bytes?

(Please ignore the fact that the "compressed" byte array is bigger than the "uncompressed" byte array - I'll normally be working with much bigger flow documents)

like image 793
Tom Hunter Avatar asked Aug 11 '12 14:08

Tom Hunter


2 Answers

You need to close the GZipStream before getting the compressed bytes from the memory stream. In this case the closing is handled by the Dispose called due to the using.

using (var compressed = new MemoryStream())
{
    using (var compressor = new GZipStream(compressed, CompressionMode.Compress))
    {
        uncompressed.CopyTo(compressor);
    }
    // Get the compressed bytes only after closing the GZipStream
    compressedBytes = compressed.ToArray();
}

This works and you could even remove the using for the MemoryStream since it will be disposed by the GZipStream unless you use the constructor overload that allows you to specify that the underlying stream should be left open. This implies with that code you are calling ToArray on a disposed stream but that is allowed because the bytes are still available which makes disposing memory streams a bit weird but if you don't do it FXCop will annoy you.

like image 120
João Angelo Avatar answered Oct 10 '22 13:10

João Angelo


Joao's answer did the trick. I've copied the full working example below. I've added a line to output compressedData.Length. Interestingly this outputs 218 bytes, whereas compressedStream.Length outputs only 202 bytes. If you don't close the GZipStream before reading the byte array then compressedData.Length is 202. I'm not sure why closing the GZipStream gives you an extra 16 bytes..

var flowDocumentIn = new FlowDocument();
flowDocumentIn.Blocks.Add(new Paragraph(new Run("Hello")));

Debug.WriteLine("Compress");

byte[] compressedData;

using (var uncompressedStream = new MemoryStream())
{
    XamlWriter.Save(flowDocumentIn, uncompressedStream);
    uncompressedStream.Position = 0;
    using (var compressedStream = new MemoryStream())
    {
        using (var gZipCompressor = new GZipStream(compressedStream, CompressionMode.Compress))
        {
            Debug.WriteLine(" uncompressedStream.Length: " + uncompressedStream.Length);
            uncompressedStream.CopyTo(gZipCompressor);
            Debug.WriteLine(" compressedStream.Length: " + compressedStream.Length);
        }
        compressedData = compressedStream.ToArray();
    }
}

Debug.WriteLine(" compressedData.Length: " + compressedData.Length);

Debug.WriteLine("Decompress");

FlowDocument flowDocumentOut;

using (var compressedStream = new MemoryStream(compressedData))
using (var uncompressedStream = new MemoryStream())
{
    using (var gZipDecompressor = new GZipStream(compressedStream, CompressionMode.Decompress))
    {
        Debug.WriteLine(" compressedStream.Length: " + compressedStream.Length);
        gZipDecompressor.CopyTo(uncompressedStream);
        Debug.WriteLine(" uncompressedStream.Length: " + uncompressedStream.Length);
    }
    uncompressedStream.Position = 0;
    flowDocumentOut = (FlowDocument)XamlReader.Load(uncompressedStream);
}

Debug output:

Compress
 uncompressedStream.Length: 123
 compressedStream.Length: 202
 compressedData.Length: 218
Decompress
 compressedStream.Length: 218
 uncompressedStream.Length: 123

Note also the additional uncompressedStream.Position = 0; before the call to XamlReader.Load.

like image 5
Tom Hunter Avatar answered Oct 10 '22 13:10

Tom Hunter