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

C# 確保單一程式實例運行:Process 類別實作與最佳實踐

     編輯:關於C語言

問題場景

防止使用者重複啟動應用程式(如雙擊圖示多次),需在啟動時檢測是否已有相同程式實例在運行,若存在則將其視窗帶至前景並結束新啟動的實例。

核心思路

  1. 取得目前進程資訊(Process.GetCurrentProcess())。
  2. 依**進程名稱**查找所有同名進程(Process.GetProcessesByName)。
  3. 排除自身進程 ID。
  4. 比對**執行檔完整路徑**(Assembly.GetExecutingAssembly().Location vs process.MainModule.FileName),確保是同一份部署檔案(避免同名不同路徑誤判)。
  5. 若找到,回傳該 Process 實例;否則回傳 null。

完整實作程式碼

using System;
using System.Diagnostics;
using System.Reflection;
using System.Windows.Forms;

namespace SingleInstanceApp
{
    static class Program
    {
        /// 
        /// 應用程式主入口點
        /// 
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            // 1. 檢查是否已有實例運行
            Process existing = GetRunningInstance();
            if (existing != null)
            {
                // 2. 將既有視窗帶至前景並還原
                HandleRunningInstance(existing);
                return; // 結束新啟動的實例
            }

            // 3. 無既有實例,正常啟動
            Application.Run(new MainForm());
        }

        /// 
        /// 尋找相同路徑的同名進程(排除自身)
        /// 
        static Process GetRunningInstance()
        {
            Process current = Process.GetCurrentProcess();
            Process[] processes = Process.GetProcessesByName(current.ProcessName);

            // 目前組件的完整路徑(統一路徑分隔符)
            string currentPath = Assembly.GetExecutingAssembly().Location.Replace('/', '\\');

            foreach (Process p in processes)
            {
                if (p.Id == current.Id) continue; // 忽略自身

                try
                {
                    // 比對執行檔路徑(需處理權限例外)
                    if (p.MainModule.FileName.Replace('/', '\\') == currentPath)
                    {
                        return p; // 找到目標實例
                    }
                }
                catch (System.ComponentModel.Win32Exception)
                {
                    // 存取系統進程或權限不足時忽略
                }
            }
            return null;
        }

        /// 
        /// 將既有視窗置頂、還原、激活
        /// 
        static void HandleRunningInstance(Process instance)
        {
            IntPtr hWnd = instance.MainWindowHandle;
            if (hWnd != IntPtr.Zero)
            {
                // 還原最小化視窗
                if (IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE);
                // 置頂並激活
                SetForegroundWindow(hWnd);
            }
        }

        // P/Invoke 宣告
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);
        [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 IsIconic(IntPtr hWnd);
        private const int SW_RESTORE = 9;
    }
}

關鍵細節與避坑指南

細節說明
路徑比對正規化使用 .Replace('/', '\\') 統一分隔符,避免大小寫或分隔符差異導致誤判。
權限例外處理枚舉進程模組可能因權限不足拋出 Win32Exception(如系統進程),必須 try-catch 忽略。
視窗句柄可能為零若既有進程為無介面服務或尚未建立視窗,MainWindowHandle 為 IntPtr.Zero,需容錯。
多使用者 Session 隔離Terminal Server / 遠端桌面環境下,不同 Session 進程名稱相同但互不干擾,路徑比對通常足夠區分。
Mutex 替代方案更輕量、跨 Session 可用:new Mutex(true, "Global\\MyAppUniqueName", out createdNew)。若 !createdNew 則已有實例。但無法直接取得視窗句柄置頂,需配合 IPC 通訊。

進階:WPF / .NET Core 通用單一實例模式

.NET Core / .NET 5+ 建議使用 **`Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase`** 繼承並覆寫 `OnStartupNextInstance`,或引用 Microsoft.Toolkit.Mvvm / CommunityToolkit.Mvvm 中的 SingleInstanceApplication 輔助類,支援命令列參數傳遞給既有實例。

總結

使用 System.Diagnostics.Process 比對**進程名稱 + 完整路徑**是 WinForms 確保單一實例的經典做法。關鍵在於**路徑正規化、權限例外處理、視窗置頂 P/Invoke**。對於現代 .NET 應用,優先考慮 Mutex + IPC 或框架內建單一實例支援,架構更乾淨、跨平台相容性更好。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved