При интеграции 1С с внешними приложениями через механизм COM/OLE (например, продуктами Microsoft Office или графическими редакторами вроде CorelDraw) разработчики часто сталкиваются со специфическими ошибками приведения типов. Одна из самых распространенных и коварных проблем — ошибка «Несоответствие типов» при вызове методов, параметры которых в родной среде (VBA, C++) описаны как перечисления (Enum) или являются необязательными (Optional).
В этой статье мы подробно разберем, почему возникает эта ошибка, как устроены перечисления Enum с точки зрения платформы 1С, и как правильно вызывать строгие COM-методы на примере функции выравнивания объектов в CorelDraw.
Рассмотрим классическую ситуацию. В VBA код выравнивания объектов выглядит просто и лаконично:
CorelApp.ActiveDocument.Pages(1).Layers(2).Shapes(6).AlignAndDistribute(cdrAlignDistributeHDistributeCenter, cdrAlignDistributeVDistributeCenter, cdrAlignShapesToCenterOfPage)
Здесь cdrAlignDistributeHDistributeCenter — это именованная константа типа Enum. Внутри библиотеки типов (Type Library) внешнего приложения под каждым элементом такого перечисления скрывается обычное целое число (обычно типа Integer или Long).
Когда мы пытаемся выполнить аналогичное действие в 1С, мы сталкиваемся с двумя препятствиями:
IDispatch. Она не может напрямую прочитать список констант из библиотеки типов, поэтому конструкции вида CorelApp.cdrAlignDistributeHDistributeCenter вызывают ошибку обращения к свойству.cdrAlignDistributeHNone на 0, а cdrAlignDistributeHAlignRight на 1). Однако при вызове метода вроде shp.AlignAndDistribute(3, 0, 2) 1С упорно возвращает ошибку «Несоответствие типов».Давайте разберем по шагам, как решить эту задачу и заставить внешнее приложение правильно принимать параметры из 1С.
Прежде всего, нам необходимо узнать, какие именно числа скрываются за красивыми именами констант в VBA. Сделать это можно тремя путями:
cdrAlignDistributeH значения выглядят так:
cdrAlignDistributeHNone = 0cdrAlignDistributeHAlignRight = 1cdrAlignDistributeHAlignLeft = 2cdrAlignDistributeHAlignCenter = 3MsgBox(cdrAlignDistributeHNone) или поставьте точку останова на переменной, чтобы увидеть ее реальное числовое наполнение.Проанализируем ситуацию, в которой простая подстановка чисел (например, shp.AlignAndDistribute(3, 0, 2)) все равно вызывает сбой. Проблема здесь кроется вовсе не в числах.
Существуют два типа COM-серверов по степени строгости обработки параметров:
DISP_E_PARAMNOTFOUND), и Excel подставляет значения по умолчанию.IDispatch не умеет обрабатывать маркеры отсутствия. Метод ожидает строго определенное количество параметров. Если метод AlignAndDistribute в сигнатуре имеет 9 параметров, а мы передаем только 3, COM-сервер выдает ошибку несоответствия типов, сообщая, что переданные данные не соответствуют ожидаемой структуре метода.Таким образом, для успешного вызова метода нам необходимо передать абсолютно все параметры, предусмотренные сигнатурой функции.
Посмотрим на полную сигнатуру метода AlignAndDistribute в CorelDraw:
AlignAndDistribute(
AlignH As cdrAlignDistributeH,
AlignV As cdrAlignDistributeV,
Placement As cdrAlignShapesTo,
TargetArea As cdrAlignDistributeH,
RelativeToPage As Boolean,
Reserved1 As Long,
Reserved2 As Long,
Reserved3 As Long,
TargetRect As Rect
)
Как мы видим, метод принимает 9 параметров. Последний параметр — это объект типа Rect (прямоугольник границ выравнивания). Из-за него стандартная подстановка простых типов терпит неудачу:
Неопределено приводит к ошибке, так как 1С конвертирует его в VT_EMPTY, что не совпадает с ожидаемым интерфейсом IDispatch.Null приводит к передаче VT_NULL, что также отвергается строгим сервером.0 вызывает ошибку, так как число не является объектом.Единственный верный способ передать этот параметр в 1С — физически создать пустой экземпляр нужного COM-объекта и передать его в метод. В нашем случае это класс CorelDraw.Rect.
Результирующий рабочий код вызова метода будет выглядеть следующим образом:
// Создаем необходимый вспомогательный объект-пустышку
RectClass = Новый COMОбъект("CorelDraw.Rect");
// Передаем ВСЕ параметры метода, соблюдая типы данных
// 1-й параметр (3) - HAlignCenter
// 2-й параметр (0) - VAlignNone
// 3-й параметр (2) - AlignToPage
// ...последний параметр - созданный нами объект RectClass
shp.AlignAndDistribute(3, 0, 2, 0, Ложь, 2, 0, 0, RectClass);
Писать в коде вызовы с набором непонятных цифр — плохая практика программирования. Чтобы сделать код документированным и понятным для других разработчиков, мы рекомендуем использовать структуру-обертку, имитирующую системное перечисление.
Рассмотрим пример красивой реализации:
// Объявляем структуру аналогов Enum для CorelDraw
cdrAlignH = Новый Структура;
cdrAlignH.Вставить("None", 0);
cdrAlignH.Вставить("AlignRight", 1);
cdrAlignH.Вставить("AlignLeft", 2);
cdrAlignH.Вставить("AlignCenter", 3);
cdrAlignV = Новый Структура;
cdrAlignV.Вставить("None", 0);
cdrAlignV.Вставить("AlignTop", 1);
cdrAlignV.Вставить("AlignBottom", 2);
cdrAlignV.Вставить("AlignCenter", 3);
// Вызов метода с использованием структуры становится понятным и читаемым
RectClass = Новый COMОбъект("CorelDraw.Rect");
shp.AlignAndDistribute(
cdrAlignH.AlignCenter,
cdrAlignV.None,
2,
0,
Ложь,
2,
0,
0,
RectClass
);
Разберем критически важный нюанс, связанный со скоростью работы программы. Операция создания нового COM-объекта через конструктор Новый COMОбъект(...) является крайне «дорогой» и ресурсоемкой для операционной системы. При ее вызове происходит обращение к реестру Windows, инициализация сторонней библиотеки в памяти и выделение системных дескрипторов.
Если вы обрабатываете большое количество объектов (например, перебираете сотни шейпов на страницах CorelDraw в цикле), категорически запрещено создавать вспомогательный объект внутри итераций цикла.
Посмотрим на пример правильной организации пакетной обработки объектов:
CorelApp = Новый COMОбъект("CorelDraw.Application");
CorelApp.Visible = True;
// ПРАВИЛЬНО: Создаем пустой Rect-объект ОДИН раз перед началом циклов
RectClass = Новый COMОбъект("CorelDraw.Rect");
Для каждого pg Из CorelApp.ActiveDocument.Pages Цикл
Для каждого lyr Из pg.Layers Цикл
Для каждого shp Из lyr.Shapes Цикл
Если shp.Name = "Название наряда" Тогда
shp.Text.Frame.Range.Text = "Новое значение текста";
// Переиспользуем один и тот же объект RectClass на каждой итерации
shp.AlignAndDistribute(3, 0, 2, 0, Ложь, 2, 0, 0, RectClass);
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
Вынос инициализации CorelDraw.Rect за пределы циклов позволяет ускорить выполнение данного алгоритма в десятки раз и предотвратит утечки оперативной памяти на сервере или клиентской машине.