Skip to content

BIMOpenGroup/InterprocessCommunication

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

15 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Interprocess Communication: Strategies and Best Practices

ВсС ΠΌΡ‹ Π·Π½Π°Π΅ΠΌ ΠΊΠ°ΠΊ слоТно ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Ρ‚ΡŒ ΠΊΡ€ΡƒΠΏΠ½Ρ‹Π΅ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΡ‹, ΠΈ ΡƒΡΠΏΠ΅Π²Π°Ρ‚ΡŒ Π·Π° прогрСссом. Π Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ ΠΏΠ»Π°Π³ΠΈΠ½ΠΎΠ² для Revit это ΠΏΠΎΠ½ΠΈΠΌΠ°ΡŽΡ‚ ΠΊΠ°ΠΊ Π½ΠΈΠΊΡ‚ΠΎ Π»ΡƒΡ‡ΡˆΠ΅. Нам приходится ΠΏΠΈΡΠ°Ρ‚ΡŒ свои ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΡ‹ Π½Π° .NET Framework 4.8. Нам приходится ΠΎΡ‚ΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒΡΡ ΠΎΡ‚ соврСмСнных ΠΈ быстрых Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊ. Π­Ρ‚ΠΎ Π² ΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎΠΌ сказываСтся ΠΈ Π½Π° ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡΡ…, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π²Ρ‹Π½ΡƒΠΆΠ΄Π΅Π½Ρ‹ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒΡΡ ΡƒΡΡ‚Π°Ρ€Π΅Π²ΡˆΠΈΠΌ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ½Ρ‹ΠΌ обСспСчСниСм.

Π’ Ρ‚Π°ΠΊΠΈΡ… сцСнариях Ρ€Π°Π·Π΄Π΅Π»Π΅Π½ΠΈΠ΅ прилоТСния Π½Π° нСсколько процСссов с использованиСм Named Pipes прСдставляСтся прСвосходным Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ΠΌ благодаря своСй ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ ΠΈ надСТности. Π’ этой ΡΡ‚Π°Ρ‚ΡŒΠ΅ ΠΌΡ‹ рассмотрим, ΠΊΠ°ΠΊ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ ΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Named Pipes для взаимодСйствия ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΠΌ Revit, Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‰ΠΈΠΌ Π½Π° .NET 4.8 ΠΈ Π΅Π³ΠΎ ΠΏΠ»Π°Π³ΠΈΠ½Π°, Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‰ΠΈΠΌ Π½Π° .NET 7.

Π‘ΠΎΠ΄Π΅Ρ€ΠΆΠ°Π½ΠΈΠ΅

Π’Π²Π΅Π΄Π΅Π½ΠΈΠ΅ Π² использованиС Named Pipes для общСния ΠΌΠ΅ΠΆΠ΄Ρƒ прилоТСниями Π½Π° Ρ€Π°Π·Π½Ρ‹Ρ… вСрсиях .NET

Π’ ΠΌΠΈΡ€Π΅ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ часто трСбуСтся ΠΎΠ±Π΅ΡΠΏΠ΅Ρ‡ΠΈΡ‚ΡŒ ΠΎΠ±ΠΌΠ΅Π½ Π΄Π°Π½Π½Ρ‹ΠΌΠΈ ΠΌΠ΅ΠΆΠ΄Ρƒ Ρ€Π°Π·Π½Ρ‹ΠΌΠΈ прилоТСниями, особСнно Π² случаях, ΠΊΠΎΠ³Π΄Π° ΠΎΠ½ΠΈ Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‚ Π½Π° Ρ€Π°Π·Π½Ρ‹Ρ… вСрсиях .NET ΠΈΠ»ΠΈ Ρ€Π°Π·Π½Ρ‹Ρ… языках. Π Π°Π·Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ΠΎΠ΄Π½ΠΎΠ³ΠΎ прилоТСния Π½Π° нСсколько процСссов Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ обоснованным. Π§Ρ‚ΠΎ ΠΏΡ€ΠΎΡ‰Π΅, Π²Ρ‹Π·Π²Π°Ρ‚ΡŒ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ, ΠΈΠ»ΠΈ ΠΎΠ±ΠΌΠ΅Π½ΡΡ‚ΡŒΡΡ сообщСниями? ΠžΡ‡Π΅Π²ΠΈΠ΄Π½ΠΎ ΠΏΠ΅Ρ€Π²ΠΎΠ΅.

Π’ΠΎΠ³Π΄Π° ΠΊΠ°ΠΊΠΈΠ΅ прСимущСства Π² Ρ‚ΠΎΠΌ Ρ‡Ρ‚ΠΎΠ±Ρ‹ это Π΄Π΅Π»Π°Ρ‚ΡŒ?

  • РСшСниС ΠΊΠΎΠ½Ρ„Π»ΠΈΠΊΡ‚Π° зависимостСй

    Π‘ ΠΊΠ°ΠΆΠ΄Ρ‹ΠΌ Π³ΠΎΠ΄ΠΎΠΌ Ρ€Π°Π·ΠΌΠ΅Ρ€ ΠΏΠ»Π°Π³ΠΈΠ½ΠΎΠ² для Revit всС большС ΠΈ большС растСт, Π° зависимости растут Π² гСомСтричСской прогрСссии. ΠŸΠ»Π°Π³ΠΈΠ½Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ нСсовмСстимыС вСрсии ΠΎΠ΄Π½ΠΎΠΉ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ, Ρ‡Ρ‚ΠΎ Π²Ρ‹Π·ΠΎΠ²Π΅Ρ‚ ΠΊΡ€Π°Ρˆ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΡ‹. Π˜Π·ΠΎΠ»ΡΡ†ΠΈΡ процСссов Ρ€Π΅ΡˆΠ°Π΅Ρ‚ эту ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡƒ.

  • ΠŸΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ

    НиТС ΠΏΡ€ΠΈΠ²Π΅Π΄Π΅Π½Ρ‹ Π·Π°ΠΌΠ΅Ρ€Ρ‹ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ сортировки ΠΈ матСматичСских вычислСний Π½Π° Ρ€Π°Π·Π½Ρ‹Ρ… вСрсиях .NET

    BenchmarkDotNet v0.13.9, Windows 11 (10.0.22621.1702/22H2/2022Update/SunValley2)
    AMD Ryzen 5 2600X, 1 CPU, 12 logical and 6 physical cores
    .NET 7.0           : .NET 7.0.9 (7.0.923.32018), X64 RyuJIT AVX2
    .NET Framework 4.8 : .NET Framework 4.8.1 (4.8.9139.0), X64 RyuJIT VectorSize=256
    
    Method Runtime Mean Error StdDev Allocated
    ListSort .NET 7.0 1,113,161.8 ns 20,385.15 ns 21,811.88 ns 804753 B
    ListOrderBy .NET 7.0 1,064,851.1 ns 12,401.25 ns 11,600.13 ns 807054 B
    MinValue .NET 7.0 979.4 ns 7.40 ns 6.56 ns -
    MaxValue .NET 7.0 970.6 ns 4.32 ns 3.60 ns -
    ListSort .NET Framework 4.8 2,144,723.5 ns 40,359.72 ns 37,752.51 ns 1101646 B
    ListOrderBy .NET Framework 4.8 2,192,414.7 ns 25,938.78 ns 24,263.15 ns 1105311 B
    MinValue .NET Framework 4.8 58,019.0 ns 460.30 ns 430.57 ns 40 B
    MaxValue .NET Framework 4.8 66,053.4 ns 610.28 ns 541.00 ns 41 B

    Π Π°Π·Π½ΠΈΡ†Π° Π² 68 Ρ€Π°Π· Π² скорости ΠΏΡ€ΠΈ Π½Π°Ρ…ΠΎΠΆΠ΄Π΅Π½ΠΈΠΈ минимального Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅, ΠΈ ΠΏΠΎΠ»Π½ΠΎΠ΅ отсутствиС выдСлСния памяти, впСчатляСт.

Π’ΠΎΠ³Π΄Π° ΠΊΠ°ΠΊ Π½Π°ΠΏΠΈΡΠ°Ρ‚ΡŒ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΡƒ Π½Π° послСднСй вСрсии .NET, которая Π±ΡƒΠ΄Π΅Ρ‚ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡ‚Π²ΠΎΠ²Π°Ρ‚ΡŒ с нСсовмСстимым .NET framework? Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ Π΄Π²Π° прилоТСния, Server ΠΈ Client, Π½Π΅ добавляя зависимостСй ΠΌΠ΅ΠΆΠ΄Ρƒ Π΄Ρ€ΡƒΠ³ Π΄Ρ€ΡƒΠ³ΠΎΠΌ ΠΈ Π½Π°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ взаимодСйствиС ΠΌΠ΅ΠΆΠ΄Ρƒ Π½ΠΈΠΌΠΈ ΠΏΠΎ настроСнному ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ»Ρƒ.

НиТС ΠΏΡ€ΠΈΠ²Π΅Π΄Π΅Π½Ρ‹ Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΈΠ· Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ‹Ρ… Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ΠΎΠ² взаимодСйствия Π΄Π²ΡƒΡ… ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ:

  1. ИспользованиС WCF (Windows Communication Foundation)

  2. ИспользованиС сокСтов (TCP ΠΈΠ»ΠΈ UDP)

  3. ИспользованиС Named Pipes

  4. ИспользованиС сигналов ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΎΠ½Π½ΠΎΠΉ систСмы (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, сигналов Windows):

    ΠŸΡ€ΠΈΠΌΠ΅Ρ€ ΠΊΠΎΠ΄Π° ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ Autodesk, взаимодСйствиС ΠΏΠ»Π°Π³ΠΈΠ½Π° Project Browser с Π±Π΅ΠΊΠ΅Π½Π΄ΠΎΠΌ Revit посрСдством сообщСний

    public class DataTransmitter : IEventObserver
    {
        private void PostMessageToMainWindow(int iCmd) => 
            this.HandleOnMainThread((Action) (() => 
                Win32Api.PostMessage(Application.UIApp.getUIApplication().MainWindowHandle, 273U, new IntPtr(iCmd), IntPtr.Zero)));
    
        public void HandleShortCut(string key, bool ctrlPressed)
        {
            string lower = key.ToLower();
            switch (PrivateImplementationDetails.ComputeStringHash(lower))
            {
            case 388133425:
              if (!(lower == "f2")) break;
              this.PostMessageToMainWindow(DataTransmitter.ID_RENAME);
              break;
            case 1740784714:
              if (!(lower == "delete")) break;
              this.PostMessageToMainWindow(DataTransmitter.ID_DELETE);
              break;
            case 3447633555:
              if (!(lower == "contextmenu")) break;
              this.PostMessageToMainWindow(DataTransmitter.ID_PROJECTBROWSER_CONTEXT_MENU_POP);
              break;
            case 3859557458:
              if (!(lower == "c") || !ctrlPressed) break;
              this.PostMessageToMainWindow(DataTransmitter.ID_COPY);
              break;
            case 4077666505:
              if (!(lower == "v") || !ctrlPressed) break;
              this.PostMessageToMainWindow(DataTransmitter.ID_PASTE);
              break;
            case 4228665076:
              if (!(lower == "y") || !ctrlPressed) break;
              this.PostMessageToMainWindow(DataTransmitter.ID_REDO);
              break;
            case 4278997933:
              if (!(lower == "z") || !ctrlPressed) break;
              this.PostMessageToMainWindow(DataTransmitter.ID_UNDO);
              break;
            }
        }
    }

Π£ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Π° Π΅ΡΡ‚ΡŒ свои достоинства ΠΈ нСдостатки, самым ΡƒΠ΄ΠΎΠ±Π½Ρ‹ΠΌ Π½Π° ΠΌΠΎΠΉ взгляд, для взаимодСйствия Π½Π° ΠΎΠ΄Π½ΠΎΠΉ локальной машинС являСтся Named Pipes. Π•Π³ΠΎ ΠΌΡ‹ ΠΈ рассмотрим.

Π§Ρ‚ΠΎ Ρ‚Π°ΠΊΠΎΠ΅ Named Pipes?

Named Pipes ΠΏΡ€Π΅Π΄ΡΡ‚Π°Π²Π»ΡΡŽΡ‚ собой ΠΌΠ΅Ρ…Π°Π½ΠΈΠ·ΠΌ мСТпроцСссного взаимодСйствия (Inter-Process Communication, IPC), ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ позволяСт процСссам ΠΎΠ±ΠΌΠ΅Π½ΠΈΠ²Π°Ρ‚ΡŒΡΡ Π΄Π°Π½Π½Ρ‹ΠΌΠΈ Ρ‡Π΅Ρ€Π΅Π· ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½Π½Ρ‹Π΅ ΠΊΠ°Π½Π°Π»Ρ‹. Они ΠΎΠ±Π΅ΡΠΏΠ΅Ρ‡ΠΈΠ²Π°ΡŽΡ‚ ΠΎΠ΄Π½ΠΎΠ½Π°ΠΏΡ€Π°Π²Π»Π΅Π½Π½ΠΎΠ΅ ΠΈΠ»ΠΈ Π΄Π²ΡƒΠ½Π°ΠΏΡ€Π°Π²Π»Π΅Π½Π½ΠΎΠ΅ соСдинСниС ΠΌΠ΅ΠΆΠ΄Ρƒ процСссами. Помимо высокой ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ, Named Pipes Ρ‚Π°ΠΊΠΆΠ΅ ΠΏΡ€Π΅Π΄Π»Π°Π³Π°ΡŽΡ‚ Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Π΅ ΡƒΡ€ΠΎΠ²Π½ΠΈ бСзопасности, Ρ‡Ρ‚ΠΎ Π΄Π΅Π»Π°Π΅Ρ‚ ΠΈΡ… ΠΏΡ€ΠΈΠ²Π»Π΅ΠΊΠ°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΌ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ΠΌ для ΠΌΠ½ΠΎΠ³ΠΈΡ… сцСнариСв взаимодСйствия ΠΌΠ΅ΠΆΠ΄Ρƒ процСссами.

ВзаимодСйствиС ΠΌΠ΅ΠΆΠ΄Ρƒ прилоТСниями Π½Π° .NET 4.8 ΠΈ .NET 7

Рассмотрим Π΄Π²Π° прилоТСния, ΠΎΠ΄Π½ΠΎ ΠΈΠ· ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… содСрТит бизнСс-Π»ΠΎΠ³ΠΈΠΊΡƒ (сСрвСр), Π° Π΄Ρ€ΡƒΠ³ΠΎΠ΅ - ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠΉ интСрфСйс (ΠΊΠ»ΠΈΠ΅Π½Ρ‚). Для обСспСчСния связи ΠΌΠ΅ΠΆΠ΄Ρƒ этими двумя процСссами ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ NamedPipe.

ΠŸΡ€ΠΈΠ½Ρ†ΠΈΠΏ Ρ€Π°Π±ΠΎΡ‚Ρ‹ NamedPipe Π²ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ Π² сСбя ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ шаги:

  1. Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΈ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ NamedPipe: Π‘Π΅Ρ€Π²Π΅Ρ€ создаСт ΠΈ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€ΠΈΡ€ΡƒΠ΅Ρ‚ NamedPipe с ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½Ρ‹ΠΌ ΠΈΠΌΠ΅Π½Π΅ΠΌ, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ Π±ΡƒΠ΄Π΅Ρ‚ доступно ΠΊΠ»ΠΈΠ΅Π½Ρ‚Ρƒ. ΠšΠ»ΠΈΠ΅Π½Ρ‚Ρƒ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Π·Π½Π°Ρ‚ΡŒ это имя, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒΡΡ ΠΊ Ρ‚Ρ€ΡƒΠ±Π΅.
  2. ОТиданиС ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ: Π‘Π΅Ρ€Π²Π΅Ρ€ Π½Π°Ρ‡ΠΈΠ½Π°Π΅Ρ‚ ΠΎΠΆΠΈΠ΄Π°Ρ‚ΡŒ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° ΠΊ Ρ‚Ρ€ΡƒΠ±Π΅. Π­Ρ‚ΠΎ Π±Π»ΠΎΠΊΠΈΡ€ΡƒΡŽΡ‰Π°Ρ опСрация, ΠΈ сСрвСр остаСтся Π² подвСшСнном состоянии Π΄ΠΎ Ρ‚Π΅Ρ… ΠΏΠΎΡ€, ΠΏΠΎΠΊΠ° ΠΊΠ»ΠΈΠ΅Π½Ρ‚ Π½Π΅ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡΡ.
  3. ΠŸΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΊ NamedPipe: ΠšΠ»ΠΈΠ΅Π½Ρ‚ ΠΈΠ½ΠΈΡ†ΠΈΠΈΡ€ΡƒΠ΅Ρ‚ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΊ NamedPipe, указывая имя Ρ‚Ρ€ΡƒΠ±Ρ‹, ΠΊ ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ ΠΎΠ½ Ρ…ΠΎΡ‡Π΅Ρ‚ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒΡΡ.
  4. ОбмСн Π΄Π°Π½Π½Ρ‹ΠΌΠΈ: ПослС ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎΠ³ΠΎ соСдинСния ΠΊΠ»ΠΈΠ΅Π½Ρ‚ ΠΈ сСрвСр ΠΌΠΎΠ³ΡƒΡ‚ ΠΎΠ±ΠΌΠ΅Π½ΠΈΠ²Π°Ρ‚ΡŒΡΡ Π΄Π°Π½Π½Ρ‹ΠΌΠΈ Π² Π²ΠΈΠ΄Π΅ Π±Π°ΠΉΡ‚ΠΎΠ²Ρ‹Ρ… ΠΏΠΎΡ‚ΠΎΠΊΠΎΠ². ΠšΠ»ΠΈΠ΅Π½Ρ‚ отправляСт запросы Π½Π° Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ бизнСс-Π»ΠΎΠ³ΠΈΠΊΠΈ, Π° сСрвСр ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅Ρ‚ эти запросы ΠΈ отсылаСт Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹.
  5. Π—Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΈΠ΅ сСанса: ПослС Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΈΡ ΠΎΠ±ΠΌΠ΅Π½Π° Π΄Π°Π½Π½Ρ‹ΠΌΠΈ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ ΠΈ сСрвСр ΠΌΠΎΠ³ΡƒΡ‚ Π·Π°ΠΊΡ€Ρ‹Ρ‚ΡŒ соСдинСниС с NamedPipe.

Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ сСрвСра

На ΠΏΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌΠ΅ .NET сСрвСрная Ρ‡Π°ΡΡ‚ΡŒ прСдставлСна классом NamedPipeServerStream. РСализация класса прСдоставляСт асинхронныС ΠΈ синхронныС ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с NamedPipe. Π’ΠΎ ΠΈΠ·Π±Π΅ΠΆΠ°Π½ΠΈΠ΅ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠΈ основного ΠΏΠΎΡ‚ΠΎΠΊΠ°, ΠΌΡ‹ Π±ΡƒΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ асинхронныС ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹.

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ ΠΊΠΎΠ΄Π° для создания NamedPipeServer:

public static class NamedPipeUtil
{
    /// <summary>
    /// Create a server for the current user only
    /// </summary>
    public static NamedPipeServerStream CreateServer(PipeDirection? pipeDirection = null)
    {
        const PipeOptions pipeOptions = PipeOptions.Asynchronous | PipeOptions.WriteThrough;
        return new NamedPipeServerStream(
            GetPipeName(),
            pipeDirection ?? PipeDirection.InOut,
            NamedPipeServerStream.MaxAllowedServerInstances,
            PipeTransmissionMode.Byte,
            pipeOptions);
    }
    
    private static string GetPipeName()
    {
        var serverDirectory = AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar);
        var pipeNameInput = $"{Environment.UserName}.{serverDirectory}";
        var hash = new SHA256Managed().ComputeHash(Encoding.UTF8.GetBytes(pipeNameInput));
    
        return Convert.ToBase64String(hash)
            .Replace("/", "_")
            .Replace("=", string.Empty);
    }
}

Имя сСрвСра Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½Ρ‹Π΅ символы Π²ΠΎ ΠΈΠ·Π±Π΅ΠΆΠ°Π½ΠΈΠ΅ ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ. Для создания ΠΈΠΌΠ΅Π½ΠΈ Ρ‚Ρ€ΡƒΠ±Ρ‹ ΠΌΡ‹ Π±ΡƒΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Ρ…Π΅Ρˆ, созданный ΠΈΠ· ΠΈΠΌΠ΅Π½ΠΈ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΠΈ Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΉ ΠΏΠ°ΠΏΠΊΠΈ, достаточно ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½ΠΎ Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ ΠΏΡ€ΠΈ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ использовал ΠΈΠΌΠ΅Π½Π½ΠΎ этот сСрвСр. Π’Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ это ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ ΠΈΠ»ΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ любоС имя Π² Ρ€Π°ΠΌΠΊΠ°Ρ… своСго ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°, особСнно Ссли ΠΊΠ»ΠΈΠ΅Π½Ρ‚ ΠΈ сСрвСр находятся Π² Ρ€Π°Π·Π½Ρ‹Ρ… дирСкториях.

Π”Π°Π½Π½Ρ‹ΠΉ ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π² Roslyn .NET compiler. Для Ρ‚Π΅Ρ… ΠΊΡ‚ΠΎ сильнСС Ρ…ΠΎΡ‡Π΅Ρ‚ ΡƒΠ³Π»ΡƒΠ±ΠΈΡ‚ΡŒΡΡ Π² эту Ρ‚Π΅ΠΌΡƒ, Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡƒΡŽ ΠΈΠ·ΡƒΡ‡ΠΈΡ‚ΡŒ исходный ΠΊΠΎΠ΄ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°

PipeDirection ΡƒΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ направлСния ΠΊΠ°Π½Π°Π»Π°, PipeDirection.In Π³ΠΎΠ²ΠΎΡ€ΠΈΡ‚ ΠΎ Ρ‚ΠΎΠΌ Ρ‡Ρ‚ΠΎ сСрвСр Π±ΡƒΠ΄Π΅Ρ‚ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Ρ‚ΡŒ сообщСния, Π° PipeDirection.InOut смоТСт ΠΊΠ°ΠΊ ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Ρ‚ΡŒ, Ρ‚Π°ΠΊ ΠΈ ΠΎΡ‚ΠΏΡ€Π°Π²Π»ΡΡ‚ΡŒ ΠΈΡ….

Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°

Для создания ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° Π²ΠΎΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡΡ классом NamedPipeClientStream. Код практичСски Π°Π½Π°Π»ΠΎΠ³ΠΈΡ‡Π΅Π½ с сСрвСром, ΠΈ ΠΌΠΎΠΆΠ΅Ρ‚ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΠΎΡ‚Π»ΠΈΡ‡Π°Ρ‚ΡŒΡΡ Π² зависимости ΠΎΡ‚ вСрсий .NET. НапримСр, Π² .NET framework 4.8 значСния PipeOptions.CurrentUserOnly Π½Π΅Ρ‚, Π½ΠΎ появилось Π² .NET 7.

/// <summary>
/// Create a client for the current user only
/// </summary>
public static NamedPipeClientStream CreateClient(PipeDirection? pipeDirection = null)
{
    const PipeOptions pipeOptions = PipeOptions.Asynchronous | PipeOptions.WriteThrough | PipeOptions.CurrentUserOnly;
    return new NamedPipeClientStream(".",
        GetPipeName(),
        pipeDirection ?? PipeDirection.Out,
        pipeOptions);
}

private static string GetPipeName()
{
    var clientDirectory = AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar);
    var pipeNameInput = $"{System.Environment.UserName}.{clientDirectory}";
    var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(pipeNameInput));

    return Convert.ToBase64String(bytes)
        .Replace("/", "_")
        .Replace("=", string.Empty);
}

ΠŸΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ» ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ

NamedPipe прСдставляСт собой Stream, Ρ‡Ρ‚ΠΎ позволяСт Π½Π°ΠΌ Π·Π°ΠΏΠΈΡΡ‹Π²Π°Ρ‚ΡŒ Π»ΡŽΠ±ΡƒΡŽ ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ Π±Π°ΠΉΡ‚ΠΎΠ² Π² ΠΏΠΎΡ‚ΠΎΠΊ. Однако, Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ с Π±Π°ΠΉΡ‚Π°ΠΌΠΈ Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ Π½Π΅ ΠΎΡ‡Π΅Π½ΡŒ ΡƒΠ΄ΠΎΠ±Π½ΠΎ, особСнно ΠΊΠΎΠ³Π΄Π° ΠΌΡ‹ ΠΈΠΌΠ΅Π΅ΠΌ Π΄Π΅Π»ΠΎ со слоТными Π΄Π°Π½Π½Ρ‹ΠΌΠΈ ΠΈΠ»ΠΈ структурами. Для упрощСния взаимодСйствия с ΠΏΠΎΡ‚ΠΎΠΊΠ°ΠΌΠΈ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈ структурирования ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ Π² ΡƒΠ΄ΠΎΠ±Π½ΠΎΠΌ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ΡΡ ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ»Ρ‹ ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ.

ΠŸΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ»Ρ‹ ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΡΡŽΡ‚ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ ΠΈ порядок ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ Π΄Π°Π½Π½Ρ‹Ρ… ΠΌΠ΅ΠΆΠ΄Ρƒ прилоТСниями. Они ΠΎΠ±Π΅ΡΠΏΠ΅Ρ‡ΠΈΠ²Π°ΡŽΡ‚ структурированиС ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΠ±Π΅ΡΠΏΠ΅Ρ‡ΠΈΡ‚ΡŒ ΠΏΠΎΠ½ΠΈΠΌΠ°Π½ΠΈΠ΅ ΠΈ ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΡƒΡŽ ΠΈΠ½Ρ‚Π΅Ρ€ΠΏΡ€Π΅Ρ‚Π°Ρ†ΠΈΡŽ Π΄Π°Π½Π½Ρ‹Ρ… ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚Π΅Π»Π΅ΠΌ ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π°Ρ‚Π΅Π»Π΅ΠΌ.

Π’ случая ΠΊΠΎΠ³Π΄Π° Π½Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ "Запрос Π½Π° Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ Π½Π° сСрвСрС" ΠΈΠ»ΠΈ "Запрос Π½Π° ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ настроСк прилоТСния", сСрвСр Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΏΠΎΠ½ΠΈΠΌΠ°Ρ‚ΡŒ ΠΊΠ°ΠΊ Π΅Π³ΠΎ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Ρ‚ΡŒ ΠΎΡ‚ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ для облСгчСния ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ запросов ΠΈ ΡƒΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ΠΌ ΠΎΠ±ΠΌΠ΅Π½Π° Π΄Π°Π½Π½Ρ‹ΠΌΠΈ, создадим Enum RequestType.

public enum RequestType
{
    PrintMessage,
    UpdateModel
}

Π‘Π°ΠΌ заброс Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€Π΅Π΄ΡΡ‚Π°Π²Π»ΡΡ‚ΡŒ класс, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±ΡƒΠ΄Π΅Ρ‚ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ всю ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ ΠΏΠ΅Ρ€Π΅Π΄Π°Π²Π°Π΅ΠΌΡ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ….

public abstract class Request
{
    public abstract RequestType Type { get; }

    protected abstract void AddRequestBody(BinaryWriter writer);

    /// <summary>
    ///     Write a Request to the given stream.
    /// </summary>
    public async Task WriteAsync(Stream outStream)
    {
        using var memoryStream = new MemoryStream();
        using var writer = new BinaryWriter(memoryStream, Encoding.Unicode);

        writer.Write((int) Type);
        AddRequestBody(writer);
        writer.Flush();

        // Write the length of the request
        var length = checked((int) memoryStream.Length);
        
        // There is no way to know the number of bytes written to
        // the pipe stream. We just have to assume all of them are written
        await outStream.WriteAsync(BitConverter.GetBytes(length), 0, 4);
        memoryStream.Position = 0;
        await memoryStream.CopyToAsync(outStream, length);
    }

    /// <summary>
    /// Write a string to the Writer where the string is encoded
    /// as a length prefix (signed 32-bit integer) follows by
    /// a sequence of characters.
    /// </summary>
    protected static void WriteLengthPrefixedString(BinaryWriter writer, string value)
    {
        writer.Write(value.Length);
        writer.Write(value.ToCharArray());
    }
}

Класс содСрТит Π±Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΊΠΎΠ΄ для записи Π΄Π°Π½Π½Ρ‹Ρ… Π² ΠΏΠΎΡ‚ΠΎΠΊ. AddRequestBody() ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄Π½Ρ‹ΠΌΠΈ классами, для записи для записи собствСнных структурированных Π΄Π°Π½Π½Ρ‹Ρ….

ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄Π½Ρ‹Ρ… классов:

/// <summary>
/// Represents a Request from the client. A Request is as follows.
/// 
///  Field Name         Type            Size (bytes)
/// --------------------------------------------------
///  RequestType        Integer         4
///  Message            String          Variable
/// 
/// Strings are encoded via a character count prefix as a 
/// 32-bit integer, followed by an array of characters.
/// 
/// </summary>
public class PrintMessageRequest : Request
{
    public string Message { get; }

    public override RequestType Type => RequestType.PrintMessage;

    public PrintMessageRequest(string message)
    {
        Message = message;
    }

    protected override void AddRequestBody(BinaryWriter writer)
    {
        WriteLengthPrefixedString(writer, Message);
    }
}

/// <summary>
/// Represents a Request from the client. A Request is as follows.
/// 
///  Field Name         Type            Size (bytes)
/// --------------------------------------------------
///  ResponseType       Integer         4
///  Iterations         Integer         4
///  ForceUpdate        Boolean         1
///  ModelName          String          Variable
/// 
/// Strings are encoded via a character count prefix as a 
/// 32-bit integer, followed by an array of characters.
/// 
/// </summary>
public class UpdateModelRequest : Request
{
    public int Iterations { get; }
    public bool ForceUpdate { get; }
    public string ModelName { get; }

    public override RequestType Type => RequestType.UpdateModel;

    public UpdateModelRequest(string modelName, int iterations, bool forceUpdate)
    {
        Iterations = iterations;
        ForceUpdate = forceUpdate;
        ModelName = modelName;
    }

    protected override void AddRequestBody(BinaryWriter writer)
    {
        writer.Write(Iterations);
        writer.Write(ForceUpdate);
        WriteLengthPrefixedString(writer, ModelName);
    }
}

Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ Π΄Π°Π½Π½ΡƒΡŽ структуру, ΠΊΠ»ΠΈΠ΅Π½Ρ‚Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ ΡΠΎΠ·Π΄Π°Π²Π°Ρ‚ΡŒ запросы Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… Ρ‚ΠΈΠΏΠΎΠ², ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ ΠΈΠ· ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… опрСдСляСт ΡΠΎΠ±ΡΡ‚Π²Π΅Π½Π½ΡƒΡŽ Π»ΠΎΠ³ΠΈΠΊΡƒ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ². ΠšΠ»Π°ΡΡΡ‹ PrintMessageRequest ΠΈ UpdateModelRequest ΠΏΡ€Π΅Π΄ΠΎΡΡ‚Π°Π²Π»ΡΡŽΡ‚ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ запросов, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΌΠΎΠΆΠ½ΠΎ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ сСрвСру для выполнСния ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹Ρ… Π·Π°Π΄Π°Ρ‡.

На сторонС сСрвСра, Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΡƒΡŽ Π»ΠΎΠ³ΠΈΠΊΡƒ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ входящих запросов. Для этого сСрвСр Π΄ΠΎΠ»ΠΆΠ΅Π½ Ρ‡ΠΈΡ‚Π°Ρ‚ΡŒ Π΄Π°Π½Π½Ρ‹Π΅ ΠΈΠ· ΠΏΠΎΡ‚ΠΎΠΊΠ° ΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½Π½Ρ‹Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ для выполнСния Π½ΡƒΠΆΠ½Ρ‹Ρ… ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ.

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½Π½ΠΎΠ³ΠΎ запроса Π½Π° сторонС сСрвСра:

/// <summary>
/// Represents a request from the client. A request is as follows.
/// 
///  Field Name         Type                Size (bytes)
/// ----------------------------------------------------
///  RequestType       enum RequestType   4
///  RequestBody       Request subclass   variable
/// 
/// </summary>
public abstract class Request
{
    public enum RequestType
    {
        PrintMessage,
        UpdateModel
    }
    
    public abstract RequestType Type { get; }

    /// <summary>
    ///     Read a Request from the given stream.
    /// </summary>
    public static async Task<Request> ReadAsync(Stream stream)
    {
        var lengthBuffer = new byte[4];
        await ReadAllAsync(stream, lengthBuffer, 4).ConfigureAwait(false);
        var length = BitConverter.ToUInt32(lengthBuffer, 0);

        var requestBuffer = new byte[length];
        await ReadAllAsync(stream, requestBuffer, requestBuffer.Length);

        using var reader = new BinaryReader(new MemoryStream(requestBuffer), Encoding.Unicode);

        var requestType = (RequestType) reader.ReadInt32();
        return requestType switch
        {
            RequestType.PrintMessage => PrintMessageRequest.Create(reader),
            RequestType.UpdateModel => UpdateModelRequest.Create(reader),
            _ => throw new ArgumentOutOfRangeException()
        };
    }
    
    /// <summary>
    /// This task does not complete until we are completely done reading.
    /// </summary>
    private static async Task ReadAllAsync(Stream stream, byte[] buffer, int count)
    {
        var totalBytesRead = 0;
        do
        {
            var bytesRead = await stream.ReadAsync(buffer, totalBytesRead, count - totalBytesRead);
            if (bytesRead == 0) throw new EndOfStreamException("Reached end of stream before end of read.");
            totalBytesRead += bytesRead;
        } while (totalBytesRead < count);
    }

    /// <summary>
    /// Read a string from the Reader where the string is encoded
    /// as a length prefix (signed 32-bit integer) followed by
    /// a sequence of characters.
    /// </summary>
    protected static string ReadLengthPrefixedString(BinaryReader reader)
    {
        var length = reader.ReadInt32();
        return length < 0 ? null : new string(reader.ReadChars(length));
    }
}

/// <summary>
/// Represents a Request from the client. A Request is as follows.
/// 
///  Field Name         Type            Size (bytes)
/// --------------------------------------------------
///  RequestType        Integer         4
///  Message            String          Variable
/// 
/// Strings are encoded via a character count prefix as a 
/// 32-bit integer, followed by an array of characters.
/// 
/// </summary>
public class PrintMessageRequest : Request
{
    public string Message { get; }

    public override RequestType Type => RequestType.PrintMessage;

    public PrintMessageRequest(string message)
    {
        Message = message;
    }

    protected override void AddRequestBody(BinaryWriter writer)
    {
        WriteLengthPrefixedString(writer, Message);
    }
}

/// <summary>
/// Represents a Request from the client. A Request is as follows.
/// 
///  Field Name         Type            Size (bytes)
/// --------------------------------------------------
///  RequestType        Integer         4
///  Iterations         Integer         4
///  ForceUpdate        Boolean         1
///  ModelName          String          Variable
/// 
/// Strings are encoded via a character count prefix as a 
/// 32-bit integer, followed by an array of characters.
/// 
/// </summary>
public class UpdateModelRequest : Request
{
    public int Iterations { get; }
    public bool ForceUpdate { get; }
    public string ModelName { get; }

    public override RequestType Type => RequestType.UpdateModel;

    public UpdateModelRequest(string modelName, int iterations, bool forceUpdate)
    {
        Iterations = iterations;
        ForceUpdate = forceUpdate;
        ModelName = modelName;
    }

    protected override void AddRequestBody(BinaryWriter writer)
    {
        writer.Write(Iterations);
        writer.Write(ForceUpdate);
        WriteLengthPrefixedString(writer, ModelName);
    }
}

ΠœΠ΅Ρ‚ΠΎΠ΄ ReadAsync() считываСт Ρ‚ΠΈΠΏ запроса ΠΈΠ· ΠΏΠΎΡ‚ΠΎΠΊΠ°, Π° Π·Π°Ρ‚Π΅ΠΌ, Π² зависимости ΠΎΡ‚ Ρ‚ΠΈΠΏΠ°, считываСт ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Π΅ ΠΈ создаСт ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰Π΅Π³ΠΎ запроса.

РСализация ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ»Π° ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈ структурированиС запросов Π² Π²ΠΈΠ΄Π΅ классов ΠΏΠΎΠ·Π²ΠΎΠ»ΡΡŽΡ‚ эффСктивно ΡƒΠΏΡ€Π°Π²Π»ΡΡ‚ΡŒ ΠΎΠ±ΠΌΠ΅Π½ΠΎΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠ΅ΠΉ ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠΌ ΠΈ сСрвСром, обСспСчивая ΠΏΡ€ΠΈ этом структурированноС ΠΈ понятноС взаимодСйствиС ΠΌΠ΅ΠΆΠ΄Ρƒ двумя сторонами. Однако, ΠΏΡ€ΠΈ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠΈ ΠΏΠΎΠ΄ΠΎΠ±Π½Ρ‹Ρ… ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ»ΠΎΠ² Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡƒΡ‡ΠΈΡ‚Ρ‹Π²Π°Ρ‚ΡŒ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ‹Π΅ риски бСзопасности, Π° Ρ‚Π°ΠΊΠΆΠ΅ ΡƒΠ±Π΅Π΄ΠΈΡ‚ΡŒΡΡ, Ρ‡Ρ‚ΠΎ ΠΎΠ±Π° ΠΊΠΎΠ½Ρ†Π° взаимодСйствия ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°ΡŽΡ‚ всС Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ‹Π΅ случаи.

Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ соСдинСниями

Для ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ сообщСний с UI ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° Π½Π° сСрвСр, создадим класс ClientDispatcher ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±ΡƒΠ΄Π΅Ρ‚ ΠΎΠ±Π΅ΡΠΏΠ΅Ρ‡ΠΈΠ²Π°Ρ‚ΡŒ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΡƒ соСдинСний, Ρ‚Π°ΠΉΠΌ-Π°ΡƒΡ‚ΠΎΠ² ΠΈ ΠΏΠ»Π°Π½ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ запросов, прСдоставляя интСрфСйс для взаимодСйствия ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° с сСрвСром Ρ‡Π΅Ρ€Π΅Π· ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½Π½Ρ‹Π΅ Ρ‚Ρ€ΡƒΠ±Ρ‹.

/// <summary>
///     This class manages the connections, timeout and general scheduling of requests to the server.
/// </summary>
public class ClientDispatcher
{
    private const int TimeOutNewProcess = 10000;

    private Task _connectionTask;
    private readonly NamedPipeClientStream _client = NamedPipeUtil.CreateClient(PipeDirection.Out);

    /// <summary>
    ///     Connects to server without awaiting
    /// </summary>
    public void ConnectToServer()
    {
        _connectionTask = _client.ConnectAsync(TimeOutNewProcess);
    }

    /// <summary>
    ///     Write a Request to the server.
    /// </summary>
    public async Task WriteRequestAsync(Request request)
    {
        await _connectionTask;
        await request.WriteAsync(_client);
    }
}

ΠŸΡ€ΠΈΠ½Ρ†ΠΈΠΏ Ρ€Π°Π±ΠΎΡ‚Ρ‹:

  1. Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ: Π’ конструкторС класса инициализируСтся NamedPipeClientStream, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹ΠΉ для создания клиСнтского ΠΏΠΎΡ‚ΠΎΠΊΠ° с ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½Π½Ρ‹ΠΌ ΠΊΠ°Π½Π°Π»ΠΎΠΌ.
  2. Установка ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ: ΠœΠ΅Ρ‚ΠΎΠ΄ ConnectToServer ΠΈΠ½ΠΈΡ†ΠΈΠΈΡ€ΡƒΠ΅Ρ‚ асинхронноС ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΊ сСрвСру. Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ сохраняСтся Π² Task. TimeOutNewProcess ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ для ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° Π² случаС возникновСния Π½Π΅ΠΏΡ€Π΅Π΄Π²ΠΈΠ΄Π΅Π½Π½Ρ‹Ρ… ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΉ.
  3. ΠžΡ‚ΠΏΡ€Π°Π²ΠΊΠ° запросов: ΠœΠ΅Ρ‚ΠΎΠ΄ WriteRequestAsync ΠΏΡ€Π΅Π΄Π½Π°Π·Π½Π°Ρ‡Π΅Π½ для асинхронной ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° Request Ρ‡Π΅Ρ€Π΅Π· установлСнноС соСдинСниС. Запрос отправится Ρ‚ΠΎΠ»ΡŒΠΊΠΎ послС установки соСдинСния.

Для ΠΏΡ€ΠΈΠ΅ΠΌΠ° сообщСний сСрвСром, создадим класс ServerDispatcher ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΡƒΠΏΡ€Π°Π²Π»ΡΡ‚ΡŒ соСдинСниСм ΠΈ Ρ‡ΠΈΡ‚Π°Ρ‚ΡŒ запросы.

/// <summary>
///     This class manages the connections, timeout and general scheduling of the client requests.
/// </summary>
public class ServerDispatcher
{
    private readonly NamedPipeServerStream _server = NamedPipeUtil.CreateServer(PipeDirection.In);

    /// <summary>
    ///     This function will accept and process new requests until the client disconnects from the server
    /// </summary>
    public async Task ListenAndDispatchConnections()
    {
        try
        {
            await _server.WaitForConnectionAsync();
            await ListenAndDispatchConnectionsCoreAsync();
        }
        finally
        {
            _server.Close();
        }
    }

    private async Task ListenAndDispatchConnectionsCoreAsync()
    {
        while (_server.IsConnected)
        {
            try
            {
                var request = await Request.ReadAsync(_server);
                if (request.Type == Request.RequestType.PrintMessage)
                {
                    var printRequest = (PrintMessageRequest) request;
                    Console.WriteLine($"Message from client: {printRequest.Message}");
                }
                else if (request.Type == Request.RequestType.UpdateModel)
                {
                    var printRequest = (UpdateModelRequest) request;
                    Console.WriteLine($"The {printRequest.ModelName} model has been {(printRequest.ForceUpdate ? "forcibly" : string.Empty)} updated {printRequest.Iterations} times");
                }
            }
            catch (EndOfStreamException)
            {
                return; //Pipe disconnected
            }
        }
    }
}

ΠŸΡ€ΠΈΠ½Ρ†ΠΈΠΏ Ρ€Π°Π±ΠΎΡ‚Ρ‹:

  1. Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ: Π’ конструкторС класса инициализируСтся NamedPipeServerStream, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹ΠΉ для создания сСрвСрного ΠΏΠΎΡ‚ΠΎΠΊΠ° с ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½Π½Ρ‹ΠΌ ΠΊΠ°Π½Π°Π»ΠΎΠΌ.
  2. ΠŸΡ€ΠΎΡΠ»ΡƒΡˆΠΈΠ²Π°Π½ΠΈΠ΅ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΉ: ΠœΠ΅Ρ‚ΠΎΠ΄ ListenAndDispatchConnections асинхронного ΠΎΠΆΠΈΠ΄Π°Π΅Ρ‚ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°, послС Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΈΡ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ запросов Π·Π°ΠΊΡ€Ρ‹Π²Π°Π΅Ρ‚ ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ ΠΊΠ°Π½Π°Π» ΠΈ освобоТдаСт рСсурсы.
  3. ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° запросов: ΠœΠ΅Ρ‚ΠΎΠ΄ ListenAndDispatchConnectionsCoreAsync ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅Ρ‚ запросы, Π΄ΠΎ ΠΌΠΎΠΌΠ΅Π½Ρ‚Π° ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°. Π’ зависимости ΠΎΡ‚ Ρ‚ΠΈΠΏΠ° запроса происходит ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰Π°Ρ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° Π΄Π°Π½Π½Ρ‹Ρ…, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, Π²Ρ‹Π²ΠΎΠ΄ Π² консоль содСрТания сообщСния ΠΈΠ»ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΠΌΠΎΠ΄Π΅Π»ΠΈ.

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ запроса ΠΈΠ· UI Π½Π° сСрвСр:

/// <summary>
///     Programme entry point
/// </summary>
public sealed partial class App
{
    public static ClientDispatcher ClientDispatcher { get; }

    static App()
    {
        ClientDispatcher = new ClientDispatcher();
        ClientDispatcher.ConnectToServer();
    }
}

/// <summary>
///     WPF view business logic 
/// </summary>
public partial class MainViewModel : ObservableObject
{
    [ObservableProperty] private string _message = string.Empty;

    [RelayCommand]
    private async Task SendMessageAsync()
    {
        var request = new PrintMessageRequest(Message);
        await App.ClientDispatcher.WriteRequestAsync(request);
    }

    [RelayCommand]
    private async Task UpdateModelAsync()
    {
        var request = new UpdateModelRequest(AppDomain.CurrentDomain.FriendlyName, 666, true);
        await App.ClientDispatcher.WriteRequestAsync(request);
    }
}

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ ΠΊΠΎΠ΄Π° ΠΏΠΎΠ»Π½ΠΎΡΡ‚ΡŒΡŽ доступСн Π² Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΈ, Π²Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ Π·Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ Π΅Π³ΠΎ Π½Π° своСй машинС Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΠ² нСсколько шагов:

  • ЗапуститС "Build Solution"
  • ЗапуститС "Run OneWay\Backend"

ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ автоматичСски запустит Server ΠΈ Client, Π° ΠΏΠΎΠ»Π½Ρ‹ΠΉ Π²Ρ‹Π²ΠΎΠ΄ сообщСний ΠΏΠ΅Ρ€Π΅Π΄Π°ΡŽΡ‰ΠΈΡ…ΡΡ ΠΏΠΎ NamedPipe Π²Ρ‹ ΡƒΠ²ΠΈΠ΄ΠΈΡ‚Π΅ Π² консоли IDE.

Двусторонняя ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡Π°

Часто Π²ΠΎΠ·Π½ΠΈΠΊΠ°ΡŽΡ‚ ситуации, ΠΊΠΎΠ³Π΄Π° обычная однонаправлСнная ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡Π° Π΄Π°Π½Π½Ρ‹Ρ… ΠΎΡ‚ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° ΠΊ сСрвСру нСдостаточна. Π’ Ρ‚Π°ΠΊΠΈΡ… случаях Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Ρ‚ΡŒ ошибки ΠΈΠ»ΠΈ ΠΎΡ‚ΠΏΡ€Π°Π²Π»ΡΡ‚ΡŒ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ Π² ΠΎΡ‚Π²Π΅Ρ‚. Π§Ρ‚ΠΎΠ±Ρ‹ ΠΎΠ±Π΅ΡΠΏΠ΅Ρ‡ΠΈΡ‚ΡŒ Π±ΠΎΠ»Π΅Π΅ слоТноС взаимодСйствиС ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠΌ ΠΈ сСрвСром, Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ°ΠΌ приходится ΠΏΡ€ΠΈΠ±Π΅Π³Π°Ρ‚ΡŒ ΠΊ ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½ΠΈΡŽ двухстороннСй ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ Π΄Π°Π½Π½Ρ‹Ρ…, которая позволяСт ΠΎΠ±ΠΌΠ΅Π½ΠΈΠ²Π°Ρ‚ΡŒΡΡ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠ΅ΠΉ Π² ΠΎΠ±ΠΎΠΈΡ… направлСниях.

Как ΠΈ Π² случаС с запросами, для эффСктивной ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ² Ρ‚Π°ΠΊΠΆΠ΅ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ пСрСчислСниС для Ρ‚ΠΈΠΏΠΎΠ² ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ². Π­Ρ‚ΠΎ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ‚ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Ρƒ ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎ ΠΈΠ½Ρ‚Π΅Ρ€ΠΏΡ€Π΅Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅.

public enum ResponseType
{
    // The update request completed on the server and the results are contained in the message. 
    UpdateCompleted,
    
    // The request was rejected by the server.
    Rejected
}

Для эффСктивной ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ² потрСбуСтся ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ Π½ΠΎΠ²Ρ‹ΠΉ класс, Π½Π°Π·Π²Π°Π½Π½Ρ‹ΠΉ Response. По Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»Ρƒ ΠΎΠ½ Π½ΠΈΡ‡Π΅ΠΌ Π½Π΅ отличаСтся ΠΎΡ‚ класса Request, ΠΎΠ΄Π½Π°ΠΊΠΎ Π² ΠΎΡ‚Π»ΠΈΡ‡ΠΈΠ΅ ΠΎΡ‚ Request, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΌΠΎΠΆΠ΅Ρ‚ Ρ‡ΠΈΡ‚Π°Ρ‚ΡŒΡΡ Π½Π° сСрвСрС, Response Π±ΡƒΠ΄Π΅Ρ‚ Π·Π°ΠΏΠΈΡΡ‹Π²Π°Ρ‚ΡŒΡΡ Π² ΠΏΠΎΡ‚ΠΎΠΊ.

/// <summary>
/// Base class for all possible responses to a request.
/// The ResponseType enum should list all possible response types
/// and ReadResponse creates the appropriate response subclass based
/// on the response type sent by the client.
/// The format of a response is:
///
/// Field Name       Field Type          Size (bytes)
/// -------------------------------------------------
/// ResponseType     enum ResponseType   4
/// ResponseBody     Response subclass   variable
/// </summary>
public abstract class Response
{
    public enum ResponseType
    {
        // The update request completed on the server and the results are contained in the message. 
        UpdateCompleted,
    
        // The request was rejected by the server.
        Rejected
    }

    public abstract ResponseType Type { get; }

    protected abstract void AddResponseBody(BinaryWriter writer);

    /// <summary>
    ///     Write a Response to the stream.
    /// </summary>
    public async Task WriteAsync(Stream outStream)
    {
        // Same as request class from client
    }

    /// <summary>
    /// Write a string to the Writer where the string is encoded
    /// as a length prefix (signed 32-bit integer) follows by
    /// a sequence of characters.
    /// </summary>
    protected static void WriteLengthPrefixedString(BinaryWriter writer, string value)
    {
        // Same as request class from client
    }
}

ΠŸΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄Π½Ρ‹Π΅ классы Π²Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ Π½Π°ΠΉΡ‚ΠΈ Π² Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΈ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°: PipeProtocol

Для Ρ‚ΠΎΠ³ΠΎ Ρ‡Ρ‚ΠΎΠ±Ρ‹ сСрвСр ΠΌΠΎΠ³ ΠΎΡ‚ΠΏΡ€Π°Π²Π»ΡΡ‚ΡŒ ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Ρƒ, ΠΌΡ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΠΌΠΎΠ΄ΠΈΡ„ΠΈΡ†ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ класс ServerDispatcher. Π­Ρ‚ΠΎ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ‚ Π·Π°ΠΏΠΈΡΡ‹Π²Π°Ρ‚ΡŒ ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ Π² Stream послС выполнСния Π·Π°Π΄Π°Ρ‡ΠΈ.

Π’Π°ΠΊ ΠΆΠ΅ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠΌ Π½Π°ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ Ρ‚Ρ€ΡƒΠ±Ρ‹ Π½Π° Π΄Π²ΡƒΠ½Π°ΠΏΡ€Π°Π²Π»Π΅Π½Π½ΠΎΠ΅:

_server = NamedPipeUtil.CreateServer(PipeDirection.InOut);

/// <summary>
///     Write a Response to the client.
/// </summary>
public async Task WriteResponseAsync(Response response) => await response.WriteAsync(_server);

Для дСмонстрации Ρ€Π°Π±ΠΎΡ‚Ρ‹ Π΄ΠΎΠ±Π°Π²ΠΈΠΌ Π·Π°Π΄Π΅Ρ€ΠΆΠΊΡƒ Π½Π° 2 сСкунды, эмулируя Ρ‚ΡΠΆΠ΅Π»ΡƒΡŽ Π·Π°Π΄Π°Ρ‡Ρƒ, Π² ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ ListenAndDispatchConnectionsCoreAsync:

private async Task ListenAndDispatchConnectionsCoreAsync()
{
    while (_server.IsConnected)
    {
        try
        {
            var request = await Request.ReadAsync(_server);
            
            // ...
            if (request.Type == Request.RequestType.UpdateModel)
            {
                var printRequest = (UpdateModelRequest) request;

                await Task.Delay(TimeSpan.FromSeconds(2));
                await WriteResponseAsync(new UpdateCompletedResponse(changes: 69, version: "2.1.7"));
            }
        }
        catch (EndOfStreamException)
        {
            return; //Pipe disconnected
        }
    }
}

Π’ настоящий ΠΌΠΎΠΌΠ΅Π½Ρ‚ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ Π½Π΅ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅Ρ‚ ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ ΠΎΡ‚ сСрвСра. Π”Π°Π²Π°ΠΉΡ‚Π΅ сдСлаСм это. Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ Π² ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π΅ класс Response, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±ΡƒΠ΄Π΅Ρ‚ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Ρ‚ΡŒ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½Π½Ρ‹Π΅ ΠΎΡ‚Π²Π΅Ρ‚Ρ‹.

/// <summary>
/// Base class for all possible responses to a request.
/// The ResponseType enum should list all possible response types
/// and ReadResponse creates the appropriate response subclass based
/// on the response type sent by the client.
/// The format of a response is:
///
/// Field Name       Field Type          Size (bytes)
/// -------------------------------------------------
/// ResponseType     enum ResponseType   4
/// ResponseBody     Response subclass   variable
/// 
/// </summary>
public abstract class Response
{
    public enum ResponseType
    {
        // The update request completed on the server and the results are contained in the message. 
        UpdateCompleted,

        // The request was rejected by the server.
        Rejected
    }

    public abstract ResponseType Type { get; }

    /// <summary>
    ///     Read a Request from the given stream.
    /// </summary>
    public static async Task<Response> ReadAsync(Stream stream)
    {
        // Same as request class from server
    }

    /// <summary>
    /// This task does not complete until we are completely done reading.
    /// </summary>
    private static async Task ReadAllAsync(Stream stream, byte[] buffer, int count)
    {
        // Same as request class from server
    }

    /// <summary>
    /// Read a string from the Reader where the string is encoded
    /// as a length prefix (signed 32-bit integer) followed by
    /// a sequence of characters.
    /// </summary>
    protected static string ReadLengthPrefixedString(BinaryReader reader)
    {
        // Same as request class from server
    }
}

Π”Π°Π»Π΅Π΅ ΠΎΠ±Π½ΠΎΠ²ΠΈΠΌ класс ClientDispatcher, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΠ½ ΠΌΠΎΠ³ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Ρ‚ΡŒ ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ ΠΎΡ‚ сСрвСра. Для этого Π΄ΠΎΠ±Π°Π²ΠΈΠΌ Π½ΠΎΠ²Ρ‹ΠΉ ΠΌΠ΅Ρ‚ΠΎΠ΄ ΠΈ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠΌ Π½Π°ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ Π½Π° Π΄Π²ΡƒΠ½Π°ΠΏΡ€Π°Π²Π»Π΅Π½Π½ΠΎΠ΅.

_client = NamedPipeUtil.CreateClient(PipeDirection.InOut);

/// <summary>
///     Read a Response from the server.
/// </summary>
public async Task<Response> ReadResponseAsync() => await Response.ReadAsync(_client);

Π’Π°ΠΊΠΆΠ΅ Π΄ΠΎΠ±Π°Π²ΠΈΠΌ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΡƒ ΠΎΡ‚Π²Π΅Ρ‚Π° Π²ΠΎ ViewModel, Π³Π΄Π΅ Π±ΡƒΠ΄Π΅ΠΌ просто Π²Ρ‹Π²ΠΎΠ΄ΠΈΡ‚ΡŒ Π΅Π³ΠΎ ΠΊΠ°ΠΊ сообщСниС.

[RelayCommand]
private async Task UpdateModelAsync()
{
    var request = new UpdateModelRequest(AppDomain.CurrentDomain.FriendlyName, 666, true);
    await App.ClientDispatcher.WriteRequestAsync(request);

    var response = await App.ClientDispatcher.ReadResponseAsync();
    if (response.Type == Response.ResponseType.UpdateCompleted)
    {
        var completedResponse = (UpdateCompletedResponse) response;

        MessageBox.Show($"{completedResponse.Changes} elements successfully updated to version {completedResponse.Version}");
    }
    else if (response.Type == Response.ResponseType.Rejected)
    {
        MessageBox.Show("Update failed");
    }
}

Π­Ρ‚ΠΈ измСнСния позволят Π±ΠΎΠ»Π΅Π΅ эффСктивно ΠΎΡ€Π³Π°Π½ΠΈΠ·ΠΎΠ²Π°Ρ‚ΡŒ взаимодСйствиС ΠΌΠ΅ΠΆΠ΄Ρƒ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠΌ ΠΈ сСрвСром, обСспСчивая Π±ΠΎΠ»Π΅Π΅ ΠΏΠΎΠ»Π½ΡƒΡŽ ΠΈ Π½Π°Π΄Π΅ΠΆΠ½ΡƒΡŽ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΡƒ запросов ΠΈ ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ².

РСализация ΠΏΠ»Π°Π³ΠΈΠ½Π° для Revit

Π’Π΅Ρ…Π½ΠΎΠ»ΠΎΠ³ΠΈΠΈ Ρ€Π°Π·Π²ΠΈΠ²Π°ΡŽΡ‚ΡΡ, Π° Revit Π½Π΅ мСняСтся Β© ΠšΠΎΠ½Ρ„ΡƒΡ†ΠΈΠΉ

Π’ настоящСС врСмя Revit ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ .NET Framework 4.8. Однако для ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½ΠΈΡ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΎΠ³ΠΎ интСрфСйса ΠΏΠ»Π°Π³ΠΈΠ½ΠΎΠ², рассмотрим ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ Π½Π° .NET 7. Π’Π°ΠΆΠ½ΠΎ ΠΎΡ‚ΠΌΠ΅Ρ‚ΠΈΡ‚ΡŒ, Ρ‡Ρ‚ΠΎ бэкэнд ΠΏΠ»Π°Π³ΠΈΠ½Π° Π±ΡƒΠ΄Π΅Ρ‚ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡ‚Π²ΠΎΠ²Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ с Revit Π½Π° ΡƒΡΡ‚Π°Ρ€Π΅Π²ΡˆΠ΅ΠΌ Framework, ΠΈ Π±ΡƒΠ΄Π΅Ρ‚ Π²Ρ‹ΡΡ‚ΡƒΠΏΠ°Ρ‚ΡŒ Π² качСствС сСрвСра.

Π”Π°Π²Π°ΠΉΡ‚Π΅ создадим ΠΌΠ΅Ρ…Π°Π½ΠΈΠ·ΠΌ взаимодСйствия, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ‚ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Ρƒ ΠΎΡ‚ΠΏΡ€Π°Π²Π»ΡΡ‚ΡŒ запросы Π½Π° ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠ΅ элСмСнтов ΠΌΠΎΠ΄Π΅Π»ΠΈ, Π° Π·Π°Ρ‚Π΅ΠΌ ΠΏΠΎΠ»ΡƒΡ‡Π°Ρ‚ΡŒ ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ ΠΎ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π΅ удалСния. Для Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ этой Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ ΠΌΡ‹ Π±ΡƒΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π΄Π²ΡƒΡΡ‚ΠΎΡ€ΠΎΠ½Π½ΡŽΡŽ ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡Ρƒ Π΄Π°Π½Π½Ρ‹Ρ… ΠΌΠ΅ΠΆΠ΄Ρƒ сСрвСром ΠΈ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠΌ.

ΠŸΠ΅Ρ€Π²Ρ‹ΠΌ шагом Π² нашСм процСссС Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ Π±ΡƒΠ΄Π΅Ρ‚ Π½Π°ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΏΠ»Π°Π³ΠΈΠ½ автоматичСски Π·Π°ΠΊΡ€Ρ‹Π²Π°Ρ‚ΡŒΡΡ ΠΏΡ€ΠΈ Π·Π°ΠΊΡ€Ρ‹Ρ‚ΠΈΠΈ Revit. Для этого ΠΌΡ‹ написали ΠΌΠ΅Ρ‚ΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ отправляСт ID Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π³ΠΎ процСсса ΠΊΠ»ΠΈΠ΅Π½Ρ‚Ρƒ. Π­Ρ‚ΠΎ ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ‚ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Ρƒ ΠΎΡΡƒΡ‰Π΅ΡΡ‚Π²ΠΈΡ‚ΡŒ автоматичСскоС Π·Π°ΠΊΡ€Ρ‹Ρ‚ΠΈΠ΅ своСго процСсса ΠΏΡ€ΠΈ Π·Π°ΠΊΡ€Ρ‹Ρ‚ΠΈΠΈ Ρ€ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΡΠΊΠΎΠ³ΠΎ процСсса Revit.

Код для ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ ID Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π³ΠΎ процСсса ΠΊΠ»ΠΈΠ΅Π½Ρ‚Ρƒ:

private static void RunClient(string clientName)
{
    var startInfo = new ProcessStartInfo
    {
        FileName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!.AppendPath(clientName),
        Arguments = Process.GetCurrentProcess().Id.ToString()
    };

    Process.Start(startInfo);
}

А Π²ΠΎΡ‚ ΠΊΠΎΠ΄ для ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ осущСствляСт Π·Π°ΠΊΡ€Ρ‹Ρ‚ΠΈΠ΅ своСго процСсса ΠΏΡ€ΠΈ Π·Π°ΠΊΡ€Ρ‹Ρ‚ΠΈΠΈ Ρ€ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΡΠΊΠΎΠ³ΠΎ процСсса Revit:

protected override void OnStartup(StartupEventArgs args)
{
    ParseCommandArguments(args.Args);
}

private void ParseCommandArguments(string[] args)
{
    var ownerPid = args[0];
    var ownerProcess = Process.GetProcessById(int.Parse(ownerPid));
    ownerProcess.EnableRaisingEvents = true;
    ownerProcess.Exited += (_, _) => Shutdown();
}

ΠšΡ€ΠΎΠΌΠ΅ Ρ‚ΠΎΠ³ΠΎ, Π½Π°ΠΌ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌ ΠΌΠ΅Ρ‚ΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±ΡƒΠ΄Π΅Ρ‚ ΠΎΡ‚Π²Π΅Ρ‡Π°Ρ‚ΡŒ Π·Π° ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠ΅ Π²Ρ‹Π±Ρ€Π°Π½Π½Ρ‹Ρ… элСмСнтов ΠΌΠΎΠ΄Π΅Π»ΠΈ:

public static ICollection<ElementId> DeleteSelectedElements()
{
    var transaction = new Transaction(Document);
    transaction.Start("Delete elements");
    
    var selectedIds = UiDocument.Selection.GetElementIds();
    var deletedIds = Document.Delete(selectedIds);

    transaction.Commit();
    return deletedIds;
}

Π’Π°ΠΊΠΆΠ΅ ΠΎΠ±Π½ΠΎΠ²ΠΈΠΌ ΠΌΠ΅Ρ‚ΠΎΠ΄ ListenAndDispatchConnectionsCoreAsync для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ входящих соСдинСний:

private async Task ListenAndDispatchConnectionsCoreAsync()
{
    while (_server.IsConnected)
    {
        try
        {
            var request = await Request.ReadAsync(_server);
            if (request.Type == Request.RequestType.DeleteElements)
            {
                await ProcessDeleteElementsAsync();
            }
        }
        catch (EndOfStreamException)
        {
            return; //Pipe disconnected
        }
    }
}

private async Task ProcessDeleteElementsAsync()
{
    try
    {
        var deletedIds = await Application.AsyncEventHandler.RaiseAsync(_ => RevitApi.DeleteSelectedElements());
        await WriteResponseAsync(new DeletionCompletedResponse(deletedIds.Count));
    }
    catch (Exception exception)
    {
        await WriteResponseAsync(new RejectedResponse(exception.Message));
    }
}

И, Π½Π°ΠΊΠΎΠ½Π΅Ρ†, ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½Π½Ρ‹ΠΉ ΠΊΠΎΠ΄ ViewModel:

[RelayCommand]
private async Task DeleteElementsAsync()
{
    var request = new DeleteElementsRequest();
    await App.ClientDispatcher.WriteRequestAsync(request);

    var response = await App.ClientDispatcher.ReadResponseAsync();
    if (response.Type == Response.ResponseType.Success)
    {
        var completedResponse = (DeletionCompletedResponse) response;
        MessageBox.Show($"{completedResponse.Changes} elements successfully deleted");
    }
    else if (response.Type == Response.ResponseType.Rejected)
    {
        var rejectedResponse = (RejectedResponse) response;
        MessageBox.Show($"Deletion failed\n{rejectedResponse.Reason}");
    }
}

Установка .NET Runtime Π²ΠΎ врСмя установки ΠΏΠ»Π°Π³ΠΈΠ½Π°

НС Ρƒ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ установлСна послСдняя вСрсия .NET Runtime Π½Π° локальной машинС, Π½Π°ΠΌ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ внСсти измСнСния Π² установщик ΠΏΠ»Π°Π³ΠΈΠ½Π°.

Если Π²Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚Π΅ ΡˆΠ°Π±Π»ΠΎΠ½Ρ‹ Nice3point.RevitTemplates, Ρ‚ΠΎ внСсти измСнСния Π½Π΅ составит Ρ‚Ρ€ΡƒΠ΄Π°. Π’ ΡˆΠ°Π±Π»ΠΎΠ½Π°Ρ… ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° WixSharp, которая позволяСт ΡΠΎΠ·Π΄Π°Π²Π°Ρ‚ΡŒ .msi Ρ„Π°ΠΉΠ»Ρ‹ прямо Π½Π° C#.

Для добавлСния ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΡ… дСйствий, ΠΈ установки .NET Runtime создадим CustomAction

public static class RuntimeActions
{
    /// <summary>
    ///     Add-in client .NET version
    /// </summary>
    private const string DotnetRuntimeVersion = "7";

    /// <summary>
    ///     Direct download link
    /// </summary>
    private const string DotnetRuntimeUrl = $"https://aka.ms/dotnet/{DotnetRuntimeVersion}.0/windowsdesktop-runtime-win-x64.exe";

    /// <summary>
    ///     Installing the .NET runtime after installing software
    /// </summary>
    [CustomAction]
    public static ActionResult InstallDotnet(Session session)
    {
        try
        {
            var isRuntimeInstalled = CheckDotnetInstallation();
            if (isRuntimeInstalled) return ActionResult.Success;

            var destinationPath = Path.Combine(Path.GetTempPath(), "windowsdesktop-runtime-win-x64.exe");

            UpdateStatus(session, "Downloading .NET runtime");
            DownloadRuntime(destinationPath);

            UpdateStatus(session, "Installing .NET runtime");
            var status = InstallRuntime(destinationPath);

            var result = status switch
            {
                0 => ActionResult.Success,
                1602 => ActionResult.UserExit,
                1618 => ActionResult.Success,
                _ => ActionResult.Failure
            };

            File.Delete(destinationPath);
            return result;
        }
        catch (Exception exception)
        {
            session.Log("Error downloading and installing DotNet: " + exception.Message);
            return ActionResult.Failure;
        }
    }

    private static int InstallRuntime(string destinationPath)
    {
        var startInfo = new ProcessStartInfo(destinationPath)
        {
            Arguments = "/q",
            UseShellExecute = false
        };

        var installProcess = Process.Start(startInfo)!;
        installProcess.WaitForExit();
        return installProcess.ExitCode;
    }

    private static void DownloadRuntime(string destinationPath)
    {
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;

        using var httpClient = new HttpClient();
        var responseBytes = httpClient.GetByteArrayAsync(DotnetRuntimeUrl).Result;

        File.WriteAllBytes(destinationPath, responseBytes);
    }

    private static bool CheckDotnetInstallation()
    {
        var startInfo = new ProcessStartInfo
        {
            FileName = "dotnet",
            Arguments = "--list-runtimes",
            RedirectStandardOutput = true,
            UseShellExecute = false,
            CreateNoWindow = true
        };

        try
        {
            var process = Process.Start(startInfo)!;
            var output = process.StandardOutput.ReadToEnd();
            process.WaitForExit();

            return output.Split('\n')
                .Where(line => line.Contains("Microsoft.WindowsDesktop.App"))
                .Any(line => line.Contains($"{DotnetRuntimeVersion}."));
        }
        catch
        {
            return false;
        }
    }

    private static void UpdateStatus(Session session, string message)
    {
        var record = new Record(3);
        record[2] = message;

        session.Message(InstallMessage.ActionStart, record);
    }
}

Π­Ρ‚ΠΎΡ‚ ΠΊΠΎΠ΄ провСряСт, установлСна Π»ΠΈ трСбуСмая вСрсия .NET Π½Π° локальной машинС, ΠΈ Ссли Π½Π΅Ρ‚, Ρ‚ΠΎ скачиваСт ΠΈ устанавливаСт Π΅Π΅. Π’ΠΎ врСмя установки обновляСтся Status Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π³ΠΎ Ρ…ΠΎΠ΄Π° выполнСния скачивания ΠΈ распаковки Runtime.

ΠžΡΡ‚Π°Π»ΠΎΡΡŒ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ CustomAction Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ WixSharp, для этого ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ свойство Actions:

var project = new Project
{
    Name = "Wix Installer",
    UI = WUI.WixUI_FeatureTree,
    GUID = new Guid("8F2926C8-3C6C-4D12-9E3C-7DF611CD6DDF"),
    Actions = new Action[]
    {
        new ManagedAction(RuntimeActions.InstallDotnet, 
            Return.check,
            When.Before,
            Step.InstallFinalize,
            Condition.NOT_Installed)
    }
};

Π—Π°ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅

Π’ Π΄Π°Π½Π½ΠΎΠΉ ΡΡ‚Π°Ρ‚ΡŒΠ΅ ΠΌΡ‹ рассмотрСли ΠΊΠ°ΠΊ Named Pipes, прСимущСствСнно ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ для взаимодСйствия ΠΌΠ΅ΠΆΠ΄Ρƒ Ρ€Π°Π·Π½Ρ‹ΠΌΠΈ процСссами, ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Π½Ρ‹ Π² сцСнариях, Π³Π΄Π΅ трСбуСтся ΠΎΠ±ΠΌΠ΅Π½ Π΄Π°Π½Π½Ρ‹ΠΌΠΈ ΠΌΠ΅ΠΆΠ΄Ρƒ прилоТСниями Π½Π° Ρ€Π°Π·Π½Ρ‹Ρ… вСрсиях .NET. ИмСя Π΄Π΅Π»ΠΎ с ΠΊΠΎΠ΄ΠΎΠΌ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Ρ‚ΡŒ Π² Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… вСрсиях, вывСрСнная стратСгия мСТпроцСссного взаимодСйствия (Inter-Process Communication, IPC) ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ ΠΏΠΎΠ»Π΅Π·Π½ΠΎΠΉ ΠΈ ΠΎΠ±Π΅ΡΠΏΠ΅Ρ‡ΠΈΠ²Π°Ρ‚ΡŒ ΠΊΠ»ΡŽΡ‡Π΅Π²Ρ‹Π΅ прСимущСства, Ρ‚Π°ΠΊΠΈΠ΅ ΠΊΠ°ΠΊ:

  • РСшСниС ΠΊΠΎΠ½Ρ„Π»ΠΈΠΊΡ‚ΠΎΠ² зависимостСй
  • Π£Π»ΡƒΡ‡ΡˆΠ΅Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ
  • Π€ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½Π°Ρ Π³ΠΈΠ±ΠΊΠΎΡΡ‚ΡŒ

ΠœΡ‹ обсудили процСсс создания сСрвСра ΠΈ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡ‚Π²ΡƒΡŽΡ‚ Π΄Ρ€ΡƒΠ³ с Π΄Ρ€ΡƒΠ³ΠΎΠΌ Ρ‡Π΅Ρ€Π΅Π· Π·Π°Ρ€Π°Π½Π΅Π΅ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½Ρ‹ΠΉ ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ», Π° Ρ‚Π°ΠΊΠΆΠ΅ Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Π΅ способы управлСния соСдинСниями.

РассмотрСли ΠΏΡ€ΠΈΠΌΠ΅Ρ€ ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ² сСрвСра ΠΈ Π΄Π΅ΠΌΠΎΠ½ΡΡ‚Ρ€Π°Ρ†ΠΈΡŽ Ρ€Π°Π±ΠΎΡ‚Ρ‹ ΠΎΠ±Π΅ΠΈΡ… сторон взаимодСйствия.

НаконСц, ΠΌΡ‹ ΠΏΠΎΠ΄Ρ‡Π΅Ρ€ΠΊΠ½ΡƒΠ»ΠΈ ΠΊΠ°ΠΊ Named Pipes ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ΡΡ Π² Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠ΅ ΠΏΠ»Π°Π³ΠΈΠ½Π° для Revit для обСспСчСния взаимодСйствия ΠΌΠ΅ΠΆΠ΄Ρƒ Π±Π΅ΠΊΠ΅Π½Π΄ΠΎΠΌ, Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‰ΠΈΠΌ Π½Π° ΡƒΡΡ‚Π°Ρ€Π΅Π²ΡˆΠ΅ΠΉ ΠΏΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌΠ΅ .NET 4.8, ΠΈ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠΌ интСрфСйсом, Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‰ΠΈΠΌ Π½Π° Π±ΠΎΠ»Π΅Π΅ Π½ΠΎΠ²ΠΎΠΉ вСрсии .NET 7.

ДСмонстрационный ΠΊΠΎΠ΄ для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ части этой ΡΡ‚Π°Ρ‚ΡŒΠΈ доступСн Π½Π° GitHub.

Π’ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½Ρ‹Ρ… случаях Ρ€Π°Π·Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ Π½Π° ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Π΅ процСссы ΠΌΠΎΠΆΠ΅Ρ‚ Π½Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΡƒΠΌΠ΅Π½ΡŒΡˆΠΈΡ‚ΡŒ зависимости Π² ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ΅, Π½ΠΎ ΠΈ ΡƒΡΠΊΠΎΡ€ΠΈΡ‚ΡŒ Π΅Π³ΠΎ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅. Но Π΄Π°Π²Π°ΠΉΡ‚Π΅ Π½Π΅ Π·Π°Π±Ρ‹Π²Π°Ρ‚ΡŒ, Ρ‡Ρ‚ΠΎ Π²Ρ‹Π±ΠΎΡ€ ΠΏΠΎΠ΄Ρ…ΠΎΠ΄Π° Ρ‚Ρ€Π΅Π±ΡƒΠ΅Ρ‚ Π°Π½Π°Π»ΠΈΠ·Π° ΠΈ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΎΡΠ½ΠΎΠ²Ρ‹Π²Π°Ρ‚ΡŒΡΡ Π½Π° Ρ€Π΅Π°Π»ΡŒΠ½Ρ‹Ρ… трСбованиях ΠΈ ограничСниях вашСго ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°.

ΠœΡ‹ надССмся, Ρ‡Ρ‚ΠΎ эта ΡΡ‚Π°Ρ‚ΡŒΡ ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ‚ Π²Π°ΠΌ Π½Π°ΠΉΡ‚ΠΈ ΠΎΠΏΡ‚ΠΈΠΌΠ°Π»ΡŒΠ½ΠΎΠ΅ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ для Π²Π°ΡˆΠΈΡ… сцСнариСв мСТпроцСссного взаимодСйствия ΠΈ даст ΠΏΠΎΠ½ΠΈΠΌΠ°Π½ΠΈΠ΅, ΠΊΠ°ΠΊ ΠΏΡ€ΠΈΠΌΠ΅Π½ΡΡ‚ΡŒ ΠΏΠΎΠ΄Ρ…ΠΎΠ΄Ρ‹ IPC Π½Π° ΠΏΡ€Π°ΠΊΡ‚ΠΈΠΊΠ΅.

About

Fork to make library

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages