Overview
This post shows how to create a single instance WinForm C# application using a Mutex. It also demonstrates how to send any object between instances with Named Pipes.
While writing OSD Background I wanted an application that would only allow a single instance to run at any given time. In addition I wanted it so that if another instance was executed with different parameters – or a quit request – then those parameters would be sent to the original instance so that it could process them. After sending the parameters the second instance would terminate.
The final solution I came up with uses a Mutex to handle the single instancing. The information I wanted to send between instances is wrapped into a ‘payload’ object that is serialized into XML and sent over a Named Pipe.
Downloads
A fully working example can be downloaded from GitHub at https://github.com/AutoItConsulting/examples-csharp/tree/master/MutexSingleInstanceAndNamedPipe
On first run the application shows a simple WinForm application with an empty text box. When another attempt is made to run the application the command-line parameters are sent via a Named Pipe to the first instance and shown in the text box.
Single Instance Using a Mutex
Mutex is a simple object, you just pick a unique instance name and create a new object. A bool is returned that allows you to test if this mutex is owned by just the current process or if it is already in use. As long as the mutex is referenced other processes that request a new Mutex object will be able to see that another instance is running. In the code below I request the Mutex in the form Load event, and release it in the form Close event.
Here are the relevant functions:
public partial class FormMain : Form
{
private const string MutexName = "MUTEX_SINGLEINSTANCEANDNAMEDPIPE";
private bool _firstApplicationInstance;
private Mutex _mutexApplication;
private bool IsApplicationFirstInstance()
{
// Allow for multiple runs but only try and get the mutex once
if (_mutexApplication == null)
{
_mutexApplication = new Mutex(true, MutexName, out _firstApplicationInstance);
}
return _firstApplicationInstance;
}
private void FormMain_Load(object sender, EventArgs e)
{
// First instance
if (IsApplicationFirstInstance())
{
// Yes
// Do something
}
else
{
// Close down
Close();
}
}
private void FormMain_FormClosed(object sender, FormClosedEventArgs e)
{
// Close and dispose our mutex.
if (_mutexApplication != null)
{
_mutexApplication.Dispose();
}
}
}
Communication Payload
The ‘payload’ is the data I wanted to send between instances. In this example I used a custom object that has a copy of the command-line parameters used in the form of a List<string>. I am using the XmlSerializer so I’ve decorated the class with XML attributes. The BinaryFormatter (or any other serializer) could also be used in the same way. I chose XmlSerializer because in my OSD Background application I use it to send a copy of an XML options file.
public class NamedPipeXmlPayload
{
/// <summary>
/// A list of command line arguments.
/// </summary>
[XmlElement("CommandLineArguments")]
public List<string> CommandLineArguments { get; set; } = new List<string>();
}
Named Pipe Server
We create a single Named Pipe server which does the following.
- Creates a new Named Pipe server and asynchronously waits for a client to connect ( NamedPipeCreateServer() )
- On client connection reads payload sent from the client ( NamedPipeServerConnectionCallback() )
- De-serializes the payload into our custom NamedPipeXmlPayload object ( NamedPipeServerConnectionCallback() )
- Create a new Named Pipe server and repeats ( NamedPipeCreateServer() )
By default a Named Pipe can be accessed from the network as well as local machine. I don’t want that so I explicitly create a Named Pipe that denies network access.
The code for all the relevant functions is below:
public partial class FormMain : Form
{
private const string PipeName = "PIPE_SINGLEINSTANCEANDNAMEDPIPE";
private readonly object _namedPiperServerThreadLock = new object();
private NamedPipeServerStream _namedPipeServerStream;
private NamedPipeXmlPayload _namedPipeXmlPayload;
private void FormMain_FormClosed(object sender, FormClosedEventArgs e)
{
// Dispose the named pipe steam
if (_namedPipeServerStream != null)
{
_namedPipeServerStream.Dispose();
}
}
private void FormMain_Load(object sender, EventArgs e)
{
// If are the first instance then we start the named pipe server listening and allow the form to load
if (IsApplicationFirstInstance())
{
// Create a new pipe - it will return immediately and async wait for connections
NamedPipeServerCreateServer();
}
}
/// <summary>
/// Starts a new pipe server if one isn't already active.
/// </summary>
private void NamedPipeServerCreateServer()
{
// Create a new pipe accessible by local authenticated users, disallow network
var sidNetworkService = new SecurityIdentifier(WellKnownSidType.NetworkServiceSid, null);
var sidWorld = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
var pipeSecurity = new PipeSecurity();
// Deny network access to the pipe
var accessRule = new PipeAccessRule(sidNetworkService, PipeAccessRights.ReadWrite, AccessControlType.Deny);
pipeSecurity.AddAccessRule(accessRule);
// Alow Everyone to read/write
accessRule = new PipeAccessRule(sidWorld, PipeAccessRights.ReadWrite, AccessControlType.Allow);
pipeSecurity.AddAccessRule(accessRule);
// Current user is the owner
SecurityIdentifier sidOwner = WindowsIdentity.GetCurrent().Owner;
if (sidOwner != null)
{
accessRule = new PipeAccessRule(sidOwner, PipeAccessRights.FullControl, AccessControlType.Allow);
pipeSecurity.AddAccessRule(accessRule);
}
// Create pipe and start the async connection wait
_namedPipeServerStream = new NamedPipeServerStream(
PipeName,
PipeDirection.In,
1,
PipeTransmissionMode.Byte,
PipeOptions.Asynchronous,
0,
0,
pipeSecurity);
// Begin async wait for connections
_namedPipeServerStream.BeginWaitForConnection(NamedPipeServerConnectionCallback, _namedPipeServerStream);
}
/// <summary>
/// The function called when a client connects to the named pipe. Note: This method is called on a non-UI thread.
/// </summary>
/// <param name="iAsyncResult"></param>
private void NamedPipeServerConnectionCallback(IAsyncResult iAsyncResult)
{
try
{
// End waiting for the connection
_namedPipeServerStream.EndWaitForConnection(iAsyncResult);
// Read data and prevent access to _namedPipeXmlPayload during threaded operations
lock (_namedPiperServerThreadLock)
{
// Read data from client
var xmlSerializer = new XmlSerializer(typeof(NamedPipeXmlPayload));
_namedPipeXmlPayload = (NamedPipeXmlPayload)xmlSerializer.Deserialize(_namedPipeServerStream);
// _namedPipeXmlPayload contains the data sent from the other instance
// As an example output it to the textbox
// In more complicated cases would need to do some processing here and possibly pass to UI thread
TextBoxAppend(_namedPipeXmlPayload);
}
}
catch (ObjectDisposedException)
{
// EndWaitForConnection will exception when someone closes the pipe before connection made
// In that case we dont create any more pipes and just return
// This will happen when app is closing and our pipe is closed/disposed
return;
}
catch (Exception)
{
// ignored
}
finally
{
// Close the original pipe (we will create a new one each time)
_namedPipeServerStream.Dispose();
}
// Create a new pipe for next connection
NamedPipeServerCreateServer();
}
/// <summary>
/// Appends string version of the payload to the end of the text box. Handles being called from a non UI thread.
/// </summary>
private void TextBoxAppend(NamedPipeXmlPayload namedPipeXmlPayload)
{
if (textBoxOutput.InvokeRequired)
{
textBoxOutput.Invoke((MethodInvoker)delegate { TextBoxAppend(namedPipeXmlPayload); });
return;
}
foreach (string commandLine in namedPipeXmlPayload.CommandLineArguments)
{
message += "\r\nCommandLine: " + commandLine;
}
textBoxOutput.Text += message + "\r\n\r\n";
}
}
Named Pipe Client
The code for the Named Pipe client operations are much more simple:
- Check if first instance of application, if not prepare client message ( FormMain_Load() )
- Add command-line parameters to our custom payload object ( FormMain_Load() )
- Connect to the Named Pipe and send the payload ( NamedPipeClientSendOptions() )
- Close application
The code is below:
public partial class FormMain : Form
{
private const string PipeName = "PIPE_SINGLEINSTANCEANDNAMEDPIPE";
private NamedPipeServerStream _namedPipeServerStream;
private NamedPipeXmlPayload _namedPipeXmlPayload;
private void FormMain_Load(object sender, EventArgs e)
{
// If are the first instance then we start the named pipe server listening and allow the form to load
if (IsApplicationFirstInstance())
{
// Create a new pipe - it will return immediately and async wait for connections
NamedPipeServerCreateServer();
}
else
{
// We are not the first instance, send the named pipe message with our payload and stop loading
var namedPipeXmlPayload = new NamedPipeXmlPayload
{
CommandLineArguments = Environment.GetCommandLineArgs().ToList()
};
// Send the message
NamedPipeClientSendOptions(namedPipeXmlPayload);
// Stop loading form and quit
Close();
}
}
/// <summary>
/// Uses a named pipe to send the currently parsed options to an already running instance.
/// </summary>
/// <param name="namedPipePayload"></param>
private void NamedPipeClientSendOptions(NamedPipeXmlPayload namedPipePayload)
{
try
{
using (var namedPipeClientStream = new NamedPipeClientStream(".", PipeName, PipeDirection.Out))
{
namedPipeClientStream.Connect(3000); // Maximum wait 3 seconds
var xmlSerializer = new XmlSerializer(typeof(NamedPipeXmlPayload));
xmlSerializer.Serialize(namedPipeClientStream, namedPipePayload);
}
}
catch (Exception)
{
// Error connecting or sending
}
}
}
Downloads
A fully working example can be downloaded from GitHub at https://github.com/AutoItConsulting/examples-csharp/tree/master/MutexSingleInstanceAndNamedPipe
On first run the application shows a simple WinForm application with an empty text box. When another attempt is made to run the application the command-line parameters are sent via a Named Pipe to the first instance and shown in the text box.