C#使用Mutex实现单例应用程序
不少应用程序有单一实例的需求,也就是同时只能开启一个实例(一般也就是一个进程)。
实现的方式可能有判断进程名字,使用特殊文件等等,但是最靠谱的方式还是使用系统提供的 Mutex 工具。
Mutex是互斥体,命名的互斥体可以跨进程使用,所以可以用以实现程序单一实例这个需求。相关的例子网上应该不少,不过很多给出的例子中并没有注意到一些细节,这里就完整总结下。
Mutex 需要一个名字,这个名字需要唯一,一般的方式是使用一个固定的 GUID 作为名字。
对于 .NET 应用,可以通过 Assembly 上的GuidAttribute来获取。默认情况下建立工程的时候 VS 就会生成一个 GUID 给 Assembly,这样无需自己再生成一个 GUID 来使用。
另外,为了调试方面,最好给 GUID 加一个便于人识别的前缀,一般就是程序的名字。这样使用一些查看系统对象的工具时,可以方便找到这个 Mutex。
一般在程序启动的代码中进行判断,判断的方式是使用 Mutex 上的WaitOne方法。但是有两点需要注意:
- 程序异常退出,WaitOne 会抛出
AbandonedMutexException
异常,需要处理。 - 如果程序使用了
Application.Restart
来重新启动,就需要 WaitOne 等待更长的时间。这是因为Application.Restart
会在程序退出前启动新程序实例,需要等待原程序完全退出释放 Mutex。
简单一点也可以使用Mutex的构造函数来判断
//
// 摘要:
// 使用可指示调用线程是否应具有互斥体的初始所有权以及字符串是否为互斥体的名称的 Boolean 值和当线程返回时可指示调用线程是否已赋予互斥体的初始所有权的
// Boolean 值初始化 System.Threading.Mutex 类的新实例。
//
// 参数:
// initiallyOwned:
// 如果为 true,则给予调用线程已命名的系统互斥体的初始所属权(如果已命名的系统互斥体是通过此调用创建的);否则为 false。
//
// name:
// System.Threading.Mutex 的名称。如果值为 null,则 System.Threading.Mutex 是未命名的。
//
// createdNew:
// 在此方法返回时,如果创建了局部互斥体(即,如果 name 为 null 或空字符串)或指定的命名系统互斥体,则包含布尔值 true;如果指定的命名系统互斥体已存在,则为
// false。此参数未经初始化即被传递。
//
// 异常:。。。。
public Mutex(bool initiallyOwned, string name, out bool createdNew);
createdNew参数为true
则可以正常启动,否则程序已在运行。
有些场景下,如果应用已在运行,用户再启动应用时,需要将已在运行的应用显示给用户。如果应用已经有自己的进程间通讯方式,那就可以直接利用,如果没有,则可以使用 Windows 系统的消息广播。
还有些场景下,需要将程序参数传递给已在运行的应用,也可以使用Windows系统的消息。
综上,将上述功能封装在XMutex类型中,如下,
/// <summary>
/// 互斥体辅助类型
/// </summary>
static class XMutex
{
/// <summary>
/// 拷贝数据结构
/// </summary>
public struct CopyDataStruct
{
public IntPtr dwData; public int cbData; public IntPtr lpData;
}
private static Mutex _mutex;
private static int _showMeMessage; /// <summary>
/// 运行程序,
/// 如果互斥体已经有另一个实例在运行,就展示该实例,否则运行新实例
/// </summary>
/// <param name="run">运行实例的方法</param>
/// <param name="args">入口参数</param>
public static void Run(Action run, string[] args)
{
//取应用程序所在的程序集的Guid,本例中应该是Program程序所在的程序集的Guid
var guidAttr = typeof(Program).Assembly.GetCustomAttribute<GuidAttribute>();
//使用该Guid拼接一个字符串作为互斥体的名称
var key = string.Format("XMutex-{0}", guidAttr.Value);
bool flag;
//尝试创建互斥体
_mutex = new Mutex(true, key, out flag);
//注册消息代码
_showMeMessage = RegisterWindowMessage(key); if (flag)
{
//互斥体创建成功,运行程序
run();
}
else
{
//互斥体已经存在,获取该实例的窗口句柄
IntPtr intPtr = GetRunning();
//发送消息到目标实例
PostMessage(intPtr, _showMeMessage, IntPtr.Zero, IntPtr.Zero);
//如果有入口参数,发送参数消息
if (args.Length > )
{
SendCopyData(intPtr, args);
}
}
} /// <summary>
/// 获取已运行实例的主窗口句柄
/// </summary>
/// <returns></returns>
private static IntPtr GetRunning()
{
//当前进程名称
string procName = Process.GetCurrentProcess().ProcessName;
//和当前进程名称相同的所有进程
Process[] processes = Process.GetProcessesByName(procName);
if (processes.Length > )
{
//取获取到的进程的第一个主窗口句柄
return processes.FirstOrDefault(x => x.MainWindowHandle != IntPtr.Zero)?.MainWindowHandle ?? IntPtr.Zero;
}
return IntPtr.Zero;
} /// <summary>
/// 释放互斥体
/// </summary>
public static void Release()
{
_mutex.ReleaseMutex();
} /// <summary>
/// 拷贝数据消息代码
/// </summary>
public const int WM_COPYDATA = ; #region API [DllImport("user32.dll")]
private static extern bool IsIconic(IntPtr hWnd); [DllImport("user32.dll")]
private static extern bool IsZoomed(IntPtr hWnd); [DllImport("User32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int cmdShow); [DllImport("user32.dll")]
private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", SetLastError = true)]
private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref CopyDataStruct lParam); //[DllImport("user32.dll", EntryPoint = "SendMessage")]
//private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")]
private static extern int RegisterWindowMessage(string message); #endregion public static bool WndProc(Form form, Action<string[]> argsAction, ref Message m)
{
if (m.Msg == _showMeMessage)
{
var hwnd = form.Handle;
if (IsIconic(hwnd))
{//取消最小化
ShowWindowAsync(hwnd, );
}
//else if (IsZoomed(hwnd))
//{//最大化
// ShowWindowAsync(hwnd, 3);
//}
//else
//{//普通大小
// ShowWindowAsync(hwnd, 1);
//} //取消隐藏
if (!form.Visible)
{
form.Show();
} //将窗口移到最顶层
var top = form.TopMost;
form.TopMost = true;
form.TopMost = top; //激活窗口并获取焦点
form.Activate(); m.Result = IntPtr.Zero;
return true;
}
if (m.Msg == WM_COPYDATA)
{
//接收参数消息
CopyDataStruct copyDataStruct = (CopyDataStruct)Marshal.PtrToStructure(m.LParam, typeof(CopyDataStruct));
int num = copyDataStruct.dwData.ToInt32();
if (num == )
{
byte[] array = new byte[copyDataStruct.cbData];
Marshal.Copy(copyDataStruct.lpData, array, , copyDataStruct.cbData);
string @string = Encoding.UTF8.GetString(array);
var args = @string.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).Select(x => x.Substring(, x.Length - )).ToArray(); //处理参数
argsAction?.Invoke(args);
} m.Result = IntPtr.Zero;
return true;
} return false;
}
/// <summary>
/// 发送参数消息
/// </summary>
/// <param name="hWnd"></param>
/// <param name="args"></param>
/// <returns></returns>
private static int SendCopyData(IntPtr hWnd, string[] args)
{
string data = string.Join(" ", args.Select(x => string.Format("'{0}'", x)));
byte[] bytes = Encoding.UTF8.GetBytes(data);
CopyDataStruct copyDataStruct = new CopyDataStruct
{
dwData = (IntPtr),
cbData = bytes.Length,
lpData = Marshal.AllocHGlobal(bytes.Length)
};
Marshal.Copy(bytes, , copyDataStruct.lpData, bytes.Length);
IntPtr intPtr = Marshal.AllocHGlobal(Marshal.SizeOf(copyDataStruct));
Marshal.StructureToPtr(copyDataStruct, intPtr, true);
try
{
return SendMessage(hWnd, WM_COPYDATA, (IntPtr), ref copyDataStruct);
}
finally
{
Marshal.FreeHGlobal(copyDataStruct.lpData);
Marshal.DestroyStructure(intPtr, typeof(CopyDataStruct));
Marshal.FreeHGlobal(intPtr);
}
}
}
程序入口点的启动方法修改为:
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] args)
{
XMutex.Run(() =>
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
//程序结束时释放Mutex
XMutex.Release();
}, args);
}
Form1类型部分代码如下
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
} /// <summary>
/// 处理新参数的方法
/// </summary>
/// <param name="args"></param>
private void LoadArgs(string[] args)
{
MessageBox.Show(string.Join(" ", args));
} /// <summary>
/// windows消息处理
/// </summary>
/// <param name="m"></param>
protected override void WndProc(ref Message m)
{
if (XMutex.WndProc(this, LoadArgs, ref m))
return;
base.WndProc(ref m);
}
}
以上代码是以Winform为例,将代码稍作修改也可以应用在WPF中。
本解决方案中有一个遗留问题,就是GetRunning方法,目前实现方案是通过进程名称找到同名进程,再找到进程的主窗口句柄,这就有问题了,如果存在相同名称的进程,后面的消息发送可能就不会产生预期的效果了,当然也可以对所有同名进程的主窗口句柄广播消息。
理想的处理方法应该是通过互斥体的实例获取句柄,但是我不知道怎么实现,谁有方案请指教。
参考引用文章:https://blog.gkarch.com/2015/07/single-instance-application.html
C#使用Mutex实现单例应用程序的更多相关文章
- DevExpress Winform使用单例运行程序方法和非DevExpress使用Mutex实现程序单实例运行且运行则激活窗体的方法
原文:DevExpress Winform使用单例运行程序方法和非DevExpress使用Mutex实现程序单实例运行且运行则激活窗体的方法 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA ...
- qt 共享内存 单例
QT 进程间通信之古老的方法(内存共享) 让QT只运行一个实例 以上两篇文章中分别讲述了QSharedMemory的不同作用,第一篇讲了进程间通信,第二篇讲述了怎么让应用程序只 ...
- 四大传值详解:属性传值,单例传值,代理传值,block传值
一:属性传值 传值情景:从前一个页面向后一个页面传值 a.在后一个页面,根据传值类型和个数,写属性 b.在前一个页面, 为属性赋值 c.在后一个页面, 使用值 例如: 第一个视图: #import & ...
- QT中实现应用程序的单例化
一介绍 通过编写一个QSingleApplication类,来实现Qt程序的单例化,原文的作者是在Windows Vista + Qt4.4 下实现的,不过应用在其他平台上是没问题的.(本文是我在ht ...
- 编写高质量代码改善C#程序的157个建议——建议107:区分静态类和单例
建议107:区分静态类和单例 有一种观点认为:静态类可以作为单件模式的一种实现方式.事实上,这是不妥当的.按照传统的观点来看,单例是一个实例对象.而静态类并不满足这一点.静态类也直接违反面向对象三大特 ...
- 编写高质量代码改善C#程序的157个建议——建议105:使用私有构造函数强化单例
建议105:使用私有构造函数强化单例 单例指一个类型只生成一个实例对象.单例的一个简单实现如下所示: static void Main(string[] args) { Singleton.Insta ...
- PHP实现程序单例执行
原创文章,转载请注明出处:http://huyanping.sinaapp.com/?p=222 作者:Jenner 一.场景描写叙述: 近期我们一块业务.须要不断的监听一个文件夹的变化.假设文件夹中 ...
- C#实现程序单例日志输出
对于一个完整的程序系统,一个日志记录是必不可少的.可以用它来记录程序在运行过程中的运行状态和报错信息.比如,那些不想通过弹框提示的错误,程序执行过程中捕获的异常等. 首先,在你的解决方案中,适当的目录 ...
- C#应用程序单例并激活程序的窗口 使其显示在最前端
public class SoftHelper { ///<summary> /// 该函数设置由不同线程产生的窗口的显示状态 /// </summary> /// <p ...
随机推荐
- 作业---修改haproxy配置文件
#查询 f=open("C:\\aaaaaaaaaaaaa\\haproxy.txt", "r", encoding="utf-8") ha ...
- bat文件与Vbs文件常用操作(获取用户输入,执行VBS文件)
bat文件: set /P StrInput="输入数字:" echo 输入的数字为%StrInput% set /P Flg="是否执行(y/n):" IF ...
- Spring的IOC原理
1. IoC理论的背景 我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑. 图1:软件系统中耦合的对象 如果我们打开机 ...
- leetcode70 爬楼梯 Python
组合数学Fibonacci 例3.4.1 (上楼梯问题)某人欲登上n级楼梯,若每次只能跨一级或两级,问他从地面上到第n级楼梯,共有多少种不同的方法? (解)设上到第n级楼梯的方法数为an.分类统计 ...
- JAVA的DES加密解密在windows上测试一切正常,在linux上异常
windows上加解密正常,linux上加密正常,解密时发生 如下异常,异常信息如下: [ERROR] 2018-10-15 09:30:35,998 method:com.iscas.ippc.co ...
- html的换行代码<br/>介绍和写法
在网页中,我们要对文字进行换行,就需要使用到<br/>标签,写法如下 换行:<br/> <br/>属于一个单独标签,仅需要将需要换行的文字后方加入此标签即可实现换行 ...
- Java定时器小实例
有时候,我们需要在Java中定义一个定时器来轮询操作,比如每隔一段时间查询.删除数据库中的某些数据等,下面记录一下一种简单实现方式 1,首先新建一个类,类中编写方法来实现业务操作 public cla ...
- Oracle中 to_date和to_char用法
to_date("要转换的字符串","转换的格式") 两个参数的格式必须匹配,否则会报错. 即按照第二个参数的格式解释第一个参数. to_char(日期,& ...
- CSS3之3D轮播图
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 微信公众号openid处理的一些笔记
每个用户对每个公众号的OpenID是唯一的.对于不同公众号,同一用户的openid不同.如果公司有多个公众号,可以通过开放平台关联,这样同一用户,对同一个微信开放平台下的不同应用,unionid是相同 ...