Как известно, подходы к созданию внешних компонент 1С подразумевают использование двух технологий — Native API и COM. Пример с Native API неплохо раскрыт в статье.
Но проблема в том, что в случае применения технологии Native API возникает довольно нетривиальная задача обмена массивами информации между внешней компонентой и 1С: Предприятием. Как правильно было замечено в комментах к статье, эту задачу приходится решать либо многократным вызовов процедур, либо сериализацией содержимого массива.
Но если применять COM-технологию, то все в значительной степени упрощается. Дело в том, что в 1С есть такой малоизвестный, но в данном случае незаменимый тип данных, как COMSafeArray.
Описание:
Объектная оболочка над многомерным массивом SAFEARRAY из COM. Позволяет создавать и использовать SAFEARRAY для обмена данными между COM-объектами.
Для передачи массива в качестве параметра метода COM-объекта необходимо построить COMSafeArray нужной размерности с нужным типом элемента и указать построенный COMSafeArray в качестве значения входного параметра. Другие объекты 1С: Предприятия можно использовать в качестве значений входных параметров типа Массив только при наличии исчерпывающей информации о типах параметров в библиотеке типа COM-объекта.
Результат метода COM-объекта или значение выходного параметра типа Массив всегда представляется объектом COMSafeArray.
Помимо предоставления самого типа COMSafeArray 1С сопровождает его исчерпывающим набором методов, позволяющим преобразовывать стандартные массивы 1С в значения этого типа и обратно.
Весь фокус состоит в том, что если передать этот объект в качестве параметра в функцию внешней компоненты, то в самой внешней компоненте, построенной по технологии COM, этот массив будет получен в виде указателя на массив типа SAFEARRAY.
Аналогично, если вернуть указатель на такой массив из функции внешней компоненты, в 1С результат будет интерпретирован как объект типа COMSafeArray.
Мало того, в функции внешней компоненты можно изменять сам входной массив, указатель на который получен в качестве параметра, а из функции вернуть только S_OK. В 1С после этого можно продолжить работу с переданным массивом и он будет содержать в себе изменения сформированные внешней компонентой. Т.е. в связке между 1С и COM-компонентой можно использовать обычную передачу параметров по ссылке.
Покажем собственным примером.
В 1С все может быть размещено в простом событии кнопки:
Процедура КнопкаНажатие(Элемент)
// Создание массива 1С.
Массив = Новый Массив;
Массив.Добавить(10.1);
Массив.Добавить(20.2);
Массив.Добавить(30.3);
// Создание объекта COMSafeArray, содержащего значения типа double
// на основании массива 1С.
МассивПараметровКОМ = Новый COMSafeArray(Массив, "VT_R8");
// Вызов функции внешней компоненты.
МассивРезультатовКОМ = Компонента2.ФункцияМассив(МассивПараметровКОМ);
// Создание массивов 1С на основании объектов COMSafeArray.
МассивПараметров = МассивПараметровКОМ.Выгрузить();
МассивРезультатов = МассивРезультатовКОМ.Выгрузить();
КонецПроцедуры
Т.е. создали и заполнили обычный массив, создали на его основе объект типа COMSafeArray и скормили его функции внешней компоненты.
Функция как-то преобразовывает входной COMSafeArray и что-то возвращает тоже в виде COMSafeArray.
Далее оба полученных объекта COMSafeArray мы выгружаем в обычные массивы 1С и просмотрщиком (Alt+F9) смотрим результат.
На стороне внешней компоненты всё выглядит ненамного сложнее. В соответствующем блоке switch-case, расположенном в функции CallAsFunc мы будем не только создавать результирующий массив, но и изменять сам входной массив.
Собственно, все описано в комментах:
case arrayFunc:
{
// **********************************
// *** РАБОТА С ИСХОДНЫМ МАССИВОМ ***
// **********************************
// Получение указателя на нулевой элемент массива SAFEARRAY
// (содержит первый параметр функции 1С, который тоже является массивом).
long inputIdx = 0;
void* inputVoidPtr = NULL;
HRESULT hr = SafeArrayPtrOfIndex(*paParams, &inputIdx, &inputVoidPtr);
// Приведение полученного указателя void* к типу VARIANT*
// (т.к. мы точно знаем, что там именно VARIANT).
VARIANT* inputVarPtr = (VARIANT*)inputVoidPtr;
///////////// ОПРЕДЕЛЕНИЕ ГРАНИЦ МАССИВА.
long iLowerBound;
hr = SafeArrayGetLBound(inputVarPtr->parray, 1, &iLowerBound);
if(FAILED(hr))
return S_FALSE;
long iUpperBound;
hr = SafeArrayGetUBound(inputVarPtr->parray, 1, &iUpperBound);
if(FAILED(hr))
return S_FALSE;
//////////////////////////////////////////////
void* sourcePtr = NULL; // Указатель на элемент, заполняемый функцией SafeArrayPtrOfIndex.
double* sourceValPtr = NULL;
// Перебор входного массива от нижней границы к верхней.
for(long l = iLowerBound; l <= iUpperBound; l++)
{
// Заполнение указателя на элемент.
hr = SafeArrayPtrOfIndex(inputVarPtr->parray, &l, &sourcePtr);
// Приведение указателей void* к требуемому типу.
sourceValPtr = (double*)sourcePtr;
// Изменение (инкремент) значения во входном массиве.
++(*sourceValPtr);
}
///////////// СОЗДАНИЕ НОВОГО МАССИВА (С ТАКОЙ ЖЕ РАЗМЕРНОСТЬЮ).
// Границы нового массива
SAFEARRAYBOUND sBound[1];
sBound[0].cElements = iUBound - iLBound + 1;
sBound[0].lLbound = 0;
// Получение типа, хранящегося в исходном массиве.
VARTYPE varType;
hr = SafeArrayGetVartype(sArr, &varType);
if(FAILED(hr))
return S_FALSE;
// Создание нового массива.
SAFEARRAY* sArrNew = SafeArrayCreate(varType, 1, sBound);
//////////////////////////////////////////////
/////////// ПЕРЕБОР МАССИВА.
// Указатели на элементы, заполняемые функцией SafeArrayPtrOfIndex.
void* sourPtr = NULL;
void* destPtr = NULL;
// Перебор исходного массива от нижней границы к верхней.
for(long l = iLBound; l <= iUBound; l++)
{
// Заполнение указателя на элемент.
hr = SafeArrayPtrOfIndex(sArr, &l, &sourPtr);
hr = SafeArrayPtrOfIndex(sArrNew, &l, &destPtr);
// Приведение указателей void* к требуемому типу
// и присвоение значений целевому массиву (исходный, умноженный на 2).
*((double*)destPtr) = *((double*)sourPtr) * 2;
}
////////////////////////////////////////////
///////////// ПРИСВОЕНИЕ ВОЗВРАЩАЕМОМУ ЗНАЧЕНИЮ ЗНАЧЕНИЯ МАССИВА.
V_VT(pvarRetValue) = VT_ARRAY;
V_ARRAY(pvarRetValue) = sArrNew;
break;
}
Т.е. мы получили исходный массив и как-то изменили (в данном случае — инкрементировали) его.
После этого создали новый массив такой же размерности и заполнили его некими значениями (в данном случае — входные, умноженные на 2).
Таким образом, если до обработки функцией внешней компоненты мы имели массив:
МассивПараметров: { 10.1, 20.2, 30.3 }
То после отрабатывания этой функции, полученные в 1С массивы примут следующий вид:
МассивПараметров: { 11.1, 21.2, 31.3 }
МассивРезультатов: { 22.2, 42.4, 62.6 }
Т.е. и массив-параметр и результирующий массив были обработаны функцией внешней компоненты и получены в 1С
в измененном виде.
Полный проект внешней компоненты (с расширенным набором демонстрационных функций) находится здесь.
Автор: zhuravlev_oe