Process isolation library for .NET that protects your application from legacy, unmanaged, or problematic code by running it in separate sandboxed processes.
You have legacy code that:
- Calls into unmanaged COM objects or native DLLs
- Has resource leaks (memory, handles, GDI objects)
- Is single-threaded but your app is multi-threaded
- Occasionally crashes or hangs
- You can't easily modify or replace
ProcessSandbox solves this by running the problematic code in isolated worker processes with automatic resource monitoring, lifecycle management, and transparent proxying.
- 🛡️ Process Isolation: Crashes and leaks don't affect your main application
- 🔄 Automatic Recycling: Workers recycled based on memory, handles, or call count
- 🎯 Interface-Based Proxy: Use your interfaces naturally, calls routed transparently
- ⚡ High Performance: Named pipes + MessagePack for fast IPC
- 🔧 32/64-bit Support: Run 32-bit workers from 64-bit apps (COM interop)
- 🚫 No Orphans: Job Objects ensure workers never leak
- 📊 Resource Monitoring: Track memory, GDI/USER handles, call counts
- 🧵 Thread-Safe: Multiple threads can call concurrently
- 🎚️ Configurable: Extensive options for pool size, limits, timeouts
See the tutorial for an easy to follow example of how ProcessSandbox.Runner works.
See the com tutorial to see an example of how to call a 32 bit com object in an Azure App Service.
dotnet add package ProcessSandbox.Runnerusing ProcessSandbox;
using Microsoft.Extensions.Logging;
// 1. Define your interface
public interface ILegacyService
{
string ProcessData(string input);
}
// 2. Create implementation (in separate assembly)
public class LegacyServiceImpl : ILegacyService
{
public string ProcessData(string input)
{
// Your legacy/unmanaged code here
return LegacyComObject.DoWork(input);
}
}
// 3. Configuration
var config = new ProcessPoolConfiguration
{
MaxPoolSize = 5,
MaxMemoryMB = 1024,
WorkerAssembly = "MyLegacy.dll",
WorkerType = "LegacyServiceImpl"
};
// 4. Create a logger factory and configure it to use the Console provider
using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
// 5. Create the proxy factory
using var factory = await ProcessProxyFactory<ILegacyService>.CreateAsync(config, loggerFactory);
// 6. Use the factory to give you a scoped proxy
string overallResult = await factory.UseProxyAsync<string>(async proxy =>
{
// 7. Use it like any interface
// Proxy methods will run in seperate process that has loaded MyLegacy.dll
// Proxy object is stateful whist inside this code block
string result = proxy.ProcessData("test data");
string result2 = proxy.ProcessData("Some more test data");
return $"{result} {result2}";
});
Console.WriteLine(overallResult);
// Or if you prefer a lease pattern - you can do that too. Just remember to dispose it otherwise you'll hog the pool
using var lease = await await factory.AcquireLeaseAsync();
string leaseResult = $"{lease.ProcessData("test data")} {lease.ProcessData("Some more test data")}";
Console.WriteLine(leaseResult);var config = new ProcessPoolConfiguration
{
// Pool sizing
MinPoolSize = 1, // Start with 1 worker
MaxPoolSize = 5, // Scale up to 5 workers
// Worker process
DotNetVersion = DotNetVersion.Net48_32Bit, // Use a 32-bit framework dll for COM
WorkerAssembly = "MyLegacy.dll",
WorkerType = "MyLegacy.ServiceImpl",
// Resource limits
MaxMemoryMB = 1024, // Recycle at 1GB
MaxGdiHandles = 10000, // GDI handle limit
ProcessRecycleThreshold = 100,// Recycle after 100 calls
// Timeouts
MethodCallTimeout = TimeSpan.FromSeconds(90),
};Supply the ComClsid to call directly in to a com object without the need to register it.
Use extra com dependencies if you have dependant com object you need to call into. Look in src/tutorials/ComSandboxDemo for an example of how this works.
Very useful for running in web apps / app services when you don't have access to the registry
var config = new ProcessPoolConfiguration
{
DotNetVersion = DotNetVersion.Net48_32Bit,
ComClsid = new Guid("11111111-2222-3333-4444-555555555555"),
ImplementationAssemblyPath = Path.Combine(AppContext.BaseDirectory, "workers", "SimpleComDelphi32.dll"),
ExtraComDependencies = [
new ComDependency
{
Clsid = new Guid("B1E9D2C4-8A6F-4E2B-9D3D-1234567890AB"),
DllPath = Path.Combine(AppContext.BaseDirectory, "workers", "ComEngineInfo32.dll")
}
],
MaxMemoryMB = 1024, // well within 32-bit limit
MaxGdiHandles = 10000,
NewInstancePerProxy = true // Set to false if you don't want the instance to be cleared down on each run
};var config = new ProcessPoolConfiguration
{
ProcessRecycleThreshold = 1000,
MaxProcessLifetime = TimeSpan.FromHours(1)
};var config = new ProcessPoolConfiguration
{
MaxPoolSize = 10, // Handle concurrent requests
MethodCallTimeout = TimeSpan.FromMinutes(5)
};Your App (.NET) → ProcessProxy<T> → [Worker Pool] → Worker Process
↓ ↓
Named Pipes Legacy DLL/COM
- ProcessProxy: Transparent proxy implementing your interface
- Worker Pool: Manages process lifecycle and resource monitoring
- Worker Process: Isolated process hosting your implementation
- IPC: Named pipes with MessagePack serialization
git clone https://github.com/yourusername/ProcessSandbox.git
cd ProcessSandbox
dotnet build
dotnet testTypical overhead per call:
- Local calls: < 0.1ms
- Process proxy: 1-2ms (named pipes + serialization)
Perfect for scenarios where isolation benefits outweigh small latency cost.
Contributions welcome! Please read CONTRIBUTING.md first.
MIT License - see LICENSE for details.
-
Call com objects directly just via an interface contract. No need for the manifest or an intermediate c# wrapper. This would need to use the COM Binary Interface (the VTable) but skip the COM Infrastructure (the Registry and the Service Control Manager).DONE - Telemetry/metrics export
- 🐛 Report issues
- 💬 Discussions
- 📧 Email: jonnypmuir@gmail.com
If I'm having to make changes to ProcessSandbox and write an end to end solution which uses it from packages, I find it easier to test from a local nuget server rather than having to wait for nuget to publish new versions.
To do this set up a local nuget server e.g.
dotnet nuget add source ~/LocalNuGetFeed --name LocalTestFeed --at-position 1Then to build all the packages and put them in the local feed you can do the following (from the root of ProcessSanbox - e.g. where the ProcessSandbox.sln is)
dotnet build --configuration Release
dotnet build src/ProcessSandbox.Worker/ProcessSandbox.Worker.csproj -c Release -f net48 -r win-x86
dotnet pack /p:ExcludeProjects="**/ProcessSandbox.Worker.csproj" --configuration Release --no-build --output push-ready-artifacts
dotnet pack src/ProcessSandbox.Worker/ProcessSandbox.Worker.nuspec --configuration Release --output push-ready-artifacts -p:NoWarn=NU5100
cp push-ready-artifacts/* ~/LocalNuGetFeedRemember to clear the local cache when you want to use them
dotnet nuget locals all --clearAnd remember to get rid of from the local package source if you really want to pick them up from nuget.
rm ~/LocalNuGetFeed/*In order to build to SimpleCom object, you need a c compiler. On a mac you can do
brew install mingw-w64And then to build you need to
i686-w64-mingw32-gcc -shared -static -o publish/workers/net48/win-x86/SimpleCom.dll SimpleCom/SimpleCom.c -lole32 -loleaut32 -lpsapi -Wl,--add-stdcall-aliasReplace jonnymoo_rg_9172 with the name of your resource group and com-sandbox-demo-app with the name of you web app (from src/tutorials/ComSandboxDemo)
dotnet clean
rm -rf publish
rm site.zip
dotnet nuget locals all --clear
dotnet publish AzureSandboxHost/AzureSandboxHost.csproj -c Release -o ./publish
i686-w64-mingw32-gcc -shared -static -o publish/workers/net48/win-x86/SimpleCom.dll SimpleCom/SimpleCom.c -lole32 -loleaut32 -lpsapi -Wl,--add-stdcall-alias
cd publish
zip -r ../site.zip *
cd ..
az webapp deployment source config-zip --resource-group jonnymoo_rg_9172 --name com-sandbox-demo-app --src site.zipRemember if you want to deploy SimpleComDelphi.dll - you can get it from the artifacts on the github build. You will have to manually go put it onto azure in site/wwwroot/workers/net48/win-x86
