Смекни!
smekni.com

Работа с бинарными данными и реестром Windows на платформе .NET (стр. 4 из 7)

Аналогичным образом применяется класс AcedReaderStream, который также является потомком класса System.IO.Stream. Этот класс предназначен для чтения данных из потоков типа AcedMemoryReader и AcedStreamReader, реализующих интерфейс IAcedReader. Класс AcedReaderStream предназначен исключительно для чтения данных, поэтому его свойство CanRead возвращает значение True, а свойства CanWrite и CanSeek возвращают значение False. Вызов методов Read(), ReadByte(), Close() перенаправляется соответствующим методам объекта, реализующего интерфейс IAcedReader. При чтении свойства Position возвращается текущая позиция в потоке относительно начала данных. Свойство Length возвращает общее число байт, которое может быть прочитано из потока. В некоторых случаях количество байт, помещенное в поток, неизвестно. Тогда свойство Length возвращает значение -1. Попытка присвоения значения свойству Position или вызова одного из следующих методов: Seek(), SetLength(), Write(), WriteByte() заканчивается возникновением исключения типа System.NotSupportedException. Свойство Reader класса AcedReaderStream возвращает интерфейс IAcedReader, переданный в конструктор класса. Подробную информацию о свойствах и методах интерфейсов IAcedWriter, IAcedReader можно найти в файле Interfaces.cs исходного кода.

Класс AcedRegistry

AcedRegistry восполняет собой отсутствие в .NET Framework класса, подобного классу TRegistry в Borland Delphi. Его особенностью по сравнению со стандартным классом Microsoft.Win32.Registry является наличие специальных методов для помещения в реестр значений различного типа и для чтения соответствующих значений из реестра. Класс AcedRegistry включает методы для работы с данными, которые представлены значениями следующих типов: String, Byte[], Int32, Boolean, DateTime, Decimal, Double, Guid, Int64.

Работа с классом AcedRegistry начинается с вызова конструктора, который принимает три параметра: первый (registryBaseKey) – выбирает ветвь реестра, такую как HKEY_CURRENT_USER или HKEY_LOCAL_MACHINE; второй параметр (registryKey) указывает наименование ключа реестра, с которым предполагается работать; третий параметр задает режим работы: только чтение или чтение/запись. Если указанный ключ не существует, то при открытии его в режиме "только для чтения" ошибка не возникает. Тогда каждое обращение к функциям Get() для чтения значений ключа вернет False, а при вызове GetDef() будет возвращаться значение по умолчанию. При открытии ключа в режиме, допускающем запись, если он отсутствует, соответствующий ключ немедленно создается в реестре. Обратиться к объекту типа Microsoft.Win32.RegistryKey, представляющему открытый ключ реестра, можно через свойство RegistryKey класса AcedRegistry.

Перегруженный метод Put() предназначен для записи в реестр значений различного типа. Функция Get() считывает значение с указанным именем и сохраняет его в переменной, передаваемой как ref-параметр. Если запрашиваемое значение присутствует в реестре, функция возвращает True. Функция GetDef() отличается от Get() тем, что она возвращает прочитанное значение как результат функции. Если соответствующее значение отсутствует в реестре, GetDef() возвращает значение по умолчанию, которое задается вторым параметром при вызове этой функции.

В конце работы с экземпляром класса AcedRegistry для него обязательно надо вызвать метод Dispose(). При использовании языка C# удобно поместить создание класса AcedRegistry в блок using для гарантированного освобождения unmanaged-ресурсов.

Пример использования класса AcedRegistry:

private const string

DemoRegistryKey = "Software\AcedUtils.NET\Demo",

cfgStreamFileName = "StreamFileName",

cfgCompressionMode = "CompressionMode";

private static string _streamFileName = String.Empty;

private static AcedCompressionMode _compressionMode;

private static void LoadConfig()

{

using (AcedRegistry config = new AcedRegistry

(AcedBaseKey.CurrentUser, DemoRegistryKey, false))

{

config.Get(cfgStreamFileName, ref

_streamFileName);

_compressionMode = (AcedCompressionMode)

config.GetDef(cfgCompressionMode, 0);

}

}

private static void SaveConfig()

{

using (AcedRegistry config = new AcedRegistry

(AcedBaseKey.CurrentUser, DemoRegistryKey, true))

{

config.Put(cfgStreamFileName, _streamFileName);

config.Put(cfgCompressionMode, (int)

_compressionMode);

}} }

Данный пример взят из демонстрационного проекта, прилагаемого к статье. Значения статических полей _streamFileName и _compressionMode сохраняются в реестре методом SaveConfig() и считываются из реестра методом LoadConfig(). Тип AcedCompressionMode представляет собой перечисление, которое нужно привести к типу System.Int32, чтобы поместить его в реестр. После чтения из реестра с помощью GetDef() значение должно быть преобразовано обратно к типу AcedCompressionMode.

Описание демонстрационного проектаа

Чтобы проиллюстрировать различные способы работы с бинарными данными с помощью рассмотренных выше классов, разработано небольшое приложение, которое представляет собой примитивный аналог архиватора файлов. Верхняя часть главной формы используется для помещения в бинарный поток данных произвольных файлов. При нажатии на кнопку "Добавить файл" пользователю предлагается выбрать на диске файл, который будет добавлен в поток. После помещения в поток одного или нескольких файлов можно сохранить весь поток на диске в виде одного файла. Причем данные при этом могут быть упакованы и зашифрованы. Чтобы проверить механизм контроля целостности данных, можно немного повредить данные в выходном потоке при сохранении его на диске. Для этого нужно пометить опцию "Инвертировать третий байт". Нижняя часть формы позволяет загрузить данные потока из файла на диске. Если поток зашифрован, перед чтением с диска надо установить опцию "Расшифровывать с паролем" и указать соответствующий пароль в поле ввода. Кроме данных, в бинарном потоке сохраняются имена и размеры файлов, которые отображаются в соответствующем списке после чтения потока с диска. Отдельные файлы из этого списка можно пометить и выгрузить нажатием кнопки "Сохранить отмеченный файл".

Информация об имени файла потока, а также об использованном режиме сжатия сохраняется в реестре с помощью класса AcedRegistry и восстанавливается при следующем запуске программы. При добавлении очередного файла в бинарный поток сначала записывается его имя методом AcedMemoryWriter.WriteString(), потом длина файла в байтах методом AcedMemoryWriter.WriteInt32(), затем в потоке резервируется место для самих данных вызовом AcedMemoryWriter.Skip(). Фактическое заполнение данными происходит при вызове метода FileStream.Read(), в который передается ссылка на внутренний массив экземпляра класса AcedMemoryWriter, возвращаемый функцией GetBuffer(), и смещение, соответствующее длине потока до вызова метода Skip(). При сохранении потока на диске ключ шифрования получается как значение односторонней хеш-функции RipeMD-160 для строки символов, введенной пользователем в качестве пароля. Это значение преобразуется к типу System.Guid вызовом метода AcedRipeMD.ToGuid() и передается в метод ToArray() класса AcedMemoryWriter.

В момент загрузки потока с диска проверяется его контрольная сумма. Затем данные потока расшифровываются и для них проверяется сигнатура RipeMD-160, после чего данные распаковываются. Из потока читаются имена и размеры файлов методами AcedMemoryReader.ReadString() и AcedMemoryReader.ReadInt32(). Для каждого файла вычисляется значение сигнатуры RipeMD-160. Эта информация помещается в список типа ListView. Сами данные пропускаются вызовом метода AcedMemoryReader.Skip(). В свойстве Tag каждого элемента типа ListViewItem списка сохраняется соответствующее ему смещение данных относительно начала потока. Когда пользователь выбрал элемент списка и нажал кнопку "Сохранить отмеченный файл", поток позиционируется на начало методом AcedMemoryReader.Reset(), а затем текущая позиция смещается на число байт, соответствующее значению свойства Tag элемента списка. После создания соответствующего файла на диске, данные выгружаются в файл напрямую из бинарного потока, минуя промежуточные массивы. Для этого в метод FileStream.Write() передается ссылка на внутренний массив экземпляра класса AcedMemoryReader с указанием смещения, равного текущей позиции в потоке.

В этом приложении демонстрируются также способы работы с потоками на базе классов AcedStreamWriter и AcedStreamReader. При нажатии кнопок в нижней части главной формы приложения вызывается функция TestStreams(). Если параметр useAcedStreams этой функции равен False, в качестве основного хранилища данных используется стандартный класс System.IO.MemoryStream. Функция TestStreams() подготавливает некоторые разнотипные данные, а затем передает их в метод PutData(), который должен поместить их в поток типа System.IO.Stream (в данном случае MemoryStream). Метод PutData() ассоциирует экземпляр класса AcedStreamWriter с переданным в нее потоком типа Stream. При этом указывается, что сохраняемые данные должны сжиматься в режиме Fast и шифроваться с ключом, который передается как последний параметр в функцию AssignStream(). Затем данные помещаются в поток с помощью методов класса AcedStreamWriter. Последним вызываемым методом является Close(), которые помещает в поток все буферизованные изменения. После выхода из PutData() заполненный бинарный поток типа MemoryStream превращается в массив байт, а его размер в байтах выводится в окне сообщения. Следующим этапом работы функции TestStreams() является загрузка данных из потока MemoryStream, созданного на основе полученного массива байт. Чтение данные выполняется методом GetData() с помощью экземпляра класса AcedStreamReader, ассоциированного с потоком типа System.IO.Stream (в данном случае MemoryStream).

Если в параметре useAcedStreams функции TestStreams() передано значение True, в качестве хранилища данных вместо MemoryStream используется экземпляр класса AcedMemoryWriter. Так как метод PutData() работает только с потоками типа System.IO.Stream, необходимо создать класс-оболочку AcedWriterStream, который является потомком класса System.IO.Stream и в то же время инкапсулирует экземпляр класса AcedMemoryWriter. Ссылка на класс-оболочку передается в метод PutData(), и через него данные записываются в поток AcedMemoryWriter. В завершении, данные из AcedMemoryWriter превращаются в массив байт вызовом функции ToArray() аналогично предыдущему случаю. На этапе чтения данных в качестве хранилища выступает экземпляр класса AcedMemoryReader, который создается на основе полученного массива байт. Так как метод GetData() загружает данные только из потоков типа System.IO.Stream, создается класс-оболочка AcedReaderStream на основе экземпляра AcedMemoryReader. Ссылка на AcedReaderStream передается в метод GetData(). Таким образом, данные считываются из потока типа AcedMemoryReader посредством класса-оболочки AcedReaderStream, являющегося потомком класса System.IO.Stream.