0

This is a follow-on question from here: Decompress Stream to String using SevenZipSharp.

The following code works in the sense that it takes a string and successfully compresses and decompresses it.

using System;
using System.IO;
using SevenZip;

namespace _7ZipWrapper
{
    public class ProgramOriginal
    {
        public static void Main()
        // This should be broken into separate methods
        {
            // Setup Input String
            var strToCompress = "This String"; // will pass as parameter
            var memStreamToCompress = new MemoryStream();

            var StringToStream = new StreamWriter(memStreamToCompress);
            StringToStream.Write(strToCompress);
            StringToStream.Flush();

            // Confirm the Input Stream is As-Expected
            memStreamToCompress.Position = 0;
            var MemoryAsString = new StreamReader(memStreamToCompress);
            Console.WriteLine("Stream in memory: " + MemoryAsString.ReadToEnd());
            Console.ReadKey();

            // Setup the SevenZip Dll
            SevenZipCompressor.SetLibraryPath(@"C:\Temp\7za64.dll");

            // Set up a compressor...
            SevenZipCompressor SevenZipC = new SevenZipCompressor();
            SevenZipC.CompressionMethod = CompressionMethod.Ppmd;
            SevenZipC.CompressionLevel = global::SevenZip.CompressionLevel.Ultra;
            SevenZipC.ScanOnlyWritable = true;

            // Compress PassedStream -> CompressedStream
            var compressedMemoryStream = new MemoryStream();
            SevenZipC.CompressStream(memStreamToCompress, compressedMemoryStream, "Optional Password Field");
            compressedMemoryStream.Position = 0;
            StreamReader compressedMemoryAsString = new StreamReader(compressedMemoryStream);

            // Show that we have a compressed String
            compressedMemoryStream.Position = 0;
            Console.WriteLine(compressedMemoryAsString.ReadToEnd());
            Console.ReadKey();

            // Set up a decompressor... (only needs to know what to decompress)
            compressedMemoryStream.Position = 0;
            var SevenZipE = new SevenZip.SevenZipExtractor(compressedMemoryStream, "Optional Password Field");

            // Decompress CompressedStream
            var DecompressedMemoryStream = new MemoryStream();
            SevenZipE.ExtractFile(0, DecompressedMemoryStream);

            // Show that DecompressedMemoryStream is valid
            DecompressedMemoryStream.Position = 0;
            StreamReader decompressedStreamAsText = new StreamReader(DecompressedMemoryStream);
            Console.WriteLine("Decompressed String: " + decompressedStreamAsText.ReadToEnd());
            Console.ReadKey();
        }
    }
}

However, the above code obviously does little good in its current form (this is destined to become a COM DLL).

I thought I was home-and-hosed and that refactoring would be a cinch, However, my attempts to rearranging the code into something useful have left me at a loss as to why the following code throws a System.ArgumentException ('The stream is invalid or no corresponding signature was found.') but the code above runs without issue.

Fundamentally there must be some sort of difference but I'm at a loss as to what causes it, and how to resolve. A solution accompanied with a brief explanation will be much appreciated.

using System;
using System.IO;
using SevenZip;

namespace _7ZipWrapper
{
    public class Program
    {
        public static void Main()
        {
            // Setup Input String
            var strToCompress = "This String"; // will eventually pass as parameter


            // Convert input string to memory stream
            var memStreamToCompress = StringToStream(strToCompress);


            // Confirm the Input Stream is As-Expected
            memStreamToCompress.Position = 0;
            var MemoryAsString = new StreamReader(memStreamToCompress);
            Console.WriteLine("Stream in memory: " + MemoryAsString.ReadToEnd());
            Console.ReadKey();

            // Compress the Stream
            memStreamToCompress.Position = 0;
            var compressedString = StreamCompress(memStreamToCompress, "password");

            // Decompress the String
            var memStreamToRestore = StringToStream(compressedString);
            memStreamToRestore.Position = 0;

            var decompressedString = StreamDecompress(memStreamToRestore, "password");

            Console.WriteLine(decompressedString);
            Console.ReadKey();
        }

        private static MemoryStream StringToStream(string strToMemoryStream)
        {
            var memoryStream = new MemoryStream();
            var writer = new StreamWriter(memoryStream);
            writer.Write(strToMemoryStream);
            writer.Flush();
            return memoryStream;
        }

        private static MemoryStream StringToStream1(string strToMemoryStream)
        {
            var memoryStream = new MemoryStream();
            var writer = new StreamWriter(memoryStream);
            writer.Write(strToMemoryStream);
            writer.Flush();
            return memoryStream;
        }


        private static string StreamCompress(MemoryStream memStreamToCompress, string optionalPassword)
        {
            // Setup the SevenZip Dll
            SevenZipCompressor.SetLibraryPath(@"C:\Temp\7za64.dll");

            // Set up a compressor...
            SevenZipCompressor SevenZip = new SevenZipCompressor();
            SevenZip.CompressionMethod = CompressionMethod.Ppmd;
            SevenZip.CompressionLevel = global::SevenZip.CompressionLevel.Ultra;
            SevenZip.ScanOnlyWritable = true;

            // Compress PassedStream -> CompressedStream
            var compressedMemoryStream = new MemoryStream();
            SevenZip.CompressStream(memStreamToCompress, compressedMemoryStream, optionalPassword); // "Optional Password Field"
            compressedMemoryStream.Position = 0;
            StreamReader compressedMemoryAsString = new StreamReader(compressedMemoryStream);
            return compressedMemoryAsString.ReadToEnd();
        }

        private static string StreamDecompress(MemoryStream compressedMemoryStream, string optionalPassword)
        {
            // Setup the SevenZip Dll
            SevenZipExtractor.SetLibraryPath(@"C:\Temp\7za64.dll");

            // Set up a decompressor... (only needs to know what to decompress)
            compressedMemoryStream.Position = 0;
            // CRASHES Next Line: System.ArgumentException: 'The stream is invalid or no corresponding signature was found.'
            var SevenZip = new SevenZip.SevenZipExtractor(compressedMemoryStream, optionalPassword);  

            // Decompress CompressedStream
            var DecompressedMemoryStream = new MemoryStream();
            SevenZip.ExtractFile(0, DecompressedMemoryStream);

            // Show that DecompressedMemoryStream is valid
            DecompressedMemoryStream.Position = 0;
            StreamReader decompressedStreamAsText = new StreamReader(DecompressedMemoryStream);
            return decompressedStreamAsText.ReadToEnd();
        }
    }
}

Note: While I appreciate there may be multiple issues with this code I'm learning and intend to put the next iteration up on CodeReview. That said, feel free to offer suggestions but right now this is a learning exercise for me. TIA

SlowLearner
  • 3,086
  • 24
  • 54

2 Answers2

1

Your working code decompressed compressedMemoryStream. Your broken code decompressed a string created from compressedMemoryStream.

As I said in your previous question, the result of compression is not text. If you must represent it as text, you should use Base64 or hex. But just reading from it as if it's UTF-8-encoded text (which is what you're doing now) simply will not work.

The result of your StreamCompress method should probably be a byte[]. That's easy to achieve:

// Note: changed input type to just Stream to make it more general
private static byte[] StreamCompress(Stream input, string optionalPassword)
{
    SevenZipCompressor.SetLibraryPath(@"C:\Temp\7za64.dll");
    SevenZipCompressor SevenZip = new SevenZipCompressor();
    SevenZip.CompressionMethod = CompressionMethod.Ppmd;
    SevenZip.CompressionLevel = global::SevenZip.CompressionLevel.Ultra;
    SevenZip.ScanOnlyWritable = true;

    var output = new MemoryStream();
    SevenZip.CompressStream(input, output, optionalPassword);
    // You don't even need to rewind when you call ToArray
    return output.ToArray();
}

When you want to decompress, you can just create a MemoryStream to wrap that byte array, as one of many options.

If you really need the result as a string, you can then call Convert.ToBase64String, then later call Convert.FromBase64String to get back the original bytes. This will not lose information, unlike your current approach.

I should also point out that unless you want 7zip-specific compression, there are plenty of purely-managed compression libraries available too.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thank you @Daisy. I think I've been staring at this for too long, so far all my programming is in VBA and the reason I'm fixated on strings is because I'm new to OOP and I plan to call this from VBA where it will already exist as a string; hence the convert to stream. Anyway, in light of your comments I'll take the time to compare the working code with the broken code again. Your help is much appreciated :) – SlowLearner May 20 '18 at 06:38
  • BTW - the reason I'm using 7z is because it offers some password protection which I'd like to use; other than that it is convenient as I could _presumably_ decompress files without needing to run 'my' code if I had a copy of 7zip at hand. With these 2 things in mind I'm happy to take recommendations - but I do also want to learn how to make the above work. Thanks again :) – SlowLearner May 20 '18 at 06:41
  • 1
    @SlowLearner: I believe most compression libraries include password protection as well. But hopefully the above will get you going - as I said, if you really need text, use a base64 representation of the compressed data. – Jon Skeet May 20 '18 at 06:44
  • WRT: base64, should I convert to base64 directly from the memory stream? Ultimately the compressed text will be stored as an element in an xml/json file so can see Base64 helping there. – SlowLearner May 20 '18 at 14:27
  • 1
    @SlowLearner: It's simplest to convert to a byte array (as I've shown) and convert that to base64 using `Convert.ToBase64String`. – Jon Skeet May 20 '18 at 14:31
  • Thank you - really appreciate your help :) – SlowLearner May 20 '18 at 14:39
  • Oh, pls correct me if wrong - I'm assuming that to decompress all steps would be done in reverse order... – SlowLearner May 20 '18 at 14:43
  • 1
    @SlowLearner: Yes, exactly. You take the string, call `Convert.FromBase64String` to get a `byte[]`, then pass that to the `MemoryStream` constructor to get a stream ready to decompress. – Jon Skeet May 20 '18 at 15:33
  • It's working now, thank you for sticking with me. VBA, what I'm used to, does not use constructors and I finally understand that I needed to do this: `Byte[] compBytes = Convert.FromBase64String(base64); MemoryStream compStreamIn = new MemoryStream(compBytes);` I can see why you "Love coding in C#" :) – SlowLearner May 21 '18 at 00:10
0

C# compress and decompress string using SevenZipSharp

Basic problem was that I was not using strings properly, converting memory stream to string does not work. Solution uses base64 encoding to make the compressed string portable; this enables it to be stored in an XML / JSON file which suits my needs. Thank you @Daisy Shipton (see: this answer).

Coming from VBA the use of constructors (passing an argument while newing) was not immediately obvious, but this helps. This was the key:

// Create a memory stream from the input: base64 --> bytes --> memStream
Byte[] compBytes = Convert.FromBase64String(input);
MemoryStream compStreamIn = new MemoryStream(compBytes);

In the hopes that this helps someone else:

using System;
using System.IO;
using System.Text;
using SevenZip;

namespace _7ZipWrapper
{
    public class ProgramToModify
    {
        public static void Main()
        {
            var input = "Some string"; // Input String will pass as parameter

            var compressed = MyEncode(input);
            Console.WriteLine("Compressed String: " + compressed);

            var decodedString = myDecode(compressed);
            Console.WriteLine("Decompressed String: " + decodedString);
            Console.ReadKey();
        }

        // Returns compressed and encoded base64 string from input 
        private static String MyEncode(string input) 
        {
            // Setup the SevenZip Dll
            SevenZipCompressor.SetLibraryPath(@"C:\Temp\7za64.dll");
            SevenZipCompressor SevenZipC = new SevenZipCompressor();
            SevenZipC.CompressionMethod = CompressionMethod.Ppmd;
            SevenZipC.CompressionLevel = global::SevenZip.CompressionLevel.Ultra;
            SevenZipC.ScanOnlyWritable = true;

            var memoryStream = new MemoryStream(); // Create Memory Stream
            var streamWriter = new StreamWriter(memoryStream);
            streamWriter.Write(input); // streamWriter writes input to memoryStream
            streamWriter.Flush();

            // Compress: memoryStream -> cmpdMemoryStream
            var cmpdMemoryStream = new MemoryStream();
            SevenZipC.CompressStream(memoryStream, cmpdMemoryStream, "Optional Password Field");

            Byte[] bytes = cmpdMemoryStream.ToArray();
            return Convert.ToBase64String(bytes);
        }


        // Returns plain string from compressed and encoded input
        private static String myDecode(string input) 
        {
            // Create a memory stream from the input: base64 --> bytes --> memStream
            Byte[] compBytes = Convert.FromBase64String(input);
            MemoryStream compStreamIn = new MemoryStream(compBytes);

            SevenZipExtractor.SetLibraryPath(@"C:\Temp\7za64.dll");
            var SevenZipE = new SevenZip.SevenZipExtractor(compStreamIn, "Optional Password Field");

            var OutputStream = new MemoryStream();
            SevenZipE.ExtractFile(0, OutputStream);

            var OutputBase64 = Convert.ToBase64String(OutputStream.ToArray());
            Byte[] OutputBytes = Convert.FromBase64String(OutputBase64);
            string output = Encoding.UTF8.GetString(OutputBytes);
            return output;
        }
    }
}
Community
  • 1
  • 1
SlowLearner
  • 3,086
  • 24
  • 54