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 ...
随机推荐
- RAM和Flash区别
都是随机存储器,断电数据消失,但Flash有点不一样,它在消失数据之前,添加了一个""性质",这个性质能上电后再识别,且把这个信号返回到ram中,这样近似的把flash当 ...
- LeetCode——17. Letter Combinations of a Phone Number
一.题目链接: https://leetcode.com/problems/letter-combinations-of-a-phone-number/ 二.题目大意: 给定一段数字字符串,其中每个数 ...
- ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (13)解答
我在使用mysqll客户端连接我的mysql服务器的时候,出现了上述的问题.我的操作系统是ubuntu,安装版本是对应的64位服务器.我的服务器的启动方式是sudo service mysql sta ...
- java-消息中间件-基于内存的mq
如果用户的请求比较费时,可以考虑将用户的请求信息放到队列中,立即返回给用户处理中等信息,这样可以给用户比较流畅的体验,后端可以利用单独的服务消费消息,做到了解耦,提高了并发能力. 本文使用jdk为我们 ...
- ORA-27302: 错误发生在: sskgpwrm1
opidrv aborting process M002 ospid (3561) as a result of ORA-600ORA-27300: 操作系统相关操作: semctl 失败, 状态为: ...
- Quick Search Articles in My Blog
=== Quickly Search Articles in My Blog: === 本文介绍了如何快速在主流搜索引擎搜索本专栏内文章的方法. Use Google's Search : pres ...
- 学习excel的使用技巧统计文本出现的次数
其实是使用一个函数来完成的 =ifcount(C1:C214,''test123") 统计c1到c214中出现的test123的次数
- 彻底搞懂Scrapy的中间件(一)
中间件是Scrapy里面的一个核心概念.使用中间件可以在爬虫的请求发起之前或者请求返回之后对数据进行定制化修改,从而开发出适应不同情况的爬虫. "中间件"这个中文名字和前面章节讲到 ...
- (11)ssh免密登录配置
***在Linux命令行中登录到另一台虚拟机(需要用到ssh协议) Linux中默认有ssh的服务器端和客户端,客户端的名字就叫ssh 前提是当前使用的用户名在待连接的虚拟机中存在 格式: ssh ...
- JavaScript:鼠标拖拽效果
(之前的那个模板方法模式实在没搞懂...等几天再去研究8) 预览效果: 限制拖动范围在视口内.调整窗口时自动居中... <!DOCTYPE html> <html lang=&quo ...