Как передать таблицу значений или массив из 1С в VBA без ошибок 438 и 1001?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При интеграции офисных приложений с 1С через COM-соединение (с использованием V83.COMConnector или V82.COMConnector) разработчики часто сталкиваются с ситуацией, когда простые типы данных (числа, строки, булево) передаются без нареканий, а при попытке вернуть коллекцию данных — например, ТаблицаЗначений или Массив — возникают исключения среды Visual Basic for Applications (VBA) или самой платформы 1С.

Проанализируем природу этих ошибок, выясним причину их возникновения и разберем по шагам рабочие способы передачи табличных данных из 1С в среду VBA Excel.

Причины возникновения ошибок Runtime 438 и Runtime 1001

В процессе обмена данными через COM-соединение чаще всего возникают две взаимосвязанные проблемы:

  1. Ошибка VBA «Runtime error 438: Object doesn't support this property or method»: В среде Visual Basic все переменные, содержащие ссылки на COM-объекты, обязаны инициализироваться через ключевое слово Set. Если написать конструкцию вида Rslt = Con.МояФункция(), VBA пытается прочитать у возвращенного объекта его свойство по умолчанию (DISPID_VALUE). Поскольку у объектов платформы 1С (таких как Массив, Структура или ТаблицаЗначений) свойства по умолчанию нет, интерпретатор VBA генерирует ошибку 438. Кроме того, данная ошибка возникает при попытке перебрать элементы коллекции 1С циклом For Each, так как объекты 1С не поддерживают стандартный интерфейс перечислителя IEnumVARIANT.
  2. Ошибка 1С «Runtime 1001: Попытка передачи мутабельного значения результата метода»: Данная ошибка формируется на стороне платформы 1С, когда вызов функции происходит с переходом границы клиент-серверного контекста. Если метод объявлен в общем модуле с признаком «Вызов сервера», платформа запрещает передавать обратно клиенту или внешнему соединению любые изменяемые (мутабельные) объекты: ТаблицаЗначений, Массив, Структура и COMSafeArray.

Способ 1. Использование оператора Set и прямой вызов методов объектов 1С

Разберем самый простой вариант, который решает ошибку 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

Способ 2. Использование иммутабельных типов при серверных вызовах

Если код выборки данных обязательно должен выполняться на стороне сервера (например, при наличии серверных вызовов в модуле с признаком «Сервер»), мутабельные объекты вернут ошибку 1001. Чтобы обойти это платформенное ограничение, преобразуем изменяемые коллекции в фиксированные аналоги:

  1. Вместо Массив используем ФиксированныйМассив;
  2. Вместо Структура используем ФиксированнаяСтруктура;
  3. Вместо Соответствие используем ФиксированноеСоответствие.

Рассмотрим пример функции 1С, безопасной для клиент-серверной передачи:


Функция ПолучитьСписокСервер() Экспорт
    ВременныйМассив = Новый Массив;
    ВременныйМассив.Добавить("Значение 1");
    ВременныйМассив.Добавить("Значение 2");
    
    // Преобразуем мутабельный массив в фиксированный
    Возврат Новый ФиксированныйМассив(ВременныйМассив);
КонецФункции

В коде VBA данный объект точно так же принимается через Set и поддерживает чтение элементов через метод Получить(i).

Способ 3. Передача через COMSafeArray для прямой выгрузки на лист Excel

Построчное чтение 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

Способ 4. Передача через сериализацию в JSON

Если архитектура конфигурации не позволяет менять свойства общих модулей, наиболее универсальным и быстрым решением является передача данных в виде сериализованной строки JSON. Строка — это примитивный тип, который никогда не вызывает ошибку мутабельности 1001 и гарантированно передается через COM-соединение любой версии.

Сформируем JSON на стороне 1С:


Функция ПолучитьТаблицуJSON() Экспорт
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.УстановитьСтроку();
    
    // Допустим, получаем готовую таблицу значений
    Таблица = ПолучитьДанныеИзБазы();
    
    // Сериализуем структуру данных
    ЗаписатьJSON(ЗаписьJSON, Таблица);
    Возврат ЗаписьJSON.Закрыть();
КонецФункции

Полученную строку в VBA можно разобрать с помощью внешнего JSON-парсера либо использовать механизм ADODB.Recordset для моментальной вставки данных на лист через метод CopyFromRecordset.

← На главную