Кроссплатформенное использование классов .Net в 1С через Native ВК. Или замена COM на Linux

    Это продолжение статьи «Кроссплатформенное использование классов .Net из неуправляемого кода. Или аналог IDispatch на Linux».

    Там мы получили возможность использования классов .Net в неуправляемом приложении. Теперь воспользуемся «Создание компонент с использованием технологии Native API».

    Итак, начнем.

    Для начала определим используемые классы:

    // Определение функции для единообразного вызова методов
    	typedef bool(*CallAsFunc) (void * , tVariant* , tVariant* , const long);
    
    	// Вспомогательный класс для хранения и единообразного вызова
    	// Что бы не городить кучу switch 
    	class MethodsArray
    	{
    	public:
    		// Имя метода на кириллице
    		//1С ники понимают только на нём
    		wstring MethodName;
             // Ссылка на метод
    		CallAsFunc  Method;
    		//Количество параметров
    		long    ParamCount;
    		//Признак возвращаемого значения
    		bool    HasRetValue;
           // Метод инициализации класса
    		void Init(wstring MethodName, CallAsFunc  Method, long    ParamCount, bool    HasRetValue);
    	};
    
    	
    	///////////////////////////////////////////////////////////////////////////////
    	// class CAddInNative
    	class BaseNetObjectToNative : public IComponentBase
    	{
    	public:
    	
    		static BaseNetObjectToNative* pCurrentObject;
    
    		// Ссылка на массив методов
    		MethodsArray* pMethodsArray;
    		// Размер массива параметров
    		int SizeArray;
    		// Имя класса для 1С
    		wstring ClassName;
    		// Имя текущего вызываемого метода
    		wstring MethodName;
    		// Сслка на объект для вызова методв класса .Net
    		ManagedDomainLoader* NetProvider;
    
    		// Строковое представление объекта .Net для передачи в параметрах методов.
    		wstring RefNetObject;
    		// Индекс в массиве объектов на стороне .Net 
    		long  IdNetObject;
    
    		// Текущий найденный метод ВК
    		MethodsArray* CurrentElem;
    		// Нужен для нахождения количества параметрах м методах с переменных их количествои и перегрузках
    		long LastParamsIndex;
    
    


    class LoaderCLR :public  BaseNetObjectToNative
    	{
    	public:
    
    		// Массив методов из двух элементов
    		MethodsArray MethodsArray[2];
    		LoaderCLR();
    		virtual ~LoaderCLR();
    
    		virtual bool ADDIN_API Init(void*  pConnection);
    		virtual bool ADDIN_API setMemManager(void* memManager);
    
    		// Метод для перичной инициализации .Net
    		static bool CreateDamain(void* Self, tVariant* pvarRetValue, tVariant* paParams, const long lSizeArray);
    		// Метод для предотвращения выгрузки DLL
    		static bool LoadDLL(void* Self, tVariant* pvarRetValue, tVariant* paParams, const long lSizeArray);
    		
           
    		
           // Метод для выделения памяти ссылка на который отправляется в .Net
    		static void* GetMem(long ByteCount);
    		// Метод для сообщения об ссылка на который отправляется в .Net
    		static void  AddError(const wchar_t* ErrorInfo);
    	};
    
    	class NetObjectToNative :public BaseNetObjectToNative
    	{
    	public:
    		MethodsArray MethodsArray[3];
    		NetObjectToNative();
    	
    
    		
    		// Установка ссылки для передачи в параметрах и получение индекса объекта в сиске объектов .Net 
    		static bool SetNetObjectRef(void* Self, tVariant* pvarRetValue, tVariant* paParams, const long lSizeArray);
    		// Получение ссылки для передачи в параметрах
    		static bool GetNetObjectRef(void* Self, tVariant* pvarRetValue, tVariant* paParams, const long lSizeArray);
    
    
    
    	
    	};
    


    Теперь перейдем к реализации. Остановлюсь только на основных

    	//---------------------------------------------------------------------------//
    	long BaseNetObjectToNative::FindProp(const WCHAR_T* wsPropName)
    	{
    		// Свойства есть только у .Net классов.
    		if (NetProvider == nullptr) return -1;
    		long plPropNum = 1;
    
    		// Устанавливаем MethodName для вызова GetPropVal или SetPropVal
    		// и не ищем их на стороне .Net
    		MethodName = wsPropName;
    
    		return plPropNum;
    	}
    
    	//---------------------------------------------------------------------------//
    	const WCHAR_T* BaseNetObjectToNative::GetPropName(long lPropNum, long lPropAlias)
    	{
    
    		// Можно вернуть MethodName но лень.
    		return 0;
    	}
    	//---------------------------------------------------------------------------//
    	bool BaseNetObjectToNative::GetPropVal(const long lPropNum, tVariant* pvarPropVal)
    	{
    		// Установим на всякий случай для выделения памяти текущий объект
    		SetMemoryManager();
    		// Свойства есть только у .Net классов. Их и вызываем используя соххраненное имя свойства 
    		//MethodName
    		return NetProvider->pGetPropVal(IdNetObject,MethodName.c_str(), pvarPropVal);
    
    	}
    	//---------------------------------------------------------------------------//
    	bool BaseNetObjectToNative::SetPropVal(const long lPropNum, tVariant* varPropVal)
    	{
    		// Аналогично GetPropVal
    		return NetProvider->pSetPropVal(IdNetObject, MethodName.c_str(), varPropVal);
    	}
    	//---------------------------------------------------------------------------//
    	bool BaseNetObjectToNative::IsPropReadable(const long lPropNum)
    	{
    		// Не будем лезть в .Net. 
    		// Подразумевается, что автор сам следит за тем, что делает
    		// Если свойство нечитаемо будет выдана ошибка. Но к сожалению 1С эту ошибку не обрабатывает.
    			return true;
    		
    	}
    	//---------------------------------------------------------------------------//
    	bool BaseNetObjectToNative::IsPropWritable(const long lPropNum)
    	{
    		// Аналогично IsPropReadable
    			return true;
    		
    	}
    	//---------------------------------------------------------------------------//
    	long BaseNetObjectToNative::GetNMethods()
    	{
    		// Не знаем сколько методов
    		return 0;
    	}
    	//---------------------------------------------------------------------------//
    	long BaseNetObjectToNative::FindMethod(const WCHAR_T* wsMethodName)
    	{
    	// Запомним имя метода
    		MethodName = wsMethodName;
       // Сначала посмотрим есть ли метод в компоненте
    		long res= findMethod(MethodName);
    		if (res==0 && NetProvider == nullptr) return -1;
    
    		// Так как методы .Net используют перегрузку и используя params 
    		//можно указать параметр метода, принимающий переменное количество аргументов.
    		//LastParamsIndex нужен для нахождения количества использумых параметров в вызываемом методе
    		LastParamsIndex = -1;
    		MethodName = wsMethodName;
    		return res;
    	
    	}
    	//---------------------------------------------------------------------------//
    	const WCHAR_T* BaseNetObjectToNative::GetMethodName(const long lMethodNum,
    		const long lMethodAlias)
    	{
    
    		return 0;//MethodName.c_str();
    	}
    	//---------------------------------------------------------------------------//
    	long BaseNetObjectToNative::GetNParams(const long lMethodNum)
    	{
    		// Здесь  возвращаем количество параметров
    		//Для парамс ограничеваем 16 параметрами
    		if (lMethodNum==0)
    			return  NetProvider->pGetNParams(IdNetObject, MethodName.c_str());
    		else
    			return CurrentElem->ParamCount;
    		
    	
    	}
    	//---------------------------------------------------------------------------//
    	bool BaseNetObjectToNative::GetParamDefValue(const long lMethodNum, const long lParamNum,
    		tVariant *pvarParamDefValue)
    	{
    
    		// В этом методе идет запрос значения по умолчанию
    		//Для нас это означает испльзуемое количество параметров в вызываемом методе
    		if (LastParamsIndex == -1) LastParamsIndex = lParamNum;
    
    		pvarParamDefValue->vt = VTYPE_I4;
    		pvarParamDefValue->lVal = 0;
    
    
    
    
    
    
    		return true;
    	}
    	//---------------------------------------------------------------------------//
    	bool BaseNetObjectToNative::HasRetVal(const long lMethodNum)
    	{
    		if (lMethodNum > 0) return CurrentElem->HasRetValue;
    		// Для .Net классов считаем что все возвращают значения. Даже если нет, то вернем null
    		return true;
    	}
    	//---------------------------------------------------------------------------//
    	bool BaseNetObjectToNative::CallAsProc(const long lMethodNum,
    		tVariant* paParams, const long lSizeArray)
    	{
    		
    		// Вызываем метод. Но 1С вызывает его не по тому как он вызван, а зависит от HasRetVal
    		if (lMethodNum==0)
    		{
    			SetMemoryManager();
    			if (LastParamsIndex == -1) LastParamsIndex = lSizeArray;
    			return NetProvider->pCallAsFunc(IdNetObject, MethodName.c_str(), 0, paParams, LastParamsIndex);
    		}
    		
    		return CurrentElem->Method(this, 0, paParams, lSizeArray);
    
    		
    	}
    	//---------------------------------------------------------------------------//
    	bool BaseNetObjectToNative::CallAsFunc(const long lMethodNum,
    		tVariant* pvarRetValue, tVariant* paParams, const long lSizeArray)
    	{
    		// Вызываем метод. Но 1С вызывает его не по тому как он вызван, а зависит от HasRetVal
    		// Даже если он вызван как процедура 1С вызывает его как функцию.
    		pvarRetValue->vt = VTYPE_NULL;
    		pvarRetValue->lVal = 0;
    
    		if (lMethodNum == 0)
    		{
    			SetMemoryManager();
    			if (LastParamsIndex == -1) LastParamsIndex = lSizeArray;
    			return NetProvider->pCallAsFunc(IdNetObject, MethodName.c_str(), pvarRetValue, paParams, LastParamsIndex);
    		}
    
    		return CurrentElem->Method(this, pvarRetValue, paParams, lSizeArray);
    	}
    	//----------------------------
    
    
    


    Это основные методы. Теперь можно перейти к вызовам кода из 1С.

    Сначала объявим переменные и вспомогательные функции.

    Перем   Врап,СсылкаНаДомен;
    
    Функция СоздатьОбъектПоСсылке(Ссылка)
    // Создаем объект по ссылке полученной из методов .Net классов
    //Физически это строка ёЁ<Ьъ>№_%)Э?&2 содержащее 12 символов для отделения их от других строк
    //и индекс в спике исполуемых объектов на стороне .Net
        рез = Новый("AddIn.NetObjectToNative.NetObjectToNative");
        рез.УстановитьСсылку(Ссылка);    
        возврат  рез
    КонецФункции // СоздатьОбъектПоСсылке()
    
    Функция Ъ(Ссылка)
    	
    	// Зосдадим объект ВК
        рез = Новый("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(ИмяФайла);
    КонецПроцедуры
    
    


    А теперь потирая руки можно создать код для использования .Net в 1С. И вот этот волнительный момент настал!

    Процедура ТестStringBuilderНажатие(Элемент)
    	СБ=ъ(Врап.Новый("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 осуществляется внутри ВК
    КонецПроцедуры
    
    


    Сразу отмечу, то что 1С зачем то хочет установить свойство InvariantCulture хотя её об этом никто не просит.
    Если я вызову метод Сб.Append(«Новая Строка»); как процедуру, то 1С все равно вызывает как функцию и на стороне .Net сохраняется в списке, из которого нужно освобождать.

    Теперь посмотрим более сложный пример.

    // Создадим 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;
     Сообщить(СтрДлина(стр));
    
    


    Ух ты, и это работает!

    Можно загружать сторонние библиотеки.

    Тестовый=ъТип("TestDllForCoreClr.Тестовый, TestDllForCoreClr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
    Тест=ъ(Врап.Новый(Тестовый.ПолучитьСсылку()," Свойство из Конструктора"));
    Сообщить(Тестовый.Поле);
    Тестовый.Поле="Установка из 1С";
    Сообщить(Тестовый.Поле);
    
    Сообщить(Тест.СвойствоОбъекта);
    
    Тест.СвойствоОбъекта=("Установлено Свойство из 1С");
    Сообщить(Тест.СвойствоОбъекта);
    Сообщить(Тест.ПолучитьСтроку());
    


    Теперь перейдем к более грустному.
    В предыдущей статье был тест скорости время вызова кторого составляло оболее 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:06.45 Для .Net
    00:00:01.20 Для 1С

    То есть скорость вызова уменьшилась до 30 000 вызовов в секунду.
    Но и этого достаточно, так обычно вызываются более тяжелые методы.

    Добавил поддержку объектов с поддержкой IDynamicMetaObjectProvider

    Для теста создад ExpandoObject

      public object ПолучитьExpandoObject()
            {
    
                dynamic res = new ExpandoObject();
                res.Имя = "Тест ExpandoObject";
                res.Число = 456;
                res.ВСтроку = (Func<string>)(() => res.Имя);
                res.Сумма = (Func<int, int, int>)((x, y) => x + y);
    
                return res;
            }
    
    


    И наследника DynamicObject

    class TestDynamicObject : DynamicObject
        {
    
            public override bool TrySetMember(SetMemberBinder binder, object value)
            {
    
                return true;
            }
            // получение свойства
            public override bool TryGetMember(GetMemberBinder binder, out object result)
            {
                result = binder.Name;
                return true;
            }
            // вызов метода
            public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
            {
                var res = new StringBuilder("{0}(");
                var param = new object[args.Length + 1];
                param[0] = binder.Name;
                if (args.Length > 0)
                {
                    Array.Copy(args, 0, param, 1, args.Length);
                    for (int i = 0; i < args.Length; i++)
                    {
                        res.AppendFormat("{{{0}}},", i + 1);
    
                    }
    
                    res.Remove(res.Length - 1, 1);
    
                }
                res.Append(")");
    
                result = String.Format(res.ToString(), param);
                return true;
    
    
            }
        }
    
    


    Теперь можно вызвать на 1С

    Тестовый=ъТип("TestDllForCoreClr.Тестовый, TestDllForCoreClr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
    	Тест=ъ(Врап.Новый(Тестовый.ПолучитьСсылку()," Свойство из Конструктора"));
    	
    	// Это аналог структуры, но с поддержкой методов
    	РасширяемыйОбъект=ъ(Тест.ПолучитьExpandoObject());
    	
    	Сообщить("ВСтроку="+РасширяемыйОбъект.ВСтроку());
    	Сообщить("Сумма=" + РасширяемыйОбъект.Сумма(1, 2));
    	
    	Сообщить(РасширяемыйОбъект.Имя);
    	Сообщить(РасширяемыйОбъект.Число);
    	
    	РасширяемыйОбъект.Имя="Новое Имя";
    	РасширяемыйОбъект.Число=768;
    	// Добавим новое свойство
    	РасширяемыйОбъект.НовоеСвойство="Новое Свойство";
    	
    	Сообщить(РасширяемыйОбъект.Имя);
    	Сообщить(РасширяемыйОбъект.Число);
    	Сообщить(РасширяемыйОбъект.НовоеСвойство);
    	
    	НовыйРеквизит=ъ(Врап.Новый("System.Dynamic.ExpandoObject, System.Dynamic.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"));
    	
    	НовыйРеквизит.Имя="Новый Реквизит";
    	НовыйРеквизит.Значение=123;
    	
    	
    	РасширяемыйОбъект.НовыйРквизит=НовыйРеквизит.ПолучитьСсылку();
    	Сообщить(ъ(РасширяемыйОбъект.НовыйРквизит).Имя);
    	
    	
    	TestDynamicObject=ъТип("TestDllForCoreClr.TestDynamicObject, TestDllForCoreClr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
    	
    	ДинамикОбъект=ъНовый(TestDynamicObject.ПолучитьСсылку());
    	ДинамикОбъект.УстановимЛюбоеСвойство="Чего то там";
    	Сообщить( ДинамикОбъект.ПолучитТоЧтоПередали);
    	Сообщить(ДинамикОбъект.ПолучитТоЧтоПередалиСПараметрами(1,3.45,ТекущаяДата()));
    
    
    


    Это удобно при работе с различными парсерами

    Добавил вывод типов для дженерик методов
    Теперь можно не выводить отдельно метод
    
    
    // public T ДженерикМетод<V, T>(V param1, T param2, V param3) 
    Сообщить(Тест.ДженерикМетод(1,"Привет",3));
    //
    
    
    //public V ДженерикМетод2<K, V>(Dictionary<K, V> param1, K param2, V param3)
    
    Словарь= ъНовый("System.Collections.Generic.Dictionary`2[System.Int32,System.String]");
    Сообщить(Тест.ДженерикМетод2(Словарь.ПолучитьСсылку(),3,"Привет2"));
    
    // public K ДженерикМетод3<K>(IList<K> param1, int param2, K param3)
    
    List=ъНовый("System.Collections.Generic.List`1[System.String]");
    Сообщить(Тест.ДженерикМетод3(List.ПолучитьСсылку(),3,"Привет3"));
    
    


    Сделал поддержку внешнего события
    В классе создадим пле типа Action<string, string, string>

     public class Тестовый 
        { 
          public Action<string, string, string>   ВнешнееСобытие1С;
    
    // И сделаем эмуляцию события.
    
    
    
    public async void TestВнешнегоСобытия() 
            { 
                for(int i=0;i<100; i++)
                {
                    var значение = i.ToString();
                    Task.Run(async() =>
                    {
                       await Task.Delay(1000).ConfigureAwait(false);
                       ВнешнееСобытие1С?.DynamicInvoke("Тестовый", "ТестовоеСообщение", значение);
                    });
    
                    await Task.Delay(50).ConfigureAwait(false);
                }
    
    
            } 
    
    

    В 1С.

    Процедура ВнешнееСобытие(Источник, Событие, Данные)
        // Вставить содержимое обработчика.
    
        
        Сообщить("Источник="+Источник);
        Сообщить("Событие="+Событие);
        Сообщить("Данные="+Данные);
    КонецПроцедуры
    
    Процедура ТестВнешнегоСобытияНажатие(Элемент)
        // Вставить содержимое обработчика.
    
        Тестовый=ъТип("TestDllForCoreClr.Тестовый, TestDllForCoreClr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
    
    Тест=ъ(Врап.Новый(Тестовый.ПолучитьСсылку()," Свойство из Конструктора")); 
    
    Делегат=Ъ(Врап.ПолучитьДелегатВнешнегоСобытия1C());
    Тест.ВнешнееСобытие1С=Делегат.ПолучитьСсылку();
    Тест.TestВнешнегоСобытия();
    КонецПроцедуры
    
    
    

    И не забыть в Переменных модуля установить
    Перем   Тест;
    


    В будущем добавлю аналог .NET(C#) для 1С. Динамическая компиляция класса обертки для использования .Net событий в 1С через ДобавитьОбработчик или ОбработкаВнешнегоСобытия

    Теперь стоит поговорить о недостатках 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С.

    Исходники и тесты можно посмотреть здесь.
    Share post

    Similar posts

    Comments 12

      +2
      Ну хаброкат же.
        +1
        Кажется, вы забыли убрать под кат
          0
          А как это сделать. Я тут новичек
          0
          Я <cut /> поставил
            0
            Спойлер в студию
              0
              Добавил вывод типов для дженерик методов
              Теперь можно не выводить отдельно метод
              // public T ДженерикМетод<V, T>(V param1, T param2, V param3) 
              Сообщить(Тест.ДженерикМетод(1,"Привет",3));
              //
              
              
              //public V ДженерикМетод2<K, V>(Dictionary<K, V> param1, K param2, V param3)
              
              Словарь= ъНовый("System.Collections.Generic.Dictionary`2[System.Int32,System.String]");
              Сообщить(Тест.ДженерикМетод2(Словарь.ПолучитьСсылку(),3,"Привет2"));
              
              // public K ДженерикМетод3<K>(IList<K> param1, int param2, K param3)
              
              List=ъНовый("System.Collections.Generic.List`1[System.String]");
              Сообщить(Тест.ДженерикМетод3(List.ПолучитьСсылку(),3,"Привет3"));
              
              

                0
                Добавил поддержку объектов с поддержкой IDynamicMetaObjectProvider

                Для теста создад ExpandoObject

                  public object ПолучитьExpandoObject()
                        {
                
                            dynamic res = new ExpandoObject();
                            res.Имя = "Тест ExpandoObject";
                            res.Число = 456;
                            res.ВСтроку = (Func<string>)(() => res.Имя);
                            res.Сумма = (Func<int, int, int>)((x, y) => x + y);
                
                            return res;
                        }
                
                


                И наследника DynamicObject

                class TestDynamicObject : DynamicObject
                    {
                
                        public override bool TrySetMember(SetMemberBinder binder, object value)
                        {
                
                            return true;
                        }
                        // получение свойства
                        public override bool TryGetMember(GetMemberBinder binder, out object result)
                        {
                            result = binder.Name;
                            return true;
                        }
                        // вызов метода
                        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
                        {
                            var res = new StringBuilder("{0}(");
                            var param = new object[args.Length + 1];
                            param[0] = binder.Name;
                            if (args.Length > 0)
                            {
                                Array.Copy(args, 0, param, 1, args.Length);
                                for (int i = 0; i < args.Length; i++)
                                {
                                    res.AppendFormat("{{{0}}},", i + 1);
                
                                }
                
                                res.Remove(res.Length - 1, 1);
                
                            }
                            res.Append(")");
                
                            result = String.Format(res.ToString(), param);
                            return true;
                
                
                        }
                    }
                
                


                Теперь можно вызвать на 1С

                Тестовый=ъТип("TestDllForCoreClr.Тестовый, TestDllForCoreClr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
                	Тест=ъ(Врап.Новый(Тестовый.ПолучитьСсылку()," Свойство из Конструктора"));
                	
                	// Это аналог структуры, но с поддержкой методов
                	РасширяемыйОбъект=ъ(Тест.ПолучитьExpandoObject());
                	
                	Сообщить("ВСтроку="+РасширяемыйОбъект.ВСтроку());
                	Сообщить("Сумма=" + РасширяемыйОбъект.Сумма(1, 2));
                	
                	Сообщить(РасширяемыйОбъект.Имя);
                	Сообщить(РасширяемыйОбъект.Число);
                	
                	РасширяемыйОбъект.Имя="Новое Имя";
                	РасширяемыйОбъект.Число=768;
                	// Добавим новое свойство
                	РасширяемыйОбъект.НовоеСвойство="Новое Свойство";
                	
                	Сообщить(РасширяемыйОбъект.Имя);
                	Сообщить(РасширяемыйОбъект.Число);
                	Сообщить(РасширяемыйОбъект.НовоеСвойство);
                	
                	НовыйРеквизит=ъ(Врап.Новый("System.Dynamic.ExpandoObject, System.Dynamic.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"));
                	
                	НовыйРеквизит.Имя="Новый Реквизит";
                	НовыйРеквизит.Значение=123;
                	
                	
                	РасширяемыйОбъект.НовыйРквизит=НовыйРеквизит.ПолучитьСсылку();
                	Сообщить(ъ(РасширяемыйОбъект.НовыйРквизит).Имя);
                	
                	
                	TestDynamicObject=ъТип("TestDllForCoreClr.TestDynamicObject, TestDllForCoreClr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
                	
                	ДинамикОбъект=ъНовый(TestDynamicObject.ПолучитьСсылку());
                	ДинамикОбъект.УстановимЛюбоеСвойство="Чего то там";
                	Сообщить( ДинамикОбъект.ПолучитТоЧтоПередали);
                	Сообщить(ДинамикОбъект.ПолучитТоЧтоПередалиСПараметрами(1,3.45,ТекущаяДата()));
                
                
                


                  0
                  Сделал поддержку внешнего события
                  В классе создадим пле типа Action<string, string, string>

                   public class Тестовый 
                      { 
                        public Action<string, string, string>   ВнешнееСобытие1С;
                  
                  // И сделаем эмуляцию события.
                  
                  
                  
                  public async void TestВнешнегоСобытия() 
                          { 
                              for(int i=0;i<100; i++)
                              {
                                  var значение = i.ToString();
                                  Task.Run(async() =>
                                  {
                                     await Task.Delay(1000).ConfigureAwait(false);
                                     this?.ВнешнееСобытие1С("Тестовый", "ТестовоеСообщение", значение);
                                  });
                  
                                  await Task.Delay(50).ConfigureAwait(false);
                              }
                  
                  
                          } 
                  
                  

                  В 1С.

                  Процедура ВнешнееСобытие(Источник, Событие, Данные)
                      // Вставить содержимое обработчика.
                  
                      
                      Сообщить("Источник="+Источник);
                      Сообщить("Событие="+Событие);
                      Сообщить("Данные="+Данные);
                  КонецПроцедуры
                  
                  Процедура ТестВнешнегоСобытияНажатие(Элемент)
                      // Вставить содержимое обработчика.
                  
                      Тестовый=ъТип("TestDllForCoreClr.Тестовый, TestDllForCoreClr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
                  
                  Тест=ъ(Врап.Новый(Тестовый.ПолучитьСсылку()," Свойство из Конструктора")); 
                  
                  Делегат=Ъ(Врап.ПолучитьДелегатВнешнегоСобытия1C());
                  Тест.ВнешнееСобытие1С=Делегат.ПолучитьСсылку();
                  Тест.TestВнешнегоСобытия();
                  КонецПроцедуры
                  
                  
                  

                  И не забыть в Переменных модуля установить
                  Перем   Тест;
                  

                      0
                      В VS 2015 апдейт 3 появилась возможность создавать библиотеки и приложения под .Net Core
                      Вкладка
                      Шаблоны->Windows->.Net Core

                      Такой проект отличается от портативного.
                      В тестах добавил пример создания JObject и сериализации его в Json
                        0
                        Про установку SDK и прочее можно посмотреть здесь
                        http://metanit.com/sharp/aspnet5/1.2.php
                          0
                          Добавил поддержку параметров по умолчанию
                          // Тест вызова метода с параметрами по умолчанию

                          //public static int OptionalParam(int x, int y, int z = 5, int s = 4,string str="Привет")
                          	//       {
                          	//           return x + y + z + s;
                          	//       }
                          
                          


                          Сообщить("OptionalParam(int x, int y)="+Тестовый.OptionalParam(1,2));
                          	Сообщить("OptionalParam(int x, int y, int z = 5)="+Тестовый.OptionalParam(1,2,3));
                          	Сообщить("OptionalParam(int x, int y, int z = 5, int s,string str)="+Тестовый.OptionalParam(1,2,3,4,"ХаХа"));
                          

                          Only users with full accounts can post comments. Log in, please.