現代軟件普遍需要「在線升級」功能:用戶主動檢查新版本、下載更新包、自動/引導安裝。核心要求:用戶主動觸發(而非啟動時強制彈窗干擾)、輕量級版本信息獲取、靈活的下載安裝策略。
version.ini 至 Web 服務器(HTTP/FTP)
[Version]
Version=120 ; 版本號:1.2.0 → 120(整數便於比較)
BuildDate=20240115 ; 構建日期
MinVersion=100 ; 最低兼容版本
[Download]
URL=https://example.com/update/MyApp_v1.2.0_Setup.exe
Size=12582912 ; 文件大小(字節),可選
MD5=abc123... ; 校驗碼,可選
[Changelog]
; 更新日誌(可選,支持換行轉義)
Notes=1. 修復登錄崩潰問題\n2. 優化啟動速度\n3. 新增導出 PDF 功能
IDC_STATIC_VERSION:顯示當前版本/檢查狀態IDC_BTN_CHECK:「檢查更新」IDC_BTN_DOWNLOAD:「下載更新」(初始禁用)IDC_PROGRESS:可選,顯示下載進度
class CUpdateDlg : public CDialogEx
{
CString m_strCurrentVersion; // 當前版本,如 "1.1.0"
int m_nCurrentVerNum; // 數值版本,如 110
CString m_strDownloadURL; // 新版本下載地址
CString m_strChangeLog; // 更新日誌
// ...
};
void CUpdateDlg::OnBnClickedBtnCheck()
{
// 1. 界面反饋
SetDlgItemText(IDC_STATIC_VERSION, _T("正在連接服務器檢查更新..."));
GetDlgItem(IDC_BTN_DOWNLOAD)->EnableWindow(FALSE);
m_strDownloadURL.Empty();
m_strChangeLog.Empty();
// 2. 下載版本信息文件
CString strTempIni = GetTempIniPath(); // 如 %TEMP%\MyApp_version.ini
HRESULT hr = URLDownloadToFile(NULL, _T("https://example.com/version.ini"),
strTempIni, 0, NULL);
if (hr != S_OK)
{
SetDlgItemText(IDC_STATIC_VERSION, _T("網絡連接失敗,請檢查網絡設置。"));
return;
}
// 3. 解析 INI
int nNewVerNum = GetPrivateProfileInt(_T("Version"), _T("Version"), 0, strTempIni);
if (nNewVerNum <= 0)
{
SetDlgItemText(IDC_STATIC_VERSION, _T("版本信息格式錯誤。"));
DeleteFile(strTempIni);
return;
}
// 4. 版本比較
if (nNewVerNum > m_nCurrentVerNum)
{
// 有新版本
m_strDownloadURL = GetIniString(strTempIni, _T("Download"), _T("URL"));
m_strChangeLog = GetIniString(strTempIni, _T("Changelog"), _T("Notes"));
CString strMsg;
strMsg.Format(_T("發現新版本!\n當前版本:%s\n最新版本:%d.%d.%d\n\n是否查看更新詳情?"),
m_strCurrentVersion,
nNewVerNum / 100, (nNewVerNum % 100) / 10, nNewVerNum % 10);
SetDlgItemText(IDC_STATIC_VERSION, strMsg);
GetDlgItem(IDC_BTN_DOWNLOAD)->EnableWindow(TRUE);
}
else
{
SetDlgItemText(IDC_STATIC_VERSION, _T("當前已是最新版本。"));
}
DeleteFile(strTempIni); // 清理臨時文件
}
// 輔助:讀取 INI 字符串值
CString CUpdateDlg::GetIniString(LPCTSTR lpszFile, LPCTSTR lpszSection, LPCTSTR lpszKey)
{
TCHAR szBuf[1024] = {0};
GetPrivateProfileString(lpszSection, lpszKey, _T(""), szBuf, 1024, lpszFile);
return CString(szBuf);
}
// 獲取臨時 INI 路徑
CString CUpdateDlg::GetTempIniPath()
{
TCHAR szTemp[MAX_PATH];
GetTempPath(MAX_PATH, szTemp);
CString strPath = szTemp;
strPath += _T("MyApp_version.ini");
return strPath;
}
void CUpdateDlg::OnBnClickedBtnDownload()
{
if (m_strDownloadURL.IsEmpty()) return;
// 方案 A:啟動默認瀏覽器下載(最簡單,用戶熟悉)
ShellExecute(m_hWnd, _T("open"), m_strDownloadURL, NULL, NULL, SW_SHOWNORMAL);
// 方案 B:後台下載並自動安裝(需額外實現下載器、校驗、靜默安裝)
// 參考:WinINet / URLDownloadToFile + 進度回調 + 驗證簽名 + ShellExecute 安裝包 /msi /qn
// 記錄用戶主動更新行為(可選)
LogUpdateEvent(_T("User initiated download"));
}
若需啟動時靜默檢查(不彈窗),可在主框架 OnCreate 中啟動工作線程:
UINT CheckUpdateThread(LPVOID pParam)
{
CMainFrame* pFrame = (CMainFrame*)pParam;
// 執行檢查邏輯(不更新 UI,僅設置標誌/寫注冊表)
// 發現新版本時發送自定義消息 WM_USER_UPDATE_AVAILABLE 通知主線程
return 0;
}
// 主框架啟動
AfxBeginThread(CheckUpdateThread, this);
bsdiff、Courgette)InternetOpen 設置代理或使用 WinHttp 自動檢測 WPAD| 問題 | 原因 | 解決 |
|---|---|---|
URLDownloadToFile 返回 E_ACCESSDENIED | 權限不足/寫入目錄受保護 | 寫入 %TEMP% 目錄,以當前用戶權限運行 |
| 版本號比較錯誤(如 1.10 < 1.2) | 字符串比較而非數值比較 | 統一轉換為整數(主版本*10000 + 次版本*100 + 修訂號) |
| ShellExecute 無反應 | URL 協議關聯缺失/參數錯誤 | 使用 ShellExecuteEx 檢查返回值,確保 lpFile 為完整 URL |
| 防火牆攔截下載 | 應用無網絡訪問權限 | 數字簽名可執行文件,申請防火牆例外,或使用系統下載服務 |
本文提出的在線升級方案採用「HTTP + INI + ShellExecute」輕量級架構,核心優勢在於:用戶主動觸發避免干擾、INI 文本格式便於服務端動態生成、ShellExecute 利用系統瀏覽器/下載器無需自行實現複雜下載邏輯。對於專業級產品,建議在此基礎上增加 HTTPS、數字簽名、BITS 下載、增量補丁、靜默安裝等企業級特性,構建安全可靠的軟件分發管道。