[C# 开发技巧]如何防止程序屡次运行

最近发现不少人在论坛中问到如何防止程序被屡次运行的问题的,如: http://social.msdn.microsoft.com/Forums/zh-CN/6398fb10-ecc2-4c03-ab25-d03544f5fcc9, 因此这里就记录下来,但愿给遇到一样问题的朋友有所参考的,同时也是对本身的一个积累。在介绍具体实现代码以前,咱们必须明确解决这个问题的思路是什么的?下面只要分享个人一个思考的这个问题的方式:前端

  1. 当咱们点击一个exe文件时,此时该exe程序将会运行,咱们能够看到该程序的界面,对于计算机而言,就是会在系统上开启一个该程序的进行,这个咱们能够经过任务管理器来查看的(当咱们点击exe以后,程序运行,系统会建立一个与与程序同名的进程)编程

  2. 既然咱们要防止程序运行屡次,也就是说程序只能运行一次,从操做系统的角度来说就是该程序的进程只能是惟一的,分析到这里咱们天然就想到了,要保证该程序进程只有一个,咱们就要判断下该程序进程是否在本身的操做系统上运行了,若是已经运行了一个进程,当咱们下次运行exe的时候,此时不是再开启该程序进程,而是退出,弹出一个提示框告诉用户该程序已经运行,若是操做系统没有运行该程序进程,则运行这个程序c#

  3. 从而这个问题就转换为判断该程序进程的数量问题了,此时咱们就想.NET 有没有提供一个类能够得到该进程名的数量,若是数量大于1则说明该程序已经运行了,小于就代表程序没有运行。若是熟悉.NET类库的人确定知道.NET类库中有一个Process类,该类的意思就是一个进程的抽象。(有些人就会说,我一开始不知道有这个类那怎么办呢?那就是考验你英文了,由于进程的英文就是Process,然而全部编程语言的命名都很通俗易懂,此时就能够用Process在MSDN上搜索,这样你也就发现这个类了)windows

  4. 除了第三点中提出找进程数量的思路外,还有另一种实现思路就是——咱们能不能让运行一个进程的时候,让该进程具备一个变量,该变量是惟一标识该进程,当点击exe文件预建立一个改程序进程时,咱们去判断这个变量是否存在,若是存在就说明这个进程已经运行,从而退出本次的程序,而且提示给用户说该程序已经运行多线程

从上面的分析过程当中能够看出,咱们解决这个问题的思路就是从进程入手,第三点的思路就是直接从进程数量入手,而第四点思路也是从进程入手,只是作了一个变换罢了,让一个变量来惟一标识一个进程,当变量存在时说明该程序进程也运行了。编程语言

2、使用互斥量Mutex

弄懂了主要的实现思路以后,下面看代码实现就彻底不是问题了,使用互斥量的实现就是第四点的思路的体现,咱们用为该程序进程建立一个互斥量Mutex对象变量,当运行该程序时,该程序进程就具备了这个互斥的Mutex变量,若是再次运行该程序时,经过检查该互斥变量是否存在(来替换检测这个进程是否存在),若是存在则说明程序已运行,不然就没运行。这里须要注意的是:从个人多线程同步的文章你们能够知道,Mutex类也能够对线程进行同步,那是否是其余对线程同步的类也能够解决本专题中的问题呢?答案是否认,之因此Mutex类能够解决这个问题,是由于Mutex类除了能够对线程同步,也能够对进程同步。下面就具体看看实现代码吧:ide

using System;
using System.Threading;
using System.Windows.Forms;
namespace OnlyInstanceRunning
{
    static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main()
        {
            #region 方法一:使用互斥量
            bool createNew;
            //  createdNew:
            // 在此方法返回时,若是建立了局部互斥体(即,若是 name 为 null 或空字符串)或指定的命名系统互斥体,则包含布尔值 true;
            // 若是指定的命名系统互斥体已存在,则为false
            using (Mutex mutex = new Mutex(true, Application.ProductName, out createNew))
            {
                if (createNew)
                {
                    Application.EnableVisualStyles();
                    Application.SetCompatibleTextRenderingDefault(false);
                    Application.Run(new Form1());
                }
                // 程序已经运行的状况,则弹出消息提示并终止这次运行
                else
                {
                    MessageBox.Show("应用程序已经在运行中...");
                    System.Threading.Thread.Sleep(1000);
                    //  终止此进程并为基础操做系统提供指定的退出代码。
                    System.Environment.Exit(1);
                }
            }
            #endregion
        }
    }
}

3、直接判断进程是否存在的方式来解决这个问题

3.1 判断该程序进程数量的方式

有了上面的思路分析以后,相信你们看下面代码会以为一目了然,这里就很少解释了,直接看代码:函数

#region 方法二:使用进程名
            Process[] processcollection = Process.GetProcessesByName(Application.CompanyName);
            // 若是该程序进程数量大于,则说明该程序已经运行,则弹出提示信息并提出本次操做,不然就建立该程序
            if (processcollection.Length >= 1)
            {
                MessageBox.Show("应用程序已经在运行中。。");
                Thread.Sleep(1000);
                System.Environment.Exit(1);
            }
            else
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                // 运行该应用程序
                Application.Run(new Form1());
            }
            #endregion

3.2 直接判断程序进程是否存在的方式

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Way3
{
    static class Program
    {
        #region 方法三:使用的Win32函数的声明
        /// <summary>
        ///  设置窗口的显示状态
        ///  Win32 函数定义为:http://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
        /// </summary>
        /// <param name="hWnd">窗口句柄</param>
        /// <param name="cmdShow">指示窗口如何被显示</param>
        /// <returns>若是窗体以前是可见,返回值为非零;若是窗体以前被隐藏,返回值为零</returns>
        [DllImport("User32.dll")]
        private static extern bool ShowWindow(IntPtr hWnd, int cmdShow);
        /// <summary>
        ///  建立指定窗口的线程设置到前台,而且激活该窗口。键盘输入转向该窗口,并为用户改变各类可视的记号。
        ///  系统给建立前台窗口的线程分配的权限稍高于其余线程。
        /// </summary>
        /// <param name="hWnd">将被激活并被调入前台的窗口句柄</param>
        /// <returns>若是窗口设入了前台,返回值为非零;若是窗口未被设入前台,返回值为零</returns>
        [DllImport("User32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);
        // 指示窗口为普通显示
        private const int WS_SHOWNORMAL = 1;
        #endregion
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main()
        {
            #region 方法三:调用Win32 API,并激活运行程序的窗口显示在最前端
            // 这种方式在VS调用的状况不成立的,由于在VS中按F5运行的进程为OnlyInstanceRunning.vshost,从这个进程的命名就能够看出,该进程为OnlyInstanceRunning进程的宿主进程
            // 关于这个进程的更多内容能够查看:http://msdn.microsoft.com/zh-cn/library/ms185331(v=vs.100).aspx
            // 而直接点OnlyInstanceRunning.exe运行的程序进程为OnlyInstanceRunning,
            // 可是咱们能够一些小的修改,即currentProcess.ProcessName.Replace(".vshose","")此时不管如何都为 OnlyInstanceRunning
            // 得到正在运行的程序,若是没有相同的程序,则运行该程序
            Process process = RunningInstance();
            if (process == null)
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
            else
            {
                // 已经运行该程序,显示信息并使程序显示在前端
                MessageBox.Show("应用程序已经在运行中......");
                HandleRunningInstance(process);
            }
            #endregion
        }
        #region 方法三定义的方法
        /// <summary>
        /// 获取正在运行的程序,没有运行的程序则返回null
        /// </summary>
        /// <returns></returns>
        private static Process RunningInstance()
        {
            // 获取当前活动的进程
            Process currentProcess = Process.GetCurrentProcess();
            // 根据当前进程的进程名得到进程集合
            // 若是该程序运行,进程的数量大于1
            Process[] processcollection = Process.GetProcessesByName(currentProcess.ProcessName.Replace(".vshost", ""));
            foreach (Process process in processcollection)
            {
                // 若是进程ID不等于当前运行进程的ID以及运行进程的文件路径等于当前进程的文件路径
                // 则说明同一个该程序已经运行了,此时将返回已经运行的进程
                if (process.Id != currentProcess.Id)
                {
                    if (Assembly.GetExecutingAssembly().Location.Replace("/", "\\") == process.MainModule.FileName)
                    {
                        return process;
                    }
                }
            }
            return null;
        }
        /// <summary>
        /// 显示已运行的程序
        /// </summary>
        /// <param name="instance"></param>
        private static void HandleRunningInstance(Process instance)
        {
            // 显示窗口
            ShowWindow(instance.MainWindowHandle, WS_SHOWNORMAL);
            // 把窗体放在前端
            SetForegroundWindow(instance.MainWindowHandle);
        }
        #endregion
    }
}

3.3 解决3.2实现方式中存在的问题——只能是最小化的窗体显示出来,若是隐藏到托盘中则不能把运行的程序显示出来

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Way4
{
    static class Program
    {
        #region 方法四:使用的Win32函数的声明
        /// <summary>
        /// 找到某个窗口与给出的类别名和窗口名相同学口
        /// 非托管定义为:http://msdn.microsoft.com/en-us/library/windows/desktop/ms633499(v=vs.85).aspx
        /// </summary>
        /// <param name="lpClassName">类别名</param>
        /// <param name="lpWindowName">窗口名</param>
        /// <returns>成功找到返回窗口句柄,不然返回null</returns>
        [DllImport("user32.dll")]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
        /// <summary>
        /// 切换到窗口并把窗口设入前台,相似 SetForegroundWindow方法的功能
        /// </summary>
        /// <param name="hWnd">窗口句柄</param>
        /// <param name="fAltTab">True表明窗口正在经过Alt/Ctrl +Tab被切换</param>
        [DllImport("user32.dll ", SetLastError = true)]
        static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
        ///// <summary>
        /////  设置窗口的显示状态
        /////  Win32 函数定义为:http://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
        ///// </summary>
        ///// <param name="hWnd">窗口句柄</param>
        ///// <param name="cmdShow">指示窗口如何被显示</param>
        ///// <returns>若是窗体以前是可见,返回值为非零;若是窗体以前被隐藏,返回值为零</returns>
        [DllImport("user32.dll", EntryPoint = "ShowWindow", CharSet = CharSet.Auto)]
        public static extern int ShowWindow(IntPtr hwnd, int nCmdShow);
        public const int SW_RESTORE = 9;
        public static IntPtr formhwnd;
        #endregion
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main()
        {
            #region 方法四: 能够是托盘中的隐藏程序显示出来
            // 方法四相对于方法三而言应该能够说是一个改进,
            // 由于方法三只能是最小化的窗体显示出来,若是隐藏到托盘中则不能把运行的程序显示出来
            // 具体问题能够看这个帖子:http://social.msdn.microsoft.com/Forums/zh-CN/6398fb10-ecc2-4c03-ab25-d03544f5fcc9
            Process currentproc = Process.GetCurrentProcess();
            Process[] processcollection = Process.GetProcessesByName(currentproc.ProcessName.Replace(".vshost", string.Empty));
           //  该程序已经运行,
            if (processcollection.Length >= 1)
            {
                foreach (Process process in processcollection)
                {
                    if (process.Id != currentproc.Id)
                    {
                        // 若是进程的句柄为0,即表明没有找到该窗体,即该窗体隐藏的状况时
                        if (process.MainWindowHandle.ToInt32() == 0)
                        {
                            // 得到窗体句柄
                            formhwnd = FindWindow(null, "Form1");
                            // 从新显示该窗体并切换到带入到前台
                            ShowWindow(formhwnd, SW_RESTORE);
                            SwitchToThisWindow(formhwnd, true);
                        }
                        else
                        {
                            // 若是窗体没有隐藏,就直接切换到该窗体并带入到前台
                            // 由于窗体除了隐藏到托盘,还能够最小化
                            SwitchToThisWindow(process.MainWindowHandle, true);
                        }
                    }
                }
            }
            else
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
            #endregion
        }
    }
}

4、程序实现效果

四种实现方式的运行效果都是差很少的,这里就以实现方式一做为演示的,具体实现效果以下图:学习

5、总结

写这个专题主要是看到缘由是看到论坛中有些朋友问了这样的问题,而且本人也回答了,因此就总结下具体的实现代码来帮助遇到一样问题的朋友作一个参考,同时也是对本身一个学习的积累和复习。下面附上程序全部源码:spa

相关文章
相关标签/搜索