防止应用多开-WPF

互斥锁实现阻止应用多开。

以下是WPF程序

csharp 复制代码
using System.Configuration;
using System.Data;
using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private static Mutex? _appMutex;

        protected override void OnStartup(StartupEventArgs e)
        {
            const string mutexName = "Local\\{YOUR-UNIQUE-GUID}_WpfApp1_Singleton";
            bool createdNew = false;

            try
            {
                _appMutex = new Mutex(true, mutexName, out createdNew);
            }
            catch (UnauthorizedAccessException)
            {
                Shutdown();
                return;
            }

            if (!createdNew)
            {
                Shutdown();
                return;
            }

            base.OnStartup(e);
        }

        protected override void OnExit(ExitEventArgs e)
        {
            _appMutex?.Dispose();
            base.OnExit(e);
        }
    }
}

启动第一个实例之后,第二个就不能再次启动。

细节

代码很简单,就是一个锁而已。但这里面有很多的细节。

启动两次应用程序是两个进程,而代码中除了初始化锁之外,没有加锁的操作 以及释放锁的操作?这是如何保证只能启动一个实例的哪?
为什么放在App类里面 而不是放在其他的里面
private static Mutex? _appMutex; 为什么要加static
除了使用锁,信号量 事件可以实现同样的功能吗
Local\{YOUR-UNIQUE-GUID}_WpfApp1_Singleton Local有什么用?
锁使用了内核,不通过内核,能实现吗? 锁是内核层的实现,需要从.net托管到native,再到内核层 然后再原路返回 性能相对较差

复制代码
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
using System.Windows;
using System.Windows.Interop;

namespace WpfApp1
{
    public partial class App : Application
    {
        // 1. 定义全局唯一标识(替换为自己的GUID)
        private const string UniqueAppGuid = "6B29FC40-CA47-1067-B31D-00DD010662DA";
        private const string MmfName = $"Local\\CAS_Singleton_{UniqueAppGuid}";
        private MemoryMappedFile? _mmf;
        private MemoryMappedViewAccessor? _viewAccessor;

        // 2. 原子标记的偏移量(共享内存中存储一个int值:0=空闲,1=占用)
        private const int FlagOffset = 0;

        protected override unsafe void OnStartup(StartupEventArgs e)
        {
            try
            {
                // 3. 创建/打开共享内存(4字节足够存储int标记)
                _mmf = MemoryMappedFile.CreateOrOpen(MmfName, 4, MemoryMappedFileAccess.ReadWrite);
                _viewAccessor = _mmf.CreateViewAccessor(0, 4, MemoryMappedFileAccess.ReadWrite);

                // 4. CAS原子操作:尝试将标记从0改为1
                int expected = 0;
                int desired = 1;

                byte* pointer = null;
                _viewAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
                try
                {
                    // pointer 是视图起始处的字节指针,FlagOffset 为字节偏移
                    int* intPtr = (int*)(pointer + FlagOffset);

                    // 使用 Interlocked 在内存映射区域上进行 CAS
                    int original = Interlocked.CompareExchange(ref *intPtr, desired, expected);

                    if (original != 0)
                    {
                        // CAS失败:已有实例运行,激活已有实例并退出
                        ActivateExistingInstance();
                        CleanupResources();
                        Shutdown();
                        return;
                    }
                }
                finally
                {
                    _viewAccessor.SafeMemoryMappedViewHandle.ReleasePointer();
                }

                // CAS成功:当前是第一个实例,正常启动
                base.OnStartup(e);
            }
            catch (UnauthorizedAccessException)
            {
                // 无权限访问共享内存(已有实例占用)
                ActivateExistingInstance();
                Shutdown();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"启动失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                CleanupResources();
                Shutdown();
            }
        }

        /// <summary>
        /// 激活已有实例窗口(可选,提升用户体验)
        /// </summary>
        private void ActivateExistingInstance()
        {
            // 结合窗口API查找并激活已有实例(复用之前的窗口激活逻辑)
            const int SW_RESTORE = 9;
            IntPtr hwnd = FindWindowByClassNameOrTitle(UniqueAppGuid);
            if (hwnd != IntPtr.Zero)
            {
                ShowWindow(hwnd, SW_RESTORE);
                SetForegroundWindow(hwnd);
            }
        }

        protected override unsafe void OnExit(ExitEventArgs e)
        {
            // 5. 程序退出时:CAS还原标记为0,释放共享内存
            if (_viewAccessor != null)
            {
                byte* pointer = null;
                _viewAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
                try
                {
                    int* intPtr = (int*)(pointer + FlagOffset);
                    Interlocked.Exchange(ref *intPtr, 0);
                }
                finally
                {
                    _viewAccessor.SafeMemoryMappedViewHandle.ReleasePointer();
                }
            }

            CleanupResources();
            base.OnExit(e);
        }

        /// <summary>
        /// 释放共享内存资源
        /// </summary>
        private void CleanupResources()
        {
            _viewAccessor?.Dispose();
            _mmf?.Dispose();
        }

        // ========== 辅助API(窗口操作) ==========
        [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
        private static extern IntPtr FindWindow(string? lpClassName, string lpWindowName);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);

        /// <summary>
        /// 根据唯一标识查找窗口
        /// </summary>
        private IntPtr FindWindowByClassNameOrTitle(string uniqueId)
        {
            // 给MainWindow设置标题包含uniqueId,便于查找
            return FindWindow(null, $"WpfApp1_MainWindow_{uniqueId}");
        }
    }
}

这段代可修改性如何 如果换方案哪? 不止有这一种方案。

相关推荐
运维行者_7 小时前
企业无线网络监控的挑战与智能化演进趋势
大数据·运维·服务器·网络·数据库
三8448 小时前
文件查找/文件压缩/解压缩
linux·运维·服务器
小猪写代码8 小时前
Linux 管道(Pipeline)作业
linux·运维·服务器
桌面运维家8 小时前
如何用半缓存云桌面将服务器硬盘容量扩展至本地终端?
运维·服务器·缓存
Jurio.8 小时前
Codex App SSH 远程开发教程:本地连接远程服务器项目
服务器·ssh·远程工作·codex
会周易的程序员10 小时前
microLog 的本地日志读取接口 log_reader — 本地日志文件读取工具开发指南
linux·物联网·架构·嵌入式·日志·iot·aiot
无心水11 小时前
【全域智能营销实战】2、Spring AI 模块化架构深度解读:从 1.0 到 2.0 的演进与最佳实践
人工智能·spring·架构·harness·顶尖架构师·全域智能营销·harmess
北域码匠11 小时前
嵌入式限幅滤波:工业信号降噪利器
c#·传感器采集·数据预处理·嵌入式算法·限幅滤波·数字滤波·数据降噪
tryCbest11 小时前
Python 文件操作
服务器·python
HavenlonLabs11 小时前
Havenlon 对抗性完整(十七):安全不是“防住攻击”,而是控制失败方式
网络·人工智能·架构·安全威胁分析·安全架构·havenlon