Как организовать интерактивное взаимодействие 1С с консольным приложением через потоки ввода-вывода StdIn и StdOut?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При интеграции 1С с внешним программным обеспечением разработчики часто сталкиваются с необходимостью запуска консольных утилит (CLI). В простых сценариях достаточно запустить приложение, передать ему аргументы командной строки и дождаться завершения работы. Однако существуют более сложные задачи, когда консольное приложение работает в интерактивном режиме: оно запускается, ожидает ввода пользователя, обрабатывает команду, возвращает результат в консоль и снова переходит в режим ожидания ввода. Яркими примерами таких утилит являются средства проброса портов (например, ngrok), консольные клиенты баз данных (mysql, sqlite3) или специализированные утилиты криптозащиты.

Попытки реализовать такое двустороннее общение «на лету» средствами стандартного COM-объекта WScript.Shell часто приводят к непредсказуемому поведению: интерфейс 1С намертво зависает, команды не отправляются, а консольный вывод возвращается пустым. В этой статье мы подробно разберем физику этого процесса, выясним причины блокировок и построим надежный алгоритм интерактивного взаимодействия с консолью непосредственно из кода 1С.

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


Физика процесса: как Windows связывает 1С и консольное приложение

Для начала проанализируем, как операционная система организует работу консоли. Любой консольный процесс по умолчанию имеет три стандартных потока данных (так называемые файловые дескрипторы):

  1. StdIn (Standard Input) — поток ввода, через который программа получает команды (обычно ассоциирован с клавиатурой).
  2. StdOut (Standard Output) — поток вывода, куда программа отправляет текстовые результаты работы (обычно выводится на монитор).
  3. StdErr (Standard Error) — поток ошибок, предназначенный для вывода диагностических и аварийных сообщений.

Когда мы запускаем консольное приложение из 1С через объект WScript.Shell методом Exec(), операционная система перенаправляет эти потоки в специальные программные каналы (Pipes). Обмен данными начинает происходить в оперативной памяти между процессом 1С и процессом запущенной утилиты. Объект WshScriptExec, который возвращает метод Exec(), предоставляет программисту доступ к трем свойствам: StdIn, StdOut и StdErr. Все они представляют собой объекты типа TextStream.

Казалось бы, схема проста: пишем данные в StdIn, читаем ответ из StdOut. Но именно здесь начинаются технические проблемы, которые мы рассмотрим далее.


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

Главная причина зависания 1С при работе с потоками — это использование синхронных (блокирующих) методов чтения объекта TextStream. К ним относятся методы ReadAll(), ReadLine() и Read(КоличествоСимволов). Выясним причину их опасного поведения:


Решение проблемы средствами WScript.Shell

Чтобы организовать стабильный обмен данными и не заблокировать платформу, мы должны соблюдать два золотых правила:

  1. Никогда не вызывать метод ReadAll() для интерактивных процессов.
  2. Обязательно анализировать состояние потока перед чтением с помощью свойств AtEndOfStream и посимвольного буфера.

Рассмотрим рабочий пример кода, демонстрирующий запуск консольной утилиты (в данном случае гипотетического интерактивного приложения 55.exe, которое принимает текст и выводит результат) и пошаговый обмен командами:


// Шаг 1. Инициализируем системную оболочку Windows
WshShell = Новый COMОбъект("WScript.Shell");

// Шаг 2. Запускаем консольное приложение через командный интерпретатор CMD
// Ключ /C указывает закрыть окно CMD после завершения утилиты.
// Обратите внимание на экранирование кавычек для путей с пробелами.
ПутьКПриложению = "D:\55.exe";
WshExec = WshShell.Exec("cmd.exe /C """ + ПутьКПриложению + """");

// Даем процессу долю секунды на инициализацию
Предупреждение("Инициализация процесса...", 1);

// Шаг 3. Получаем доступ к потокам ввода-вывода
InStream  = WshExec.StdIn;
OutStream = WshExec.StdOut;
ErrStream = WshExec.StdErr;

// Шаг 4. Отправляем первую команду в поток ввода приложения
КомандаДляОтправки = "same text zzzz";
InStream.WriteLine(КомандаДляОтправки);

// Шаг 5. Неблокирующее чтение ответа из потока StdOut
// Мы будем читать поток посимвольно только тогда, когда там физически есть данные.
РезультатВывода = "";

// Организуем цикл чтения, пока не достигнут конец потока или пока процесс активен
Пока Не OutStream.AtEndOfStream Цикл
    
    // Считываем ровно один символ, чтобы избежать блокировки строки
    Символ = OutStream.Read(1);
    РезультатВывода = РезультатВывода + Символ;
    
    // В реальном сценарии здесь должен быть анализ полученных данных.
    // Например, если мы встретили ожидаемый маркер конца вывода (символ ">" или ":"),
    // мы можем прервать чтение и подготовить следующую команду.
    Если Символ = ":" Тогда
        Прервать;
    КонецЕсли;
    
КонецЦикла;

Сообщить("Консоль вернула: " + РезультатВывода);

В приведенном кодовой схеме мы уходим от зависания благодаря посимвольному чтению Read(1). Если нам нужно прочитать конкретную строку, мы также можем использовать свойство AtEndOfLine для построчного разбора, что исключает блокировку выполнения при отсутствии символа конца файла.


Важная деталь: Проблема кодировки текста (OEM vs ANSI/UTF-8)

Часто при чтении потока StdOut в 1С вместо русских букв отображаются непонятные символы («кракозябры»). Выясним причину этой проблемы: консоль Windows по умолчанию работает в кодировке DOS (для русской локализации это кодовая страница CP866), а платформа 1С ожидает строки в кодировке Unicode (UTF-16). Чтобы исправить кодировку, у нас есть два пути:

Вариант 1. Смена кодовой страницы консоли перед запуском утилиты

Мы можем принудительно перевести сессию командной строки в режим UTF-8 (кодовая страница 65001) или Windows-1251 (кодовая страница 1251). Для этого запуск утилиты нужно объединить со сменой кодовой страницы с помощью оператора && в команде вызова:


// Переводим консоль в UTF-8 перед запуском нашего приложения
КомандаЗапуска = "cmd.exe /C ""chcp 65001 && D:\55.exe""";
WshExec = WshShell.Exec(КомандаЗапуска);

Вариант 2. Программное перекодирование через ADODB.Stream

Если изменить кодировку самой консоли не удается, мы можем прочитать данные как бинарный поток и перекодировать их с помощью стандартного COM-объекта ADODB.Stream. Посмотрим на пример функции для преобразования кодировки из CP866 в UTF-8:


Функция ДекодироватьСтрокуИзCP866(ИсходнаяСтрока)
    
    // Создаем объект потока ADO
    AdodbStream = Новый COMОбъект("ADODB.Stream");
    AdodbStream.Type = 2; // Текстовый тип данных
    AdodbStream.Mode = 3; // Доступ на чтение и запись
    
    AdodbStream.Open();
    AdodbStream.Charset = "cp866"; // Указываем исходную кодировку консоли
    AdodbStream.WriteText(ИсходнаяСтрока);
    
    // Перематываем поток в начало
    AdodbStream.Position = 0;
    
    // Меняем целевую кодировку для чтения платформой 1С
    AdodbStream.Charset = "utf-8";
    Результат = AdodbStream.ReadText();
    
    AdodbStream.Close();
    Возврат Результат;
    
КонецФункции

Современные кроссплатформенные альтернативы

Использование WScript.Shell через механизм COM имеет один огромный недостаток — привязку исключительно к операционной системе Windows. Если ваша база 1С работает на сервере под управлением Linux, данный код вызовет ошибку инициализации COM-объекта. Рассмотрим современные архитектурные решения, позволяющие обойти эти ограничения.

1. Использование 1С:Исполнителя

В современной экосистеме 1С для автоматизации администрирования и написания скриптов поставляется специализированный инструмент — 1С:Исполнитель. В нем работа с внешними процессами реализована на кроссплатформенном уровне с использованием асинхронных неблокирующих потоков.

Посмотрим, как выглядит работа с интерактивным процессом в синтаксисе 1С:Исполнителя:


// Создаем описание запускаемого процесса
Перем ПараметрыЗапуска = Новый ОписаниеЗапускаПроцесса("D:\55.exe");
ПараметрыЗапуска.ПеренаправитьСтандартныйВвод = Истина;
ПараметрыЗапуска.ПеренаправитьСтандартныйВывод = Истина;

// Запускаем процесс ОС
Перем Процесс = ПроцессыОс.Запустить(ПараметрыЗапуска);

// Получаем потоки для интерактивной работы
Перем ПотокЗаписи = Процесс.СтандартныйВвод;
Перем ПотокЧтения = Процесс.СтандартныйВывод;

// Записываем данные в поток ввода приложения
ПотокЗаписи.ЗаписатьСтроку("Новая команда");
ПотокЗаписи.СброситьБуфер(); // Выталкиваем данные из буфера в pipe

// Читаем ответ построчно без зависания основного интерфейса
Перем Ответ = ПотокЧтения.ПрочитатьСтроку();

2. Использование скриптового движка OneScript (oscript.io)

Для создания консольных сценариев на языке 1С активно применяется открытый проект OneScript. В его стандартной библиотеке реализован класс Процесс, который лишен недостатков буферизации COM-объектов Windows и работает как на Windows, так и на Linux.


Процесс = Новый Процесс("D:\55.exe");
Процесс.Запустить();

// Пишем в стандартный ввод процесса
Процесс.ЗаписатьСтроку("Привет, консоль!");

// Считываем результат
Ответ = Процесс.ПрочитатьСтроку();

3. Написание внешней компоненты Native API

Для масштабных и отказоустойчивых решений в высоконагруженных базах 1С лучшим подходом является разработка небольшой внешней компоненты Native API (например, на языках C++, C# или Go). Компонента запускает консольную утилиту в параллельном системном потоке ОС и по мере появления новых данных в StdOut генерирует в 1С Внешнее событие.

В этом случае разработчику 1С не нужно заботиться о циклах чтения и проверках AtEndOfStream. Вся обработка сводится к стандартной предопределенной процедуре модуля формы 1С:


&НаКлиенте
Процедура ОбработкаВнешнегоСобытия(Источник, Событие, Данные)
    
    Если Источник = "КонсольнаяКомпонента" И Событие = "НовыеДанныеStdOut" Тогда
        // Обрабатываем полученный из консоли текст, пришедший асинхронно
        Сообщить("Получено из консоли: " + Данные);
    КонецЕсли;
    
КонецПроцедуры

Резюме и практические рекомендации

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

← На главную