📜 ⬆️ ⬇️

Cross-platform use of .Net classes in 1C through Native VK. Or replacing COM with Linux

This is a continuation of the article “Cross-platform use of .Net classes from unmanaged code. Or analogue IDispatch on Linux .

There we got the opportunity to use .Net classes in an unmanaged application. Now we will use the “Creating Components Using the Native API Technology” .

So, let's begin.

First, let's define the classes to use:
')
//       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); }; 


We now turn to the implementation. I will dwell only on the main

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


These are the main methods. Now you can go to the code calls from 1C.

First, we declare variables and auxiliary functions.

  ,;  () //        .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();  


Now, rubbing your hands, you can create code to use .Net in 1C. And this exciting moment has come!

  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     


Immediately, I note that for some reason 1C wants to set the InvariantCulture property, although nobody asks for it.
If I call the Sb.Apppend method (“New Line”); as a procedure, 1C still calls as a function and on the .Net side is stored in the list from which it is necessary to free it.

Now let's see a more complex example.

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


Wow, and it works!

You can load third-party libraries.

 =("TestDllForCoreClr., TestDllForCoreClr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); =(.(.(),"   ")); (.); .="  1"; (.); (.); .=("   1"); (.); (.()); 


We now turn to the sadder.
In the previous article there was a speed test, the call time was only more than 300,000 calls per second.

We will conduct a similar test for 1C:

  ()  ;  // ()  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 For .Net
00: 00: 01.20 For 1C

That is, the call rate has decreased to 30,000 calls per second.
But this is enough, usually heavier methods are called.

Added support for objects with IDynamicMetaObjectProvider support

For the test, create 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; } 


And the heir to 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; } } 


Now you can call on 1C

 =("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,())); 


This is convenient when working with various parsers.

Added type inference for generic methods
Now you can not display a separate method
 // 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")); 


Made support external event
In the class, create a body of type 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); } } 

In 1C.

  (, , ) //   . ("="+); ("="+); ("="+);   () //   . =("TestDllForCoreClr., TestDllForCoreClr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); =(.(.(),"   ")); =(.1C()); .1=.(); .Test();  

And do not forget to install in the Variables module
  ; 


In the future, I will add .NET (C #) analogue for 1C. Dynamic compilation of a wrapper class for using .Net events in 1C via Add Handler or Processing an External Event

Now it is worth talking about the shortcomings of the 1C implementation of the Technology of External Components.

1. Absolutely not needed methods are FindMethod, FindProp, IsPropReadable, IsPropWritable, GetNParams, HasRetVal, GetParamDefValue
Since the methods
bool CallAsProc
bool CallAsFunc
bool SetPropVal and bool GetPropVal is the return value for success
Error information is returned via AddError.
And the index call is an anachronism from IDiapatch where there was a description of the interfaces
to increase call speed.

2. When returning with the SetPropVal and GetPropVal methods, an exception is not raised.
3. Why does the installation of properties occur, where it is not required in the code?
4. A method is called as a function, where the method is called as a procedure.

5. One of the main ones is that it is impossible to return and transfer a copy of the VK from the VK methods.

I personally do not see any problems. Determine the value for this type and set the link in the pInterfaceVal field.

Reference counting takes place on the 1C side. It is possible to transfer including 1C objects only for the duration of the method call.

In the future, you can develop to use the events of .Net objects in 1C following the example 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

Use asynchronous calls by the example ".Net in 1C. Asynchronous HTTP requests, sending Post several multipart / form-data files, compressing traffic using gzip, deflate, convenient parsing of sites, etc."

In general, .Net integration is in Microsoft Dynamics AX ClrObject .

Using cross-platform Core Clr can be integrated into 1C. This is especially true for Linux as import substitution.
While checked running on Windows 7.10. Linux and IOS yet, but soon I will check on the virtual machine. .Net Core CLR can be downloaded here.
With what I would love to help 1C. There is a huge experience of using .Net classes in 1C.

Sources and tests can be viewed here .

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


All Articles