Являясь автором Использование сборок .NET в 1С 7.x b 8.x. Создание внешних Компонент, я часто слышал в свой адрес, что это все туфта, ибо не кроссплатформенна. Кроме того, под Windows полно готовых COM библиотек и несложно сделать свои COM библиотеки на разных языках. Для Linux ситуация сложнее. Там нет COM, а технология .Native API не слишком-то и дружелюбна.
Поэтому было решено, используя Native API, сделать ВК, которая будет взаимодействовать с .Net библиотекой для использования классов .Net в 1С. Следует учесть, что .Net Core CLR пока только на начальном пути, и многое недоступно, что есть в большом .Net, но даже того, что есть, достаточно для расширения возможностей 1С.
Одним из недостатком Native API является то, что мы не можем возвратить ВК и передать параметры ВК в метод ВК. Поэтому пришлось возвращать ссылку на объект в виде строки. И создавать методы для обертки ВК над строкой.
Не буду описывать внутренности ВК. Описание и исходники можно посмотреть
Кроссплатформенное использование классов .Net из неуправляемого кода. Или аналог IDispatch на Linux
Кроссплатформенное использование классов .Net в 1С через Native ВК. Или замена COM на Linux
Там же можно скачать и исходники.
Сначала определим вспомогательные методы для создания объектоа типов и обертки.
Перем Врап,СсылкаНаДомен;
Функция Ъ(Ссылка)
// Создаем объект по ссылке полученной из методов .Net классов
//Физически это строка ёЁ<Ьъ>№_%)Э?&2 содержащее 12 символов для отделения их от других строк
//и индекс в спике исполуемых объектов на стороне .Net
рез = Новый("AddIn.NetObjectToNative.NetObjectToNative");
// И установим ссылку
рез.УстановитьСсылку(Ссылка);
возврат рез
КонецФункции // СоздатьОбъектПоСсылке()
// Сокращенное использование метода ВК Новый
// Создает объект по строковому представлению типа или по ссылке на тип
Функция ъНовый(стр)
возврат ъ(Врап.Новый(стр));
КонецФункции
// Сокращенное использование метода ВК ПолучитьТип
// Создает получает тип по строковому представлению типа
Функция ъТип(стр)
возврат ъ(Врап.ПолучитьТип(стр));
КонецФункции
Процедура ПриОткрытии()
// Установим отчет рядом с AddInNetObjectToNative.dll
// NetObjectToNative.dll
// и библиотеками Microsoft.NETCore.App\1.0.0\
// Так как нужны 32 разрядные так как клиент 1С 32 разрядный
// Скачать можно здесь https://github.com/dotnet/cli
// На сервере можно использовать 64 разрядную Core Clr
Файл=Новый Файл(ЭтотОбъект.ИспользуемоеИмяФайла);
КаталогОтчета=Файл.Путь;
ИмяФайла=КаталогОтчета+"\AddInNetObjectToNative.dll";
ПодключитьВнешнююКомпоненту(ИмяФайла, "NetObjectToNative",ТипВнешнейКомпоненты.Native);
Врап = Новый("AddIn.NetObjectToNative.LoaderCLR");
CoreClrDir=КаталогОтчета+"\bin\";
ДиректорияNetObjectToNative=КаталогОтчета;
СсылкаНаДомен=Врап.СоздатьОбертку(CoreClrDir,ДиректорияNetObjectToNative,"");
Врап.ЗагрузитьDLL(ИмяФайла);
КонецПроцедуры
Я создал каталог, в который положил библиотеки
AddInNetObjectToNative.dll это сама ВК
NetObjectToNative.dll это .Net библиотека для получения классов .Net из .Net
TestDllForCoreClr.dll это тестовая сборка для загрузки её в пространство .Net и использования класса и его методов и свойств.
Кроме того, в папке Bin\ лежит сам .Net Core Clr, который можно скачать отсюда
.NET Core Installers and Binaries
Ну вот он, долгожданный миг, когда можно наконец-то использовать классы .Net Core CLR.
Заодно проверим передаваемые типы.
СБ=ъ(Врап.Новый("System.Text.StringBuilder","Первая Строка"));
CultureInfo=ъТип("System.Globalization.CultureInfo");
CultureInfoES=ъ(Врап.Новый(CultureInfo.ПолучитьСсылку(),"es-ES"));
Сообщить(СБ.Capacity);
Сообщить(СБ.ПолучитьСсылку());
InvariantCulture=ъ(CultureInfo.InvariantCulture);
// К сожалению 1С вызывает метод имеющий возвращаемое значение как функцию даже если вызов идет как процедура
//Нужно очистить ссылку в списке объектов
ссылка=Сб.Append("Новая Строка"); Врап.ОчиститьСсылку(ссылка);
ссылка=Сб.AppendLine(); Врап.ОчиститьСсылку(ссылка);
ссылка=Сб.Append("Вторая Строка"); Врап.ОчиститьСсылку(ссылка);
ссылка=Сб.AppendLine(); Врап.ОчиститьСсылку(ссылка);
ссылка=Сб.AppendFormat("AppendFormat {0}, {1}, {2}, {3}, {4},", "Строка", 21, 45.89, ТекущаяДата(),истина ); Врап.ОчиститьСсылку(ссылка);
ссылка=Сб.AppendLine(); Врап.ОчиститьСсылку(ссылка);
// Так как в параметрах можно передавать только простые типы закодирум ссылку на объект в строку
ссылка=Сб.AppendFormat(CultureInfoES.ПолучитьСсылку(),"AppendFormat {0}, {1}, {2}, {3}, {4},", "Строка", 21, 45.89, ТекущаяДата(),истина ); Врап.ОчиститьСсылку(ссылка);
ссылка=Сб.AppendLine(); Врап.ОчиститьСсылку(ссылка);
ссылка=Сб.AppendFormat(InvariantCulture.ПолучитьСсылку(),"AppendFormat {0}, {1}, {2}, {3}, {4},", "Строка", 21, 45.89, ТекущаяДата(),истина );
Сообщить(СБ.ToString());
Сообщить("Ёмкостъ ="+СБ.Capacity);
// Увеличим емкость
СБ.Capacity=СБ.Capacity+40;
Сообщить("Новая Ёмкостъ ="+СБ.Capacity);
// Очистка ссылок СБ и СultureInfo осуществляется внутри ВК
Теперь перейдем к более сложным классам. А именно HTTPClient с возможностью сжатия трафика
// Вставить содержимое обработчика.
// System.Net.Http, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// Создадим HttpClient и вызовем Get запрос используя сжатие трафика
// На данный момент я не нашел как получить загруженные сборки или как загрузить сборку по имени файла
// Поэтому загружаем по полному имени
HttpClient=ъТип("System.Net.Http.HttpClient, System.Net.Http, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
HttpClientHandler = ъТип("System.Net.Http.HttpClientHandler, System.Net.Http, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
DecompressionMethods= ъТип("System.Net.DecompressionMethods, System.Net.Primitives, Version=4.0.11.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
handler = ъНовый(HttpClientHandler.ПолучитьСсылку());
// Можно использовать и так. Только Не соберем ссылки
//handler.AutomaticDecompression=Врап.OR(DecompressionMethods.GZip,DecompressionMethods.Deflate);
ссылкаGZip=DecompressionMethods.GZip;
ссылкаDeflate=DecompressionMethods.Deflate;
// В 1С нет бинарных операция. Для этого на стороне .Net есть функция
handler.AutomaticDecompression=Врап.OR(ссылкаGZip,ссылкаDeflate);
Врап.ОчиститьСсылку(ссылкаGZip); Врап.ОчиститьСсылку(ссылкаDeflate);
Клиент = ъ(Врап.Новый(HttpClient.ПолучитьСсылку(),handler.ПолучитьСсылку()));
uriSources ="https://msdn.microsoft.com/en-us/library/system.net.decompressionmethods(v=vs.110).aspx";
Стр=ъ(Клиент.GetStringAsync(uriSources)).Result;
Сообщить(СтрДлина(стр));
Ух ты, и это работает!
Еще одна особенность Native API в том, что мы можем получить двоичные данные, но вот передать их в параметры метода метода ВК нет.
Дальше идет проверка вызова сторонней библиотеки и работа с двоичными данными.
Тестовый=ъТип("TestDllForCoreClr.Тестовый, TestDllForCoreClr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
Тест=ъ(Врап.Новый(Тестовый.ПолучитьСсылку()," Свойство из Конструктора"));
Сообщить(Тестовый.Поле);
Тестовый.Поле="Установка из 1С";
Сообщить(Тестовый.Поле);
Сообщить(Тест.СвойствоОбъекта);
Тест.СвойствоОбъекта=("Установлено Свойство из 1С");
Сообщить(Тест.СвойствоОбъекта);
Сообщить(Тест.ПолучитьСтроку());
Врап.ДвоичныеДанныеКакОбъект=истина;
ДД=Тест.ПолучитьДвоичныеДанные();
UnicodeEncoding= ъНовый("System.Text.UnicodeEncoding, System.Text.Encoding.Extensions, Version=4.0.10.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
//Если не установить Врап.ДвоичныеДанныеКакОбъект=истина;
//То при вызове UnicodeEncoding.GetString(ДД);
// 1С выдает ошибку
// Неверный аргумент
СтрокаИзДД=UnicodeEncoding.GetString(ДД);
Сообщить(СтрокаИзДД);
Самое главное, что мы можем использовать сторонние библиотеки, которые должны лежать рядом с NetObjectToNative.dll
К сожалению, в отличие от COM в .Native ВК мы не можем использовать энумераторы
Для Каждого Элемент ИЗ Списка Цикл
Поэтому для упрощения использования массивов и списков создана функция ПолучитьЭнумератор
Функция ПолучитьЭнумератор(Объект)
Перечислимый=ъ(Врап.ПолучитьИнтерфейс(Объект.ПолучитьСсылку(),"IEnumerable"));
ПеречислительСсылка=Перечислимый.GetEnumerator();
// На всякий случай приведем к Интерфейсу IEnumerator
Перечислитель=ъ(Врап.ПолучитьИнтерфейс(ПеречислительСсылка,"IEnumerator"));
Врап.ОчиститьСсылку(ПеречислительСсылка);
// Можно и так
//IEnumerable=ъТип("System.Collections.IEnumerable");
//IEnumerator=ъТип("System.Collections.IEnumerator")
// Перечислимый=ъ(Врап.ПолучитьИнтерфейс(Объект.ПолучитьСсылку(),IEnumerable));
//ПеречислительСсылка=Перечислимый.GetEnumerator();
// На всякий случай приведем к Интерфейсу IEnumerator
// Перечислитель=ъ(Врап.ПолучитьИнтерфейс(ПеречислительСсылка,IEnumerator));
//Врап.ОчиститьСсылку(ПеречислительСсылка);
возврат Перечислитель
КонецФункции // ПолучитьЭнумератор()
Процедура ТестЭнумератораНажатие(Элемент)
List=ъТип("System.Collections.Generic.List`1[System.String]");
Список=ъНовый(List.ПолучитьСсылку());
Для сч=1 По 10 Цикл
Список.Add(строка(сч));
КонецЦикла;
// Получим энумератор через соответствующие интерфейсы
// Заодно проверим метод ПолучитьИнтерфейс
СписокЭнум=ПолучитьЭнумератор(Список);
Пока СписокЭнум.MoveNext() Цикл
// Врап.ВСтроку вывоит строковое представление всех типов в том числе числовые, строки, неопределено
Сообщить(СписокЭнум.Current);
КонецЦикла;
// Получение Энумератора из ВК
СписокЭнум2=ъ(Врап.ПолучитьЭнумератор(Список.ПолучитьСсылку()));
Пока СписокЭнум2.MoveNext() Цикл
// Врап.ВСтроку вывоит строковое представление всех типов в том числе числовые, строки, неопределено
Сообщить(СписокЭнум2.Current);
КонецЦикла;
КонецПроцедуры
Теперь перейдем к более грустному.
В этой статье был тест скорости, время вызова которого составляло более 300 000 вызовов в секунду.
Проведем аналогичный тест на 1С:
Функция ПолучитьЧисло(зн)
возврат зн;
КонецФункции // ()
Процедура ТестСкорости2Нажатие(Элемент)
// Вставить содержимое обработчика.
КоличествоИтераций=200000;
Тестовый=ъТип("TestDllForCoreClr.Тестовый, TestDllForCoreClr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
Тест=ъ(Врап.Новый(Тестовый.ПолучитьСсылку()," Свойство из Конструктора"));
stopWatch = ъНовый("System.Diagnostics.Stopwatch,System.Runtime.Extensions, Version=4.0.11.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
стр="";
Тест.ПолучитьЧисло(1);
НачВремя=ТекущаяДата();
stopWatch.Start();
Для сч=1 по КоличествоИтераций Цикл
Тест.ПолучитьЧисло(сч);
КонецЦикла;
stopWatch.Stop();
ВремяВыполнения=ТекущаяДата()-НачВремя;
Сообщить("ПолучитьЧисло ="+ВремяВыполнения);
ВывестиВремя(stopWatch);
НачВремя=ТекущаяДата();
stopWatch.Restart();
Для сч=1 по КоличествоИтераций Цикл
ПолучитьЧисло(сч);
КонецЦикла;
stopWatch.Stop();
ВремяВыполнения=ТекущаяДата()-НачВремя;
Сообщить("ПолучитьЧисло ="+ВремяВыполнения);
ВывестиВремя(stopWatch);
КонецПроцедуры
00:00:09.63 Для .Net
00:00:01.23 Для 1С
00:00:09.93 Для .Net
00:00:01.20 Для 1С
То есть скорость вызова уменьшилась до 20 000 вызовов в секунду.
Но и этого достаточно, так обычно вызываются более тяжелые методы.
Добавил поддержку IDynamicMetaObjectProvider (DynamicObject,ExpandoObject). В исходниках есть примеры. Это важно при использовании различного рода парсеров
Теперь стоит поговорить о недостатках 1С реализации Технологии Внешних Компонент.
1. Абсолютно не нужны методы FindMethod, FindProp, IsPropReadable, IsPropWritable, GetNParams, HasRetVal, GetParamDefValue
Так как у методов
bool CallAsProc
bool CallAsFunc
bool SetPropVal и bool GetPropVal есть возвращаемое значение об успешном выполнении
Информация об ошибке возвращается через AddError.
Да и вызов по индексу это анахронизм от IDiapatch, где было описание диспинтерфейсов для увеличения скорости вызова.
2. При возвращении методами SetPropVal и GetPropVal исключение не вызывается
3. Зачем-то происходит установка свойств там, где в коде этого не требуется.
4. Вызывается метод как функция там, где метод вызывается как процедура.
5. Один из основных - это нельзя вернуть и передать экземпляр ВК из методов ВК.
Я лично не вижу никаких проблем. Определить значение для такого типа и установить ссылку в поле pInterfaceVal.
Подсчет ссылок происходит на стороне 1С. Передавать можно в том числе и объекты 1С только на время вызова метода.
В дальнейшем можно развить до использования событий объектов .Net в 1С по примеру .NET(C#) для 1С. Динамическая компиляция класса обертки для использования .Net событий в 1С через ДобавитьОбработчик или ОбработкаВнешнегоСобытия
Использовать асинхронные вызовы по примеру ".Net в 1С. Асинхронные HTTP запросы, отправка Post нескольких файлов multipart/form-data, сжатие трафика с использованием gzip, deflate, удобный парсинг сайтов и т.д."
Вообще интеграция .Net есть в Microsoft Dynamics AX ClrObject.
Используя кроссплатформенный Core Clr, можно интегрировать в 1С. Особенно это актуально для Linux как импортозамещения.
Пока проверил, работает на Windows 7,10. Linux и IOS пока нет, но в скором проверю на виртуальной машине. .Net Core CLR можно скачать здесь
С чем я бы с удовольствием помог 1С. Есть огромный опыт использования классов .Net в 1С.