不少应用程序有单一实例的需求,也就是同时只能开启一个实例(一般也就是一个进程)。

实现的方式可能有判断进程名字,使用特殊文件等等,但是最靠谱的方式还是使用系统提供的 Mutex 工具。

Mutex是互斥体,命名的互斥体可以跨进程使用,所以可以用以实现程序单一实例这个需求。相关的例子网上应该不少,不过很多给出的例子中并没有注意到一些细节,这里就完整总结下。

Mutex 需要一个名字,这个名字需要唯一,一般的方式是使用一个固定的 GUID 作为名字。

对于 .NET 应用,可以通过 Assembly 上的GuidAttribute来获取。默认情况下建立工程的时候 VS 就会生成一个 GUID 给 Assembly,这样无需自己再生成一个 GUID 来使用。

另外,为了调试方面,最好给 GUID 加一个便于人识别的前缀,一般就是程序的名字。这样使用一些查看系统对象的工具时,可以方便找到这个 Mutex。

一般在程序启动的代码中进行判断,判断的方式是使用 Mutex 上的WaitOne方法。但是有两点需要注意:

  1. 程序异常退出,WaitOne 会抛出AbandonedMutexException异常,需要处理。
  2. 如果程序使用了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实现单例应用程序的更多相关文章

  1. DevExpress Winform使用单例运行程序方法和非DevExpress使用Mutex实现程序单实例运行且运行则激活窗体的方法

    原文:DevExpress Winform使用单例运行程序方法和非DevExpress使用Mutex实现程序单实例运行且运行则激活窗体的方法 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA ...

  2. qt 共享内存 单例

        QT 进程间通信之古老的方法(内存共享)     让QT只运行一个实例     以上两篇文章中分别讲述了QSharedMemory的不同作用,第一篇讲了进程间通信,第二篇讲述了怎么让应用程序只 ...

  3. 四大传值详解:属性传值,单例传值,代理传值,block传值

    一:属性传值 传值情景:从前一个页面向后一个页面传值 a.在后一个页面,根据传值类型和个数,写属性 b.在前一个页面, 为属性赋值 c.在后一个页面, 使用值 例如: 第一个视图: #import & ...

  4. QT中实现应用程序的单例化

    一介绍 通过编写一个QSingleApplication类,来实现Qt程序的单例化,原文的作者是在Windows Vista + Qt4.4 下实现的,不过应用在其他平台上是没问题的.(本文是我在ht ...

  5. 编写高质量代码改善C#程序的157个建议——建议107:区分静态类和单例

    建议107:区分静态类和单例 有一种观点认为:静态类可以作为单件模式的一种实现方式.事实上,这是不妥当的.按照传统的观点来看,单例是一个实例对象.而静态类并不满足这一点.静态类也直接违反面向对象三大特 ...

  6. 编写高质量代码改善C#程序的157个建议——建议105:使用私有构造函数强化单例

    建议105:使用私有构造函数强化单例 单例指一个类型只生成一个实例对象.单例的一个简单实现如下所示: static void Main(string[] args) { Singleton.Insta ...

  7. PHP实现程序单例执行

    原创文章,转载请注明出处:http://huyanping.sinaapp.com/?p=222 作者:Jenner 一.场景描写叙述: 近期我们一块业务.须要不断的监听一个文件夹的变化.假设文件夹中 ...

  8. C#实现程序单例日志输出

    对于一个完整的程序系统,一个日志记录是必不可少的.可以用它来记录程序在运行过程中的运行状态和报错信息.比如,那些不想通过弹框提示的错误,程序执行过程中捕获的异常等. 首先,在你的解决方案中,适当的目录 ...

  9. C#应用程序单例并激活程序的窗口 使其显示在最前端

    public class SoftHelper { ///<summary> /// 该函数设置由不同线程产生的窗口的显示状态 /// </summary> /// <p ...

随机推荐

  1. 从已删除邮箱copy数据到活动邮箱

    Start Windows PowerShell Start > search for "PowerShell" > Start Windows PowerShell ...

  2. (转)Android之发送短信的两种方式

    https://www.cnblogs.com/dongweiq/p/4866022.html if(TextUtils.isEmpty(number)||TextUtils.isEmpty(cont ...

  3. 7.2.5 多层嵌套的if语句

    7.2.5 多层嵌套的if语句 在编写程序的代码之前要先规划好.首先,要总体设计一下程序. 为方便起见,程序应该使用一个连续的循环让用户能连续输入待测试的 数.这样,测试一个新的数字不必每次都要重新运 ...

  4. windows环境下ElasticSearch6 安装head插件

    转: https://blog.csdn.net/shubingzhuoxue/article/details/80998738 https://blog.csdn.net/camelcanoe/ar ...

  5. [UE4]AttachToComponent的AttachmentRule

    官方文档 KeepRelative 将当前相对转换保持为新父级的相对转换 KeepWorld 自动计算相对变换,使附着的组件保持相同的世界变换 SnapToTarget 捕捉转换到附着点

  6. ASP.NET Core 1.1版本之Hello word

    1.下载ASP.NET Core 1.1版本,并且安装. 2.新建一个工作文件夹,本文以WebApiFrame名称为例建立一个新的文件夹: mk WebApiFrame 3.启动命令行,在命令行中进入 ...

  7. Java 工程师成神之路 | 2019正式版

    本文为转载,原文见以下链接:https://mp.weixin.qq.com/s/4AMzq87V6eW3YPgE0mCdSw 1 基础篇 01 面向对象 → 什么是面向对象 面向对象.面向过程 面向 ...

  8. Redis深入学习笔记(三)RDB及AOF流程

    RDB是Redis持久化数据的一种方式,是执行时间点的Redis内存快照,redis数据还原时加载rdb文件,Redis的主从数据同步也是基于RDB实现的. RDB流程: 1)执行bgsave命令,R ...

  9. Garbage-Only-One的IO路

    我的任务计划 刷题计划 BZOJ 1.1202 2.1008 3.等等 搜索 1.搜索题单 学习计划 树 1.树状数组or树状数组or树状数组 2.线段树 搜索 1.A*

  10. python数据类型、if判断语句

    python的数据类型: int(整型) float(浮点型) #相较c++,去除了char.long.longlong... str(字符串)    #同等c++ sting类型 list(列表) ...