Работа с современными веб-сервисами и API часто ставит перед программистами 1С 7.7 задачу обработки данных в кодировке UTF-8. Платформа 7.7, разработанная задолго до повсеместного внедрения Unicode, использует однобайтовую кодировку (обычно Windows-1251). При попытке загрузить и перекодировать файлы, полученные с сайта (например, JSON или XML), мы можем столкнуться с неожиданной проблемой: на маленьких файлах код работает идеально, а на файлах размером в несколько мегабайт 1С аварийно закрывается (вылетает).
В этой статье мы подробно разберем, почему возникает эта ошибка, и рассмотрим несколько надежных способов перекодировки больших файлов XML, которые гарантируют стабильную работу системы.
Давайте проанализируем исходный код, который привел к ошибке. Типичный подход к перекодировке через объект ADODB.Stream выглядит так:
// ОШИБОЧНЫЙ ПОДХОД ДЛЯ БОЛЬШИХ ФАЙЛОВ
АдоДБСтрим = CreateObject("Adodb.Stream");
АдоДБСтрим.Charset = "utf-8";
АдоДБСтрим.Open();
АдоДБСтрим.LoadFromFile(ИмяФайла);
// В этой строке происходит катастрофа
ТекстАдоДБСтрим = АдоДБСтрим.ReadText();
АдоДБСтрим.Close();
Проблема кроется в методе ReadText(), вызванном без параметров. Он пытается прочитать весь содержимое файла (аналогично преобразованию из Base64) и передать его в строковую переменную 1С.
Здесь вступают в силу ограничения архитектуры 1С:Предприятие 7.7 и работы с памятью:
Adodb.Stream) в 1С, системе необходимо выделить непрерывный блок памяти под эту строку.Таким образом, мы выяснили: нельзя загружать содержимое большого файла целиком в переменную 1С.
Самое эффективное решение, не требующее установки дополнительных компонент — это использование двух потоков (Stream). Мы будем читать данные из одного потока (входного) и сразу же писать их в другой поток (выходной) небольшими порциями. При этом огромная строка никогда не создается в памяти 1С целиком.
Рассмотрим алгоритм действий:
Разберем код реализации. Обратите внимание на переменную РазмерБуфера. В обсуждении предлагалось использовать 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 самостоятельно управляет разделителями строк, если они попадают в буфер.
Если по каким-то причинам потоковый метод работает медленно или вы хотите полностью исключить попадание данных в память 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.
Если ваша 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 запроса.
Если вы используете WinHttp.WinHttpRequest.5.1 или аналогичные компоненты, помните:
Accept-Charset: windows-1251.Однако, проанализируем реальность: большинство современных JSON API работают строго в UTF-8 (согласно стандарту RFC 8259) и игнорируют заголовок Accept-Charset. Поэтому надеяться на этот способ не стоит, и необходимо реализовывать перекодировку на стороне 1С, используя один из методов, описанных выше.
Для стабильной работы с большими файлами в 1С 7.7 откажитесь от методов, которые загружают весь файл в одну переменную (например, ReadText() без параметров). Используйте потоковое чтение-запись небольшими блоками или делегируйте задачу внешним обработчикам (MSScriptControl, PowerShell). Это навсегда избавит вас от ошибок "Out of memory" и аварийных завершений программы.