One of the special features of Pull is the ability to have deal with special protocol handlers for podcasts, for example if you click the iTunes Podcast link (itpc://) or Zune Podcast (zune://) then it should add the podcast to Pull. The way it works in Windows is you register an executable with a protocol. Then when a user clicks a link with a protocol it launches the associated executable passing the URL as the arguments. It will launch a new instance of the executable even if an instance of that executable is already running.
This means when someone clicks a link, you could end up with a scenario like the following where the program is running two instances and the second instance to launch has the new feed info while the original instance doesn’t.
This is not an ideal scenario and to solve it meant solving two problems:
- Check if an application is already running.
- If there is an application running, tell the existing application to process the new feed.
Solving problem 1: Check if an application is already running
Checking if an application is already running is a well known problem and is solved using a mutex, which I put in the Main method of the application. The key parts of that in the code below are:
- Line 1 – Figure out a unique name for the mutex. It needs to be unique across the whole PC. For my use I use the executable path and strip out any symbols. This allows me to run multiple instances based on different paths.
- Line 5 – OpenExisting tries to open the mutex with the name. If there is no mutex in use then an exception is generated. If it succeeds then you know the application is already running.
- Line 19 – Creating the mutex.
- Line 20 – Wrap everything in a try…finally so that the mutex is always cleaned up.
- Line 40 – Clean up the mutex.
string mutexName = Regex.Replace(Application.ExecutablePath, @"\W*", string.Empty); try { Mutex.OpenExisting(mutexName); // we found that mutex, is any parameters exist pass them on and go on with life. if (arguments.Length > 0) { IPMF.SendMessageToServer(mutexName, arguments[0]); } return; } catch (WaitHandleCannotBeOpenedException) { // no mutex with that name exists already... excellant (in your best mr burns voice please) } Mutex mut = new Mutex(true, mutexName); try { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Database.ConnectionString = "data source=pull.sqllite"; MainForm launchForm = new MainForm(); if (Properties.Settings.Default.FirstRun) { Application.Run(new SettingsForm()); Properties.Settings.Default.FirstRun = false; } launchForm.StartupArguments = arguments; using (IPMF ipmf = new IPMF(mutexName, message => { Bus.GetBus().Broadcast(DataAction.ParseFeed, message); })) { Application.Run(launchForm); } } finally { mut.ReleaseMutex(); }
Solving problem 2: Communication with the existing instance
Now we know if an instance of the application is running or not, we need to solve the second issue which is communication with the existing application. There is a ton of ways to do this
- The first process monitors a special folder and the second process writes to that folder.
- Use named pipes to communicate.
- Write to a special table in the database and have the other process pick up there.
All those are good, but as part of my learning I really wanted to use a new .NET feature called a Memory mapped file. This is a file which exists in memory and can optionally exist on disk too. While in memory, if you know the name you can access it from any process which means it’s not limited to the process which creates it.
Memory mapped files, when in memory only mode, also significantly out perform any other options above and offer a variety of powerful features. Memory mapped files when backed by disk also give powerful features - for example you can take a 10Gb file on disk and load only the part from 2.5Gb to 3.5Gb in memory – this enables you to work only with the part you need. For me though I just need a tiny (< 100Kb) amount of memory and it never needs to be persisted to disk because I am using it for communication between processes so I just used the in memory only option.
The process itself becomes very simple
- The second process writes the arguments to the memory mapped file.
- The first process reads the arguments from the memory mapped file and acts upon it.
- The first process clears the memory mapped file.
To handle this I wrapped everything in a class called IPMF, which stands for inter-process messaging framework, sounds professional doesn’t it . Creating the memory mapped file is very easy using MemoryMappedFile.CreateNew method which takes a name, max size for the file and then various options to control read/write, how to allocate pages etc…
serverMemoryMappedFile = MemoryMappedFile.CreateNew(instanceName, maxSize, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.DelayAllocatePages, null, HandleInheritability.None);
The one thing not documented which cause me to go grey is that the name of the memory mapped file needs to be unique not only between other memory mapped files, but also between mutex’s on the machine as the memory mapped file uses a mutex internally.
Reading is very easy: you first create a view which returns a stream which you can read with a binary reader and parse to a string or any other data format.
using (MemoryMappedViewStream stream = serverMemoryMappedFile.CreateViewStream()) { using (BinaryReader reader = new BinaryReader(stream)) { string data = reader.ReadString();
Finally writing to a memory mapped file is also easy and basically the same as creating & reading.
- Open the memory mapped file using the OpenExisting method.
- Create a view to get the data stream.
- Write to the stream using a BinaryWriter
public static void SendMessageToServer(string instanceName, string messsage) { instanceName += ".memoryMappedFile"; using (MemoryMappedFile clientMemoryMappedFile = MemoryMappedFile.OpenExisting(instanceName, MemoryMappedFileRights.Write, HandleInheritability.None)) { using (MemoryMappedViewStream stream = clientMemoryMappedFile.CreateViewStream()) { BinaryWriter writer = new BinaryWriter(stream); writer.Write(messsage); } } }
So all I do is in the first instance I continually check the data when I read it, if the data is not empty then I use the bus to tell my application to process that new podcast URL.
Final Thoughts
Memory mapped files are exceptionally easy to use and opens a lot of doors for solving problems better, from loading parts of files to inter-process communication and definitely something worth investing time into.