مقدمه:
یکی از مشکلاتی که معمولا برنامه نویسان برنامه های تحت ویندوز با آن روبرو هستند، اجرای فقط یک نمونه از برنامه در زمان اجرا می باشد.
اجرای چند نمونه از برنامه می تواند مشکلاتی را در مورد دسترسی همزمان به اطلاعات و به اشتراک گزاردن منابع و غیره به وجود آورد. حتی ممکن است که کاربر لایسنس اجرای یک نمونه از برنامه را در هر زمان خریده باشد و در این صورت نباید بتواند چندین نمونه از برنامه را همزمان اجرا نماید.
برای انجام این عمل روش های متفاوتی وجود دارد که هر کدام دارای مشکلاتی می باشند. در این مقاله به معرفی یکی از بهترین روش ها می پردازم.
شروع:
در این مقاله ما از کلاسی به نام Mutex استفاده می کنیم که در فضای نامی System.Threading قرار دارد و جزو کلاس هایی می باشد که جهت مدیریت دسترسی Thread ها و حتی Process های مختلف به حافظه استفاده می شود.
قبل از اینکه به نوشتن قطعه کدهای مربوطه بپردازم، یاداوری می کنم که معمولا کلاس Program در برنامه های تحت ویندوز شبیه قطعه کد زیر می باشد.
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Barber());
}
}
اکنون قصد داریم که یک شرط بگذاریم تا اگر یک نمونه از برنامه در حال اجرا است و کاربر برنامه را مجددا اجرا کرد، به کاربر پیامی دهیم که یک نمونه از برنامه در حال اجرا است و از اجرا شدن نمونه دیگری از برنامه جلوگیری کنیم.
قطعه کد کلاس Program را به شکل زیر تغییر می دهیم. قسمت های تغییر یافته به شکل bold شده، قابل مشاهده هستند.
static class Program
{
static Mutex mutex = new Mutex(true, "{39d5e2c9-8791-4f77-a1f4-d0e81f1b1681}");
[STAThread]
static void Main()
{
if (mutex.WaitOne(TimeSpan.Zero, true))
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
mutex.ReleaseMutex();
}
else
{
MessageBox.Show("در حال حاضر یک نمونه از برنامه در حال اجرا می باشد");
}
}
}
اکنون به بررسی قطعه کد بالا می پردازم.
ابتدا یک نمونه شی استاتیک از نوع کلاس Mutex ایجاد نموده ایم و یک نام منحصر به فرد به آن نسبت داده ایم. این نام در حقیقت یک شناسه GUID می باشد.
سپس داخل متد Main که برنامه ما از آنجا اجرا می شود یک شرط گذاشته ایم.
خروجی متد WaitOne از کلاس Mutex یک مقدار منطقی می باشد. در صورتی که برای اولین بار این متد فراخوانی شود، مقدار بازگشتی آن true می باشد و تا زمانی که متد ReleaseMutex فراخوانی نشده باشد، خروجی تابع WaitOne برای سایر فراخوانی ها برابر false خواهد بود.
بنابراین هنگامی که برای اولین بار تابع WaitOne فراخوانی می شود مقدار true را بر می گرداند و برنامه اجرا می شود و تا زمانی که برنامه در حال اجرا می باشد، اگر نمونه های دیگری از برنامه را اجرا کنیم، مقدار false را برمی گرداند.
دقت کنید که فراموش نکنید که حتما Mutex را پس از خروج از برنامه ReleaseMutex کنید. همانطور که ما در بالا این عمل را انجام داده ایم.
برای درک بهتر، برنامه بالا را اجرا کنید و عملکرد آن را ملاحظه نمایید.
فعال نمودن نسخه ی در حال اجرای برنامه :
با اعمالی که تاکنون انجام دادیم، می توانیم به کاربر اعلام کنیم که در حال حاضر یک نمونه از برنامه در حال اجرا می باشد. ولی حالت ایده آل تری نیز وجود دارد.
بهتر است هنگامی که کاربر برای بار چندم قصد اجرای برنامه را دارد، نسخه ای از برنامه ای که در حال حاضر در حال اجرا می باشد را به صورت TopMost به کاربر نمایش دهیم زیرا ممکن است که برنامه در حالت Minimize بوده و کاربر آن را مشاهده ننماید.
برای انجام این کار کلاسی به نام NativeMethods می نویسیم که حاوی چند API سیستمی می باشد و از آن ها در برنامه استفاده خواهیم نمود.
internal class NativeMethods
{
public const int HWND_BROADCAST = 0xffff;
public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
[DllImport("user32")]
public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
[DllImport("user32")]
public static extern int RegisterWindowMessage(string message);
}
تذکر :
با توجه به اینکه این API ها به صورت ثابت مورد استفاده قرار می گیرند و نیاز به تغییری در آن ها نیست، از تشریح عملکرد آن ها در برنامه خودداری می کنم.
اکنون کلاس Program را به شکل زیر تغییر می دهیم. قسمت های تغییر نموده به صورت bold مشخص شده اند.
static class Program
{
static Mutex mutex = new Mutex(true, "{39d5e2c9-8791-4f77-a1f4-d0e81f1b1681}");
[STAThread]
static void Main()
{
if (mutex.WaitOne(TimeSpan.Zero, true))
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
mutex.ReleaseMutex();
}
else
{
NativeMethods.PostMessage((IntPtr)NativeMethods.HWND_BROADCAST,
NativeMethods.WM_SHOWME,
IntPtr.Zero,
IntPtr.Zero);
}
}
}
خوب تنها یک کار دیگر باقی مانده است.
اکنون باید به فرم اصلی برنامه خود مراجعه نمایید (در اینجا Form1 می باشد) و دو متد زیر را به آن اضافه نمایید.
protected override void WndProc(ref Message m)
{
if (m.Msg == NativeMethods.WM_SHOWME)
{
ShowMe();
}
base.WndProc(ref m);
}
private void ShowMe()
{
if (WindowState == FormWindowState.Minimized)
{
WindowState = FormWindowState.Normal;
}
// get our current "TopMost" value (ours will always be false though)
bool top = TopMost;
// make our form jump to the top of everything
TopMost = true;
// set it back to whatever it was
TopMost = top;
}
کار تمام است. برنامه را اجرا نمایید و سعی کنید چند نمونه از آن را اجرا کنید و نتیجه را ملاحظه نمایید