При автоматизации пакетной печати документов (например, накладных, счетов, ценников или бланков строгой отчетности) часто возникает необходимость отправлять разные типы документов на разные лотки одного и того же физического принтера. При этом процесс должен проходить в фоновом режиме, без участия пользователя и без вызова интерактивных окон предварительного просмотра.
Разберем, почему стандартные средства платформы 1С и попытки подключения системной библиотеки winspool.drv через COM вызывают сложности, а также рассмотрим рабочие способы решения этой задачи.
Проанализируем технические особенности операционной системы Windows и платформы 1С. Попытка инициализировать библиотеку вызовом Новый COMОбъект("winspool.drv") приводит к ошибке. Причина заключается в следующем:
winspool.drv является стандартной Win32 DLL, а не COM-сервером. У нее отсутствуют интерфейсы ActiveX/COM, ProgID и CLSID. Вызвать ее функции напрямую через COM-модель 1С невозможно.ТабличныйДокумент. В объектной модели 1С у табличного документа нет штатных свойств для явного указания лотка подачи бумаги (Paper Source / Paper Bin). Свойство ИмяПараметровПечати сохраняет настройки только после интерактивного выбора в диалоге печати.dmDefaultSource структуры DEVMODE подсистемы GDI. Платформа 1С не предоставляет прямого доступа к модификации этой структуры перед отправкой документа на печать.Рассмотрим проверенные способы обойти эти ограничения.
Разберем самый надежный административный подход, который не требует доработки платформы внешними библиотеками. Один физический принтер устанавливается в операционной системе несколько раз под разными именами, и для каждого экземпляра в свойствах драйвера фиксируется свой лоток по умолчанию.
Разберем порядок действий по шагам:
HP_LaserJet_Tray1 и HP_LaserJet_Tray2.Посмотрим на пример кода на встроенном языке 1С:
// Пример выбора принтера с предустановленным лотком
ТабДок = Новый ТабличныйДокумент;
// Заполняем табличный документ данными макета...
Если ТребуетсяПлотнаяБумага Тогда
ТабДок.ИмяПринтера = "HP_LaserJet_Tray1";
Иначе
ТабДок.ИмяПринтера = "HP_LaserJet_Tray2";
КонецЕсли;
// Отправляем на печать без отображения диалоговых окон
ТабДок.Напечатать(РежимИспользованияДиалогаПечати.НеИспользовать);
Преимущества метода: полная стабильность, отсутствие необходимости регистрировать внешние библиотеки, работа как в толстом, так и в тонком клиенте.
Если требуется динамически опрашивать принтер, получать реальный список его лотков и передавать их пользователю в интерфейс 1С, потребуется создать внешнюю компоненту на C++ или Rust с использованием Native API 1С.
Выясним, как работает данный механизм на уровне Windows API:
DeviceCapabilities с флагом DC_BINS.DeviceCapabilities с флагом DC_BINNAMES.DEVMODE принтера через DocumentProperties, устанавливает значение поля dmDefaultSource и передает модифицированный контекст в функцию печати.Посмотрим, как выполняется подключение такой компоненты в коде 1С без необходимости регистрации в реестре Windows через regsvr32:
// Подключение Native API компоненты из макета конфигурации
УстановитьВнешнююКомпоненту("Макет.ВнешняяКомпонентаПечати");
ПодключитьВнешнююКомпоненту("Макет.ВнешняяКомпонентаПечати", "PrinterExt", ТипВнешнейКомпоненты.Native);
КомпонентаПечати = Новый("AddIn.PrinterExt.TrayManager");
СписокЛотков = КомпонентаПечати.ПолучитьСписокЛотков("HP LaserJet Pro");
// СписокЛотков возвращает массив названий для отображения в форме 1С
Рассмотрим альтернативный сценарий: табличный документ выгружается во временный файл формата PDF, после чего отправляется на печать через стороннюю консольную утилиту, поддерживающую аргументы выбора лотка.
Разберем реализацию этого алгоритма:
// Сохраняем табличный документ во временный PDF-файл
ПутьКФайлу = ПолучитьИмяВременногоФайла("pdf");
ТабДок.Записать(ПутьКФайлу, ТипФайлаТабличногоДокумента.PDF);
// Формируем команду для внешней утилиты печати с указанием лотка
ИмяПринтера = "HP LaserJet Pro";
НомерЛотка = 2;
СтрокаКоманды = СтрШаблон("PDFtoPrinter.exe ""%1"" ""%2"" tray=%3", ПутьКФайлу, ИмяПринтера, НомерЛотка);
// Запускаем процесс в фоновом режиме с ожиданием завершения
ЗапуститьПриложение(СтрокаКоманды, "", Истина);
// Удаляем временный файл
УдалитьФайлы(ПутьКФайлу);
Подведем итог и выберем оптимальный вариант в зависимости от условий внедрения: