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

VC++ 實現單一應用程式實例:互斥體與視窗尋找雙重保障

     編輯:VC 教學

為什麼需要單一實例?

  • 獨佔硬體資源 (聲卡、序列埠、USB 設備)
  • 避免設定檔衝突、資料庫連線衝突
  • 使用者體驗:點擊捷徑切換到已開啟視窗,而非開啟新視窗

方法一:視窗尋找法 (適用於有 GUI 的 MFC 程式)

原理:CWnd::FindWindow 尋找特定類別名稱或標題的視窗。32 位元 Windows 下 hPrevInstance 永為 NULL,不可用。

核心代碼

BOOL CMyApp::FirstInstance() {
    CWnd* pPrev = CWnd::FindWindow(NULL, _T("MyAppTitle")); // 以標題尋找
    if (pPrev) {
        // 找到既有視窗
        CWnd* pPopup = pPrev->GetLastActivePopup(); // 取得最上層彈出視窗
        if (pPrev->IsIconic())                      // 若最小化則還原
            pPrev->ShowWindow(SW_RESTORE);
        pPopup->SetForegroundWindow();              // 置前顯示
        return FALSE;                               // 非首次實例
    }
    return TRUE; // 首次實例
}

BOOL CMyApp::InitInstance() {
    if (!FirstInstance()) return FALSE; // 直接退出
    // ... 正常初始化 ...
}

關鍵 API 對照

函數用途
FindWindow依類別名/標題尋找頂層視窗
GetLastActivePopup取得視窗擁有的最後啟動彈出視窗
IsIconic判斷是否最小化
ShowWindow(SW_RESTORE)還原視窗大小位置並啟動
SetForegroundWindow將視窗帶到最前端並取得焦點

方法二:具名互斥體法 (通用、支援無 GUI 程式)

原理:CreateMutex 建立全系統唯一命名互斥體。若已存在同名互斥體,GetLastError() == ERROR_ALREADY_EXISTS。

Win32 SDK 純 C 版本

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int) {
    const char* APP_MUTEX = "Global\\MyUniqueAppMutex_12345678"; // Global\ 可跨 Session
    HANDLE hMutex = CreateMutex(NULL, FALSE, APP_MUTEX);
    if (hMutex) {
        if (GetLastError() == ERROR_ALREADY_EXISTS) {
            MessageBox(NULL, "程式已在執行中!", "提示", MB_OK | MB_ICONINFORMATION);
            CloseHandle(hMutex);
            return 1;
        }
        // 首次實例,繼續執行...
        MessageBox(NULL, "首次啟動", "提示", MB_OK);
    }
    // 程式結束前釋放
    if (hMutex) CloseHandle(hMutex);
    return 0;
}

MFC 版本 (在 CWinApp::InitInstance 最前端)

BOOL CMyApp::InitInstance() {
    const TCHAR* MUTEX_NAME = _T("Global\\MyApp_{GUID_HERE}");
    m_hMutex = CreateMutex(NULL, FALSE, MUTEX_NAME);
    if (GetLastError() == ERROR_ALREADY_EXISTS) {
        // 找到既有視窗並置前 (輔助視窗法)
        CWnd* pWnd = CWnd::FindWindow(NULL, _T("MyAppTitle"));
        if (pWnd) {
            CWnd* pPopup = pWnd->GetLastActivePopup();
            if (pWnd->IsIconic()) pWnd->ShowWindow(SW_RESTORE);
            pPopup->SetForegroundWindow();
        }
        CloseHandle(m_hMutex);
        return FALSE;
    }
    // ... 正常初始化 ...
    return TRUE;
}

int CMyApp::ExitInstance() {
    if (m_hMutex) CloseHandle(m_hMutex);
    return CWinApp::ExitInstance();
}

方法三:DLL 共享資料段法 (舊技術,了解即可)

在 DLL 中使用 #pragma data_seg(".shared") 定義共享變數計數實例數。缺點:需額外 DLL、安全性較差 (任何進程可修改)、不支援 ASLR。現代開發不推薦。

最佳實踐建議

  1. 雙重保障:互斥體防重複啟動 + 視窗尋找置前既有視窗
  2. 互斥體命名:使用 Global\\ 前綴確保跨 Session (終端機服務/RDP) 唯一,並加入 GUID 避免衝突
  3. 命令列參數傳遞:第二次啟動時,將參數傳給第一個實例 (WM_COPYDATA 或命名管道),實現「關聯檔案雙擊切換到主程式」
  4. 數位簽章:配合單一實例,防止惡意程式偽裝同名互斥體阻礙啟動

Delphi 版本參考

program OnlyOne;
uses Windows, Forms, uMain in 'uMain.pas' {FormMain};
{$R *.res}
var hMutex: THandle;
begin
  hMutex := CreateMutex(nil, False, 'Global\\MyDelphiApp_UniqueName');
  if (hMutex = 0) or (GetLastError = ERROR_ALREADY_EXISTS) then begin
    MessageBox(0, '程式已在執行中!', '提示', MB_OK + MB_ICONINFORMATION);
    if hMutex <> 0 then CloseHandle(hMutex);
    Exit;
  end;
  Application.Initialize;
  Application.CreateForm(TFormMain, FormMain);
  Application.Run;
  CloseHandle(hMutex);
end.

掌握互斥體 + 視窗尋找雙重機制,您的 VC++ 程式即擁有專業級單一實例保護能力。

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