📜 ⬆️ ⬇️

Cross-platform use of .Net classes from unmanaged code. Or analogue IDispatch on Linux

Being a 1C programmer, I often have to use .Net classes through different layers.

Using .Net assemblies through a wrapper that implements IReflect
CLR Hosting API is used to connect .NET assemblies
1C.Net: Enterprise is an example of commercial success of .Net solutions in Russia
How to call a method from C # to 1C?

But they all use COM to one degree or another. With the advent of .Net Core, it became possible to use .Net assemblies on any axis other than Windows.
')
A solution was found on the Internet: Hosting .NET Core Clr in your own process and simpleCoreCLRHost and
initializeCoreCLR createDelegate and
unixinterface

The essence of the connection is to load the coreclr.dll library, get the interfaces you need and start the CLR Runtime Host.

Now we can get references to static methods from the .Net class:

//               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) 

Now you can get links to them from C ++. We declare the types of methods:

 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); 

Now we will get links to them through the function:

 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; } 

And accordingly the challenge:

 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); 

The first part of Marlezonsky ballet safely ended. Now we need to solve several problems:

1. Storage for .Net objects. We cannot pass a reference to a .Net object as .Net objects are defragmented. Plus you need to keep a link to them, to prevent them from garbage collection; (GCHandle can also be applied, but this is an unnecessary load on the GC)
2. Make a shell for calling methods and properties;
3. Make a system for finding and calling class methods. The fact is that in Core .Net such a powerful Type.InvokeMember function;
4. Make an analogue of the Variant structure in COM and get and set values ​​into it.

But then, having solved this problem, we will be able to use extension methods and generic methods, which are determined by the types of parameters. Let's start with the repository. This is a list implementation on an array. An elementary analogue of the memory manager. The main task: to set the object, get and delete.

  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].; } } 

We now turn to creating a wrapper.

 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; } 

Now we will describe the option. Rather, it is described 1C:

 // 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; } } 

The rest of the implementation can be viewed in the source or see here Calling a managed code (.Net Core) from unmanaged

I will pass to use on With ++:

 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); 

In general, this is a primitive analogue of IDispatch without the support of reference counting. But you can make such an analogue on the side of the native and use it in .Net through a wrapper through DynamicObject. The execution speed per million calls is 2.7 seconds on my i3-2120 3.3 GHz.

If on Windows for 1C it was possible to solve through COM, then this shop covered itself on the linkus. But you can use Core .Net!

In perspective, you can use the .Net objects of .NET (C #) for 1C. Dynamic compilation of a wrapper class for using .Net events in 1C via Add Handler or Processing an External Event

Sources can be downloaded here.

In the next article I will write about using .Net Core in 1C using Native VK technology

Added support for dynamic objects supporting IDynamicMetaObjectProvider (DynamicObject, ExpandoObject)
Examples can be found here.
Cross-platform use of .Net classes in 1C through Native VK. Or replacing COM with Linux

Source: https://habr.com/ru/post/304482/


All Articles