Как исправить критическую ошибку и падение платформы 1С при выводе большого количества печатных форм и этикеток?

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Торговля и дистрибуция
← На главную

При работе в среде «1С:Предприятие» разработчики и системные администраторы часто сталкиваются со следующей проблемой: при попытке вывести на печать, отобразить на экране или сохранить большой массив ценников или этикеток (от нескольких сотен до тысяч штук) сеанс пользователя аварийно завершается. Windows выдает стандартное окно с сообщением «Прекращена работа программы 1cv8», при этом явных утечек оперативной памяти в диспетчере задач может не наблюдаться (процесс завершается на относительно скромных 500–600 МБ).

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

--- ### Выясняем истинную причину: почему падает 1С?

Многие разработчики ошибочно полагают, что падение связано исключительно с утечкой оперативной памяти (RAM) из-за неоптимального кода. Однако в случае массовой печати этикеток ключевую роль играют совершенно другие факторы:

  1. Лимит GDI-объектов (GDI Handles) в Windows. Интерфейс графических устройств (GDI) операционной системы Windows использует специальные дескрипторы для отрисовки элементов интерфейса (шрифтов, рамок, картинок, кистей). По умолчанию в Windows установлен жесткий лимит — не более 10 000 GDI-объектов на один процесс. Когда в макет этикетки внедряется устаревшая ActiveX-компонента печати штрихкодов (например, 1CBarCod.dll), каждый сформированный штрихкод регистрируется в системе как отдельный тяжеловесный графический объект. При выводе более 500–1000 этикеток лимит в 10 000 дескрипторов мгновенно исчерпывается, что приводит к немедленному падению платформы.
  2. Фрагментация виртуальной памяти 32-битного процесса. Если вы работаете на платформе «1С:Предприятие 8.2» или в 32-разрядном клиенте платформы 8.3, процесс 1cv8.exe ограничен 2 ГБ адресного пространства. Постоянное создание и уничтожение тысяч COM-объектов ActiveX дробит память. Когда метод Записать() пытается сохранить итоговый табличный документ в формате XLSX или PDF, платформе требуется выделить один большой непрерывный блок памяти. Из-за сильной фрагментации ОС отвечает отказом, вызывая аварийное завершение программы.
  3. Технология EasyPrint в RDP. При работе в терминальном режиме перенаправление печати через EasyPrint пытается переварить огромные объемы графических данных за раз, что дополнительно перегружает графическую подсистему сессии пользователя.

--- ### Способ 1. Переход на шрифтовую печать штрихкодов (Оптимальный и быстрый метод)

Самый надежный способ раз и навсегда победить лимит GDI-объектов и ускорить печать этикеток в десятки раз — отказаться от использования ActiveX-компоненты 1CBarCod.dll в пользу специализированных шрифтов (например, EanGnivc или Barcode Font).

При таком подходе штрихкод выводится в ячейку табличного документа как обычная текстовая строка. Отрисовкой линий занимается сам графический движок операционной системы на уровне шрифта, что вообще не создает дополнительных GDI-дескрипторов.

Рассмотрим по шагам, как реализовать этот механизм для популярного формата EAN-13:

Шаг 1. Установка шрифта

Скачайте и установите шрифт EanGnivc на сервере терминалов (если работа идет в RDP) или на локальных компьютерах пользователей.

Шаг 2. Добавление функции преобразования штрихкода

Просто применить шрифт к обычным цифрам штрихкода нельзя — сканер его не прочитает. Штрихкод содержит контрольные символы и стартовые/стоповые маркеры. Разберем и применим функцию, которая преобразует цифровую последовательность штрихкода в специальный набор символов для шрифта EanGnivc:


Функция СформироватьСтрокуEanGnivc(ШтрихкодСтрокойСимволов) Экспорт
    Попытка
        Тест = Число(ШтрихкодСтрокойСимволов);
    Исключение
        ВызватьИсключение "Штрихкод должен содержать только цифры";
    КонецПопытки;
 
    СимволыСпособовКодирования = "#$%&'()*+,";
    ЛевыйЗащитныйШаблон = "!";
    СреднийЗащитныйШаблон = "-";
    ПравыйЗащитныйШаблон = "!";
 
    СимволыШрифтаПоТипамКодов = Новый Соответствие;
    СимволыШрифтаПоТипамКодов.Вставить("L", "0123456789");
    СимволыШрифтаПоТипамКодов.Вставить("G", "ABCDEFGHIJ");
    СимволыШрифтаПоТипамКодов.Вставить("R", "abcdefghij");
 
    СпособыКодированияЛевойЧасти = Новый Массив;
    СпособыКодированияЛевойЧасти.Добавить("LLLLLL");
    СпособыКодированияЛевойЧасти.Добавить("LLGLGG");
    СпособыКодированияЛевойЧасти.Добавить("LLGGLG");
    СпособыКодированияЛевойЧасти.Добавить("LLGGGL");
    СпособыКодированияЛевойЧасти.Добавить("LGLLGG");
    СпособыКодированияЛевойЧасти.Добавить("LGGLLG");
    СпособыКодированияЛевойЧасти.Добавить("LGGGLL");
    СпособыКодированияЛевойЧасти.Добавить("LGLGLG");
    СпособыКодированияЛевойЧасти.Добавить("LGLGGL");
    СпособыКодированияЛевойЧасти.Добавить("LGGLGL");
 
    СпособКодированияПравойЧасти = "RRRRRR";
 
    Если СтрДлина(ШтрихкодСтрокойСимволов) = 13 Тогда
        КоличествоСимволовВГруппе = 6;
    ИначеЕсли СтрДлина(ШтрихкодСтрокойСимволов) = 8 Тогда
        КоличествоСимволовВГруппе = 4;
        СимволыСпособовКодирования = "";
        ШтрихкодСтрокойСимволов = "0" + ШтрихкодСтрокойСимволов;
    Иначе
        ВызватьИсключение "Поддерживаются только штрихкоды длиной 13 и 8 символов";
    КонецЕсли;
 
    ИндексСпособаКодирования = Число(Лев(ШтрихкодСтрокойСимволов, 1));
 
    ЛеваяЧастьКода = "";
    ПраваяЧастьКода = "";
 
    Для ИндексСимвола = 1 По КоличествоСимволовВГруппе Цикл
        ЛеваяЧастьКода = ЛеваяЧастьКода
            + Сред(СимволыШрифтаПоТипамКодов[Сред(СпособыКодированияЛевойЧасти[ИндексСпособаКодирования], ИндексСимвола, 1)]
                    , Число(Сред(ШтрихкодСтрокойСимволов, (ИндексСимвола + 1), 1)) + 1
                    , 1);
        ПраваяЧастьКода = ПраваяЧастьКода
            + Сред(СимволыШрифтаПоТипамКодов[Сред(СпособКодированияПравойЧасти, ИндексСимвола, 1)]
                    , Число(Сред(ШтрихкодСтрокойСимволов, (ИндексСимвола + КоличествоСимволовВГруппе + 1), 1)) + 1
                    , 1);
    КонецЦикла;
 
    Результат = "" + Сред(СимволыСпособовКодирования, ИндексСпособаКодирования + 1, 1)
                    + ЛевыйЗащитныйШаблон
                    + ЛеваяЧастьКода
                    + СреднийЗащитныйШаблон
                    + ПраваяЧастьКода
                    + ПравыйЗащитныйШаблон;
    Возврат Результат;
КонецФункции

Шаг 3. Изменение макета этикетки

В макете вашей обработки печати удалите объект ActiveX «1С:Печать штрихкодов». Вместо него назначьте обычной текстовой ячейке шрифт EanGnivc с необходимым размером (обычно от 26 до 36 пунктов). При заполнении параметров макета передавайте в эту ячейку результат выполнения функции СформироватьСтрокуEanGnivc(Штрихкод).

Важное ограничение: Данный метод безупречно подходит для стандартных линейных кодов (EAN-13, Code128). Однако он не решит проблему, если вам требуется выводить современные двумерные коды маркировки (QR-коды, DataMatrix для «Честного Знака»). Для работы с ними рассмотрите решения ниже.

--- ### Способ 2. Вывод штрихкода в виде картинки (Native-метод)

Если вам необходима поддержка DataMatrix или вы не хотите заниматься установкой сторонних шрифтов на все клиентские машины, проанализируем современный подход, используемый в БСП (Библиотеке стандартных подсистем). Вместо тяжелого контейнера ActiveX штрихкод генерируется во внешней компоненте и помещается в ячейку как обычный плоский рисунок.

Программная вставка рисунка в 1С выглядит следующим образом:


// Создаем или получаем картинку штрихкода из компоненты Native API
КартинкаШтрихкода = ПолучитьКартинкуШтрихкода(ДанныеШтрихкода); 

// Добавляем рисунок в область табличного документа
ОбластьМакета = Макет.ПолучитьОбласть("Строка");
Рисунок = ОбластьМакета.Рисунки.Добавить(ТипРисункаТабличногоДокумента.Картинка);
Рисунок.Картинка = КартинкаШтрихкода;
Рисунок.ГраницаСверху = Ложь;
Рисунок.ГраницаСнизу = Ложь;
Рисунок.ГраницаСлева = Ложь;
Рисунок.ГраницаСправа = Ложь;

// Позиционируем рисунок поверх нужных ячеек
Рисунок.Расположить(ОбластьМакета.Область("R1C2:R3C4"));

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

--- ### Способ 3. Изменение лимита GDI-объектов в реестре Windows

Если у вас нет возможности оперативно изменить код обработки печати (например, конфигурация находится на жесткой поддержке, а ценники нужно распечатать прямо сейчас), можно применить системный обходной путь. Мы можем принудительно увеличить лимит дескрипторов графических объектов для процессов в операционной системе Windows Server.

Разберем этот процесс по шагам:

  1. Запустите редактор реестра: нажмите сочетание клавиш Win + R, введите regedit и нажмите Enter.
  2. Перейдите к разделу реестра:
    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
  3. Найдите параметр с именем GDIProcessHandleQuota. По умолчанию его значение в десятичной системе счисления составляет 10000.
  4. Дважды кликните по параметру, переключите систему исчисления на Десятичную (Decimal) и установите новое значение. Безопасным и стабильным максимумом считается значение 16384 (в некоторых конфигурациях можно выставлять вплоть до 65536, но это повышает требования к общей стабильности RAM сервера).
  5. Для вступления изменений в силу обязательно перезагрузите сервер терминалов.

Этот временный «костыль» отодвинет порог аварийного завершения платформы 1С примерно в 1.5–2 раза, что позволит успешно выводить средние объемы документов без переписывания логики программы.

--- ### Способ 4. Пакетная генерация и фоновое объединение документов

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

Рассмотрим логику построения такого процесса:

  1. При нажатии кнопки «Печать» программа разделяет общий массив номенклатуры на пакеты (например, по 200 этикеток в каждом).
  2. Для каждого пакета запускается фоновое задание на сервере, которое формирует свой обособленный ТабличныйДокумент.
  3. Каждый готовый пакет программно сохраняется во временную папку сервера в формате PDF:
    ТабДок.Записать(ИмяВременногоФайла, ТипФайлаТабличногоДокумента.PDF);
  4. После формирования всех пакетов на сервере вызывается консольная утилита слияния PDF-документов (например, PDFtk или Ghostscript), которая объединяет множество мелких файлов в один большой итоговый документ.
  5. Итоговый файл передается на клиентское рабочее место для вывода на принтер. Порционная генерация полностью исключает накопление мусора в оперативной памяти текущего сеанса.

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

← На главную