程序单进程实例

C#窗口实现单例模式的方法,对于一个软件如果第二次打开程序,就把已经启动的那个进程的窗口放到最前端显示

1 . 线程互斥量 - Mutex

System.Threading.Mutex

Mutex 是进程间同步的同步基元
所以我们可以通过在主线程中构建互斥量来判断一个主进程是否启动多次

1
2
3
4
5
6
7
8
9
10
11
protected override void OnStartup (StartupEventArgs e)
{
bool _isRuning = false;
mutex = new Mutex (true, Assembly.GetExecutingAssembly().FullName, out _isRuning);
if (!_isRuning)
{
MessageBox.Show ("程序已经在运行,请勿重复启动!");
Environment.Exit (0);
}
base.OnStartup (e);
}

上面的代码是 WPF 项目,我们是重写App 级视图类的OnStartup方法,来达到启动的时候创建一个名为当前程序集名称的互斥量,如果 _isRuningFalse 的时候,表示这个互斥量名称已经被别的主线程创建了,也就是已经有一个 实例 在运行了,因为主线程的互斥量属于系统级。

2 . 进程判断

我们可以通过判断进程是否存在达到单实例模式的效果,但是缺点就是可以通过修改程序名路径来达到重复启动的目的,不过一般用户情况下还是适用的。

Process current
1
2
3
4
5
6
7
8
9
10
11
foreach (Process process in Process.GetProcessesByName (current.ProcessName))
{
if (process.Id != current.Id)
{
if (process.MainModule.FileName == current.MainModule.FileName)
{
MessageBox.Show ("程序已经在运行,请勿重复启动!");
Environment.Exit (0);
}
}
}

3 . 全局原子

Win32 系统中,为了实现信息共享,系统维护了一张全局原子表,用于保存字符串与之对应的标志符的组合。
我们需要引入 Kernel32.dll

1
2
3
4
5
6
7
8
9
10
11
// 添加原子
[System.Runtime.InteropServices.DllImport ("kernel32.dll")]
public static extern UInt32 GlobalAddAtom (String lpString);

// 查找原子
[System.Runtime.InteropServices.DllImport ("kernel32.dll")]
public static extern UInt32 GlobalFindAtom (String lpString);

// 删除原子
[System.Runtime.InteropServices.DllImport ("kernel32.dll")]
public static extern UInt32 GlobalDeleteAtom (UInt32 nAtom);

然后在主函数入口写上这样类似于这样的判断

1
2
3
4
5
6
7
8
9
10
if (GlobalFindAtom (Assembly.GetExecutingAssembly ().FullName) == 0)
{
GlobalAddAtom (Assembly.GetExecutingAssembly ().FullName);
Run ();
}
else
{
MessageBox.Show ("程序已经在运行,请勿重复启动!");
Environment.Exit (0);
}

因为系统查找全局原子的时候,如果没有找到,会返回一个数值为 0UInt32类型值,所以通过这里就可以判断如果为0,那么我们添加这个原子然后启动应用程序

注意:

全局原子有一个需要注意的地方,就是在退出应用程序的时候需要删除这个原子,要不然系统会一直保留这个原子,导致你的程序无法正常启动

1
GlobalDeleteAtom(GlobalFindAtom(Assembly.GetExecutingAssembly().FullName));

活动已启动的程序窗口置顶


上面介绍了几种 .NET 程序的单实例实现方法,那么可能会有这样一个需求,就是当我们的应用程序已经启动,但是窗口状态处于最小化,或者隐藏在系统托盘的时候,我再次启动程序的时候,除了警告用户程序已启动,还需要把隐藏或者最小化的程序,最大化活动窗口置顶。

引入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private const int SW_HIDE = 0;           //隐藏窗口,活动状态给令一个窗口
private const int SW_SHOWNORMAL = 1; //用原来的大小和位置显示一个窗口,同时令其进入活动状态
private const int SW_SHOWMINIMIZED = 2; //最小化窗口,并将其激活
private const int SW_SHOWMAXIMIZED = 3; //最大化窗口,并将其激活
private const int SW_SHOWNOACTIVATE = 4; //用最近的大小和位置显示一个窗口,同时不改变活动窗口
private const int SW_RESTORE = 9; //用原来的大小和位置显示一个窗口,同时令其进入活动状态
private const int SW_SHOWDEFAULT = 10; //根据默认 创建窗口时的样式 来显示


// 恢复一个最小化的程序,并将其激活
[System.Runtime.InteropServices.DllImport ("User32.dll")]
private static extern bool OpenIcon (IntPtr hWnd);

// 窗口是否已最小化
[System.Runtime.InteropServices.DllImport ("User32.dll")]
private static extern bool IsIconic (IntPtr hWnd);

// 将窗口设为系统的前台窗口
[System.Runtime.InteropServices.DllImport ("User32.dll")]
private static extern int SetForegroundWindow (IntPtr hWnd);

// 异步ShowWindow
[System.Runtime.InteropServices.DllImport ("User32.dll")]
private static extern bool ShowWindowAsync (IntPtr hWnd, int cmdShow);
实现方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//获得当前实例的进程
public static Process GetRunning ()
{
Process current = Process.GetCurrentProcess ();
Process[] processes = Process.GetProcessesByName (current.ProcessName);
foreach (Process process in processes) {
if (process.Id != current.Id)
{
if (Assembly.GetExecutingAssembly ().Location.Replace ("/", "\\") == current.MainModule.FileName)
{
return process;
}
}
}
return null;
}

//调用Win32方法实现恢复活动窗口
public static void HandleShowRunning (Process instance)
{
ShowWindowAsync (instance.MainWindowHandle, SW_SHOWNORMAL);
SetForegroundWindow (instance.MainWindowHandle);
}
调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static Mutex mutex = null;
protected override void OnStartup (StartupEventArgs e)
{
Process instance = GetRunning ();
bool _isRuning = false;
mutex = new Mutex (true, Assembly.GetExecutingAssembly ().FullName, out _isRuning);
if (!_isRuning)
{
HandleShowRunning (instance);
MessageBox.Show ("程序已经在运行,请勿重复启动!");
Environment.Exit (0);
}
base.OnStartup (e);
}

这里我们依然以WPF为例,调用了Win32的一些方法,实现在启动前,先检测是否已经有运行的实例,如果有获得该进程Handle然后传给 Win32 方法实现激活窗口

评论