Pulled Apart - Part IV: Talking to myself, using memory mapped files for communication

[Pull Icon]

Note: This is part of a series. You can find the rest of the parts in the series index.

One of the special features of Pull is the ability to handle special protocol handlers for podcasts—for example, if you click the iTunes Podcast link (itpc://) or Zune Podcast (zune://), it should add the podcast to Pull. In Windows, this works by registering an executable with a protocol. When a user clicks such a link, it launches the associated executable and passes the URL as an argument. However, it will launch a new instance of the executable even if another instance is already running.

This could lead to a scenario where two instances are running simultaneously, with the second instance containing the new feed data while the original does not.

The multiple process

To solve this, two problems needed addressing:

  1. Check if an application is already running.
  2. If an instance is running, instruct the existing one to process the new feed.

Solving Problem 1: Check if an Application is Already Running

This is a well-known problem, solved using a mutex. Below is the key logic in the application’s Main method:

string mutexName = Regex.Replace(Application.ExecutablePath, @"\W*", string.Empty);

try
{
    Mutex.OpenExisting(mutexName);
    // If another instance is running, pass arguments to it and exit.
    if (arguments.Length > 0)
    {
        IPMF.SendMessageToServer(mutexName, arguments[0]);
    }
    return;
}
catch (WaitHandleCannotBeOpenedException)
{
    // No existing mutex → new instance allowed (Mr. Burns voice: "Excellently!")
}

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

Once we know an instance is running, we need inter-process communication. Options include:

However, I chose .NET’s MemoryMappedFile—a lightweight, high-performance solution that works entirely in memory (no disk I/O). It’s also uniquely named to avoid conflicts with mutexes (a pitfall I discovered after turning gray!).

How it works:

  1. The new instance writes arguments to the shared MemoryMappedFile.
  2. The existing instance reads and processes them.
  3. The existing instance clears the file after handling.

Implementation via IPMF (Inter-Process Messaging Framework):

// Create a new in-memory file (unique name, 100KB max, read/write access).
serverMemoryMappedFile = MemoryMappedFile.CreateNew(
    instanceName,
    maxSize,
    MemoryMappedFileAccess.ReadWrite,
    MemoryMappedFileOptions.DelayAllocatePages,
    null,
    HandleInheritability.None
);

// Reading (first instance):
using (MemoryMappedViewStream stream = serverMemoryMappedFile.CreateViewStream())
{
    using (BinaryReader reader = new BinaryReader(stream))
    {
        string data = reader.ReadString();
        // Process non-empty data via the bus.
    }
}

// Writing (second instance):
public static void SendMessageToServer(string instanceName, string message)
{
    instanceName += ".memoryMappedFile";
    using (MemoryMappedFile clientMemoryMappedFile = MemoryMappedFile.OpenExisting(
        instanceName,
        MemoryMappedFileRights.Write,
        HandleInheritability.None))
    {
        using (MemoryMappedViewStream stream = clientMemoryMappedFile.CreateViewStream())
        {
            BinaryWriter writer = new BinaryWriter(stream);
            writer.Write(message);
        }
    }
}

image

Final Thoughts

Memory-mapped files are exceptionally versatile—ideal for partial file loading, IPC, or zero-copy data sharing. Their simplicity and performance make them a standout tool in .NET, and well worth mastering. (Wink: Part III covers the "bus" system used here.)