├── .editorconfig
├── Icon_64x64.png
├── Icon_Working.pdn
├── TestCore
├── ICrasher.cs
├── TestCore.csproj
├── PipeSide.cs
├── IConcatenator.cs
├── PipeSerializerType.cs
├── Crasher.cs
├── Scenario.cs
├── Concatenator.cs
├── WrappedInt.cs
├── IAdder.cs
└── Adder.cs
├── PipeMethodCalls
├── Models
│ ├── IMessage.cs
│ ├── MessageType.cs
│ ├── PendingCall.cs
│ ├── PipeState.cs
│ ├── SerializedPipeRequest.cs
│ ├── TypedPipeRequest.cs
│ ├── SerializedPipeResponse.cs
│ └── TypedPipeResponse.cs
├── RequestHandler
│ ├── IRequestHandler.cs
│ └── RequestHandler.cs
├── Invoker
│ ├── IResponseHandler.cs
│ ├── IPipeInvokerHost.cs
│ ├── ArgumentResolver.cs
│ ├── IPipeInvoker.cs
│ └── MethodInvoker.cs
├── Endpoints
│ ├── IPipeServerWithCallback.cs
│ ├── IPipeServer.cs
│ ├── IPipeClient.cs
│ ├── PipeServer.cs
│ ├── PipeServerWithCallback.cs
│ ├── PipeClient.cs
│ └── PipeClientWithCallback.cs
├── Extensions
│ ├── ActionExtensionMethods.cs
│ ├── StreamExtensions.cs
│ └── PipeInvokerHostExtensions.cs
├── Serialization
│ └── IPipeSerializer.cs
├── Exceptions
│ └── PipeInvokeFailedException.cs
├── PipeMethodCalls.csproj
├── PipeMessageProcessor.cs
├── Utilities.cs
└── PipeStreamWrapper.cs
├── PerformanceTestRunner
├── PerformanceTestRunner.csproj
└── Program.cs
├── TestScenarioRunner
├── TestScenarioRunner.csproj
├── ServerCrashScenario.cs
├── NoCallbackScenario.cs
├── PerformanceScenario.cs
├── WithCallbackScenario.cs
└── Program.cs
├── PipeMethodCalls.NetJson
├── NetJsonPipeSerializer.cs
└── PipeMethodCalls.NetJson.csproj
├── PipeMethodCalls.Tests
├── PipeMethodCalls.Tests.csproj
└── EndToEndTests.cs
├── PipeMethodCalls.MessagePack
├── MessagePackPipeSerializer.cs
└── PipeMethodCalls.MessagePack.csproj
├── LICENSE
├── README.md
├── PipeMethodCalls.sln
└── .gitignore
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.{cs,xaml}]
4 | indent_style = tab
5 | tab_width = 4
--------------------------------------------------------------------------------
/Icon_64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RandomEngy/PipeMethodCalls/HEAD/Icon_64x64.png
--------------------------------------------------------------------------------
/Icon_Working.pdn:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RandomEngy/PipeMethodCalls/HEAD/Icon_Working.pdn
--------------------------------------------------------------------------------
/TestCore/ICrasher.cs:
--------------------------------------------------------------------------------
1 | namespace TestCore
2 | {
3 | public interface ICrasher
4 | {
5 | void Crash();
6 | }
7 | }
--------------------------------------------------------------------------------
/TestCore/TestCore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TestCore/PipeSide.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace TestCore
6 | {
7 | public enum PipeSide
8 | {
9 | Client,
10 | Server
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/TestCore/IConcatenator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace TestCore
6 | {
7 | public interface IConcatenator
8 | {
9 | string Concatenate(string a, string b);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/TestCore/PipeSerializerType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace TestCore
6 | {
7 | public enum PipeSerializerType
8 | {
9 | NetJson,
10 | MessagePack
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/TestCore/Crasher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace TestCore
6 | {
7 | public class Crasher : ICrasher
8 | {
9 | public void Crash()
10 | {
11 | Environment.Exit(1);
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/TestCore/Scenario.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace TestCore
6 | {
7 | public enum Scenario
8 | {
9 | WithCallback,
10 | NoCallback,
11 | Performance,
12 | ServerCrash,
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/TestCore/Concatenator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace TestCore
6 | {
7 | public class Concatenator : IConcatenator
8 | {
9 | public string Concatenate(string a, string b)
10 | {
11 | return a + b;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Models/IMessage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace PipeMethodCalls
6 | {
7 | internal interface IMessage
8 | {
9 | ///
10 | /// The call ID.
11 | ///
12 | int CallId { get; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Models/MessageType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace PipeMethodCalls
6 | {
7 | ///
8 | /// The pipe message type.
9 | ///
10 | internal enum MessageType
11 | {
12 | Request = 0,
13 | Response = 1,
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/TestCore/WrappedInt.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.Serialization;
4 | using System.Text;
5 |
6 | namespace TestCore
7 | {
8 | [DataContract]
9 | public class WrappedInt
10 | {
11 | [DataMember(Order = 0)]
12 | public int Num { get; set; }
13 |
14 | public override string ToString()
15 | {
16 | return "Wrapped " + this.Num;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/PerformanceTestRunner/PerformanceTestRunner.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/PipeMethodCalls/RequestHandler/IRequestHandler.cs:
--------------------------------------------------------------------------------
1 | namespace PipeMethodCalls
2 | {
3 | ///
4 | /// Handles a request message received from a remote endpoint.
5 | ///
6 | internal interface IRequestHandler
7 | {
8 | ///
9 | /// Handles a request message received from a remote endpoint.
10 | ///
11 | /// The request message.
12 | void HandleRequest(SerializedPipeRequest request);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Invoker/IResponseHandler.cs:
--------------------------------------------------------------------------------
1 | namespace PipeMethodCalls
2 | {
3 | ///
4 | /// Handles a response message received from a remote endpoint.
5 | ///
6 | internal interface IResponseHandler
7 | {
8 | ///
9 | /// Handles a response message received from a remote endpoint.
10 | ///
11 | /// The response message to handle.
12 | void HandleResponse(SerializedPipeResponse response);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Invoker/IPipeInvokerHost.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace PipeMethodCalls
6 | {
7 | ///
8 | /// Holds an Invoker property.
9 | ///
10 | /// The type to make method calls for.
11 | public interface IPipeInvokerHost
12 | where TRequesting : class
13 | {
14 | ///
15 | /// Get the invoker.
16 | ///
17 | IPipeInvoker Invoker { get; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Endpoints/IPipeServerWithCallback.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq.Expressions;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace PipeMethodCalls
7 | {
8 | ///
9 | /// A named pipe server with a callback channel.
10 | ///
11 | /// The callback channel interface that the client will be handling.
12 | public interface IPipeServerWithCallback : IPipeServer, IPipeInvokerHost
13 | where TRequesting : class
14 | {
15 | }
16 | }
--------------------------------------------------------------------------------
/PipeMethodCalls/Models/PendingCall.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace PipeMethodCalls
7 | {
8 | ///
9 | /// Represents a pending call executing on the remote endpoint.
10 | ///
11 | internal class PendingCall
12 | {
13 | ///
14 | /// The task completion source for the call.
15 | ///
16 | public TaskCompletionSource TaskCompletionSource { get; } = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/TestCore/IAdder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace TestCore
7 | {
8 | public interface IAdder
9 | {
10 | int AddNumbers(int a, int b);
11 | double Sum(double[] numbers);
12 |
13 | WrappedInt AddWrappedNumbers(WrappedInt a, WrappedInt b);
14 |
15 | Task AddAsync(int a, int b);
16 |
17 | IList Listify(T item);
18 |
19 | int Unwrap(WrappedInt a);
20 |
21 | void DoesNothing();
22 |
23 | Task DoesNothingAsync();
24 |
25 | void AlwaysFails();
26 |
27 | void HasRefParam(ref int refParam);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Extensions/ActionExtensionMethods.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace PipeMethodCalls
6 | {
7 | ///
8 | /// Extension methods for actions.
9 | ///
10 | internal static class ActionExtensionMethods
11 | {
12 | ///
13 | /// Invokes the given action as a logger.
14 | ///
15 | /// The action to invoke as a logger.
16 | /// The function to produce the string to log.
17 | public static void Log(this Action logger, Func messageFunc)
18 | {
19 | logger?.Invoke(messageFunc());
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Models/PipeState.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace PipeMethodCalls
6 | {
7 | ///
8 | /// Represents the state of the pipe connection.
9 | ///
10 | public enum PipeState
11 | {
12 | ///
13 | /// The pipe has not yet connected.
14 | ///
15 | NotOpened,
16 |
17 | ///
18 | /// The pipe is connected, methods can be invoked and we are listening for message from the other side of the pipe.
19 | ///
20 | Connected,
21 |
22 | ///
23 | /// The pipe has closed gracefully.
24 | ///
25 | Closed,
26 |
27 | ///
28 | /// An unexpected error caused the pipe to close.
29 | ///
30 | Faulted
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/TestScenarioRunner/TestScenarioRunner.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/PipeMethodCalls.NetJson/NetJsonPipeSerializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Text.Json;
6 |
7 | namespace PipeMethodCalls.NetJson
8 | {
9 | ///
10 | /// Serializes pipe method call information with System.Text.Json.
11 | ///
12 | public class NetJsonPipeSerializer : IPipeSerializer
13 | {
14 | public object Deserialize(byte[] data, Type type)
15 | {
16 | return JsonSerializer.Deserialize(data, type);
17 | }
18 |
19 | public byte[] Serialize(object o)
20 | {
21 | using (var memoryStream = new MemoryStream())
22 | using (var utf8JsonWriter = new Utf8JsonWriter(memoryStream))
23 | {
24 | JsonSerializer.Serialize(utf8JsonWriter, o);
25 | return memoryStream.ToArray();
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/PipeMethodCalls.Tests/PipeMethodCalls.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 |
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/PipeMethodCalls.MessagePack/MessagePackPipeSerializer.cs:
--------------------------------------------------------------------------------
1 | using MessagePack;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace PipeMethodCalls.MessagePack
7 | {
8 | ///
9 | /// Serializes pipe method call information with MessagePack.
10 | ///
11 | public class MessagePackPipeSerializer : IPipeSerializer
12 | {
13 | public object Deserialize(byte[] data, Type type)
14 | {
15 | if (data.Length == 0)
16 | {
17 | return null;
18 | }
19 |
20 |
21 | return MessagePackSerializer.Deserialize(type, data);
22 | }
23 |
24 | public byte[] Serialize(object o)
25 | {
26 | if (o == null || o.GetType().FullName == "System.Threading.Tasks.VoidTaskResult")
27 | {
28 | return Array.Empty();
29 | }
30 |
31 | return MessagePackSerializer.Serialize(o.GetType(), o);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/TestScenarioRunner/ServerCrashScenario.cs:
--------------------------------------------------------------------------------
1 | using PipeMethodCalls;
2 | using Shouldly;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using TestCore;
9 |
10 | namespace TestScenarioRunner
11 | {
12 | public static class ServerCrashScenario
13 | {
14 | public static async Task RunClientAsync(PipeClient pipeClient)
15 | {
16 | await pipeClient.ConnectAsync().ConfigureAwait(false);
17 | await Should.ThrowAsync(async () =>
18 | {
19 | await pipeClient.InvokeAsync(crasher => crasher.Crash()).ConfigureAwait(false);
20 | });
21 |
22 | pipeClient.Dispose();
23 | }
24 |
25 | public static async Task RunServerAsync(PipeServer pipeServer)
26 | {
27 | await pipeServer.WaitForConnectionAsync();
28 | await pipeServer.WaitForRemotePipeCloseAsync();
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Serialization/IPipeSerializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace PipeMethodCalls
6 | {
7 | ///
8 | /// Defines how to serialize and deserialize objects to pass through the pipe.
9 | ///
10 | public interface IPipeSerializer
11 | {
12 | ///
13 | /// Serializes the given object into bytes.
14 | ///
15 | /// The object to serialize.
16 | /// The bytes to represent the serialized object.
17 | byte[] Serialize(object o);
18 |
19 | ///
20 | /// Deserializes the given bytes into an object.
21 | ///
22 | /// The data to deserialize.
23 | /// The type to deserialize to.
24 | /// The deserialized object.
25 | object Deserialize(byte[] data, Type type);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/TestScenarioRunner/NoCallbackScenario.cs:
--------------------------------------------------------------------------------
1 | using PipeMethodCalls;
2 | using Shouldly;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using TestCore;
9 |
10 | namespace TestScenarioRunner
11 | {
12 | public static class NoCallbackScenario
13 | {
14 | public static async Task RunClientAsync(PipeClient pipeClient)
15 | {
16 | await pipeClient.ConnectAsync().ConfigureAwait(false);
17 | WrappedInt result = await pipeClient.InvokeAsync(adder => adder.AddWrappedNumbers(new WrappedInt { Num = 1 }, new WrappedInt { Num = 3 })).ConfigureAwait(false);
18 | result.Num.ShouldBe(4);
19 |
20 | pipeClient.Dispose();
21 | }
22 |
23 | public static async Task RunServerAsync(PipeServer pipeServer)
24 | {
25 | await pipeServer.WaitForConnectionAsync();
26 | await pipeServer.WaitForRemotePipeCloseAsync();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/PerformanceTestRunner/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using TestCore;
3 |
4 | ProcessStartInfo serverInfo = new ProcessStartInfo("TestScenarioRunner", $"--scenario {Scenario.Performance} --side {PipeSide.Server} --type {PipeSerializerType.MessagePack}");
5 | Process serverProcess = Process.Start(serverInfo);
6 |
7 | var stopwatch = new Stopwatch();
8 | stopwatch.Start();
9 |
10 | ProcessStartInfo clientInfo = new ProcessStartInfo("TestScenarioRunner", $"--scenario {Scenario.Performance} --side {PipeSide.Client} --type {PipeSerializerType.MessagePack}");
11 | Process clientProcess = Process.Start(clientInfo);
12 |
13 | clientProcess.WaitForExit();
14 | stopwatch.Stop();
15 |
16 | var standardError = new StreamWriter(Console.OpenStandardError());
17 | standardError.AutoFlush = true;
18 |
19 | serverProcess.WaitForExit();
20 |
21 | //Console.SetError(standardError);
22 |
23 | Console.WriteLine();
24 | Console.WriteLine($@"Time elapsed: {stopwatch.Elapsed.TotalMilliseconds} ms");
25 | Console.ReadLine();
26 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Endpoints/IPipeServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace PipeMethodCalls
6 | {
7 | ///
8 | /// A named pipe server.
9 | ///
10 | public interface IPipeServer : IDisposable
11 | {
12 | ///
13 | /// Sets up the given action as a logger for the module.
14 | ///
15 | /// The logger action.
16 | void SetLogger(Action logger);
17 |
18 | ///
19 | /// Waits for a client to connect to the pipe.
20 | ///
21 | /// A token to cancel the request.
22 | Task WaitForConnectionAsync(CancellationToken cancellationToken = default);
23 |
24 | ///
25 | /// Waits for the client to close the pipe.
26 | ///
27 | /// A token to cancel the request.
28 | Task WaitForRemotePipeCloseAsync(CancellationToken cancellationToken = default);
29 | }
30 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 David Rickard
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Exceptions/PipeInvokeFailedException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Runtime.Serialization;
5 | using System.Text;
6 |
7 | namespace PipeMethodCalls
8 | {
9 | ///
10 | /// Represents when an invoke was successfully executed on the remote endpoint, but the method threw an exception.
11 | ///
12 | public class PipeInvokeFailedException : IOException
13 | {
14 | ///
15 | /// Initializes a new instance of the class.
16 | ///
17 | /// The exception message.
18 | public PipeInvokeFailedException(string message)
19 | : base(message)
20 | {
21 | }
22 |
23 | ///
24 | /// Initializes a new instance of the class.
25 | ///
26 | /// The exception message.
27 | /// The inner exception.
28 | public PipeInvokeFailedException(string message, Exception innerException)
29 | : base(message, innerException)
30 | {
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Endpoints/IPipeClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq.Expressions;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace PipeMethodCalls
7 | {
8 | ///
9 | /// A named pipe client.
10 | ///
11 | /// The interface that the client will be invoking on the server.
12 | public interface IPipeClient : IPipeInvokerHost
13 | where TRequesting : class
14 | {
15 | ///
16 | /// Connects to the server.
17 | ///
18 | /// A token to cancel the request.
19 | Task ConnectAsync(CancellationToken cancellationToken = default);
20 |
21 | ///
22 | /// Sets up the given action as a logger for the module.
23 | ///
24 | /// The logger action.
25 | void SetLogger(Action logger);
26 |
27 | ///
28 | /// Waits for the server to close the pipe.
29 | ///
30 | /// A token to cancel the request.
31 | Task WaitForRemotePipeCloseAsync(CancellationToken cancellationToken = default);
32 | }
33 | }
--------------------------------------------------------------------------------
/PipeMethodCalls/PipeMethodCalls.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 | true
5 | David Rickard
6 | LICENSE
7 | Lightweight .NET Standard 2.0 library for method calls over named pipes for IPC. Supports two-way communication with callbacks.
8 | https://github.com/RandomEngy/PipeMethodCalls
9 | https://github.com/RandomEngy/PipeMethodCalls
10 | 4.0.2
11 | Icon_64x64.png
12 | README.md
13 | Fixed an issue with hung calls when pipe server disconnects. Pending calls now throw an IOException when the pipe closes in the middle of the call.
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/PipeMethodCalls.MessagePack/PipeMethodCalls.MessagePack.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | true
6 | David Rickard
7 | LICENSE
8 | MessagePack serializer for PipeMethodCalls. Uses the MessagePack-CSharp library.
9 | https://github.com/RandomEngy/PipeMethodCalls.NetJson
10 | https://github.com/RandomEngy/PipeMethodCalls.NetJson
11 | 3.0.2
12 | Icon_64x64.png
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | True
26 |
27 |
28 |
29 | True
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/PipeMethodCalls.NetJson/PipeMethodCalls.NetJson.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | true
6 | David Rickard
7 | LICENSE
8 | System.Text.Json serializer for PipeMethodCalls.
9 | https://github.com/RandomEngy/PipeMethodCalls.NetJson
10 | https://github.com/RandomEngy/PipeMethodCalls.NetJson
11 | 3.0.0
12 | Icon_64x64.png
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | True
26 |
27 |
28 |
29 | True
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/TestCore/Adder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TestCore
8 | {
9 | public class Adder : IAdder
10 | {
11 | public int AddNumbers(int a, int b)
12 | {
13 | return a + b;
14 | }
15 |
16 | public double Sum(double[] numbers)
17 | {
18 | return numbers.Sum();
19 | }
20 |
21 | public WrappedInt AddWrappedNumbers(WrappedInt a, WrappedInt b)
22 | {
23 | return new WrappedInt { Num = a.Num + b.Num };
24 | }
25 |
26 | public Task AddAsync(int a, int b)
27 | {
28 | return Task.FromResult(a + b);
29 | }
30 |
31 | public IList Listify(T item)
32 | {
33 | return new List { item };
34 | }
35 |
36 | public int Unwrap(WrappedInt a)
37 | {
38 | if (a == null)
39 | {
40 | return 0;
41 | }
42 |
43 | return a.Num;
44 | }
45 |
46 | public void DoesNothing()
47 | {
48 | }
49 |
50 | public Task DoesNothingAsync()
51 | {
52 | return Task.CompletedTask;
53 | }
54 |
55 | public void AlwaysFails()
56 | {
57 | throw new InvalidOperationException("This method always fails.");
58 | }
59 |
60 | public void HasRefParam(ref int refParam)
61 | {
62 | refParam = 5;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/TestScenarioRunner/PerformanceScenario.cs:
--------------------------------------------------------------------------------
1 | using PipeMethodCalls;
2 | using Shouldly;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using TestCore;
9 |
10 | namespace TestScenarioRunner
11 | {
12 | public static class PerformanceScenario
13 | {
14 | public static async Task RunClientAsync(PipeClient pipeClient)
15 | {
16 | await pipeClient.ConnectAsync().ConfigureAwait(false);
17 |
18 | double[] numbers = new[] {
19 | 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0,
20 | 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0,
21 | 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0,
22 | 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 };
23 |
24 | var tasks = new List>();
25 |
26 | for (int i = 0; i < 100_000; i++)
27 | {
28 | var task = pipeClient.Invoker.InvokeAsync(i => i.Sum(numbers));
29 | tasks.Add(task);
30 | }
31 |
32 | foreach (var task in tasks)
33 | {
34 | var result = await task;
35 | result.ShouldBe(45.0 * 4);
36 | }
37 |
38 | pipeClient.Dispose();
39 | }
40 |
41 | public static async Task RunServerAsync(PipeServer pipeServer)
42 | {
43 | await pipeServer.WaitForConnectionAsync();
44 |
45 | await pipeServer.WaitForRemotePipeCloseAsync();
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Models/SerializedPipeRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace PipeMethodCalls
7 | {
8 | ///
9 | /// A request sent over the pipe that has been user serialized by an .
10 | ///
11 | ///
12 | /// There are two stages to serialization:
13 | /// 1: User serialization, where typed values are serialized to a byte array.
14 | /// 2: Message serialization, where the message is sent with a custom binary serialization over the pipe.
15 | ///
16 | internal class SerializedPipeRequest : IMessage
17 | {
18 | ///
19 | /// The call ID.
20 | ///
21 | public int CallId { get; set; }
22 |
23 | ///
24 | /// The name of the method to invoke.
25 | ///
26 | public string MethodName { get; set; }
27 |
28 | ///
29 | /// The list of parameters to pass to the method.
30 | ///
31 | ///
32 | /// The method invoker will know what type each parameter needs to be converted to, and with that type the serializer will be able to convert it to and from a byte array.
33 | ///
34 | public byte[][] Parameters { get; set; }
35 |
36 | ///
37 | /// The types for the generic arguments.
38 | ///
39 | public Type[] GenericArguments { get; set; }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Invoker/ArgumentResolver.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq.Expressions;
3 | using System.Reflection;
4 | using System;
5 | using System.Linq;
6 |
7 | namespace PipeMethodCalls
8 | {
9 | public static class ArgumentResolver
10 | {
11 | public static object GetValue(Expression expression)
12 | {
13 | switch (expression)
14 | {
15 | case null:
16 | return null;
17 | case ConstantExpression constantExpression:
18 | return constantExpression.Value;
19 | case MemberExpression memberExpression:
20 | return GetValue(memberExpression);
21 | case MethodCallExpression methodCallExpression:
22 | return GetValue(methodCallExpression);
23 | }
24 |
25 | var lambdaExpression = Expression.Lambda(expression);
26 | var @delegate = lambdaExpression.Compile();
27 | return @delegate.DynamicInvoke();
28 | }
29 |
30 | private static object GetValue(MemberExpression memberExpression)
31 | {
32 | var value = GetValue(memberExpression.Expression);
33 |
34 | var member = memberExpression.Member;
35 | switch (member)
36 | {
37 | case FieldInfo fieldInfo:
38 | return fieldInfo.GetValue(value);
39 | case PropertyInfo propertyInfo:
40 | try
41 | {
42 | return propertyInfo.GetValue(value);
43 | }
44 | catch (TargetInvocationException e)
45 | {
46 | throw e.InnerException;
47 | }
48 | default:
49 | throw new Exception("Unknown member type: " + member.GetType());
50 | }
51 | }
52 |
53 | private static object GetValue(MethodCallExpression methodCallExpression)
54 | {
55 | var paras = GetArray(methodCallExpression.Arguments);
56 | var obj = GetValue(methodCallExpression.Object);
57 |
58 | try
59 | {
60 | return methodCallExpression.Method.Invoke(obj, paras);
61 | }
62 | catch (TargetInvocationException e)
63 | {
64 | throw e.InnerException;
65 | }
66 | }
67 |
68 | private static object[] GetArray(IEnumerable expressions)
69 | {
70 | return expressions.Select(GetValue).ToArray();
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/PipeMethodCalls.Tests/EndToEndTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 | using Shouldly;
3 | using System.Diagnostics;
4 | using TestCore;
5 |
6 | namespace PipeMethodCalls.Tests
7 | {
8 | [TestClass]
9 | public class EndToEndTests
10 | {
11 | [TestMethod]
12 | public void WithCallbacks_NetJson()
13 | {
14 | TestScenario(Scenario.WithCallback, PipeSerializerType.NetJson);
15 | }
16 |
17 | [TestMethod]
18 | public void WithCallbacks_MessagePack()
19 | {
20 | TestScenario(Scenario.WithCallback, PipeSerializerType.MessagePack);
21 | }
22 |
23 | [TestMethod]
24 | public void NoCallbacks_NetJson()
25 | {
26 | TestScenario(Scenario.NoCallback, PipeSerializerType.NetJson);
27 | }
28 |
29 | [TestMethod]
30 | public void NoCallbacks_MessagePack()
31 | {
32 | TestScenario(Scenario.NoCallback, PipeSerializerType.MessagePack);
33 | }
34 |
35 | [TestMethod]
36 | public void ServerCrash()
37 | {
38 | Process serverProcess = StartRunnerProcess(Scenario.ServerCrash, PipeSerializerType.NetJson, PipeSide.Server);
39 | Process clientProcess = StartRunnerProcess(Scenario.ServerCrash, PipeSerializerType.NetJson, PipeSide.Client);
40 |
41 | clientProcess.WaitForExit();
42 | serverProcess.WaitForExit();
43 |
44 | clientProcess.ExitCode.ShouldBe(0);
45 | serverProcess.ExitCode.ShouldBe(1);
46 | }
47 |
48 | private static void TestScenario(Scenario scenario, PipeSerializerType pipeSerializerType)
49 | {
50 | Process serverProcess = StartRunnerProcess(scenario, pipeSerializerType, PipeSide.Server);
51 | Process clientProcess = StartRunnerProcess(scenario, pipeSerializerType, PipeSide.Client);
52 |
53 | clientProcess.WaitForExit();
54 | serverProcess.WaitForExit();
55 |
56 | clientProcess.ExitCode.ShouldBe(0);
57 | serverProcess.ExitCode.ShouldBe(0);
58 | }
59 |
60 | private static Process StartRunnerProcess(Scenario scenario, PipeSerializerType pipeSerializerType, PipeSide side)
61 | {
62 | ProcessStartInfo processInfo = new ProcessStartInfo("TestScenarioRunner", $"--scenario {scenario} --side {side} --type {pipeSerializerType}");
63 | return Process.Start(processInfo);
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/PipeMethodCalls/Models/TypedPipeRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace PipeMethodCalls
7 | {
8 | ///
9 | /// A method request to be sent over the pipe. Contains typed parameters.
10 | ///
11 | internal class TypedPipeRequest
12 | {
13 | ///
14 | /// The call ID.
15 | ///
16 | public int CallId { get; set; }
17 |
18 | ///
19 | /// The name of the method to invoke.
20 | ///
21 | public string MethodName { get; set; }
22 |
23 | ///
24 | /// The list of parameters to pass to the method.
25 | ///
26 | public object[] Parameters { get; set; }
27 |
28 | ///
29 | /// The types for the generic arguments.
30 | ///
31 | public Type[] GenericArguments { get; set; }
32 |
33 | ///
34 | /// Gets a string representation of this typed request.
35 | ///
36 | /// The string representation of this typed request.
37 | public override string ToString()
38 | {
39 | var builder = new StringBuilder("Request");
40 | builder.AppendLine();
41 | builder.Append(" CallId: ");
42 | builder.Append(this.CallId);
43 | builder.AppendLine();
44 | builder.Append(" MethodName: ");
45 | builder.Append(this.MethodName);
46 | if (this.Parameters.Length > 0)
47 | {
48 | builder.AppendLine();
49 | builder.Append(" Parameters:");
50 | foreach (object parameter in this.Parameters)
51 | {
52 | builder.AppendLine();
53 | builder.Append(" ");
54 | builder.Append(parameter?.ToString() ?? "");
55 | }
56 | }
57 |
58 | if (this.GenericArguments.Length > 0)
59 | {
60 | builder.AppendLine();
61 | builder.Append(" Generic arguments:");
62 | foreach (Type genericArgument in this.GenericArguments)
63 | {
64 | builder.AppendLine();
65 | builder.Append(" ");
66 | builder.Append(genericArgument.ToString());
67 | }
68 | }
69 |
70 | return builder.ToString();
71 | }
72 |
73 | ///
74 | /// Serializes the request.
75 | ///
76 | /// The serializer to use.
77 | /// The serialized request.
78 | public SerializedPipeRequest Serialize(IPipeSerializer serializer)
79 | {
80 | return new SerializedPipeRequest
81 | {
82 | CallId = this.CallId,
83 | MethodName = this.MethodName,
84 | Parameters = this.Parameters.Select(r => serializer.Serialize(r)).ToArray(),
85 | GenericArguments = this.GenericArguments
86 | };
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PipeMethodCalls
2 |
3 | Lightweight .NET Standard 2.0 library to use method calls over named and anonymous pipes for IPC. Supports two-way communication with callbacks.
4 |
5 | ### Calls from client to server
6 |
7 | ```csharp
8 | var pipeServer = new PipeServer(
9 | new NetJsonPipeSerializer(),
10 | "mypipe",
11 | () => new Adder());
12 | await pipeServer.WaitForConnectionAsync();
13 | ```
14 |
15 | ```csharp
16 | var pipeClient = new PipeClient(new NetJsonPipeSerializer(), "mypipe");
17 | await pipeClient.ConnectAsync();
18 | int result = await pipeClient.InvokeAsync(adder => adder.AddNumbers(1, 3));
19 | ```
20 |
21 | ### Calls both way
22 |
23 | ```csharp
24 | var pipeServer = new PipeServerWithCallback(
25 | new NetJsonPipeSerializer(),
26 | "mypipe",
27 | () => new Adder());
28 | await pipeServer.WaitForConnectionAsync();
29 | string concatResult = await pipeServer.InvokeAsync(c => c.Concatenate("a", "b"));
30 | ```
31 |
32 | ```csharp
33 | var pipeClient = new PipeClientWithCallback(
34 | new NetJsonPipeSerializer(),
35 | "mypipe",
36 | () => new Concatenator());
37 | await pipeClient.ConnectAsync();
38 | int result = await pipeClient.InvokeAsync(a => a.AddNumbers(4, 7));
39 | ```
40 |
41 | ### About the library
42 | This library uses named pipes to invoke method calls on a remote endpoint. The method arguments are serialized to binary and sent over the pipe.
43 |
44 | ### Serialization
45 | PipeMethodCalls supports customizable serialization logic through `IPipeSerializer`. You've got two options:
46 |
47 | * Use a pre-built serializer
48 | * `new NetJsonPipeSerializer()` from the PipeMethodCalls.NetJson package. That uses the System.Text.Json serializer that's built into .NET.
49 | * `new MessagePackPipeSerializer()` from the PipeMethodCalls.MessagePack package. That uses the [MessagePack-CSharp](https://github.com/neuecc/MessagePack-CSharp) serializer, which has excellent performance.
50 | * Plug in your own implementation of `IPipeSerializer`. Refer to [the NetJsonPipeSerializer code](https://github.com/RandomEngy/PipeMethodCalls/blob/master/PipeMethodCalls.NetJson/NetJsonPipeSerializer.cs) for an example of how to do this.
51 |
52 | Open an issue or pull request if you'd like to see more built-in serializers.
53 |
54 | ### Features
55 | * 100% asynchronous communication with .ConfigureAwait(false) to minimize context switches and reduce thread use
56 | * 45KB with no built-in dependencies
57 | * Invoking async methods
58 | * Passing and returning complex types with pluggable JSON or binary serialization
59 | * Interleaved or multiple simultaneous calls
60 | * Throwing exceptions
61 | * CancellationToken support
62 | * Works on Windows, Linux and MacOS
63 |
64 | ### Not supported
65 | * Methods with out and ref parameters
66 | * Properties
67 | * Method overloads
68 |
69 |
--------------------------------------------------------------------------------
/TestScenarioRunner/WithCallbackScenario.cs:
--------------------------------------------------------------------------------
1 | using PipeMethodCalls;
2 | using Shouldly;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using TestCore;
9 |
10 | namespace TestScenarioRunner
11 | {
12 | public static class WithCallbackScenario
13 | {
14 | public static async Task RunClientAsync(PipeClientWithCallback pipeClientWithCallback)
15 | {
16 | await pipeClientWithCallback.ConnectAsync().ConfigureAwait(false);
17 | WrappedInt result = await pipeClientWithCallback.InvokeAsync(adder => adder.AddWrappedNumbers(new WrappedInt { Num = 1 }, new WrappedInt { Num = 3 })).ConfigureAwait(false);
18 | result.Num.ShouldBe(4);
19 |
20 | int asyncResult = await pipeClientWithCallback.InvokeAsync(adder => adder.AddAsync(4, 7)).ConfigureAwait(false);
21 | asyncResult.ShouldBe(11);
22 |
23 | IList listifyResult = await pipeClientWithCallback.InvokeAsync(adder => adder.Listify("item")).ConfigureAwait(false);
24 | listifyResult.Count.ShouldBe(1);
25 | listifyResult[0].ShouldBe("item");
26 |
27 | int unwrapResult = await pipeClientWithCallback.InvokeAsync(adder => adder.Unwrap(null)).ConfigureAwait(false);
28 | unwrapResult.ShouldBe(0);
29 |
30 | await pipeClientWithCallback.InvokeAsync(adder => adder.DoesNothing()).ConfigureAwait(false);
31 | await pipeClientWithCallback.InvokeAsync(adder => adder.DoesNothingAsync()).ConfigureAwait(false);
32 |
33 | var expectedException = await Should.ThrowAsync(async () =>
34 | {
35 | await pipeClientWithCallback.InvokeAsync(adder => adder.AlwaysFails()).ConfigureAwait(false);
36 | });
37 |
38 | expectedException.Message.ShouldContain("This method always fails");
39 |
40 | var refException = await Should.ThrowAsync(async () =>
41 | {
42 | int refValue = 4;
43 | await pipeClientWithCallback.InvokeAsync(adder => adder.HasRefParam(ref refValue)).ConfigureAwait(false);
44 | });
45 |
46 | refException.Message.ShouldBe("ref parameters are not supported. Method: 'HasRefParam'");
47 | pipeClientWithCallback.Dispose();
48 | }
49 |
50 | public static async Task RunServerAsync(PipeServerWithCallback pipeServerWithCallback)
51 | {
52 | await pipeServerWithCallback.WaitForConnectionAsync().ConfigureAwait(false);
53 |
54 | string concatResult = await pipeServerWithCallback.InvokeAsync(c => c.Concatenate("a", "b")).ConfigureAwait(false);
55 | concatResult.ShouldBe("ab");
56 |
57 | // 100 character string. Concat to make sure that the continuation bit works for varint.
58 | string longString = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
59 |
60 | string longConcatResult = await pipeServerWithCallback.InvokeAsync(c => c.Concatenate(longString, longString)).ConfigureAwait(false);
61 | longConcatResult.ShouldBe(longString + longString);
62 |
63 | await pipeServerWithCallback.WaitForRemotePipeCloseAsync().ConfigureAwait(false);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Models/SerializedPipeResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace PipeMethodCalls
6 | {
7 | ///
8 | /// A response sent over the pipe that has been user serialized with an .
9 | ///
10 | ///
11 | /// There are two stages to serialization:
12 | /// 1: User serialization, where typed values are serialized to a byte array.
13 | /// 2: Message serialization, where the message is sent with a custom binary serialization over the pipe.
14 | ///
15 | internal class SerializedPipeResponse : IMessage
16 | {
17 | ///
18 | /// The call ID.
19 | ///
20 | public int CallId { get; private set; }
21 |
22 | ///
23 | /// True if the call succeeded.
24 | ///
25 | public bool Succeeded { get; private set; }
26 |
27 | ///
28 | /// The response data. Valid if Succeeded is true.
29 | ///
30 | ///
31 | /// The method invoker will know what type this is, and with that type the serializer will be able to convert it to and from a byte array.
32 | ///
33 | public byte[] Data { get; private set; }
34 |
35 | ///
36 | /// The error details. Valid if Succeeded is false.
37 | ///
38 | public string Error { get; private set; }
39 |
40 | public override string ToString()
41 | {
42 | var builder = new StringBuilder("SerializedResponse");
43 | builder.AppendLine();
44 | builder.Append(" CallId: ");
45 | builder.Append(this.CallId);
46 | builder.AppendLine();
47 | builder.Append(" Succeeded: ");
48 | builder.Append(this.Succeeded);
49 | builder.AppendLine();
50 | if (this.Succeeded)
51 | {
52 | builder.Append(" Data: ");
53 | if (this.Data == null)
54 | {
55 | builder.Append("");
56 | }
57 | else
58 | {
59 | builder.Append("0x");
60 | builder.Append(Utilities.BytesToHexString(this.Data));
61 | }
62 | }
63 | else
64 | {
65 | builder.Append(" Error: ");
66 | builder.Append(this.Error);
67 | }
68 |
69 | return builder.ToString();
70 | }
71 |
72 | ///
73 | /// Creates a new success pipe response.
74 | ///
75 | /// The ID of the call.
76 | /// The returned data.
77 | /// The success pipe response.
78 | public static SerializedPipeResponse Success(int callId, byte[] data)
79 | {
80 | return new SerializedPipeResponse { Succeeded = true, CallId = callId, Data = data };
81 | }
82 |
83 | ///
84 | /// Creates a new failure pipe response.
85 | ///
86 | /// The ID of the call.
87 | /// The failure message.
88 | /// The failure pipe response.
89 | public static SerializedPipeResponse Failure(int callId, string message)
90 | {
91 | return new SerializedPipeResponse { Succeeded = false, CallId = callId, Error = message };
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Models/TypedPipeResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace PipeMethodCalls
6 | {
7 | ///
8 | /// A method response to be sent over the pipe. Contains a typed response value.
9 | ///
10 | internal class TypedPipeResponse
11 | {
12 | ///
13 | /// The call ID.
14 | ///
15 | public int CallId { get; private set; }
16 |
17 | ///
18 | /// True if the call succeeded.
19 | ///
20 | public bool Succeeded { get; private set; }
21 |
22 | ///
23 | /// The response data. Valid if Succeeded is true.
24 | ///
25 | public object Data { get; private set; }
26 |
27 | ///
28 | /// The error details. Valid if Succeeded is false.
29 | ///
30 | public string Error { get; private set; }
31 |
32 | ///
33 | /// Creates a new success pipe response.
34 | ///
35 | /// The ID of the call.
36 | /// The returned data.
37 | /// The success pipe response.
38 | public static TypedPipeResponse Success(int callId, object data)
39 | {
40 | return new TypedPipeResponse { Succeeded = true, CallId = callId, Data = data };
41 | }
42 |
43 | ///
44 | /// Creates a new failure pipe response.
45 | ///
46 | /// The ID of the call.
47 | /// The failure message.
48 | /// The failure pipe response.
49 | public static TypedPipeResponse Failure(int callId, string message)
50 | {
51 | return new TypedPipeResponse { Succeeded = false, CallId = callId, Error = message };
52 | }
53 |
54 | ///
55 | /// Gets a string representation of this typed response.
56 | ///
57 | /// A string representation of this typed response.
58 | public override string ToString()
59 | {
60 | var builder = new StringBuilder("Response");
61 | builder.AppendLine();
62 | builder.Append(" CallId: ");
63 | builder.Append(this.CallId);
64 | builder.AppendLine();
65 | builder.Append(" Succeeded: ");
66 | builder.Append(this.Succeeded);
67 | builder.AppendLine();
68 | if (this.Succeeded)
69 | {
70 | builder.Append(" Data: ");
71 | builder.Append(this.Data?.ToString() ?? "");
72 | }
73 | else
74 | {
75 | builder.Append(" Error: ");
76 | builder.Append(this.Error);
77 | }
78 |
79 | return builder.ToString();
80 | }
81 |
82 | ///
83 | /// Serializes the response.
84 | ///
85 | /// The serializer to use.
86 | /// The serialized response.
87 | public SerializedPipeResponse Serialize(IPipeSerializer serializer)
88 | {
89 | if (this.Succeeded)
90 | {
91 | return SerializedPipeResponse.Success(this.CallId, serializer.Serialize(this.Data));
92 | }
93 | else
94 | {
95 | return SerializedPipeResponse.Failure(this.CallId, this.Error);
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/PipeMethodCalls/Invoker/IPipeInvoker.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq.Expressions;
5 | using System.Text;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace PipeMethodCalls
10 | {
11 | ///
12 | /// Invokes methods on a named pipe.
13 | ///
14 | /// The interface that we will be invoking.
15 | public interface IPipeInvoker
16 | where TRequesting : class
17 | {
18 | ///
19 | /// Invokes a method on the remote endpoint.
20 | ///
21 | /// The method to invoke.
22 | /// A token to cancel the request.
23 | /// Thrown when the invoked method throws an exception.
24 | /// Thrown when there is an issue with the pipe communication.
25 | /// Thrown when the cancellation token is invoked.
26 | Task InvokeAsync(Expression> expression, CancellationToken cancellationToken = default);
27 |
28 | ///
29 | /// Invokes a method on the remote endpoint.
30 | ///
31 | /// The method to invoke.
32 | /// A token to cancel the request.
33 | /// Thrown when the invoked method throws an exception.
34 | /// Thrown when there is an issue with the pipe communication.
35 | /// Thrown when the cancellation token is invoked.
36 | Task InvokeAsync(Expression> expression, CancellationToken cancellationToken = default);
37 |
38 | ///
39 | /// Invokes a method on the remote endpoint.
40 | ///
41 | /// The type of result from the method.
42 | /// The method to invoke.
43 | /// A token to cancel the request.
44 | /// The method result.
45 | /// Thrown when the invoked method throws an exception.
46 | /// Thrown when there is an issue with the pipe communication.
47 | /// Thrown when the cancellation token is invoked.
48 | Task InvokeAsync(Expression>> expression, CancellationToken cancellationToken = default);
49 |
50 | ///
51 | /// Invokes a method on the remote endpoint.
52 | ///
53 | /// The type of result from the method.
54 | /// The method to invoke.
55 | /// A token to cancel the request.
56 | /// The method result.
57 | /// Thrown when the invoked method throws an exception.
58 | /// Thrown when there is an issue with the pipe communication.
59 | /// Thrown when the cancellation token is invoked.
60 | Task InvokeAsync(Expression> expression, CancellationToken cancellationToken = default);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/PipeMethodCalls/PipeMessageProcessor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace PipeMethodCalls
9 | {
10 | ///
11 | /// Handles the pipe connection state and work processing.
12 | ///
13 | internal sealed class PipeMessageProcessor : IDisposable
14 | {
15 | private TaskCompletionSource