Будучи программистом 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.
Суть подключения заключается в загрузке библиотеки 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: %sn", 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 подвергаются дефрагментации. Плюс нужно держать ссылку на них, для предотваращения их от сборки мусора;
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 secondn", 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 : %dn", 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() : %sn", 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 : %dn", RetVal.lVal);
// Вызовим ошибочный метод
if (!mD->pGetPropVal(Target, L"Несуществующее Свойство", &RetVal))
wprintf_s(L"Ошибка при получении свойства : %sn", 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 : %dn", 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 ВК
Автор: Serginio1