// Всё это следует поместить в серверный общий модуль с именем МенеджерКомСоединений.
// При необходимости можно установить флаг вызова модуля с клиента и привилегированности.
// Для правильной работы в метаданных должна быть константа КомандыМенеджеруКомСоединений типа неограниченная строка.
// По имени метода и состоянию "Активно" получает фоновые задания, обслуживающие com-соединения.
// Возвращает массив фоновых заданий.
//
Функция ПолучитьАктивныеЗаданияКомСоединений() Экспорт
рИмяМетода="МенеджерКомСоединений.РаботаКомСоединения";
отбЗаданий=Новый Структура;
отбЗаданий.Вставить("Состояние",СостояниеФоновогоЗадания.Активно);
отбЗаданий.Вставить("ИмяМетода",рИмяМетода);
Возврат ФоновыеЗадания.ПолучитьФоновыеЗадания();
КонецФункции
// Запускает фоновое задание, обслуживающее com-соединение согласно указанным параметрам.
// Подробнее см. описание процедуры РаботаКомСоединения.
// Возвращает булево - общую успешность (запустилось или нет).
//
// Параметры:
// ПараметрыПодключения - структура; свойства, определяющие установление com-соединения;
// КлючСоединения - строка; уникальный ключ фонового задания, будет определять запускаемое com-соединение; должен удовлетворять требованиям к ключу структуры;
// ТаймаутОжиданияЗапуска - число; время в секундах, определяющее, сколько ждать запуска com-базы (функция завершится лишь по запуску или истечении этого таймаута),
// по умолчанию 180 сек. (3 минуты).
//
Функция УстановитьКомСоединение(ПараметрыПодключения, КлючСоединения, ТаймаутОжиданияЗапуска=180) Экспорт
Попытка
// выясняем, подключено ли уже такое задание
рКоманда=Новый Структура("Действие","ЗапущеноЛи");
рОтвет=ПолучитьОтветКомСоединения(рКоманда, КлючСоединения);
Если СтрНайти(рОтвет,"ЗапущеноЛи:Запущено")<>0 Тогда // уже запущено
Возврат Истина;
КонецЕсли;
// запускаем
рПараметрыСоединенияВМенеджере=Новый Структура;
рПараметрыСоединенияВМенеджере.Вставить("НомерСеанса",НомерСеансаИнформационнойБазы());
рПараметрыСоединенияВМенеджере.Вставить("НомерСоединения",НомерСоединенияИнформационнойБазы());
рПараметрыСоединенияВМенеджере.Вставить("КлючСоединения",КлючСоединения);
рПараметрыСоединенияВМенеджере.Вставить("ТаймаутПростоя",180);
рИмяМетода="МенеджерКомСоединений.РаботаКомСоединения";
мПараметровФЗ=Новый Массив;
мПараметровФЗ.Добавить(ПараметрыПодключения);
мПараметровФЗ.Добавить(рПараметрыСоединенияВМенеджере);
рПредставление=?(ПараметрыПодключения.Свойство("ПредставлениеСоединения"),ПараметрыПодключения.ПредставлениеСоединения,"");
ФоновыеЗадания.Выполнить(рИмяМетода,мПараметровФЗ,КлючСоединения,рПредставление);
// ждём запуска (соединение может устанавливаться долго)
Если ТаймаутОжиданияЗапуска=0 Тогда ТаймаутОжиданияЗапуска=180 КонецЕсли;
//
начДата=ТекущаяДатаСеанса();
Пока Истина Цикл // ожидать завершения средствами платфоры не будем, в файловой базе неподходящее поведение
рОтвет=ПолучитьОтветКомСоединения(рКоманда, КлючСоединения);
Если СтрНайти(рОтвет,"ЗапущеноЛи:Запущено")<>0 Тогда // запустилось, можно больше не ждать
Возврат Истина;
ИначеЕсли СтрНайти(рОтвет,"Ошибка установки com-соединения:")<>0 Тогда
Сообщить(рОтвет);
Возврат Ложь;
КонецЕсли;
текДата=ТекущаяДатаСеанса();
Если текДата-начДата>ТаймаутОжиданияЗапуска Тогда
Сообщить("УстановитьКомСоединение: за "+Строка(ТаймаутОжиданияЗапуска)+" сек. не дождались запуска com-соединения!");
Возврат Ложь;
КонецЕсли;
КонецЦикла;
Возврат Ложь;
Исключение
инфо="УстановитьКомСоединение, общая ошибка: "+ОписаниеОшибки();
Сообщить(инфо);
Возврат Ложь;
КонецПопытки;
КонецФункции
// Останавливает фоновое задание, обслуживающее com-соединение с указанным ключом, или все заданиях всех соединений.
//
// Параметры:
// КлючСоединения - строка; уникальный ключ фонового задания, определяет прерываемое задание; если не указан - прерываются все запущенные com-задания.
//
Процедура ПрерватьКомСоединение(КлючСоединения="") Экспорт
Попытка
Если ПустаяСтрока(КлючСоединения) Тогда
мФЗ=ПолучитьАктивныеЗаданияКомСоединений();
Для каждого рФЗ Из мФЗ Цикл
рФЗ.Отменить();
КонецЦикла;
Иначе
рКоманда=Новый Структура("Действие","Стоп");
ПолучитьОтветКомСоединения(рКоманда, КлючСоединения); // тут нас не интересует ответ
КонецЕсли;
Исключение
инфо="ПрерватьКомСоединение, общая ошибка: "+ОписаниеОшибки();
Сообщить(инфо);
КонецПопытки;
КонецПроцедуры
// Выполняется в фоновом задании (вызывается как метод фонового задания), внутри себя устанавливает и хранит контексты серверных com-соединений.
// Каждое фоновое задание имеет свой ключ, который структуре, хранящейся в константе КомандыМенеджеруКомСоединений, определяет команды конкретному соединению.
// Таким образом, в клиент-серверной базе можно запустить и несколько разных com-соединений с разными ключами.
// Рассматривает содержимое константы КомандыМенеджеруКомСоединений и, если оно не пусто и содержит сериализованную структуру, ищет в ней ключ, равный
// указанному ключу соединения. Если находит и значение по этому ключу - управляющая структура, то действует согласно содержимому этой структуры.
// Если содержимое константы иное, то оно очищается.
// В управляющей структуре 3 ключа: НомерСеанса (число), НомерСоединения (число), Управление (структура с обязательным ключом Действие или Неопределено, если ничего не надо),
// где "Действие" строка вида:
// служебные действия:
// "Стоп" - прерывает соединение с указанным ключом (не трогая
// "ЗапущеноЛи" - определяет, запущено ли соединение с указанным ключом, и если да, то вернёт "ЗапущеноЛи:Запущено";
// конкретные действия:
// "ИБ" - определение, для какой ИБ запущено соединение с указанным ключом, вернёт строковое представление ГУИДа ИБ;
// "Алгоритм" - выполнение произвольного кода, если передано строковое свойство с ключом "ТекстАлгоритма" (содержащее исполняемый код);
// В зависимости от содержания выполняет некие действия и выводит результат как сообщение пользователю, которое затем забирает ПолучитьОтветКомСоединения
// как получение сообщений пользователю из фонового задания, и далее уже анализирует.
// Механизм позволяет выполнять один серверный вызов установки com-соединения и затем не терять его, пока оно нужно, и обращаться к нему в любой момент.
//
// Параметры:
// ПараметрыПодключения - структура; свойства, определяющие установление com-соединения;
// ПараметрыСоединенияВМенеджере - структура, где обязательны ключи:
// "НомерСеанса" (число),
// "НомерСоединения" (число),
// "КлючСоединения" (строка) - уникальный ключ фонового задания, в командной константе определяет действие для конкретного com-соединения;
// также допустим ключ "ТаймаутПростоя" (число) в секундах, с какой периодичностью задание опрашивает наличие запустившего её сеанса,
// по умолчанию 180 сек. (3 минуты). Если задание не находит запустивший сеанс среди имеющихся по ИБ, то оно немедленно завершается.
//
Процедура РаботаКомСоединения(ПараметрыПодключения, ПараметрыСоединенияВМенеджере) Экспорт
#Область Подключение
//=========== Конкретика подключения к com-объекту/приложению/службе =========================================
//===================================================================================================
#КонецОбласти
рКлючСоединения=ПараметрыСоединенияВМенеджере.КлючСоединения;
Если ПравоДоступа("АктивныеПользователи",Метаданные) Тогда
рТаймаутПростоя=?(ПараметрыСоединенияВМенеджере.Свойство("ТаймаутПростоя"),ПараметрыСоединенияВМенеджере.ТаймаутПростоя,0);
Если рТаймаутПростоя=0 Тогда рТаймаутПростоя=180 КонецЕсли;
Иначе
рТаймаутПростоя=0;
КонецЕсли;
начДата=ТекущаяДатаСеанса();
Пока Истина Цикл
строКоманд=Константы.КомандыМенеджеруКомСоединений.Получить();
#Область АдаптацияХранимогоВКонстанте
Если ПустаяСтрока(строКоманд) Тогда
// никаких действий ещё не было, инициализируем
струКоманд=Новый Структура;
Иначе
Попытка
струКоманд=ЗначениеИзСтрокиВнутр(строКоманд);
Исключение
// содержание константы имеет неверный формат - это означает, что надо её сбросить и выйти
Константы.КомандыМенеджеруКомСоединений.Установить("");
Прервать;
КонецПопытки;
Если ТипЗнч(струКоманд)<>Тип("Структура") Тогда
// содержание константы имеет неверный формат - это означает, что надо её сбросить и выйти
Константы.КомандыМенеджеруКомСоединений.Установить("");
Прервать;
КонецЕсли;
КонецЕсли;
#КонецОбласти
// получаем команду для нужного ключа
рБылоИзменение=Ложь;
струКомандыПоКлючу=Неопределено;
струКоманд.Свойство(рКлючСоединения,струКомандыПоКлючу);
Если ТипЗнч(струКомандыПоКлючу)<>Тип("Структура") Тогда // нет сведений по указанному ключу, либо неверный формат этих сведений; инициализируем их
струКомандыПоКлючу=Новый Структура;
струКомандыПоКлючу.Вставить("НомерСеанса",ПараметрыСоединенияВМенеджере.НомерСеанса);
струКомандыПоКлючу.Вставить("НомерСоединения",ПараметрыСоединенияВМенеджере.НомерСоединения);
струКомандыПоКлючу.Вставить("Управление",Неопределено); // по умолчанию пока никаких действий
струКоманд.Вставить(рКлючСоединения,струКомандыПоКлючу);
рБылоИзменение=Истина;
Иначе
Попытка струУправление=струКомандыПоКлючу.Управление Исключение струУправление=Неопределено КонецПопытки;
Если ТипЗнч(струУправление)=Тип("Структура") Тогда // есть некие данные команды
#Область ОбработкаДействия
рДействие=?(струУправление.Свойство("Действие"),НРег(СокрЛП(струУправление.Действие)),"");
Если рДействие="стоп" Тогда
// убираем себя из структуры команд и выходим
струКоманд.Удалить(рКлючСоединения);
Константы.КомандыМенеджеруКомСоединений.Установить(ЗначениеВСтрокуВнутр(струКоманд));
Прервать;
ИначеЕсли рДействие="запущеноли" Тогда
Логирование.Сообщать("ЗапущеноЛи:Запущено");
Иначе
#Область ДействияСПодключением
//=========== Конкретика действий с подключением к com-объекту/приложению/службе ================================
//===================================================================================================
#КонецОбласти
КонецЕсли;
// и в любом случае сбрасываем команду как уже выполненную, отработанную
струКомандыПоКлючу.Вставить("Управление",Неопределено);
// фиксируем
струКоманд.Вставить(рКлючСоединения,струКомандыПоКлючу);
рБылоИзменение=Истина;
#КонецОбласти
Иначе
#Область ОбработкаБездействия
// никакой команды нет, простаиваем
текДата=ТекущаяДатаСеанса();
Если рТаймаутПростоя>0 Тогда
Если текДата-начДата>рТаймаутПростоя Тогда // проверяем наличие родительского (запустившего) сеанса
рНашлиРодителя=Ложь;
мСоединенийИБ=ПолучитьСоединенияИнформационнойБазы();
Для каждого рСоединениеИБ Из мСоединенийИБ Цикл
Если рСоединениеИБ.НомерСеанса=ПараметрыСоединенияВМенеджере.НомерСеанса
и рСоединениеИБ.НомерСоединения=ПараметрыСоединенияВМенеджере.НомерСоединения
Тогда
рНашлиРодителя=Истина; Прервать;
КонецЕсли;
КонецЦикла;
Если рНашлиРодителя Тогда // продолжаем ждать указаний
начДата=текДата;
Иначе // прерываемся
// убираем себя из структуры команд и выходим
струКоманд.Удалить(рКлючСоединения);
Константы.КомандыМенеджеруКомСоединений.Установить(ЗначениеВСтрокуВнутр(струКоманд));
Прервать;
КонецЕсли;
КонецЕсли; // если превысили таймаут
Иначе
// страховочный таймаут
Если текДата-начДата>3600 Тогда // прошёл час, пора прерываться
// убираем себя из структуры команд и выходим
струКоманд.Удалить(рКлючСоединения);
Константы.КомандыМенеджеруКомСоединений.Установить(ЗначениеВСтрокуВнутр(струКоманд));
Прервать;
КонецЕсли;
КонецЕсли;
#КонецОбласти
КонецЕсли;
КонецЕсли;
Если рБылоИзменение Тогда
// фиксация в управляющей константе
Константы.КомандыМенеджеруКомСоединений.Установить(ЗначениеВСтрокуВнутр(струКоманд));
КонецЕсли;
КонецЦикла; // бесконечный цикл ожидания команд
#Область ПрерываниеПодключения
//=========== Конкретика сброса подключения к com-объекту/приложению/службе ====================================
//===================================================================================================
#КонецОбласти
КонецПроцедуры
// Сериализует переданные данные команды, вносит в структуру командной константы КомандыМенеджеруКомСоединений, затем находит нужное фоновое задание,
// т.е. активное и имеющее нужный ключ соединения, и читает его СообщенияПользователю в строку, где каждое сообщение отделено от других переносом строки.
// Возвращает прочитанные данные как строку.
//
// Параметры:
// КомандаСоединению - структура, где наиболее важен строковый ключ "Действие". Подробнее см. описание процедуры РаботаКомСоединения;
// КлючСоединения - строка; уникальный ключ фонового задания, в командной константе определяет действие для конкретного com-соединения.
//
Функция ПолучитьОтветКомСоединения(КомандаСоединению, КлючСоединения) Экспорт
Попытка
// получаем текущую командную структуру
строКоманд=Константы.КомандыМенеджеруКомСоединений.Получить();
струКоманд=Неопределено;
Если не ПустаяСтрока(строКоманд) Тогда
Попытка струКоманд=ЗначениеИзСтрокиВнутр(строКоманд) Исключение КонецПопытки;
КонецЕсли;
Если ТипЗнч(струКоманд)<>Тип("Структура") Тогда
струКоманд=Новый Структура;
КонецЕсли;
// смотрим, есть ли команда по ключу (если нет, мы её создаём)
струКомандыПоКлючу=Неопределено;
струКоманд.Свойство(КлючСоединения,струКомандыПоКлючу);
Если ТипЗнч(струКомандыПоКлючу)<>Тип("Структура") Тогда
струКомандыПоКлючу=Новый Структура;
струКомандыПоКлючу.Вставить("НомерСеанса",НомерСеансаИнформационнойБазы());
струКомандыПоКлючу.Вставить("НомерСоединения",НомерСоединенияИнформационнойБазы());
КонецЕсли;
// собственно передаём командные указания
струКомандыПоКлючу.Вставить("Управление",КомандаСоединению);
//
// вставляем нужную команду для соединения по его ключу
струКоманд.Вставить(КлючСоединения,струКомандыПоКлючу);
// фиксируем
Константы.КомандыМенеджеруКомСоединений.Установить(ЗначениеВСтрокуВнутр(струКоманд));
// при необходимости тут можно поставить задержку, чтобы успела обновиться константа и отработать фоновое задание!
// читаем ответ
мФЗ=ПолучитьАктивныеЗаданияКомСоединений();
Для каждого рФЗ Из мФЗ Цикл
Если рФЗ.Ключ<>КлючСоединения Тогда Продолжить КонецЕсли;
// получаем выданные им сообщения, очищая их
мсооб=рФЗ.ПолучитьСообщенияПользователю(Истина);
мТекст=Новый Массив;
Для каждого сооб Из мсооб Цикл
Если мТекст.Найти(сооб.Текст)=Неопределено Тогда мТекст.Добавить(сооб.Текст) КонецЕсли;
КонецЦикла;
Возврат СтрСоединить(мТекст,Символы.ПС);
КонецЦикла;
//
Возврат "";
Исключение
инфо="ПолучитьОтветКомСоединения, общая ошибка: "+ОписаниеОшибки();
Сообщить(инфо);
Возврат "";
КонецПопытки;
КонецФункции