防止使用者重複啟動應用程式(如雙擊圖示多次),需在啟動時檢測是否已有相同程式實例在運行,若存在則將其視窗帶至前景並結束新啟動的實例。
Process.GetCurrentProcess())。Process.GetProcessesByName)。Assembly.GetExecutingAssembly().Location vs process.MainModule.FileName),確保是同一份部署檔案(避免同名不同路徑誤判)。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 通訊。 |
.NET Core / .NET 5+ 建議使用 **`Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase`** 繼承並覆寫 `OnStartupNextInstance`,或引用 Microsoft.Toolkit.Mvvm / CommunityToolkit.Mvvm 中的 SingleInstanceApplication 輔助類,支援命令列參數傳遞給既有實例。
使用 System.Diagnostics.Process 比對**進程名稱 + 完整路徑**是 WinForms 確保單一實例的經典做法。關鍵在於**路徑正規化、權限例外處理、視窗置頂 P/Invoke**。對於現代 .NET 應用,優先考慮 Mutex + IPC 或框架內建單一實例支援,架構更乾淨、跨平台相容性更好。