在 C++ 應用開發中,程序自我重啟是常見需求(如配置生效、插件更新、錯誤恢復、權限提升)。本文介紹一種基於 CreateProcess 在主入口點實現的優雅重啟方案,通過全局標誌位協調舊進程退出與新進程啟動,確保無縫切換。
g_bIsRunAgain = trueWinMain 尾部CreateProcess 啟動自身新實例
// Project1.cpp
#include "Unit1.h"
bool g_bIsRunAgain = false; // 全局重啟標誌
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
try {
Application->Initialize();
Application->CreateForm(__classid(TfrmMain), &frmMain);
Application->Run();
}
catch (Exception &exception) {
Application->ShowException(&exception);
}
catch (...) {
try { throw Exception(""); }
catch (Exception &exception) {
Application->ShowException(&exception);
}
}
// 關鍵:重啟邏輯
if (g_bIsRunAgain) {
AnsiString strPath = Application->ExeName; // 獲取自身完整路徑
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi;
// 創建新進程,優先級正常
if (CreateProcess(
(LPCTSTR)strPath.c_str(), // 應用程序名
NULL, // 命令行參數
NULL, NULL, // 進程/線程安全屬性
FALSE, // 不繼承句柄
NORMAL_PRIORITY_CLASS, // 優先級
NULL, NULL, // 環境塊/當前目錄
&si, &pi)) { // 啟動信息/進程信息
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
}
return 0;
}
// Unit1.h
extern bool g_bIsRunAgain; // 聲明全局標誌
class TfrmMain : public TForm {
// ...
void __fastcall btnCloseClick(TObject *Sender); // 關閉按鈕
void __fastcall btnReExcuteClick(TObject *Sender); // 重啟按鈕
};
// Unit1.cpp
#include "Unit1.h"
bool g_bIsRunAgain = false; // 定義全局標誌
void __fastcall TfrmMain::btnCloseClick(TObject *Sender) {
Close(); // 正常關閉窗口
}
void __fastcall TfrmMain::btnReExcuteClick(TObject *Sender) {
g_bIsRunAgain = true; // 設置重啟標誌
Close(); // 觸發關閉流程
}
| 關鍵點 | 說明 |
|---|---|
| Application->ExeName | 獲取當前可執行文件完整路徑,包含路徑中的空格也能正確處理 |
| CreateProcess 參數 | 第一參數為應用程序名,第二參數為命令行(NULL 時使用第一參數) |
| STARTUPINFO 初始化 | 必須設置 cb = sizeof(STARTUPINFO),否則調用失敗 |
| 句柄清理 | 調用成功後必須 CloseHandle 進程/線程句柄,避免洩漏 |
| 優先級類 | NORMAL_PRIORITY_CLASS 標準優先級,亦可用 HIGH_PRIORITY_CLASS |
| 工作目錄 | NULL 表示繼承調用進程工作目錄,通常為程序所在目錄 |
if (g_bIsRunAgain) {
AnsiString strPath = Application->ExeName;
AnsiString strParams = ""; // 收集需傳遞的參數
// 例如:strParams = "--restarted --config=new.cfg";
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi;
// 使用命令行參數版本
AnsiString cmdLine = strPath + " " + strParams;
if (CreateProcess(NULL, (LPSTR)cmdLine.c_str(), NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) {
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
}
// WinMain 開頭
HANDLE hMutex = CreateMutex(NULL, TRUE, "Global\\MyApp_Restart_Mutex");
if (GetLastError() == ERROR_ALREADY_EXISTS) {
// 已有實例運行,可選:激活舊窗口、傳遞參數給舊實例、或直接退出
CloseHandle(hMutex);
return 0;
}
// ... 正常運行 ...
// 退出前
ReleaseMutex(hMutex);
CloseHandle(hMutex);
if (g_bIsRunAgain && NeedAdminRights()) {
SHELLEXECUTEINFO sei = { sizeof(sei) };
sei.lpVerb = "runas"; // 關鍵:請求管理員權限
sei.lpFile = Application->ExeName.c_str();
sei.lpParameters = "--restarted";
sei.nShow = SW_SHOWNORMAL;
ShellExecuteEx(&sei);
return 0; // 當前進程退出
}
// 重啟前保存狀態
void __fastcall TfrmMain::btnReExcuteClick(TObject *Sender) {
// 保存窗口位置、大小、用戶數據等
TIniFile* ini = new TIniFile(ChangeFileExt(Application->ExeName, ".ini"));
ini->WriteInteger("Window", "Left", Left);
ini->WriteInteger("Window", "Top", Top);
ini->WriteInteger("Window", "Width", Width);
ini->WriteInteger("Window", "Height", Height);
// 保存其他業務狀態...
delete ini;
g_bIsRunAgain = true;
Close();
}
// 新進程啟動時恢復
// 在 FormCreate 或 FormShow 中讀取 ini 恢復狀態
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 重啟後出現兩個進程 | 舊進程未完全退出前新進程已啟動 | 確保 CreateProcess 在 Application->Run() 返回後調用,舊進程即將退出 |
| CreateProcess 失敗返回 0 | 路徑含空格未加引號、權限不足、路徑錯誤 | 使用 GetModuleFileName 獲取路徑,或 Application->ExeName;檢查文件存在性 |
| 重啟後工作目錄改變 | 快捷方式/關聯啟動導致工作目錄非程序目錄 | CreateProcess 第 8 參數傳遞程序目錄:ExtractFilePath(Application->ExeName).c_str() |
| 參數傳遞失敗 | 命令行解析錯誤 | 使用 GetCommandLine + CommandLineToArgvW 解析,或約定 INI/註冊表傳遞複雜數據 |
基於 WinMain 尾部檢測全局標誌調用 CreateProcess 的自我重啟方案,具有實現簡單、無需額外啟動器、參數傳遞靈活、易於擴展(UAC提升、狀態保存、互斥體保護)等優點。關鍵在於:標誌位設置時機、STARTUPINFO 正確初始化、進程句柄及時關閉、工作目錄與參數正確傳遞。結合具體業務場景選擇基礎版或進階版,即可實現穩定可靠的程序自我重啟功能。