Запись строки в текстовый файл на VB6: перезапись, дозапись и кодировки

РазделVisual Basic 6УровеньНовичок Чтение8 минОпубликовано ТегиVB6ФайлыFileSystemObject

Сохранить строку в файл на VB6 можно тремя способами. Разбираем, чем они отличаются, как дописывать текст без перечитывания файла и как не потерять данные при ошибке.

Главное. Для дозаписи используйте режим ForAppending или Open For Append, а не перечитывание файла; кодировку выбирайте осознанно, а файл закрывайте даже при ошибке.

Задача: сохранить готовую строку на диск

Лог операций, экспорт отчёта в CSV, сохранение настроек, выгрузка результата запроса для другого приложения. Во всех этих сценариях программа сначала собирает текст в строковой переменной, а потом одним вызовом отправляет его в файл. Удобно иметь для этого одну процедуру, которую можно вызывать из любого модуля проекта.

Важная договорённость: процедура пишет строку ровно в том виде, в каком её получила. Переносы строк, разделители полей и заголовки должен расставить вызывающий код, когда готовит текст. Так функция остаётся простой и предсказуемой, а форматированием занимается тот, кто знает, что именно сохраняется.

Второй параметр, без которого не обойтись, это режим: перезаписать файл целиком или дописать текст в конец. Лог почти всегда дописывают, а экспорт обычно создают заново.

Три способа записи в VB6

В классическом Visual Basic текст на диск выводят тремя основными путями, и у каждого своя ниша.

  • встроенные операторы Open, Print # и Close: работают без внешних ссылок, быстрые, но пишут только в ANSI-кодировке текущей системы;
  • объект FileSystemObject из библиотеки Microsoft Scripting Runtime (scrrun.dll): объектный интерфейс, режимы ForWriting и ForAppending, умеет писать в Unicode (UTF-16);
  • объект ADODB.Stream из ADO: единственный штатный способ получить файл в UTF-8, что важно для обмена с вебом и современными системами.
Для FileSystemObject в проект добавляется ссылка Project → References → Microsoft Scripting Runtime. Без неё объявления As FileSystemObject и As TextStream не скомпилируются.

Процедура Str2Text на FileSystemObject

В исходном варианте процедуры для дозаписи файл сначала читался целиком, к старому содержимому приклеивался новый текст, и всё записывалось обратно. Это работает, но на логе размером в десятки мегабайт каждая запись превращается в полное чтение и полную перезапись. У FileSystemObject есть режим ForAppending, который открывает файл и ставит курсор в конец, поэтому перечитывать ничего не нужно.

Третий аргумент OpenTextFile, равный True, разрешает создать файл, если его ещё нет. Это избавляет от отдельной проверки FileExists.

modFiles.basVB6
' Запись строки в файл: перезапись или дозапись
Public Sub Str2Text(ByVal strText As String, _
                    ByVal strPath As String, _
                    Optional ByVal IsOverWrite As Boolean = True)
    Dim fso As New Scripting.FileSystemObject
    Dim stm As Scripting.TextStream
    Dim mode As Scripting.IOMode

    On Error GoTo ErrTrap
    mode = IIf(IsOverWrite, ForWriting, ForAppending)
    ' True: создать файл, если его нет
    Set stm = fso.OpenTextFile(strPath, mode, True)
    stm.Write strText   ' Write не добавляет перевод строки
    stm.Close
    Exit Sub

ErrTrap:
    Dim lngErr As Long, strDesc As String
    lngErr = Err.Number: strDesc = Err.Description
    If Not stm Is Nothing Then stm.Close  ' не оставляем файл открытым
    Err.Raise lngErr, "modFiles.Str2Text", strDesc
End Sub

Write или WriteLine, Print # или Write #

У TextStream два метода вывода. Write пишет строку как есть, WriteLine добавляет в конец vbCrLf. Если строка уже заканчивается переводом, WriteLine даст лишнюю пустую строку, а при дозаписи без перевода новые данные прилипнут к последней строке файла. Решение простое: выбрать одно правило и соблюдать его везде. Для логов удобно, чтобы каждая запись сама заканчивалась vbCrLf, а процедура использовала Write.

Со встроенными операторами похожая путаница. Print # выводит текст без изменений, а Write # оборачивает строки в кавычки и разделяет значения запятыми, то есть формирует данные для последующего чтения через Input #. Для обычного текстового файла нужен Print #, причём точка с запятой в конце подавляет перевод строки.

Вариант без внешних ссылокVB6
Public Sub Str2TextNative(ByVal strText As String, ByVal strPath As String, _
                          Optional ByVal IsOverWrite As Boolean = True)
    Dim f As Integer
    f = FreeFile   ' свободный номер, а не жёсткий #1
    If IsOverWrite Then
        Open strPath For Output As #f
    Else
        Open strPath For Append As #f
    End If
    Print #f, strText;   ' точка с запятой: без vbCrLf в конце
    Close #f
End Sub

Кодировки: ANSI, Unicode и UTF-8

Строки в VB6 внутри хранятся в UTF-16, но Open и Print # при записи преобразуют их в кодовую страницу системы. На русской Windows это cp1251, и символы вне неё (например, греческие или китайские) превратятся в знаки вопроса. FileSystemObject по умолчанию ведёт себя так же, но четвёртый параметр OpenTextFile или CreateTextFile со значением TristateTrue включает запись в UTF-16 с маркером BOM.

Если файл будут читать веб-приложение, скрипт на Python или Linux-утилиты, обычно нужен UTF-8. Для этого используют ADODB.Stream: задают Charset, пишут текст методом WriteText и сохраняют через SaveToFile. Учтите, что поток добавит в начало BOM из трёх байтов EF BB BF. Большинство программ это понимают, но некоторые парсеры CSV и старые утилиты воспринимают BOM как часть первого поля.

Запись в UTF-8 через ADODB.StreamVB6
Public Sub Str2TextUtf8(ByVal strText As String, ByVal strPath As String)
    Dim stm As Object
    Set stm = CreateObject("ADODB.Stream")
    stm.Type = 2              ' adTypeText
    stm.Charset = "utf-8"
    stm.Open
    stm.WriteText strText
    stm.SaveToFile strPath, 2 ' adSaveCreateOverWrite
    stm.Close
End Sub

Надёжность: ошибки и атомарная запись

Типичные ошибки при записи: 53 (файл не найден, обычно из-за несуществующей папки), 70 (доступ запрещён или файл открыт другим процессом), 75 (ошибка пути) и 61 (диск переполнен). Процедура должна либо обработать их, либо пробросить наверх с понятным источником, а главное, закрыть файл. Незакрытый дескриптор держит файл заблокированным до завершения программы.

Номер файла всегда берите через FreeFile. Жёсткий #1 ломается, как только две процедуры одновременно работают с файлами, и ошибка 55 (файл уже открыт) появляется в самых неожиданных местах.

Для важных файлов, например настроек, полезен приём атомарной записи: сначала пишем во временный файл рядом с целевым, затем удаляем старый и переименовываем новый через Name. Если программа упадёт в середине записи, на диске останется прежняя целая версия, а не обрезанный файл.

Перед записью проверяйте существование папки через fso.FolderExists и создавайте её при необходимости. Ошибка 76 (путь не найден) на чужой машине обычно означает именно отсутствующий каталог.

Как это делают сегодня

В .NET та же задача решается одной строкой: File.WriteAllText(path, text, Encoding.UTF8) для перезаписи и File.AppendAllText для дозаписи. Для потоковой записи большого объёма применяют StreamWriter внутри Using, а атомарную замену файла даёт File.Replace. По умолчанию .NET пишет UTF-8 без BOM, что удобнее для обмена с другими системами.

Вопросы и ответы

Почему русский текст в файле превратился в знаки вопроса?

Файл записан в ANSI на машине с другой кодовой страницей, или в строке есть символы вне cp1251. Пишите в Unicode через FileSystemObject с TristateTrue или в UTF-8 через ADODB.Stream.

Чем Write отличается от WriteLine в TextStream?

Write выводит строку без изменений, WriteLine добавляет vbCrLf в конце. Выберите одно правило для всего проекта, иначе появятся пустые строки или склеенные записи.

Можно ли обойтись без Microsoft Scripting Runtime?

Да. Операторы Open, Print # и Close встроены в язык и не требуют ссылок. Для UTF-8 без FSO подходит ADODB.Stream через позднее связывание CreateObject.

Читайте также