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

Будучи программистом 1С, мне часто приходится использовать классы .Net через различные прослойки.

Использование сборок .Net через обертку реализующую IReflect
Для подключения .NET сборок используется «CLR Hosting API»
1C.Net: Предприятие – пример коммерческого успеха .Net-решений в России
Как вызвать метод из C# в 1С?

Но все они используют в той или иной степени COM. С появлением .Net Core стало возможным использование сборок .Net и на любой оси отличной от Windows.

На просторах интернета было найдено решение: Hosting .NET Core Clr in your own process и simpleCoreCLRHost и
initializeCoreCLR createDelegate и
unixinterface

Суть подключения заключается в загрузке библиотеки coreclr.dll, получения нужных интерфейсов и запуск CLR Runtime Host.

Теперь мы можем получить ссылки на статические методы из класса .Net:

// Метод для установки ссылок на нативные методы для выделения памяти и сообщении об ошибки
public static void SetDelegate(IntPtr ДляВыделенияПамяти,IntPtr ДляВызоваОшибки)

// Метод для вызова функции или процедуры (для процедуры ReturnValue==null)
public static bool CallAsFunc(int Target, IntPtr ИмяМетодаPtr, IntPtr ReturnValue, IntPtr МассивПараметров, int РазмерМассива)

// Этот метод нужен для ВК из 1С
public static   int GetNParams(int Target, IntPtr ИмяМетодаPtr)

// Методы для установки и получения свойства
public static  bool SetPropVal(int Target, IntPtr ИмяСвойстваPtr, IntPtr pvarPropVal)

public static bool GetPropVal(int Target, IntPtr ИмяСвойстваPtr, IntPtr varPropVal)

// Удаление объекта из списка используемых объектов
public static void DeleteObject(int Target)

Теперь можно получить ссылки на них из C++. Объявим типы методов:

typedef bool(STDMETHODCALLTYPE *ManagedCallAsFunc)(const __int32, const wchar_t*, tVariant* pvarRetValue, tVariant* paParams, const __int32  lSizeArray);
	typedef int(STDMETHODCALLTYPE *ManagedGetNParams)(const __int32, const wchar_t*);
	typedef bool(STDMETHODCALLTYPE *ManagedGetPropVal)(const __int32, const wchar_t*, tVariant*);
	typedef bool(STDMETHODCALLTYPE *ManagedSetPropVal)(const __int32, const wchar_t*, tVariant*);
	typedef void(STDMETHODCALLTYPE *ManagedSetDelegate)(void*(*) (long), void(*) (const wchar_t*));
	typedef void(STDMETHODCALLTYPE *ManagedDeleteObject)(const __int32);

Теперь получим ссылки на них через функцию:

bool ManagedDomainLoader::CreateDelegate(DWORD appDomainID,  wstring MethodName, INT_PTR * fnPtr)
	{

		HRESULT	hr = ClrLoader::pClrLoader->pCLRRuntimeHost->CreateDelegate(
			appDomainID,
			L"NetObjectToNative", // Имя Сборки
			L"NetObjectToNative.AutoWrap", // Имя класса
			MethodName.c_str(), //Имя импортируемого метода
			(INT_PTR*)fnPtr); // Ссылка на импортируемый метод

		if (FAILED(hr))
		{
			wprintf_s(L"Failed to create a delegate to the managed entry point: %s\n", MethodName.c_str());
			printf_s("Failed to create a delegate to the managed entry point: (%d).\n", hr);
			ClrLoader::pClrLoader->pCLRRuntimeHost->UnloadAppDomain(appDomainID, true);
			return false;
		}
	
		
		return true;
	}

И соответственно вызов:

if (!CreateDelegate(domainId,L"CallAsFunc", (INT_PTR*)&pCallAsFunc)) return false;
		if (!CreateDelegate(domainId, L"GetNParams", (INT_PTR*)&pGetNParams)) return false;
		if (!CreateDelegate(domainId, L"GetPropVal", (INT_PTR*)&pGetPropVal)) return false;
		if (!CreateDelegate(domainId, L"SetPropVal", (INT_PTR*)&pSetPropVal)) return false;
		if (!CreateDelegate(domainId, L"DeleteObject", (INT_PTR*)&pDeleteObject)) return false;
		if (!CreateDelegate(domainId, L"SetDelegate", (INT_PTR*)&pSetDelegate)) return false;

// Передадим ссылки на нужные методы
		pSetDelegate(ManagedDomainLoader::GetMem, ManagedDomainLoader::AddError);

Первая часть марлезонского балета благополучно закончилась. Теперь нужно решить несколько задач:

1. Хранилище для объектов .Net. Мы не можем передать ссылку на объект .Net так как объекты .Net подвергаются дефрагментации. Плюс нужно держать ссылку на них, для предотваращения их от сборки мусора; (Можно и GCHandle применить, но это излишняя нагрузка на GC)
2. Сделать оболочку для вызова методов и свойств;
3. Сделать систему поиска и вызова методов классов. Дело в том, что в Core .Net такой мощной функции Type.InvokeMember;
4. Сделать аналог структуры Variant в COM и получение и установка значений в него.

Но зато, решив эту задачу, мы сможем использовать методы расширения и дженерик методы, которые определяются по типам параметров. Итак, начнем с хранилища. Это реализация списка на массиве. Элементарный аналог менеджера памяти. Основная задача: установить объект, получить и удалить.

 public struct ЭлементХранилища
    {
        internal AutoWrap Объект;
        internal int Next;


       internal ЭлементХранилища(AutoWrap Объект)
        {

            this.Объект = Объект;
            Next = -1;
        }

        internal ЭлементХранилища(AutoWrap Объект, int next)
        {

            this.Объект = Объект;
            Next = next;
        }
    }

   internal class ХранилищеОбъектов
    {
     
         List<ЭлементХранилища> Элементы= new List<ЭлементХранилища>();
         int FirstDeleted = -1;

        public int Add(AutoWrap Объект)
        {

            var элемент = new ЭлементХранилища(Объект);

// Если нет удаленных записей, то добавляем и возвращаем индекс на последний элемент
            if (FirstDeleted == -1)
            { Элементы.Add(элемент);
                return Элементы.Count-1;
            }
            else
            {
            // Если есть удаленные то берем первый из списка
           // и корректируем ссылку на начало цепочки свободных индексов
               int newPos = FirstDeleted;
                FirstDeleted = Элементы[newPos].Next;
                Элементы[newPos] = элемент;
                return newPos;

            }
 
        }

        public void RemoveKey(int Pos)
        {
            if (Pos > 0 && Pos < Элементы.Count && Элементы[Pos].Объект != null)
            {
               
                var Элемент = new ЭлементХранилища(null, FirstDeleted);
                Элементы[Pos] =Элемент;
            
                FirstDeleted = Pos;
            }
        }

        public AutoWrap GetValue(int Pos)
        {
            if (!(Pos > -1 && Pos < Элементы.Count && Элементы[Pos].Объект != null))
                return null;
            return Элементы[Pos].Объект;

        }

    }


Теперь перейдем к созданию обертки.


 public class AutoWrap
    {

        
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        internal delegate IntPtr ВыделитьПамятьDelegate(int КоличествоБайтов);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        internal delegate void ИнформацияОбОшибкеDelegate(IntPtr Ошибка);

        // Хранилище где будем хранить объекты
        internal static ХранилищеОбъектов СписокОбъектов;
        // Нужен для 1С, так какона понимает тоолько примитивные типы. Byte[] может только принимать
        //Поэтому будем передавать индекс в хранилище через строку и начало её зокодирум 
        internal static string ХэшДляСсылки = "ёЁ<Ьъ>№_%)Э?&";//new Guid().GetHashCode();
            

        // ссылка на объект
        protected internal object O = null;
        // Ссылка на тип. Нужна для скорости и для использования типа интерфейса
        protected internal Type T = null;

        protected internal int ИндекасВСписке;

        // Для типа можно вызвать статические методы
        internal bool ЭтоТип;
        // Для перечислений нужно вызывать Enum.Parse(T, name);
        internal bool IsEnum;

        // ЭтоExpandoObject тоже отдельно обрабатывается.
        //В дальнейшем реализую поддерку DynamicObject или универсально DynamicMetaObject
        internal bool ЭтоExpandoObject;

        // Это анахронизмы от COM
        internal static bool ЭтоСемерка = false;
        internal static bool ВыводитьСообщениеОбОшибке = true;
        internal static Exception ПоследняяОшибка = null;

        // Делегат для выделения памяти на стороне неуправляемого кода
        internal static ВыделитьПамятьDelegate ВыделитьПямять;
        //Делегат для сообщения об ошибке в неуправляемый код
        internal static ИнформацияОбОшибкеDelegate ИнформацияОбОшибке;

        //Вызвается из натива. Устанавливаем нужные делегаты
        public static void SetDelegate(IntPtr ДляВыделенияПамяти,IntPtr ДляВызоваОшибки)
        {
            ВыделитьПямять = Marshal.GetDelegateForFunctionPointer<ВыделитьПамятьDelegate>(ДляВыделенияПамяти);
            ИнформацияОбОшибке = Marshal.GetDelegateForFunctionPointer<ИнформацияОбОшибкеDelegate>(ДляВызоваОшибки);

        }
        static AutoWrap()
        {
            // В начале установим ссылку на вспомогательный класс
            //Для создания объектов, получения типов итд
            //который будет идти в списке под индексом 0
            СписокОбъектов = new ХранилищеОбъектов();
            var первый = new AutoWrap(typeof(NetObjectToNative));

        }


        public AutoWrap(object obj)
        {

            ИндекасВСписке = СписокОбъектов.Add(this);
            O = obj;
            if (O is Type)
            {
                T = O as Type;
                ЭтоТип = true;
            }
            else
            {
                T = O.GetType();
                ЭтоТип = false;
                ЭтоExpandoObject = O is System.Dynamic.ExpandoObject;
                IsEnum = T.GetTypeInfo().IsEnum;


            }



        }

        // Нужен для установки типа интерфейса
        public AutoWrap(object obj, Type type)
        {
            ИндекасВСписке = СписокОбъектов.Add(this);
            O = obj;
            T = type;
            ЭтоТип = false;
            //   ЭтоExpandoObject = O is System.Dynamic.ExpandoObject;

        }


Теперь опишем вариант. Вернее, он описан 1С:

//    struct _tVariant
    //    {
    //        _ANONYMOUS_UNION union
    //        {
    //            int8_t i8Val;
    //            int16_t shortVal;
    //            int32_t lVal;
    //        int intVal;
    //        unsigned int uintVal;
    //        int64_t llVal;
    //        uint8_t ui8Val;
    //        uint16_t ushortVal;
    //        uint32_t ulVal;
    //        uint64_t ullVal;
    //        int32_t errCode;
    //        long hRes;
    //        float fltVal;
    //        double dblVal;
    //        bool bVal;
    //        char chVal;
    //        wchar_t wchVal;
    //        DATE date;
    //        IID IDVal;
    //        struct _tVariant *pvarVal;
    //        struct tm      tmVal;
    //        _ANONYMOUS_STRUCT struct
    //        {
    //            void* pInterfaceVal;
    //        IID InterfaceID;
    //    }
    //    __VARIANT_NAME_2/*iface*/;
    //        _ANONYMOUS_STRUCT struct
    //        {
    //            char* pstrVal;
    //    uint32_t strLen; //count of bytes
    //}
    //__VARIANT_NAME_3/*str*/;
    //        _ANONYMOUS_STRUCT struct
    //        {
    //            WCHAR_T* pwstrVal;
    //uint32_t wstrLen; //count of symbol
    //        } __VARIANT_NAME_4/*wstr*/;
    //    } __VARIANT_NAME_1;
    //    uint32_t cbElements;    //Dimension for an one-dimensional array in pvarVal
    //TYPEVAR vt;
    //};
    public enum EnumVar
    {
        VTYPE_EMPTY = 0,
        VTYPE_NULL,
        VTYPE_I2,                   //int16_t
        VTYPE_I4,                   //int32_t
        VTYPE_R4,                   //float
        VTYPE_R8,                   //double
        VTYPE_DATE,                 //DATE (double)
        VTYPE_TM,                   //struct tm
        VTYPE_PSTR,                 //struct str    string
        VTYPE_INTERFACE,            //struct iface
        VTYPE_ERROR,                //int32_t errCode
        VTYPE_BOOL,                 //bool
        VTYPE_VARIANT,              //struct _tVariant *
        VTYPE_I1,                   //int8_t
        VTYPE_UI1,                  //uint8_t
        VTYPE_UI2,                  //uint16_t
        VTYPE_UI4,                  //uint32_t
        VTYPE_I8,                   //int64_t
        VTYPE_UI8,                  //uint64_t
        VTYPE_INT,                  //int   Depends on architecture
        VTYPE_UINT,                 //unsigned int  Depends on architecture
        VTYPE_HRESULT,              //long hRes
        VTYPE_PWSTR,                //struct wstr
        VTYPE_BLOB,                 //means in struct str binary data contain
        VTYPE_CLSID,                //UUID
        VTYPE_STR_BLOB = 0xfff,
        VTYPE_VECTOR = 0x1000,
        VTYPE_ARRAY = 0x2000,
        VTYPE_BYREF = 0x4000,    //Only with struct _tVariant *
        VTYPE_RESERVED = 0x8000,
        VTYPE_ILLEGAL = 0xffff,
        VTYPE_ILLEGALMASKED = 0xfff,
        VTYPE_TYPEMASK = 0xfff,
        VTYPE_AutoWrap = 0xff // Нужен для внутреннего использования
            // Хотя может использоваться отдельно от 1С

    };

    public class РаботаСВариантами
    {
        internal static Dictionary<Type, EnumVar> СоответствиеТипов;
        
        static РаботаСВариантами()
        {

            СоответствиеТипов = new Dictionary<Type, EnumVar>()
            { 
                { typeof(Int16),EnumVar.VTYPE_I2 },
                {typeof(Int32),EnumVar.VTYPE_I4 },
                {typeof(float),EnumVar.VTYPE_R4 },
                {typeof(double),EnumVar.VTYPE_R8 },
                {typeof(bool),EnumVar.VTYPE_BOOL },
                {typeof(sbyte),EnumVar.VTYPE_I1 },
                {typeof(byte),EnumVar.VTYPE_UI1 },
                {typeof(UInt16),EnumVar.VTYPE_UI2},
                {typeof(UInt32),EnumVar.VTYPE_UI4},
                {typeof(Int64),EnumVar.VTYPE_I8},
                {typeof(UInt64),EnumVar.VTYPE_UI8},
                {typeof(string),EnumVar.VTYPE_PWSTR},
                {typeof(byte[]),EnumVar.VTYPE_BLOB},
                {typeof(DateTime),EnumVar.VTYPE_DATE},
                {typeof(AutoWrap),EnumVar.VTYPE_AutoWrap},
            };


        }

        public static DateTime ConvertTmToDateTime(IntPtr Элемент)
        {

                tm val = Marshal.PtrToStructure<tm>(Элемент);
                return val.ToDateTime();
     


    }
      public static  object ПолучитьОбъекИзIntPtr(IntPtr Элемент)
        {
            IntPtr текПоз = Элемент + 44;
            int размерIntPtr = Marshal.SizeOf<IntPtr>();
            EnumVar тип =(EnumVar) Marshal.ReadInt16(текПоз);

            switch (тип)
            {
                case EnumVar.VTYPE_EMPTY:
                case EnumVar.VTYPE_NULL: return null;
                case EnumVar.VTYPE_I2: return Marshal.ReadInt16(Элемент);
                case EnumVar.VTYPE_I4: return Marshal.ReadInt32(Элемент);
                case EnumVar.VTYPE_R4: return Marshal.PtrToStructure<float>(Элемент);
                case EnumVar.VTYPE_R8: return Marshal.PtrToStructure<double>(Элемент);
                case EnumVar.VTYPE_BOOL:return Marshal.ReadByte(Элемент)!=0;
                case EnumVar.VTYPE_I1: return (sbyte)Marshal.ReadByte(Элемент);
                case EnumVar.VTYPE_UI1: return Marshal.ReadByte(Элемент);
                case EnumVar.VTYPE_UI2: return (UInt16)Marshal.ReadInt16(Элемент);

                case EnumVar.VTYPE_UI4: return (UInt32)Marshal.ReadInt32(Элемент);

                case EnumVar.VTYPE_I8: return Marshal.ReadInt64(Элемент);
                case EnumVar.VTYPE_UI8: return (UInt64)Marshal.ReadInt64(Элемент);
                case EnumVar.VTYPE_PWSTR:

                    var str= Marshal.PtrToStringUni(Marshal.ReadIntPtr(Элемент));

                    return AutoWrap.ПолучитьОбъектПоСсылке(str);

                case EnumVar.VTYPE_BLOB:
                    текПоз = Элемент + размерIntPtr;
                    byte[] res = new byte[Marshal.ReadInt32(текПоз)];
                    Marshal.Copy(Marshal.ReadIntPtr(Элемент), res,0,res.Length);
                    
                    return res;
                case EnumVar.VTYPE_DATE:
                    var date= Marshal.PtrToStructure<double>(Элемент);
                    return DateTimeHelper.FromOADate(date);

                
                case EnumVar.VTYPE_TM: return ConvertTmToDateTime(Элемент);
            }
            return null;
            }


        public static IntPtr ЗаписатьСтрокувIntPtr(string str)
        {

            var res = UnicodeEncoding.Unicode.GetBytes(str);

            IntPtr ВыделеннаяПамять = AutoWrap.ВыделитьПямять(res.Length + 2);
            Marshal.Copy(res, 0, ВыделеннаяПамять, res.Length);
            Marshal.WriteInt16(ВыделеннаяПамять + res.Length, 0);

            return ВыделеннаяПамять;
        }
        static void УстановитьСтрокуВIntPtr(string str, IntPtr Элемент)
        {

           

            Marshal.WriteIntPtr(Элемент, ЗаписатьСтрокувIntPtr(str));

            IntPtr текПоз = Элемент + Marshal.SizeOf<IntPtr>();
            Marshal.WriteInt32(текПоз, str.Length);

        }


       static  void УстановитьМассивБайтВIntPtr(byte[] value, IntPtr Элемент)
        {

    
            IntPtr ВыделеннаяПамять = AutoWrap.ВыделитьПямять(value.Length);
            Marshal.Copy(value, 0, ВыделеннаяПамять, value.Length);

            Marshal.WriteIntPtr(Элемент, ВыделеннаяПамять);

            IntPtr текПоз = Элемент + Marshal.SizeOf<IntPtr>();
            Marshal.WriteInt32(текПоз, value.Length);

        }
        public static bool УстановитьОбъектВIntPtr(object Объект, IntPtr Элемент)
        {

            IntPtr текПоз = Элемент + 44;
            int размерIntPtr = Marshal.SizeOf<IntPtr>();

            if (Объект == null)
            {

                Marshal.WriteInt16(текПоз, (Int16)EnumVar.VTYPE_NULL);
                Marshal.WriteInt32(Элемент, 0);
                return true;

            }


            EnumVar тип;

            var res = СоответствиеТипов.TryGetValue(Объект.GetType(), out тип);

            if (!res) return false;


            Marshal.WriteInt16(текПоз, (Int16)тип);
            switch (тип)
            {
                case EnumVar.VTYPE_I2:  Marshal.WriteInt16(Элемент,(Int16) Объект); break;
                case EnumVar.VTYPE_I4:  Marshal.WriteInt32(Элемент, (Int32)Объект); break;
                case EnumVar.VTYPE_R4:
                    double val = (double)(float)Объект;
                    Marshal.StructureToPtr<double>(val, Элемент,false);
                    Marshal.WriteInt16(текПоз, (Int16)EnumVar.VTYPE_R8);
                    break;
                case EnumVar.VTYPE_R8:  Marshal.StructureToPtr<double>((double)Объект, Элемент, false); break;
                case EnumVar.VTYPE_BOOL:  Marshal.WriteByte(Элемент, Convert.ToByte(Объект)); break;
                case EnumVar.VTYPE_I1:  Marshal.WriteByte(Элемент, Convert.ToByte(Объект)); break;
                case EnumVar.VTYPE_UI1:  Marshal.WriteByte(Элемент, (byte)Объект); break;
                case EnumVar.VTYPE_UI2:  Marshal.WriteInt16(Элемент, Convert.ToInt16(Объект)); break;

                case EnumVar.VTYPE_UI4:  Marshal.WriteInt32(Элемент, Convert.ToInt32(Объект)); break;

                case EnumVar.VTYPE_I8:  Marshal.WriteInt64(Элемент, (Int64)Объект); break;
                case EnumVar.VTYPE_UI8: Marshal.WriteInt64(Элемент, Convert.ToInt64(Объект)); break;
                case EnumVar.VTYPE_PWSTR: УстановитьСтрокуВIntPtr((string)Объект, Элемент); break;

                case EnumVar.VTYPE_BLOB:  УстановитьМассивБайтВIntPtr((byte[])Объект, Элемент); break;
                case EnumVar.VTYPE_DATE:  Marshal.StructureToPtr<double>(((DateTime)Объект).ToOADate(), Элемент, false);  break;
                case EnumVar.VTYPE_AutoWrap:
                    УстановитьСтрокуВIntPtr(((AutoWrap)Объект).ПолучитьСсылку(), Элемент);
                    Marshal.WriteInt16(текПоз, (Int16)EnumVar.VTYPE_PWSTR);
                    break;
                    
            }
            return true;
        }

    }


Остальнуюю реализацию можно посмотреть в исходниках или посмотреть здесь Вызов управляемого кода (.Net Core) из неуправляемого

Перейду к использованию на С++:

void TestCallMethod(NetObjectToNative::ManagedDomainLoader* mD, long Target)
{
	tVariant Params[4];
	tVariant RetVal;
	tVariant* paParams = Params;
	typedef std::chrono::high_resolution_clock Clock;
	auto start = Clock::now();
	

	long r = 0;
	for (int i = 0; i < 1000000; i++)
	{
		
		paParams->lVal = i;
		paParams->vt = VTYPE_I4;
		mD->pCallAsFunc(Target, L"ПолучитьЧисло", &RetVal, paParams, 1);
		
		r += RetVal.lVal;
		r %= 1 << 30;

	}

	auto finish = Clock::now();


	auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start).count() / 1000000000.0;


	wprintf_s(L"Tme=:  %.2f  second\n", elapsed);
	wprintf_s(L"Eval Value=:  %d \n", r);
}

long GetTarget(tVariant* CurParam)
{
	wchar_t* curstr = CurParam->pwstrVal;
	curstr += 13;
	wstring temp = curstr;
	return stol(temp);

}


int main()
{
	setlocale(0, "");

	// Загрузим Core CLR
	// И создадим домен
	//Первый параметр это путь к папке с coreclr.dll
	NetObjectToNative::ManagedDomainLoader* mD = NetObjectToNative::ManagedDomainLoader::InitManagedDomain(L"c:\\Program Files\\DNX\\runtimes\\dnx-coreclr-win-x86.1.0.0-rc1-update1\\bin\\", L"", L"");

	if (!mD)  return 0;

	tVariant Params[4];
	tVariant RetVal;
	tVariant* paParams = Params;

	paParams->vt = VTYPE_PWSTR;
	paParams->pwstrVal = L"System.Text.StringBuilder";

	cout << "Press Key";
	cin.get();

	// 0 это индекс вспомогательного класса для получения типов объектов и прочих удобный методов
	bool res = mD->pCallAsFunc(0, L"Новый", &RetVal, paParams, 1);
	if (!res) return 0;


	// Так как в 1С нет возможности установить пользовательский тип
	// То возвращаем строку первый 12 символов постоянное значени. После них long в строку
	// третьи это индекс в списке экспортируемых объектов
	// Можно его передавать в качестве параметра типа BLOB
	
	// Теперь мы можем передавать ссылку ref в параметрах;
	
	long Target = GetTarget(&RetVal); // Получили индекс в списке

	wprintf_s(L"index : %d\n", Target);

	// Память выделяется на стороне натива. Нам и удалять.
	delete[] RetVal.pstrVal;

	paParams->vt = VTYPE_PWSTR;
	paParams->pwstrVal = L"Новая Строка";

	// 0 так как вызывается как void даже если метод что то и возвращает, что бы не оборачивать результат
	res = mD->pCallAsFunc(Target, L"Append", 0, paParams, 1);

	res = mD->pCallAsFunc(Target, L"ToString", &RetVal, paParams, 0);
	wprintf_s(L"ToString() : %s\n", RetVal.pwstrVal);

	delete[] RetVal.pstrVal;

	paParams->vt = VTYPE_I4;
	paParams->lVal = 40;
	res = mD->pSetPropVal(Target, L"Capacity", paParams);


	res = mD->pGetPropVal(Target, L"Capacity", &RetVal);
	wprintf_s(L"Capacity : %d\n", RetVal.lVal);

	// Вызовим ошибочный метод
	if (!mD->pGetPropVal(Target, L"Несуществующее Свойство", &RetVal)) 
		wprintf_s(L"Ошибка при получении свойства : %s\n", L"Несуществующее Свойство");

	// Удалим объект из списка. Теперь его может собрать GC
	mD->pDeleteObject(Target);

	// Создадим объект через тип

	paParams->vt = VTYPE_PWSTR;
	paParams->pwstrVal = L"System.Text.StringBuilder";
	// Получим ID оббъекта typeof(System.Text.StringBuilder)
	res = mD->pCallAsFunc(0, L"ПолучитьТип", &RetVal, paParams, 1);

	//paParams[0] = RetVal;

	// Скопируем ID оббъекта typeof(System.Text.StringBuilder)
	
	//в RetVal у нас ссылка на typeof(System.Text.StringBuilder)

	//Просто скопируем параметры
	 paParams[0] = RetVal;
     
	 wchar_t* ref = RetVal.pwstrVal;
	// И создадим экземпляр StringBuilder
	res = mD->pCallAsFunc(0, L"Новый", &RetVal, paParams, 1);
	// Удалим строку
	delete[] ref;
	if (!res) return 0;
	
	// Удалим из списка по ссылке
	paParams[0] = RetVal;
	res = mD->pCallAsFunc(0, L"ОчиститьСсылку", &RetVal, paParams, 1);

	//Теперь Создадим эклемпляр класса из сторонней сборки
	paParams->vt = VTYPE_PWSTR;
	paParams->pwstrVal = L"TestDllForCoreClr.Тестовый, TestDllForCoreClr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";

	auto par2 = paParams;
	par2++;
	par2->vt = VTYPE_PWSTR;
	par2->pwstrVal = L"Свойство из Конструктора";
	res = mD->pCallAsFunc(0, L"Новый", &RetVal, paParams, 2);

	long testRef = GetTarget(&RetVal);
	paParams->vt = VTYPE_I4;
	paParams->lVal = 3;

	res = mD->pCallAsFunc(testRef, L"ПолучитьЧисло", &RetVal, paParams, 1);
	wprintf_s(L"input int : %d\n", RetVal.lVal);


	TestCallMethod(mD, testRef);
	TestCallMethod(mD, testRef);
	TestCallMethod(mD, testRef);


В общем, это примитивный аналог IDispatch без поддержки подсчета ссылок. Но можно такой аналог сделать на стороне натива и использовать в .Net через обертку через DynamicObject. Скорость выполнения на миллион вызовов 2,7 секунды на моем i3-2120 3.3 GHz.

Если на Windows для 1С можно было решать через COM, то на линкус эта лавочка прикрылась. Но можно использовать Core .Net!

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

Исходники можно скачать Здесь

В следующей статье напишу об использовании .Net Core в 1С по технологии Native ВК

Добавил поддержку динамических объектов поддерживающих IDynamicMetaObjectProvider (DynamicObject,ExpandoObject)
Примеры можно посмотреть здесь.
Кроссплатформенное использование классов .Net в 1С через Native ВК. Или замена COM на Linux
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 9
  • +1
    Теперь следить за GCHandle. Объекты могут храниться на стороне 1С долгое время. Обычно GCHandle создаются на время вызова функции неуправляемого кода. Да и реализация GCHandle внутри может быть сложнее и большая нагрузка на GC.

    А режет с непривычки.
    • +2
      Боже мой, как же режет глаз кириллица в коде. Я понимаю 1С там все дела… Но русские символы в «C»-like ИМХО — жесть!

      Ближе к делу:

      «Мы не можем передать ссылку на объект .Net так как объекты .Net подвергаются дефрагментации.»
      А как же GCHandle?
      • +1
        Согласен. Нужно написать Мы не можем передать ссылку на объект без последствий нагрузки на GC. Подправлю
      • +1
        Че за фетиш такой у 1Сников на кириллицу?
        • –3
          Это не фетиш, а необходимость. Приходится писать много кода и тратить время на перевод русского названия на англицкий нет времени, а чем использовать транслитерацию проще сразу писать на русском. У меня даже проблема в использовании классов .Net в 1С. Народ плюется на латиницу. Кририллицу им подавай. А что касается совмещения кириллицы с латиницей то быстро привыкаешь к переключениям. Punto к сожалению не помогает, так как не понимает совмещенных слов например СписокКонтрагентов начинает тупить.
          • 0
            А зачем париться по поводу названий объектов, особенно каких-нибудь специфических, вроде Торг-12, МХ18 или счет-фактура, если все равно придется писать наименования еще и на русском языке?
            • 0
              Торг12 это печатная форма. А документы это РасходнаяНакладная или СчетФактураВыданный.
              Кстати о по существу, что можешь сказать. Или всех только кириллица в C# коде только интересует?
              • 0
                >Торг12 это печатная форма. А документы это РасходнаяНакладная или СчетФактураВыданный.

                Я знаю что это и чем они отличаются между собой. Я про то, что проще писать названия метаданных сразу на русском, чем думать о том как

                >Кстати о по существу, что можешь сказать. Или всех только кириллица в C# коде только интересует?

                По существу топика ничего не могу сказать, т.к. никогда не писал компоненты для 1С, но взял статью на заметку, если вдруг понадобится
                • 0
                  Прошу прощения. Сразу не понял. Полностью согласен.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.