This tip teaches how to make an application that will only run the main portion once. While that is running, subsequent runs will not start the main app again. Instead, they will pass their command line arguments to the main running app. This is a solid way to handle apps that can run in a single instance, and then be called again to open documents specified on the command line.
Introduction
Sometimes, you might have an application where it doesn't make sense to launch the main application twice, but you may want to run the app again passing additional command line parameters. This way, you can run the app, it will open your program, and then you can run the app again, and pass additional command line information to the already running app. This is useful, for example, when you want to get the existing app to open a new file from the command line. So it bakes out like this:
C:\Foo> MyApp myfile1.txt
C:\Foo> MyApp myfile2.txt myfile3.txt
In this case, MyApp only launches the actual application the first time. The second time, it detects that it's already running, and simply sends the passed in command line to the already running app for processing.
Conceptualizing this Mess
We've got two major aspects to this application in order to provide this functionality.
First, the app needs to detect if it's already running, and it will do different things depending on whether it is already running.
Second, we need a way for two processes to communicate. In this case, the primary app will wait on command line data coming from subsequence launches.
The first aspect is pretty easy. We use a named mutex in order to prevent the main app code from launching twice. Named mutexes can be seen by all running processes for this user.
The second aspect is a little more difficult, but not by much, thankfully. We will be using .NET's named pipes feature to facilitate communication between the main app (the server) and subsequent launches (the clients). A named pipe can also be seen by all running processes for this user.
Coding this Mess
The code we're using is presented here, almost in its entirety. I've only eliminated the using directives here to save some space.
class Program
{
static string _AppName=
Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().GetName().Name);
static void Main(string[] args)
{
var notAlreadyRunning = true;
using (var mutex = new Mutex(true, _AppName + "Singleton", out notAlreadyRunning))
{
if (notAlreadyRunning)
{
Console.WriteLine("Running. Press any key to exit...");
_ProcessCommandLine(args);
var srv = new NamedPipeServerStream(_AppName+"IPC",
PipeDirection.InOut,1,PipeTransmissionMode.Message,PipeOptions.Asynchronous);
srv.BeginWaitForConnection(new AsyncCallback(_ConnectionHandler), srv);
Console.ReadKey();
srv.Close();
}
else
{
var cli = new NamedPipeClientStream(".",_AppName + "IPC",PipeDirection.InOut);
cli.Connect();
var bf = new BinaryFormatter();
bf.Serialize(cli, args);
cli.Close();
}
}
}
static void _ConnectionHandler(IAsyncResult result)
{
var srv = result.AsyncState as NamedPipeServerStream;
srv.EndWaitForConnection(result);
var bf = new BinaryFormatter();
var inargs = bf.Deserialize(srv) as string[];
_ProcessCommandLine(inargs);
srv.Close();
srv = new NamedPipeServerStream(_AppName + "IPC", PipeDirection.InOut,
1, PipeTransmissionMode.Message, PipeOptions.Asynchronous);
srv.BeginWaitForConnection(new AsyncCallback(_ConnectionHandler), srv);
}
static void _ProcessCommandLine(string[] args)
{
Console.Write("Command line received: ");
for(var i = 0;i<args.Length;++i)
{
Console.Write(args[i]);
Console.Write(" ");
}
Console.WriteLine();
}
}
We've got a lot going on here.
Let's start at the top, getting the application name we'll be using to identify our app's mutex and named pipe.
Next. we create a named mutex, and wrap our main application code inside this block. This prevents the app from launching its main portion twice.
If it's not already running, the first thing we do is run any app initialization code and then process any command line arguments. We then start the named-pipe based IPC server which then registers a callback for whenever we get a connection from a subsequent instance of the app. The callback we've registered reads incoming command line data and processes the sent arguments as they come in. Finally, we block until exit, in this case using Console.ReadKey()
, but realistically, you'll want to create your own exit condition. For a windows form app, you'd block by calling Application.Run()
with your main application form, instead of Console.ReadKey()
.
Now, if it is already running, all we do is open the named pipe the main application instance created before, and serialize our command line data, sending it down the pipe, and then we shut down our pipe connection and exit.
In _ConnectionHandler()
we handle each incoming connection, reading any command line args from the pipe, and then we actually restart the named pipe server because for some reason, it likes to close itself once you've ended a connection. I haven't determined if this is by design, or due to some sort of problem, but this workaround seems to be fine.
In the _ProcessCommandLine()
args, we do our argument processing. All we do in the demo is spit it to the console, but realistically you're going to want to do something more substantial, like open a file based on the arguments.
History
- 1st May, 2020 - Initial submission