Будучи программистом 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:
Теперь можно получить ссылки на них из C++. Объявим типы методов:
Теперь получим ссылки на них через функцию:
И соответственно вызов:
Первая часть марлезонского балета благополучно закончилась. Теперь нужно решить несколько задач:
1. Хранилище для объектов .Net. Мы не можем передать ссылку на объект .Net так как объекты .Net подвергаются дефрагментации. Плюс нужно держать ссылку на них, для предотваращения их от сборки мусора; (Можно и GCHandle применить, но это излишняя нагрузка на GC)
2. Сделать оболочку для вызова методов и свойств;
3. Сделать систему поиска и вызова методов классов. Дело в том, что в Core .Net такой мощной функции Type.InvokeMember;
4. Сделать аналог структуры Variant в COM и получение и установка значений в него.
Но зато, решив эту задачу, мы сможем использовать методы расширения и дженерик методы, которые определяются по типам параметров. Итак, начнем с хранилища. Это реализация списка на массиве. Элементарный аналог менеджера памяти. Основная задача: установить объект, получить и удалить.
Теперь перейдем к созданию обертки.
Теперь опишем вариант. Вернее, он описан 1С:
Остальнуюю реализацию можно посмотреть в исходниках или посмотреть здесь Вызов управляемого кода (.Net Core) из неуправляемого
Перейду к использованию на С++:
В общем, это примитивный аналог 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
Использование сборок .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
