Интеграция конфигураций «1С:Предприятие 8» через COM-соединение — это классический инструмент для обмена данными. Однако разработчики часто сталкиваются с неочевидным поведением платформы, когда при вызове метода СоздатьДокумент() через внешнее подключение внезапно возникают ошибки компиляции общих модулей или обработчиков подписок на события.
В этой статье мы подробно разберем, почему возникают ошибки инициализации модулей (например, связанные с переменной РежимДиалогаВопрос), почему «теряются» обработчики подписок на события при записи объектов через COM и как спроектировать код обмена по лучшим практикам разработки 1С.
Рассмотрим типичную ситуацию. Вы подключаетесь к базе-приемнику с помощью объекта V83.COMConnector и пытаетесь программно создать документ:
ДокКомандаОЛЕ = МенеджерДокумента.СоздатьДокумент();
В ответ платформа возвращает исключение вида:
«Произошла исключительная ситуация (1C:Enterprise 8.3): Ошибка инициализации модуля: ОбщийМодуль.ОбщегоНазначения.Модуль. Переменная не определена (РежимДиалогаВопрос)»
При этом вы точно знаете, что создаваемый документ не использует этот общий модуль напрямую, а сам метод создания документа не содержит интерактивных вызовов. Выясним причину этой проблемы.
Когда внешнее приложение подключается к информационной базе через COM-соединение, платформа инициализирует сеанс. При этом компилируются все общие модули, у которых в свойствах установлен флаг «Внешнее соединение». Компиляция модуля происходит целиком. Если в коде модуля присутствует хотя бы одна строка с упоминанием элементов клиентского интерфейса (например, Вопрос(), Предупреждение(), системное перечисление РежимДиалогаВопрос, ЭлементыФормы), компилятор выдает ошибку.
Контекст внешнего соединения (в отличие от обычного или тонкого клиента) полностью лишен интерфейсной части. Для него таких объектов и типов данных просто не существует. В результате сбоя компиляции общего модуля рушится весь процесс инициализации или выполнения метода менеджера объекта.
Для того чтобы решить проблему компиляции интерактивных функций во внешнем соединении, необходимо использовать инструкции препроцессора. Они позволяют исключить «проблемный» код из компиляции в тех контекстах, где он не поддерживается.
Проанализируем пример функции, вызывающей ошибку:
Функция ЗаписатьНовыйОбъектВФорме(ФормаОбъекта) Экспорт
Если ФормаОбъекта.ЭтоНовый Тогда
ТекстВопроса = "Для выполнения данной операции необходимо записать объект. Записать?";
Ответ = Вопрос(ТекстВопроса, РежимДиалогаВопрос.ДаНет);
Если Ответ <> КодВозвратаДиалога.Да Тогда
Возврат Ложь;
Иначе
Попытка
Возврат ФормаОбъекта.ЗаписатьВФорме();
Исключение
Сообщить(ОписаниеОшибки());
Возврат Ложь;
КонецПопытки;
КонецЕсли;
Иначе
Возврат Истина;
КонецЕсли;
КонецФункции
Чтобы этот общий модуль успешно компилировался под внешним соединением, мы должны обернуть весь интерактивный блок в инструкции препроцессора #Если Клиент Тогда:
Функция ЗаписатьНовыйОбъектВФорме(ФормаОбъекта) Экспорт
#Если Клиент Тогда
Если ФормаОбъекта.ЭтоНовый Тогда
ТекстВопроса = "Для выполнения данной операции необходимо записать объект. Записать?";
Ответ = Вопрос(ТекстВопроса, РежимДиалогаВопрос.ДаНет);
Если Ответ <> КодВозвратаДиалога.Да Тогда
Возврат Ложь;
Иначе
Попытка
Возврат ФормаОбъекта.ЗаписатьВФорме();
Исключение
Сообщить(ОписаниеОшибки());
Возврат Ложь;
КонецПопытки;
КонецЕсли;
Иначе
Возврат Истина;
КонецЕсли;
#Иначе
// Во внешнем соединении или на сервере форма не существует, возвращаем Ложь или Истина
Возврат Ложь;
#КонецЕсли
КонецФункции
При такой структуре кода препроцессор во время компиляции под внешним соединением просто проигнорирует ветку #Если Клиент, заменив ее безопасным возвратом значения, и компиляция модуля завершится успешно.
Согласно стандартам разработки фирмы «1С» (включая стандарты Библиотеки Стандартных Подсистем — БСП), смешивание клиентского (интерактивного) и серверного кода в одном общем модуле крайне не рекомендуется.
Правильным архитектурным решением будет разделение логики:
После решения проблем компиляции общего модуля разработчики часто сталкиваются со следующей проблемой при вызове метода Записать() или Записать(РежимЗаписиДокумента.Проведение):
«При подписке [ИмяПодписки] на событие ПередЗаписью произошла ошибка. Метод обработчика события [ИмяПроцедуры] не найден».
Выясним причину этой ошибки. Подписки на события объектов (такие как «ПередЗаписью», «ПриЗаписи», «ОбработкаПроведения») выполняются исключительно на стороне сервера или во внешнем соединении. Если разработчик конфигурации ошибочно обернул весь метод обработчика подписки в директиву препроцессора #Если Клиент Тогда ... #КонецЕсли, то происходит следующее:
#Если Клиент не выполняется, препроцессор полностью вырезает процедуру-обработчик из скомпилированного кода.Правило разработки: Экспортные методы общих модулей, которые назначены в качестве обработчиков подписок на события, никогда не должны полностью оборачиваться в директивы #Если Клиент или #Если Сервер. Сам заголовок процедуры должен компилироваться во всех средах (Сервер, Внешнее соединение):
// ПРАВИЛЬНО: заголовок процедуры доступен везде
Процедура ПроведениеБудущейДатойПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
// Внутри процедуры можно ограничить выполнение конкретных блоков
#Если Клиент Тогда
// Интерактивные предупреждения (хотя в подписках их быть не должно)
#КонецЕсли
// Основная логика проверки, которая должна работать и при COM-соединении
Если Источник.Дата > ТекущаяДатаСеанса() Тогда
// Код проверки...
КонецЕсли;
КонецПроцедуры
Когда вы переносите документы или справочники из одной базы в другую через COM, вам, как правило, не нужно, чтобы при записи выполнялись интерактивные проверки, заполнение реквизитов по умолчанию, отправка уведомлений или проверка прав пользователя. Все эти проверки должны выполняться в интерактивном режиме при вводе данных человеком.
Для отключения прикладных проверок при программном импорте данных в платформе 1С предусмотрено специальное свойство объекта — ОбменДанными.Загрузка. Установите его в значение Истина сразу после создания или получения объекта в COM-соединении:
ComПоступление = СоединениеБух.Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
// Заполнение реквизитов документа...
// Включаем режим загрузки данных
ComПоступление.ОбменДанными.Загрузка = Истина;
// Записываем документ
ComПоступление.Записать(СоединениеБух.РежимЗаписиДокумента.Записать);
Посмотрим, почему этот подход решает большинство проблем:
ПередЗаписью, ПриЗаписи, ОбработкаПроведения) и в подписках на события стоит проверка:
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Разберем важный нюанс работы технологий интеграции 1С. Для связи баз могут использоваться два разных класса:
V83.COMConnector — внутрипроцессный сервер. Он выполняет код в контексте внешнего соединения. У него нет интерфейса, он работает максимально быстро и требует минимум ресурсов. Это рекомендуемый стандарт для обмена данными.V83.Application — полноценный Automation-сервер. Он запускает копию клиентского приложения 1С в фоновом режиме. В этом контексте доступны клиентские модули и функции (включая Вопрос()). Однако использовать его для регулярных обменов крайне не рекомендуется: он работает медленно, потребляет много оперативной памяти, а в случае возникновения модального окна на стороне COM-сервера процесс просто «зависнет» навсегда в ожидании нажатия кнопки на невидимом экране.Поэтому правильным выбором всегда является использование V83.COMConnector в сочетании с корректным оформлением общих модулей директивами препроцессора.
Рассмотрим готовый шаблон кода, демонстрирующий правильное создание, заполнение табличной части и проведение документа во внешней базе с обработкой возможных исключений:
// Инициализируем COM-соединение
V83 = Новый COMОбъект("V83.COMConnector");
ПараметрыПодключения = "File=""C:\Base\Бухгалтерия"";Usr=""Администратор"";Pwd=""123"";";
Попытка
СоединениеБух = V83.Connect(ПараметрыПодключения);
Исключение
ВызватьИсключение "Не удалось установить связь с базой данных: " + ОписаниеОшибки();
КонецПопытки;
// Создаем новый документ "Поступление товаров и услуг"
ComПоступление = СоединениеБух.Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
// Заполняем реквизиты шапки через COM-соответствие типов
ComПоступление.Дата = ТекущаяДата();
ComПоступление.Организация = СоединениеБух.Справочники.Организации.НайтиПоКоду("00001");
ComПоступление.Контрагент = СоединениеБух.Справочники.Контрагенты.НайтиПоКоду("КН-00125");
ComПоступление.ДоговорКонтрагента = СоединениеБух.Справочники.ДоговорыКонтрагентов.НайтиПоКоду("ДГ-0041");
ComПоступление.Склад = СоединениеБух.Справочники.Склады.НайтиПоКоду("00000001");
// Добавляем строку в табличную часть "Товары"
НоваяСтрока = ComПоступление.Товары.Добавить();
НоваяСтрока.Номенклатура = СоединениеБух.Справочники.Номенклатура.НайтиПоКоду("НМ-00085");
НоваяСтрока.Количество = 10;
НоваяСтрока.Цена = 1500;
НоваяСтрока.Сумма = 15000;
НоваяСтрока.СтавкаНДС = СоединениеБух.Перечисления.СтавкиНДС.НДС20;
// Применяем свойство загрузки данных, чтобы обойти лишние проверки и UI-ошибки
ComПоступление.ОбменДанными.Загрузка = Истина;
// Записываем и проводим документ в безопасном блоке Попытка...Исключение
Попытка
// Сначала записываем объект в режиме записи
ComПоступление.Записать(СоединениеБух.РежимЗаписиДокумента.Записать);
// Если требуется провести документ:
// Перед проведением снимаем флаг Загрузка = Истина, так как проведение с флагом Загрузка не формирует движения в БСП
ComПоступление.ОбменДанными.Загрузка = Ложь;
ComПоступление.Записать(СоединениеБух.РежимЗаписиДокумента.Проведение);
Сообщить("Документ успешно создан и проведен: " + ComПоступление.Ссылка);
Исключение
Сообщить("Ошибка при записи/проведении документа: " + ОписаниеОшибки(), СтатусСообщения.Важное);
КонецПопытки;
Используя приведенные рекомендации, вы сможете проектировать надежные механизмы интеграции программных продуктов на платформе «1С:Предприятие 8», полностью защищенные от непредвиденных ошибок компиляции интерфейсных модулей и сбоев подписок на события.