Работа с файлами в Visual Basic (исходники)

Источник: vbstreets
Гайдар Магдануров

Введение

    Допустим, приложение, написанное на Visual Basic перенесено на платформу .NET  и работает, что самое забавное, даже так, как надо. Но, всегда есть какое-нибудь "но", необходимо обеспечить совместимость .NET версии приложения с более ранними версиями, если, конечно, вы не хотите потерять часть пользователей предыдущих версий продукта, не желающих переносить данные из одной версии в другую. Очевидно, что для этого необходимо оставить возможность работать с файлами, созданными предыдущими версиями.
    Если .NET версия является точной копией VB6 приложения (с точностью до реализации), то необходимо добиться идентичности создаваемых файлов, если же .NET версия является очередной ступенью развития программного продукта, то необходимо сохранить возможность читать/создавать/редактировать файлы предыдущих версий.
    Во втором случае существует два основных метода: встроенные преобразователь файлов ранних форматов в новый, при этом будет неправильно не оставить возможность сохранять файлы в старый формат (желательно без потери информации, если это возможно), либо полная поддержка файлов старого формата параллельно с файлами нового формата.
    Так или иначе, вам предстоит добиться совместимости форматов файлов, создаваемых приложением, написанным на Visual Basic, и приложением, написанным на Visual Basic .NET. Об этом и пойдет речь в данной статье.

Текстовые файлы

    Большинство данных предназначенных для просмотра и редактирования с помощью текстовых редакторов типа Notepad, записывается в текстовые файлы в "приятном" для пользователя формате, что не исключает дальнейшей обработки этих файлов с помощью специально для этого предназначенных программ. Примером может являться ПО для квантово-химических расчетов Gaussian, создающее "выходные" файлы в текстовом формате, которые, в дальнейшем могут быть использованы программами GaussView, ChemOffice, HyperChem и др.
    При переходе на .NET необходимо сохранить тот же формат вывода данных в текстовые файлы, для этого достаточно использовать команды записи в файл, совпадающие по своему действию с командами использовавшимися в Visual Basic 6.
    К счастью Visual Basic .NET поддерживает синтаксис аналогичный Open ... Close.

 Visual Basic 6  Visual Basic .NET  Действие
 Print #fFile, "Some text"  PrintLine(fFile, "Some text") Записывает строку в файл и добавляет символ новой строки.
 Print #fFile, "Some text";  Print(fFile, "Some text") Записывает строку в файл.
 Write #fFile, "Some text"  WriteLine(fFile, "Some text") Записывает строку в файл, добавляя двойные кавычки в начале и конце строки и символ новой строки.
 Write #fFile, "Some text";  Write(fFile, "Some text") Записывает строку в файл, добавляя двойные кавычки в начале и конце строки

    Upgrade Wizard автоматически заменяет команды и вам нет нужды делать это самостоятельно, но необходимо помнить о всех возможностях при "ручной" миграции кода.

Вот два эквивалентных фрагмента кода на VB6 и VB.NET.

VB6

Dim fFile As Integer
fFile = FreeFile

Open C_PATH & "myTextFile.txt" For Output As #fFile
Print #fFile, "Print line 1"
Print #fFile, "Print line 2;";
Write #fFile, "Write line 3"
Write #fFile, "Write line 4;";
Close #fFile

MsgBox FileLen(C_PATH & "myTextFile.txt") & " bytes were written to " & _
C_PATH & "myTextFile.txt"
VB.NET

Dim fFile As Short
fFile = FreeFile

FileOpen(fFile, C_PATH & "myTextFile.txt", OpenMode.Output)
PrintLine(fFile, "Print line 1")
Print(fFile, "Print line 2;")
WriteLine(fFile, "Write line 3")
Write(fFile, "Write line 4;")
FileClose(fFile)

MsgBox(FileLen(C_PATH & "myTextFile.txt") & " bytes were written to " & _
C_PATH & "myTextFile.txt")

    Никаких проблем с текстовыми файлами не возникает, поскольку функции VB.NET позволяют работать со строками при записи в файл также как и VB6. "Необычайно странно, что парни из Microsoft не накидали здесь "подводных" камней!", - скажете вы. Да, они очень о вас заботятся.

Двоичные файлы и файлы произвольного доступа

    Еще больше удивления у вас, наверное, вызовет тот факт, что и с двоичными файлами ситуация обстоит также как и с текстовыми. Ага! Вы уже успели обрадоваться, что ж, здесь-то несколько "валунов" притоплено на вашем пути.
    Прежде всего "старые новые" функции Get и Put, использовавшиеся для записи в двоичные файлы (Binary) и файлы произвольного доступа (Random) теперь называются FileGet и FilePut и ведут себя немного иначе. Когда вы записываете строки переменной длины или динамические массивы данных в файлы произвольного доступа, автоматически добавляется заголовок, определяющий длину, из двух байт.
    Также, FileGet не определяет тип переданного массива во время выполнения, если массив не был инициализирован предварительно.
    Сразу же рассмотрим примеры. Для "пущей наглядности" я определил специальную структуру. Вот что мы имеем для двоичного файла:

VB6

' Type
Private Type DataFile
FileName As String
SomeNumber As Integer
LongNumber As Long
BinaryData(128) As Byte
End Type
' Code
Dim fFile As Integer, i As Integer
Dim mData As DataFile

fFile = FreeFile
mData.FileName = "MyBinaryFile"
mData.SomeNumber = 12345
mData.LongNumber = 1234567890
For i = 0 To UBound(mData.BinaryData)
mData.BinaryData(i) = i
Next i

Open C_PATH & "myBinaryFile.bin" For Binary As #fFile
Put #fFile, , mData
Close #fFile

MsgBox FileLen(C_PATH & "myBinaryFile.bin") & " bytes were written to " & _
C_PATH & "myBinaryFile.bin"

VB.NET
Dim fFile, i As Short
Dim mData As DataFile
mData.Initialize()


fFile = FreeFile
mData.FileName = "MyBinaryFile"
mData.SomeNumber = 12345
mData.LongNumber = 1234567890

For i = 0 To UBound(mData.BinaryData)
mData.BinaryData(i) = i
Next i



FileOpen(fFile, C_PATH & "myBinaryFile.bin", OpenMode.Binary)
FilePut(fFile, mData)
FileClose(fFile)

MsgBox(FileLen(C_PATH & "myBinaryFile.bin") & " bytes were written to " & _
C_PATH & "myBinaryFile.bin")

    Все вполне логично и понятно без каких-либо комментариев. Единственная вещь, за которой нужно следить дополнительно при переносе кода в ручную - размер типов Short и Integer, Integer и Long и т.д. в VB6 и VB.NET соответственно (смотрите предыдущие статьи)
    Могу вас уверить, что и с файлами произвольного доступа будет тоже самое (в прилагаемом к статье примере можно посмотреть и на Random файлы). В следующем же примере я продемонстрирую различия о которых писал выше.

Для начала пример на VB6:

Private Type DataFile
SomeString As String
SomeInteger As Integer
SomeLong As Long
ByteArray() As Byte
End Type

'Запись
Dim fFile As Integer, i As Integer
Dim mData As DataFile
Dim myBinArray() As Byte

ReDim mData.ByteArray(10)
ReDim myBinArray(UBound(mData.ByteArray))

fFile = FreeFile

mData.SomeInteger = 10
mData.SomeLong = 100
mData.SomeString = "Simple String"

For i = 0 To UBound(mData.ByteArray)
mData.ByteArray(i) = i
myBinArray(i) = i
Next i


Open C_PATH & "RandomFile.rnd" For Random As #fFile Len = 256
Put #fFile, , mData
Put #fFile, , myBinArray
Close #fFile

' Чтение
Dim fFile As Integer
Dim mData As DataFile
Dim myBinArray() As Byte

fFile = FreeFile

Open C_PATH & "RandomFile.rnd" For Random As #fFile Len = 256
Get #fFile, , mData
Get #fFile, , myBinArray
Close #fFile

    Вы еще помните, о чем я писал, обсуждая поведение функции FileGet? Обычно в VB6 для чтения массива данных функции Get передается именно неинициализированный массив, как это и сделано в примере выше.

Dim myBinArray() As Byte
...
Get #fFile, , myBinArray

В VB.NET это вызовет ошибку, в чем мы и убедимся, передав проект на съедение Upgrade Wizard. Рассмотрим полученный код:

Dim fFile As Short
Dim mData As DataFile
Dim myBinArray() As Byte

fFile = FreeFile


FileOpen(fFile, C_PATH & "RandomFile.rnd", OpenMode.Random, , , 256)
FileGet(fFile, mData)
FileGet(fFile, myBinArray) ' ошибка
FileClose(fFile)

    Чтобы исправить эту ошибку достаточно инициализировать массив с хотя бы одним элементом. Например так:

ReDim myBinArray(0)

    Теперь мы можем прочитать данные, но при этом легко заметить, что вместо ожидаемых пар значений i = myBinArray(i) мы видим i <> 0 (например, 0 = 0, 1=1, 2=22 и т.п.). Вот это как раз и есть следствие того, что FileGet рассчитывает прочитать заголовок с указанием числа элементов в массиве. Достаточно указать, что необходимо считывать динамический массив, установив ArrayIsDynamic = True, как все наши проблемы снимаются.

FileGet(fFile, myBinArray, ,True)

Соответственно то же самое надо сделать и в FilePut.

    Примерно такая же проблема возникает при работе со строками фиксированной длины - файлы созданные из VB6 неверно считываются в VB.NET. Например, следующий код записи и чтения в VB6 прекрасно работает, но, в VB.NET могут возникнуть сложности.

VB6
'Запись
Dim strText As String * 6
strText = "Gaidar"


Dim fFile As Integer
fFile = FreeFile


Open C_PATH & "TextFile.txt" For Random As #fFile Len = 256
Put #fFile, , strText
Close #fFile

txtText.Text = strText

' Чтение
Dim strText As String * 6

Dim fFile As Integer
fFile = FreeFile

Open C_PATH & "TextFile.txt" For Random As #fFile Len = 256
Get #fFile, , strText
Close #fFile

txtText.Text = strText

VB.NET
' запись
Dim strText As New VB6.FixedLengthString(6)
strText.Value = "Gaidar"


Dim fFile As Short
fFile = FreeFile


FileOpen(fFile, C_PATH & "TextFile.txt", OpenMode.Random, , , 256)
FilePut(fFile, strText.Value)
FileClose(fFile)

txtText.Text = strText.Value

' чтение
Dim strText As String * 6


Dim fFile As Integer
fFile = FreeFile


Open C_PATH & "TextFile.txt" For Random As #fFile Len = 256
Get #fFile, , strText
Close #fFile

txtText.Text = strText

    Причина в том, что VB.NET, записывая строку, считает ее строкой переменной длины и добавляет заголовок указывающий на ее размер.  В этом случае решение не сложнее, чем в предыдущем. Установка StringIsFixedLength решит эту проблему.

FileGet(fFile, strText.Value, , True)
Заключение

    Как вы могли убедится прочитав статью и посмотрев прилагающиеся примеры, чтобы продолжать работать с файлами, созданными в приложениях написанных на VB6 в VB.NET вам не придется затрачивать много усилий. Надо всего один раз внимательно проверить ваш код на наличие строк фиксированной длины и динамических массивов.
    Благодаря тому, что синтаксис типа Open ... Close все еще поддерживается, вам не придется пытаться даже работать с потоками (streams) о которых я расскажу в следующих статьях.

 

Примечания

Типы файлов

    Для новичков в программировании я расскажу о разных типах файлов. Начнем с наиболее очевидного - текстовых. Текстовый файл содержит символы "пригодные" для чтения человеком, тот самый текст, который так легко и приятно редактировать. Файлы с исходными текстами и в VB6 и в VB .NET являются текстовыми (более того, открою вам секрет, даже исходные файлы C++ тоже текстовые , простите, не сдержался).
    Текстовые файлы являются файлами последовательного доступа, то есть в них выполняется либо чтение информации, либо запись в файл (именно поэтому при открытии файла в Open ... Close указывается Input или Output). То есть, если файл открыт на запись, то из него ничего прочитать нельзя, если же он открыт на чтение, то ничего записать в него не получится, соответственно.
    Двоичный файл (Binary) содержит не только читаемые человеком символы, но и символы, имеющие смысл только для машины. Они предназначены для хранения нетекстовых данных (все, что не есть текстовый файл - есть двоичный файл).
    Файл произвольного доступа (Random) может содержать и текстовые и двоичные записи, этакую смесь. Вообще-то, это тоже двоичный файл, но у него есть одно отличие от "обычного" двоичного файла - запись в нем имеет определенную длину (то есть за каждый сеанс записи вы можете записать не более и не менее символов, чем заданная длина записи, если вы запишете меньше данных, остаток будет заполнен нулями автоматически).
    Двоичные файлы и файлы произвольного доступа являются файлами именно произвольного доступа. (К сожалению здесь есть некоторая проблема с терминологией, но что есть, то есть). То есть, открыв файл (Open) вы можете и писать и читать информацию из него, указывая позицию в файле из которой вы хотите читать/писать.

Схематично содержимое файлов разных типов можно представить так (х - любой символ, текстовый или двоичный):

Текстовый файл или двоичный файл

xxx xxx xxx xxxx
xxxx xxxxx xxxx xxxx
    xxxxx xxxxxxxxxxxxx
xxxxx
xxxxxxxxxxxxxxx

Файл произвольного доступа

xxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxx
 
Динамические массивы

    Насколько мне известно, подавляющая часть программистов не могут объяснить, чем отличается поведение динамического массива, от статического массива (с жестко заданным размером - числом элементов). Пожалуй, сейчас как раз тот случай, когда необходимо прояснить этот момент.
    Создадим два массива:

Dim StaticArray(256) As Byte ' статический массив
Dim DynamicArray() As Byte ' динамический массив

    Теперь у нас есть один массив с 256 элементами и один формально "пустой массив", для заполнения которого нужно использовать ReDim.

ReDim DynamicArray(256) ' теперь здесь тоже 256 элементов

    В дальнейшем мы можем всегда переопределить размер динамического массива по нашему усмотрению используя ReDim и ReDim Preserve, но мы не можем переопределить размер статического массива. Хотя, казалось бы, у этих двух массивов много общего.

ReDim DynmicArray(512) ' OK
ReDim StaticArray(512) ' Ошибка!

Страница сайта http://www.interface.ru
Оригинал находится по адресу http://www.interface.ru/home.asp?artId=22016