单例应用程序的实现

 一些程序需要单实例运行,这里简单介绍下单实例的实现及相关问题。

首先定义单实例指的是整个操作系统中只运行一个应用程序的实例。当用户运行应用程序时先检查系统是否已经启动该应用程序,若启动则将自动弹出应用程序窗体,否则启动应用程序。OK,这样单实例应用程序的基本功能就出来了。我们分别进行讨论。

一、如何检测系统中是否已经启动该应用程序?

1.使用线程互斥体Mutex

当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。 Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。 如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。详见[MSDN]http://msdn.microsoft.com/zh-cn/library/system.threading.mutex.aspx

        代码如下:

       bool isCreated = false;  //是否创建成功
        //声明一个互斥体
        mutex = new Mutex(true, "63133E79-F9E7-471B-A0BE-9F3CCE4ADFF7", out isCreated);
       if (!isCreated) //系统存在该应用程序的实例
        {
            Process  process = GetRunningProcessInstance();
            if (process != null)
            {

                Win32API.SetForegroundProcess(process, MainForm.Title);
            }

            Application.Exit();
            return;
        }

 

     2. 使用Process检查是否已经存在应用程序的进程

代码如下:

        /// <summary>
        /// 根据“进程名称和路径”获取已运行的实例
         /// </summary>
        /// <param name=" processName ">应用程序的名称,不包含后缀”exe”</param>
        /// <returns>实例在运行的进程</returns>
        /// <remarks>存在问题:修改了文件名或改变了文件路径,此方法失效。</remarks>
        private static Process GetRunningProcessInstance(string processName)
        {
            Process runningProcess = null;
            string fileName = processName+ ".exe";
            Process[] processes = Process.GetProcessesByName(processName);
            foreach (Process process in processes)
            {
                if (process.MainModule.ModuleName == fileName)
                {
                    runningProcess = process;
                    break;
                }
            }
            return runningProcess;
        }


    若获取到的进程实例不为空则说明应用程序已存在。

 

3. EventWaitHandle或实现WindowsFormsApplicationBase

    EventWaitHandle 类允许线程通过发信号互相通信。通常,一个或多个线程在 EventWaitHandle 上阻止,直到一个未阻止的线程调用Set 方法,以释放一个或多个被阻止的线程。线程可以通过调用 static(在 Visual Basic 中为 Shared)WaitHandle.SignalAndWait 方法,以原子操作的形式向 EventWaitHandle 发出信号,然后在它上面阻止。详见[MSDN] http://msdn.microsoft.com/zh-cn/library/yfk7eby4(v=VS.80).aspx

WindowsFormsApplicationBase提供与当前应用程序相关的属性、方法和事件。WindowsFormsApplicationBase位于Microsoft.VisualBasic.dll中,使用时注意引用该组件。

之所以把这两种方式写一起是因为WindowsFormsApplicationBase判断应用程序是否已运行的方法也是使用的EventWaitHandle。只不过WindowsFormsApplicationBase进行了一些封装,使单实例应用程序更容易实现。对EventWaitHandle和WindowsFormsApplicationBase有疑问的看MSDN,这里不多说了,不喜欢这种方式因为WindowsFormsApplicationBase的代码写的实在太复杂了。不过懒人可以使用。上代码。

调用SingleInstanceManager:

SingleInstanceManager singleInstance = new SingleInstanceManager();
            singleInstance.Run(null);
       //SingleInstanceManager实现:
         public class SingleInstanceManager : WindowsFormsApplicationBase
        {
            public SingleInstanceManager()
            {
                //确定此应用程序是否为单实例应用程序。  
                   this.IsSingleInstance = true;
                //确定应用程序的主窗体关闭时执行的操作。
                   this.ShutdownStyle = ShutdownMode.AfterAllFormsClose;
            } 

            protected override bool OnStartup(StartupEventArgs eventArgs)
            {
                //在程序第一次运行时调用   
                   MainForm form = new MainForm();
                Application.Run(form);
                //运行后返回FLASE,指示应用程序是否应继续启动。 
                   return false;
            } 

            protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
            {
                base.OnStartupNextInstance(eventArgs);
                eventArgs.BringToForeground = true;
            }
        }


注意引用:Microsoft.VisualBasic.dll

二、如何自动弹出应用程序窗体?

对于第3种方法就不说了,稍微修改下代码就可以弹出应用程序窗体了。

无论使用什么方法,要弹出窗体必须获取到已经存在的应用程序的进程,否则一切都是免谈。获取到进程后再通过该进程中找到主窗体的句柄,下面的事情就很简单了。

其实对于方法1只是判断应用程序是否已运行,方法2则可以获取到已运行的应用程序的进程。所以两种方法不管如何判断获取进行都要调用GetRunningProcessInstance()或GetCurrentProcess来获取进程。

最初我的代码是这样的,当然网上有很多代码也都是这样滴,抄一遍吧:

    bool createdNew = true;
    using (Mutex mutex = new Mutex(true, "MyApplicationName", out createdNew))
    {
        if (createdNew)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new FrmMain());
        }
        else
        {
            Process current = Process.GetCurrentProcess();
            foreach (Process process in Process.GetProcessesByName(current.ProcessName))
            {
                if (process.Id != current.Id)
                {
		  //调API
                    SetForegroundWindow(process.MainWindowHandle);
                    break;
                }
            }
        }


           最初还不错,当我再次运行程序时会自动弹出窗体。

           不久问题就出现了。我的应用程序最小化时隐藏窗体,只在任务栏上显示个图标。这时当我最小化窗体后后,无论怎样点击程序他都不弹出窗体了。跟踪代码发现Hide主窗体后,process.MainWindowHandle值为0。完了,忽略这茬了。窗体隐藏后虽然句柄不会消失,但是系统会认为进程已经没有了主窗体,于是把MainWindowHandle置为0。那就只能问Process的Thread要窗体句柄了。于是我使用EnumThreadWindows枚举每个线程的窗体,然后判断该窗体是否为需要的窗体,我通过窗体的Title来判断的,有好的方法请告诉我。OK!问题解决,哈哈!上代码( ⊙ o ⊙ )!

Win32API:

         /// <summary>
        /// 该委托是一个与EnumWindows或EnumDesktopWindows一起使用的应用程序定义的回调函数。它接收顶层窗口句柄。
         /// WNDENUMPROC定义一个指向这个回调函数的指针。EnumWindowsProc是应用程序定义函数名的位置标志符。
         /// </summary>
        /// <param name="hwnd">顶层窗口句柄。</param>
        /// <param name="lParam">指定在EnumWIndowsh或EnumDesktopWindows中的应用程序定义值。</param>
        /// <returns>为继续列表,回调函数必须返回TRUE;若停止列表,它必须返回FALSE</returns>
        public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);

        /// <summary>
        /// 该函数枚举所有与一个线程相关联的非子窗口,办法是先将句柄传送给每一个窗口,随后传送给应用程序定义的回调函数。
         /// EnumThreadWindows函数继续直到所有窗口枚举完为止或回调函数返回FALSE为止。
         /// 要枚举一个特定窗口的所有子窗口,使用EnumChildWindows函数。 
         /// </summary>
        /// <param name="dwThreadId">标识将被列举窗口的线程。</param>
        /// <param name="lpfn">指向一个应用程序定义的回调函数指针,请参看EnumThreadWndProc。</param>
        /// <param name="lParam">指定一个传递给回调函数的应用程序定义值。</param>
        /// <returns></returns>
        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern bool EnumThreadWindows(int dwThreadId, EnumWindowsProc lpfn, IntPtr lParam);

        /// <summary>
        /// 该函数获得给定窗口的可视状态。
         /// </summary>
        /// <param name="hWnd">被测试窗口的句柄</param>
        /// <returns></returns>
        [System.Runtime.InteropServices.DllImport("User32.dll")]
        public static extern bool IsWindowVisible(IntPtr hWnd);

        /// <summary>
        /// 窗口是否已最小化
         /// </summary>
        /// <param name="hWnd">窗口句柄</param>
        /// <returns>非零表示成功,零表示失败</returns>
        [System.Runtime.InteropServices.DllImport("User32.dll")]
        public static extern bool IsIconic(IntPtr hWnd);

        /// <summary>
        /// 将窗口设为系统的前台窗口
         /// </summary>
        /// <param name="hWnd">窗口句柄</param>
        /// <returns>非零表示成功,零表示失败</returns>
        [System.Runtime.InteropServices.DllImport("User32.dll")]
        public static extern int SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        static extern IntPtr SetActiveWindow(IntPtr hWnd);

        #region - 激活进程指定标题的窗口 -

        /// <summary>
        /// 设置进程指定标题的窗口为前台窗口
         /// </summary>
        /// <param name="process">进程</param>
        /// <param name="caption">窗口的标题</param>
        public static void SetForegroundProcess(Process process, string caption)
        {
            IntPtr handler = process.MainWindowHandle;
            if (handler == IntPtr.Zero)
            {
                EunmProcessWindows(process, caption);
            }
            else
            {
                bool isVisible = IsWindowVisible(handler);
                if (isVisible)
                {
                    bool isIcon = IsIconic(handler);
                    // 窗口是否已最小化
                       if (isIcon)
                    {
                        ShowWindow(handler, SWCmd.SW_SHOW);
                        // 将窗口设为前台窗口
                            SetForegroundWindow(handler);
                    }
                    else
                    {
                        // 将窗口设为前台窗口
                            SetForegroundWindow(handler);
                    }
                }
                else
                {
                    // 显示窗口
                       ShowWindowAsync(handler, CmdShow.SW_RESTORE);
                }
            }
        }

        private static void EunmProcessWindows(Process process, string caption)
        {
            //窗体Title
            IntPtr pCaption = Marshal.StringToHGlobalAnsi(caption);
            foreach (ProcessThread thread in process.Threads)
            {
                int tid = thread.Id;
                bool flag = EnumThreadWindows(tid, new EnumWindowsProc(EnumWindowsCallBack), pCaption);
                if (!flag)
                {
                    return;
                }
            }
            Marshal.FreeHGlobal(pCaption);
        }

        //枚举Windows时的回调函数
         private static bool EnumWindowsCallBack(IntPtr handle, IntPtr lparam)
        {
            string caption = Marshal.PtrToStringAnsi(lparam); //窗体标题
              IntPtr pStr = Marshal.AllocHGlobal(512);
            GetWindowText(handle, pStr, 512);
            string str = Marshal.PtrToStringAuto(pStr);
            Marshal.FreeHGlobal(pStr);
            if (string.IsNullOrEmpty(str))
            {
                return true;
            }
            else
            {
                if (str.Equals(caption, StringComparison.OrdinalIgnoreCase))
                {
                    ShowWindow(handle, SWCmd.SW_SHOW);
                    SetActiveWindow(handle);
                    SetForegroundWindow(handle);
                    return false;
                }
                return true;
            }
        }

        #endregion

 

调用:

      static class Program
      {
        private static Mutex mutex;
        const string uniqueName = "63133E79-F9E7-471B-A0BE-9F3CCE4ADFF7";

        /// <summary>
        /// 应用程序的主入口点。
         /// </summary>
        [STAThread]
        static void Main()
        {
           #region 单实例

             bool isCreated = false;  //是否已运行了该程序
             //声明一个互斥体
             using( mutex = new Mutex(true, uniqueName, out isCreated))
           {
             if (!isCreated)
             {
                Process process = GetRunningProcessInstance();
                if (process != null)
                    Win32API.SetForegroundProcess(process, “主窗体标题”);
                Environment.Exit(0);
                return;
             }
            }
            #endregion

             Application.EnableVisualStyles();
             Application.SetCompatibleTextRenderingDefault(false);
             Application.Run(new MainForm());
           }
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值