Почему? Ошибка C# после рефакторинга: «Поток недействителен или соответствующая подпись не найдена».

Это следующий вопрос отсюда: Распаковать поток в строку с помощью SevenZipSharp .

Следующий код работает в том смысле, что он берет строку и успешно сжимает и распаковывает ее.

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();
        }
    }
}

Однако приведенный выше код, очевидно, мало что дает в его нынешнем виде (ему суждено стать COM-библиотекой DLL).

Я думал, что я дома и в шланге, и что рефакторинг будет несложным. Однако мои попытки преобразовать код во что-то полезное оставили меня в недоумении относительно того, почему следующий код генерирует исключение System.ArgumentException («Поток недействительна или соответствующая подпись не найдена.'), но приведенный выше код работает без проблем.

В принципе должна быть какая-то разница, но я не понимаю, что ее вызывает и как решить. Решение, сопровождаемое кратким объяснением, будет высоко оценено.

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();
        }
    }
}

Примечание. Хотя я понимаю, что с этим кодом, который я изучаю, может быть несколько проблем, я намерен опубликовать следующую итерацию на CodeReview. Тем не менее, не стесняйтесь предлагать предложения, но сейчас это учебное упражнение для меня. ТИА


person SlowLearner    schedule 20.05.2018    source источник


Ответы (2)


Ваш рабочий код распаковал compressedMemoryStream. Ваш неработающий код распаковал строку, созданную из compressedMemoryStream.

Как я уже говорил в предыдущем вопросе, результат сжатия не является текстом. Если вы должны представить его в виде текста, вы должны использовать Base64 или шестнадцатеричный формат. Но простое чтение из него, как если бы это был текст в кодировке UTF-8 (что вы и делаете сейчас), просто не сработает.

Результатом вашего метода StreamCompress, вероятно, должен быть файл byte[]. Этого легко добиться:

// 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();
}

Когда вы хотите распаковать, вы можете просто создать MemoryStream для переноса этого массива байтов, как один из многих вариантов.

Если вам действительно нужен результат в виде строки, вы можете вызвать Convert.ToBase64String, а затем вызвать Convert.FromBase64String, чтобы вернуть исходные байты. Это не приведет к потере информации, в отличие от вашего текущего подхода.

Я также должен отметить, что если вы не хотите сжатия, специфичного для 7zip, также доступно множество чисто управляемых библиотек сжатия.

person Jon Skeet    schedule 20.05.2018
comment
Спасибо @Дейзи. Я думаю, что слишком долго смотрел на это, пока все мое программирование находится в VBA, и причина, по которой я зациклен на строках, заключается в том, что я новичок в ООП, и я планирую вызывать это из VBA, где это уже будет существовать в виде строки; следовательно, преобразование в поток. В любом случае, в свете ваших комментариев я найду время, чтобы снова сравнить рабочий код с неработающим кодом. Ваша помощь очень ценится :) - person SlowLearner; 20.05.2018
comment
Кстати, причина, по которой я использую 7z, заключается в том, что он предлагает некоторую защиту паролем, которую я хотел бы использовать; кроме того, это удобно, поскольку я мог бы предположительно распаковывать файлы без необходимости запуска «моего» кода, если бы у меня была копия 7zip под рукой. Помня об этих двух вещах, я с удовольствием приму рекомендации, но я также хочу узнать, как заставить все вышеперечисленное работать. Спасибо еще раз :) - person SlowLearner; 20.05.2018
comment
@SlowLearner: я считаю, что большинство библиотек сжатия также включают защиту паролем. Но, надеюсь, приведенное выше поможет вам — как я уже сказал, если вам действительно нужен текст, используйте представление сжатых данных в формате base64. - person Jon Skeet; 20.05.2018
comment
WRT: base64, должен ли я конвертировать в base64 непосредственно из потока памяти? В конечном итоге сжатый текст будет храниться как элемент в файле xml/json, поэтому вы можете увидеть помощь Base64. - person SlowLearner; 20.05.2018
comment
@SlowLearner: проще всего преобразовать в массив байтов (как я показал) и преобразовать его в base64, используя Convert.ToBase64String. - person Jon Skeet; 20.05.2018
comment
Спасибо - очень ценю вашу помощь :) - person SlowLearner; 20.05.2018
comment
О, пожалуйста, поправьте меня, если я ошибаюсь - я предполагаю, что для распаковки все шаги будут выполняться в обратном порядке... - person SlowLearner; 20.05.2018
comment
@SlowLearner: Да, именно так. Вы берете строку, вызываете Convert.FromBase64String, чтобы получить byte[], затем передаете ее конструктору MemoryStream, чтобы получить поток, готовый к распаковке. - person Jon Skeet; 20.05.2018
comment
Теперь все работает, спасибо, что остаетесь со мной. VBA, к которому я привык, не использует конструкторы, и я, наконец, понимаю, что мне нужно было сделать это: Byte[] compBytes = Convert.FromBase64String(base64); MemoryStream compStreamIn = new MemoryStream(compBytes); Я понимаю, почему вы любите программировать на C# :) - person SlowLearner; 21.05.2018

С# сжимать и распаковывать строку с помощью SevenZipSharp

Основная проблема заключалась в том, что я неправильно использовал строки, преобразование потока памяти в строку не работает. Решение использует кодировку base64, чтобы сделать сжатую строку переносимой; это позволяет хранить его в файле XML/JSON, который соответствует моим потребностям. Спасибо @Daisy Shipton (см.: этот ответ).

Исходя из VBA, использование конструкторов (передача аргумента при обновлении) не было сразу очевидным, но это помогает. Это был ключ:

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

В надежде, что это поможет кому-то другому:

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;
        }
    }
}
person SlowLearner    schedule 21.05.2018