При интеграции внешних систем с платформой 1С:Предприятие через COM-соединение (Automation сервер или V83.COMConnector) часто возникает задача обработки возвращаемых данных со сложной иерархией. Если внешняя обработка или функция возвращает объекты типов Структура или Соответствие, разработчики сталкиваются с трудностью: у этих типов нет методов последовательного чтения вроде Следующий(), к их элементам нельзя обратиться по числовому индексу, а состав ключей заранее неизвестен.
Разберем детально механизм взаимодействия .NET и платформы 1С, выясним, почему возникает это ограничение и как правильно организовать обход коллекций 1С на стороне C# без доработки конфигурации или изменения исходного кода обработок.
Проанализируем техническую сторону работы COM-объектов 1С. Во встроенном языке 1С доступ к элементам структуры осуществляется по имени свойства, а к соответствию — по ключу через метод Получить(). Однако прямой индексации по порядковому номеру (как в массиве) эти типы не поддерживают.
При обращении через OLE/COM из внешней среды кажется, что без знания имен ключей прочитать данные невозможно. Но в платформе 1С:Предприятие для объектов-коллекций реализован стандартный COM-интерфейс IEnumVARIANT (через DISPID_NEWENUM). Именно этот интерфейс обеспечивает работу цикла Для Каждого внутри 1С и позволяет внешним языкам перебирать элементы.
Поскольку среда выполнения .NET CLR автоматически проецирует интерфейс IEnumVARIANT COM-объекта в стандартный интерфейс System.Collections.IEnumerable, нам не требуется вручную вызывать низкоуровневые методы навигации. Достаточно использовать стандартный оператор foreach языка C#.
Посмотрим на пример обхода структуры или соответствия 1С в C# с использованием типа dynamic:
// Подключаемся к базе через COMConnector
Type comType = Type.GetTypeFromProgID("V83.COMConnector");
dynamic connector = Activator.CreateInstance(comType);
dynamic connection = connector.Connect(@"File=""C:\Bases\Demo"";Usr=""Admin"";");
// Получаем объект обработки или результат вычислений
dynamic extProcessing = connection.ВнешниеОбработки.Создать(@"C:\Shared\GetData.epf");
dynamic resultData = extProcessing.ПолучитьРезультат(); // Возвращает Структуру или Соответствие
// Перебираем элементы коллекции
foreach (dynamic item in (System.Collections.IEnumerable)resultData)
{
// Каждый элемент представляет собой объект 1С "КлючИЗначение"
dynamic key = item.Key; // или item.Ключ
dynamic value = item.Value; // или item.Значение
Console.WriteLine($"Ключ: {connection.String(key)}, Значение: {connection.String(value)}");
}
Рассмотрим подробнее, что происходит внутри цикла:
System.Collections.IEnumerable запрашивает у COM-объекта интерфейс перечислителя.КлючИЗначение.Ключ (или Key) и Значение (или Value), которые содержат исходные данные.Когда ключом или значением соответствия является не примитивный тип, а ссылка, массив или вложенная структура, необходимо корректно определить тип полученного объекта. Встроенная функция ТипЗнч() недоступна напрямую через внешнее соединение, так как является синтаксической конструкцией языка 1С. Для решения этой задачи используем системные методы глобального контекста:
// Получение строкового представления объекта 1С
string stringRepresentation = connection.String(value);
// Получение типа XDTO для точного анализа типа 1С
dynamic xdtoType = connection.XMLТипЗнч(value);
if (xdtoType != null)
{
string typeName = xdtoType.TypeName; // Имя типа (например, "CatalogRef.Контрагенты")
Console.WriteLine($"Тип данных 1С: {typeName}");
}
Если результат вычисления содержит глубоко вложенную иерархию данных и частые COM-вызовы создают задержки по сети, целесообразно выполнить сериализацию силами самой платформы 1С прямо через COM-соединение, не меняя код конфигурации.
Разберем по шагам сериализацию через СериализаторXDTO или объект ЗаписьJSON:
// Создаем объект ЗаписьJSON в контексте 1С
dynamic jsonWriter = connection.NewObject("ЗаписьJSON");
jsonWriter.УстановитьСтроку();
// Записываем структуру данных
connection.ЗаписатьJSON(jsonWriter, resultData);
// Получаем готовую JSON-строку
string jsonString = jsonWriter.Закрыть();
// Теперь строку можно разобрать любым стандартным парсером .NET (System.Text.Json или Newtonsoft.Json)
Console.WriteLine(jsonString);
Этот подход избавляет от необходимости рекурсивно обходить дерево COM-объектов и существенно повышает скорость передачи больших объемов данных.
При реализации обхода коллекций 1С через COM-соединение обязательно учитываем следующие архитектурные особенности:
Marshal.ReleaseComObject(item) или Marshal.FinalReleaseComObject(connection) после завершения работы, чтобы предотвратить утечки памяти и зависание сеансов на сервере 1С.comcntr.dll платформы 1С. При несовпадении потребуется использовать суррогатный процесс через службу компонентов Windows (COM+).Выполнить() выполняет код в изолированной области видимости — локальные переменные внутри блока уничтожаются сразу после выполнения команды. Поэтому динамический запуск кода не подходит для сохранения промежуточных ссылок на ключи.