Как в 1С 7.7 перекодировать большой файл из UTF-8 в Windows-1251 без вылета программы?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

Работа с современными веб-сервисами и API часто ставит перед программистами 1С 7.7 задачу обработки данных в кодировке UTF-8. Платформа 7.7, разработанная задолго до повсеместного внедрения Unicode, использует однобайтовую кодировку (обычно Windows-1251). При попытке загрузить и перекодировать файлы, полученные с сайта (например, JSON или XML), мы можем столкнуться с неожиданной проблемой: на маленьких файлах код работает идеально, а на файлах размером в несколько мегабайт 1С аварийно закрывается (вылетает).

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

Почему происходит падение 1С при чтении файла?

Давайте проанализируем исходный код, который привел к ошибке. Типичный подход к перекодировке через объект ADODB.Stream выглядит так:


// ОШИБОЧНЫЙ ПОДХОД ДЛЯ БОЛЬШИХ ФАЙЛОВ
АдоДБСтрим = CreateObject("Adodb.Stream");
АдоДБСтрим.Charset = "utf-8";
АдоДБСтрим.Open();
АдоДБСтрим.LoadFromFile(ИмяФайла);
// В этой строке происходит катастрофа
ТекстАдоДБСтрим = АдоДБСтрим.ReadText(); 
АдоДБСтрим.Close();

Проблема кроется в методе ReadText(), вызванном без параметров. Он пытается прочитать весь содержимое файла (аналогично преобразованию из Base64) и передать его в строковую переменную 1С.

Здесь вступают в силу ограничения архитектуры 1С:Предприятие 7.7 и работы с памятью:

  1. При передаче строки из OLE-объекта (каким является Adodb.Stream) в 1С, системе необходимо выделить непрерывный блок памяти под эту строку.
  2. Если файл весит 10 МБ, то при конвертации из UTF-8 (Unicode) в ANSI (строку 1С) системе требуется значительный объем оперативной памяти.
  3. Память процесса 1С часто фрагментирована (особенно если программа запущена давно). Найти огромный непрерывный кусок памяти не удается, что приводит к переполнению или нарушению доступа к памяти, и программа закрывается без предупреждения.

Таким образом, мы выяснили: нельзя загружать содержимое большого файла целиком в переменную 1С.

Способ 1: Потоковая перезапись (Буферизация)

Самое эффективное решение, не требующее установки дополнительных компонент — это использование двух потоков (Stream). Мы будем читать данные из одного потока (входного) и сразу же писать их в другой поток (выходной) небольшими порциями. При этом огромная строка никогда не создается в памяти 1С целиком.

Рассмотрим алгоритм действий:

  1. Создаем Входной поток (Reader) с кодировкой источника (UTF-8).
  2. Создаем Выходной поток (Writer) с целевой кодировкой (Windows-1251).
  3. В цикле читаем кусок текста из входного потока и пишем его в выходной.
  4. Сохраняем результат.

Разберем код реализации. Обратите внимание на переменную РазмерБуфера. В обсуждении предлагалось использовать 255 байт, но для ускорения работы с файлами в 10-50 МБ лучше увеличить буфер, например, до 64 КБ или 128 КБ. Это значительно снизит накладные расходы на вызов методов OLE.


Функция ПерекодироватьФайлПотоком(ПутьКФайлуВход, ПутьКФайлуВыход)
    
    // Размер блока чтения в символах. 
    // 131072 = 128 Кб. Можно ставить меньше, если памяти совсем мало.
    РазмерБуфера = 131072; 
    
    // 1. Настраиваем читающий поток (UTF-8)
    СтримВход = СоздатьОбъект("Adodb.Stream");
    СтримВход.Type = 2; // adTypeText (Текстовые данные)
    СтримВход.Mode = 3; // adModeReadWrite
    СтримВход.Charset = "utf-8";
    СтримВход.Open();
    СтримВход.LoadFromFile(ПутьКФайлуВход);
    
    // 2. Настраиваем пишущий поток (Windows-1251)
    СтримВыход = СоздатьОбъект("Adodb.Stream");
    СтримВыход.Type = 2; // adTypeText
    СтримВыход.Mode = 3; // adModeReadWrite
    СтримВыход.Charset = "windows-1251";
    СтримВыход.Open();
    
    // 3. Цикл перекачки данных
    // Свойство EOS (End Of Stream) показывает, достигнут ли конец файла
    Пока СтримВход.EOS = 0 Цикл
        
        // Читаем порцию текста. В память 1С попадает только этот кусок.
        ТекстБуфер = СтримВход.ReadText(РазмерБуфера);
        
        // Сразу записываем в выходной поток. ADODB сам выполнит перекодировку.
        СтримВыход.WriteText(ТекстБуфер);
        
    КонецЦикла;
    
    // 4. Сохраняем результат
    // Параметр 2 (adSaveCreateOverWrite) разрешает перезапись файла
    СтримВыход.SaveToFile(ПутьКФайлуВыход, 2);
    
    // Закрываем потоки и освобождаем память
    СтримВход.Close();
    СтримВыход.Close();
    СтримВход = "";
    СтримВыход = "";

    Возврат 1;

КонецФункции

Важный нюанс: Метод WriteText имеет второй необязательный параметр. В некоторых случаях (как указано в обсуждении) использовалась конструкция ?(Байт=-2,1,0). Однако для простой перекодировки текста достаточно просто передавать строку, так как ADODB.Stream самостоятельно управляет разделителями строк, если они попадают в буфер.

Способ 2: Использование MSScriptControl

Если по каким-то причинам потоковый метод работает медленно или вы хотите полностью исключить попадание данных в память 1С, можно воспользоваться объектом MSScriptControl.ScriptControl. Смысл этого метода в том, чтобы выполнить весь код перекодировки внутри движка VBScript. В этом случае данные перекладываются из переменной в переменную внутри системных библиотек Windows, минуя ограничения 1С 7.7.

Посмотрим, как это реализовать:


Функция ПерекодироватьЧерезСкрипт(ПутьКФайлу, КодировкаВход, КодировкаВыход)
    
    Скрипт = СоздатьОбъект("MSScriptControl.ScriptControl");
    Скрипт.Language = "VBScript";
    
    // Формируем текст скрипта, который выполнит всю работу "на той стороне"
    КодVBS = "
    |Function ConvertFile(FilePath, CharsetIn, CharsetOut)
    |   Set StreamIn = CreateObject(""ADODB.Stream"")
    |   StreamIn.Type = 2
    |   StreamIn.Charset = CharsetIn
    |   StreamIn.Open
    |   StreamIn.LoadFromFile FilePath
    |   
    |   strText = StreamIn.ReadText ' Читаем всё в память VBScript (там нет лимита 1С)
    |   StreamIn.Close
    |   
    |   Set StreamOut = CreateObject(""ADODB.Stream"")
    |   StreamOut.Type = 2
    |   StreamOut.Charset = CharsetOut
    |   StreamOut.Open
    |   StreamOut.WriteText strText
    |   StreamOut.SaveToFile FilePath, 2 ' Перезаписываем тот же файл
    |   StreamOut.Close
    |End Function
    |";
    
    Скрипт.AddCode(КодVBS);
    
    // Вызываем функцию скрипта из 1С
    Попытка
        Скрипт.Run("ConvertFile", ПутьКФайлу, "utf-8", "windows-1251");
        Результат = 1;
    Исключение
        Сообщить("Ошибка при перекодировке: " + ОписаниеОшибки());
        Результат = 0;
    КонецПопытки;
    
    Возврат Результат;

КонецФункции

Этот метод считается одним из самых надежных для "семерки", так как фактически 1С выступает лишь как пульт управления, а тяжелая работа с памятью ложится на плечи ОС Windows.

Способ 3: Использование PowerShell (для современных ОС)

Если ваша 1С 7.7 работает на Windows Server 2008 R2 и новее, или на Windows 10/11, в системе доступен мощный инструмент PowerShell. Мы можем просто попросить операционную систему перекодировать файл одной командой.


Команда = "powershell -Command ""Get-Content -Path '" + ПутьКФайлу + "' -Encoding UTF8 | Set-Content -Path '" + ПутьКФайлуРезультат + "' -Encoding String""";
ЗапуститьПриложение(Команда); // Или использовать WScript.Shell для ожидания завершения

Здесь параметр -Encoding String в PowerShell обычно соответствует системной кодировке ANSI (1251 для русской локали), что актуально при загрузке курсов валют.

Особенности работы с HTTP заголовками

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

Если вы используете WinHttp.WinHttpRequest.5.1 или аналогичные компоненты, помните:

Однако, проанализируем реальность: большинство современных JSON API работают строго в UTF-8 (согласно стандарту RFC 8259) и игнорируют заголовок Accept-Charset. Поэтому надеяться на этот способ не стоит, и необходимо реализовывать перекодировку на стороне 1С, используя один из методов, описанных выше.

Резюме

Для стабильной работы с большими файлами в 1С 7.7 откажитесь от методов, которые загружают весь файл в одну переменную (например, ReadText() без параметров). Используйте потоковое чтение-запись небольшими блоками или делегируйте задачу внешним обработчикам (MSScriptControl, PowerShell). Это навсегда избавит вас от ошибок "Out of memory" и аварийных завершений программы.

← На главную