Recently I decided to migrate one of my WPF Windows desktop apps written in C# and targeting .NET Framework 4.5 to the newest .NET Core 3.1. All was good until I had to add support for single instance application while being able to pass any parameters from the second instance to the first running instance. My previous WPF implementation for single instance application was using System.Runtime.Remoting which is not available in .NET Core. Therefore I had to do something new. Below is the implementation that I came up with. It works great but I feel it can be improved. Please, feel free to discuss and improve the proposed solution.
I created a SingleInstanceService which is using a semaphore to signal if it is the first instance or not. If it is the first instance, I create a TcpListener and I wait indefinitely for any arguments passed from a second instance. If a second instance is started, then I send the parameters of the second instance to the first listening instance and then I exit the second instance.
internal class SingleInstanceService
{
internal Action<string[]> OnArgumentsReceived;
internal bool IsFirstInstance()
{
if (Semaphore.TryOpenExisting(semaphoreName, out semaphore))
{
Task.Run(() => { SendArguments(); Environment.Exit(0); });
return false;
}
else
{
semaphore = new Semaphore(0, 1, semaphoreName);
Task.Run(() => ListenForArguments());
return true;
}
}
private void ListenForArguments()
{
TcpListener tcpListener = new TcpListener(IPAddress.Parse(localHost), localPort);
try
{
tcpListener.Start();
while (true)
{
TcpClient client = tcpListener.AcceptTcpClient();
Task.Run(() => ReceivedMessage(client));
}
}
catch (SocketException ex)
{
Log.Error(ex);
tcpListener.Stop();
}
}
private void ReceivedMessage(TcpClient tcpClient)
{
try
{
using (NetworkStream networkStream = tcpClient?.GetStream())
{
string data = null;
byte[] bytes = new byte[256];
int bytesCount;
while ((bytesCount = networkStream.Read(bytes, 0, bytes.Length)) != 0)
{
data += Encoding.UTF8.GetString(bytes, 0, bytesCount);
}
OnArgumentsReceived(data.Split(' '));
}
}
catch (Exception ex)
{
Log.Error(ex);
}
}
private void SendArguments()
{
try
{
using (TcpClient tcpClient = new TcpClient(localHost, localPort))
{
using (NetworkStream networkStream = tcpClient.GetStream())
{
byte[] data = Encoding.UTF8.GetBytes(string.Join(" ", Environment.GetCommandLineArgs()));
networkStream.Write(data, 0, data.Length);
}
}
}
catch (Exception ex)
{
Log.Error(ex);
}
}
private Semaphore semaphore;
private string semaphoreName = $"Global\\{Environment.MachineName}-myAppName{Assembly.GetExecutingAssembly().GetName().Version}-sid{Process.GetCurrentProcess().SessionId}";
private string localHost = "127.0.0.1";
private int localPort = 19191;
}
Then in App.xaml.cs, I have the following code:
protected override void OnStartup(StartupEventArgs e)
{
SingleInstanceService singleInstanceService = new SingleInstanceService();
if (singleInstanceService.IsFirstInstance())
{
singleInstanceService.OnArgumentsReceived += OnArgumentsReceived;
// Some other calls
}
base.OnStartup(e);
}
private void OnArgumentsReceived(string[] args)
{
// ProcessCommandLineArgs(args);
}
Please feel free to discuss over this topic which I think is a very common use case among Windows desktop developers. Thank you.