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

实现的方式可能有判断进程名字,使用特殊文件等等,但是最靠谱的方式还是使用系统提供的 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. 工控随笔_12_西门子_WinCC的VBS脚本_03_变量类型

    说到编程语言,总是绕不开数据类型,因为数据类型决定了数据可以进行什么样的操作.同时数据类型 从广义上来说是一种数据结构,在过程式编程的过程中,曾经有过这样一种说法: 程序 = 数据结构 + 算法 可见 ...

  2. win10 solidity开发环境搭建

    1. 软件安装 1) 安装nodejs 安装完成后将node.exe所在路径加入环境变量PATH中,以便在cmd命令行中直接使用node和npm命令 下面的操作在git bash下进行 2) 安装so ...

  3. Ubuntu平台rm误删的文件如何恢复

    安装:Ubuntu下也可以直接用apt-get来获取extundelete 以我自己的Ubuntu14.04.3来看: df 命令是linux系统上以磁盘分区为单位来查看文件系统的命令,后面可以加上不 ...

  4. Python量化分析,计算KDJ

    Python: v3.6 Pandas: v0.23.4 使用以下方法计算与国内财经软件显示一致 low_list = df['最低价'].rolling(9, min_periods=9).min( ...

  5. Python读取文件内容与存储

    Python读取与存储文件内容 一..csv文件 读取: import pandas as pd souce_data = pd.read_csv(File_Path) 其中File_path是文件的 ...

  6. 【Windows 7】发现一个奇怪的现象

    最近在Windows7-32位操作系统上发现一个奇怪的现象,不知道64位操作系统上会不会发生这个现象.这个现象就是:如果系统上的一个或多个账户没有设置密码,那么在此条件下终止winlogon.exe进 ...

  7. VMware12 安装 Mac OS 10.12 步骤及设置优化教程

    最近公司要开发苹果的ARKit应用,但是项目组穷啊,只有美工用着一台苹果本本,所以肯定不能老用他的电脑,效率低还老打扰他.所以我就想着用虚拟机整,毕竟玩了N年的虚拟机了,应该是没啥问题的.所以就开始各 ...

  8. win 10 在vs2017下对mpi的安装以及认识

    这里我先对MPI进行一下简单的介绍,MPI的全称是Message Passing Interface,即消息传递接口. 它并不是一门语言,而是一个库,我们可以用Fortran.C.C++结合MPI提供 ...

  9. 设计一函数,求整数区间[a,b]和[c,d]的交集

    问题: 设计一函数,求整数区间[a,b]和[c,d]的交集.(c/c++.Java.Javascript.C#.Python)  1.Python: def calcMixed(a,b,c,d): r ...

  10. centos7配置网易yum源

    部分参考centos6: https://blog.csdn.net/jinzhencs/article/details/53673999 下载yum:  https://www.cnblogs.co ...