COM-объект живёт только на клиенте: как довезти его данные до сервера 1С
Содержание
Передать сам 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 нужно указать явно |
Массив из COMSafeArray | Excel Range.Value, ADO GetRows() | Порядок индексов надо проверить |
Перенос данных через таблицу значений и табличный реквизит формы
Самый удобный путь для табличных данных из Excel или ADO-выборки — заполнить табличный реквизит формы на клиенте. Данные формы платформа передаёт на сервер сама при вызове &НаСервере, а там реквизит превращается в обычную таблицу значений.
Шаги для управляемой формы
- Добавьте реквизит формы
ТаблицаДанныхс типомТаблицаЗначений. - Опишите колонки:
Артикул(Строка),Количество(Число),Цена(Число). Если источник «грязный», задайте колонкам составной тип илиПроизвольный, а приведение сделайте на сервере. - На клиенте прочитайте COM-объект и заполните строки.
- Вызовите серверную процедуру без параметров — таблица уже в данных формы.
&НаКлиенте
Процедура ЗагрузитьИз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. Строковая сериализация нужна в трёх случаях:
- 🌳 Данные иерархические и глубокие — так их проще проверить и записать в журнал.
- ⏳ Сервер запускает фоновое задание и передаёт ему пакет параметром.
- 📄 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
Данные = Значения.Выгрузить(); // Массив массивов, можно передавать
Три детали, на которых спотыкаются:
- Порядок индексов. Какой индекс внешний — колонка или строка — проверьте на диапазоне 2×3 с разными значениями в ячейках. Это минута работы, зато код не перепутает цену с количеством.
- Одна ячейка. Если диапазон из одной ячейки,
Valueвернёт само значение, а не массив. ПередВыгрузить()проверяйте тип:ТипЗнч(Значения) = Тип("COMSafeArray"). - Пустые ячейки приходят как
Неопределено. Приводите их к нулю или пустой строке до записи.
Обратное направление тоже бывает нужно — передать в 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-объект на клиенте?
COMSafeArray и присвоить его Range.Value одним вызовом.