├── Tests
├── TestClient
│ ├── Properties
│ │ └── launchSettings.json
│ ├── project.json
│ ├── TestClient.xproj
│ └── Program.cs
├── TestComponent
│ ├── project.json
│ ├── TestComponent.xproj
│ └── TestType.cs
└── TestServer
│ ├── project.json
│ ├── Program.cs
│ └── TestServer.xproj
├── RemoteExecution
├── Commands.cs
├── project.json
├── RemotelyCreatedObject.cs
├── RemoteCommand.cs
├── RemoteExecution.xproj
├── RemoteExecutionClient.cs
├── Logger.cs
├── Serialization
│ ├── TypeConverter.cs
│ └── SerializationHelper.cs
├── RemoteProxy.cs
└── RemoteExecutionServer.cs
├── RemoteExecution.sln
├── .gitignore
└── ReadMe.md
/Tests/TestClient/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "TestClient": {
4 | "commandName": "Project"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/RemoteExecution/Commands.cs:
--------------------------------------------------------------------------------
1 | namespace RemoteExecution
2 | {
3 | internal enum Commands
4 | {
5 | NewObject,
6 | RetrieveObject,
7 | Invoke,
8 | GetProperty,
9 | SetProperty,
10 | CloseConnection
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Tests/TestComponent/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0-*",
3 |
4 | "dependencies": {
5 | "NETStandard.Library": "1.6.0",
6 | "System.Diagnostics.Process": "4.1.0"
7 | },
8 |
9 | "frameworks": {
10 | "netstandard1.3": {
11 | "imports": "dnxcore50"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/RemoteExecution/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.9.0-*",
3 |
4 | "dependencies": {
5 | "NETStandard.Library": "1.6.1",
6 | "Newtonsoft.Json": "9.0.1",
7 | "System.IO.Pipes": "4.3.0"
8 | },
9 |
10 | "frameworks": {
11 | "netstandard1.3": {
12 | "imports": "dnxcore50"
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/RemoteExecution/RemotelyCreatedObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace RemoteExecution
7 | {
8 | internal class RemotelyCreatedObject
9 | {
10 | public int RefCount;
11 | public object Value { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Tests/TestServer/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0-*",
3 | "buildOptions": {
4 | "emitEntryPoint": true
5 | },
6 |
7 | "dependencies": {
8 | "RemoteExecution": "0.9.0"
9 | },
10 |
11 | "frameworks": {
12 | "netcoreapp1.0": {
13 | "imports": "dnxcore50",
14 | "dependencies": {
15 | "Microsoft.NETCore.App": {
16 | "type": "platform",
17 | "version": "1.0.1"
18 | }
19 | }
20 | },
21 | "net461": { }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tests/TestClient/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0-*",
3 | "buildOptions": {
4 | "emitEntryPoint": true
5 | },
6 |
7 | "dependencies": {
8 | "RemoteExecution": "0.9.0",
9 | "TestComponent": "1.0.0"
10 | },
11 |
12 | "frameworks": {
13 | "netcoreapp1.0": {
14 | "imports": "dnxcore50",
15 | "dependencies": {
16 | "Microsoft.NETCore.App": {
17 | "type": "platform",
18 | "version": "1.0.1"
19 | }
20 | }
21 | },
22 | "net461": { }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/TestServer/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using RemoteExecution;
4 |
5 | namespace TestServer
6 | {
7 | public class Program
8 | {
9 | const string RemoteExecutionName = "MyRemoteServer";
10 | static RemoteExecutionServer RemoteServer;
11 |
12 | public static void Main(string[] args)
13 | {
14 | Console.WriteLine("Creating RemoteExecutionServer...");
15 |
16 | RemoteServer = new RemoteExecutionServer(RemoteExecutionName);
17 |
18 | // Enable verbose logging
19 | RemoteServer.Logger.LogLevel = MessagePriority.Verbose;
20 |
21 | // Report server creation and wait
22 | Console.WriteLine($"Remote server created with name {RemoteServer.RemoteName}");
23 | Console.WriteLine("Press enter to quit");
24 | Console.WriteLine();
25 | Console.ReadLine();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/RemoteExecution/RemoteCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace RemoteExecution
4 | {
5 | internal class RemoteCommand
6 | {
7 | // Common properties
8 | public int CommandVersion { get; set; } = 1;
9 | public Commands Command { get; set; }
10 |
11 | // Used for object retrieval or instance method invocation
12 | public Guid ObjectId { get; set; }
13 |
14 | // Used for method invocation
15 | public string MemberName { get; set; }
16 |
17 | // Used for method invocation or object creation
18 | public object[] Parameters { get; set; }
19 |
20 | // Newtonsoft.Json does not persist type information for
21 | // some primitive types, so that information needs to be tracked explicitly
22 | // for parameters.
23 | public Type[] ParameterTypes { get; set; }
24 |
25 | // Used for new object creation or static method execution
26 | public Type Type { get; set; }
27 | }
28 | }
--------------------------------------------------------------------------------
/Tests/TestClient/TestClient.xproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 |
10 | 6875513a-73e8-4cce-8648-82494a33efb2
11 | TestClient
12 | .\obj
13 | .\bin\
14 | v4.5.2
15 |
16 |
17 |
18 | 2.0
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Tests/TestServer/TestServer.xproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 |
10 | 29192211-ae15-4b68-b79c-da6c2ef998d5
11 | TestServer
12 | .\obj
13 | .\bin\
14 | v4.5.2
15 |
16 |
17 |
18 | 2.0
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/RemoteExecution/RemoteExecution.xproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 |
10 | fa090568-11af-45b9-afa8-40536677e8fc
11 | RemoteExecution
12 | .\obj
13 | .\bin\
14 | v4.5.2
15 |
16 |
17 |
18 | 2.0
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Tests/TestComponent/TestComponent.xproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 |
10 | c81624ab-5a17-4385-8dc8-f3817e6e6552
11 | TestComponent
12 | .\obj
13 | .\bin\
14 | v4.5.2
15 |
16 |
17 |
18 | 2.0
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Tests/TestComponent/TestType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 |
5 | namespace TestTypes
6 | {
7 | ///
8 | /// A simple type to be created and accessed cross-process
9 | ///
10 | public class MessageHolder
11 | {
12 | public string Name { get; }
13 |
14 | internal Queue MessageQueue { get; } = new Queue();
15 |
16 | public byte Length => (byte)MessageQueue.Count;
17 |
18 | public byte MaxLength { get; set; } = 5;
19 |
20 | public MessageHolder(string name)
21 | {
22 | Name = name;
23 | }
24 |
25 | public void AddMessageToQueue(string message)
26 | {
27 | if (MessageQueue.Count < MaxLength)
28 | MessageQueue.Enqueue(message);
29 | }
30 |
31 | public string RetrieveMessageFromQueue()
32 | {
33 | return MessageQueue.Dequeue();
34 | }
35 |
36 | ///
37 | /// A Console.WriteLine-using diagnostic to demonstrate where the object is running
38 | ///
39 | public void LogQueueToConsole()
40 | {
41 | Console.WriteLine($"Contents of queue '{Name}' in process {Process.GetCurrentProcess().Id}:\n\t{string.Join("\n\t", MessageQueue)}");
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/RemoteExecution/RemoteExecutionClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace RemoteExecution
7 | {
8 | public static class RemoteExecutionClient
9 | {
10 | ///
11 | /// Creates a new RemoteProxy for a given type T
12 | ///
13 | /// The type to create remotely
14 | /// The remote execution server's name (for named pipe communication)
15 | /// Parameters to pass to the type's constructor
16 | /// A proxy for managing the remote object
17 | public static RemoteProxy CreateRemoteInstance(string remoteServerName, params object[] paramaters)
18 | {
19 | return new RemoteProxy(typeof(T), remoteServerName, paramaters);
20 | }
21 |
22 | ///
23 | /// Creates a proxy to an existing remote object
24 | ///
25 | /// The remote execution server's name (for named pipe communication)
26 | /// The remote object's ID
27 | /// A proxy for managing the remote object
28 | public static RemoteProxy GetRemoteInstance(string remoteServerName, Guid objectId)
29 | {
30 | return new RemoteProxy(remoteServerName, objectId);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/RemoteExecution/Logger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 |
4 | namespace RemoteExecution
5 | {
6 | public class Logger
7 | {
8 | object LoggingLock = new object();
9 |
10 | public MessagePriority LogLevel { get; set; } = MessagePriority.Informational;
11 |
12 | public void Log(string message, MessagePriority priority = MessagePriority.Verbose)
13 | {
14 | if (priority <= LogLevel)
15 | {
16 | lock (LoggingLock)
17 | {
18 | ConsoleColor previousForeColor = Console.ForegroundColor;
19 | StringBuilder toWrite = new StringBuilder($"[{DateTime.Now.ToString("HH:MM:ss.ff")}] ");
20 |
21 | switch (priority)
22 | {
23 | case MessagePriority.Error:
24 | Console.ForegroundColor = ConsoleColor.Red;
25 | toWrite.Append("[ERROR] ");
26 | break;
27 | case MessagePriority.Warning:
28 | Console.ForegroundColor = ConsoleColor.Yellow;
29 | toWrite.Append("[Warning] ");
30 | break;
31 | case MessagePriority.Verbose:
32 | Console.ForegroundColor = ConsoleColor.Gray;
33 | break;
34 | case MessagePriority.Informational:
35 | Console.ForegroundColor = ConsoleColor.White;
36 | break;
37 | }
38 |
39 | toWrite.Append(message);
40 |
41 | Console.WriteLine(toWrite.ToString());
42 | Console.ForegroundColor = previousForeColor;
43 | }
44 | }
45 | }
46 | }
47 |
48 | public enum MessagePriority
49 | {
50 | Error,
51 | Warning,
52 | Informational,
53 | Verbose
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/RemoteExecution.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25420.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RemoteExecution", "RemoteExecution\RemoteExecution.xproj", "{FA090568-11AF-45B9-AFA8-40536677E8FC}"
7 | EndProject
8 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestClient", "Tests\TestClient\TestClient.xproj", "{6875513A-73E8-4CCE-8648-82494A33EFB2}"
9 | EndProject
10 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestComponent", "Tests\TestComponent\TestComponent.xproj", "{C81624AB-5A17-4385-8DC8-F3817E6E6552}"
11 | EndProject
12 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestServer", "Tests\TestServer\TestServer.xproj", "{29192211-AE15-4B68-B79C-DA6C2EF998D5}"
13 | EndProject
14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C5B7FAEF-B884-4EE5-A52C-39FA0B530920}"
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {FA090568-11AF-45B9-AFA8-40536677E8FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {FA090568-11AF-45B9-AFA8-40536677E8FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {FA090568-11AF-45B9-AFA8-40536677E8FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {FA090568-11AF-45B9-AFA8-40536677E8FC}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {6875513A-73E8-4CCE-8648-82494A33EFB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {6875513A-73E8-4CCE-8648-82494A33EFB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {6875513A-73E8-4CCE-8648-82494A33EFB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {6875513A-73E8-4CCE-8648-82494A33EFB2}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {C81624AB-5A17-4385-8DC8-F3817E6E6552}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {C81624AB-5A17-4385-8DC8-F3817E6E6552}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {C81624AB-5A17-4385-8DC8-F3817E6E6552}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {C81624AB-5A17-4385-8DC8-F3817E6E6552}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {29192211-AE15-4B68-B79C-DA6C2EF998D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {29192211-AE15-4B68-B79C-DA6C2EF998D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {29192211-AE15-4B68-B79C-DA6C2EF998D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {29192211-AE15-4B68-B79C-DA6C2EF998D5}.Release|Any CPU.Build.0 = Release|Any CPU
38 | EndGlobalSection
39 | GlobalSection(SolutionProperties) = preSolution
40 | HideSolutionNode = FALSE
41 | EndGlobalSection
42 | GlobalSection(NestedProjects) = preSolution
43 | {6875513A-73E8-4CCE-8648-82494A33EFB2} = {C5B7FAEF-B884-4EE5-A52C-39FA0B530920}
44 | {C81624AB-5A17-4385-8DC8-F3817E6E6552} = {C5B7FAEF-B884-4EE5-A52C-39FA0B530920}
45 | {29192211-AE15-4B68-B79C-DA6C2EF998D5} = {C5B7FAEF-B884-4EE5-A52C-39FA0B530920}
46 | EndGlobalSection
47 | EndGlobal
48 |
--------------------------------------------------------------------------------
/RemoteExecution/Serialization/TypeConverter.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Reflection;
4 | using System.Linq;
5 |
6 | namespace RemoteExecution
7 | {
8 | ///
9 | /// Type converter to allow limited NetCore <->NetFX interoperability
10 | /// There are still plenty of cross-platform scenarios that don't work, but
11 | /// by stripping out obviously different base/core assembly names from type
12 | /// strings, some simple cases can be made to work.
13 | ///
14 | public class TypeConverter : JsonConverter
15 | {
16 | // .NET Core-specific core assemblies which don't have NetFX facades
17 | static string[] SkippedAssemblies = {
18 | "System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e",
19 | "System.Private.CoreLib"
20 | };
21 |
22 | // Specify which types (System.Type) this type converter works on
23 | public override bool CanConvert(Type objectType)
24 | {
25 | if (objectType == null) return false;
26 | return typeof(Type).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
27 | }
28 |
29 | // Retrieves a type from its string representation
30 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
31 | {
32 | Type ret = null;
33 | if (reader.TokenType == JsonToken.String)
34 | {
35 | ret = Type.GetType(reader.Value?.ToString());
36 | }
37 | return ret;
38 | }
39 |
40 | // Writes a type as a string
41 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
42 | {
43 | var type = (value as Type).GetTypeInfo();
44 | string typeString = null;
45 |
46 | // Assume that types in System.Private.CoreLib can be found without their assembly name qualifier (since they are base types)
47 | // Don't include the assembly name since it changes from Framework-to-Framework
48 | if (SkippedAssemblies.Contains(type.Assembly.FullName, StringComparer.OrdinalIgnoreCase))
49 | {
50 | typeString = type.FullName;
51 | }
52 | else
53 | {
54 | typeString = type.AssemblyQualifiedName;
55 | }
56 |
57 | // Even if FullName is used, assembly qualified names can show up in generic parameters.
58 | // This subsequent scrub of the string removes those.
59 | // Using this approach, the previous decision to use FullName or AssemblyQualifiedName
60 | // doesn't actually change anything, but I'm leaving it for the moment since it's the
61 | // more obvious approach.
62 | typeString = CleanSerializedString(typeString);
63 |
64 | writer.WriteValue(typeString);
65 | }
66 |
67 | // Helper method to strip skipped assembly names from general string representations of types
68 | public static string CleanSerializedString(string serializedString)
69 | {
70 | foreach (var assmName in SkippedAssemblies)
71 | {
72 | serializedString = serializedString.Replace($", {assmName}", string.Empty);
73 | }
74 |
75 | return serializedString;
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/RemoteExecution/Serialization/SerializationHelper.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Reflection;
6 |
7 | namespace RemoteExecution
8 | {
9 | ///
10 | /// A helper class for serializing and deserializing types to/from JSON
11 | /// for transport over named pipes
12 | ///
13 | internal static class SerializationHelper
14 | {
15 | private static bool initialized = false;
16 |
17 | private static JsonSerializerSettings GetJsonSettings() =>
18 | new JsonSerializerSettings
19 | {
20 | TypeNameHandling = TypeNameHandling.Objects,
21 | NullValueHandling = NullValueHandling.Ignore,
22 | ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
23 | PreserveReferencesHandling = PreserveReferencesHandling.Objects,
24 | Converters = new List() {
25 | // Use a custom type converter to massage type names in order
26 | // to allow limited cross-platform (NetFX <-> NetCore) interoperability
27 | new TypeConverter()
28 | }
29 | };
30 |
31 | public static string Serialize(object o)
32 | {
33 | if (!initialized)
34 | {
35 | JsonConvert.DefaultSettings = SerializationHelper.GetJsonSettings;
36 | initialized = true;
37 | }
38 |
39 | var json = JsonConvert.SerializeObject(o);
40 | return TypeConverter.CleanSerializedString(json);
41 | }
42 |
43 | public static object Deserialize(string s)
44 | {
45 | if (!initialized)
46 | {
47 | JsonConvert.DefaultSettings = SerializationHelper.GetJsonSettings;
48 | initialized = true;
49 | }
50 |
51 | try
52 | {
53 | return JsonConvert.DeserializeObject(s);
54 | }
55 | catch (JsonException exc)
56 | {
57 | throw new ArgumentException("Invalid json string", exc);
58 | }
59 | }
60 |
61 | public static T Deserialize(string s)
62 | {
63 | if (!initialized)
64 | {
65 | JsonConvert.DefaultSettings = SerializationHelper.GetJsonSettings;
66 | initialized = true;
67 | }
68 |
69 | try
70 | {
71 | return JsonConvert.DeserializeObject(s);
72 | }
73 | catch (JsonException exc)
74 | {
75 | throw new ArgumentException("Invalid json string", exc);
76 | }
77 | }
78 |
79 | // A wrapper around Convert.ChangeType to handle more cases known to be interesting
80 | // with Json.Net serialization.
81 | // http://www.newtonsoft.com/json/help/html/SerializationGuide.htm
82 | internal static object GetObjectAsType(object obj, Type type)
83 | {
84 | if (obj == null) return null;
85 |
86 | if (type == null) return obj;
87 |
88 | if (obj is JArray)
89 | {
90 | return ((JArray)obj).ToObject(type);
91 | }
92 |
93 | if (obj is JObject)
94 | {
95 | return ((JObject)obj).ToObject(type);
96 | }
97 |
98 | if (obj is string && type == typeof(Guid))
99 | {
100 | return new Guid(obj as string);
101 | }
102 |
103 | if (obj is string && type == typeof(byte[]))
104 | {
105 | return Convert.FromBase64String(obj as string);
106 | }
107 |
108 | if (obj is string && typeof(DateTime).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
109 | {
110 | return DateTime.ParseExact(obj as string, "YYYY-MM-DDTHH:mmZ", null);
111 | }
112 |
113 | if (obj is string && typeof(Type).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
114 | {
115 | return Type.GetType(obj as string);
116 | }
117 |
118 |
119 | return Convert.ChangeType(obj, type);
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | syntax: glob
2 | ## From: https://raw.githubusercontent.com/github/gitignore/master/VisualStudio.gitignore
3 | ## Ignore Visual Studio temporary files, build results, and
4 | ## files generated by popular Visual Studio add-ons.
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 |
26 | # Visual Studio 2015 cache/options directory
27 | .vs/
28 | **/wwwroot/lib/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 |
85 | # Visual Studio profiler
86 | *.psess
87 | *.vsp
88 | *.vspx
89 | *.sap
90 |
91 | # TFS 2012 Local Workspace
92 | $tf/
93 |
94 | # Guidance Automation Toolkit
95 | *.gpState
96 |
97 | # ReSharper is a .NET coding add-in
98 | _ReSharper*/
99 | *.[Rr]e[Ss]harper
100 | *.DotSettings.user
101 |
102 | # JustCode is a .NET coding add-in
103 | .JustCode
104 |
105 | # TeamCity is a build add-in
106 | _TeamCity*
107 |
108 | # DotCover is a Code Coverage Tool
109 | *.dotCover
110 |
111 | # NCrunch
112 | _NCrunch_*
113 | .*crunch*.local.xml
114 | nCrunchTemp_*
115 |
116 | # MightyMoose
117 | *.mm.*
118 | AutoTest.Net/
119 |
120 | # Web workbench (sass)
121 | .sass-cache/
122 |
123 | # Installshield output folder
124 | [Ee]xpress/
125 |
126 | # DocProject is a documentation generator add-in
127 | DocProject/buildhelp/
128 | DocProject/Help/*.HxT
129 | DocProject/Help/*.HxC
130 | DocProject/Help/*.hhc
131 | DocProject/Help/*.hhk
132 | DocProject/Help/*.hhp
133 | DocProject/Help/Html2
134 | DocProject/Help/html
135 |
136 | # Click-Once directory
137 | publish/
138 |
139 | # Publish Web Output
140 | *.[Pp]ublish.xml
141 | *.azurePubxml
142 | # TODO: Comment the next line if you want to checkin your web deploy settings
143 | # but database connection strings (with potential passwords) will be unencrypted
144 | *.pubxml
145 | *.publishproj
146 |
147 | # NuGet Packages
148 | *.nupkg
149 | # The packages folder can be ignored because of Package Restore
150 | **/packages/*
151 | # except build/, which is used as an MSBuild target.
152 | !**/packages/build/
153 | # Uncomment if necessary however generally it will be regenerated when needed
154 | #!**/packages/repositories.config
155 | # NuGet v3's project.json files produces more ignoreable files
156 | *.nuget.props
157 | *.nuget.targets
158 |
159 | # Microsoft Azure Build Output
160 | csx/
161 | *.build.csdef
162 |
163 | # Microsoft Azure Emulator
164 | ecf/
165 | rcf/
166 |
167 | # Microsoft Azure ApplicationInsights config file
168 | ApplicationInsights.config
169 |
170 | # Windows Store app package directory
171 | AppPackages/
172 | BundleArtifacts/
173 |
174 | # Visual Studio cache files
175 | # files ending in .cache can be ignored
176 | *.[Cc]ache
177 | # but keep track of directories ending in .cache
178 | !*.[Cc]ache/
179 |
180 | # Others
181 | ClientBin/
182 | ~$*
183 | *~
184 | *.dbmdl
185 | *.dbproj.schemaview
186 | *.pfx
187 | *.publishsettings
188 | node_modules/
189 | orleans.codegen.cs
190 |
191 | # RIA/Silverlight projects
192 | Generated_Code/
193 |
194 | # Backup & report files from converting an old project file
195 | # to a newer Visual Studio version. Backup files are not needed,
196 | # because we have git ;-)
197 | _UpgradeReport_Files/
198 | Backup*/
199 | UpgradeLog*.XML
200 | UpgradeLog*.htm
201 |
202 | # SQL Server files
203 | *.mdf
204 | *.ldf
205 |
206 | # Business Intelligence projects
207 | *.rdl.data
208 | *.bim.layout
209 | *.bim_*.settings
210 |
211 | # Microsoft Fakes
212 | FakesAssemblies/
213 |
214 | # GhostDoc plugin setting file
215 | *.GhostDoc.xml
216 |
217 | # Node.js Tools for Visual Studio
218 | .ntvs_analysis.dat
219 |
220 | # Visual Studio 6 build log
221 | *.plg
222 |
223 | # Visual Studio 6 workspace options file
224 | *.opt
225 |
226 | # Visual Studio LightSwitch build output
227 | **/*.HTMLClient/GeneratedArtifacts
228 | **/*.DesktopClient/GeneratedArtifacts
229 | **/*.DesktopClient/ModelManifest.xml
230 | **/*.Server/GeneratedArtifacts
231 | **/*.Server/ModelManifest.xml
232 | _Pvt_Extensions
233 |
234 | # Paket dependency manager
235 | .paket/paket.exe
236 |
237 | # FAKE - F# Make
238 | .fake/
--------------------------------------------------------------------------------
/ReadMe.md:
--------------------------------------------------------------------------------
1 | .NET Core Remote Communication and Marshaling
2 | ==============================================
3 |
4 | Overview
5 | --------
6 |
7 | .NET remoting is one of the .NET Framework feature area that is [not available](https://docs.microsoft.com/en-us/dotnet/articles/core/porting/libraries#key-technologies-not-yet-available-on-the-net-standard-or-net-core) in .NET Core.
8 |
9 | Although remoting (as it exists in NetFX) is not available, many scenarios that call for remoting can be implemented with alternate approaches in .NET Core. This repo attempts to show a simple sample of that. Note that this repo demonstrates a fairly general approach to the problem. In specific cases, better (and simpler) solutions may be possible which target the goals of that scenario. This repo is just a general-purpose demo of how cross-process managed code invocation can be accomplished in .NET Core.
10 |
11 | The required technologies are:
12 |
13 | * [Named pipes](https://msdn.microsoft.com/en-us/library/bb546085(v=vs.110).aspx) for cross-process communication.
14 | * A serialization technology for marshaling managed objects. In this example, I use [Json.NET](http://www.newtonsoft.com/json). This means that the sample can only send parameters cross-process if their types are serializable/deserializable with Json.NET. All of the serialization logic is confined to a couple classes in the [serialization](https://github.com/mjrousos/NetCoreRemoting/tree/master/RemoteExecution/Serialization) directory, though, so it should be easy to swap out Json.NET for an alternate serialization provider, if needed.
15 | * A proxying technology. In order to keep the sample as simple as possible, it's not using real proxies - I just have a `RemoteProxy` class that takes a string parameter specifying which API to call on a remote object. A more fully-featured remoting story could use something like [Castle.DynamixProxy](https://github.com/castleproject/Core/blob/master/docs/dynamicproxy.md) to create proxy objects for use on the client.
16 |
17 | How it Works
18 | ------------
19 |
20 | As mentioned above, the communication is based on named pipes. To start remoting, the server piece of the scenario must create a [`RemoteExecutionServer`](https://github.com/mjrousos/NetCoreRemoting/blob/master/RemoteExecution/RemoteExecutionServer.cs) object. Doing so will cause a named pipe to be setup for clients to connect to. Once that's done, the server's work is complete.
21 |
22 | Then, clients are able to create [`RemoteProxy`](https://github.com/mjrousos/NetCoreRemoting/blob/master/RemoteExecution/RemoteProxy.cs) objects (or use the [`RemoteExecutionClient`](https://github.com/mjrousos/NetCoreRemoting/blob/master/RemoteExecution/RemoteExecutionClient.cs) wrapper class). Creating this type causes the client to connect with the server via named pipes and send a request on that pipe (all requests are instances of [`RemoteCommand`](https://github.com/mjrousos/NetCoreRemoting/blob/master/RemoteExecution/RemoteCommand.cs)) for an instance of the type to be remoted to be created.
23 |
24 | On the server side, the necessary type will be created and it will be put in a Dictionary to be referenced later. The key (for looking up the object in the dictionary) is then returned to the `RemoteProxy` where it is stored.
25 |
26 | Later, when the client wants to call some API on the remote object, `RemoteProxy.InvokeAsync` (or one of a number of other similar APIs) can be called. This will cause another `RemoteCommand` to be created which will include:
27 |
28 | 1. The ID of the remote object that the API should be invoked on.
29 | 2. The name of the API as a string.
30 | 3. The parameters (if any) to pass to the API.
31 | 4. The types of those parameters (since they can be lost in serialization).
32 |
33 | This information is all serialized and then sent via the named pipe to the server. The server, as might be expected, deserializes the command and uses reflection to invoke the API requested, serializing the return object (if any) to be sent back to the proxy (which, in turn, presents it to its caller).
34 |
35 | Trying it Out
36 | -------------
37 |
38 | There are some tests in the [Tests](https://github.com/mjrousos/NetCoreRemoting/tree/master/Tests) folder which allow for trying out the remoting mentioned here. To experiment with it, follow these steps:
39 |
40 | 1. Run the [TestServer](https://github.com/mjrousos/NetCoreRemoting/tree/master/Tests/TestServer) application to start listening for remote objects.
41 | 1. Before running, though, make sure that you've copied TestClient and TestComponent dlls next to the TestServer app. This is important because TestServer doesn't usually depend on those libraries, but the client will be requesting that types from them be created. Therefore, it's necessary for TestServer to be able to find the assemblies so that it can load and activate the required types.
42 | 2. Run the [TestClient](https://github.com/mjrousos/NetCoreRemoting/tree/master/Tests/TestClient) which will first make a series of hard-coded remote calls that I found useful for initial ad-hoc testing, and then create a remote instance of [`TestTypes.MessageHolder`](https://github.com/mjrousos/NetCoreRemoting/blob/master/Tests/TestComponent/TestType.cs) (a glorified wrapper around a queue) and allow the user to ask for different `MessageHolder` commands to be executed remotely. Of special note is the 'print' command which will cause information about the queue to be written to the console. When calling this, notice that the console output happens in the TestServer command prompt since that's the process in which all the `MessageHolder` code is executing.
43 |
44 | Demo
45 | ----
46 |
47 | There's a brief demo video of this sample available [on YouTube](https://youtu.be/QwvYXrHM4E4).
--------------------------------------------------------------------------------
/Tests/TestClient/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | using RemoteExecution;
5 | using TestTypes;
6 | using System.Linq;
7 |
8 | // Simple test client to demonstrate remoting functionality with some test input
9 | namespace TestClient
10 | {
11 | public class Program
12 | {
13 | const string RemoteExecutionName = "MyRemoteServer";
14 |
15 | public static void Main(string[] args)
16 | {
17 | Console.ForegroundColor = ConsoleColor.Cyan;
18 | Console.WriteLine("#########################################");
19 | Console.WriteLine("# Test 1 - Creating some remote objects #");
20 | Console.WriteLine("#########################################");
21 | Console.ForegroundColor = ConsoleColor.White;
22 | Console.WriteLine();
23 |
24 |
25 |
26 | // Test #1
27 | Console.WriteLine("Creating proxy objects...");
28 |
29 | var proxy0 = RemoteExecutionClient.CreateRemoteInstance(RemoteExecutionName);
30 | Console.WriteLine($"Created remote object with ID: {proxy0.ID}");
31 |
32 | var proxy1 = RemoteExecutionClient.CreateRemoteInstance(RemoteExecutionName, "Hello, world!".ToCharArray());
33 | Console.WriteLine($"Created remote object with ID: {proxy1.ID}");
34 |
35 | var proxy2 = RemoteExecutionClient.CreateRemoteInstance>(RemoteExecutionName);
36 | Console.WriteLine($"Created remote object with ID: {proxy2.ID}");
37 |
38 | var proxy3 = RemoteExecutionClient.CreateRemoteInstance>(RemoteExecutionName, 5);
39 | Console.WriteLine($"Created remote object with ID: {proxy3.ID}");
40 | proxy3.InvokeAsync("Add", 3).Wait();
41 | proxy3.InvokeAsync("Add", 5).Wait();
42 | var index = proxy3.InvokeAsync("IndexOf", 5).Result;
43 | var count = proxy3.GetPropertyAsync("Count").Result;
44 | Console.WriteLine($"Added items to remote list (count: {count}) and found index of '5' is: {index}");
45 |
46 | var proxy4 = RemoteExecutionClient.CreateRemoteInstance(RemoteExecutionName, typeof(Program), RemoteExecutionName, null);
47 | Console.WriteLine($"Created remote object with ID: {proxy4.ID}");
48 |
49 |
50 |
51 | // Test #2
52 | Console.WriteLine();
53 | Console.ForegroundColor = ConsoleColor.Cyan;
54 | Console.WriteLine("###########################################");
55 | Console.WriteLine("# Test 2 - Interactive remote custom type #");
56 | Console.WriteLine("###########################################");
57 | Console.ForegroundColor = ConsoleColor.White;
58 | Console.WriteLine();
59 |
60 | var proxy = RemoteExecutionClient.CreateRemoteInstance(RemoteExecutionName, "MyMessages");
61 | Console.WriteLine($"MessageHolder proxy created with ID: {proxy.ID}");
62 | Console.WriteLine();
63 |
64 | bool shouldQuit = false;
65 |
66 | while (!shouldQuit)
67 | {
68 | Console.WriteLine("Select option:");
69 | Console.WriteLine(" E - Enqueue");
70 | Console.WriteLine(" D - Dequeue");
71 | Console.WriteLine(" L - Length");
72 | Console.WriteLine(" M - Retrieve max length");
73 | Console.WriteLine(" S - Set max length");
74 | Console.WriteLine(" P - Print status (remotely)");
75 | Console.WriteLine(" Q - End test");
76 | Console.Write(" > ");
77 |
78 | var option = char.ToUpperInvariant(Console.ReadLine().FirstOrDefault());
79 |
80 | switch (option)
81 | {
82 | case 'E':
83 | Console.Write("Message: ");
84 | var message = Console.ReadLine();
85 | proxy.InvokeAsync("AddMessageToQueue", message).Wait();
86 | Console.WriteLine("Message sent");
87 | break;
88 | case 'D':
89 | Console.WriteLine("Retrieving message...");
90 | var retMessage = proxy.InvokeAsync("RetrieveMessageFromQueue").Result;
91 | Console.WriteLine($"Message: {retMessage}");
92 | break;
93 | case 'L':
94 | Console.WriteLine("Retrieving length...");
95 | var length = proxy.GetPropertyAsync("Length").Result;
96 | Console.WriteLine($"Length: {length}");
97 | break;
98 | case 'M':
99 | Console.WriteLine("Retrieving Max length...");
100 | var maxLength = proxy.GetPropertyAsync("MaxLength").Result;
101 | Console.WriteLine($"Max length: {maxLength}");
102 | break;
103 | case 'S':
104 | Console.Write("Max length: ");
105 | var newLength = byte.Parse(Console.ReadLine());
106 | proxy.SetPropertyAsync("MaxLength", newLength).Wait();
107 | Console.WriteLine("Message sent");
108 | break;
109 | case 'P':
110 | Console.WriteLine("Sending log command...");
111 | proxy.InvokeAsync("LogQueueToConsole").Wait();
112 | Console.WriteLine("Sent");
113 | break;
114 | case 'Q':
115 | shouldQuit = true;
116 | break;
117 | default:
118 | Console.WriteLine("Invalid option");
119 | break;
120 | }
121 | Console.WriteLine();
122 | }
123 |
124 |
125 |
126 | Console.WriteLine("- Test Done -");
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/RemoteExecution/RemoteProxy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO.Pipes;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace RemoteExecution
9 | {
10 | public class RemoteProxy : IDisposable
11 | {
12 | // ID and type of the remote object
13 | public Guid ID { get; }
14 | public Type Type { get; }
15 |
16 | // Connection information
17 | private string RemoteMachineName { get; }
18 | private string RemoteExecutionServerName { get; }
19 |
20 | // The pipe for communicating with the server
21 | NamedPipeClientStream Pipe;
22 |
23 | ///
24 | /// Create a new remote proxy for a given type
25 | ///
26 | /// The type to create remotely
27 | /// The name of the remote execution server (pipe name)
28 | /// Parameters to pass to the objects constructor
29 | public RemoteProxy(Type type, string remoteExecutionServerName, object[] parameters) : this(type, ".", remoteExecutionServerName, parameters) { }
30 |
31 | ///
32 | /// Create a new remote proxy for a given type
33 | ///
34 | /// The type to create remotely
35 | /// The machine name where the remote server is running
36 | /// The name of the remote execution server (pipe name)
37 | /// Parameters to pass to the objects constructor
38 | public RemoteProxy(Type type, string serverName, string remoteExecutionServerName, object[] parameters)
39 | {
40 | RemoteMachineName = serverName;
41 | RemoteExecutionServerName = remoteExecutionServerName;
42 | Type = type;
43 |
44 | // Setup the pipe
45 | InitializeAsync().Wait();
46 |
47 | // Request remote object be created
48 | var command = new RemoteCommand
49 | {
50 | Command = Commands.NewObject,
51 | Type = type,
52 | Parameters = parameters,
53 | ParameterTypes = parameters?.Select(p => p?.GetType()).ToArray()
54 | };
55 |
56 | // Send the command and receive the remote object's ID back
57 | ID = SendCommandAsync(command).Result;
58 | }
59 |
60 | ///
61 | /// Create a proxy for an existing remote object
62 | ///
63 | /// The name of the remote execution server (pipe name)
64 | /// The remote object's ID
65 | public RemoteProxy(string remoteExecutionServerName, Guid objectId) : this(".", remoteExecutionServerName, objectId) { }
66 |
67 | ///
68 | /// Create a proxy for an existing remote object
69 | ///
70 | /// The machine name where the remote server is running
71 | /// The name of the remote execution server (pipe name)
72 | /// The remote object's ID
73 | public RemoteProxy(string serverName, string remoteExecutionServerName, Guid objectId)
74 | {
75 | RemoteMachineName = serverName;
76 | RemoteExecutionServerName = remoteExecutionServerName;
77 | ID = objectId;
78 |
79 | // Setup the pipe
80 | InitializeAsync().Wait();
81 |
82 | // Request remote object be created
83 | var command = new RemoteCommand
84 | {
85 | Command = Commands.RetrieveObject,
86 | ObjectId = ID
87 | };
88 |
89 | // Send the command and receive the remote object's ID back
90 | Type = SendCommandAsync(command).Result;
91 | }
92 |
93 | ///
94 | /// Prepares named pipe communication with the server
95 | ///
96 | private async Task InitializeAsync()
97 | {
98 | // Setup pipe
99 | Pipe = new NamedPipeClientStream(RemoteMachineName, RemoteExecutionServerName, PipeDirection.InOut, PipeOptions.Asynchronous);
100 |
101 | // Connect and set ReadMode
102 | await Pipe.ConnectAsync();
103 | Pipe.ReadMode = PipeTransmissionMode.Message;
104 | }
105 |
106 | ///
107 | /// Invokes a method on the remote object
108 | ///
109 | /// Name of method to invoke
110 | /// Method parameters
111 | public async Task InvokeAsync(string methodName, params object[] parameters)
112 | {
113 | await InvokeAsync