При интеграции офисных приложений с 1С через COM-соединение (с использованием V83.COMConnector или V82.COMConnector) разработчики часто сталкиваются с ситуацией, когда простые типы данных (числа, строки, булево) передаются без нареканий, а при попытке вернуть коллекцию данных — например, ТаблицаЗначений или Массив — возникают исключения среды Visual Basic for Applications (VBA) или самой платформы 1С.
Проанализируем природу этих ошибок, выясним причину их возникновения и разберем по шагам рабочие способы передачи табличных данных из 1С в среду VBA Excel.
В процессе обмена данными через COM-соединение чаще всего возникают две взаимосвязанные проблемы:
Set. Если написать конструкцию вида Rslt = Con.МояФункция(), VBA пытается прочитать у возвращенного объекта его свойство по умолчанию (DISPID_VALUE). Поскольку у объектов платформы 1С (таких как Массив, Структура или ТаблицаЗначений) свойства по умолчанию нет, интерпретатор VBA генерирует ошибку 438. Кроме того, данная ошибка возникает при попытке перебрать элементы коллекции 1С циклом For Each, так как объекты 1С не поддерживают стандартный интерфейс перечислителя IEnumVARIANT.ТаблицаЗначений, Массив, Структура и COMSafeArray.Разберем самый простой вариант, который решает ошибку 438 при получении массива или таблицы значений. Если вызываемая процедура выполняется непосредственно в контексте внешнего соединения (у общего модуля установлен флаг «Внешнее соединение» без трансляции на сервер), 1С без проблем возвращает ссылку на COM-объект коллекции.
Посмотрим на пример функции в общем модуле конфигурации 1С:
Функция ПолучитьМассивДанных() Экспорт
Результат = Новый Массив;
Результат.Добавить("Товар 1");
Результат.Добавить("Товар 2");
Результат.Добавить("Товар 3");
Возврат Результат;
КонецФункции
В коде VBA критически важно использовать оператор Set при получении значения функции, а также осуществлять обход по обычному числовому индексу через метод Получить():
Sub GetDataFrom1C()
Dim Connector As Object
Dim Connection As Object
Dim ResultArray As Object
Dim i As Long
Set Connector = CreateObject("V83.COMConnector")
Set Connection = Connector.Connect("File=""C:\Bases\Demo"";Usr=""Администратор"";")
' Обязательно используем Set для COM-объекта
Set ResultArray = Connection.МойОбщийМодуль.ПолучитьМассивДанных()
' Обход элементов выполняем по индексу, начиная с нуля
For i = 0 To ResultArray.Количество() - 1
Cells(i + 1, 1).Value = ResultArray.Получить(i)
Next i
End Sub
Аналогичным образом можно обрабатывать и объект ТаблицаЗначений. Получив таблицу значений через Set, мы можем обращаться к ее строкам и колонкам:
Sub GetTableFrom1C()
Dim VT As Object
Dim Row As Object
Dim i As Long
Set VT = Connection.МойОбщийМодуль.ПолучитьТаблицу()
For i = 0 To VT.Количество() - 1
Set Row = VT.Получить(i)
Cells(i + 1, 1).Value = Row.Наименование
Cells(i + 1, 2).Value = Row.Количество
Next i
End Sub
Если код выборки данных обязательно должен выполняться на стороне сервера (например, при наличии серверных вызовов в модуле с признаком «Сервер»), мутабельные объекты вернут ошибку 1001. Чтобы обойти это платформенное ограничение, преобразуем изменяемые коллекции в фиксированные аналоги:
Массив используем ФиксированныйМассив;Структура используем ФиксированнаяСтруктура;Соответствие используем ФиксированноеСоответствие.Рассмотрим пример функции 1С, безопасной для клиент-серверной передачи:
Функция ПолучитьСписокСервер() Экспорт
ВременныйМассив = Новый Массив;
ВременныйМассив.Добавить("Значение 1");
ВременныйМассив.Добавить("Значение 2");
// Преобразуем мутабельный массив в фиксированный
Возврат Новый ФиксированныйМассив(ВременныйМассив);
КонецФункции
В коде VBA данный объект точно так же принимается через Set и поддерживает чтение элементов через метод Получить(i).
Построчное чтение COM-объекта в цикле VBA выполняется медленно, если в таблице содержатся тысячи записей, из-за накладных расходов на межпроцессный маршалинг при каждом обращении к свойству или методу. Объект COMSafeArray позволяет выгрузить весь массив данных в ячейки Excel за одну операцию.
Помним главное правило: COMSafeArray нельзя возвращать из методов с переходом клиент-серверной границы, его инициализация должна происходить исключительно в модуле с признаком «Внешнее соединение».
Посмотрим на пример создания двумерного массива на стороне 1С:
Функция ПолучитьSafeArray() Экспорт
// Создаем двумерный SafeArray (строки, колонки)
SafeArr = Новый COMSafeArray("VT_VARIANT", 3, 2);
SafeArr.Установить(0, 0, "Код 001");
SafeArr.Установить(0, 1, 100);
SafeArr.Установить(1, 0, "Код 002");
SafeArr.Установить(1, 1, 250);
SafeArr.Установить(2, 0, "Код 003");
SafeArr.Установить(2, 1, 420);
Возврат SafeArr;
КонецФункции
В среде VBA результат вызова преобразуется в стандартный встроенный массив Visual Basic, поэтому присваивание переменной выполняется без ключевого слова Set:
Sub GetSafeArrayFrom1C()
Dim SafeData As Variant
' Присваиваем без Set, так как SafeArray транслируется в нативный массив VBA
SafeData = Connection.МойОбщийМодуль.ПолучитьSafeArray()
' Мгновенная вставка всего диапазона на рабочий лист
Range("A1:B3").Value = SafeData
End Sub
Если архитектура конфигурации не позволяет менять свойства общих модулей, наиболее универсальным и быстрым решением является передача данных в виде сериализованной строки JSON. Строка — это примитивный тип, который никогда не вызывает ошибку мутабельности 1001 и гарантированно передается через COM-соединение любой версии.
Сформируем JSON на стороне 1С:
Функция ПолучитьТаблицуJSON() Экспорт
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
// Допустим, получаем готовую таблицу значений
Таблица = ПолучитьДанныеИзБазы();
// Сериализуем структуру данных
ЗаписатьJSON(ЗаписьJSON, Таблица);
Возврат ЗаписьJSON.Закрыть();
КонецФункции
Полученную строку в VBA можно разобрать с помощью внешнего JSON-парсера либо использовать механизм ADODB.Recordset для моментальной вставки данных на лист через метод CopyFromRecordset.