Допустим, приложение, написанное на 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 не
определяет тип переданного массива во время выполнения, если массив не
был инициализирован предварительно. Сразу же рассмотрим примеры.
Для "пущей наглядности" я определил специальную структуру. Вот что мы
имеем для двоичного файла:
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)
Чтобы исправить эту ошибку достаточно
инициализировать массив с хотя бы одним элементом.
Например так: Теперь мы можем прочитать данные, но при этом
легко заметить, что вместо ожидаемых пар значений 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) ' Ошибка! |