Дата публикации статьи: 02.10.2004 16:32

Гайдар Магдануров
Переход на VB.NET, статья №5
Работа с файлами

Web версия

  • Введение
  • Текстовые файлы
  • Двоичные файлы и файлы произвольного доступа
  • Заключение
  • Примечания
  • Скачать исходный код к статье

  • Введение

        Допустим, приложение, написанное на 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) ' Ошибка!