.Net Core、AppDomain、WCF、RPCマーシャリングTcp / Ip your bike

ご存じのとおり、.Net Coreには現時点ではAppDomainがなく、WCFはSOAPクライアント.Net Core、WCFおよびODATAクライアントのみです。



もちろん、WebSocketを使用してイベントをトリガーするWeb Apiでも問題を解決できます。 しかし、TCP / IPのマーシャリングとオブジェクトの作成、およびReflectionを使用したサーバー側のメソッドの呼び出しに関する代替ソリューションを提案するだけです。



リモートメソッドとプロパティコールは次のようになります。 ここから例は、演算子のオーバーロードの基本です



//      string typeStr = typeof(Console).AssemblyQualifiedName; var _Console = wrap.GetType(typeStr);//       // "Hello from Client"      _Console.WriteLine("Hello from Client"); //      TestDllForCoreClr.MyArr //   TestDll.dll var MyArr = wrap.GetType("TestDllForCoreClr.MyArr", "TestDll"); //      //      var Point1 = MyArr._new(1, 12, -4); // new MyArr(1, 12, -4); var Point2 = MyArr._new(0, -3, 18); // new MyArr(0, -3, 18); //     PointX     Console.WriteLine("  : "+Point1.x+" "+Point1.y+" "+Point1.z); Console.WriteLine("  : "+Point2.x+" "+Point2.y + " "+ Point2.z); var Point3 = Point1 + Point2; Console.WriteLine("\nPoint1 + Point2 = " + Point3.x + " " + Point3.y + " " + Point3.z); Point3 = Point1 - Point2; Console.WriteLine("Point1 - Point2 = "+ Point3.x + " " + Point3.y + " " + Point3.z); Point3 = -Point1; Console.WriteLine("-Point1 = " + Point3.x + " " + Point3.y + " " + Point3.z); Point2++; Console.WriteLine("Point2++ = "+ Point2.x + " " + Point2.y + " " + Point2.z); Point2--; Console.WriteLine("Point2-- = " + Point2.x + " " + Point2.y + " " + Point2.z);
      
      





理解できない唯一のメソッドはwrap.GetTypeで、MyArr._newおよび_Consoleはネイティブではありません。 それ以外はすべて、C#のオブジェクトと1対1で動作します。



実際、Point1、Point2、Point3は、TryXXXメソッドがオーバーライドされたDynamicObjectの子孫であり、その中にはメソッドタイプのパッキング、Stream内のメソッドとパラメーターの名前、Tcp / IPプロトコルを介したサーバーへの転送があり、そこで解凍されてメソッドが呼び出されますタイプ、メソッド名、パラメーターで検索されます。 結果を受け取った後、同じ手順ですが、サーバーからクライアントにのみ。



ソリューション自体は、IDispatchでのCOM outプロセスの相互作用に非常に近いものです。 TSocketConnectionの内部を楽しんだことを覚えています。



ただし、Idispatchとは異なり、メソッドと演算子のオーバーロードを使用し、型推論または汎用引数を使用して汎用メソッドを呼び出します。 同じアセンブリ内のクラスおよびLinqメソッドの拡張メソッドのサポート。



また、非同期メソッドとイベントサブスクリプション、refおよびoutパラメーター、インデックス[]によるアクセス、foreachでのイテレーターのサポートもサポートしています。



Web Apiとは異なり、サーバー固有のコントローラー、ハブ、コードを記述する必要はありません。

これは、RemoutingによるAppDomainに近いですが、Remotingとは異なり、各クラスはMarshalByRefObjectに類似しています。 つまり、サーバー側で任意のオブジェクトを作成し、そのオブジェクトへのリンクを返すことができます(数値の一部の言語は、doubleのみをサポートしています)。



メソッドを呼び出すときは、次のタイプのパラメーターのみが直接シリアル化されます:数値、文字列、日付、Guid、およびバイト[]。 残りのタイプについては、サーバー側で作成する必要があり、それらへのリンクはすでにメソッドパラメーターで転送されています。



そのため、構文はC#に似ているTypeScriptで例を見ることができます。

.Net Coreクラスを使用したCEF、ES6、Angular 2、TypeScript。 CEFを使用したクロスプラットフォームの.Net GUIの作成



CEF、Angular 2 .Net Coreクラスイベントの使用



サーバー側のメソッド呼び出しは、ここで見ることができます。アンマネージコードからの.Netクラスのクロスプラットフォーム使用。 またはLinuxのIDispatchの類似物



この記事では、DinamicObjectの使用、オブジェクトの呼び出しのマーシャリング、およびリモートオブジェクトの静的メソッドの機能に焦点を当てます。



まず最初に、目的のアセンブリを読み込んで型を取得します。 最初の例では、型の完全な名前、型の名前、およびアセンブリの名前で型を取得しました。



 //     //    //public static Assembly GetAssembly(string FileName, bool IsGlabalAssembly = false) // IsGlabalAssembly == true?      typeof(string).GetTypeInfo().Assembly.Location //    Server var assembly = wrap.GetAssembly("TestDll"); //      var @TestClass = assembly.GetType("TestDllForCoreClr.TestClass"); //    ,      . ,      //   //public static Type GetType(string type, string FileName = "", bool IsGlabalAssembly = false) //var @TestClass = wrap.GetType("TestDllForCoreClr.TestClass", "TestDll");
      
      





型への参照を取得したら、_newメソッドを呼び出すか、Newラッパーメソッドを呼び出すことにより、オブジェクトを作成できます。



 var TO = @TestClass._new("Property from Constructor");
      
      





または



 wrap.New(@TestClass,"Property from Constructor");
      
      





ジェネリック型を構築できます:



 var Dictionary2 = wrap.GetType("System.Collections.Generic.Dictionary`2", "System.Collections"); var DictionaryIntString = wrap.GetGenericType(Dictionary2, "System.Int32", "System.String"); var dict = wrap.New(DictionaryIS);
      
      





wrap.Newおよびwrap.GetGenericTypeでは、型またはその文字列表現への参照を渡すことができます。 文字列の場合、主なことはアセンブリがロードされることです。



次のオプションは、オブジェクトをサーバーにコピーすることです。 これは重要です。Tcp/ IP交換レートは1秒あたり約15,000コールであり、永続的な接続と、各TCP / IP接続の交換レートは 2,000のみです。



 var ClientDict = new Dictionary<int, string>() { [1] = "", [2] = "", [3] = "" }; //     Json  . //   . var dict = connector.CoryTo(ClientDict);
      
      





現在、dictはサーバー側の辞書へのリンクであり、パラメーターを渡すことができます。



 //       //public V GenericMethod<K, V>(Dictionary<K, V> param1, K param2, V param3) resGM = TO.GenericMethod(dict, 99, "Hello"); Console.WriteLine("      " + resGM);
      
      





インデックスを使用して値にアクセスして設定できます



 Console.WriteLine("dict[2] " + dict[2]); dict[2] = ""; Console.WriteLine("dict[2] " + dict[2]);
      
      





イテレータを使用できます



 foreach (string value in dict.Values) Console.WriteLine("Dict Values " + value);
      
      





次に、構文の違いに注意を向けます。 まず、Generic引数、refおよびoutパラメーターを使用したGenericメソッドの呼び出し、非同期呼び出しです。



 //     // public V GenericMethodWithRefParam<,V >( param, V param2, ref string param3) //      ref . ,   . //    RefParam,     Value      var OutParam = new ClientRPC.RefParam("TroLoLo"); resGM = TO.GenericMethodWithRefParam(5, "GenericMethodWithRefParam", OutParam); Console.WriteLine($@"      Ref {resGM} {OutParam.Value}"); //       var GenericArgs = new object[] { "System.String", "System.String" }; //          : // var @Int32 = wrap.GetType("System.Int32"); //var GenericArgs = new object[] {@Int32, "System.String" }; //            //      //    // resGM = TO.GenericMethodWithRefParam<String,String>(null, "GenericMethodWithRefParam", ref OutParam) resGM = TO.GenericMethodWithRefParam(GenericArgs, null, "GenericMethodWithRefParam", OutParam); Console.WriteLine($@"      Ref {resGM} {OutParam.Value}"); // Test return null resGM = TO.GenericMethodWithRefParam(GenericArgs, null, null, OutParam); Console.WriteLine($@"      Ref {resGM} {OutParam}");
      
      





[値]フィールドに変更されたパラメーターを書き込むには、RefParamクラスが必要です。



 public class RefParam { public dynamic Value; public RefParam(object Value) { this.Value = Value; } public RefParam() { this.Value = null; } public override string ToString() { return Value?.ToString(); } }
      
      





非同期メソッドを呼び出すには:



 // public async Task<V> GenericMethodAsync<K, V>(K param, string param4 = "Test") var GenericArgs = new object[] { "System.Int32", "System.String" }; object resTask = await TO.async.GenericMethodAsync(GenericArgs , 44);
      
      





非同期メソッドの名前の前にasyncという単語を追加する必要があります



タスクがある場合は、次を呼び出して実行を待つことができます。



 int res =await wrap.async.ReturnParam(task);
      
      





実際のコードとのもう1つの違いは、オーバーロード==を直接使用できないことです。



 if (myObject1 == myObject2) Console.WriteLine("    ==");
      
      





代わりに、明示的に呼び出す必要があります



 if (myObject1.Equals(myObject2)) Console.WriteLine("  Equals");
      
      





または、オーバーロードがある場合、演算子==



 if (MyArr.op_Equality(myObject1,myObject2)) Console.WriteLine("  op_Equality");
      
      





System.Dynamic.IDynamicMetaObjectProviderをサポートするオブジェクトのサポートがあります。 これらはExpandoObject、DinamicObject、JObjectなどです。



次のオブジェクトをテストに使用してみましょう。



 public object GetExpandoObject() { dynamic res = new ExpandoObject(); res.Name = "Test ExpandoObject"; res.Number = 456; res.toString = (Func<string>)(() => res.Name); res.Sum = (Func<int, int, int>)((x, y) => x + y); return res; }
      
      





これで使用できます:



  var EO = TO.GetExpandoObject(); Console.WriteLine(" ExpandoObject  " + EO.Name); Console.WriteLine(" ExpandoObject  " + EO.Number); //   var Delegate = EO.toString; Console.WriteLine("  toString " + Delegate()); //    //  ExpandoObject     Console.WriteLine("  toString " + EO.toString()); var DelegateSum = EO.Sum; Console.WriteLine("  Sum " + DelegateSum(3,4)); //    //  ExpandoObject     Console.WriteLine("  Sum " + EO.Sum(3,4)); //  ExpandoObject }
      
      





例からわかるように、メソッドとプロパティだけでなく、デリゲートもサポートされています。 多くの場合、オブジェクトをインターフェイスにキャストする必要があります。 これには_asキーワードがあります。



  string[] sa = new string[] { "", "", "", "", "" }; //     var ServerSa = Connector.CoryTo(sa); //   IEnumerable   var en = ServerSa._as("IEnumerable"); var Enumerator = en.GetEnumerator(); while(Enumerator.MoveNext()) Console.WriteLine(Enumerator.Current); //     var @IEnumerable = wrap.GetType("System.Collections.IEnumerable"); var @IEnumerator = wrap.GetType("System.Collections.IEnumerator"); //    ,     en = ServerSa._as(@IEnumerable); Enumerator = en.GetEnumerator(); //       IEnumerator Enumerator = Enumerator._as(@IEnumerator); while (Enumerator.MoveNext()) Console.WriteLine(Enumerator.Current);
      
      





それでは、半自動シリアル化に移りましょう。



 var dict = connector.CoryTo(ClientDict);
      
      





内部connector.CoryToでは、Jsonのシリアル化が行われます。



  public dynamic CoryTo(object obj) { //     //      string type = obj.GetType().AssemblyQualifiedName; var str = JsonConvert.SerializeObject(obj); return CoryTo(type, str); }
      
      





シリアル化可能な型のアセンブリをサーバーにロードする必要があります。 以下の説明。



また、クライアントには、シリアル化可能な型のアセンブリがない場合があります。 したがって、シリアル化には、 JObjectを使用できます

匿名タイプ



JsonObject



型は、文字列の形式で、またはシリアル化される型とオブジェクトへの参照で指定できます。



  public dynamic CoryTo(object type, object obj) { var str = JsonConvert.SerializeObject(obj); return CoryTo(type, str); }
      
      





最後に、サーバーに送信します。



  // type     Type AutoWrapClient    //     public dynamic CoryTo(object type, string objToStr) { object result; var res = AutoWrapClient.TryInvokeMember(0, "JsonToObject", new object[] { type, objToStr }, out result, this); if (!res) throw new Exception(LastError); return result; }
      
      





サーバー行での逆シリアル化を行うには、型を持つアセンブリをサーバー側に読み込む必要があることに注意してください。



  static void TestSerializeObject(ClientRPC.TCPClientConnector connector) { //      var obj = new TestDllForCoreClr.TestClass("   "); dynamic test = null; try { //     test = connector.CoryTo(obj); } //    //         CoryTo catch (Exception) { Console.WriteLine(" " + connector.LastError); var assembly = wrap.GetAssembly("TestDll"); test = connector.CoryTo(obj); } Console.WriteLine(test.ObjectProperty); }
      
      





また、Core CLRディレクトリにない、またはNuGetパッケージではないアセンブリは、手動でダウンロードする必要があります。



アセンブリロードコード
  static Assembly LoadAssembly(string fileName) { var Dir = AppContext.BaseDirectory; string path = Path.Combine(Dir, fileName); Assembly assembly = null; if (File.Exists(path)) { try { var asm = System.Runtime.Loader.AssemblyLoadContext.GetAssemblyName(path); assembly = Assembly.Load(asm); } catch (Exception) { assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(path); } } else throw new Exception("   " + path); return assembly; }
      
      







サーバーオブジェクトをクライアントにコピーするには、次の方法を使用する必要があります。



 var objFromServ = connector.CoryFrom<Dictionary<int, string>>(dict); Console.WriteLine("dict[2] " + objFromServ[2]);
      
      





このタイプがクライアント上にない場合は、次を使用してJObjectを使用できます。



  connector.CoryFrom<dynamic>(
      
      





さて、最後に。 サーバーへの接続に移りましょう。



 if (LoadLocalServer) { //   dotnet.exe c Server.dll,  . connector = ClientRPC.TCPClientConnector.LoadAndConnectToLocalServer(GetParentDir(dir, 4) + $@"\Server\Server\bin\Release\netcoreapp1.1\Server.dll"); } else { //          //         //   5  connector = new ClientRPC.TCPClientConnector("127.0.0.1", port, false); //  Tcp/IP          . port = ClientRPC.TCPClientConnector.GetAvailablePort(6892); connector.Open(port, 2); }
      
      





LoadAndConnectToLocalServer内で、Server.dllファイルのアドレスを使用してdotnet.exeプロセスを開始します。



サーバープロセスのブートコード
 public static TCPClientConnector LoadAndConnectToLocalServer(string FileName) { int port = 1025; port = GetAvailablePort(port); ProcessStartInfo startInfo = new ProcessStartInfo("dotnet.exe"); startInfo.Arguments = @""""+ FileName+ $@""" { port}"; Console.WriteLine(startInfo.Arguments); var server = Process.Start(startInfo); Console.WriteLine(server.Id); var connector = new TCPClientConnector("127.0.0.1", port); port++; port = GetAvailablePort(port); connector.Open(port, 2); return connector; }
      
      







これでプロキシを取得できます。



  wrap = ClientRPC.AutoWrapClient.GetProxy(connector);
      
      





さらに、型の取得、静的メソッドの呼び出し、オブジェクトの作成、オブジェクトのメソッドの呼び出しなどを行います。



サーバーでの作業が完了したら、サーバーから切断する必要があります。プロセスを開始した場合は、アンロードします。



  //    AutoWrapClient     GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("Press any key"); Console.ReadKey(); //        ,  50  //   connector.ClearDeletedObject(); //   ,   , Tcp/Ip    connector.Close(); //     , //    if (LoadLocalServer) connector.CloseServer(); Console.WriteLine("Press any key"); Console.ReadKey();
      
      





イベントについては、 CEFの記事「Angular 2 Using .Net Core Class Events 」を参照してください。



イベント.Netオブジェクトを処理するプロセスについて説明します。 クライアントのモジュールコードのみを取得できます。



 var @DescribeEventMethods = wrap.GetType("NetObjectToNative.DescribeEventMethods", "Server"); string CodeModule = @DescribeEventMethods.GetCodeModuleForEvents(@EventTest);
      
      







2つ以上のパラメーターを使用してイベントをサブスクライブするときに注意します。 作成されています

パラメータの名前とタイプに対応するフィールドを持つ匿名クラス。 イベントの場合:



  public event Action<string, int> EventWithTwoParameter;
      
      





ラッパーが作成されます:



 Target.EventWithTwoParameter += (arg1,arg2) => { if (EventWithTwoParameter!=null) { var EventWithTwoParameterObject = new {arg1=arg1,arg2=arg2}; EventWithTwoParameter(EventWithTwoParameterObject); } };
      
      





CodeModuleには次のコードが含まれます。



 //  value:  //   // arg1:System.String // arg2:System.Int32 static public void EventWithTwoParameter(dynamic value) { Console.WriteLine("EventWithTwoParameter " + wrap.toString(value)); //    . Console.WriteLine($"EventWithTwoParameter arg1:{value.arg1} arg2:{value.arg2}"); value(ClientRPC.AutoWrapClient.FlagDeleteObject); }
      
      





動的コンパイルの使用については、 .Net Core、1C、動的コンパイル、スクリプトAPIをご覧ください。



.NET CoreのAnalog System.Security.Permissionsのセキュリティに関しては、特定の権限を持つ特定のユーザーアカウントでプロセスを開始することをお勧めします。



C#には、静的コードチェックとIntelliSense用のTypeScript d.tsの型注釈のアナログであるスピーカー用の擬似インターフェイスがないことが残念です。



ただし、通常のコードを作成して、リモートコードに再作成することはできます。 最小限の体の動きで。



ここでのソースはRPCProjectsです。



サンプルを開始する前に、プロジェクトをコンパイルし、TestDll \ bin \ Release \ netcoreapp1.1 \フォルダからTestDll.dllライブラリをServer \ bin \ Release \ netcoreapp1.1 \およびClient \ bin \ Release \ netcoreapp1.1 \ディレクトリにコピーします。



記事に興味がある場合は、次の記事でサーバー上のメソッドを交換および呼び出すためのメカニズムについて説明します。



PS私は積極的にコードの無作法なものを取り除きますが、それはまだたくさんあります。 プロジェクトが興味深い場合は、ロシアのコードから最終的に削除します。



All Articles