При организации обмена данными между информационными базами 1С через COM-соединение разработчики часто сталкиваются с неочевидными ошибками. Среди них наиболее распространены две: ошибка вызова глобальных методов внешнего контекста (например, NewObject или НовыйОбъект) и падение программы при попытке обратиться к элементам коллекций метаданных по индексу в квадратных скобках []. Разберем по шагам причины такого поведения платформы и рассмотрим правильные варианты реализации обмена данными.
Проанализируем ситуацию, когда при выполнении следующей строки возникает исключение:
УидВоВнешнейБазе = БазаОткрыта.NewObject("УникальныйИдентификатор", Строка(УидВЭтойБазе));
Существует две основные причины, приводящие к такой ошибке:
COMConnector выполнена на клиенте, а затем переменная передается в серверную процедуру (или наоборот), значение переменной теряется и превращается в Неопределено либо теряет доступные методы. Все манипуляции с COM-соединением должны происходить строго в рамках одного контекста (рекомендуется выполнять весь обмен исключительно на сервере).V83.COMConnector метод Connect() возвращает ссылку на глобальный контекст внешней базы данных. Именно у этой ссылки доступны методы NewObject, НовыйОбъект, Справочники, Документы и т.д. Если же используется OLE-сервер V83.Application, метод Connect() возвращает лишь булево значение (Истина/Ложь), а обращение к глобальному контексту необходимо выполнять напрямую через созданный COM-объект приложения.Посмотрим на корректный пример инициализации подключения через COMConnector на стороне сервера:
&НаСервере
Процедура ВыполнитьПодключениеИОбмен()
ПараметрыПодключения = "File=""" + СокрЛП(ПутьКБазе) + """; Usr=""" + СокрЛП(Логин) + """; Pwd=""" + СокрЛП(Пароль) + """;";
Попытка
COMСоединитель = Новый COMОбъект("V83.COMConnector");
ВнешняяБаза = COMСоединитель.Connect(ПараметрыПодключения);
Исключение
ЗаписьЖурналаРегистрации("ОбменЧерезCOM", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
ВызватьИсключение "Не удалось подключиться к внешней базе: " + ОписаниеОшибки();
КонецПопытки;
// Передаем валидный COM-объект в процедуру внутри сервера
СкопироватьДанные(ВнешняяБаза);
КонецПроцедуры
Следующая частая проблема возникает при обходе метаданных внешней базы. В коде текущей конфигурации 1С программисты привыкли обращаться к элементам коллекций (реквизитам, табличным частям, колонкам) через квадратные скобки: Метаданные().Реквизиты[Сч].Имя.
Однако при работе с COM-объектом интерфейс IDispatch не поддерживает синтаксис квадратных скобок для коллекций внешней базы. Попытка выполнить такой код приведет к ошибке выполнения.
Выясним правильные способы обхода коллекций объектов внешней базы:
Получить(Индекс) или Get(Index):
КоличествоРеквизитов = ВнешнийОбъект.Метаданные().Реквизиты.Количество();
Для Сч = 0 По КоличествоРеквизитов - 1 Цикл
Реквизит = ВнешнийОбъект.Метаданные().Реквизиты.Получить(Сч);
ИмяРеквизита = Реквизит.Имя;
// Дальнейшая обработка реквизита
КонецЦикла;
Для Каждого:
Для Каждого Реквизит Из ВнешнийОбъект.Метаданные().Реквизиты Цикл
ИмяРеквизита = Реквизит.Имя;
// Дальнейшая обработка реквизита
КонецЦикла;
Рассмотрим, как правильно сопоставлять и переносить реквизиты, если состав метаданных в базах различается (в приемнике реквизитов меньше или они имеют другой порядок).
Если требуется перенести простые типы данных (числа, строки, даты, булево) по совпадающим именам, нет необходимости вручную перебирать метаданные. Функция платформы ЗаполнитьЗначенияСвойств прекрасно работает с COM-объектами:
// Автоматически скопирует все совпадающие по имени реквизиты простых типов
ЗаполнитьЗначенияСвойств(ОбъектПриемник, COMОбъектИсточник);
Если же требуется выборочный перенос с проверкой типов или сопоставлением ссылок, разберем по шагам правильный алгоритм динамического сопоставления:
&НаСервере
Процедура ПеренестиРеквизиты(ВнешняяБаза, COMИсточник, ОбъектПриемник)
МетаданныеПриемника = ОбъектПриемник.Метаданные();
МетаданныеИсточника = COMИсточник.Метаданные();
Для Каждого РеквизитПриемника Из МетаданныеПриемника.Реквизиты Цикл
ИмяРеквизита = РеквизитПриемника.Имя;
// Проверяем наличие реквизита в источнике
РеквизитИсточника = МетаданныеИсточника.Реквизиты.Найти(ИмяРеквизита);
Если РеквизитИсточника <> Неопределено Тогда
// Динамически получаем значение из COM-источника
ЗначениеИсточника = COMИсточник[ИмяРеквизита];
// Простые типы переносим напрямую
Если ТипЗнч(ЗначениеИсточника) = Тип("Строка")
Или ТипЗнч(ЗначениеИсточника) = Тип("Число")
Или ТипЗнч(ЗначениеИсточника) = Тип("Дата")
Или ТипЗнч(ЗначениеИсточника) = Тип("Булево") Тогда
ОбъектПриемник[ИмяРеквизита] = ЗначениеИсточника;
Иначе
// Ссылочные типы переносятся через строковый GUID
Если ЗначениеЗаполнено(ЗначениеИсточника) Тогда
СтрокаГУИД = Строка(ЗначениеИсточника.УникальныйИдентификатор());
УидПриемника = Новый УникальныйИдентификатор(СтрокаГУИД);
// Пример поиска элемента в справочнике базы-приемника по GUID
// ОбъектПриемник[ИмяРеквизита] = Справочники[ИмяСправочника].ПолучитьСсылку(УидПриемника);
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
При проектировании механизмов обмена на базе технологии COM следует учитывать ряд технических нюансов платформы:
COMConnector.Connect() занимает отдельную клиентскую лицензию платформы 1С на все время активности сеанса.