При интеграции различных информационных баз 1С с использованием механизмов COM-соединения (внешнего соединения) разработчики часто сталкиваются с необходимостью обрабатывать исключения. Типичный сценарий: в удаленной базе программно создается или проводится документ, возникает ошибка (например, не заполнена аналитика или не хватает остатков на складе), и нам требуется получить понятный текст этой ошибки в нашей текущей базе.
Попытка вызвать метод ОписаниеОшибки() непосредственно у объекта внешнего соединения (например, ВнешнееСоединение.ОписаниеОшибки()) закономерно приводит к аварийному завершению программы с системным сообщением: «Метод объекта не обнаружен (ErrorDescription)» или «Метод объекта не обнаружен (ОписаниеОшибки)».
В этой статье мы подробно разберем, почему возникает эта ошибка, как устроен контекст работы внешнего соединения и какие существуют два надежных способа получения детального текста ошибки из подключаемой базы.
Проанализируем ситуацию с точки зрения архитектуры платформы 1С:Предприятие. В Синтаксис-помощнике для встроенной функции ОписаниеОшибки() в графе «Доступность» действительно указано «Внешнее соединение». Это часто и вводит разработчиков в заблуждение.
Однако эта доступность означает лишь то, что данную функцию можно использовать внутри кода, который компилируется и выполняется на стороне подключаемой базы (например, в ее общих модулях, модулях менеджеров или модулях объектов). Сам по себе COM-объект ВнешнееСоединение (компонент V83.COMConnector) является лишь шлюзом, мостом к метаданным удаленной базы. Он не экспортирует системные глобальные функции платформы как свои собственные методы.
Поэтому конструкция вида ВнешнееСоединение.ОписаниеОшибки() ошибочна в своей основе — у COM-объекта физически отсутствует такой метод в его интерфейсе автоматизации.
Рассмотрим самый простой и быстрый способ перехвата ошибок. Когда в подключаемой базе происходит исключение (например, при вызове метода транзакции или проведения), и оно не обрабатывается внутри самой подключаемой базы, платформа 1С автоматически транслирует это исключение обратно в вызывающий код в виде COM-исключения.
Это означает, что нам достаточно поместить весь блок работы с внешним соединением в стандартную локальную конструкцию Попытка ... Исключение и вызвать локальную функцию ОписаниеОшибки() без каких-либо префиксов.
Разберем по шагам, как это реализовать на практике:
Попытка
// Подключаемся к базе-приемнику
COMКоннектор = Новый COMОбъект("V83.COMConnector");
ПутьКБазе = "Srvr=""Server1C"";Ref=""Trade_Base"";";
ВнешнееСоединение = COMКоннектор.Connect(ПутьКБазе);
// Пытаемся получить и провести документ в удаленной базе
ДокументСсылка = ВнешнееСоединение.Документы.РеализацияТоваровУслуг.НайтиПоНомеру("ТД0001", ТекущаяДата());
Если Не ДокументСсылка.Пустая() Тогда
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
// Пытаемся провести документ
ДокументОбъект.Записать(ВнешнееСоединение.РежимЗаписиДокумента.Проведение);
Сообщить("Документ успешно проведен в удаленной базе!");
Иначе
Сообщить("Документ не найден!");
EndIf;
Исключение
// Внимание: вызываем ЛОКАЛЬНУЮ функцию ОписаниеОшибки() вызывающей базы
ТекстОшибки = ОписаниеОшибки();
// Выводим информацию пользователю или записываем в Журнал Регистрации
ЗаписьЖурналаРегистрации("Интеграция.COM",
УровеньЖурналаРегистрации.Ошибка,
,
,
"Ошибка при проведении документа через COM: " + ТекстОшибки);
Сообщить("Не удалось выполнить операцию. Подробности: " + ТекстОшибки);
КонецПопытки;
Что произойдет под капотом системы: При возникновении ошибки проведения в удаленной базе (например, «Недостаточно товара на складе»), COM-соединение сгенерирует стандартную исключительную ситуацию Windows (HRESULT). Локальный блок Исключение перехватит ее. Локальная функция ОписаниеОшибки() вернет строку, в которой будет содержаться подробный стек ошибки, пришедший из удаленной базы. Текст будет выглядеть примерно так:
Произошла исключительная ситуация (V83.COMConnector.1): {ОбщийМодуль.УчетЗапасовСлужебный.Модуль(1435)}: Ошибка при вызове метода контекста (Записать): Не удалось провести "Реализация товаров и услуг ТД0001"! Недостаточно товара на складе...
Этот способ отлично подходит для простых задач, когда вам нужно лишь зафиксировать факт сбоя и получить системный лог ошибки.
Хотя первый способ прост, у него есть существенный недостаток: системный текст ошибки часто перегружен техническими деталями (именами общих модулей, номерами строк кода, техническими заголовками COM-соединения). Пользователю такую информацию показывать не стоит. Кроме того, при падении в системное исключение прерывается текущая транзакция в вызывающей базе, что не всегда желательно.
Для создания отказоустойчивых систем интеграции мы рекомендуем использовать архитектурный паттерн «Функция-обертка».
Посмотрим на пример реализации данного подхода шаг за шагом:
В конфигурации подключаемой базы необходимо создать (или использовать готовый) общий модуль, например, с именем ИнтеграцияВызовИзCOM. В свойствах этого модуля обязательно должны быть установлены следующие флаги:
Внутри этого модуля мы пишем функцию, которая будет инкапсулировать всю небезопасную логику работы с объектами в блоке Попытка ... Исключение. Поскольку этот код выполняется непосредственно в контексте подключаемой базы, системная функция ОписаниеОшибки() здесь отработает корректно и вернет чистый текст ошибки без COM-оберток.
// Функция выполняет проведение документа и возвращает текст ошибки, если она возникла
//
// Параметры:
// ДокументСсылка - ДокументСсылка - ссылка на документ, который нужно провести
//
// Возвращаемое значение:
// Строка - Пустая строка, если проведение успешно, иначе - текст ошибки
//
Функция ПровестиДокументИзВнешнегоИсточника(ДокументСсылка) Экспорт
УстановитьПривилегированныйРежим(Истина); // При необходимости обхода ограничений RLS
Попытка
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
Если ДокументОбъект = Неопределено Тогда
Возврат "Документ не найден по переданной ссылке.";
КонецЕсли;
// Пытаемся провести документ в режиме проведения
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Возврат ""; // Ошибок нет, возвращаем пустую строку
Исключение
// Здесь ОписаниеОшибки() работает локально внутри контекста подключаемой базы
ТекстОшибки = ОписаниеОшибки();
// Можно дополнительно проанализировать сообщения пользователю
ТаблицаСообщений = ПолучитьСообщенияПользователю(Истина);
Для Каждого Сообщение Из ТаблицаСообщений Цикл
ТекстОшибки = ТекстОшибки + Символы.ПС + Сообщение.Текст;
КонецЦикла;
Возврат ТекстОшибки;
КонецПопытки;
КонецФункции
Теперь в нашей основной базе код вызова удаленного метода будет выглядеть лаконично, безопасно и контролируемо:
// Подключение к базе (предполагаем, что объект ВнешнееСоединение уже инициализирован)
// ...
УдаленныйДокумент = ВнешнееСоединение.Документы.РеализацияТоваровУслуг.НайтиПоНомеру("ТД0001", ТекущаяДата());
Если Не УдаленныйДокумент.Пустая() Тогда
// Вызываем нашу функцию-обертку через точку от имени общего модуля удаленной базы
РезультатПроведения = ВнешнееСоединение.ИнтеграцияВызовИзCOM.ПровестиДокументИзВнешнегоИсточника(УдаленныйДокумент);
Если ЗначениеЗаполнено(РезультатПроведения) Тогда
// Ошибка обработана удаленной базой и возвращена нам в виде строки
Сообщить("Не удалось провести документ. Причина: " + РезультатПроведения);
Иначе
Сообщить("Документ успешно проведен!");
EndIf;
КонецЕсли;
Используя данный подход, мы полностью защищаем вызывающий код от непредвиденных падений COM-соединения, получаем чистый лог ошибок и можем гибко управлять транзакционной логикой.
Проанализируем еще одну скрытую проблему, из-за которой часто не получается провести документ через внешнее соединение, хотя интерактивно под тем же пользователем все проводится без проблем.
Когда вы запускаете проведение документа через COM, весь программный код (включая обработчики событий ПередЗаписью, ПриЗаписи, ОбработкаПроведения в модуле документа и подписках на события) выполняется в контексте внешнего соединения.
Если в цепочке вызовов процедур и функций окажется хотя бы один общий модуль, у которого в свойствах не взведен флаг «Внешнее соединение», платформа 1С не сможет скомпилировать этот модуль. В результате система мгновенно прекратит выполнение с критической ошибкой инициализации модуля или ошибкой вызова метода.
Рекомендация по устранению: Если при проведении через COM вы получаете неинформативную ошибку, проверьте свойства всех общих модулей, задействованных в цепочке проведения документа в базе-приемнике. Убедитесь, что для них разрешена компиляция во внешнем соединении.
Применяйте рассмотренные нами методы, проектируйте интеграционные решения правильно, и ваши COM-соединения будут работать стабильно и предсказуемо!