首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 防止程式重複啟動四大方案:Mutex、進程檢查、Win32 API 與系統托盤兼容

     編輯:關於C語言

核心邏輯:單一實例 = 進程唯一性或命名同步物件唯一性

防止重複啟動本質上是「檢測同一個應用程式是否已在運行」。主流三大思路:

  1. 命名 Mutex:作業系統層面的命名同步物件,跨進程可見,釋放自動,最推薦。
  2. 進程名稱掃描:Process.GetProcessesByName 計數或比對路徑。
  3. Win32 視窗尋找:FindWindow 定位已有視窗並喚醒,適合需「喚醒既有視窗」場景。

方案一:Mutex 單一實例模式 (標準寫法)

放在 Program.cs Main() 最開頭,using 區塊確保進程結束自動釋放。

static class Program
{
    [STAThread]
    static void Main()
    {
        bool createdNew;
        // 使用產品名稱作為 Mutex 名稱,確保唯一性
        using (var mutex = new Mutex(true, Application.ProductName, out createdNew))
        {
            if (createdNew)
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new MainForm());
            }
            else
            {
                MessageBox.Show("應用程式已在執行中...", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                // 可選:喚醒既有視窗 (需配合方案三/四)
            }
        }
    }
}

關鍵點:Mutex(true, name, out createdNew) 參數意義——initiallyOwned=true 申請擁有權、name 全系統唯一、createdNew 告知是否為首次建立。

方案二:進程掃描法 (簡單但有陷阱)

2.1 計數法

var procs = Process.GetProcessesByName(Application.CompanyName); // 或 ProcessName
if (procs.Length >= 1) { /* 已運行 */ }

⚠️ 缺陷:同名不同路徑程式會誤判;VS 偵錯時進程名為 xxx.vshost 導致誤判。

2.2 路徑比對法 (較嚴謹)

Process current = Process.GetCurrentProcess();
var procs = Process.GetProcessesByName(current.ProcessName.Replace(".vshost", ""));
foreach (var p in procs)
{
    if (p.Id != current.Id &&
        Assembly.GetExecutingAssembly().Location.Replace("/", "\\") == p.MainModule.FileName)
    {
        // 找到同路徑既有進程
        return p;
    }
}
return null;

方案三:Win32 API 喚醒視窗 (基礎版)

適用於「已有視窗最小化」情況,無法處理縮至系統托盤 (無視窗句柄)。

[DllImport("user32.dll")] static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")] static extern bool SetForegroundWindow(IntPtr hWnd);
const int SW_SHOWNORMAL = 1;

static void ActivateExisting(Process proc)
{
    ShowWindow(proc.MainWindowHandle, SW_SHOWNORMAL);
    SetForegroundWindow(proc.MainWindowHandle);
}

方案四:完美兼容系統托盤隱藏 (進階版)

當主視窗 Hide() 或縮至托盤時,MainWindowHandle 為 0。需改用 FindWindow 依類別名/標題尋找,再 SwitchToThisWindow 強制切換。

[DllImport("user32.dll")] static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")] static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
[DllImport("user32.dll")] static extern int ShowWindow(IntPtr hWnd, int nCmdShow);
const int SW_RESTORE = 9;

static void Main()
{
    Process current = Process.GetCurrentProcess();
    var procs = Process.GetProcessesByName(current.ProcessName.Replace(".vshost", ""));

    if (procs.Length > 1) // 含自己
    {
        foreach (var p in procs)
        {
            if (p.Id == current.Id) continue;

            IntPtr hWnd = p.MainWindowHandle;
            if (hWnd == IntPtr.Zero) // 可能隱藏/托盤
            {
                // 依視窗標題尋找 (需確保標題唯一)
                hWnd = FindWindow(null, "MainForm"); // 或類別名
            }

            if (hWnd != IntPtr.Zero)
            {
                ShowWindow(hWnd, SW_RESTORE); // 還原最小化/隱藏
                SwitchToThisWindow(hWnd, true); // 切換到前台
            }
        }
        return; // 結束新啟動的實例
    }

    // 首次啟動
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}

方案選型決策表

需求推薦方案備註
最簡單、標準、無需喚醒視窗方案一 Mutex官方推薦模式,代碼最少
需喚醒最小化視窗方案一 + 方案三Mutex 判斷 + Win32 喚醒
需喚醒托盤隱藏視窗方案四FindWindow + SwitchToThisWindow
無視覺介面 (服務/Console)方案一 Mutex同樣適用
跨使用者 Session 隔離Mutex 名稱加 Global\\ 前綴需管理員權限

避坑指南

  • VS 偵錯誤判:偵錯時進程名含 .vshost,正式發布無此問題;掃描法務必 Replace(".vshost", "")。
  • Mutex 名稱衝突:使用 Application.ProductName 或 Guid 確保唯一;加 Global\\ 可跨 Session 但需高權限。
  • 視窗標題非唯一:FindWindow 建議用類別名 (#32770 等) 或自訂 this.Text 保證唯一。
  • 管理員權限差異:以管理員啟動與否屬不同權限隔離,Mutex/進程互不可見,需統一啟動權限或用 Global\\。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved