├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── README.md └── src ├── AppDomainAlternative.Tests ├── AppDomainAlternative.Tests.csproj ├── Ipc │ ├── Channels │ │ ├── ChannelListenerTest.cs │ │ ├── ChannelStartTest.cs │ │ └── MockChannel.cs │ ├── ConnectionTests.cs │ └── ReadWriteBufferTests.cs ├── Proxy │ ├── ConstructorsTests.cs │ ├── MethodsWithArgumentsTests.cs │ ├── MockInterceptor.cs │ ├── MockSerializer.cs │ ├── PropertiesTests.cs │ ├── ProxyOptionsTests.cs │ ├── ReturnValueMethodsTests.cs │ └── VoidMethodsTests.cs └── Serializer │ ├── ArrayTests.cs │ ├── DateTimeTests.cs │ ├── EnumTests.cs │ ├── GuidTests.cs │ ├── MockResolveProxyIds.cs │ ├── NullTests.cs │ ├── PrimitiveTypesTests.cs │ ├── ProxyInstanceTests.cs │ ├── SerializableTests.cs │ ├── StringTests.cs │ ├── TimeSpanTests.cs │ └── TypeTests.cs ├── AppDomainAlternative.VsCode.sln ├── AppDomainAlternative.sln ├── AppDomainAlternative ├── AppDomainAlternative.csproj ├── ChildDomain.cs ├── CurrentDomain.cs ├── DomainConfiguration.cs ├── Domains.cs ├── Extensions │ ├── ChannelListensExtensions.cs │ └── ChannelStartExtensions.cs ├── Ipc │ ├── Channel.cs │ ├── Connection.cs │ └── ReadWriteBuffer.cs ├── Properties │ └── AssemblyInfo.cs ├── Proxy │ ├── DefaultProxyFactory.cs │ ├── IGenerateProxies.cs │ ├── IInterceptor.cs │ └── ProxyAttribute.cs └── Serializer │ ├── Default │ ├── ArrayReaderWriter.cs │ ├── DateTimeReaderWrite.cs │ ├── DefaultSerializer.cs │ ├── EnumReaderWriter.cs │ ├── GuidReaderWriter.cs │ ├── ObjectReaderWriter.cs │ ├── SerializableReaderWriter.cs │ ├── TimeSpanReaderWriter.cs │ └── TypeReaderWriter.cs │ ├── IAmASerializer.cs │ └── IResolveProxyIds.cs ├── ClientApp ├── ClientApp.csproj └── ClientProgram.cs ├── Common ├── ChatRoom.cs └── Common.csproj ├── HostApp ├── HostApp.csproj └── HostProgram.cs └── VsDebugger ├── Program.cs └── VsDebugger.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 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 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug HostApp", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/src/HostApp/bin/Debug/netcoreapp2.0/HostApp.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "stopAtEntry": false, 16 | "console": "externalTerminal" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet", 9 | "type": "shell", 10 | "args": [ 11 | "build", 12 | "src/AppDomainAlternative.VsCode.sln", 13 | "/property:GenerateFullPaths=true", 14 | "/consoleloggerparameters:NoSummary" 15 | ], 16 | "group": "build", 17 | "presentation": { 18 | "reveal": "silent" 19 | }, 20 | "problemMatcher": "$msCompile" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Cy Scott 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AppDomainAlternative 2 | 3 | The [AppDomain](https://docs.microsoft.com/en-us/dotnet/api/system.appdomain) SandBox features were intentionally left out of .Net Core for several [technical and security reasons](https://devblogs.microsoft.com/dotnet/porting-to-net-core/). Microsoft's recommendation for .Net Core SandBoxing is to use [process](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process) isolation instead of AppDomain isolation and to use [inter-process communication (IPC)](https://docs.microsoft.com/en-us/windows/desktop/ipc/interprocess-communications) classes (ie pipes, sockets, etc.) instead of remoting classes like the [MarshalByRefObject](https://docs.microsoft.com/en-us/dotnet/api/system.marshalbyrefobject) class for data communication between those processes. AppDomainAlternative is a .Net Core friendly alternative to using AppDomains as SandBoxes and uses the recommended solution of process isolation and supports object remoting over [Anonymous Pipes](https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-use-anonymous-pipes-for-local-interprocess-communication). 4 | 5 | ### Important Differences between AppDomains and Processes 6 | 7 | 1) All AppDomains exist under one process so unhandled exceptions from any AppDomain terminates the entire process and all AppDomains within it. However, processes do not work like that and if one process has an unhandled exception only that process terminates and not the parent or child processes. Although it is possible to replicate this feature between processes, AppDomainAlternative does not replicate this feature because it is not a desired feature for proper SandBoxing. 8 | 9 | 2) AppDomains do not have a “main” method (like processes) that is executed when the AppDomain is created. A process’s “main” method always executes immediately when the process is created and is intrinsic to how processes work. It is possible to support this feature by creating a generic launcher assembly that executes its “main” method when the process starts and waits for instructions from the parent process to do work like AppDomains do. However, this is a complex feature that will be developed in a later release of AppDomainAlternative. 10 | 11 | 3) AppDomains can load [Dynamic Assemblies](https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/emitting-dynamic-methods-and-assemblies) generated during run time by another AppDomain. Dynamic assembly run time generation via Emit is still supported in .Net Core but the [assembly serialization features are not](https://github.com/dotnet/corefx/issues/4491#issuecomment-189756092). Since those assemblies cannot be serialized, they cannot be passed from one process to another for execution. [ILPack](https://github.com/Lokad/ILPack) looks like a good choice to fill in the feature gap, but it has not been released yet. 12 | 13 | ### Remoting Alternative 14 | 15 | Remoting was an important feature for AppDomains and [is no longer supported](https://docs.microsoft.com/en-us/dotnet/core/porting/net-framework-tech-unavailable#remoting) in .Net Core. Which means classes that inherit from [MarshalByRefObject](https://docs.microsoft.com/en-us/dotnet/api/system.marshalbyrefobject) can no longer be used for remoting from one domain to another. Classes that inherit from MarshalByRefObject work by creating a proxy instance on a remote domain and all calls to that proxy instance are serialized and passed to the original domain for execution. Here is an example class that can be proxied across the domain barrier: 16 | 17 | ``` 18 | public class ChatRoom : MarshalByRefObject 19 | { 20 | public void SendMessage(string message) 21 | { 22 | Console.WriteLine(message); 23 | } 24 | } 25 | ``` 26 | 27 | AppDomainAlternative takes a similar approach to remoting across the process barrier. However, there is no need to inherit from a special class. Here is an example class that can be proxied across the process barrier: 28 | 29 | ``` 30 | public class ChatRoom 31 | { 32 | public virtual void SendMessage(string message) 33 | { 34 | Console.WriteLine(message); 35 | } 36 | } 37 | ``` 38 | 39 | The difference is the methods and properties need to be marked as [virtual](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/virtual). This allows AppDomainAlternative to create a proxy instance of the class by overriding those methods and properties. The override of those methods and properties handle the remoting responsibilities, include exceptions thrown by the remote call. 40 | 41 | #### AppDomain Alternative 42 | 43 | Creating a child AppDomain is similar to starting a child process with AppDomainAlternative. Below is an example on how to create a child AppDomain and using the `ChatRoom` class for remoting: 44 | 45 | ``` 46 | var domain = AppDomain.CreateDomain("Some Name"); 47 | var chatRoom = (ChatRoom)domain.CreateInstanceAndUnwrap(typeof(ChatRoom).Assembly.FullName, typeof(ChatRoom).FullName); 48 | chatRoom.SendMessage("Hello World"); 49 | ``` 50 | 51 | That example creates an instance on the child domain and returns a proxied instance of that class to the parent domain for remoting. Below is an example of how to do the same thing with AppDomainAlternative: 52 | 53 | ``` 54 | var domain = Domains.Current.AddChildDomain(new ProcessStartInfo("dotnet", "path to .Net Core assembly")); 55 | var chatRoom = (ChatRoom)await childDomain.Channels.CreateInstance(typeof(ChatRoom).GetConstructors().First(), hostInstance: false).ConfigureAwait(false); 56 | chatRoom.SendMessage("Hello World"); 57 | ``` 58 | 59 | You may notice that AppDomainAlternative does offer additional features that are not included in remoting for AppDomains. One of those features is it supports constructors when creating remote objects. 60 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/AppDomainAlternative.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | AppDomainAlternative 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Ipc/Channels/ChannelListenerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using AppDomainAlternative.Extensions; 9 | using AppDomainAlternative.Proxy; 10 | using NUnit.Framework; 11 | 12 | // ReSharper disable AccessToDisposedClosure 13 | 14 | namespace AppDomainAlternative.Ipc.Channels 15 | { 16 | [TestFixture] 17 | public class ChannelListenerTest 18 | { 19 | public const string ArgumentNullMsg = "The GUID value was null."; 20 | 21 | public class DummyClass 22 | { 23 | public ConcurrentBag Values { get; } = new ConcurrentBag(); 24 | public virtual Task SetValue(Guid? value) 25 | { 26 | if (value == null) 27 | { 28 | throw new ArgumentNullException(ArgumentNullMsg); 29 | } 30 | 31 | Values.Add(value.Value); 32 | 33 | return Task.FromResult(value.ToString()); 34 | } 35 | } 36 | 37 | internal class MockConnectionLayer : MockConnection 38 | { 39 | private ManualResetEventSlim signal; 40 | private readonly ConcurrentQueue<(long id, Stream data)> queue = new ConcurrentQueue<(long id, Stream data)>(); 41 | private int disposed; 42 | 43 | public async void Start(MockChannel channel, CancellationTokenSource cancel) 44 | { 45 | await Task.Yield(); 46 | 47 | signal = new ManualResetEventSlim(false); 48 | 49 | while (!cancel.IsCancellationRequested && disposed == 0) 50 | { 51 | signal.Wait(cancel.Token); 52 | 53 | while (queue.TryDequeue(out var request) && !cancel.IsCancellationRequested && disposed == 0) 54 | { 55 | await channel.Buffer.Fill(request.data, (int)request.data.Length, cancel.Token).ConfigureAwait(false); 56 | } 57 | } 58 | 59 | signal.Dispose(); 60 | } 61 | public override void Terminate(IInternalChannel channel) 62 | { 63 | Dispose(); 64 | base.Terminate(channel); 65 | } 66 | public override void Write(long channelId, Stream stream) 67 | { 68 | queue.Enqueue((channelId, stream)); 69 | signal.Set(); 70 | base.Write(channelId, stream); 71 | } 72 | public override void Dispose() 73 | { 74 | Interlocked.CompareExchange(ref disposed, 1, 0); 75 | signal.Set(); 76 | } 77 | } 78 | 79 | #region Stress Test 80 | 81 | public async void ThreadTest( 82 | CancellationToken cancel, 83 | DummyClass remoteInstance, 84 | ConcurrentBag values, 85 | TaskCompletionSource onFinish) 86 | { 87 | try 88 | { 89 | await Task.Yield(); 90 | 91 | var cancelTask = new TaskCompletionSource(); 92 | var ids = Enumerable.Range(0, 1000).Select(_ => Guid.NewGuid()).ToArray(); 93 | var rnd = new Random(); 94 | 95 | cancel.Register(() => cancelTask.TrySetResult(true)); 96 | 97 | foreach (var value in ids.TakeWhile(_ => !cancel.IsCancellationRequested)) 98 | { 99 | await Task.Yield(); 100 | 101 | Thread.SpinWait(rnd.Next(1, 1000)); 102 | 103 | if (cancel.IsCancellationRequested) 104 | { 105 | break; 106 | } 107 | 108 | values.Add(value); 109 | 110 | var task = remoteInstance.SetValue(value); 111 | 112 | await Task.WhenAny(cancelTask.Task, task).ConfigureAwait(false); 113 | } 114 | 115 | onFinish.TrySetResult(true); 116 | } 117 | catch (Exception error) 118 | { 119 | onFinish.TrySetException(error); 120 | } 121 | } 122 | 123 | [Test] 124 | public async Task StressTest() 125 | { 126 | var proxyGenerator = new DefaultProxyFactory(); 127 | 128 | using (var cancel = new CancellationTokenSource(Debugger.IsAttached ? TimeSpan.FromDays(1) : TimeSpan.FromMinutes(5))) 129 | using (var localConnectionLayer = new MockConnectionLayer()) 130 | using (var remoteConnectionLayer = new MockConnectionLayer()) 131 | using (var localChannel = new MockChannel(localConnectionLayer)) 132 | using (var remoteChannel = new MockChannel(remoteConnectionLayer)) 133 | { 134 | localConnectionLayer.Start(remoteChannel, cancel); 135 | remoteConnectionLayer.Start(localChannel, cancel); 136 | 137 | var localStart = localChannel.LocalStart(cancel, proxyGenerator, 138 | typeof(DummyClass).GetConstructors().First(), 139 | true); 140 | var remoteStart = remoteChannel.RemoteStart(cancel, proxyGenerator); 141 | 142 | await Task.WhenAll(localStart, remoteStart).ConfigureAwait(false); 143 | 144 | var localInstance = (DummyClass)localStart.Result; 145 | var remoteInstance = (DummyClass)remoteStart.Result.instance; 146 | 147 | localChannel.Instance = localInstance; 148 | localChannel.IsHost = true; 149 | localChannel.StartListening(cancel, localChannel.RemoteRequests); 150 | remoteChannel.Instance = remoteInstance; 151 | remoteChannel.IsHost = false; 152 | remoteChannel.StartListening(cancel, remoteChannel.RemoteRequests); 153 | 154 | //stress test 155 | const int threadCount = 10; 156 | var tasks = new TaskCompletionSource[threadCount]; 157 | var values = new ConcurrentBag(); 158 | for (var index = 0; index < threadCount; index++) 159 | { 160 | ThreadTest(cancel.Token, remoteInstance, values, tasks[index] = new TaskCompletionSource()); 161 | } 162 | await Task.WhenAll(tasks.Select(item => item.Task)).ConfigureAwait(false); 163 | 164 | //validate state 165 | Assert.AreEqual(values.Count, localInstance.Values.Count); 166 | Assert.AreEqual(values.Count, values.Count(id => localInstance.Values.Contains(id))); 167 | } 168 | } 169 | 170 | #endregion 171 | 172 | [Test] 173 | public async Task IntegrationTestExceptionCall() 174 | { 175 | var proxyGenerator = new DefaultProxyFactory(); 176 | 177 | using (var cancel = new CancellationTokenSource(Debugger.IsAttached ? TimeSpan.FromDays(1) : TimeSpan.FromMinutes(1))) 178 | using (var localConnectionLayer = new MockConnectionLayer()) 179 | using (var remoteConnectionLayer = new MockConnectionLayer()) 180 | using (var localChannel = new MockChannel(localConnectionLayer)) 181 | using (var remoteChannel = new MockChannel(remoteConnectionLayer)) 182 | { 183 | localConnectionLayer.Start(remoteChannel, cancel); 184 | remoteConnectionLayer.Start(localChannel, cancel); 185 | 186 | var localStart = localChannel.LocalStart(cancel, proxyGenerator, 187 | typeof(DummyClass).GetConstructors().First(), 188 | true); 189 | var remoteStart = remoteChannel.RemoteStart(cancel, proxyGenerator); 190 | 191 | await Task.WhenAll(localStart, remoteStart).ConfigureAwait(false); 192 | 193 | var localInstance = (DummyClass)localStart.Result; 194 | var remoteInstance = (DummyClass)remoteStart.Result.instance; 195 | 196 | localChannel.Instance = localInstance; 197 | localChannel.IsHost = true; 198 | localChannel.StartListening(cancel, localChannel.RemoteRequests); 199 | remoteChannel.Instance = remoteInstance; 200 | remoteChannel.IsHost = false; 201 | remoteChannel.StartListening(cancel, remoteChannel.RemoteRequests); 202 | 203 | //test exception case 204 | Assert.ThrowsAsync(() => remoteInstance.SetValue(null), ArgumentNullMsg); 205 | Assert.AreEqual(0, localInstance.Values.Count); 206 | } 207 | } 208 | 209 | [Test] 210 | public async Task IntegrationTestExceptionFreeCall() 211 | { 212 | var proxyGenerator = new DefaultProxyFactory(); 213 | 214 | using (var cancel = new CancellationTokenSource(Debugger.IsAttached ? TimeSpan.FromDays(1) : TimeSpan.FromMinutes(1))) 215 | using (var localConnectionLayer = new MockConnectionLayer()) 216 | using (var remoteConnectionLayer = new MockConnectionLayer()) 217 | using (var localChannel = new MockChannel(localConnectionLayer)) 218 | using (var remoteChannel = new MockChannel(remoteConnectionLayer)) 219 | { 220 | localConnectionLayer.Start(remoteChannel, cancel); 221 | remoteConnectionLayer.Start(localChannel, cancel); 222 | 223 | var localStart = localChannel.LocalStart(cancel, proxyGenerator, 224 | typeof(DummyClass).GetConstructors().First(), 225 | true); 226 | var remoteStart = remoteChannel.RemoteStart(cancel, proxyGenerator); 227 | 228 | await Task.WhenAll(localStart, remoteStart).ConfigureAwait(false); 229 | 230 | var localInstance = (DummyClass)localStart.Result; 231 | var remoteInstance = (DummyClass)remoteStart.Result.instance; 232 | 233 | localChannel.Instance = localInstance; 234 | localChannel.IsHost = true; 235 | localChannel.StartListening(cancel, localChannel.RemoteRequests); 236 | remoteChannel.Instance = remoteInstance; 237 | remoteChannel.IsHost = false; 238 | remoteChannel.StartListening(cancel, remoteChannel.RemoteRequests); 239 | 240 | //test exception free case 241 | var value = Guid.NewGuid(); 242 | await remoteInstance.SetValue(value).ConfigureAwait(false); 243 | Assert.AreEqual(1, localInstance.Values.Count); 244 | Assert.AreEqual(value, localInstance.Values.First()); 245 | 246 | localInstance.Values.Clear(); 247 | } 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Ipc/Channels/ChannelStartTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using AppDomainAlternative.Extensions; 8 | using AppDomainAlternative.Proxy; 9 | using NUnit.Framework; 10 | 11 | // ReSharper disable AccessToModifiedClosure 12 | 13 | namespace AppDomainAlternative.Ipc.Channels 14 | { 15 | [TestFixture] 16 | public class ChannelStartTest 17 | { 18 | private static async Task test(bool hostInstance, params object[] arguments) 19 | { 20 | var proxyGenerator = new DefaultProxyFactory(); 21 | 22 | using (var cancel = new CancellationTokenSource(Debugger.IsAttached ? TimeSpan.FromDays(1) : TimeSpan.FromMinutes(1))) 23 | using (var localChannel = new MockChannel()) 24 | using (var remoteChannel = new MockChannel()) 25 | { 26 | var writeTask = new TaskCompletionSource<(long id, Stream data)>(); 27 | cancel.Token.Register(() => writeTask.TrySetCanceled()); 28 | localChannel.MockConnection.Writes += (id, data) => writeTask.TrySetResult((id, data)); 29 | remoteChannel.MockConnection.Writes += (id, data) => writeTask.TrySetResult((id, data)); 30 | 31 | //make a local start on another thread 32 | var localStart = localChannel.LocalStart(cancel, proxyGenerator, 33 | typeof(DummyClass).GetConstructors() 34 | .FirstOrDefault(ctor => ctor.GetParameters().Length == arguments.Length) ?? 35 | typeof(DummyClass).GetConstructors() 36 | .First(ctor => ctor.GetParameters().Length != arguments.Length), 37 | hostInstance, 38 | arguments); 39 | if (localStart.IsCompleted) 40 | { 41 | //this usually means an error happened so lets throw it now 42 | await localStart.ConfigureAwait(false); 43 | } 44 | 45 | //wait for local start to start the waiting step for a response 46 | await writeTask.Task.ConfigureAwait(false); 47 | 48 | //read the write request from the local channel 49 | var writeRequest = writeTask.Task.Result; 50 | Assert.AreEqual(localChannel.Id, writeRequest.id); 51 | Assert.IsNotNull(writeRequest.data); 52 | writeTask = new TaskCompletionSource<(long id, Stream data)>(); 53 | 54 | //send the request to the remote channel 55 | await remoteChannel.Buffer.Fill(writeRequest.data, (int)writeRequest.data.Length, cancel.Token).ConfigureAwait(false); 56 | 57 | var remoteStart = await remoteChannel.RemoteStart(cancel, proxyGenerator).ConfigureAwait(false); 58 | await writeTask.Task.ConfigureAwait(false); 59 | 60 | //read the write request from the remote channel 61 | writeRequest = writeTask.Task.Result; 62 | Assert.AreEqual(remoteChannel.Id, writeRequest.id); 63 | Assert.IsNotNull(writeRequest.data); 64 | 65 | //send the request to the local channel 66 | await localChannel.Buffer.Fill(writeRequest.data, (int)writeRequest.data.Length, cancel.Token).ConfigureAwait(false); 67 | 68 | //wait for all start tasks to finish 69 | await localStart.ConfigureAwait(false); 70 | 71 | //validate local instance 72 | var localInstance = localStart.Result as DummyClass; 73 | Assert.IsNotNull(localInstance); 74 | 75 | Assert.AreEqual(localInstance.Arg1, arguments.Skip(0).FirstOrDefault()); 76 | Assert.AreEqual(localInstance.Arg2, arguments.Skip(1).FirstOrDefault()); 77 | Assert.AreEqual(localInstance.Arg3, arguments.Skip(2).FirstOrDefault()); 78 | 79 | //validate remote instance 80 | var remoteInstance = remoteStart.instance as DummyClass; 81 | Assert.IsNotNull(remoteInstance); 82 | 83 | Assert.AreEqual(remoteInstance.Arg1, arguments.Skip(0).FirstOrDefault()); 84 | Assert.AreEqual(remoteInstance.Arg2, arguments.Skip(1).FirstOrDefault()); 85 | Assert.AreEqual(remoteInstance.Arg3, arguments.Skip(2).FirstOrDefault()); 86 | 87 | //validate proxy was generated correctly 88 | if (hostInstance) 89 | { 90 | Assert.IsFalse(remoteStart.isHost); 91 | 92 | Assert.AreEqual(typeof(DummyClass), localInstance.GetType()); 93 | Assert.AreNotEqual(typeof(DummyClass), remoteInstance.GetType()); 94 | } 95 | else 96 | { 97 | Assert.IsTrue(remoteStart.isHost); 98 | 99 | Assert.AreNotEqual(typeof(DummyClass), localInstance.GetType()); 100 | Assert.AreEqual(typeof(DummyClass), remoteInstance.GetType()); 101 | } 102 | } 103 | } 104 | 105 | public class DummyClass 106 | { 107 | public DummyClass() 108 | { 109 | } 110 | public DummyClass(Guid arg1) => Arg1 = arg1; 111 | public DummyClass(Guid arg1, DateTime arg2) 112 | { 113 | Arg1 = arg1; 114 | Arg2 = arg2; 115 | } 116 | public DummyClass(Guid arg1, DateTime arg2, int arg3) 117 | { 118 | Arg1 = arg1; 119 | Arg2 = arg2; 120 | Arg3 = arg3; 121 | } 122 | 123 | public readonly Guid? Arg1; 124 | public readonly DateTime? Arg2; 125 | public readonly int? Arg3; 126 | public virtual void EnabledMethod() => throw new NotSupportedException(); 127 | } 128 | 129 | [Test] 130 | public async Task IntegrationTest() 131 | { 132 | await test(true, 133 | Guid.NewGuid()).ConfigureAwait(false); 134 | 135 | await test(true, 136 | Guid.NewGuid(), 137 | DateTime.UtcNow).ConfigureAwait(false); 138 | 139 | await test(true, 140 | Guid.NewGuid(), 141 | DateTime.UtcNow, 142 | 13).ConfigureAwait(false); 143 | 144 | await test(false, 145 | Guid.NewGuid()).ConfigureAwait(false); 146 | 147 | await test(false, 148 | Guid.NewGuid(), 149 | DateTime.UtcNow).ConfigureAwait(false); 150 | 151 | await test(false, 152 | Guid.NewGuid(), 153 | DateTime.UtcNow, 154 | 17).ConfigureAwait(false); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Ipc/Channels/MockChannel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using AppDomainAlternative.Extensions; 11 | using AppDomainAlternative.Proxy; 12 | using AppDomainAlternative.Serializer; 13 | using AppDomainAlternative.Serializer.Default; 14 | 15 | namespace AppDomainAlternative.Ipc.Channels 16 | { 17 | internal class MockChannel : IInternalChannel 18 | { 19 | public MockChannel(MockConnection connection = null) 20 | { 21 | MockConnection = connection ?? new MockConnection(); 22 | Buffer = new ReadWriteBuffer(Id = 7); 23 | Reader = new BinaryReader(Buffer, Encoding.UTF8); 24 | } 25 | 26 | public BinaryReader Reader { get; } 27 | public ConcurrentDictionary> RemoteRequests { get; } = new ConcurrentDictionary>(); 28 | public IAmASerializer Serializer { get; } = new DefaultSerializer(); 29 | public IConnection Connection => MockConnection; 30 | public MockConnection MockConnection { get; } 31 | public ReadWriteBuffer Buffer { get; } 32 | public Task RemoteInvoke(bool fireAndForget, string methodName, params Tuple[] args) => 33 | this.RemoteInvoke(RemoteRequests, () => Interlocked.Increment(ref RequestCounter), fireAndForget, methodName, args); 34 | public Task LocalStart(IGenerateProxies proxyGenerator, ConstructorInfo ctor, bool hostInstance, params object[] arguments) => throw new NotSupportedException(); 35 | public Task LocalStart(T instance) 36 | where T : class, new() => throw new NotSupportedException(); 37 | public Task RemoteStart(IGenerateProxies proxyGenerator) => throw new NotSupportedException(); 38 | public bool IsDisposed { get; } = false; 39 | public bool IsHost { get; set; } 40 | public int RequestCounter; 41 | public long Id { get; } 42 | public object Instance { get; set; } 43 | public void Dispose() 44 | { 45 | } 46 | } 47 | 48 | internal class MockConnection : IConnection 49 | { 50 | IEnumerator IEnumerable.GetEnumerator() => throw new NotSupportedException(); 51 | IEnumerator IEnumerable.GetEnumerator() => throw new NotSupportedException(); 52 | Task IConnection.CreateInstance(ConstructorInfo ctor, bool hostInstance, params object[] arguments) => throw new NotSupportedException(); 53 | bool IResolveProxyIds.TryToGetInstanceId(object instance, out long id) 54 | { 55 | id = 0; 56 | return false; 57 | } 58 | bool IResolveProxyIds.TryToGetInstance(long id, out object instance) 59 | { 60 | instance = null; 61 | return false; 62 | } 63 | public event Action NewChannel; 64 | 65 | public event Action Terminations; 66 | public virtual void Terminate(IInternalChannel channel) => Terminations?.Invoke(); 67 | 68 | public event Action Writes; 69 | public virtual void Write(long channelId, Stream stream) => Writes?.Invoke(channelId, stream); 70 | 71 | public virtual void Dispose() 72 | { 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Ipc/ReadWriteBufferTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Security.Cryptography; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using NUnit.Framework; 9 | 10 | // ReSharper disable AccessToDisposedClosure 11 | 12 | namespace AppDomainAlternative.Ipc 13 | { 14 | [TestFixture] 15 | public class ReadWriteBufferTests 16 | { 17 | [Test] 18 | public void MultipleReadAttemptsTest() 19 | { 20 | //test that multiple attempts to read when the first read operation has not finished triggers an error 21 | 22 | var buffer = new ReadWriteBuffer(0); 23 | 24 | var writeBuffer = Guid.NewGuid().ToByteArray(); 25 | var readBuffer = new byte[writeBuffer.Length]; 26 | 27 | buffer.ReadAsync(readBuffer, 0, readBuffer.Length); 28 | 29 | Assert.Catch(() => buffer.ReadAsync(readBuffer, 0, readBuffer.Length)); 30 | } 31 | 32 | [Test] 33 | public async Task ReadThenWriteTest() 34 | { 35 | var buffer = new ReadWriteBuffer(0); 36 | 37 | var writeBuffer = Guid.NewGuid().ToByteArray(); 38 | var readBuffer = new byte[writeBuffer.Length]; 39 | 40 | var read = buffer.ReadAsync(readBuffer, 0, readBuffer.Length); 41 | 42 | await buffer.Fill(new MemoryStream(writeBuffer), writeBuffer.Length, CancellationToken.None).ConfigureAwait(false); 43 | 44 | Assert.AreEqual(readBuffer.Length, await read.ConfigureAwait(false)); 45 | Assert.AreEqual(new Guid(writeBuffer), new Guid(readBuffer)); 46 | } 47 | 48 | [Test] 49 | public async Task StressTest() 50 | { 51 | var randomData = new byte[1_048_576];//1 MB 52 | var readData = new byte[randomData.Length]; 53 | var rnd = new Random(846845445); 54 | 55 | rnd.NextBytes(randomData); 56 | 57 | foreach (var __ in Enumerable.Range(0, 100)) 58 | { 59 | using (var buffer = new ReadWriteBuffer(0)) 60 | { 61 | var readTask = new TaskCompletionSource(); 62 | var timer = new Stopwatch(); 63 | var writeTask = new TaskCompletionSource(); 64 | 65 | //write thread 66 | ThreadPool.QueueUserWorkItem(async _ => 67 | { 68 | try 69 | { 70 | var writeIndex = 0; 71 | 72 | timer.Start(); 73 | 74 | while (writeIndex < randomData.Length) 75 | { 76 | var writeLength = Math.Min(rnd.Next(1, 1000), randomData.Length - writeIndex); 77 | await buffer.Fill(new MemoryStream(randomData, writeIndex, writeLength, false), writeLength, CancellationToken.None).ConfigureAwait(false); 78 | Thread.SpinWait(rnd.Next(1, 1000)); 79 | writeIndex += writeLength; 80 | } 81 | 82 | writeTask.TrySetResult(null); 83 | } 84 | catch (Exception error) 85 | { 86 | writeTask.TrySetException(error); 87 | } 88 | }); 89 | 90 | //read thread 91 | ThreadPool.QueueUserWorkItem(async _ => 92 | { 93 | try 94 | { 95 | var readIndex = 0; 96 | while (readIndex < randomData.Length) 97 | { 98 | var readLength = await buffer.ReadAsync(readData, readIndex, Math.Min(rnd.Next(1, 1000), readData.Length - readIndex)).ConfigureAwait(false); 99 | Thread.SpinWait(rnd.Next(1, 1000)); 100 | readIndex += readLength; 101 | } 102 | 103 | timer.Stop(); 104 | 105 | readTask.TrySetResult(null); 106 | } 107 | catch (Exception error) 108 | { 109 | readTask.TrySetException(error); 110 | } 111 | }); 112 | 113 | await Task.WhenAll(readTask.Task, writeTask.Task).ConfigureAwait(false); 114 | 115 | Console.WriteLine($"Time: {timer.Elapsed.TotalMilliseconds:0.00} ms"); 116 | 117 | using (var md5 = MD5.Create()) 118 | { 119 | var randomDataHash = new Guid(md5.ComputeHash(randomData)); 120 | var readDataHash = new Guid(md5.ComputeHash(readData)); 121 | 122 | Console.WriteLine($"random data ({randomDataHash}) == read data ({readDataHash})"); 123 | 124 | Assert.AreEqual(randomDataHash, readDataHash); 125 | } 126 | } 127 | } 128 | } 129 | 130 | [Test] 131 | public async Task WriteThenReadTest() 132 | { 133 | var buffer = new ReadWriteBuffer(0); 134 | 135 | var writeBuffer = Guid.NewGuid().ToByteArray(); 136 | var readBuffer = new byte[writeBuffer.Length]; 137 | 138 | await buffer.Fill(new MemoryStream(writeBuffer), writeBuffer.Length, CancellationToken.None).ConfigureAwait(false); 139 | 140 | Assert.AreEqual(readBuffer.Length, await buffer.ReadAsync(readBuffer, 0, readBuffer.Length).ConfigureAwait(false)); 141 | Assert.AreEqual(new Guid(writeBuffer), new Guid(readBuffer)); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Proxy/ConstructorsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using NUnit.Framework; 5 | 6 | namespace AppDomainAlternative.Proxy 7 | { 8 | [TestFixture] 9 | public class ConstructorsTests 10 | { 11 | public class SampleClass 12 | { 13 | public SampleClass(int a, DateTime b, string c) 14 | { 15 | CtorArgs = 3; 16 | 17 | A = a; 18 | B = b; 19 | C = c; 20 | } 21 | public SampleClass(int a, DateTime b) 22 | { 23 | CtorArgs = 2; 24 | 25 | A = a; 26 | B = b; 27 | C = null; 28 | } 29 | public SampleClass(int a) 30 | { 31 | CtorArgs = 1; 32 | 33 | A = a; 34 | B = default(DateTime); 35 | C = null; 36 | } 37 | 38 | public readonly int CtorArgs; 39 | 40 | public readonly int A; 41 | public readonly DateTime B; 42 | public readonly string C; 43 | 44 | public virtual void SyncMethod(int a, DateTime b, string c) => throw new NotSupportedException(); 45 | } 46 | 47 | private void test(DefaultProxyFactory factory, MockInterceptor interceptor, ConstructorInfo ctor, params object[] arguments) 48 | { 49 | var proxyInstance = (SampleClass)factory.GenerateProxy(interceptor, ctor, arguments); 50 | 51 | Assert.AreEqual(interceptor.Logs.Count, 0); 52 | 53 | Assert.AreEqual(arguments.Length, proxyInstance.CtorArgs); 54 | 55 | Assert.AreEqual(arguments[0], proxyInstance.A); 56 | Assert.AreEqual(arguments.Length > 1 ? arguments[1] : default(DateTime), proxyInstance.B); 57 | Assert.AreEqual(arguments.Length > 2 ? arguments[2] : default(string), proxyInstance.C); 58 | } 59 | 60 | [Test] 61 | public void Test() 62 | { 63 | var factory = new DefaultProxyFactory(); 64 | var interceptor = new MockInterceptor(); 65 | 66 | var constructors = typeof(SampleClass).GetConstructors(); 67 | 68 | test(factory, interceptor, constructors.First(ctor => ctor.GetParameters().Length == 3), 69 | int.MinValue, DateTime.UtcNow, "Hello World"); 70 | 71 | test(factory, interceptor, constructors.First(ctor => ctor.GetParameters().Length == 2), 72 | int.MinValue, DateTime.UtcNow); 73 | 74 | test(factory, interceptor, constructors.First(ctor => ctor.GetParameters().Length == 1), 75 | int.MinValue); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Proxy/MethodsWithArgumentsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NUnit.Framework; 4 | 5 | namespace AppDomainAlternative.Proxy 6 | { 7 | [TestFixture] 8 | public class MethodsWithArgumentsTests 9 | { 10 | public class SampleClass 11 | { 12 | public virtual Task AsyncMethod(int a, DateTime b, string c) => throw new NotSupportedException(); 13 | public virtual void SyncMethod(int a, DateTime b, string c) => throw new NotSupportedException(); 14 | } 15 | 16 | [Test] 17 | public async Task Test() 18 | { 19 | var factory = new DefaultProxyFactory(); 20 | var interceptor = new MockInterceptor(); 21 | 22 | var proxyInstance = (SampleClass)factory.GenerateProxy(interceptor, typeof(SampleClass).GetConstructor(Type.EmptyTypes)); 23 | 24 | Assert.AreEqual(interceptor.Logs.Count, 0); 25 | 26 | foreach (var values in new[] 27 | { 28 | (a: int.MinValue, b: DateTime.MinValue, c:string.Empty), 29 | (a: 0, b: DateTime.UtcNow, c:(string)null), 30 | (a: int.MaxValue, b: DateTime.MaxValue, c:"Hello World") 31 | }) 32 | { 33 | //SyncMethod 34 | proxyInstance.SyncMethod(values.a, values.b, values.c); 35 | 36 | Assert.AreEqual(interceptor.Logs.Count, 1); 37 | var remoteInvoke = interceptor.Logs.Dequeue(); 38 | Assert.AreEqual(false, remoteInvoke.FireAndForget); 39 | Assert.AreEqual(nameof(proxyInstance.SyncMethod), remoteInvoke.MethodName); 40 | Assert.AreEqual(typeof(object), remoteInvoke.ReturnType); 41 | Assert.AreEqual(3, remoteInvoke.Args.Length); 42 | Assert.AreEqual(typeof(int), remoteInvoke.Args[0].Item1); 43 | Assert.AreEqual(values.a, remoteInvoke.Args[0].Item2); 44 | Assert.AreEqual(typeof(DateTime), remoteInvoke.Args[1].Item1); 45 | Assert.AreEqual(values.b, remoteInvoke.Args[1].Item2); 46 | Assert.AreEqual(typeof(string), remoteInvoke.Args[2].Item1); 47 | Assert.AreEqual(values.c, remoteInvoke.Args[2].Item2); 48 | 49 | //AsyncMethod 50 | await proxyInstance.AsyncMethod(values.a, values.b, values.c).ConfigureAwait(false); 51 | 52 | Assert.AreEqual(interceptor.Logs.Count, 1); 53 | remoteInvoke = interceptor.Logs.Dequeue(); 54 | Assert.AreEqual(false, remoteInvoke.FireAndForget); 55 | Assert.AreEqual(nameof(proxyInstance.AsyncMethod), remoteInvoke.MethodName); 56 | Assert.AreEqual(typeof(object), remoteInvoke.ReturnType); 57 | Assert.AreEqual(3, remoteInvoke.Args.Length); 58 | Assert.AreEqual(typeof(int), remoteInvoke.Args[0].Item1); 59 | Assert.AreEqual(values.a, remoteInvoke.Args[0].Item2); 60 | Assert.AreEqual(typeof(DateTime), remoteInvoke.Args[1].Item1); 61 | Assert.AreEqual(values.b, remoteInvoke.Args[1].Item2); 62 | Assert.AreEqual(typeof(string), remoteInvoke.Args[2].Item1); 63 | Assert.AreEqual(values.c, remoteInvoke.Args[2].Item2); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Proxy/MockInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using AppDomainAlternative.Serializer; 5 | 6 | namespace AppDomainAlternative.Proxy 7 | { 8 | public class MockInterceptor : IInterceptor 9 | { 10 | public IAmASerializer Serializer => MockSerializer; 11 | public MockSerializer MockSerializer { get; } = new MockSerializer(); 12 | public async Task RemoteInvoke(bool fireAndForget, string methodName, params Tuple[] args) 13 | { 14 | await Task.Yield(); 15 | 16 | Logs.Enqueue(new InvokeArgs 17 | { 18 | Args = args, 19 | FireAndForget = fireAndForget, 20 | MethodName = methodName, 21 | ReturnType = typeof(T) 22 | }); 23 | 24 | //wait to simulate the time it takes to remote invoke the call across the process barrier 25 | await Task.Delay(1).ConfigureAwait(false); 26 | 27 | return (T)RemoteInvokeReturnValue; 28 | } 29 | public object RemoteInvokeReturnValue { get; set; } 30 | public readonly Queue Logs = new Queue(); 31 | } 32 | 33 | public class InvokeArgs 34 | { 35 | public Tuple[] Args { get; set; } 36 | public Type ReturnType { get; set; } 37 | public bool FireAndForget { get; set; } 38 | public string MethodName { get; set; } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Proxy/MockSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using AppDomainAlternative.Serializer; 6 | 7 | namespace AppDomainAlternative.Proxy 8 | { 9 | public class MockSerializer : IAmASerializer 10 | { 11 | public Task Serialize(BinaryWriter writer, Type valueType, object value, IResolveProxyIds resolver) => throw new NotSupportedException(); 12 | 13 | public Task Deserialize(BinaryReader reader, Type valueType, IResolveProxyIds resolver, CancellationToken token) => throw new NotSupportedException(); 14 | 15 | public Func CanSerializeInterceptor = _ => true; 16 | public bool CanSerialize(Type type) => CanSerializeInterceptor(type); 17 | 18 | public string Name { get; } = nameof(MockSerializer); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Proxy/PropertiesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NUnit.Framework; 4 | 5 | namespace AppDomainAlternative.Proxy 6 | { 7 | [TestFixture] 8 | public class PropertiesTests 9 | { 10 | public class SampleClass 11 | { 12 | public virtual Task AsyncGetProperty { get; } = null; 13 | //NOTE: Task<> for set property methods is not supported since Task<> cannot be serialized 14 | 15 | public virtual int SyncGetProperty { get; } = 0; 16 | public virtual int SyncGetSetProperty { get; set; } 17 | public virtual int SyncSetProperty { set => throw new NotSupportedException(); } 18 | } 19 | 20 | [Test] 21 | public async Task Test() 22 | { 23 | var factory = new DefaultProxyFactory(); 24 | var interceptor = new MockInterceptor(); 25 | 26 | var proxyInstance = (SampleClass)factory.GenerateProxy(interceptor, typeof(SampleClass).GetConstructor(Type.EmptyTypes)); 27 | 28 | Assert.AreEqual(interceptor.Logs.Count, 0); 29 | 30 | foreach (var value in new [] { int.MinValue, 1, int.MaxValue }) 31 | { 32 | //SyncGetProperty 33 | interceptor.RemoteInvokeReturnValue = value; 34 | var returnValue = proxyInstance.SyncGetProperty; 35 | Assert.AreEqual(value, returnValue); 36 | 37 | Assert.AreEqual(interceptor.Logs.Count, 1); 38 | var remoteInvoke = interceptor.Logs.Dequeue(); 39 | Assert.AreEqual(false, remoteInvoke.FireAndForget); 40 | Assert.AreEqual($"get_{nameof(proxyInstance.SyncGetProperty)}", remoteInvoke.MethodName); 41 | Assert.AreEqual(typeof(int), remoteInvoke.ReturnType); 42 | Assert.AreEqual(0, remoteInvoke.Args.Length); 43 | 44 | //AsyncGetProperty 45 | interceptor.RemoteInvokeReturnValue = value; 46 | returnValue = await proxyInstance.AsyncGetProperty.ConfigureAwait(false); 47 | Assert.AreEqual(value, returnValue); 48 | 49 | Assert.AreEqual(interceptor.Logs.Count, 1); 50 | remoteInvoke = interceptor.Logs.Dequeue(); 51 | Assert.AreEqual(false, remoteInvoke.FireAndForget); 52 | Assert.AreEqual($"get_{nameof(proxyInstance.AsyncGetProperty)}", remoteInvoke.MethodName); 53 | Assert.AreEqual(typeof(int), remoteInvoke.ReturnType); 54 | Assert.AreEqual(0, remoteInvoke.Args.Length); 55 | 56 | //SyncSetProperty 57 | proxyInstance.SyncSetProperty = value; 58 | 59 | Assert.AreEqual(interceptor.Logs.Count, 1); 60 | remoteInvoke = interceptor.Logs.Dequeue(); 61 | Assert.AreEqual(false, remoteInvoke.FireAndForget); 62 | Assert.AreEqual($"set_{nameof(proxyInstance.SyncSetProperty)}", remoteInvoke.MethodName); 63 | Assert.AreEqual(typeof(object), remoteInvoke.ReturnType); 64 | Assert.AreEqual(1, remoteInvoke.Args.Length); 65 | Assert.AreEqual(typeof(int), remoteInvoke.Args[0].Item1); 66 | Assert.AreEqual(value, remoteInvoke.Args[0].Item2); 67 | 68 | //SyncGetSetProperty - get 69 | interceptor.RemoteInvokeReturnValue = value; 70 | returnValue = proxyInstance.SyncGetSetProperty; 71 | Assert.AreEqual(value, returnValue); 72 | 73 | Assert.AreEqual(interceptor.Logs.Count, 1); 74 | remoteInvoke = interceptor.Logs.Dequeue(); 75 | Assert.AreEqual(false, remoteInvoke.FireAndForget); 76 | Assert.AreEqual($"get_{nameof(proxyInstance.SyncGetSetProperty)}", remoteInvoke.MethodName); 77 | Assert.AreEqual(typeof(int), remoteInvoke.ReturnType); 78 | Assert.AreEqual(0, remoteInvoke.Args.Length); 79 | 80 | //SyncGetSetProperty - set 81 | proxyInstance.SyncGetSetProperty = value; 82 | 83 | Assert.AreEqual(interceptor.Logs.Count, 1); 84 | remoteInvoke = interceptor.Logs.Dequeue(); 85 | Assert.AreEqual(false, remoteInvoke.FireAndForget); 86 | Assert.AreEqual($"set_{nameof(proxyInstance.SyncGetSetProperty)}", remoteInvoke.MethodName); 87 | Assert.AreEqual(typeof(object), remoteInvoke.ReturnType); 88 | Assert.AreEqual(1, remoteInvoke.Args.Length); 89 | Assert.AreEqual(typeof(int), remoteInvoke.Args[0].Item1); 90 | Assert.AreEqual(value, remoteInvoke.Args[0].Item2); 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Proxy/ProxyOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NUnit.Framework; 4 | 5 | namespace AppDomainAlternative.Proxy 6 | { 7 | [TestFixture] 8 | public class ProxyOptionsTests 9 | { 10 | public class TestEnabledClass 11 | { 12 | public virtual void EnabledMethod() => throw new NotSupportedException(); 13 | 14 | [Proxy(Enabled = false)] 15 | public virtual void DisabledMethod() => throw new NotSupportedException(); 16 | } 17 | [Test] 18 | public void TestEnabled() 19 | { 20 | var factory = new DefaultProxyFactory(); 21 | var interceptor = new MockInterceptor(); 22 | 23 | var proxyInstance = (TestEnabledClass)factory.GenerateProxy(interceptor, typeof(TestEnabledClass).GetConstructor(Type.EmptyTypes)); 24 | 25 | Assert.AreEqual(interceptor.Logs.Count, 0); 26 | 27 | Assert.Catch(proxyInstance.DisabledMethod); 28 | 29 | Assert.AreEqual(interceptor.Logs.Count, 0); 30 | } 31 | 32 | public class TestFireAndForgetClass 33 | { 34 | [Proxy(FireAndForget = true)] 35 | public virtual Task FireAndForgetReturnTaskValueMethod() => throw new NotSupportedException(); 36 | 37 | [Proxy(FireAndForget = true)] 38 | public virtual Task FireAndForgetTaskMethod() => throw new NotSupportedException(); 39 | 40 | [Proxy(FireAndForget = true)] 41 | public virtual int FireAndForgetReturnValueMethod() => throw new NotSupportedException(); 42 | 43 | [Proxy(FireAndForget = true)] 44 | public virtual void FireAndForgetVoidMethod() => throw new NotSupportedException(); 45 | } 46 | [Test] 47 | public async Task TestFireAndForget() 48 | { 49 | var factory = new DefaultProxyFactory(); 50 | var interceptor = new MockInterceptor(); 51 | 52 | var proxyInstance = (TestFireAndForgetClass)factory.GenerateProxy(interceptor, typeof(TestFireAndForgetClass).GetConstructor(Type.EmptyTypes)); 53 | 54 | Assert.AreEqual(interceptor.Logs.Count, 0); 55 | 56 | interceptor.RemoteInvokeReturnValue = 0; 57 | await proxyInstance.FireAndForgetReturnTaskValueMethod().ConfigureAwait(false); 58 | Assert.AreEqual(interceptor.Logs.Count, 1); 59 | var remoteInvoke = interceptor.Logs.Dequeue(); 60 | Assert.AreEqual(remoteInvoke.FireAndForget, false); 61 | 62 | interceptor.RemoteInvokeReturnValue = null; 63 | await proxyInstance.FireAndForgetTaskMethod().ConfigureAwait(false); 64 | Assert.AreEqual(interceptor.Logs.Count, 1); 65 | remoteInvoke = interceptor.Logs.Dequeue(); 66 | Assert.AreEqual(remoteInvoke.FireAndForget, true); 67 | 68 | interceptor.RemoteInvokeReturnValue = 1; 69 | proxyInstance.FireAndForgetReturnValueMethod(); 70 | Assert.AreEqual(interceptor.Logs.Count, 1); 71 | remoteInvoke = interceptor.Logs.Dequeue(); 72 | Assert.AreEqual(remoteInvoke.FireAndForget, false); 73 | 74 | interceptor.RemoteInvokeReturnValue = null; 75 | proxyInstance.FireAndForgetVoidMethod(); 76 | Assert.AreEqual(interceptor.Logs.Count, 1); 77 | remoteInvoke = interceptor.Logs.Dequeue(); 78 | Assert.AreEqual(remoteInvoke.FireAndForget, true); 79 | } 80 | 81 | public class TestInvalidTypeHandlingIgnoreClass 82 | { 83 | public virtual void EnabledMethod() => throw new NotSupportedException(); 84 | 85 | [Proxy(InvalidTypeHandling = InvalidTypeHandling.Ignore)] 86 | public virtual int InvalidTypeHandlingMethod() => throw new NotSupportedException(); 87 | } 88 | [Test] 89 | public void TestInvalidTypeHandlingIgnore() 90 | { 91 | var factory = new DefaultProxyFactory(); 92 | var interceptor = new MockInterceptor(); 93 | 94 | interceptor.MockSerializer.CanSerializeInterceptor = _ => false; 95 | 96 | var proxyInstance = (TestInvalidTypeHandlingIgnoreClass)factory.GenerateProxy(interceptor, typeof(TestInvalidTypeHandlingIgnoreClass).GetConstructor(Type.EmptyTypes)); 97 | 98 | Assert.AreEqual(interceptor.Logs.Count, 0); 99 | 100 | Assert.Catch(() => proxyInstance.InvalidTypeHandlingMethod()); 101 | 102 | Assert.AreEqual(interceptor.Logs.Count, 0); 103 | } 104 | 105 | public class TestInvalidTypeHandlingThrowErrorOnCreateClass 106 | { 107 | public virtual void EnabledMethod() => throw new NotSupportedException(); 108 | 109 | [Proxy(InvalidTypeHandling = InvalidTypeHandling.ThrowErrorOnCreate)] 110 | public virtual int InvalidTypeHandlingMethod() => throw new NotSupportedException(); 111 | } 112 | [Test] 113 | public void TestInvalidTypeHandlingThrowErrorOnCreate() 114 | { 115 | var factory = new DefaultProxyFactory(); 116 | var interceptor = new MockInterceptor(); 117 | 118 | interceptor.MockSerializer.CanSerializeInterceptor = _ => false; 119 | 120 | Assert.Catch(() => factory.GenerateProxy(interceptor, typeof(TestInvalidTypeHandlingThrowErrorOnCreateClass).GetConstructor(Type.EmptyTypes))); 121 | } 122 | 123 | public class TestInvalidTypeHandlingThrowErrorOnInvokeClass 124 | { 125 | [Proxy(InvalidTypeHandling = InvalidTypeHandling.ThrowErrorOnInvoke)] 126 | public virtual int InvalidTypeHandlingMethod() => throw new NotSupportedException(); 127 | } 128 | [Test] 129 | public void TestInvalidTypeHandlingThrowErrorOnInvoke() 130 | { 131 | var factory = new DefaultProxyFactory(); 132 | var interceptor = new MockInterceptor(); 133 | 134 | interceptor.MockSerializer.CanSerializeInterceptor = _ => false; 135 | 136 | var proxyInstance = (TestInvalidTypeHandlingThrowErrorOnInvokeClass)factory.GenerateProxy(interceptor, typeof(TestInvalidTypeHandlingThrowErrorOnInvokeClass).GetConstructor(Type.EmptyTypes)); 137 | 138 | Assert.AreEqual(interceptor.Logs.Count, 0); 139 | 140 | Assert.Catch(() => proxyInstance.InvalidTypeHandlingMethod()); 141 | 142 | Assert.AreEqual(interceptor.Logs.Count, 0); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Proxy/ReturnValueMethodsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NUnit.Framework; 4 | 5 | namespace AppDomainAlternative.Proxy 6 | { 7 | [TestFixture] 8 | public class ReturnValueMethodsTests 9 | { 10 | public class SampleClass 11 | { 12 | public virtual Task AsyncMethod() => throw new NotSupportedException(); 13 | public virtual T SyncMethod() => throw new NotSupportedException(); 14 | } 15 | 16 | private async Task test(DefaultProxyFactory factory, MockInterceptor interceptor, params T[] values) 17 | { 18 | var proxyInstance = (SampleClass)factory.GenerateProxy(interceptor, typeof(SampleClass).GetConstructor(Type.EmptyTypes)); 19 | 20 | Assert.AreEqual(interceptor.Logs.Count, 0); 21 | 22 | foreach (var value in values) 23 | { 24 | //SyncMethod 25 | interceptor.RemoteInvokeReturnValue = value; 26 | var returnValue = proxyInstance.SyncMethod(); 27 | Assert.AreEqual(value, returnValue); 28 | 29 | Assert.AreEqual(interceptor.Logs.Count, 1); 30 | var remoteInvoke = interceptor.Logs.Dequeue(); 31 | Assert.AreEqual(false, remoteInvoke.FireAndForget); 32 | Assert.AreEqual(nameof(proxyInstance.SyncMethod), remoteInvoke.MethodName); 33 | Assert.AreEqual(typeof(T), remoteInvoke.ReturnType); 34 | Assert.AreEqual(0, remoteInvoke.Args.Length); 35 | 36 | //AsyncMethod 37 | interceptor.RemoteInvokeReturnValue = value; 38 | returnValue = await proxyInstance.AsyncMethod().ConfigureAwait(false); 39 | Assert.AreEqual(value, returnValue); 40 | 41 | Assert.AreEqual(interceptor.Logs.Count, 1); 42 | remoteInvoke = interceptor.Logs.Dequeue(); 43 | Assert.AreEqual(false, remoteInvoke.FireAndForget); 44 | Assert.AreEqual(nameof(proxyInstance.AsyncMethod), remoteInvoke.MethodName); 45 | Assert.AreEqual(typeof(T), remoteInvoke.ReturnType); 46 | Assert.AreEqual(0, remoteInvoke.Args.Length); 47 | } 48 | } 49 | 50 | [Test] 51 | public async Task Test() 52 | { 53 | var factory = new DefaultProxyFactory(); 54 | var interceptor = new MockInterceptor(); 55 | 56 | //test primitive 57 | await test(factory, interceptor, int.MinValue, 1, int.MaxValue).ConfigureAwait(false); 58 | 59 | //test struct 60 | await test(factory, interceptor, DateTime.MinValue, DateTime.UtcNow, DateTime.MaxValue).ConfigureAwait(false); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Proxy/VoidMethodsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NUnit.Framework; 4 | 5 | namespace AppDomainAlternative.Proxy 6 | { 7 | [TestFixture] 8 | public class VoidMethodsTests 9 | { 10 | public class SampleClass 11 | { 12 | public virtual Task AsyncMethod() => throw new NotSupportedException(); 13 | public virtual void SyncMethod() => throw new NotSupportedException(); 14 | } 15 | 16 | [Test] 17 | public async Task Test() 18 | { 19 | var factory = new DefaultProxyFactory(); 20 | var interceptor = new MockInterceptor(); 21 | 22 | var proxyInstance = (SampleClass)factory.GenerateProxy(interceptor, typeof(SampleClass).GetConstructor(Type.EmptyTypes)); 23 | 24 | Assert.AreEqual(interceptor.Logs.Count, 0); 25 | 26 | //SyncMethod 27 | proxyInstance.SyncMethod(); 28 | 29 | Assert.AreEqual(interceptor.Logs.Count, 1); 30 | var remoteInvoke = interceptor.Logs.Dequeue(); 31 | Assert.AreEqual(false, remoteInvoke.FireAndForget); 32 | Assert.AreEqual(nameof(proxyInstance.SyncMethod), remoteInvoke.MethodName); 33 | Assert.AreEqual(typeof(object), remoteInvoke.ReturnType); 34 | Assert.AreEqual(0, remoteInvoke.Args.Length); 35 | 36 | //AsyncMethod 37 | await proxyInstance.AsyncMethod().ConfigureAwait(false); 38 | 39 | Assert.AreEqual(interceptor.Logs.Count, 1); 40 | remoteInvoke = interceptor.Logs.Dequeue(); 41 | Assert.AreEqual(false, remoteInvoke.FireAndForget); 42 | Assert.AreEqual(nameof(proxyInstance.AsyncMethod), remoteInvoke.MethodName); 43 | Assert.AreEqual(typeof(object), remoteInvoke.ReturnType); 44 | Assert.AreEqual(0, remoteInvoke.Args.Length); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Serializer/ArrayTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using AppDomainAlternative.Serializer.Default; 7 | using NUnit.Framework; 8 | 9 | namespace AppDomainAlternative.Serializer 10 | { 11 | [TestFixture] 12 | public class ArrayTests 13 | { 14 | private async Task test(DefaultSerializer serializer, MockResolveProxyIds resolver, Array value) 15 | { 16 | var arrayType = value.GetType(); 17 | 18 | Assert.IsTrue(serializer.CanSerialize(arrayType)); 19 | 20 | var stream = new MemoryStream(); 21 | 22 | using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) 23 | { 24 | await serializer.Serialize(writer, arrayType, value, resolver).ConfigureAwait(false); 25 | } 26 | 27 | Console.WriteLine($"Size of {value}: {stream.Length}"); 28 | 29 | stream.Position = 0; 30 | 31 | using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) 32 | { 33 | var deserializedValue = await serializer.Deserialize(reader, arrayType, resolver, CancellationToken.None).ConfigureAwait(false) as Array; 34 | 35 | Assert.IsNotNull(deserializedValue); 36 | Assert.AreEqual(value.Rank, deserializedValue.Rank, "Ranks don't match."); 37 | for (var dimension = 0; dimension < value.Rank; dimension++) 38 | { 39 | Assert.AreEqual(value.GetLength(dimension), deserializedValue.GetLength(dimension), $"Lengths ({dimension}) don't match."); 40 | Assert.AreEqual(value.GetLowerBound(dimension), deserializedValue.GetLowerBound(dimension), $"Lower bounds ({dimension}) don't match."); 41 | Assert.AreEqual(value.GetUpperBound(dimension), deserializedValue.GetUpperBound(dimension), $"Upper bounds ({dimension}) don't match."); 42 | } 43 | 44 | var enumerator = value.GetEnumerator(); 45 | var deserializedEnumerator = deserializedValue.GetEnumerator(); 46 | 47 | while (enumerator.MoveNext()) 48 | { 49 | Assert.IsTrue(deserializedEnumerator.MoveNext()); 50 | Assert.AreEqual(enumerator.Current, deserializedEnumerator.Current); 51 | } 52 | Assert.IsFalse(deserializedEnumerator.MoveNext()); 53 | } 54 | } 55 | 56 | [Test] 57 | public async Task Test() 58 | { 59 | var resolver = new MockResolveProxyIds(); 60 | var serializer = new DefaultSerializer(); 61 | 62 | //single dimension arrays (primitive, nullable, structs, and objects) 63 | await test(serializer, resolver, new[] { int.MinValue, -1, 0, 1, int.MaxValue }).ConfigureAwait(false); 64 | await test(serializer, resolver, new int?[] { int.MinValue, -1, 0, null, 1, int.MaxValue }).ConfigureAwait(false); 65 | await test(serializer, resolver, new[] { DateTime.MinValue, DateTime.Today, DateTime.Now, DateTime.UtcNow, DateTime.MaxValue }).ConfigureAwait(false); 66 | await test(serializer, resolver, new object[] { int.MinValue, DateTime.UtcNow, Guid.NewGuid(), TimeSpan.MinValue }).ConfigureAwait(false); 67 | 68 | //jagged array 69 | await test(serializer, resolver, new[] 70 | { 71 | new[] { int.MinValue, int.MaxValue }, 72 | new[] { -1, 1 }, 73 | new[] { 0 } 74 | }).ConfigureAwait(false); 75 | 76 | //multi dimensional arrays 77 | await test(serializer, resolver, new[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } }).ConfigureAwait(false); 78 | 79 | //array with lower bounds 80 | var array = Array.CreateInstance(typeof(Guid), 81 | new [] { 1, 2 },//1 columns, 2 rows 82 | new [] { 10, 11});//10 lower bound column, 11 lower bound row 83 | array.SetValue(Guid.NewGuid(), 10, 11); 84 | array.SetValue(Guid.NewGuid(), 10, 12); 85 | await test(serializer, resolver, array).ConfigureAwait(false); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Serializer/DateTimeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using AppDomainAlternative.Serializer.Default; 7 | using NUnit.Framework; 8 | 9 | namespace AppDomainAlternative.Serializer 10 | { 11 | [TestFixture] 12 | public class DateTimeTests 13 | { 14 | [Test] 15 | public async Task Test() 16 | { 17 | var resolver = new MockResolveProxyIds(); 18 | var serializer = new DefaultSerializer(); 19 | 20 | Assert.IsTrue(serializer.CanSerialize(typeof(DateTime))); 21 | 22 | var values = new[] 23 | { 24 | DateTime.MinValue, DateTime.Today, DateTime.Now, DateTime.UtcNow, DateTime.MaxValue 25 | }; 26 | 27 | foreach (var value in values) 28 | { 29 | var stream = new MemoryStream(); 30 | 31 | using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) 32 | { 33 | await serializer.Serialize(writer, typeof(DateTime), value, resolver).ConfigureAwait(false); 34 | } 35 | 36 | Console.WriteLine($"Size of {value}: {stream.Length}"); 37 | 38 | stream.Position = 0; 39 | 40 | using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) 41 | { 42 | var deserializedValue = await serializer.Deserialize(reader, typeof(DateTime), resolver, CancellationToken.None).ConfigureAwait(false); 43 | Assert.AreEqual(value, deserializedValue); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Serializer/EnumTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using AppDomainAlternative.Serializer.Default; 8 | using NUnit.Framework; 9 | 10 | namespace AppDomainAlternative.Serializer 11 | { 12 | [TestFixture] 13 | public class EnumTests 14 | { 15 | [Flags] 16 | public enum SampleEnum 17 | { 18 | One = 1, 19 | Two = 2, 20 | Three = One | Two, 21 | Four = 4, 22 | Five = One | Four, 23 | Six = Two | Four, 24 | Seven = One | Two | Four, 25 | Eight = 8 26 | } 27 | 28 | private async Task test(DefaultSerializer serializer, MockResolveProxyIds resolver, SampleEnum value) 29 | { 30 | var stream = new MemoryStream(); 31 | 32 | using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) 33 | { 34 | await serializer.Serialize(writer, typeof(SampleEnum), value, resolver).ConfigureAwait(false); 35 | } 36 | 37 | Console.WriteLine($"Size of {value}: {stream.Length}"); 38 | 39 | stream.Position = 0; 40 | 41 | using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) 42 | { 43 | var deserializedValue = await serializer.Deserialize(reader, typeof(SampleEnum), resolver, CancellationToken.None).ConfigureAwait(false); 44 | Assert.AreEqual(deserializedValue, value); 45 | } 46 | } 47 | 48 | [Test] 49 | public async Task Test() 50 | { 51 | var resolver = new MockResolveProxyIds(); 52 | var serializer = new DefaultSerializer(); 53 | 54 | foreach (var value in Enum.GetValues(typeof(SampleEnum)).Cast()) 55 | { 56 | await test(serializer, resolver, value).ConfigureAwait(false); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Serializer/GuidTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using AppDomainAlternative.Serializer.Default; 7 | using NUnit.Framework; 8 | 9 | namespace AppDomainAlternative.Serializer 10 | { 11 | [TestFixture] 12 | public class GuidTests 13 | { 14 | [Test] 15 | public async Task Test() 16 | { 17 | var resolver = new MockResolveProxyIds(); 18 | var serializer = new DefaultSerializer(); 19 | 20 | Assert.IsTrue(serializer.CanSerialize(typeof(Guid))); 21 | 22 | var values = new[] 23 | { 24 | Guid.Empty, Guid.NewGuid(), Guid.NewGuid() 25 | }; 26 | 27 | foreach (var value in values) 28 | { 29 | var stream = new MemoryStream(); 30 | 31 | using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) 32 | { 33 | await serializer.Serialize(writer, typeof(Guid), value, resolver).ConfigureAwait(false); 34 | } 35 | 36 | Console.WriteLine($"Size of {value}: {stream.Length}"); 37 | 38 | stream.Position = 0; 39 | 40 | using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) 41 | { 42 | var deserializedValue = await serializer.Deserialize(reader, typeof(Guid), resolver, CancellationToken.None).ConfigureAwait(false); 43 | Assert.AreEqual(value, deserializedValue); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Serializer/MockResolveProxyIds.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Linq; 3 | 4 | namespace AppDomainAlternative.Serializer 5 | { 6 | public class MockResolveProxyIds : IResolveProxyIds 7 | { 8 | public readonly ConcurrentDictionary Instances = new ConcurrentDictionary(); 9 | 10 | public bool TryToGetInstanceId(object instance, out long id) 11 | { 12 | foreach (var pair in Instances.Where(pair => ReferenceEquals(pair.Value, instance))) 13 | { 14 | id = pair.Key; 15 | return true; 16 | } 17 | 18 | id = 0; 19 | return false; 20 | } 21 | 22 | public bool TryToGetInstance(long id, out object instance) => Instances.TryGetValue(id, out instance); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Serializer/NullTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using AppDomainAlternative.Serializer.Default; 7 | using NUnit.Framework; 8 | 9 | namespace AppDomainAlternative.Serializer 10 | { 11 | [TestFixture] 12 | public class NullTests 13 | { 14 | [Test] 15 | public async Task Test() 16 | { 17 | var resolver = new MockResolveProxyIds(); 18 | var serializer = new DefaultSerializer(); 19 | 20 | var stream = new MemoryStream(); 21 | 22 | using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) 23 | { 24 | await serializer.Serialize(writer, typeof(object), null, resolver).ConfigureAwait(false); 25 | } 26 | 27 | Console.WriteLine($"Size: {stream.Length}"); 28 | 29 | stream.Position = 0; 30 | 31 | using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) 32 | { 33 | var deserializedValue = await serializer.Deserialize(reader, typeof(object), resolver, CancellationToken.None).ConfigureAwait(false); 34 | Assert.IsNull(deserializedValue); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Serializer/PrimitiveTypesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using AppDomainAlternative.Serializer.Default; 7 | using NUnit.Framework; 8 | 9 | namespace AppDomainAlternative.Serializer 10 | { 11 | [TestFixture] 12 | public class PrimitiveTypesTests 13 | { 14 | private async Task test(DefaultSerializer serializer, MockResolveProxyIds resolver, params T[] values) 15 | { 16 | Assert.IsTrue(serializer.CanSerialize(typeof(T))); 17 | 18 | foreach (var value in values) 19 | { 20 | var stream = new MemoryStream(); 21 | 22 | using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) 23 | { 24 | await serializer.Serialize(writer, typeof(T), value, resolver).ConfigureAwait(false); 25 | } 26 | 27 | Console.WriteLine($"Size of {value}: {stream.Length}"); 28 | 29 | stream.Position = 0; 30 | 31 | using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) 32 | { 33 | var deserializedValue = await serializer.Deserialize(reader, typeof(T), resolver, CancellationToken.None).ConfigureAwait(false); 34 | Assert.AreEqual(value, deserializedValue); 35 | } 36 | } 37 | } 38 | 39 | [Test] 40 | public async Task Test() 41 | { 42 | var resolver = new MockResolveProxyIds(); 43 | var serializer = new DefaultSerializer(); 44 | 45 | //boolean 46 | await test(serializer, resolver, true, false).ConfigureAwait(false); 47 | 48 | //char 49 | await test(serializer, resolver, char.MinValue, char.MaxValue).ConfigureAwait(false); 50 | 51 | //signed integer types 52 | await test(serializer, resolver, sbyte.MinValue, -1, 0, 1, sbyte.MaxValue).ConfigureAwait(false); 53 | await test(serializer, resolver, int.MinValue, -1, 0, 1, int.MaxValue).ConfigureAwait(false); 54 | await test(serializer, resolver, long.MinValue, -1, 0, 1, long.MaxValue).ConfigureAwait(false); 55 | await test(serializer, resolver, short.MinValue, -1, 0, 1, short.MaxValue).ConfigureAwait(false); 56 | 57 | //unsigned integer types 58 | await test(serializer, resolver, byte.MinValue, 1, byte.MaxValue).ConfigureAwait(false); 59 | await test(serializer, resolver, uint.MinValue, (uint)1, uint.MaxValue).ConfigureAwait(false); 60 | await test(serializer, resolver, ulong.MinValue, (ulong)1, ulong.MaxValue).ConfigureAwait(false); 61 | await test(serializer, resolver, ushort.MinValue, 1, ushort.MaxValue).ConfigureAwait(false); 62 | 63 | //float point types 64 | await test(serializer, resolver, decimal.MinValue, -1, 0, 1, decimal.MaxValue).ConfigureAwait(false); 65 | await test(serializer, resolver, double.MinValue, -1, 0, 1, double.MaxValue).ConfigureAwait(false); 66 | await test(serializer, resolver, float.MinValue, -1, 0, 1, float.MaxValue).ConfigureAwait(false); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Serializer/ProxyInstanceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using AppDomainAlternative.Serializer.Default; 7 | using NUnit.Framework; 8 | 9 | namespace AppDomainAlternative.Serializer 10 | { 11 | [TestFixture] 12 | public class ProxyInstanceTests 13 | { 14 | private async Task test(DefaultSerializer serializer, MockResolveProxyIds resolver, object value) 15 | { 16 | var stream = new MemoryStream(); 17 | 18 | using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) 19 | { 20 | await serializer.Serialize(writer, typeof(object), value, resolver).ConfigureAwait(false); 21 | } 22 | 23 | Console.WriteLine($"Size of {value}: {stream.Length}"); 24 | 25 | stream.Position = 0; 26 | 27 | using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) 28 | { 29 | var deserializedValue = await serializer.Deserialize(reader, typeof(object), resolver, CancellationToken.None).ConfigureAwait(false); 30 | Assert.AreEqual(deserializedValue, value); 31 | } 32 | } 33 | 34 | [Test] 35 | public async Task Test() 36 | { 37 | var resolver = new MockResolveProxyIds(); 38 | var serializer = new DefaultSerializer(); 39 | 40 | var min = new object(); 41 | resolver.Instances[long.MinValue] = min; 42 | 43 | var zero = new object(); 44 | resolver.Instances[0] = zero; 45 | 46 | var max = new object(); 47 | resolver.Instances[long.MaxValue] = max; 48 | 49 | await test(serializer, resolver, min).ConfigureAwait(false); 50 | await test(serializer, resolver, zero).ConfigureAwait(false); 51 | await test(serializer, resolver, max).ConfigureAwait(false); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Serializer/SerializableTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.Serialization; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using AppDomainAlternative.Serializer.Default; 10 | using NUnit.Framework; 11 | 12 | #pragma warning disable 659 13 | 14 | namespace AppDomainAlternative.Serializer 15 | { 16 | [TestFixture] 17 | public class SerializableTests 18 | { 19 | [Serializable] 20 | public class SampleClass : ISerializable 21 | { 22 | public SampleClass() => CreatedByDefaultCtor = true; 23 | public SampleClass(SerializationInfo info, StreamingContext context) 24 | { 25 | CreatedByDefaultCtor = false; 26 | Number = info.GetInt32(nameof(Number)); 27 | Parent = (SerializableTests)info.GetValue(nameof(Parent), typeof(SerializableTests)); 28 | Str = info.GetString(nameof(Str)); 29 | } 30 | 31 | public readonly bool CreatedByDefaultCtor; 32 | 33 | public SerializableTests Parent { get; set; } 34 | public int Number { get; set; } 35 | public string Str { get; set; } 36 | 37 | public override bool Equals(object obj) => 38 | obj is SampleClass value && 39 | Number == value.Number && 40 | Str == value.Str && 41 | ReferenceEquals(Parent, value.Parent); 42 | 43 | public void GetObjectData(SerializationInfo info, StreamingContext context) 44 | { 45 | info.AddValue(nameof(Number), Number); 46 | info.AddValue(nameof(Parent), Parent); 47 | info.AddValue(nameof(Str), Str); 48 | } 49 | } 50 | 51 | private async Task test(DefaultSerializer serializer, MockResolveProxyIds resolver, SampleClass value) 52 | { 53 | var stream = new MemoryStream(); 54 | 55 | using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) 56 | { 57 | await serializer.Serialize(writer, typeof(SampleClass), value, resolver).ConfigureAwait(false); 58 | } 59 | 60 | Console.WriteLine($"Size of {value}: {stream.Length}"); 61 | 62 | stream.Position = 0; 63 | 64 | using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) 65 | { 66 | var deserializedValue = (SampleClass)await serializer.Deserialize(reader, typeof(SampleClass), resolver, CancellationToken.None).ConfigureAwait(false); 67 | Assert.AreEqual(deserializedValue, value); 68 | Assert.IsFalse(deserializedValue.CreatedByDefaultCtor); 69 | } 70 | } 71 | 72 | [Test] 73 | public async Task CustomSerializerTest() 74 | { 75 | var resolver = new MockResolveProxyIds(); 76 | var serializer = new DefaultSerializer(); 77 | 78 | resolver.Instances[3] = this; 79 | 80 | await test(serializer, resolver, new SampleClass 81 | { 82 | Number = 0, 83 | Parent = this, 84 | Str = null 85 | }).ConfigureAwait(false); 86 | await test(serializer, resolver, new SampleClass 87 | { 88 | Number = int.MinValue, 89 | Parent = this, 90 | Str = "" 91 | }).ConfigureAwait(false); 92 | await test(serializer, resolver, new SampleClass 93 | { 94 | Number = int.MaxValue, 95 | Parent = this, 96 | Str = "Hello World" 97 | }).ConfigureAwait(false); 98 | } 99 | 100 | [Test] 101 | public async Task CustomSerializerWNonPublicCtorTest() 102 | { 103 | var resolver = new MockResolveProxyIds(); 104 | var serializer = new DefaultSerializer(); 105 | var stream = new MemoryStream(); 106 | var value = new ArgumentNullException(Guid.NewGuid().ToString()); 107 | 108 | using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) 109 | { 110 | await serializer.Serialize(writer, typeof(ArgumentNullException), value, resolver).ConfigureAwait(false); 111 | } 112 | 113 | Console.WriteLine($"Size of {value}: {stream.Length}"); 114 | 115 | stream.Position = 0; 116 | 117 | using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) 118 | { 119 | var deserializedValue = (ArgumentNullException)await serializer.Deserialize(reader, typeof(ArgumentNullException), resolver, CancellationToken.None).ConfigureAwait(false); 120 | Assert.AreEqual(deserializedValue.Message, value.Message); 121 | } 122 | } 123 | 124 | [Test] 125 | public async Task ListTest() 126 | { 127 | var resolver = new MockResolveProxyIds(); 128 | var serializer = new DefaultSerializer(); 129 | 130 | var stream = new MemoryStream(); 131 | 132 | var value = new List 133 | { 134 | int.MinValue, 135 | 0, 136 | int.MaxValue 137 | }; 138 | 139 | using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) 140 | { 141 | await serializer.Serialize(writer, typeof(List), value, resolver).ConfigureAwait(false); 142 | } 143 | 144 | Console.WriteLine($"Size of {value}: {stream.Length}"); 145 | 146 | stream.Position = 0; 147 | 148 | using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) 149 | { 150 | var deserializedValue = (List)await serializer.Deserialize(reader, typeof(List), resolver, CancellationToken.None).ConfigureAwait(false); 151 | Assert.AreEqual(deserializedValue.Count, value.Count); 152 | Assert.IsTrue(value.Zip(deserializedValue, (a, b) => a == b).All(result => result)); 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Serializer/StringTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using AppDomainAlternative.Serializer.Default; 7 | using NUnit.Framework; 8 | 9 | namespace AppDomainAlternative.Serializer 10 | { 11 | [TestFixture] 12 | public class StringTests 13 | { 14 | [Test] 15 | public async Task Test() 16 | { 17 | var resolver = new MockResolveProxyIds(); 18 | var serializer = new DefaultSerializer(); 19 | 20 | var stream = new MemoryStream(); 21 | 22 | var str = new string(new[] 23 | { 24 | char.MinValue, 25 | 26 | //standard ASCII 27 | 'A', 28 | 'Z', 29 | 'a', 30 | 'z', 31 | 32 | //extended ASCII 33 | 'À', 34 | 'Ï', 35 | 36 | //unicode 37 | 'Ā', 38 | 'ď', 39 | 40 | char.MaxValue 41 | }); 42 | 43 | using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) 44 | { 45 | await serializer.Serialize(writer, typeof(string), str, resolver).ConfigureAwait(false); 46 | } 47 | 48 | Console.WriteLine($"Size of \"{str}\": {stream.Length}"); 49 | 50 | stream.Position = 0; 51 | 52 | using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) 53 | { 54 | var deserializedValue = await serializer.Deserialize(reader, typeof(string), resolver, CancellationToken.None).ConfigureAwait(false); 55 | Assert.AreEqual(deserializedValue, str); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Serializer/TimeSpanTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using AppDomainAlternative.Serializer.Default; 7 | using NUnit.Framework; 8 | 9 | namespace AppDomainAlternative.Serializer 10 | { 11 | [TestFixture] 12 | public class TimeSpanTests 13 | { 14 | [Test] 15 | public async Task Test() 16 | { 17 | var resolver = new MockResolveProxyIds(); 18 | var serializer = new DefaultSerializer(); 19 | 20 | Assert.IsTrue(serializer.CanSerialize(typeof(TimeSpan))); 21 | 22 | var values = new[] 23 | { 24 | TimeSpan.MinValue, TimeSpan.Zero, TimeSpan.MaxValue 25 | }; 26 | 27 | foreach (var value in values) 28 | { 29 | var stream = new MemoryStream(); 30 | 31 | using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) 32 | { 33 | await serializer.Serialize(writer, typeof(TimeSpan), value, resolver).ConfigureAwait(false); 34 | } 35 | 36 | Console.WriteLine($"Size of {value}: {stream.Length}"); 37 | 38 | stream.Position = 0; 39 | 40 | using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) 41 | { 42 | var deserializedValue = await serializer.Deserialize(reader, typeof(TimeSpan), resolver, CancellationToken.None).ConfigureAwait(false); 43 | Assert.AreEqual(value, deserializedValue); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.Tests/Serializer/TypeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using AppDomainAlternative.Serializer.Default; 7 | using NUnit.Framework; 8 | 9 | namespace AppDomainAlternative.Serializer 10 | { 11 | [TestFixture] 12 | public class TypeTests 13 | { 14 | private async Task test(DefaultSerializer serializer, MockResolveProxyIds resolver, Type value) 15 | { 16 | var stream = new MemoryStream(); 17 | 18 | using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) 19 | { 20 | await serializer.Serialize(writer, typeof(Type), value, resolver).ConfigureAwait(false); 21 | } 22 | 23 | Console.WriteLine($"Size of {value}: {stream.Length}"); 24 | 25 | stream.Position = 0; 26 | 27 | using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) 28 | { 29 | var deserializedValue = await serializer.Deserialize(reader, typeof(Type), resolver, CancellationToken.None).ConfigureAwait(false); 30 | Assert.AreEqual(deserializedValue, value); 31 | } 32 | } 33 | 34 | public class SampleClassWithÑƑ 35 | { 36 | } 37 | 38 | [Test] 39 | public async Task Test() 40 | { 41 | var resolver = new MockResolveProxyIds(); 42 | var serializer = new DefaultSerializer(); 43 | 44 | await test(serializer, resolver, typeof(DateTime)).ConfigureAwait(false); 45 | await test(serializer, resolver, typeof(DateTime?)).ConfigureAwait(false); 46 | await test(serializer, resolver, typeof(DateTime[])).ConfigureAwait(false); 47 | await test(serializer, resolver, typeof(Guid)).ConfigureAwait(false); 48 | await test(serializer, resolver, typeof(Guid?)).ConfigureAwait(false); 49 | await test(serializer, resolver, typeof(Guid[])).ConfigureAwait(false); 50 | await test(serializer, resolver, typeof(SampleClassWithÑƑ)).ConfigureAwait(false); 51 | await test(serializer, resolver, typeof(SampleClassWithÑƑ[])).ConfigureAwait(false); 52 | await test(serializer, resolver, typeof(TimeSpan)).ConfigureAwait(false); 53 | await test(serializer, resolver, typeof(TimeSpan?)).ConfigureAwait(false); 54 | await test(serializer, resolver, typeof(TimeSpan[])).ConfigureAwait(false); 55 | await test(serializer, resolver, typeof(Type)).ConfigureAwait(false); 56 | await test(serializer, resolver, typeof(Type[])).ConfigureAwait(false); 57 | await test(serializer, resolver, typeof(bool)).ConfigureAwait(false); 58 | await test(serializer, resolver, typeof(bool?)).ConfigureAwait(false); 59 | await test(serializer, resolver, typeof(bool[])).ConfigureAwait(false); 60 | await test(serializer, resolver, typeof(byte)).ConfigureAwait(false); 61 | await test(serializer, resolver, typeof(byte?)).ConfigureAwait(false); 62 | await test(serializer, resolver, typeof(byte[])).ConfigureAwait(false); 63 | await test(serializer, resolver, typeof(char)).ConfigureAwait(false); 64 | await test(serializer, resolver, typeof(char?)).ConfigureAwait(false); 65 | await test(serializer, resolver, typeof(char[])).ConfigureAwait(false); 66 | await test(serializer, resolver, typeof(decimal)).ConfigureAwait(false); 67 | await test(serializer, resolver, typeof(decimal?)).ConfigureAwait(false); 68 | await test(serializer, resolver, typeof(decimal[])).ConfigureAwait(false); 69 | await test(serializer, resolver, typeof(double)).ConfigureAwait(false); 70 | await test(serializer, resolver, typeof(double?)).ConfigureAwait(false); 71 | await test(serializer, resolver, typeof(double[])).ConfigureAwait(false); 72 | await test(serializer, resolver, typeof(float)).ConfigureAwait(false); 73 | await test(serializer, resolver, typeof(float?)).ConfigureAwait(false); 74 | await test(serializer, resolver, typeof(float[])).ConfigureAwait(false); 75 | await test(serializer, resolver, typeof(int)).ConfigureAwait(false); 76 | await test(serializer, resolver, typeof(int?)).ConfigureAwait(false); 77 | await test(serializer, resolver, typeof(int[])).ConfigureAwait(false); 78 | await test(serializer, resolver, typeof(long)).ConfigureAwait(false); 79 | await test(serializer, resolver, typeof(long?)).ConfigureAwait(false); 80 | await test(serializer, resolver, typeof(long[])).ConfigureAwait(false); 81 | await test(serializer, resolver, typeof(object)).ConfigureAwait(false); 82 | await test(serializer, resolver, typeof(object[])).ConfigureAwait(false); 83 | await test(serializer, resolver, typeof(sbyte)).ConfigureAwait(false); 84 | await test(serializer, resolver, typeof(sbyte?)).ConfigureAwait(false); 85 | await test(serializer, resolver, typeof(sbyte[])).ConfigureAwait(false); 86 | await test(serializer, resolver, typeof(short)).ConfigureAwait(false); 87 | await test(serializer, resolver, typeof(short?)).ConfigureAwait(false); 88 | await test(serializer, resolver, typeof(short[])).ConfigureAwait(false); 89 | await test(serializer, resolver, typeof(string)).ConfigureAwait(false); 90 | await test(serializer, resolver, typeof(string[])).ConfigureAwait(false); 91 | await test(serializer, resolver, typeof(uint)).ConfigureAwait(false); 92 | await test(serializer, resolver, typeof(uint?)).ConfigureAwait(false); 93 | await test(serializer, resolver, typeof(uint[])).ConfigureAwait(false); 94 | await test(serializer, resolver, typeof(ulong)).ConfigureAwait(false); 95 | await test(serializer, resolver, typeof(ulong?)).ConfigureAwait(false); 96 | await test(serializer, resolver, typeof(ulong[])).ConfigureAwait(false); 97 | await test(serializer, resolver, typeof(ushort)).ConfigureAwait(false); 98 | await test(serializer, resolver, typeof(ushort?)).ConfigureAwait(false); 99 | await test(serializer, resolver, typeof(ushort[])).ConfigureAwait(false); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.VsCode.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29509.3 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppDomainAlternative", "AppDomainAlternative\AppDomainAlternative.csproj", "{D36E0CAF-B64E-4FA7-B436-685571466D1A}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppDomainAlternative.Tests", "AppDomainAlternative.Tests\AppDomainAlternative.Tests.csproj", "{A5C31E9A-F6A4-409E-8877-DCE73F6B1CBE}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{9412893C-A6DA-4C52-851C-DB2B0D5CCE6B}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostApp", "HostApp\HostApp.csproj", "{B8A0CA83-1998-4616-99EF-C8B1C9C57A90}" 13 | ProjectSection(ProjectDependencies) = postProject 14 | {E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C} = {E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C} 15 | EndProjectSection 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientApp", "ClientApp\ClientApp.csproj", "{E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "Common\Common.csproj", "{92D74724-9524-46CB-94CF-CDEA3371A62E}" 20 | EndProject 21 | Global 22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 23 | Debug|Any CPU = Debug|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {D36E0CAF-B64E-4FA7-B436-685571466D1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {D36E0CAF-B64E-4FA7-B436-685571466D1A}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {A5C31E9A-F6A4-409E-8877-DCE73F6B1CBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {A5C31E9A-F6A4-409E-8877-DCE73F6B1CBE}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {B8A0CA83-1998-4616-99EF-C8B1C9C57A90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {B8A0CA83-1998-4616-99EF-C8B1C9C57A90}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {92D74724-9524-46CB-94CF-CDEA3371A62E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {92D74724-9524-46CB-94CF-CDEA3371A62E}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(NestedProjects) = preSolution 41 | {B8A0CA83-1998-4616-99EF-C8B1C9C57A90} = {9412893C-A6DA-4C52-851C-DB2B0D5CCE6B} 42 | {E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C} = {9412893C-A6DA-4C52-851C-DB2B0D5CCE6B} 43 | {92D74724-9524-46CB-94CF-CDEA3371A62E} = {9412893C-A6DA-4C52-851C-DB2B0D5CCE6B} 44 | EndGlobalSection 45 | GlobalSection(ExtensibilityGlobals) = postSolution 46 | SolutionGuid = {EB1975AE-2208-466B-A0DB-2045BA764E63} 47 | EndGlobalSection 48 | GlobalSection(Performance) = preSolution 49 | HasPerformanceSessions = true 50 | EndGlobalSection 51 | GlobalSection(Performance) = preSolution 52 | HasPerformanceSessions = true 53 | EndGlobalSection 54 | EndGlobal 55 | -------------------------------------------------------------------------------- /src/AppDomainAlternative.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29509.3 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppDomainAlternative", "AppDomainAlternative\AppDomainAlternative.csproj", "{D36E0CAF-B64E-4FA7-B436-685571466D1A}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {DA9E5C82-8062-44A2-A24F-A17881DAEB42} = {DA9E5C82-8062-44A2-A24F-A17881DAEB42} 9 | EndProjectSection 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppDomainAlternative.Tests", "AppDomainAlternative.Tests\AppDomainAlternative.Tests.csproj", "{A5C31E9A-F6A4-409E-8877-DCE73F6B1CBE}" 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{9412893C-A6DA-4C52-851C-DB2B0D5CCE6B}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostApp", "HostApp\HostApp.csproj", "{B8A0CA83-1998-4616-99EF-C8B1C9C57A90}" 16 | ProjectSection(ProjectDependencies) = postProject 17 | {E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C} = {E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C} 18 | EndProjectSection 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientApp", "ClientApp\ClientApp.csproj", "{E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "Common\Common.csproj", "{92D74724-9524-46CB-94CF-CDEA3371A62E}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VsDebugger", "VsDebugger\VsDebugger.csproj", "{DA9E5C82-8062-44A2-A24F-A17881DAEB42}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {D36E0CAF-B64E-4FA7-B436-685571466D1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {D36E0CAF-B64E-4FA7-B436-685571466D1A}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {D36E0CAF-B64E-4FA7-B436-685571466D1A}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {D36E0CAF-B64E-4FA7-B436-685571466D1A}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {A5C31E9A-F6A4-409E-8877-DCE73F6B1CBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {A5C31E9A-F6A4-409E-8877-DCE73F6B1CBE}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {A5C31E9A-F6A4-409E-8877-DCE73F6B1CBE}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {A5C31E9A-F6A4-409E-8877-DCE73F6B1CBE}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {B8A0CA83-1998-4616-99EF-C8B1C9C57A90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {B8A0CA83-1998-4616-99EF-C8B1C9C57A90}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {B8A0CA83-1998-4616-99EF-C8B1C9C57A90}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {B8A0CA83-1998-4616-99EF-C8B1C9C57A90}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {92D74724-9524-46CB-94CF-CDEA3371A62E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {92D74724-9524-46CB-94CF-CDEA3371A62E}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {92D74724-9524-46CB-94CF-CDEA3371A62E}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {92D74724-9524-46CB-94CF-CDEA3371A62E}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {DA9E5C82-8062-44A2-A24F-A17881DAEB42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {DA9E5C82-8062-44A2-A24F-A17881DAEB42}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {DA9E5C82-8062-44A2-A24F-A17881DAEB42}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {DA9E5C82-8062-44A2-A24F-A17881DAEB42}.Release|Any CPU.Build.0 = Release|Any CPU 56 | EndGlobalSection 57 | GlobalSection(SolutionProperties) = preSolution 58 | HideSolutionNode = FALSE 59 | EndGlobalSection 60 | GlobalSection(NestedProjects) = preSolution 61 | {B8A0CA83-1998-4616-99EF-C8B1C9C57A90} = {9412893C-A6DA-4C52-851C-DB2B0D5CCE6B} 62 | {E9DE4A6F-6FDE-4A03-9DB8-A684FEF5A88C} = {9412893C-A6DA-4C52-851C-DB2B0D5CCE6B} 63 | {92D74724-9524-46CB-94CF-CDEA3371A62E} = {9412893C-A6DA-4C52-851C-DB2B0D5CCE6B} 64 | EndGlobalSection 65 | GlobalSection(ExtensibilityGlobals) = postSolution 66 | SolutionGuid = {EB1975AE-2208-466B-A0DB-2045BA764E63} 67 | EndGlobalSection 68 | GlobalSection(Performance) = preSolution 69 | HasPerformanceSessions = true 70 | EndGlobalSection 71 | GlobalSection(Performance) = preSolution 72 | HasPerformanceSessions = true 73 | EndGlobalSection 74 | EndGlobal 75 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/AppDomainAlternative.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | false 6 | false 7 | false 8 | 9 | 10 | 11 | 12 | true 13 | Always 14 | 15 | 16 | 17 | 18 | $(SolutionDir)\AppDomainAlternative\bin\Release\AppDomainAlternative.xml 19 | true 20 | AppDomainAlternative 21 | $(Version) 22 | An AppDomain alternative for .Net Core 23 | Cy A Scott 24 | https://github.com/CyAScott/AppDomainAlternative 25 | false 26 | Core AppDomain 27 | true 28 | 29 | 30 | 31 | 32 | true 33 | lib\netstandard2.0 34 | true 35 | Always 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/ChildDomain.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using AppDomainAlternative.Ipc; 5 | 6 | namespace AppDomainAlternative 7 | { 8 | /// 9 | public sealed class ChildDomain : Domains, IDisposable 10 | { 11 | private readonly bool debuggerEnabled; 12 | private readonly int pid; 13 | private int disposed; 14 | 15 | internal ChildDomain(Process childProcess, bool debuggerEnabled, Connection connection) 16 | { 17 | Channels = connection; 18 | Process = childProcess; 19 | this.debuggerEnabled = debuggerEnabled; 20 | 21 | try 22 | { 23 | pid = childProcess.Id; 24 | } 25 | catch 26 | { 27 | pid = -1; 28 | } 29 | 30 | childProcess.Exited += (sender, eventArgs) => Dispose(); 31 | } 32 | 33 | /// 34 | public override IHaveChannels Channels { get; } 35 | 36 | /// 37 | /// The process for the child. 38 | /// 39 | public override Process Process { get; } 40 | 41 | /// 42 | public void Dispose() 43 | { 44 | if (Interlocked.CompareExchange(ref disposed, 1, 0) != 0) 45 | { 46 | return; 47 | } 48 | 49 | if (debuggerEnabled && pid != -1) 50 | { 51 | DetachDebugger(pid); 52 | } 53 | 54 | using ((IDisposable)Channels) 55 | using (Process) 56 | { 57 | try 58 | { 59 | Process.Kill(); 60 | } 61 | catch 62 | { 63 | // ignored 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/CurrentDomain.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.IO.Pipes; 8 | using System.Text.RegularExpressions; 9 | using System.Web; 10 | using AppDomainAlternative.Ipc; 11 | using AppDomainAlternative.Proxy; 12 | using AppDomainAlternative.Serializer; 13 | using AppDomainAlternative.Serializer.Default; 14 | 15 | namespace AppDomainAlternative 16 | { 17 | /// 18 | /// The current domain (aka Process). 19 | /// 20 | public sealed class CurrentDomain : Domains, IEnumerable 21 | { 22 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 23 | 24 | private const string connectionStringVarName = "__ParentProcessConnectionString__"; 25 | private readonly ConcurrentDictionary children = new ConcurrentDictionary(); 26 | 27 | internal CurrentDomain(Process current) 28 | { 29 | Process = current; 30 | 31 | var parentConnectionString = Regex.Match(Environment.GetEnvironmentVariable(connectionStringVarName) ?? "", 32 | @"^pid=(?\d+)&write=(?\d+)&read=(?\d+)&debug=(?[01])&serializer=(?[^&]+)&proxyGenerator=(?[^&]+)$", RegexOptions.IgnoreCase); 33 | 34 | if (!parentConnectionString.Success) 35 | { 36 | if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(connectionStringVarName))) 37 | { 38 | throw new InvalidOperationException($"Invalid connection string from parent: {Environment.GetEnvironmentVariable(connectionStringVarName)}"); 39 | } 40 | return; 41 | } 42 | 43 | var parent = Process.GetProcessById(Convert.ToInt32(parentConnectionString.Groups["pid"].Value)); 44 | 45 | parent.EnableRaisingEvents = true; 46 | parent.Exited += (sender, eventArgs) => Environment.Exit(0); 47 | 48 | var serializerName = HttpUtility.HtmlDecode(parentConnectionString.Groups["serializer"].Value); 49 | var proxyGeneratorName = HttpUtility.HtmlDecode(parentConnectionString.Groups["proxyGenerator"].Value); 50 | 51 | var reader = new AnonymousPipeClientStream(PipeDirection.In, parentConnectionString.Groups["read"].Value); 52 | 53 | if (parentConnectionString.Groups["debug"].Value == "1") 54 | { 55 | //wait for a signal from the parent process to proceed 56 | reader.Read(new byte[1], 0, 1); 57 | } 58 | 59 | Channels = new Connection(this, 60 | DomainConfiguration.SerializerResolver(serializerName) ?? throw new InvalidOperationException($"Invalid serializer from parent: {serializerName}"), 61 | DomainConfiguration.Resolver(proxyGeneratorName) ?? throw new InvalidOperationException($"Invalid proxy generator from parent: {proxyGeneratorName}"), 62 | reader, 63 | new AnonymousPipeClientStream(PipeDirection.Out, parentConnectionString.Groups["write"].Value)); 64 | } 65 | 66 | /// 67 | /// Gets a child domain by . 68 | /// 69 | public Domains this[int id] => children[id]; 70 | 71 | /// 72 | /// Creates a child domain. 73 | /// 74 | public ChildDomain AddChildDomain(ProcessStartInfo startInfo, IAmASerializer serializer = null, IGenerateProxies proxyGenerator = null) 75 | { 76 | //if the path is missing then 77 | if (startInfo == null) 78 | { 79 | throw new ArgumentNullException(nameof(startInfo)); 80 | } 81 | 82 | var childProcess = new Process 83 | { 84 | StartInfo = startInfo 85 | }; 86 | 87 | proxyGenerator = proxyGenerator ?? DefaultProxyFactory.Instance; 88 | serializer = serializer ?? DefaultSerializer.Instance; 89 | 90 | var debuggerEnabled = Debugger.IsAttached && DebuggingSupported; 91 | 92 | var read = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable); 93 | var write = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable); 94 | startInfo.Environment[connectionStringVarName] = 95 | $"pid={Current.Process.Id}&" + 96 | $"write={write.GetClientHandleAsString()}&" + 97 | $"read={read.GetClientHandleAsString()}&" + 98 | $"debug={(debuggerEnabled ? 1 : 0)}&" + 99 | $"serializer={HttpUtility.UrlEncode(serializer.Name)}&" + 100 | $"proxyGenerator={HttpUtility.UrlEncode(proxyGenerator.Name)}"; 101 | startInfo.UseShellExecute = false; 102 | 103 | childProcess.EnableRaisingEvents = true; 104 | 105 | childProcess.Start(); 106 | 107 | read.DisposeLocalCopyOfClientHandle(); 108 | write.DisposeLocalCopyOfClientHandle(); 109 | 110 | if (debuggerEnabled) 111 | { 112 | if (!TryToAttachDebugger(childProcess.Id)) 113 | { 114 | debuggerEnabled = false; 115 | } 116 | 117 | //signal to the child process to continue now that the debugger is attached 118 | read.Write(new byte[1], 0, 1); 119 | } 120 | 121 | //NOTE: the read and write streams are switched for the server side 122 | var child = new ChildDomain(childProcess, debuggerEnabled, new Connection(this, serializer, proxyGenerator, write, read)); 123 | 124 | children[childProcess.Id] = child; 125 | 126 | child.Process.Exited += (sender, eventArgs) => children.TryRemove(childProcess.Id, out _); 127 | 128 | if (child.Process.HasExited) 129 | { 130 | children.TryRemove(childProcess.Id, out _); 131 | } 132 | 133 | return child; 134 | } 135 | 136 | /// 137 | public IEnumerator GetEnumerator() => children.Values.GetEnumerator(); 138 | 139 | /// 140 | /// Attempts to get a child domain by . 141 | /// 142 | public bool TryToGetChild(int id, out ChildDomain child) => children.TryGetValue(id, out child); 143 | 144 | /// 145 | /// The number of live child domains created by this domain. 146 | /// 147 | public int Count => children.Count; 148 | 149 | /// 150 | public override IHaveChannels Channels { get; } 151 | 152 | /// 153 | /// The current process. 154 | /// 155 | public override Process Process { get; } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/DomainConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AppDomainAlternative.Proxy; 3 | using AppDomainAlternative.Serializer; 4 | using AppDomainAlternative.Serializer.Default; 5 | 6 | namespace AppDomainAlternative 7 | { 8 | /// 9 | /// Global configuration settings for Inter Process Communication (IPC). 10 | /// 11 | public static class DomainConfiguration 12 | { 13 | /// 14 | /// A resolver for s. 15 | /// The resolver resolves by name/id. 16 | /// 17 | public static Func SerializerResolver { get; set; } = DefaultSerializer.Resolve; 18 | 19 | /// 20 | /// A resolver for . 21 | /// The resolver resolves by name/id. 22 | /// 23 | public static Func Resolver { get; set; } = DefaultProxyFactory.Resolve; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Domains.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Runtime.InteropServices; 5 | using System.Text.RegularExpressions; 6 | using AppDomainAlternative.Ipc; 7 | 8 | namespace AppDomainAlternative 9 | { 10 | /// 11 | /// A wrapper for a . 12 | /// 13 | public abstract class Domains 14 | { 15 | #region Visual Studio Debugger Support 16 | 17 | /// 18 | /// The environment variable name to use when finding the correct DTE version to use for attaching the debugger. 19 | /// 20 | public const string VsDteEnvironmentVariableName = "__VsDteEnvironmentVariableName__"; 21 | 22 | /// 23 | /// Where the VsBugger.exe file is located. 24 | /// 25 | protected static string VsDebuggerLocation { get; } 26 | 27 | /// 28 | /// The Visual Studio DTE version to use for debugging. 29 | /// 30 | protected static string DteVersion { get; } 31 | 32 | #pragma warning disable 1591 33 | #pragma warning disable IDE1006 // Naming Styles 34 | // ReSharper disable InconsistentNaming 35 | // ReSharper disable StringLiteralTypo 36 | // ReSharper disable UnusedMember.Local 37 | [DllImport("ole32.dll")] 38 | internal static extern int CLSIDFromProgID([MarshalAs(UnmanagedType.LPWStr)] string lpszProgID, out Guid pclsid); 39 | #pragma warning restore 1591 40 | #pragma warning restore IDE1006 // Naming Styles 41 | // ReSharper restore InconsistentNaming 42 | // ReSharper restore StringLiteralTypo 43 | // ReSharper restore UnusedMember.Local 44 | 45 | internal static bool TryToAttachDebugger(int pid) 46 | { 47 | if (!DebuggingSupported) 48 | { 49 | Debug.WriteLine("No Debugger Found"); 50 | return false; 51 | } 52 | 53 | try 54 | { 55 | using (var command = Process.Start(new ProcessStartInfo(VsDebuggerLocation, $"-pid:{pid} -ppid:{Current.Process.Id} -dtev:{DteVersion} -debugger:attach") 56 | { 57 | RedirectStandardOutput = true 58 | })) 59 | { 60 | // ReSharper disable once PossibleNullReferenceException 61 | command.WaitForExit(30000);//wait for 30 seconds to attach the debugger 62 | 63 | if (!command.HasExited) 64 | { 65 | command.Kill(); 66 | throw new TimeoutException("Timed out when trying to attach debugger to process."); 67 | } 68 | 69 | var output = command.StandardOutput.ReadToEnd(); 70 | 71 | if (command.ExitCode != 1) 72 | { 73 | throw new Exception(output); 74 | } 75 | } 76 | 77 | return true; 78 | } 79 | catch (Exception error) 80 | { 81 | Debug.WriteLine($"Unable to attach debugger to process ({pid}): {error}"); 82 | 83 | return false; 84 | } 85 | } 86 | 87 | internal static void DetachDebugger(int pid) 88 | { 89 | try 90 | { 91 | using (var command = Process.Start(new ProcessStartInfo(VsDebuggerLocation, $"-pid:{pid} -dtev:{DteVersion} -debugger:detach") 92 | { 93 | RedirectStandardOutput = true 94 | })) 95 | { 96 | // ReSharper disable once PossibleNullReferenceException 97 | command.WaitForExit(30000);//wait for 30 seconds to attach the debugger 98 | 99 | if (!command.HasExited) 100 | { 101 | command.Kill(); 102 | throw new TimeoutException("Timed out when trying to attach debugger to process."); 103 | } 104 | 105 | if (command.ExitCode != 1) 106 | { 107 | throw new Exception(command.StandardOutput.ReadToEnd()); 108 | } 109 | } 110 | } 111 | catch (Exception error) 112 | { 113 | Debug.WriteLine($"Unable to detach debugger to process ({pid}): {error}"); 114 | } 115 | } 116 | 117 | #endregion 118 | 119 | static Domains() 120 | { 121 | Current = new CurrentDomain(Process.GetCurrentProcess()); 122 | 123 | try 124 | { 125 | VsDebuggerLocation = Path.Combine(Path.GetDirectoryName(typeof(Domains).Assembly.Location) ?? "", "VsDebugger.exe"); 126 | if (!File.Exists(VsDebuggerLocation)) 127 | { 128 | VsDebuggerLocation = null; 129 | return; 130 | } 131 | 132 | /* The DTE version for Visual Studio is needed to attach the debugger to a child process. 133 | * The best way to find this information is to use the VsWhere utility developed by Microsoft. 134 | * 135 | * The DTE version can be set manually using an environment variable. This will be useful for 136 | * dev machines that have multiple versions of Visual Studio installed. 137 | * 138 | * This means that attaching the debugger to a child process is only supported on Windows. 139 | */ 140 | DteVersion = Environment.GetEnvironmentVariable(VsDteEnvironmentVariableName); 141 | 142 | if (string.IsNullOrEmpty(DteVersion) || CLSIDFromProgID(DteVersion, out var classId) == 0 && classId != Guid.Empty) 143 | { 144 | using (var process = new Process()) 145 | { 146 | process.StartInfo.UseShellExecute = false; 147 | process.StartInfo.FileName = "vsWhere"; 148 | process.StartInfo.RedirectStandardOutput = true; 149 | process.Start(); 150 | 151 | var output = process.StandardOutput.ReadToEnd(); 152 | 153 | process.WaitForExit(); 154 | 155 | using (var reader = new StringReader(output)) 156 | { 157 | var regex = new Regex(@"^\s*installationVersion\s*:\s*(?\d+)(\.\d+)*$", RegexOptions.IgnoreCase); 158 | while (reader.Peek() > -1) 159 | { 160 | var match = regex.Match(reader.ReadLine() ?? ""); 161 | if (match.Success) 162 | { 163 | DteVersion = $"{match.Groups["major"].Value}.0"; 164 | break; 165 | } 166 | } 167 | } 168 | } 169 | } 170 | 171 | //confirm the dte version is valid 172 | if (CLSIDFromProgID($"VisualStudio.DTE.{DteVersion}", out classId) != 0 || classId == Guid.Empty) 173 | { 174 | throw new Exception($"Unable to Find DTE Version \"VisualStudio.DTE.{DteVersion}\". Try setting the environment variable \"{VsDteEnvironmentVariableName}\" to the correct value for this machine."); 175 | } 176 | 177 | DebuggingSupported = true; 178 | } 179 | catch (Exception error) 180 | { 181 | DteVersion = null; 182 | DebuggingSupported = false; 183 | 184 | Debug.WriteLine($"Unable to find debugger: {error}"); 185 | } 186 | } 187 | 188 | internal Domains() 189 | { 190 | } 191 | 192 | /// 193 | /// All the open s between the parent and child . 194 | /// 195 | public abstract IHaveChannels Channels { get; } 196 | 197 | /// 198 | /// The process for the domain. 199 | /// 200 | public abstract Process Process { get; } 201 | 202 | /// 203 | /// True if debugging child processes is supported. 204 | /// 205 | public static bool DebuggingSupported { get; } 206 | 207 | /// 208 | /// The current domain (aka Process). 209 | /// 210 | public static readonly CurrentDomain Current; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Extensions/ChannelStartExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using AppDomainAlternative.Ipc; 9 | using AppDomainAlternative.Proxy; 10 | 11 | namespace AppDomainAlternative.Extensions 12 | { 13 | internal static class ChannelStartExtensions 14 | { 15 | private static async Task localStart(this IInternalChannel channel, CancellationTokenSource cancel, IGenerateProxies proxyGenerator, ConstructorInfo ctor, bool hostInstance, object instance, params object[] arguments) 16 | { 17 | if (channel == null) 18 | { 19 | throw new ArgumentNullException(nameof(channel)); 20 | } 21 | 22 | if (cancel == null) 23 | { 24 | throw new ArgumentNullException(nameof(cancel)); 25 | } 26 | 27 | if (ctor == null) 28 | { 29 | throw new ArgumentNullException(nameof(ctor)); 30 | } 31 | 32 | if (arguments == null) 33 | { 34 | throw new ArgumentNullException(nameof(arguments)); 35 | } 36 | 37 | if (!(proxyGenerator == null ^ instance == null)) 38 | { 39 | throw new InvalidOperationException($"Only one instance of {nameof(proxyGenerator)} or {nameof(instance)} is allowed."); 40 | } 41 | 42 | if (instance != null && !hostInstance) 43 | { 44 | throw new InvalidOperationException("Instance must be hosted."); 45 | } 46 | 47 | var response = new byte[] { 1 }; 48 | var responseTask = channel.Buffer.ReadAsync(response, 0, 1); 49 | 50 | var initRequest = new MemoryStream(); 51 | using (var writer = new BinaryWriter(initRequest, Encoding.UTF8, true)) 52 | { 53 | //write hostInstance 54 | writer.Write(hostInstance); 55 | 56 | //write type to proxy 57 | await channel.Serializer.Serialize(writer, typeof(Type), ctor.DeclaringType, channel.Connection).ConfigureAwait(false); 58 | 59 | //write ctor param count 60 | var @params = ctor.GetParameters(); 61 | writer.Write((byte)@params.Length); 62 | 63 | if (arguments.Length != @params.Length) 64 | { 65 | throw new ArgumentException("Invalid constructor."); 66 | } 67 | 68 | //write each ctor param in pairs of type and value 69 | foreach (var param in @params.Zip(arguments, (param, arg) => new { type = arg?.GetType() ?? param.ParameterType, arg })) 70 | { 71 | await channel.Serializer.Serialize(writer, typeof(Type), param.type, channel.Connection).ConfigureAwait(false); 72 | await channel.Serializer.Serialize(writer, param.type, param.arg, channel.Connection).ConfigureAwait(false); 73 | } 74 | } 75 | 76 | initRequest.Position = 0; 77 | channel.Connection.Write(channel.Id, initRequest); 78 | 79 | await responseTask.ConfigureAwait(false); 80 | 81 | if (response[0] != 0) 82 | { 83 | return hostInstance ? instance ?? ctor.Invoke(arguments) : proxyGenerator.GenerateProxy(channel, ctor, arguments); 84 | } 85 | 86 | var exceptionType = (Type)await channel.Serializer.Deserialize(channel.Reader, typeof(Type), channel.Connection, cancel.Token).ConfigureAwait(false); 87 | var exception = (Exception)await channel.Serializer.Deserialize(channel.Reader, exceptionType, channel.Connection, cancel.Token).ConfigureAwait(false); 88 | 89 | throw exception; 90 | } 91 | 92 | public static async Task<(bool isHost, object instance)> RemoteStart(this IInternalChannel channel, CancellationTokenSource cancel, IGenerateProxies proxyGenerator) 93 | { 94 | if (channel == null) 95 | { 96 | throw new ArgumentNullException(nameof(channel)); 97 | } 98 | 99 | if (cancel == null) 100 | { 101 | throw new ArgumentNullException(nameof(cancel)); 102 | } 103 | 104 | if (proxyGenerator == null) 105 | { 106 | throw new ArgumentNullException(nameof(proxyGenerator)); 107 | } 108 | 109 | bool isHost; 110 | object instance; 111 | var responseStream = new MemoryStream(); 112 | 113 | using (var writer = new BinaryWriter(responseStream, Encoding.UTF8, true)) 114 | { 115 | try 116 | { 117 | //write success (we may rewrite this byte if there is an error later) 118 | writer.Write(true); 119 | 120 | //read hostInstance 121 | var makeProxy = channel.Reader.ReadBoolean(); 122 | isHost = !makeProxy; 123 | 124 | //read type to proxy 125 | var type = (Type)await channel.Serializer.Deserialize(channel.Reader, typeof(Type), channel.Connection, cancel.Token).ConfigureAwait(false); 126 | 127 | //read ctor param count 128 | var paramCount = channel.Reader.ReadByte(); 129 | 130 | //read each ctor param in pairs of type and value 131 | var arguments = new object[paramCount]; 132 | var types = new Type[paramCount]; 133 | for (var index = 0; index < paramCount; index++) 134 | { 135 | var paramType = types[index] = (Type)await channel.Serializer.Deserialize(channel.Reader, typeof(Type), channel.Connection, cancel.Token).ConfigureAwait(false); 136 | 137 | arguments[index] = await channel.Serializer.Deserialize(channel.Reader, paramType, channel.Connection, cancel.Token).ConfigureAwait(false); 138 | } 139 | 140 | //find the ctor 141 | var ctor = type.GetConstructors() 142 | .Select(item => new 143 | { 144 | ctor = item, 145 | @params = item.GetParameters() 146 | }) 147 | .FirstOrDefault(ctorInfo => 148 | ctorInfo.@params.Length == paramCount && 149 | ctorInfo.@params 150 | .Select(param => param.ParameterType) 151 | .Zip(types, (a, b) => a.IsAssignableFrom(b)) 152 | .All(result => result))?.ctor ?? 153 | throw new ArgumentException("Unable fond the constructor."); 154 | 155 | //create the instance 156 | instance = makeProxy ? proxyGenerator.GenerateProxy(channel, ctor, arguments) : ctor.Invoke(arguments); 157 | } 158 | catch (Exception error) 159 | { 160 | instance = null; 161 | isHost = false; 162 | responseStream.Position = 0; 163 | responseStream.SetLength(0); 164 | 165 | var errorType = error.GetType(); 166 | 167 | //write unsuccessful 168 | writer.Write(false); 169 | 170 | //write the error 171 | await channel.Serializer.Serialize(writer, typeof(Type), errorType, channel.Connection).ConfigureAwait(false); 172 | await channel.Serializer.Serialize(writer, errorType, error, channel.Connection).ConfigureAwait(false); 173 | } 174 | } 175 | 176 | //send the response 177 | try 178 | { 179 | responseStream.Position = 0; 180 | channel.Connection.Write(channel.Id, responseStream); 181 | } 182 | catch 183 | { 184 | // ignored 185 | } 186 | 187 | return (isHost, instance); 188 | } 189 | public static Task LocalStart(this IInternalChannel channel, CancellationTokenSource cancel, IGenerateProxies proxyGenerator, ConstructorInfo ctor, bool hostInstance, params object[] arguments) => 190 | channel.localStart(cancel, proxyGenerator, ctor, hostInstance, null, arguments); 191 | public static Task LocalStart(this IInternalChannel channel, CancellationTokenSource cancel, T instance) 192 | where T : class, new() 193 | { 194 | if (instance == null) 195 | { 196 | throw new ArgumentNullException(nameof(instance)); 197 | } 198 | 199 | return channel.localStart(cancel, null, instance.GetType().GetConstructor(Type.EmptyTypes), true, instance); 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Ipc/Channel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using AppDomainAlternative.Extensions; 10 | using AppDomainAlternative.Proxy; 11 | using AppDomainAlternative.Serializer; 12 | 13 | #pragma warning disable AsyncFixer03 // Avoid fire & forget async void methods 14 | 15 | namespace AppDomainAlternative.Ipc 16 | { 17 | /// 18 | /// An IPC channel for sharing an instance across domains. 19 | /// 20 | public interface IChannel : IDisposable 21 | { 22 | /// 23 | /// If the instance is hosted from this domain. 24 | /// 25 | bool IsHost { get; } 26 | 27 | /// 28 | /// The id for the channel. 29 | /// 30 | long Id { get; } 31 | 32 | /// 33 | /// The shared instance between domains. 34 | /// 35 | object Instance { get; } 36 | } 37 | 38 | internal interface IInternalChannel : IChannel, IInterceptor 39 | { 40 | BinaryReader Reader { get; } 41 | IConnection Connection { get; } 42 | ReadWriteBuffer Buffer { get; } 43 | Task LocalStart(IGenerateProxies proxyGenerator, ConstructorInfo ctor, bool hostInstance, params object[] arguments); 44 | Task LocalStart(T instance) 45 | where T : class, new(); 46 | Task RemoteStart(IGenerateProxies proxyGenerator); 47 | bool IsDisposed { get; } 48 | } 49 | 50 | internal sealed class Channel : IInternalChannel 51 | { 52 | private int disposed, requestCounter; 53 | private readonly CancellationTokenSource disposeToken = new CancellationTokenSource(); 54 | private readonly ConcurrentDictionary> remoteRequests = new ConcurrentDictionary>(); 55 | private void throwIfDisposed() 56 | { 57 | if (disposed > 0) 58 | { 59 | throw new ObjectDisposedException(nameof(Channel)); 60 | } 61 | } 62 | 63 | public Channel(long id, IConnection connection, IAmASerializer serializer) 64 | { 65 | Buffer = new ReadWriteBuffer(id); 66 | 67 | Connection = connection; 68 | Reader = new BinaryReader(Buffer, Encoding.UTF8); 69 | Id = id; 70 | IdBytes = BitConverter.GetBytes(id); 71 | Serializer = serializer; 72 | } 73 | 74 | public BinaryReader Reader { get; } 75 | public IAmASerializer Serializer { get; } 76 | public IConnection Connection { get; } 77 | public ReadWriteBuffer Buffer { get; } 78 | public Task RemoteInvoke(bool fireAndForget, string methodName, params Tuple[] args) 79 | { 80 | throwIfDisposed(); 81 | 82 | return this.RemoteInvoke(remoteRequests, () => Interlocked.Increment(ref requestCounter), fireAndForget, methodName, args); 83 | } 84 | public async Task LocalStart(IGenerateProxies proxyGenerator, ConstructorInfo ctor, bool hostInstance, params object[] arguments) 85 | { 86 | Instance = await this.LocalStart(disposeToken, proxyGenerator, ctor, hostInstance, arguments).ConfigureAwait(false); 87 | 88 | IsHost = hostInstance; 89 | 90 | this.StartListening(disposeToken, remoteRequests); 91 | } 92 | public async Task LocalStart(T instance) 93 | where T : class, new() 94 | { 95 | await this.LocalStart(disposeToken, Instance = instance).ConfigureAwait(false); 96 | 97 | IsHost = true; 98 | 99 | this.StartListening(disposeToken, remoteRequests); 100 | } 101 | public async Task RemoteStart(IGenerateProxies proxyGenerator) 102 | { 103 | var (isHost, instance) = await this.RemoteStart(disposeToken, proxyGenerator).ConfigureAwait(false); 104 | 105 | IsHost = isHost; 106 | Instance = instance; 107 | 108 | this.StartListening(disposeToken, remoteRequests); 109 | } 110 | public bool IsDisposed => disposed > 0; 111 | public bool IsHost { get; private set; } 112 | public byte[] IdBytes { get; } 113 | public long Id { get; } 114 | public object Instance { get; private set; } 115 | public void Dispose() 116 | { 117 | if (Interlocked.CompareExchange(ref disposed, 1, 0) != 0) 118 | { 119 | return; 120 | } 121 | 122 | using (disposeToken) 123 | { 124 | try 125 | { 126 | disposeToken.Cancel(); 127 | } 128 | catch 129 | { 130 | // ignored 131 | } 132 | } 133 | 134 | Connection.Terminate(this); 135 | Buffer.Dispose(); 136 | 137 | var requests = remoteRequests.Values.ToArray(); 138 | remoteRequests.Clear(); 139 | foreach (var request in requests) 140 | { 141 | request.TrySetCanceled(); 142 | } 143 | 144 | Reader.Dispose(); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Ipc/ReadWriteBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace AppDomainAlternative.Ipc 7 | { 8 | /// 9 | /// A FILO byte buffer. 10 | /// 11 | internal class ReadWriteBuffer : Stream 12 | { 13 | internal class ReadRequest : TaskCompletionSource, IAsyncResult 14 | { 15 | WaitHandle IAsyncResult.AsyncWaitHandle => ((IAsyncResult)Task).AsyncWaitHandle; 16 | bool IAsyncResult.CompletedSynchronously => ((IAsyncResult)Task).CompletedSynchronously; 17 | bool IAsyncResult.IsCompleted => Task.IsCompleted; 18 | object IAsyncResult.AsyncState => Task.AsyncState; 19 | 20 | public ReadRequest(long id, ArraySegment buffer, AsyncCallback callback, object state) 21 | : base(state) 22 | { 23 | if (callback != null) 24 | { 25 | Task.ContinueWith(_ => callback(this)); 26 | } 27 | Buffer = buffer; 28 | Id = id; 29 | } 30 | public ArraySegment Buffer { get; } 31 | public long Id { get; } 32 | public int ReadBytes { get; set; } 33 | } 34 | 35 | private ReadRequest readRequest; 36 | private byte[] buffer = new byte[pageSize]; 37 | private const int pageSize = 4096; 38 | private int 39 | disposed, 40 | readIndex, //the reader's index position in the local buffer. readIndex is always <= writeIndex 41 | writeIndex;//the writer's index position in the local buffer 42 | private readonly SemaphoreSlim readWriteLock = new SemaphoreSlim(1, 1);//a read/write lock for the local buffer 43 | private void completeReadRequest() 44 | { 45 | var request = readRequest; 46 | readRequest = null; 47 | request.TrySetResult(request.ReadBytes); 48 | } 49 | private void copyFromLocalBufferTo(bool completeIfNotFinished) 50 | { 51 | if (readRequest == null) 52 | { 53 | return; 54 | } 55 | 56 | //if the destination buffer is full then 57 | if (readRequest.ReadBytes >= readRequest.Buffer.Count) 58 | { 59 | //set the request as completed 60 | completeReadRequest(); 61 | 62 | return; 63 | } 64 | 65 | //skip if there is nothing in the local buffer 66 | if (readIndex == writeIndex) 67 | { 68 | return; 69 | } 70 | 71 | //copy unread bytes in the local buffer to the request's buffer 72 | 73 | var copyLength = Math.Min(writeIndex - readIndex, readRequest.Buffer.Count); 74 | 75 | Buffer.BlockCopy( 76 | buffer, readIndex, 77 | // ReSharper disable once AssignNullToNotNullAttribute 78 | readRequest.Buffer.Array, readRequest.Buffer.Offset, copyLength); 79 | 80 | readRequest.ReadBytes += copyLength; 81 | readIndex += copyLength; 82 | 83 | if (completeIfNotFinished || readRequest.ReadBytes >= readRequest.Buffer.Count) 84 | { 85 | //set the request as completed 86 | completeReadRequest(); 87 | } 88 | 89 | flushLocalBuffer(); 90 | } 91 | private void flushLocalBuffer() 92 | { 93 | if (readIndex == 0) 94 | { 95 | return; 96 | } 97 | 98 | //if there is no unread data in the local buffer then 99 | if (readIndex == writeIndex) 100 | { 101 | //reset all indices to 0 102 | readIndex = writeIndex = 0; 103 | } 104 | else 105 | { 106 | //else move the unread bytes to the beginning of the buffer 107 | 108 | Buffer.BlockCopy(buffer, readIndex, buffer, 0, writeIndex -= readIndex); 109 | 110 | readIndex = 0; 111 | } 112 | } 113 | private void throwIfDisposed() 114 | { 115 | if (disposed > 0) 116 | { 117 | throw new ObjectDisposedException(nameof(ReadWriteBuffer)); 118 | } 119 | } 120 | 121 | protected override void Dispose(bool disposing) 122 | { 123 | if (Interlocked.CompareExchange(ref disposed, 1, 0) != 0) 124 | { 125 | return; 126 | } 127 | 128 | readRequest?.TrySetCanceled(); 129 | readWriteLock?.Dispose(); 130 | } 131 | 132 | public ReadWriteBuffer(long id) => Id = id; 133 | public async Task Fill(Stream reader, int bytesToRead, CancellationToken token) 134 | { 135 | throwIfDisposed(); 136 | 137 | while (bytesToRead > 0) 138 | { 139 | await readWriteLock.WaitAsync(token).ConfigureAwait(false); 140 | 141 | copyFromLocalBufferTo(false); 142 | 143 | try 144 | { 145 | //if there is no request waiting for data then 146 | if (readRequest == null) 147 | { 148 | //copy the data into the local buffer to be read later 149 | 150 | var count = await reader.ReadAsync(buffer, writeIndex, Math.Min(buffer.Length - writeIndex, bytesToRead), token).ConfigureAwait(false); 151 | 152 | bytesToRead -= count; 153 | writeIndex += count; 154 | 155 | //if the local buffer is full then 156 | if (writeIndex == buffer.Length) 157 | { 158 | //increase the local buffer size by one page 159 | Array.Resize(ref buffer, buffer.Length + pageSize); 160 | } 161 | } 162 | else 163 | { 164 | //copy the data directly into the request's buffer 165 | 166 | var count = await reader.ReadAsync( 167 | // ReSharper disable once AssignNullToNotNullAttribute 168 | readRequest.Buffer.Array, 169 | readRequest.Buffer.Offset + readRequest.ReadBytes, 170 | Math.Min(bytesToRead, readRequest.Buffer.Count - readRequest.ReadBytes), token).ConfigureAwait(false); 171 | 172 | readRequest.ReadBytes += count; 173 | bytesToRead -= count; 174 | 175 | //set the request as completed 176 | completeReadRequest(); 177 | } 178 | } 179 | finally 180 | { 181 | readWriteLock.Release(); 182 | } 183 | } 184 | } 185 | public long Id { get; } 186 | public override IAsyncResult BeginRead(byte[] data, int offset, int count, AsyncCallback callback, object state) 187 | { 188 | throwIfDisposed(); 189 | 190 | var request = new ReadRequest(Id, new ArraySegment(data, offset, count), callback, state); 191 | 192 | if (count == 0) 193 | { 194 | request.TrySetResult(0); 195 | return request; 196 | } 197 | 198 | readWriteLock.Wait(); 199 | 200 | try 201 | { 202 | if (readRequest != null) 203 | { 204 | throw new InvalidOperationException("A read is already in progress."); 205 | } 206 | readRequest = request; 207 | copyFromLocalBufferTo(true); 208 | } 209 | finally 210 | { 211 | readWriteLock.Release(); 212 | } 213 | 214 | return request; 215 | } 216 | public override IAsyncResult BeginWrite(byte[] data, int offset, int count, AsyncCallback callback, object state) => throw new NotSupportedException(); 217 | public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; 218 | public override bool CanRead => disposed == 0; 219 | public override bool CanSeek { get; } = false; 220 | public override bool CanWrite { get; } = false; 221 | public override int EndRead(IAsyncResult asyncResult) 222 | { 223 | throwIfDisposed(); 224 | 225 | if (!(asyncResult is ReadRequest result) || result.Id != Id) 226 | { 227 | throw new InvalidCastException(); 228 | } 229 | 230 | return result.Task.Result; 231 | } 232 | public override int Read(byte[] data, int offset, int count) 233 | { 234 | throwIfDisposed(); 235 | return ((ReadRequest)BeginRead(data, offset, count, null, null)).Task.Result; 236 | } 237 | public override long Length => writeIndex - readIndex; 238 | public override long Position 239 | { 240 | get => throw new NotSupportedException(); 241 | set => throw new NotSupportedException(); 242 | } 243 | public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); 244 | public override void EndWrite(IAsyncResult asyncResult) => throw new NotSupportedException(); 245 | public override void Flush() 246 | { 247 | } 248 | public override void SetLength(long value) => throw new NotSupportedException(); 249 | public override void Write(byte[] data, int offset, int count) => throw new NotSupportedException(); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: ComVisible(false)] 6 | [assembly: Guid("9b5f893d-c7a6-4e27-8cb5-9278997a8531")] 7 | [assembly: AssemblyVersion("1.0.0.0")] 8 | [assembly: AssemblyFileVersion("1.0.0.0")] 9 | [assembly: InternalsVisibleTo("AppDomainAlternative.Tests")] 10 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Proxy/DefaultProxyFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Reflection.Emit; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace AppDomainAlternative.Proxy 11 | { 12 | internal sealed class DefaultProxyFactory : IGenerateProxies 13 | { 14 | private ConstructorInfo generateType(Guid id, IInterceptor interceptor, ConstructorInfo ctor) 15 | { 16 | if (interceptor == null) 17 | { 18 | throw new ArgumentNullException(nameof(interceptor)); 19 | } 20 | 21 | var baseType = ctor?.DeclaringType ?? throw new ArgumentNullException(nameof(ctor)); 22 | var name = baseType.Name; 23 | var genericNameIndex = name.IndexOf('`'); 24 | if (genericNameIndex != -1) 25 | { 26 | name = name.Substring(0, genericNameIndex); 27 | } 28 | 29 | var settings = new ProxyAttribute(); 30 | 31 | if (!settings.Enabled) 32 | { 33 | throw new NotSupportedException("This class does not support proxying."); 34 | } 35 | 36 | var typeBuilder = proxyModule.Value.DefineType($"ProxyFor{name}_{id.ToString().Replace("-", "")}", 37 | TypeAttributes.Class | 38 | TypeAttributes.Public | 39 | TypeAttributes.AutoClass | 40 | TypeAttributes.AnsiClass | 41 | TypeAttributes.Sealed | 42 | TypeAttributes.BeforeFieldInit, 43 | baseType); 44 | 45 | var interceptorField = typeBuilder.DefineField("interceptor", typeof(IInterceptor), 46 | FieldAttributes.Private | FieldAttributes.InitOnly); 47 | 48 | generateCtor(typeBuilder, interceptorField, ctor); 49 | 50 | if (baseType.GetMethods() 51 | .Where(methodInfo => methodInfo.DeclaringType != typeof(object) && methodInfo.IsVirtual && !methodInfo.IsGenericMethod) 52 | .Count(methodInfo => tryToOverrideMethod(settings, interceptor, typeBuilder, interceptorField, methodInfo)) == 0) 53 | { 54 | throw new ArgumentException($"Unable to proxy any member for this type: {baseType.FullName}"); 55 | } 56 | 57 | return typeBuilder.CreateTypeInfo().GetConstructors().Single(); 58 | } 59 | private Guid getHashId(ConstructorInfo ctor) 60 | { 61 | if (ctor?.DeclaringType == null) 62 | { 63 | throw new ArgumentNullException(nameof(ctor)); 64 | } 65 | using (var md5 = MD5.Create()) 66 | { 67 | // ReSharper disable once PossibleNullReferenceException 68 | return new Guid(md5.ComputeHash(Encoding.UTF8.GetBytes($"{ctor.DeclaringType.Assembly.FullName};{ctor.DeclaringType.FullName};" + 69 | string.Join(";", ctor.GetParameters().Select(param => param.ParameterType.FullName))))); 70 | } 71 | } 72 | private bool handleInvalidType(InvalidTypeHandling invalidTypeHandling, TypeBuilder typeBuilder, MethodInfo methodInfo, string errorMessage) 73 | { 74 | switch (invalidTypeHandling) 75 | { 76 | default: 77 | throw new InvalidCastException(); 78 | 79 | case InvalidTypeHandling.Ignore: 80 | return false; 81 | 82 | case InvalidTypeHandling.ThrowErrorOnCreate: 83 | throw new ArgumentException(errorMessage); 84 | 85 | case InvalidTypeHandling.ThrowErrorOnInvoke: 86 | var methodBuilder = typeBuilder.DefineMethod( 87 | methodInfo.Name, 88 | methodInfo.Attributes, 89 | methodInfo.CallingConvention, 90 | methodInfo.ReturnType, 91 | methodInfo.GetParameters().Select(param => param.ParameterType).ToArray()); 92 | 93 | var il = methodBuilder.GetILGenerator(); 94 | 95 | il.Emit(OpCodes.Newobj, typeof(NotSupportedException).GetConstructors().First()); 96 | il.Emit(OpCodes.Throw); 97 | 98 | typeBuilder.DefineMethodOverride(methodBuilder, methodInfo); 99 | 100 | return true; 101 | } 102 | } 103 | private bool tryToOverrideMethod(ProxyAttribute settings, IInterceptor interceptor, TypeBuilder typeBuilder, FieldBuilder interceptorField, MethodInfo methodInfo) 104 | { 105 | settings = methodInfo.GetCustomAttribute() ?? settings; 106 | 107 | if (!settings.Enabled) 108 | { 109 | return false; 110 | } 111 | 112 | var returnType = methodInfo.ReturnType; 113 | 114 | if (typeof(Task) == returnType) 115 | { 116 | returnType = typeof(void); 117 | } 118 | else if (typeof(Task).IsAssignableFrom(returnType)) 119 | { 120 | if (!returnType.IsGenericType || returnType.GetGenericTypeDefinition() != typeof(Task<>)) 121 | { 122 | return handleInvalidType(settings.InvalidTypeHandling, typeBuilder, methodInfo, 123 | $"Unable to create proxy class. Unknown Task return type for method: {methodInfo.Name}"); 124 | } 125 | returnType = returnType.GetGenericArguments().Single(); 126 | } 127 | 128 | if (returnType != typeof(void) && !interceptor.Serializer.CanSerialize(returnType)) 129 | { 130 | return handleInvalidType(settings.InvalidTypeHandling, typeBuilder, methodInfo, 131 | $"Unable to create proxy class. Invalid return type for method: {methodInfo.Name}"); 132 | } 133 | 134 | var @params = methodInfo.GetParameters(); 135 | 136 | if (@params.Any(param => param.IsOut || !interceptor.Serializer.CanSerialize(param.ParameterType))) 137 | { 138 | return handleInvalidType(settings.InvalidTypeHandling, typeBuilder, methodInfo, 139 | $"Unable to create proxy class. Invalid argument type for method: {methodInfo.Name}"); 140 | } 141 | 142 | var methodBuilder = typeBuilder.DefineMethod( 143 | methodInfo.Name, 144 | methodInfo.Attributes, 145 | methodInfo.CallingConvention, 146 | methodInfo.ReturnType, 147 | @params.Select(param => param.ParameterType).ToArray()); 148 | 149 | var il = methodBuilder.GetILGenerator(); 150 | 151 | //load interceptor field 152 | il.Emit(OpCodes.Ldarg_0); 153 | il.Emit(OpCodes.Ldfld, interceptorField); 154 | 155 | //load fire and forget boolean value 156 | var fireAndForget = settings.FireAndForget && 157 | (methodInfo.ReturnType == typeof(void) || methodInfo.ReturnType == typeof(Task)); 158 | il.Emit(fireAndForget ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); 159 | 160 | //load method name 161 | il.Emit(OpCodes.Ldstr, methodInfo.Name); 162 | 163 | //load new tuple array 164 | if (@params.Length == 0) 165 | { 166 | // ReSharper disable once PossibleNullReferenceException 167 | il.Emit(OpCodes.Call, typeof(Array).GetMethod("Empty").MakeGenericMethod(typeof(Tuple))); 168 | } 169 | else 170 | { 171 | il.Emit(OpCodes.Ldc_I4, @params.Length); 172 | il.Emit(OpCodes.Newarr, typeof(Tuple)); 173 | 174 | for (var index = 0; index < @params.Length; index++) 175 | { 176 | il.Emit(OpCodes.Dup); 177 | 178 | //load param index 179 | il.Emit(OpCodes.Ldc_I4, index); 180 | 181 | var param = @params[index]; 182 | 183 | //load typeof parameter 184 | il.Emit(OpCodes.Ldtoken, param.ParameterType); 185 | il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", new[] { typeof(RuntimeTypeHandle) })); 186 | 187 | //load argument 188 | il.Emit(OpCodes.Ldarg, index + 1); 189 | il.Emit(OpCodes.Box, param.ParameterType); 190 | 191 | //make tuple of parameter type and argument 192 | il.Emit(OpCodes.Newobj, typeof(Tuple).GetConstructors().First()); 193 | 194 | //set tuple value to loaded index in the tuple array 195 | il.Emit(OpCodes.Stelem_Ref); 196 | } 197 | } 198 | 199 | // ReSharper disable once PossibleNullReferenceException 200 | il.Emit(OpCodes.Callvirt, typeof(IInterceptor) 201 | .GetMethod(nameof(IInterceptor.RemoteInvoke)) 202 | .MakeGenericMethod(returnType == typeof(void) ? typeof(object) : returnType)); 203 | 204 | if (methodInfo.ReturnType == typeof(void)) 205 | { 206 | il.Emit(OpCodes.Callvirt, typeof(Task).GetMethod(nameof(Task.Wait), Type.EmptyTypes)); 207 | } 208 | else if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType)) 209 | { 210 | // ReSharper disable once PossibleNullReferenceException 211 | il.Emit(OpCodes.Callvirt, typeof(Task<>).MakeGenericType(returnType).GetProperty("Result").GetMethod); 212 | } 213 | 214 | il.Emit(OpCodes.Ret); 215 | 216 | typeBuilder.DefineMethodOverride(methodBuilder, methodInfo); 217 | 218 | return true; 219 | } 220 | private readonly ConcurrentDictionary types = new ConcurrentDictionary(); 221 | private readonly Lazy proxyModule = new Lazy(() => 222 | { 223 | var assemblyName = new AssemblyName("ProxyFactory.Types"); 224 | var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); 225 | return assemblyBuilder.DefineDynamicModule("Main"); 226 | }); 227 | private readonly Type[] ctorParams = { typeof(IInterceptor) }; 228 | private void generateCtor(TypeBuilder typeBuilder, FieldBuilder interceptorField, ConstructorInfo ctor) 229 | { 230 | var baseTypeParams = ctor.GetParameters(); 231 | 232 | var ctorBuilder = typeBuilder.DefineConstructor( 233 | MethodAttributes.Public | 234 | MethodAttributes.HideBySig | 235 | MethodAttributes.SpecialName | 236 | MethodAttributes.RTSpecialName, 237 | CallingConventions.Standard, 238 | ctorParams.Concat(baseTypeParams.Select(param => param.ParameterType)).ToArray()); 239 | 240 | var il = ctorBuilder.GetILGenerator(); 241 | 242 | //call base class ctor 243 | il.Emit(OpCodes.Ldarg_0); 244 | for (var index = 0; index < baseTypeParams.Length; index++) 245 | { 246 | il.Emit(OpCodes.Ldarg, index + 2); 247 | } 248 | il.Emit(OpCodes.Call, ctor); 249 | 250 | //this.interceptor = interceptor; 251 | il.Emit(OpCodes.Ldarg_0); 252 | il.Emit(OpCodes.Ldarg_1); 253 | il.Emit(OpCodes.Stfld, interceptorField); 254 | 255 | //end of ctor 256 | il.Emit(OpCodes.Ret); 257 | } 258 | 259 | public object GenerateProxy(IInterceptor interceptor, ConstructorInfo ctor, params object[] arguments) => 260 | types.GetOrAdd(getHashId(ctor), key => generateType(key, interceptor, ctor)).Invoke(new object[] 261 | { 262 | interceptor 263 | }.Concat(arguments).ToArray()); 264 | public static IGenerateProxies Resolve(string name) => Instance; 265 | public static readonly IGenerateProxies Instance = new DefaultProxyFactory(); 266 | public string Name { get; } = $"{nameof(DefaultProxyFactory)}@{typeof(DefaultProxyFactory).Assembly.GetName().Version}"; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Proxy/IGenerateProxies.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace AppDomainAlternative.Proxy 4 | { 5 | /// 6 | /// An interface for a proxy generator. 7 | /// 8 | public interface IGenerateProxies 9 | { 10 | /// 11 | /// Generates a proxy. 12 | /// 13 | object GenerateProxy(IInterceptor interceptor, ConstructorInfo ctor, params object[] arguments); 14 | 15 | /// 16 | /// A name identifier for the proxy generator. 17 | /// 18 | string Name { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Proxy/IInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using AppDomainAlternative.Serializer; 4 | 5 | namespace AppDomainAlternative.Proxy 6 | { 7 | /// 8 | /// An interceptor for proxy calls. 9 | /// 10 | public interface IInterceptor 11 | { 12 | /// 13 | /// The serializer for serializing/deserializing data. 14 | /// 15 | IAmASerializer Serializer { get; } 16 | 17 | /// 18 | /// Invokes an instance across the process barrier. 19 | /// 20 | Task RemoteInvoke(bool fireAndForget, string methodName, params Tuple[] args); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Proxy/ProxyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AppDomainAlternative.Proxy 4 | { 5 | /// 6 | /// Settings for proxying a class and its members 7 | /// 8 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] 9 | public class ProxyAttribute : Attribute 10 | { 11 | /// 12 | /// What to do if the return value or member arguments cannot be passed across the process barrier. 13 | /// The default value is . 14 | /// 15 | public InvalidTypeHandling InvalidTypeHandling { get; set; } = InvalidTypeHandling.ThrowErrorOnCreate; 16 | 17 | /// 18 | /// Enables proxying. 19 | /// The default value is true. 20 | /// 21 | public bool Enabled { get; set; } = true; 22 | 23 | /// 24 | /// If true and the member has no return type (Task or void) then the invoke command is sent to the host asynchronously and the response will be ignored. 25 | /// The default value is false. 26 | /// 27 | public bool FireAndForget { get; set; } 28 | } 29 | 30 | /// 31 | public enum InvalidTypeHandling 32 | { 33 | /// 34 | /// Don't proxy members where a return type or argument type cannot be passed across the process barrier. 35 | /// 36 | Ignore, 37 | 38 | /// 39 | /// Throw an error when generating a proxy class that has members where a return type or argument type cannot be passed across the process barrier. 40 | /// 41 | ThrowErrorOnCreate, 42 | 43 | /// 44 | /// Throw an error when invoking members where a return type or argument type cannot be passed across the process barrier. 45 | /// 46 | ThrowErrorOnInvoke 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Serializer/Default/ArrayReaderWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | 5 | namespace AppDomainAlternative.Serializer.Default 6 | { 7 | internal static class ArrayReaderWriter 8 | { 9 | private static async Task read(this BinaryReader reader, Array array, ObjectType? elementObjectType, Type elementType, bool nullable, int dimension, int[] indexArray, IResolveProxyIds resolver) 10 | { 11 | var upperBound = array.GetUpperBound(dimension); 12 | var lastDimension = dimension == array.Rank - 1; 13 | for (indexArray[dimension] = array.GetLowerBound(dimension); indexArray[dimension] <= upperBound; indexArray[dimension]++) 14 | { 15 | if (lastDimension) 16 | { 17 | if (nullable) 18 | { 19 | if (reader.ReadBoolean()) 20 | { 21 | continue; 22 | } 23 | } 24 | 25 | object element; 26 | switch (elementObjectType) 27 | { 28 | case null: 29 | element = await reader.ReadObject(resolver).ConfigureAwait(false); 30 | break; 31 | case ObjectType.Enum: 32 | element = reader.ReadEnum(elementType); 33 | break; 34 | default: 35 | element = await reader.ReadObject(elementObjectType.Value, resolver).ConfigureAwait(false); 36 | break; 37 | } 38 | 39 | array.SetValue(element, indexArray); 40 | } 41 | else 42 | { 43 | await reader.read(array, elementObjectType, elementType, nullable, dimension + 1, indexArray, resolver).ConfigureAwait(false); 44 | } 45 | } 46 | } 47 | public static async Task ReadArray(this BinaryReader reader, IResolveProxyIds resolver) 48 | { 49 | var elementType = reader.ReadType(); 50 | var rank = (int)reader.ReadByte(); 51 | 52 | var lengths = new int[rank]; 53 | var lowerBounds = new int[rank]; 54 | for (var dimension = 0; dimension < rank; dimension++) 55 | { 56 | lengths[dimension] = reader.ReadInt32(); 57 | lowerBounds[dimension] = reader.ReadInt32(); 58 | } 59 | 60 | var array = Array.CreateInstance(elementType, lengths, lowerBounds); 61 | 62 | var underlyingType = Nullable.GetUnderlyingType(elementType); 63 | var nullable = !elementType.IsValueType || underlyingType != null; 64 | 65 | elementType = underlyingType ?? elementType; 66 | 67 | ObjectType? elementObjectType; 68 | if (elementType.IsArray) 69 | { 70 | elementObjectType = ObjectType.Array; 71 | } 72 | else if (elementType.IsEnum) 73 | { 74 | elementObjectType = ObjectType.Enum; 75 | } 76 | else if (elementType.IsClass && !elementType.IsSealed) 77 | { 78 | elementObjectType = null; 79 | } 80 | else if (elementType == typeof(DateTime)) 81 | { 82 | elementObjectType = ObjectType.DateTime; 83 | } 84 | else if (elementType == typeof(Guid)) 85 | { 86 | elementObjectType = ObjectType.Guid; 87 | } 88 | else if (elementType == typeof(TimeSpan)) 89 | { 90 | elementObjectType = ObjectType.TimeSpan; 91 | } 92 | else if (elementType == typeof(Type)) 93 | { 94 | elementObjectType = ObjectType.Type; 95 | } 96 | else if (elementType == typeof(bool)) 97 | { 98 | elementObjectType = ObjectType.Boolean; 99 | } 100 | else if (elementType == typeof(byte)) 101 | { 102 | elementObjectType = ObjectType.Byte; 103 | } 104 | else if (elementType == typeof(byte[])) 105 | { 106 | elementObjectType = ObjectType.Bytes; 107 | } 108 | else if (elementType == typeof(char)) 109 | { 110 | elementObjectType = ObjectType.Char; 111 | } 112 | else if (elementType == typeof(decimal)) 113 | { 114 | elementObjectType = ObjectType.Decimal; 115 | } 116 | else if (elementType == typeof(double)) 117 | { 118 | elementObjectType = ObjectType.Double; 119 | } 120 | else if (elementType == typeof(float)) 121 | { 122 | elementObjectType = ObjectType.Float; 123 | } 124 | else if (elementType == typeof(int)) 125 | { 126 | elementObjectType = ObjectType.Int; 127 | } 128 | else if (elementType == typeof(long)) 129 | { 130 | elementObjectType = ObjectType.Long; 131 | } 132 | else if (elementType == typeof(sbyte)) 133 | { 134 | elementObjectType = ObjectType.SByte; 135 | } 136 | else if (elementType == typeof(short)) 137 | { 138 | elementObjectType = ObjectType.Short; 139 | } 140 | else if (elementType == typeof(string)) 141 | { 142 | elementObjectType = ObjectType.Str; 143 | } 144 | else if (elementType == typeof(uint)) 145 | { 146 | elementObjectType = ObjectType.UInt; 147 | } 148 | else if (elementType == typeof(ulong)) 149 | { 150 | elementObjectType = ObjectType.ULong; 151 | } 152 | else if (elementType == typeof(ushort)) 153 | { 154 | elementObjectType = ObjectType.Short; 155 | } 156 | else if (elementType == typeof(ushort)) 157 | { 158 | elementObjectType = ObjectType.Short; 159 | } 160 | else if (elementType.IsSerializable) 161 | { 162 | elementObjectType = ObjectType.Serializable; 163 | } 164 | else 165 | { 166 | throw new ArgumentException($"Unable serialize array with elements of: {elementType}"); 167 | } 168 | 169 | await reader.read(array, elementObjectType, elementType, nullable, 0, new int[array.Rank], resolver).ConfigureAwait(false); 170 | 171 | return array; 172 | } 173 | 174 | private static void write(this BinaryWriter writer, Array array, bool nullable, bool writeHeader, int dimension, int[] indexArray, IResolveProxyIds resolver) 175 | { 176 | var upperBound = array.GetUpperBound(dimension); 177 | var lastDimension = dimension == array.Rank - 1; 178 | for (indexArray[dimension] = array.GetLowerBound(dimension); indexArray[dimension] <= upperBound; indexArray[dimension]++) 179 | { 180 | if (lastDimension) 181 | { 182 | var element = array.GetValue(indexArray); 183 | 184 | if (nullable) 185 | { 186 | writer.Write(element == null); 187 | } 188 | 189 | if (element != null) 190 | { 191 | writer.Write(element.GetType(), element, resolver, writeHeader); 192 | } 193 | } 194 | else 195 | { 196 | writer.write(array, nullable, writeHeader, dimension + 1, indexArray, resolver); 197 | } 198 | } 199 | } 200 | public static void Write(this BinaryWriter writer, Type arrayType, Array array, IResolveProxyIds resolver, bool writeHeader = true) 201 | { 202 | if (writeHeader) 203 | { 204 | writer.Write((byte)ObjectType.Array); 205 | } 206 | 207 | var elementType = arrayType.GetElementType(); 208 | 209 | // ReSharper disable once PossibleNullReferenceException 210 | var nullable = !elementType.IsValueType || Nullable.GetUnderlyingType(elementType) != null; 211 | 212 | writer.Write(elementType, false); 213 | writer.Write((byte)array.Rank); 214 | 215 | for (var dimension = 0; dimension < array.Rank; dimension++) 216 | { 217 | writer.Write(array.GetLength(dimension)); 218 | writer.Write(array.GetLowerBound(dimension)); 219 | } 220 | 221 | writer.write(array, nullable, elementType.IsClass && !elementType.IsSealed, 0, new int[array.Rank], resolver); 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Serializer/Default/DateTimeReaderWrite.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace AppDomainAlternative.Serializer.Default 5 | { 6 | internal static class DateTimeReaderWrite 7 | { 8 | public static DateTime ReadDateTime(this BinaryReader reader) 9 | { 10 | var kind = (DateTimeKind)reader.ReadByte(); 11 | var ticks = reader.ReadInt64(); 12 | return new DateTime(ticks, kind); 13 | } 14 | 15 | public static void Write(this BinaryWriter writer, DateTime value, bool writeHeader = true) 16 | { 17 | if (writeHeader) 18 | { 19 | writer.Write((byte)ObjectType.DateTime); 20 | } 21 | writer.Write((byte)value.Kind); 22 | writer.Write(value.Ticks); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Serializer/Default/DefaultSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace AppDomainAlternative.Serializer.Default 7 | { 8 | internal class DefaultSerializer : IAmASerializer 9 | { 10 | public Task Serialize(BinaryWriter writer, Type valueType, object value, IResolveProxyIds resolver) 11 | { 12 | writer.Write(valueType, value, resolver); 13 | return Task.CompletedTask; 14 | } 15 | 16 | public Task Deserialize(BinaryReader reader, Type valueType, IResolveProxyIds resolver, CancellationToken token) => 17 | reader.ReadObject(resolver); 18 | 19 | public bool CanSerialize(Type type) => true; 20 | 21 | public static IAmASerializer Resolve(string name) => Instance; 22 | 23 | public static readonly IAmASerializer Instance = new DefaultSerializer(); 24 | 25 | public string Name { get; } = $"DefaultSerializer@{typeof(DefaultSerializer).Assembly.GetName().Version}"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Serializer/Default/EnumReaderWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace AppDomainAlternative.Serializer.Default 5 | { 6 | internal static class EnumReaderWriter 7 | { 8 | public static object ReadEnum(this BinaryReader reader, Type type = null) 9 | { 10 | var enumType = type ?? reader.ReadType(); 11 | 12 | var integralType = Enum.GetUnderlyingType(enumType); 13 | object integralValue; 14 | 15 | if (integralType == typeof(int)) 16 | { 17 | integralValue = reader.ReadInt32(); 18 | } 19 | else if (integralType == typeof(byte)) 20 | { 21 | integralValue = reader.ReadByte(); 22 | } 23 | else if (integralType == typeof(uint)) 24 | { 25 | integralValue = reader.ReadUInt32(); 26 | } 27 | else if (integralType == typeof(long)) 28 | { 29 | integralValue = reader.ReadInt64(); 30 | } 31 | else if (integralType == typeof(ulong)) 32 | { 33 | integralValue = reader.ReadUInt64(); 34 | } 35 | else if (integralType == typeof(sbyte)) 36 | { 37 | integralValue = reader.ReadSByte(); 38 | } 39 | else if (integralType == typeof(short)) 40 | { 41 | integralValue = reader.ReadInt16(); 42 | } 43 | else if (integralType == typeof(ushort)) 44 | { 45 | integralValue = reader.ReadUInt16(); 46 | } 47 | else 48 | { 49 | //this should not be possible based on the limitations of the enum type 50 | throw new InvalidOperationException("Unable to serialize the enum value."); 51 | } 52 | 53 | return Enum.ToObject(enumType, integralValue); 54 | } 55 | 56 | public static void WriteEnum(this BinaryWriter writer, Type type, object value, bool writeHeader = true) 57 | { 58 | if (writeHeader) 59 | { 60 | writer.Write((byte)ObjectType.Enum); 61 | writer.Write(type, false); 62 | } 63 | 64 | //this gets the .Net integral primitive type behind the enum (ie byte, int, etc.) 65 | var integralType = Enum.GetUnderlyingType(type); 66 | 67 | if (integralType == typeof(int)) 68 | { 69 | writer.Write((int)value); 70 | } 71 | else if (integralType == typeof(byte)) 72 | { 73 | writer.Write((byte)value); 74 | } 75 | else if (integralType == typeof(uint)) 76 | { 77 | writer.Write((uint)value); 78 | } 79 | else if (integralType == typeof(long)) 80 | { 81 | writer.Write((long)value); 82 | } 83 | else if (integralType == typeof(ulong)) 84 | { 85 | writer.Write((ulong)value); 86 | } 87 | else if (integralType == typeof(sbyte)) 88 | { 89 | writer.Write((sbyte)value); 90 | } 91 | else if (integralType == typeof(short)) 92 | { 93 | writer.Write((short)value); 94 | } 95 | else if (integralType == typeof(ushort)) 96 | { 97 | writer.Write((ushort)value); 98 | } 99 | else 100 | { 101 | //this should not be possible based on the limitations of the enum type 102 | throw new InvalidOperationException("Unable to serialize the enum value."); 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Serializer/Default/GuidReaderWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | 5 | namespace AppDomainAlternative.Serializer.Default 6 | { 7 | internal static class GuidReaderWriter 8 | { 9 | public static async Task ReadGuid(this BinaryReader reader) 10 | { 11 | var guid = new byte[16]; 12 | var guidIndex = 0; 13 | while (guidIndex < guid.Length) 14 | { 15 | guidIndex += await reader.BaseStream.ReadAsync(guid, guidIndex, guid.Length - guidIndex).ConfigureAwait(false); 16 | } 17 | return new Guid(guid); 18 | } 19 | 20 | public static void Write(this BinaryWriter writer, Guid value, bool writeHeader = true) 21 | { 22 | if (writeHeader) 23 | { 24 | writer.Write((byte)ObjectType.Guid); 25 | } 26 | writer.BaseStream.Write(value.ToByteArray(), 0, 16); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Serializer/Default/ObjectReaderWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | 5 | // ReSharper disable IdentifierTypo 6 | 7 | namespace AppDomainAlternative.Serializer.Default 8 | { 9 | /// 10 | /// A header byte that describes next payload of data in the stream. 11 | /// 12 | internal enum ObjectType : byte 13 | { 14 | //other common types 15 | 16 | Array, 17 | Serializable, 18 | Enum, 19 | Null, 20 | Proxy, 21 | Type, 22 | Types, 23 | 24 | //common struct types 25 | 26 | DateTime, 27 | Guid, 28 | TimeSpan, 29 | 30 | //primitive types & string 31 | 32 | Boolean, 33 | Byte, 34 | Bytes, 35 | Char, 36 | Decimal, 37 | Double, 38 | Float, 39 | Int, 40 | Long, 41 | SByte, 42 | Short, 43 | Str, 44 | UInt, 45 | ULong, 46 | UShort 47 | } 48 | 49 | internal static class ObjectReaderWriter 50 | { 51 | public static Task ReadObject(this BinaryReader reader, IResolveProxyIds resolver) => reader.ReadObject((ObjectType)reader.ReadByte(), resolver); 52 | public static async Task ReadObject(this BinaryReader reader, ObjectType objectType, IResolveProxyIds resolver) 53 | { 54 | switch (objectType) 55 | { 56 | case ObjectType.Array: 57 | return await reader.ReadArray(resolver).ConfigureAwait(false); 58 | 59 | case ObjectType.Serializable: 60 | return await reader.ReadSerializable(resolver).ConfigureAwait(false); 61 | 62 | case ObjectType.Enum: 63 | return reader.ReadEnum(); 64 | 65 | case ObjectType.Null: 66 | return null; 67 | 68 | case ObjectType.Proxy: 69 | var id = reader.ReadInt64(); 70 | return resolver.TryToGetInstance(id, out var instance) ? instance : null; 71 | 72 | case ObjectType.Type: 73 | return reader.ReadType(); 74 | 75 | case ObjectType.DateTime: 76 | return reader.ReadDateTime(); 77 | 78 | case ObjectType.Guid: 79 | return await reader.ReadGuid().ConfigureAwait(false); 80 | 81 | case ObjectType.TimeSpan: 82 | return reader.ReadTimeSpan(); 83 | 84 | case ObjectType.Byte: 85 | return reader.ReadByte(); 86 | case ObjectType.Bytes: 87 | var bytes = new byte[reader.ReadInt32()]; 88 | var byteIndex = 0; 89 | while (byteIndex < bytes.Length) 90 | { 91 | byteIndex += await reader.BaseStream.ReadAsync(bytes, byteIndex, bytes.Length - byteIndex).ConfigureAwait(false); 92 | } 93 | return bytes; 94 | 95 | case ObjectType.Boolean: 96 | return reader.ReadBoolean(); 97 | 98 | case ObjectType.Char: 99 | return reader.ReadChar(); 100 | 101 | case ObjectType.Decimal: 102 | return reader.ReadDecimal(); 103 | 104 | case ObjectType.Double: 105 | return reader.ReadDouble(); 106 | 107 | case ObjectType.Float: 108 | return reader.ReadSingle(); 109 | 110 | case ObjectType.Int: 111 | return reader.ReadInt32(); 112 | 113 | case ObjectType.Long: 114 | return reader.ReadInt64(); 115 | 116 | case ObjectType.SByte: 117 | return reader.ReadSByte(); 118 | 119 | case ObjectType.Short: 120 | return reader.ReadInt16(); 121 | 122 | case ObjectType.Str: 123 | return reader.ReadString(); 124 | 125 | case ObjectType.UInt: 126 | return reader.ReadUInt32(); 127 | 128 | case ObjectType.ULong: 129 | return reader.ReadUInt64(); 130 | 131 | case ObjectType.UShort: 132 | return reader.ReadUInt16(); 133 | 134 | default: 135 | throw new ArgumentOutOfRangeException(); 136 | } 137 | } 138 | 139 | public static void WriteNull(this BinaryWriter writer) => writer.Write((byte)ObjectType.Null); 140 | public static void Write(this BinaryWriter writer, Type valueType, object value, IResolveProxyIds resolver, bool writeHeader = true) 141 | { 142 | if (value == null) 143 | { 144 | writer.WriteNull(); 145 | return; 146 | } 147 | 148 | if (typeof(Type).IsAssignableFrom(valueType)) 149 | { 150 | writer.Write((Type)value); 151 | return; 152 | } 153 | 154 | if (valueType.IsArray) 155 | { 156 | writer.Write(valueType, (Array)value, resolver, writeHeader); 157 | return; 158 | } 159 | 160 | if (valueType.IsEnum) 161 | { 162 | writer.WriteEnum(valueType, value, writeHeader); 163 | return; 164 | } 165 | 166 | if (valueType.IsClass && 167 | !valueType.IsSealed && 168 | resolver.TryToGetInstanceId(value, out var id)) 169 | { 170 | if (writeHeader) 171 | { 172 | writer.Write((byte)ObjectType.Proxy); 173 | } 174 | writer.Write(id); 175 | return; 176 | } 177 | 178 | if (valueType == typeof(DateTime) || valueType == typeof(DateTime?)) 179 | { 180 | writer.Write((DateTime)value, writeHeader); 181 | return; 182 | } 183 | 184 | if (valueType == typeof(Guid) || valueType == typeof(Guid?)) 185 | { 186 | writer.Write((Guid)value, writeHeader); 187 | return; 188 | } 189 | 190 | if (valueType == typeof(TimeSpan) || valueType == typeof(TimeSpan?)) 191 | { 192 | writer.Write((TimeSpan)value, writeHeader); 193 | return; 194 | } 195 | 196 | if (valueType == typeof(bool) || valueType == typeof(bool?)) 197 | { 198 | if (writeHeader) 199 | { 200 | writer.Write((byte)ObjectType.Boolean); 201 | } 202 | writer.Write((bool)value); 203 | return; 204 | } 205 | 206 | if (valueType == typeof(byte) || valueType == typeof(byte?)) 207 | { 208 | if (writeHeader) 209 | { 210 | writer.Write((byte)ObjectType.Byte); 211 | } 212 | writer.Write((byte)value); 213 | return; 214 | } 215 | 216 | if (valueType == typeof(byte[])) 217 | { 218 | if (writeHeader) 219 | { 220 | writer.Write((byte)ObjectType.Bytes); 221 | } 222 | var valueBytes = (byte[])value; 223 | writer.Write(valueBytes.Length); 224 | writer.BaseStream.Write(valueBytes, 0, valueBytes.Length); 225 | return; 226 | } 227 | 228 | if (valueType == typeof(char) || valueType == typeof(char?)) 229 | { 230 | if (writeHeader) 231 | { 232 | writer.Write((byte)ObjectType.Char); 233 | } 234 | writer.Write((char)value); 235 | return; 236 | } 237 | 238 | if (valueType == typeof(decimal) || valueType == typeof(decimal?)) 239 | { 240 | if (writeHeader) 241 | { 242 | writer.Write((byte)ObjectType.Decimal); 243 | } 244 | writer.Write((decimal)value); 245 | return; 246 | } 247 | 248 | if (valueType == typeof(double) || valueType == typeof(double?)) 249 | { 250 | if (writeHeader) 251 | { 252 | writer.Write((byte)ObjectType.Double); 253 | } 254 | writer.Write((double)value); 255 | return; 256 | } 257 | 258 | if (valueType == typeof(float) || valueType == typeof(float?)) 259 | { 260 | if (writeHeader) 261 | { 262 | writer.Write((byte)ObjectType.Float); 263 | } 264 | writer.Write((float)value); 265 | return; 266 | } 267 | 268 | if (valueType == typeof(int) || valueType == typeof(int?)) 269 | { 270 | if (writeHeader) 271 | { 272 | writer.Write((byte)ObjectType.Int); 273 | } 274 | writer.Write((int)value); 275 | return; 276 | } 277 | 278 | if (valueType == typeof(long) || valueType == typeof(long?)) 279 | { 280 | if (writeHeader) 281 | { 282 | writer.Write((byte)ObjectType.Long); 283 | } 284 | writer.Write((long)value); 285 | return; 286 | } 287 | 288 | if (valueType == typeof(sbyte) || valueType == typeof(sbyte?)) 289 | { 290 | if (writeHeader) 291 | { 292 | writer.Write((byte)ObjectType.SByte); 293 | } 294 | writer.Write((sbyte)value); 295 | return; 296 | } 297 | 298 | if (valueType == typeof(short) || valueType == typeof(short?)) 299 | { 300 | if (writeHeader) 301 | { 302 | writer.Write((byte)ObjectType.Short); 303 | } 304 | writer.Write((short)value); 305 | return; 306 | } 307 | 308 | if (valueType == typeof(string)) 309 | { 310 | if (writeHeader) 311 | { 312 | writer.Write((byte)ObjectType.Str); 313 | } 314 | writer.Write((string)value); 315 | return; 316 | } 317 | 318 | if (valueType == typeof(uint) || valueType == typeof(uint?)) 319 | { 320 | if (writeHeader) 321 | { 322 | writer.Write((byte)ObjectType.UInt); 323 | } 324 | writer.Write((uint)value); 325 | return; 326 | } 327 | 328 | if (valueType == typeof(ulong) || valueType == typeof(ulong?)) 329 | { 330 | if (writeHeader) 331 | { 332 | writer.Write((byte)ObjectType.ULong); 333 | } 334 | writer.Write((ulong)value); 335 | return; 336 | } 337 | 338 | if (valueType == typeof(ushort) || valueType == typeof(ushort?)) 339 | { 340 | if (writeHeader) 341 | { 342 | writer.Write((byte)ObjectType.UShort); 343 | } 344 | writer.Write((ushort)value); 345 | return; 346 | } 347 | 348 | if (valueType.IsSerializable) 349 | { 350 | writer.WriteSerializable(resolver, valueType, value, writeHeader); 351 | return; 352 | } 353 | 354 | throw new ArgumentException($"Unable serialize: {value.GetType()}"); 355 | } 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Serializer/Default/SerializableReaderWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Runtime.Serialization; 7 | using System.Threading.Tasks; 8 | using DefaultFormatterConverter = System.Runtime.Serialization.FormatterConverter; 9 | 10 | namespace AppDomainAlternative.Serializer.Default 11 | { 12 | internal static class SerializableReaderWriter 13 | { 14 | private static ConstructorInfo getCtor(this Type type) => 15 | serializableConstructors.GetOrAdd(type, @class => 16 | @class.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new [] { typeof(SerializationInfo), typeof(StreamingContext) }, null) ?? 17 | @class.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(SerializationInfo), typeof(StreamingContext) }, null) ?? 18 | throw new InvalidOperationException("Unable to find serialization constructor.")); 19 | private static FieldInfo[] getFields(this Type type) 20 | { 21 | if (!serializableFields.TryGetValue(type, out var members)) 22 | { 23 | var fields = FormatterServices.GetSerializableMembers(type); 24 | if (fields != null) 25 | { 26 | serializableFields[type] = members = fields.Cast().ToArray(); 27 | } 28 | } 29 | 30 | if (members == null) 31 | { 32 | throw new ArgumentException($"Unable serialize: {type}"); 33 | } 34 | 35 | return members; 36 | } 37 | private static readonly ConcurrentDictionary serializableConstructors = new ConcurrentDictionary(); 38 | private static readonly ConcurrentDictionary serializableFields = new ConcurrentDictionary(); 39 | 40 | public static async Task ReadSerializable(this BinaryReader reader, IResolveProxyIds resolver, Type type = null) 41 | { 42 | type = type ?? reader.ReadType(); 43 | 44 | var useCustomSerializer = reader.ReadBoolean(); 45 | 46 | if (useCustomSerializer) 47 | { 48 | var context = new StreamingContext(StreamingContextStates.All, resolver); 49 | var info = new SerializationInfo(type, new DefaultFormatterConverter()); 50 | var length = reader.ReadInt32(); 51 | 52 | for (var index = 0; index < length; index++) 53 | { 54 | var itemName = reader.ReadString(); 55 | var itemType = reader.ReadType(); 56 | var item = await reader.ReadObject(resolver).ConfigureAwait(false); 57 | 58 | info.AddValue(itemName, item, itemType); 59 | } 60 | 61 | var ctor = type.getCtor(); 62 | 63 | return ctor.Invoke(new object[] 64 | { 65 | info, context 66 | }); 67 | } 68 | 69 | var returnValue = FormatterServices.GetUninitializedObject(type); 70 | 71 | foreach (var field in type.getFields()) 72 | { 73 | field.SetValue(returnValue, await reader.ReadObject(resolver).ConfigureAwait(false)); 74 | } 75 | 76 | return returnValue; 77 | } 78 | public static void WriteSerializable(this BinaryWriter writer, IResolveProxyIds resolver, Type type, object value, bool writeHeader = true) 79 | { 80 | if (writeHeader) 81 | { 82 | writer.Write((byte)ObjectType.Serializable); 83 | writer.Write(type, false); 84 | } 85 | 86 | if (value is ISerializable serializable) 87 | { 88 | //true for custom serialization 89 | writer.Write(true); 90 | 91 | type.getCtor(); 92 | 93 | var context = new StreamingContext(StreamingContextStates.All, resolver); 94 | var info = new SerializationInfo(type, new DefaultFormatterConverter()); 95 | serializable.GetObjectData(info, context); 96 | 97 | var start = writer.BaseStream.Position; 98 | writer.Write(new byte[sizeof(int)], 0, sizeof(int)); 99 | 100 | var length = 0; 101 | foreach (var entry in info) 102 | { 103 | length++; 104 | writer.Write(entry.Name); 105 | writer.Write(entry.ObjectType, false); 106 | writer.Write(entry.Value?.GetType() ?? entry.ObjectType, entry.Value, resolver); 107 | } 108 | 109 | var stop = writer.BaseStream.Position; 110 | writer.BaseStream.Position = start; 111 | writer.Write(length); 112 | writer.BaseStream.Position = stop; 113 | } 114 | else 115 | { 116 | //false for custom serialization 117 | writer.Write(false); 118 | 119 | foreach (var field in type.getFields()) 120 | { 121 | var fieldValue = field.GetValue(value); 122 | writer.Write(fieldValue?.GetType() ?? field.FieldType, fieldValue, resolver); 123 | } 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Serializer/Default/TimeSpanReaderWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace AppDomainAlternative.Serializer.Default 5 | { 6 | internal static class TimeSpanReaderWriter 7 | { 8 | public static TimeSpan ReadTimeSpan(this BinaryReader reader) => new TimeSpan(reader.ReadInt64()); 9 | 10 | public static void Write(this BinaryWriter writer, TimeSpan value, bool writeHeader = true) 11 | { 12 | if (writeHeader) 13 | { 14 | writer.Write((byte)ObjectType.TimeSpan); 15 | } 16 | writer.Write(value.Ticks); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Serializer/Default/TypeReaderWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | // ReSharper disable IdentifierTypo 7 | 8 | namespace AppDomainAlternative.Serializer.Default 9 | { 10 | /// 11 | /// When serializing an this enum value can be used as short hand for common .Net types. 12 | /// 13 | internal enum ShortTypes : byte 14 | { 15 | Unknown, 16 | 17 | Type, 18 | Types, 19 | 20 | DateTime, 21 | NullableDateTime, 22 | DateTimes, 23 | 24 | Guid, 25 | NullableGuid, 26 | Guids, 27 | 28 | TimeSpan, 29 | NullableTimeSpan, 30 | TimeSpans, 31 | 32 | Boolean, 33 | NullableBoolean, 34 | Booleans, 35 | 36 | Byte, 37 | NullableByte, 38 | Bytes, 39 | 40 | Char, 41 | NullableChar, 42 | Chars, 43 | 44 | Decimal, 45 | NullableDecimal, 46 | Decimals, 47 | 48 | Double, 49 | NullableDouble, 50 | Doubles, 51 | 52 | Float, 53 | NullableFloat, 54 | Floats, 55 | 56 | Int, 57 | NullableInt, 58 | Ints, 59 | 60 | Long, 61 | NullableLong, 62 | Longs, 63 | 64 | Obj, 65 | Objs, 66 | 67 | SByte, 68 | NullableSByte, 69 | SBytes, 70 | 71 | Short, 72 | NullableShort, 73 | Shorts, 74 | 75 | Str, 76 | Strs, 77 | 78 | UInt, 79 | NullableUInt, 80 | UInts, 81 | 82 | ULong, 83 | NullableULong, 84 | ULongs, 85 | 86 | UShort, 87 | NullableUShort, 88 | UShorts 89 | } 90 | 91 | internal static class TypeReaderWriter 92 | { 93 | private static readonly Dictionary map = new Dictionary 94 | { 95 | { typeof(DateTime), ShortTypes.DateTime }, 96 | { typeof(DateTime?), ShortTypes.NullableDateTime }, 97 | { typeof(DateTime[]), ShortTypes.DateTimes }, 98 | 99 | { typeof(Guid), ShortTypes.Guid }, 100 | { typeof(Guid?), ShortTypes.NullableGuid }, 101 | { typeof(Guid[]), ShortTypes.Guids }, 102 | 103 | { typeof(TimeSpan), ShortTypes.TimeSpan }, 104 | { typeof(TimeSpan?), ShortTypes.NullableTimeSpan }, 105 | { typeof(TimeSpan[]), ShortTypes.TimeSpans }, 106 | 107 | { typeof(Type), ShortTypes.Type }, 108 | { typeof(Type[]), ShortTypes.Types }, 109 | 110 | { typeof(bool), ShortTypes.Boolean }, 111 | { typeof(bool?), ShortTypes.NullableBoolean }, 112 | { typeof(bool[]), ShortTypes.Booleans }, 113 | 114 | { typeof(byte), ShortTypes.Byte }, 115 | { typeof(byte?), ShortTypes.NullableByte }, 116 | { typeof(byte[]), ShortTypes.Bytes }, 117 | 118 | { typeof(char), ShortTypes.Char }, 119 | { typeof(char?), ShortTypes.NullableChar }, 120 | { typeof(char[]), ShortTypes.Chars }, 121 | 122 | { typeof(decimal), ShortTypes.Decimal }, 123 | { typeof(decimal?), ShortTypes.NullableDecimal }, 124 | { typeof(decimal[]), ShortTypes.Decimals }, 125 | 126 | { typeof(double), ShortTypes.Double }, 127 | { typeof(double?), ShortTypes.NullableDouble }, 128 | { typeof(double[]), ShortTypes.Doubles }, 129 | 130 | { typeof(float), ShortTypes.Float }, 131 | { typeof(float?), ShortTypes.NullableFloat }, 132 | { typeof(float[]), ShortTypes.Floats }, 133 | 134 | { typeof(int), ShortTypes.Int }, 135 | { typeof(int?), ShortTypes.NullableInt }, 136 | { typeof(int[]), ShortTypes.Ints }, 137 | 138 | { typeof(long), ShortTypes.Long }, 139 | { typeof(long?), ShortTypes.NullableLong }, 140 | { typeof(long[]), ShortTypes.Longs }, 141 | 142 | { typeof(object), ShortTypes.Obj }, 143 | { typeof(object[]), ShortTypes.Objs }, 144 | 145 | { typeof(sbyte), ShortTypes.SByte }, 146 | { typeof(sbyte?), ShortTypes.NullableSByte }, 147 | { typeof(sbyte[]), ShortTypes.SBytes }, 148 | 149 | { typeof(short), ShortTypes.Short }, 150 | { typeof(short?), ShortTypes.NullableShort }, 151 | { typeof(short[]), ShortTypes.Shorts }, 152 | 153 | { typeof(string), ShortTypes.Str }, 154 | { typeof(string[]), ShortTypes.Strs }, 155 | 156 | { typeof(uint), ShortTypes.UInt }, 157 | { typeof(uint?), ShortTypes.NullableUInt }, 158 | { typeof(uint[]), ShortTypes.UInts }, 159 | 160 | { typeof(ulong), ShortTypes.ULong }, 161 | { typeof(ulong?), ShortTypes.NullableULong }, 162 | { typeof(ulong[]), ShortTypes.ULongs }, 163 | 164 | { typeof(ushort), ShortTypes.UShort }, 165 | { typeof(ushort?), ShortTypes.NullableUShort }, 166 | { typeof(ushort[]), ShortTypes.UShorts } 167 | }; 168 | 169 | public static Type ReadType(this BinaryReader reader) 170 | { 171 | var shortType = (ShortTypes)reader.ReadByte(); 172 | return shortType == ShortTypes.Unknown ? Type.GetType(reader.ReadString(), true) : map.First(item => item.Value == shortType).Key; 173 | } 174 | 175 | public static void Write(this BinaryWriter writer, Type type, bool writeHeader = true) 176 | { 177 | if (writeHeader) 178 | { 179 | writer.Write((byte)ObjectType.Type); 180 | } 181 | if (map.TryGetValue(typeof(Type).IsAssignableFrom(type) ? typeof(Type) : type, out var shortType)) 182 | { 183 | writer.Write((byte)shortType); 184 | } 185 | else 186 | { 187 | writer.Write((byte)ShortTypes.Unknown); 188 | writer.Write(type.AssemblyQualifiedName ?? throw new ArgumentException("Unable to serialize type.")); 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Serializer/IAmASerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace AppDomainAlternative.Serializer 7 | { 8 | /// 9 | /// An interface for a serializer that will serialize/deserialize data for IPC. 10 | /// 11 | public interface IAmASerializer 12 | { 13 | /// 14 | /// Serializes an object. 15 | /// 16 | Task Serialize(BinaryWriter writer, Type valueType, object value, IResolveProxyIds resolver); 17 | 18 | /// 19 | /// Deserializes an object. 20 | /// 21 | Task Deserialize(BinaryReader reader, Type valueType, IResolveProxyIds resolver, CancellationToken token); 22 | 23 | /// 24 | /// Test if a type can be serialized. 25 | /// 26 | bool CanSerialize(Type type); 27 | 28 | /// 29 | /// A name identifier for the serializer. 30 | /// 31 | string Name { get; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/AppDomainAlternative/Serializer/IResolveProxyIds.cs: -------------------------------------------------------------------------------- 1 | namespace AppDomainAlternative.Serializer 2 | { 3 | /// 4 | /// A resolver for resolving instance to id and vice versa. 5 | /// 6 | public interface IResolveProxyIds 7 | { 8 | /// 9 | /// Attempts to get the id for an instance. 10 | /// 11 | bool TryToGetInstanceId(object instance, out long id); 12 | 13 | /// 14 | /// Attempts to get the instance for an id. 15 | /// 16 | bool TryToGetInstance(long id, out object instance); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ClientApp/ClientApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7.1 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/ClientApp/ClientProgram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using AppDomainAlternative; 4 | using Common; 5 | 6 | namespace ClientApp 7 | { 8 | public static class ClientProgram 9 | { 10 | public static async Task Main() 11 | { 12 | if (Domains.Current.Channels == null) 13 | { 14 | Console.ForegroundColor = ConsoleColor.Red; 15 | Console.WriteLine("Host not found!"); 16 | return; 17 | } 18 | 19 | const bool host = false; 20 | 21 | var chatRoomInstanceInfo = await Domains.Current.Channels.GetInstanceOf(filter: (isHost, instance) => isHost == host).ConfigureAwait(false); 22 | 23 | await chatRoomInstanceInfo.Instance.SendMessage(host, "Hello server.").ConfigureAwait(false); 24 | 25 | Console.ReadLine(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Common/ChatRoom.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using AppDomainAlternative; 4 | 5 | namespace Common 6 | { 7 | public class ChatRoom 8 | { 9 | public ChatRoom(string name) 10 | { 11 | Name = name; 12 | 13 | Console.ForegroundColor = ConsoleColor.White; 14 | Console.WriteLine($"Chat room \"{name}\" started for pid {Domains.Current.Process.Id}."); 15 | } 16 | 17 | public string Name { get; } 18 | 19 | public virtual Task SendMessage(bool isHost, string message) 20 | { 21 | Console.ForegroundColor = isHost ? ConsoleColor.Green : ConsoleColor.Yellow; 22 | Console.WriteLine($"{(isHost ? "Host" : "Client")}: {message}"); 23 | Console.ForegroundColor = ConsoleColor.Gray; 24 | return Task.CompletedTask; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Common/Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/HostApp/HostApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7.1 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/HostApp/HostProgram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using AppDomainAlternative; 6 | using Common; 7 | 8 | namespace HostApp 9 | { 10 | public static class HostProgram 11 | { 12 | public static async Task Main() 13 | { 14 | const bool host = true; 15 | 16 | //get the location for the client exe 17 | var clientAppLocation = typeof(HostProgram).Assembly.Location.Replace("HostApp", "ClientApp"); 18 | 19 | var currentDomain = Domains.Current; 20 | 21 | //start the client exe 22 | var childDomain = currentDomain.AddChildDomain(new ProcessStartInfo("dotnet", $"\"{clientAppLocation}\"")); 23 | 24 | //create a shared instance between these processes 25 | //the instance will be hosted on this process and the client will proxy it calls to the instance to this process 26 | var chatRoom = (ChatRoom)await childDomain.Channels.CreateInstance(typeof(ChatRoom).GetConstructors().First(), host, "Interprocess Communication Chat").ConfigureAwait(false); 27 | 28 | await chatRoom.SendMessage(host, "Client are you there?").ConfigureAwait(false); 29 | 30 | Console.ReadLine(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/VsDebugger/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text.RegularExpressions; 6 | using EnvDTE; 7 | using OsProcess = System.Diagnostics.Process; 8 | 9 | namespace VsDebugger 10 | { 11 | public static class Program 12 | { 13 | public static int Main(string[] commandArgs) 14 | { 15 | var args = commandArgs 16 | .Where(arg => arg.Length > 0 && arg[0] == '-') 17 | .Select(arg => arg.Split(new [] { ':' }, 2)) 18 | .ToDictionary(arg => arg[0].Substring(1).Trim(), arg => arg.Length == 1 ? "" : arg[1].Trim(), StringComparer.OrdinalIgnoreCase); 19 | 20 | if (!args.TryGetValue("pid", out var pidStr) || !int.TryParse(pidStr, out var pid)) 21 | { 22 | Console.WriteLine("Missing command line argument \"-pid:...\" with the target process id."); 23 | return 2; 24 | } 25 | 26 | DTE dte; 27 | string dteVersion = null; 28 | try 29 | { 30 | if (!args.TryGetValue("dtev", out dteVersion)) 31 | { 32 | using (var process = new OsProcess()) 33 | { 34 | process.StartInfo.UseShellExecute = false; 35 | process.StartInfo.FileName = "vsWhere"; 36 | process.StartInfo.RedirectStandardOutput = true; 37 | process.Start(); 38 | 39 | var output = process.StandardOutput.ReadToEnd(); 40 | 41 | process.WaitForExit(); 42 | 43 | using (var reader = new StringReader(output)) 44 | { 45 | var regex = new Regex(@"^\s*installationVersion\s*:\s*(?\d+)(\.\d+)*$", RegexOptions.IgnoreCase); 46 | while (reader.Peek() > -1) 47 | { 48 | var match = regex.Match(reader.ReadLine() ?? ""); 49 | if (match.Success) 50 | { 51 | dteVersion = $"{match.Groups["major"].Value}.0"; 52 | break; 53 | } 54 | } 55 | } 56 | } 57 | 58 | return 3; 59 | } 60 | dte = (DTE)Marshal.GetActiveObject($"VisualStudio.DTE.{dteVersion}"); 61 | } 62 | catch 63 | { 64 | Console.WriteLine($"Unable to find the DTE version \"VisualStudio.DTE.{dteVersion}\". Try providing a different name with the argument \"-dtev:\\d.\\d\"."); 65 | return 3; 66 | } 67 | 68 | AttachMode attachMode; 69 | try 70 | { 71 | if (!args.TryGetValue("debugger", out var mode) || string.Equals(mode, "attach", StringComparison.OrdinalIgnoreCase)) 72 | { 73 | attachMode = AttachMode.Attach; 74 | } 75 | else if (string.Equals(mode, "detach", StringComparison.OrdinalIgnoreCase)) 76 | { 77 | attachMode = AttachMode.Detach; 78 | } 79 | else 80 | { 81 | throw new Exception(); 82 | } 83 | } 84 | catch 85 | { 86 | Console.WriteLine($"Unable to attach debugger to process ID: {pid}"); 87 | return 4; 88 | } 89 | 90 | int parentProcessId; 91 | try 92 | { 93 | if (!args.TryGetValue("ppid", out var parentProcessIdStr) || !int.TryParse(parentProcessIdStr, out parentProcessId)) 94 | { 95 | parentProcessId = -1; 96 | } 97 | } 98 | catch 99 | { 100 | parentProcessId = -1; 101 | } 102 | 103 | MessageFilter.Register(); 104 | 105 | Process target; 106 | try 107 | { 108 | if (attachMode == AttachMode.Detach) 109 | { 110 | target = dte.Debugger.DebuggedProcesses.OfType().FirstOrDefault(process => process.ProcessID == pid); 111 | } 112 | else 113 | { 114 | Console.WriteLine($"Ppid: {parentProcessId}"); 115 | var parentProcess = parentProcessId == -1 ? null : 116 | dte.Debugger.DebuggedProcesses.OfType().FirstOrDefault(process => process.ProcessID == parentProcessId) ?? 117 | dte.Debugger.LocalProcesses.OfType().FirstOrDefault(process => process.ProcessID == parentProcessId); 118 | 119 | target = (parentProcess?.Parent?.LocalProcesses ?? dte.Debugger.LocalProcesses).OfType().FirstOrDefault(process => process.ProcessID == pid); 120 | } 121 | } 122 | catch (Exception error) 123 | { 124 | Console.WriteLine($"Unable to find processes to debug: {error}"); 125 | return 5; 126 | } 127 | 128 | if (target == null) 129 | { 130 | Console.WriteLine($"Unable to find process ID: {pid}"); 131 | return 6; 132 | } 133 | 134 | try 135 | { 136 | if (attachMode == AttachMode.Attach) 137 | { 138 | target.Attach(); 139 | } 140 | else 141 | { 142 | target.Detach(false); 143 | } 144 | } 145 | catch 146 | { 147 | Console.WriteLine($"Unable to attach/detach the debugger to/from process ID: {pid}"); 148 | return 7; 149 | } 150 | 151 | MessageFilter.Revoke(); 152 | 153 | return 1; 154 | } 155 | } 156 | 157 | public enum AttachMode 158 | { 159 | Attach, 160 | Detach 161 | } 162 | 163 | [ComImport, Guid("00000016-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 164 | public interface IOleMessageFilter 165 | { 166 | [PreserveSig] 167 | int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo); 168 | 169 | [PreserveSig] 170 | int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType); 171 | 172 | [PreserveSig] 173 | int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType); 174 | } 175 | 176 | public class MessageFilter : IOleMessageFilter 177 | { 178 | private const int handled = 0, retryAllowed = 2, retry = 99, cancel = -1, waitAndDispatch = 2; 179 | 180 | int IOleMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo) => handled; 181 | 182 | int IOleMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType) => dwRejectType == retryAllowed ? retry : cancel; 183 | 184 | int IOleMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType) => waitAndDispatch; 185 | 186 | public static void Register() => CoRegisterMessageFilter(new MessageFilter()); 187 | 188 | public static void Revoke() => CoRegisterMessageFilter(null); 189 | 190 | public static void CoRegisterMessageFilter(IOleMessageFilter newFilter) => CoRegisterMessageFilter(newFilter, out _); 191 | 192 | [DllImport("Ole32.dll")] 193 | public static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter); 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /src/VsDebugger/VsDebugger.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472 5 | Exe 6 | 7.1 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | --------------------------------------------------------------------------------