Reputation: 53
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
Upvotes: 0
Views: 83
Reputation: 916
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:
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.
Upvotes: 0
Reputation: 9438
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
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);
}
}
}
}
Upvotes: 0