Introduction
This article addresses three issues:
- Creating a single instance application.
- Restoring the previous instance, if user tries to launch another instance.
- Minimizing (with animation) the application to the Notification Area of the task bar when the window is closed.
How to Create a Single Instance Application?
Often, it becomes necessary to ensure that only one instance of a program is running at any time. If the user tries to run another instance, either the user is to be notified that an instance is already running or the previous instance has to be activated and brought to the foreground. In the case of a Windows application we want to restore the existing app�s main window. So when an application is started, it checks to see if there is another instance already running. If there is one, the current instance quits and the previous instance�s main window is activated and displayed to the user.
Making an application single instance, can be achieved by using a mutex (Mutual Exclusion Semaphore). A Windows application loads the main form through the Application.Run( )
method. In the Main
method, create a new mutex. If a new mutex is created the application is allowed to run. If the mutex has already been created, the application cannot start. This will ensure that only one instance will be running at any time.
bool newMutexCreated = false;
string mutexName = "Local\\" +
System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
Mutex mutex = null;
try
{
// Create a new mutex object with a unique name
mutex = new Mutex(false, mutexName, out newMutexCreated);
}
catch(Exception ex)
{
MessageBox.Show (ex.Message+"\n\n"+ex.StackTrace+
"\n\n"+"Application Exiting...","Exception thrown");
Application.Exit ();
}
// When the mutex is created for the first time
// we run the program since it is the first instance.
if(newMutexCreated)
{
Application.Run(new AnimatedWindowForm());
}
When a new mutex is created the mutex name can be prefixed with either Global\ or Local\. Prefixing with Global\ means the mutex is effective in the global namespace.
Prefixing with Local\ means the mutex is effective in the user's session namespace only.
Windows XP and Windows 2003 allow fast user switching through Terminal Services Sessions. So if a mutex is created with a Global\ prefix, the application can have only one instance system wide. So if one user launches the application, other users cannot create a second instance in their sessions. If the mutex is not prefixed with Local\ it is effective per session only.
To know more about Kernel Object namespaces, read this MSDN article.
Now, there is one more task left, i.e. to bring up the previously running instance to the foreground. In a Windows application this means restoring the main window of the application to the top and showing it to the user if it were hidden.
Restoring the Previous Instance
In order to restore the main window, the app�s main window handle is needed. Getting the process� MainWindowHandle
was easy by using the following piece of code:
Process[] currentProcesses =
Process.GetProcessesByName("SingleInstanceApplication");
System.IntPtr mainWindowHandle = currentProcesses[0].MainWindowHandle;
if(mainWindowHandle != IntPtr.Zero)
{
ShowWindow(mainWindowHandle,SW_RESTORE);
UpdateWindow(mainWindowHandle);
}
But it failed to show the window because when the app�s main window is hidden, the handle returned is zero.
A reliable mechanism to get the MainWindowHandle
is needed. This is where shared memory comes into play. Shared memory is a method of IPC (Inter Process Communication) in which two or more processes communicate using a shared segment of memory. Creating a shared memory in C# can be accomplished through Win32 API calls. Memory mapping associates the contents of a file to a specific range of addresses within your process' address space or the system page file or specified address in system memory.
To share data between two processes, the shared memory is created in a system page file.
In order for one process to share data with other processes via memory mapped files (MMF), each process must have access to the file. This is achieved by giving the MMF object a name that each of the processes accessing the shared memory can use.
private const int INVALID_HANDLE_VALUE = -1;
private const int FILE_MAP_WRITE = 0x2;
private const int FILE_MAP_READ = 0x0004;
[DllImport("kernel32.dll",EntryPoint="OpenFileMapping",
SetLastError=true, CharSet=CharSet.Auto) ]
private static extern IntPtr OpenFileMapping (int
wDesiredAccess, bool bInheritHandle,String lpName );
[DllImport("Kernel32.dll",EntryPoint="CreateFileMapping",
SetLastError=true,CharSet=CharSet.Auto)]
private static extern IntPtr CreateFileMapping(int hFile,
IntPtr lpAttributes, uint flProtect,
uint dwMaximumSizeHigh, uint dwMaximumSizeLow,
string lpName);
[DllImport("Kernel32.dll")]
private static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject,
uint dwDesiredAccess, uint dwFileOffsetHigh,
uint dwFileOffsetLow, uint dwNumberOfBytesToMap);
[DllImport("Kernel32.dll",EntryPoint="UnmapViewOfFile",
SetLastError=true,CharSet=CharSet.Auto)]
private static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("kernel32.dll",EntryPoint="CloseHandle",
SetLastError=true,CharSet=CharSet.Auto)]
private static extern bool CloseHandle(uint hHandle);
[DllImport("kernel32.dll",EntryPoint="GetLastError",
SetLastError=true,CharSet=CharSet.Auto)]
private static extern uint GetLastError();
private IntPtr memoryFileHandle;
public enum FileAccess : int
{
ReadOnly = 2,
ReadWrite = 4
}
Create a new MMF for the shared memory object, using the CreateFileMapping()
function. When a new MMF object is created, a portion of the system page file gets reserved for it.
Parameters
hFile
- Handle of the file that is to be memory mapped. When the MMF is created in the system page file, this value should be 0xFFFFFFFF (-1).
lpAttributes
- A pointer to a SECURITY_ATTRIBUTES
structure.
flProtect
- The type of protection given to the memory mapped file.
PAGE_READONLY
� Read only access.
PAGE_READWRITE
� Read/write access.
PAGE_WRITECOPY
- Copy-on-write access.
PAGE_EXECUTE_READ
� Read and execute access.
PAGE_EXECUTE_READWRITE
- Read, write and execute access.
dwMaximumSizeHigh
- A high-order DWORD
for the maximum size of a file mapping object.
dwMaximumSizeLow
- A low-order DWORD
for the maximum size of a file mapping object.
lpName
� Name of the file mapping object.
public static MemoryMappedFile CreateMMF(string
fileName, FileAccess access, int size)
{
if(size < 0)
throw new ArgumentException("The size parameter" +
" should be a number greater than Zero.");
IntPtr memoryFileHandle = CreateFileMapping (0xFFFFFFFF,
IntPtr.Zero,(uint)access,0,(uint)size,fileName);
if(memoryFileHandle == IntPtr.Zero)
throw new SharedMemoryException("Creating Shared Memory failed.");
return new MemoryMappedFile(memoryFileHandle);
}
Before we launch the first instance of the application, create the MMF object.
if(newMutexCreated)
{
lock(typeof(AnimatedWindowForm))
{
sharedMemory = MemoryMappedFile.CreateMMF("Local\\" +
"sharedMemoryAnimatedWindow",
MemoryMappedFile.FileAccess .ReadWrite, 8);
}
Application.Run(new AnimatedWindowForm());
}
Once the handle to the memory-mapped file is obtained, it can be used to map views of the file to the address space of a calling process. Views can be mapped and unmapped at will as long as the MMF object is alive. The MapViewOfFile()
and UnmapViewOfFile()
are used to map and un-map views. We can perform read/write operations to the mapped views according to the access type specified in the MapViewOfFile()
function call.
Parameters of MapViewOfFile()
:
hFileMappingObject
- Handle of an MMF object. The CreateFileMapping
and OpenFileMapping
functions return this handle.
dwDesiredAccess
- The type of access to the MMF object. This parameter can be one of the following values:
FILE_MAP_READ
- Read-only access. The MMF object must have PAGE_READWRITE
or PAGE_READONLY
access.
FILE_MAP_WRITE
- Read/write access. The MMF object must have PAGE_READWRITE
access.
FILE_MAP_COPY
- Copy-on-write access. The MMF object must have PAGE_WRITECOPY
access.
FILE_MAP_EXECUTE
- Execute access. The MMF object must have PAGE_EXECUTE_READWRITE
or PAGE_EXECUTE_READ
access.
dwFileOffsetHigh
- A high-order DWORD
of the file offset where the mapped view begins.
dwFileOffsetLow
- A low-order DWORD
of the file offset where the mapped view begins.
dwNumberOfBytesToMap
- The number of bytes of a file mapping to map to the view.
After a view of the memory mapped file has been created, the view can be unmapped at any time by calling the UnmapViewOfFile ()
function. The only parameter required is the handle of the mapped view.
UnmapViewOfFile(mappedViewHandle);
In-order to write to the shared memory, first create a mapped view of the MMF object with FILE_MAP_WRITE
access. Because we are writing the handle to the main window, use the Marshal.WriteIntPtr()
method to write to the shared memory. Once the write operation is done, un-map the view, and last of all, release the mapped view by calling the function CloseHandle()
.
public void WriteHandle(IntPtr windowHandle)
{
IntPtr mappedViewHandle = MapViewOfFile(memoryFileHandle,
(uint)FILE_MAP_WRITE,0,0,8);
if(mappedViewHandle == IntPtr.Zero)
throw new SharedMemoryException("Creating" +
" a view of Shared Memory failed.");
Marshal.WriteIntPtr(mappedViewHandle,windowHandle );
UnmapViewOfFile(mappedViewHandle);
CloseHandle((uint)mappedViewHandle);
}
To read from the shared memory, create a mapped view of the MMF object with FILE_MAP_READ
access. Use the Marshal.ReadIntPtr()
method to read from the shared memory. Once the read operation is done, un-map the view and release the mapped view by calling the function CloseHandle()
.
public static IntPtr ReadHandle(string fileName)
{
IntPtr mappedFileHandle =
OpenFileMapping((int)FileAccess.ReadWrite, false, fileName);
if(mappedFileHandle == IntPtr.Zero)
throw new SharedMemoryException("Opening the" +
" Shared Memory for Read failed.");
IntPtr mappedViewHandle = MapViewOfFile(mappedFileHandle,
(uint)FILE_MAP_READ,0,0,8);
if(mappedViewHandle == IntPtr.Zero)
throw new SharedMemoryException("Creating" +
" a view of Shared Memory failed.");
IntPtr windowHandle = Marshal.ReadIntPtr(mappedViewHandle);
if(windowHandle == IntPtr.Zero)
throw new ArgumentException ("Reading from the specified" +
" address in Shared Memory failed.");
UnmapViewOfFile(mappedViewHandle);
CloseHandle((uint)mappedFileHandle);
return windowHandle;
}
Once the application�s main window handle is created, we write it to the shared memory.
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated (e);
IntPtr mainWindowHandle = this.Handle;
try
{
lock(this)
{
sharedMemory.WriteHandle (mainWindowHandle);
}
}
catch(Exception ex)
{
MessageBox.Show (ex.Message+ "\n\n"+ex.StackTrace+
"\n\n"+ "Application Exiting...","Exception thrown");
Application.Exit();
}
}
When the user tries to launch a second instance of the app, the window handle of the previous instance is retrieved from the shared memory and the main window is restored using ShowWindow()
and UpdateWindow()
.
try
{
IntPtr mainWindowHandle = System.IntPtr.Zero;
lock(typeof(AnimatedWindowForm))
{
mainWindowHandle = MemoryMappedFile.ReadHandle("Local" +
"\\sharedMemoryAnimatedWindow");
}
if(mainWindowHandle != IntPtr.Zero)
{
ShowWindow(mainWindowHandle,SW_RESTORE);
UpdateWindow(mainWindowHandle);
}
}
catch(Exception ex)
{
MessageBox.Show (ex.Message+ "\n\n"+ex.StackTrace+
"\n\n"+"Application Exiting...","Exception thrown");
}
So our application�s Main
method looks like this:
static void Main()
{
bool newMutexCreated = false;
string mutexName = "Local\\" +
System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
Mutex mutex = null;
try
{
// Create a new mutex object with a unique name
mutex = new Mutex(false, mutexName, out newMutexCreated);
}
catch(Exception ex)
{
MessageBox.Show (ex.Message+"\n\n"+ex.StackTrace+
"\n\n"+"Application Exiting...","Exception thrown");
Application.Exit ();
}
// When the mutex is created for the first time
// we run the program since it is the first instance.
if(newMutexCreated)
{
// Create the Shared Memory to store the window
// handle. This memory is shared between processes
lock(typeof(AnimatedWindowForm))
{
sharedMemory = MemoryMappedFile.CreateMMF("Local" +
"\\sharedMemoryAnimatedWindow",
MemoryMappedFile.FileAccess .ReadWrite ,8);
}
Application.Run(new AnimatedWindowForm());
}
else
// If the mutex already exists, no need to launch
// a new instance of the program because
// a previous instance is running .
{
try
{
// Get the Program's main window handle,
// which was previously stored in shared memory.
IntPtr mainWindowHandle = System.IntPtr.Zero;
lock(typeof(AnimatedWindowForm))
{
mainWindowHandle =
MemoryMappedFile.ReadHandle("Local" +
"\\sharedMemoryAnimatedWindow");
}
if(mainWindowHandle != IntPtr.Zero)
{
// Restore the Window
ShowWindow(mainWindowHandle,SW_RESTORE);
UpdateWindow(mainWindowHandle);
}
return;
}
catch(Exception ex)
{
MessageBox.Show (ex.Message+"\n\n"+ex.StackTrace+
"\n\n"+"Application Exiting...","Exception thrown");
}
// Tell the garbage collector to keep the Mutex alive
// until the code execution reaches this point,
// ie. normally when the program is exiting.
GC.KeepAlive(mutex);
// Release the Mutex
try
{
mutex.ReleaseMutex();
}
catch(ApplicationException ex)
{
MessageBox.Show (ex.Message + "\n\n"+ ex.StackTrace,
"Exception thrown");
GC.Collect();
}
}
}
Minimizing the Window to the Notification Area
This involves four tasks:
Our first step is to prevent the window from closing when the user clicks the Close button, override the protected virtual
OnClosing
method, and cancel the Close
event. The window should be hidden, while the app runs in the background. But what happens when the user tries to shutdown the system? The Operating System sends Close message to all the open windows. Our application refuses to close the window, and the system will not shutdown, it will wait until all windows are closed. Therefore we have to override the WndProc
virtual method to handle the WM_QUERYENDSESSION
message.
protected override void OnClosing(CancelEventArgs e)
{
if(systemShutdown == true)
e.Cancel = false;
else
{
e.Cancel = true;
this.AnimateWindow();
this.Visible = false;
}
}
protected override void WndProc(ref Message m)
{
if(m.Msg == WM_QUERYENDSESSION)
systemShutdown = true;
base.WndProc(ref m);
}
Next, we want to display a notify-icon in the notification area of the taskbar. Add a NotifyIcon
control to the main form and set its icon. This icon will be displayed in the notification area of the taskbar. Our next aim is to animate the window towards the notification area. Before doing the animation we want to make sure that the user has not disabled window animations in the system. A user can enable/disable window animation by setting the "MinAnimate
" key under HKeyCurrentUser\Control Panel\Desktop. We check this value and set a boolean according to the user�s preference.
RegistryKey animationKey =
Registry.CurrentUser.OpenSubKey("Control Panel" +
"\\Desktop\\WindowMetrics",true);
object animKeyValue = animationKey.GetValue("MinAnimate");
if(System.Convert.ToInt32 (animKeyValue.ToString()) == 0)
this.AnimationDisabled = true;
else
this.AnimationDisabled = false;
If animation is allowed, we use the DrawAnimatedRects(IntPtr hwnd, int idAni, ref RECT lprcFrom, ref RECT lprcTo)
function to animate the window. This function takes four parameters. hwnd
is the handle to the window that is to be animated. idAni
specifies the type of animation. If you specify IDANI_CAPTION
, the window caption is animated from the position specified by lprcFrom
to the position specified by lprcTo
. Otherwise it draws a wire-frame rectangle and animates it. lprcFrom
and lprcTo
are types of RECT
and stands for the begin and end rectangles of the animation respectively. We use the GetWindowRect(IntPtr hwnd, ref RECT lpRect)
function to get the window's rectangle from its handle. While minimizing, the start position is the RECT
of the window. And the end position is the RECT
of the notification area. So our next task is to find out the handle of the notification area. The class name of the taskbar is Shell_TrayWnd
. The taskbar contains several other child windows. We need the handle of the �notification area� which contains the notification icons. We get this handle by enumerating the child windows of Shell_TrayWnd
. Now we can get the RECT
of the notification area by using the GetWindowRect(IntPtr hwnd, ref RECT lpRect)
function.
private void AnimateWindow()
{
if(!this.AnimationDisabled)
{
RECT animateFrom = new RECT();
GetWindowRect(this.Handle, ref animateFrom);
RECT animateTo = new RECT ();
IntPtr notifyAreaHandle = GetNotificationAreaHandle();
if (notifyAreaHandle != IntPtr.Zero)
{
if ( GetWindowRect(notifyAreaHandle, ref animateTo) == true)
{
DrawAnimatedRects(this.Handle,
IDANI_CAPTION,ref animateFrom,ref animateTo);
}
}
}
}
private IntPtr GetNotificationAreaHandle()
{
IntPtr hwnd = FindWindowEx(IntPtr.Zero,IntPtr.Zero,"Shell_TrayWnd",null);
hwnd = FindWindowEx(hwnd , IntPtr.Zero ,"TrayNotifyWnd",null);
hwnd = FindWindowEx(hwnd , IntPtr.Zero ,"SysPager",null);
if (hwnd != IntPtr.Zero)
hwnd = FindWindowEx(hwnd , IntPtr.Zero ,null,"Notification Area");
return hwnd;
}
One Last Word
Indeed, there is a chance that this method of getting the window handle of the "Notification Area" might fail because the "TrayNotifyWnd
", "SysPager
" and "Notification Area
" are undocumented window class names and can be changed in any upcoming Windows versions.
Known Issues
However, there is a conflict between the Debug version and the Release version of the app. If the Release version starts first and then the user launches the Debug version then both instances will be running. Mutex is not able to prevent the second instance from starting.