0

I need to ensure only 1 instance of the application is running. So I have a Mutex lock code in the Main() method which works fine.

now I have a requirement to pass a command line argument to the application. -> If the application isn't open, it opens it using the parameter sent from command line - Not an issue, i have it coded and works ->If the application is already running I need to pass this parameter to the running instance and let it decide on how to handle it.

Not sure how to handle the second scenario. Any help appreciated ! Thank you

5
  • I have a comprehensive solution published as my article. The solution requires a good amount of code, but it is very universal, easy to use, and well accepted by the community. I'm not sure if it is appropriate to share here... Basically, it is an IPC channel, the same mechanism used to detect already running instance and to pass new command-line parameter to that instance. Commented Feb 25 at 17:13
  • 1
    @SergeyAKryukov It's usually fine to post a link to an article as an answer so long as you disclose your affiliation and provide some concrete example that helps the OP. Commented Feb 25 at 17:55
  • e.CommandLine in this code Commented Feb 25 at 19:52
  • You can't pass an argument to an application via the command line after it is already running. If the application is running then you have to communicate with it via some kind of inter-process communication (IPC) mechanism such as a socket, pipe or shared memory. So the first instance will provide a way for any new instances to communicate with it (e.g. listen on a socket), and the new instance, if it discovers an existing instance, will connect to the first instance and send it a command using IPC. Commented Feb 26 at 19:58
  • @D Stanley — I've just posted my answer. As far as I can see, my approach is the most comprehensive and yet very lean because I use the same mechanism for the instance detection and passing command-line data. Commented Feb 26 at 23:47

2 Answers 2

0

To have your singleton app process new command line arguments, just invoke it exactly the same way (e.g. on the command line or by using ProcessStart) without regard for whether the app is running or not. This way you "handle the second scenario" by capturing the "new" command line arguments before denying the new instance" and then send those new arguments to the running instance via a named pipe.


Here's what I mean: If no instance is running we take the command line args and display them in the title bar so we have some way of observing what's happening. This is the result of the if block having successfully executed in Main().

PS >> .\WinformsSingletonApp.exe orig cmd line args

first instance

private static Mutex? mutex;
private static MainForm? mainForm;
const string MUTEX = "{5F563B29-172F-4385-B813-21062155F22E}-MUTEX";

[STAThread]
static void Main()
{ 
    bool createdNew;
    using (mutex = new Mutex(true, MUTEX, out createdNew))
    {
        if (createdNew)
        {
            try
            {
                ListenForMessages(); // Running instance is now listening to pipe.
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                int N = 0;
                var displayArgs = string.Join(
                    " ", 
                    Environment.GetCommandLineArgs().Skip(1).Select(_ => $"[{N++}] {_}"));

                mainForm = new MainForm
                {
                    Text = $"Main Form {displayArgs}"
                };
                Application.Run(mainForm);
            }
            finally
            {
                mutex.ReleaseMutex();
            }
        }
        else
        {
            var args = Environment.GetCommandLineArgs();
            args[0] = Path.GetFileName(args[0]);
            SendMessageToPipe(args);
        }
    }
}

private static async void ListenForMessages()
{
    await Task.Run(() =>
    {
        while (true)
        {
            using (NamedPipeServerStream pipeServer = new NamedPipeServerStream(PIPE, PipeDirection.In))
            {
                pipeServer.WaitForConnection(); 
                using (StreamReader reader = new StreamReader(pipeServer))
                {
                    if(reader.ReadToEnd() is { } json)
                    {
                        mainForm?.OnPipeMessage(json);
                    }
                }
            }
        }
    });
}

Subsequent command line invocations

PS .\WinformsSingletonApp.exe new args orig instance

The app is already running, so this obviously doesn't actually start a new instance. However, the "new" command line arguments are transmitted to the running instance via a named pipe. This occurs in the else block in Main().

const string PIPE = "{5F563B29-172F-4385-B813-21062155F22E}-PIPE";
private static void SendMessageToPipe(Array args)
{
    try
    {
        using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", PIPE, PipeDirection.Out))
        {
            pipeClient.Connect(timeout: TimeSpan.FromSeconds(5)); 
            using (StreamWriter writer = new StreamWriter(pipeClient) { AutoFlush = true })
            {
                writer.WriteLine(JsonConvert.SerializeObject(args, Formatting.Indented)); 
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Failed to send arguments to the main instance: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

For demo purposes, when the singleton app receives a message, it pops up a message box showing the arguments.

namespace WinformsSingletonApp
{
    public partial class MainForm : Form
    {
        public MainForm() => InitializeComponent();
        public void OnPipeMessage(string json)
        {
            int N = 0;
            if(JsonConvert.DeserializeObject<JArray>(json) is { } pm)
            {
                var displayArgs = string.Join(Environment.NewLine, pm.Select(_ => $"[{N++}] {_}"));
                BeginInvoke(() => MessageBox.Show(this, $"Main Form {displayArgs}"));
                // This helps pop it up when running from CLI
                BeginInvoke(() => TopMost = true);
                BeginInvoke(() => TopMost = false);
            }
        }
    }
}

new args received

Sign up to request clarification or add additional context in comments.

1 Comment

Clone You can try this either by invoking from the command line, or by using the "Launcher" project in the same repo. The launcher uses ProcessStart to generate new args each time using a timestamp and an autoincrement counter.
0

I suggest you look at my old publication showing a comprehensive solution:

All Three Features of Single-Instance Applications at One Shot, .NET.

This is a mirror of my original article for Code Project. Unfortunately, Code Project is now out of business, the articles are available on a read-only basis only, but the site is not 100% uptime.

The source code was developed specially for Code Project and is fully open and non-commercial, covered be the [Code Project Open License (CPOL))[https://www.codeproject.com/info/cpol10.aspx], which is 100% permissive.

Now the idea of all three features is this:

  1. When an instance is started, it finds out if there was a previously started instance that is already running. In these cases, the second instance terminates itself.
  2. Optionally, but typically, before termination, the second instance transmits its command line to the first instance. In this case, the second instance receives this information and handles it, usually loading files listed in the command line.
  3. Optionally, and also typically, the first instance handles the start of the first instance by showing itself on top of the other application windows on the desktop and focusing on the appropriate element of its UI.

The implementation idea is using the same IPC channel to detect if the first application instance is already there because it is listening for the connections. If not, the UI is created, and the current instance continues normal execution. If the first instance accepts the connection, the channel is used to pass optional command line data, it this is required, and then the first instance is terminated.

The API is extremely easy to use. It is shown in the article.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.