|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis article addresses three issues:
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 // Used to check if we can create a new mutex
bool newMutexCreated = false;
// The name of the mutex is to be prefixed with Local\ to make
// sure that its is created in the per-session namespace,
// not in the global namespace.
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 InstanceIn order to restore the main window, the app’s main window handle is needed. Getting the process’ Process[] currentProcesses =
Process.GetProcessesByName("SingleInstanceApplication");
System.IntPtr mainWindowHandle = currentProcesses[0].MainWindowHandle;
if(mainWindowHandle != IntPtr.Zero)
{
ShowWindow(mainWindowHandle,SW_RESTORE); // Restore the Window
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 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 Parameters
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. // 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.
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 Parameters of
After a view of the memory mapped file has been created, the view can be unmapped at any time by calling the UnmapViewOfFile(mappedViewHandle);
In-order to write to the shared memory, first create a mapped view of the MMF object with 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 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)
{
//Write the handle to the Shared Memory
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 // 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);
}
}
catch(Exception ex)
{
MessageBox.Show (ex.Message+ "\n\n"+ex.StackTrace+
"\n\n"+"Application Exiting...","Exception thrown");
}
So our application’s static void Main()
{
// Used to check if we can create a new mutex
bool newMutexCreated = false;
// The name of the mutex is to be prefixed with Local\ to make
// sure that its is created in the per-session
// namespace, not in the global namespace.
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 AreaThis involves four tasks: Our first step is to prevent the window from closing when the user clicks the Close button, override the 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)
{
// Once the program recieves WM_QUERYENDSESSION
// message, set the boolean systemShutdown to true.
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 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 private void AnimateWindow()
{
// if the user has not disabled animating windows...
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 WordIndeed, there is a chance that this method of getting the window handle of the "Notification Area" might fail because the " Known IssuesHowever, 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. | ||||||||||||||||||||