COM-объект живёт только на клиенте: как довезти его данные до сервера 1С

· 9 мин

Содержание

Передать сам COM-объект с клиента на сервер 1С нельзя: это ссылка на процесс Windows на компьютере пользователя, а не данные. На сервер уходят только сериализуемые значения — примитивы, Массив, Структура, Соответствие, ДвоичныеДанные, данные формы. Поэтому задача решается так: на клиенте вынимаете из COM нужные значения в эти типы и передаёте их одним серверным вызовом.

Типичная ситуация: программист открывает прайс через Excel.Application в обработке, хочет отдать Лист в процедуру &НаСервере для записи в справочник — и получает ошибку при вызове. Ниже пять рабочих способов и разбор граблей.

Почему COM-объект не передаётся с клиента на сервер

Клиент и сервер 1С — разные процессы, а в клиент-серверном варианте ещё и разные компьютеры. При серверном вызове платформа сериализует параметры, передаёт их и восстанавливает на другой стороне. У COM-объекта нечего сериализовать: внутри лишь указатель на интерфейс в памяти клиента.

Что не передаётся между клиентом и сервером:

  • ❌ COMОбъект — Excel, Word, ADODB, MSXML, драйверы оборудования;
  • ❌ COMSafeArray — массив из COM тоже остаётся клиентским объектом;
  • ❌ любая коллекция, внутри которой лежит COM-объект: Структура с полем Книга упадёт так же;
  • ❌ реквизит формы с COM-объектом — такой тип реквизиту формы назначить нельзя.

Что передаётся без проблем:

  • ✅ Строка, Число, Дата, Булево, Неопределено;
  • ✅ Массив, Структура, Соответствие из этих значений;
  • ✅ ДвоичныеДанные, ХранилищеЗначения, адрес временного хранилища;
  • ✅ реквизиты формы, включая табличные (на клиенте это ДанныеФормыКоллекция).

Держать COM-объект между вызовами можно в переменной клиентского модуля формы (Перем Excel; с директивой &НаКлиенте). Она живёт, пока открыта форма, и в серверные вызовы не попадает.

Есть и обратный путь — создать COM-объект прямо на сервере. Он работает только на сервере под Windows, с нужной программой, установленной на сервере, и с правами учётной записи службы 1С. На Linux-сервере COM нет вообще. Для Excel на сервере это плохая идея: офисные программы не рассчитаны на работу из службы.

Какой способ выбрать

СпособКогда подходитОграничения
Табличный реквизит формыТабличные данные, тонкий клиент, управляемая формаНужно заранее описать колонки
Таблица значенийТолстый клиент, обычное приложениеВ тонком клиенте ТаблицаЗначений недоступна
Файл через временное хранилищеБольшие файлы, разбор на сервереСервер должен уметь прочитать формат
JSON или XML строкойИерархия, логирование, фоновые заданияДаты при чтении JSON нужно указать явно
Массив из COMSafeArrayExcel Range.Value, ADO GetRows()Порядок индексов надо проверить

Перенос данных через таблицу значений и табличный реквизит формы

Самый удобный путь для табличных данных из Excel или ADO-выборки — заполнить табличный реквизит формы на клиенте. Данные формы платформа передаёт на сервер сама при вызове &НаСервере, а там реквизит превращается в обычную таблицу значений.

Шаги для управляемой формы

  1. Добавьте реквизит формы ТаблицаДанных с типом ТаблицаЗначений.
  2. Опишите колонки: Артикул (Строка), Количество (Число), Цена (Число). Если источник «грязный», задайте колонкам составной тип или Произвольный, а приведение сделайте на сервере.
  3. На клиенте прочитайте COM-объект и заполните строки.
  4. Вызовите серверную процедуру без параметров — таблица уже в данных формы.
&НаКлиенте
Процедура ЗагрузитьИзExcel(Команда)
	Excel = Новый COMОбъект("Excel.Application");
	Попытка
		Книга = Excel.Workbooks.Open(ПутьКФайлу);
		Данные = Книга.Worksheets(1).UsedRange.Value.Выгрузить();
		Книга.Close(False);
	Исключение
		Excel.Quit();
		ВызватьИсключение;
	КонецПопытки;
	Excel.Quit();

	ТаблицаДанных.Очистить();
	КоличествоСтрок = Данные[0].Количество();
	Для НомерСтроки = 1 По КоличествоСтрок - 1 Цикл // строка 0 — шапка
		НоваяСтрока = ТаблицаДанных.Добавить();
		НоваяСтрока.Артикул    = Данные[0][НомерСтроки];
		НоваяСтрока.Количество = Данные[1][НомерСтроки];
		НоваяСтрока.Цена       = Данные[2][НомерСтроки];
	КонецЦикла;

	ЗаписатьНаСервере();
КонецПроцедуры

&НаСервере
Процедура ЗаписатьНаСервере()
	Таблица = РеквизитФормыВЗначение("ТаблицаДанных");
	Для Каждого Строка Из Таблица Цикл
		// поиск номенклатуры по артикулу, запись цен
	КонецЦикла;
КонецПроцедуры

Здесь Данные — массив массивов, полученный из COMSafeArray (подробнее в разделе про SafeArray). Сервер получает только числа и строки.

Толстый клиент и обычные формы

В толстом клиенте ТаблицаЗначений доступна на клиенте. Её можно заполнить и передать параметром в процедуру общего модуля с флагом «Вызов сервера». Колонки создайте через Таблица.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число")) — так значения сразу приведутся к типу.

Тонкий клиент без формы

Если формы нет (например, код в общем клиентском модуле), соберите Массив из Структура: Новый Структура("Артикул, Количество, Цена", А, К, Ц). Такой массив передаётся параметром в серверный модуль напрямую.

Двоичные данные и временное хранилище: когда проще передать файл

Если COM-объект работает с файлом — книга Excel, документ Word, скан со сканера, — часто проще не разбирать его на клиенте. Сохраните результат в файл, прочитайте файл в ДвоичныеДанные и отдайте серверу через временное хранилище.

&НаКлиенте
Процедура ПередатьФайл(Команда)
	Двоичные = Новый ДвоичныеДанные(ПутьКФайлу);
	Адрес = ПоместитьВоВременноеХранилище(Двоичные, УникальныйИдентификатор);
	РазобратьНаСервере(Адрес);
КонецПроцедуры

&НаСервере
Процедура РазобратьНаСервере(Адрес)
	Двоичные = ПолучитьИзВременногоХранилища(Адрес);
	ИмяФайла = ПолучитьИмяВременногоФайла("xlsx");
	Двоичные.Записать(ИмяФайла);

	ТабДок = Новый ТабличныйДокумент;
	ТабДок.Прочитать(ИмяФайла);
	УдалитьФайлы(ИмяФайла);
	// дальше читаем ТабДок.Область(...) построчно
КонецПроцедуры

Плюсы подхода:

  • 📦 не нужен Excel ни на клиенте, ни на сервере: табличный документ в актуальных версиях 8.3 читает xlsx сам;
  • 🖥️ работает на сервере под Linux;
  • 🔒 файл не путешествует в параметрах формы — в хранилище лежит один экземпляр, привязанный к форме через УникальныйИдентификатор.

Конструктор Новый ДвоичныеДанные(Путь) требует доступа к файловой системе. В веб-клиенте он недоступен — там файл помещают в хранилище асинхронно, через НачатьПомещениеФайла или его современные аналоги.

Если файл создаёт сам COM-объект (Word собирает договор, сканер сохраняет изображение), сначала вызовите его метод сохранения во временный каталог клиента. Каталог получите через КаталогВременныхФайлов().

Сериализация в JSON или XML

Массив и Структура из примитивов передаются на сервер и без JSON. Строковая сериализация нужна в трёх случаях:

  1. 🌳 Данные иерархические и глубокие — так их проще проверить и записать в журнал.
  2. ⏳ Сервер запускает фоновое задание и передаёт ему пакет параметром.
  3. 📄 COM-объект уже отдаёт текст: MSXML возвращает документ через свойство xml, многие веб-компоненты — JSON-строку.

JSON на клиенте

&НаКлиенте
Функция ВСтрокуJSON(Значение)
	Запись = Новый ЗаписьJSON;
	Запись.УстановитьСтроку();
	ЗаписатьJSON(Запись, Значение);
	Возврат Запись.Закрыть();
КонецФункции

На сервере строку разбирают обратно:

&НаСервере
Функция ИзСтрокиJSON(Текст)
	Чтение = Новый ЧтениеJSON;
	Чтение.УстановитьСтроку(Текст);
	Результат = ПрочитатьJSON(Чтение);
	Чтение.Закрыть();
	Возврат Результат;
КонецФункции

Даты в JSON превращаются в строки. Чтобы получить обратно тип Дата, перечислите имена таких свойств в параметре ПрочитатьJSON, который отвечает за свойства со значениями-датами, или приведите их на сервере вручную.

XML из MSXML

Если на клиенте у вас MSXML2.DOMDocument, на сервер не нужно передавать объект. Возьмите текст через свойство xml и передайте строку. На сервере разберите её штатными средствами: ЧтениеXML для потокового чтения или ПостроительDOM для работы с деревом.

COMSafeArray: как вытащить массив из COM на клиенте

Многие COM-методы возвращают не значение, а SafeArray. В 1С он приходит объектом типа COMSafeArray. Самые частые случаи:

  • 📊 Excel: Range("A1:C500").Value и UsedRange.Value;
  • 🗄️ ADO: Recordset.GetRows() — двумерный массив «поля × записи»;
  • 🧩 внешние библиотеки, которые возвращают списки.

COMSafeArray не передаётся на сервер, но у него есть метод Выгрузить(). Он возвращает обычный Массив 1С, для двумерного случая — массив массивов. Это и есть ключевой приём: одна строка кода превращает COM-структуру в сериализуемую.

Значения = Лист.Range("A1:C500").Value; // COMSafeArray
Данные = Значения.Выгрузить();          // Массив массивов, можно передавать

Три детали, на которых спотыкаются:

  1. Порядок индексов. Какой индекс внешний — колонка или строка — проверьте на диапазоне 2×3 с разными значениями в ячейках. Это минута работы, зато код не перепутает цену с количеством.
  2. Одна ячейка. Если диапазон из одной ячейки, Value вернёт само значение, а не массив. Перед Выгрузить() проверяйте тип: ТипЗнч(Значения) = Тип("COMSafeArray").
  3. Пустые ячейки приходят как Неопределено. Приводите их к нулю или пустой строке до записи.

Обратное направление тоже бывает нужно — передать в COM-метод массив из 1С. Для этого создают Новый COMSafeArray(Массив, "VT_VARIANT") уже на клиенте, из данных, которые вернул сервер.

Как ускорить передачу больших объёмов данных

Медленная загрузка почти всегда упирается не в сеть, а в количество вызовов. Каждое обращение к свойству COM-объекта — межпроцессный вызов. Каждая процедура &НаСервере — сетевой запрос с передачей данных формы.

Что делать:

  • 🚀 Читайте диапазон целиком. Range.Value + Выгрузить() вместо цикла по Cells(i, j).Value. Разница на тысячах строк — от минут до секунд.
  • 📦 Один серверный вызов на пакет. Не вызывайте сервер внутри цикла по строкам. Соберите всё на клиенте и передайте одним вызовом.
  • ✂️ Режьте очень большие файлы на порции. Для таблиц в сотни тысяч строк передавайте блоки, например по 10 000 строк, и показывайте прогресс. Так не упрётесь в память клиента и не подвесите форму.
  • 🧹 Выбрасывайте лишние колонки до передачи. Если нужны 3 колонки из 40 — забирайте Range по этим колонкам.
  • ⚙️ Внеконтекстный вызов. Если серверной процедуре не нужны данные формы, используйте &НаСервереБезКонтекста или общий модуль — тогда на сервер уходят только параметры, а не вся форма.
  • 📁 Файл вместо разбора. Для огромных книг способ с двоичными данными обычно быстрее: клиент не разбирает файл, Excel не нужен.

Закрывайте Excel в любом исходе: Книга.Close(False) и Excel.Quit() в обеих ветках Попытка. Иначе после каждой ошибки в диспетчере задач остаётся процесс EXCEL.EXE, и через час работы у пользователя кончается память.

Ошибки при передаче COM-объекта на сервер и их причины

Диагностика: выберите симптом

Отдельно о «невидимой» причине. Если COM-объект лежит в реквизите обычной формы толстого клиента или в глобальной переменной модуля приложения, любой код, который передаёт «всё сразу» (например, ЭтотОбъект или структуру настроек), может захватить его. Держите COM-объекты в отдельных переменных и обнуляйте их после работы: Excel = Неопределено;.

Частые вопросы

Можно ли передать COM-объект на сервер в файловом варианте базы?
Нет. Даже когда клиент и «сервер» работают в одном процессе, платформа соблюдает правила клиент-серверного взаимодействия. Код, который работает в файловой базе, должен работать и в клиент-серверной.
Работает ли COM в веб-клиенте?
Нет. В браузере COM-объекты недоступны. Для веб-клиента данные получают через файлы, внешние компоненты или серверную обработку.
Как вернуть данные с сервера обратно в COM-объект на клиенте?
Тем же путём в обратную сторону: сервер возвращает массив или структуру, клиент записывает их в COM. Для Excel удобно собрать COMSafeArray и присвоить его Range.Value одним вызовом.
Можно ли сохранить COM-объект в реквизит формы, чтобы не создавать его заново?
Нет, такой тип у реквизита формы не задать. Используйте переменную клиентского модуля формы — она живёт, пока форма открыта.
Подходит ли ЗначениеВСтрокуВнутр для передачи данных из COM?
Нет. Этот метод доступен не во всех клиентах и всё равно не сериализует COM-объекты. Выгрузите данные в массив или JSON.