Pull to refresh

Обмен массивами данных с внешними компонентами 1С

Reading time 5 min
Views 20K
Как известно, подходы к созданию внешних компонент 1С подразумевают использование двух технологий — Native API и COM. Пример с Native API неплохо раскрыт в статье.

Но проблема в том, что в случае применения технологии Native API возникает довольно нетривиальная задача обмена массивами информации между внешней компонентой и 1С: Предприятием. Как правильно было замечено в комментах к статье, эту задачу приходится решать либо многократным вызовов процедур, либо сериализацией содержимого массива.

Но если применять COM-технологию, то все в значительной степени упрощается. Дело в том, что в 1С есть такой малоизвестный, но в данном случае незаменимый тип данных, как COMSafeArray.



Выдержка из синтакс-помощника:
COMSafeArray (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С
в измененном виде.

Полный проект внешней компоненты (с расширенным набором демонстрационных функций) находится здесь.
Tags:
Hubs:
+5
Comments 5
Comments Comments 5

Articles