6

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.

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
Alexandru Dicu
  • 1,151
  • 1
  • 16
  • 24
  • You can try to use `Mutex` for that, it's available in .NET core. Which arguments to do you want to pass to second instance? – Pavel Anikhouski Jan 12 '20 at 17:13
  • I chose a semaphore since it is a signaling mechanism, over a mutex which is a locking mechanism. It can be done both ways, but I preferred the semaphore which I find easier to be used for the current purpose. The requirement is to send the command line args from the second instance to the first instance. – Alexandru Dicu Jan 12 '20 at 17:24
  • Another option is to use p/invoke functions for that, like in this [thread](https://www.codeproject.com/Articles/1224031/Passing-Parameters-to-a-Running-Application-in-WPF) – Pavel Anikhouski Jan 12 '20 at 17:24
  • By migrating to .NET Core, I would advise against using p/invoke that easy. – Alexandru Dicu Jan 12 '20 at 17:33
  • 3
    I'm voting to close this question as off-topic because it belongs on Code Review Stack Exchange. – Ian Kemp Jan 14 '20 at 11:48
  • Use dependency injection. – shobhonk Jan 22 '20 at 00:40
  • To inject what and where? Can you elaborate please. – Alexandru Dicu Jan 22 '20 at 00:45

1 Answers1

-5

Use dependency injection. You will need to define a interface then implement the interface. In startup code, you will need to add the interface and it's implementation as singleton service.

In your implementation constructor can you put the code that you want to run only once. This will last lifetime of the object.

There are other type of injections, transient and scoped but for your use case you may only need singleton.

Program.cs

using System;
using Microsoft.Extensions.DependencyInjection;

namespace example
{
    class Program
    {
        static void Main(string[] args)
        {
            // create service collection
            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);

            var serviceProvider = serviceCollection.BuildServiceProvider();

            serviceProvider.GetService<Startup>().Run();
        }

        private static void ConfigureServices(IServiceCollection serviceCollection)
        {
            // add services
            serviceCollection.AddTransient<IMyService, MyService>();

            serviceCollection.AddTransient<Startup>();
        }
    }
}

Startup.cs

namespace example
{
    public class Startup
    {
        private readonly IMyService _myService;
        public Startup(IMyService myService)
        {
            _myService = myService;
        }

        public void Run()
        {
            _myService.MyMethod();
        }

    }
}

Interace

namespace example
{
    public interface IMyService
    {
        void MyMethod();
    }
}

Implementation of the service

 namespace example
    {
        public class MyService : IMyService
        {
            public MyService()
            {
                // on signleton service this only gets invoked once
            }

            public void MyMethod()
            {
                // your imolmentation here 
            }
        }
    }

Some online reference: https://dzone.com/articles/dependency-injection-in-net-core-console-applicati

https://code-maze.com/dependency-inversion-principle/

shobhonk
  • 621
  • 5
  • 15
  • 1
    I am sorry, but I don't see how this is related to the topic in question: single instance application and passing arguments to the first running instance. – Alexandru Dicu Jan 22 '20 at 11:25