有的时候我们需要编写出的应用程序在同一时间只允许运行一个实例,运行第二个实例时提示用户“该程序已经运行”。这是很老土的表示方法,但很灵验。
我找到了三种限制程序实例被多次运行的方法,它们有各自的优点,一会儿我会用一个控制台程序做例子,分别讨论这三种方法。有一种比较流行的方式,解决另人
恶心的“该程序已经运行”的对话框,比如千千静听,如果程序被第二次运行,它会直接将已经打开的程序前置,这样做确实温柔许多。我采用第二种方法来实现了
这个效果,就是第二个那个WinForm的例子。
示例1
示例1的效果图
- /*
- * 标题:只允许运行一个实例的程序(互斥程序)示例
- * 原文:http://hi.baidu.com/wingingbob/blog/item/943f5f2396637846925807cf.html
- */
- #define METHOD1 //方法一
- //#define METHOD2 //方法二
- //#define METHOD3 //方法三
- using
System; - using
System.Text; - namespace
ConsoleApplication1 - {
-
class
Program - {
-
static
void
Main(
string
[] args) - {
- Console.WriteLine(
"==============="
); - Console.WriteLine(
"互斥程序示例"
); - Console.WriteLine(
"==============="
); - #if METHOD1 //方法一:同步基元
-
bool
runone; - System.Threading.Mutex run =
new
System.Threading.Mutex(
true
,
"只允许一个实例的程序_Bob"
,
out
runone); -
if
(runone) - {
- run.ReleaseMutex();
- MyMain();
//正常运行 - }
-
else - {
- PrintWarnning();
//输出警告 - }
- #elif METHOD2 //方法二:判断进程
- System.Diagnostics.Process theProcess =
null
; - System.Diagnostics.Process current = System.Diagnostics.Process.GetCurrentProcess();
//当前进程 - System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses();
//所有进程 - var myFilename = System.Reflection.Assembly.GetExecutingAssembly().Location.Replace(
"/"
, @
"/"); //得到当前进程名" -
foreach
(var process
in
processes) - {
-
//查找与当前进程相同名称的进程 -
if
(process.Id != current.Id && process.ProcessName == current.ProcessName) - {
-
//确认相同名称进程的程序运行位置是否一样 -
try - {
-
if
(myFilename == process.MainModule.FileName) - {
- theProcess = process;
//找到了此程序的另一个进程 - }
- }
-
catch
{ } - }
- }
-
if
(theProcess ==
null
) - {
- MyMain();
//正常运行 - }
-
else - {
- PrintWarnning();
//输出警告 - }
- #elif METHOD3 //方法三:全局原子法
-
if
(NativeMethods.GlobalFindAtomW(
"只允许一个实例的程序_Bob"
) == 0)
//没找到原子"只允许一个实例的程序" - {
- var Atom = NativeMethods.GlobalAddAtomW(
"只允许一个实例的程序_Bob"
);
//添加原子"只允许一个实例的程序" - MyMain();
//正常运行 - NativeMethods.GlobalDeleteAtom(Atom);
//一定记得程序退出时删除你的全局原子,否则只有在关机时它才会被清除。 - }
-
else - {
-
//NativeMethods.GlobalDeleteAtom(NativeMethods.GlobalFindAtomW("只允许一个实例的程序_Bob")); - PrintWarnning();
//输出警告 - }
- #endif
- }
-
/// <summary> -
/// 输出警告 -
/// </summary> -
private
static
void
PrintWarnning() - {
- Console.ForegroundColor = ConsoleColor.Red;
- Console.WriteLine(
"警告:已经运行了一个实例了。"
); - Console.ReadLine();
- }
-
/// <summary> -
/// 正常运行 -
/// </summary> -
private
static
void
MyMain() - {
- Console.WriteLine(
"正常运行..."
); - Console.ReadLine();
- }
- #if METHOD3 //方法三:全局原子法
- #region 查找原子
-
public
partial
class
NativeMethods - {
-
/// Return Type: ATOM->WORD->unsigned short -
///lpString: LPCWSTR->WCHAR* - [System.Runtime.InteropServices.DllImportAttribute(
"kernel32.dll"
, EntryPoint =
"GlobalFindAtomW"
)] -
public
static
extern
ushort
GlobalFindAtomW([System.Runtime.InteropServices.InAttribute()]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
string
lpString); -
/// Return Type: ATOM->WORD->unsigned short -
///lpString: LPCSTR->CHAR* - [System.Runtime.InteropServices.DllImportAttribute(
"kernel32.dll"
, EntryPoint =
"GlobalFindAtomA"
)] -
public
static
extern
ushort
GlobalFindAtomA([System.Runtime.InteropServices.InAttribute()]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)]
string
lpString); - }
- #endregion
- #region 添加原子
-
public
partial
class
NativeMethods - {
-
/// Return Type: ATOM->WORD->unsigned short -
///lpString: LPCWSTR->WCHAR* - [System.Runtime.InteropServices.DllImportAttribute(
"kernel32.dll"
, EntryPoint =
"GlobalAddAtomW"
)] -
public
static
extern
ushort
GlobalAddAtomW([System.Runtime.InteropServices.InAttribute()]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
string
lpString); -
/// Return Type: ATOM->WORD->unsigned short -
///lpString: LPCSTR->CHAR* - [System.Runtime.InteropServices.DllImportAttribute(
"kernel32.dll"
, EntryPoint =
"GlobalAddAtomA"
)] -
public
static
extern
ushort
GlobalAddAtomA([System.Runtime.InteropServices.InAttribute()]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)]
string
lpString); - }
- #endregion
- #region 删除原子
-
public
partial
class
NativeMethods - {
-
/// Return Type: ATOM->WORD->unsigned short -
///nAtom: ATOM->WORD->unsigned short - [System.Runtime.InteropServices.DllImportAttribute(
"kernel32.dll"
, EntryPoint =
"GlobalDeleteAtom"
)] -
public
static
extern
ushort
GlobalDeleteAtom(
ushort
nAtom); - }
- #endregion
- #endif
- }
- }
调试说明
看到6、7、8三行的预编译指令了吗?你可以通过注释、取消注释这三行来测试三种不同的方法。这段代码是完整的控制台程序源码,你可以使用csc命令来直接编译。每次将编译出来的程序同时打开数个,看输入结果。
方法一:同步基元
我认为最简单有效的方法。同步基元Mutex在MSDN中的解释是:“当两个或更多线程需要同时访问一个共
享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。Mutex
是同步基元,它只向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥
体。”源程序第26行,当指定的命名系统互斥体已存在时,第三个参数会返回false,代码就会执行到第34条,输出“已经运行了一个实例了”的提示。具
体的方法请参见Mutex的构造函数
。
方法二:判断进程
进程判断是一个不错的方式,因为当你得到了这个进程,就可以对此进程进行一些操作,比如示例2的那种将已运行程序窗口置前的效果。但它最大的不足是,虽然
可以通过进程名(不同程序的进程名也可以相同的)和主程序集中存储的执行文件名信息来做判断,但是如果我的程序是多线程的(比如Chrome浏览器),便
无法正确处理下去。这种情况更适合使用方法一。在测试此方法的时候,需要说明一点的,第43行,得到当前进程名。如果你是在VS环境下调试,那么第一次得
到的进程文件名会是VS宿主的程序文件名,也就是“只允许一个实例的程
序.vshost.exe”。而它并不是我们期待得到的程序文件名“只允许一个实例的程序.exe”。此时再启动新实例,该行代码才会获得“只允
许一个实例的程序.exe”文件名。所以你需要启动第三个调试程序才会在52行判断成功。这并不是代码缺陷,特此说明。您可以直接运行bin目录中生成的
程序进行测试,没有问题的。
方法三:全局原子法
算是非常古老的方法,就是利用Windows给我们提供的全局原子来进行判断。我们在程序启动时,先判断
自己定义的一个全局原子(少于255字符)是否已经存在Windows系统中,如果不存在,当然程序就是第一次运行,注册自己定义的全局原子,正常加载程
序;否则就认为程序已经运行了,输出“已经运行了一个实例了”的提示。重要的是,主程序在退出时一定记得将我们定义的全局原子删除,否则只有在
Windows系统关机时,该全局原子才会被释放。这样一来,如果我们编写的程序被意外中止(比如程序本身的缺陷导致,软件冲突,或者被强制结束进程),
全局原子没被正确释放,那就只好等着机器重启了。因此我不推荐此方法。
此方法使用了三个Windows API函数:查找原子(GlobalFindAtom)、添加原子(GlobalAddAtom)和删除原子(GlobalDeleteAtom),你可以使用P/Invoke工具自己查找,然后帖到代码里。
示例2
示例2的效果图
- using
System;
- using
System.Windows.Forms; - namespace
WindowsFormsApplication1 - {
-
static
class
Program - {
-
/// <summary> -
/// 应用程序的主入口点。 -
/// </summary> - [STAThread]
-
static
void
Main() - {
- var theProcess = GetRunningProcess();
//得到已经运行的进程 -
if
(theProcess !=
null
) - {
-
//应该程序已经运行,将其主窗口置前 - NativeMethods.ShowWindowAsync(theProcess.MainWindowHandle, 1);
- NativeMethods.SetForegroundWindow(theProcess.MainWindowHandle);
- }
-
else - {
-
//正常运行程序 - Application.EnableVisualStyles();
- Application.SetCompatibleTextRenderingDefault(
false
); - Application.Run(
new
Form1()); - }
- }
-
/// <summary> -
/// 得到已经运行的进程 -
/// </summary> -
/// <returns></returns> -
static
System.Diagnostics.Process GetRunningProcess() - {
- System.Diagnostics.Process theProcess =
null
; - System.Diagnostics.Process current = System.Diagnostics.Process.GetCurrentProcess();
//当前进程 - System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses();
//所有进程 - var myFilename = System.Reflection.Assembly.GetExecutingAssembly().Location.Replace(
"/"
, @
"/"); //得到当前进程名" -
foreach
(var process
in
processes) - {
-
//查找与当前进程相同名称的进程 -
if
(process.Id != current.Id && process.ProcessName == current.ProcessName) - {
-
//确认相同名称进程的程序运行位置是否一样 -
try - {
-
if
(myFilename == process.MainModule.FileName) - {
- theProcess = process;
//找到了此程序的另一个进程 - }
- }
-
catch
{ } - }
- }
-
return
theProcess; - }
-
/*PInvoke 代码(见源程序)*/ - }
- }